move translate_offset_with_edit to ra_editor

This commit is contained in:
Bernardo 2018-12-18 18:46:54 +01:00
parent 881c29192d
commit 8c9df62c1c
7 changed files with 268 additions and 311 deletions

2
Cargo.lock generated
View file

@ -723,6 +723,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)", "itertools 0.7.11 (registry+https://github.com/rust-lang/crates.io-index)",
"join_to_string 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", "join_to_string 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
"proptest 0.8.7 (registry+https://github.com/rust-lang/crates.io-index)",
"ra_syntax 0.1.0", "ra_syntax 0.1.0",
"ra_text_edit 0.1.0", "ra_text_edit 0.1.0",
"rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -763,7 +764,6 @@ dependencies = [
"languageserver-types 0.53.0 (registry+https://github.com/rust-lang/crates.io-index)", "languageserver-types 0.53.0 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"parking_lot 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "parking_lot 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
"proptest 0.8.7 (registry+https://github.com/rust-lang/crates.io-index)",
"ra_analysis 0.1.0", "ra_analysis 0.1.0",
"ra_editor 0.1.0", "ra_editor 0.1.0",
"ra_syntax 0.1.0", "ra_syntax 0.1.0",

View file

@ -14,5 +14,7 @@ rustc-hash = "1.0"
ra_syntax = { path = "../ra_syntax" } ra_syntax = { path = "../ra_syntax" }
ra_text_edit = { path = "../ra_text_edit" } ra_text_edit = { path = "../ra_text_edit" }
proptest = "0.8.7"
[dev-dependencies] [dev-dependencies]
test_utils = { path = "../test_utils" } test_utils = { path = "../test_utils" }

View file

@ -2,6 +2,7 @@ mod code_actions;
mod extend_selection; mod extend_selection;
mod folding_ranges; mod folding_ranges;
mod line_index; mod line_index;
mod line_index_utils;
mod symbols; mod symbols;
#[cfg(test)] #[cfg(test)]
mod test_utils; mod test_utils;
@ -12,6 +13,7 @@ pub use self::{
extend_selection::extend_selection, extend_selection::extend_selection,
folding_ranges::{folding_ranges, Fold, FoldKind}, folding_ranges::{folding_ranges, Fold, FoldKind},
line_index::{LineCol, LineIndex}, line_index::{LineCol, LineIndex},
line_index_utils::translate_offset_with_edit,
symbols::{file_structure, file_symbols, FileSymbol, StructureNode}, symbols::{file_structure, file_symbols, FileSymbol, StructureNode},
typing::{join_lines, on_enter, on_eq_typed}, typing::{join_lines, on_enter, on_eq_typed},
}; };

View file

@ -1,4 +1,4 @@
use crate::{TextUnit, TextRange}; use crate::TextUnit;
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use superslice::Ext; use superslice::Ext;
@ -121,7 +121,7 @@ impl LineIndex {
col col
} }
pub fn newlines(&self) -> &[TextUnit] { pub(crate) fn newlines(&self) -> &[TextUnit] {
&self.newlines[1..] &self.newlines[1..]
} }
} }

View file

@ -0,0 +1,260 @@
use ra_text_edit::{AtomTextEdit};
use ra_syntax::{TextUnit, TextRange};
use crate::{LineIndex, LineCol};
#[derive(Debug)]
struct OffsetNewlineIter<'a> {
text: &'a str,
offset: TextUnit,
}
impl<'a> Iterator for OffsetNewlineIter<'a> {
type Item = TextUnit;
fn next(&mut self) -> Option<TextUnit> {
let next_idx = self
.text
.char_indices()
.filter_map(|(i, c)| if c == '\n' { Some(i + 1) } else { None })
.next()?;
let next = self.offset + TextUnit::from_usize(next_idx);
self.text = &self.text[next_idx..];
self.offset = next;
Some(next)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum TranslatedPos {
Before,
After,
}
/// None means it was deleted
type TranslatedOffset = Option<(TranslatedPos, TextUnit)>;
fn translate_offset(offset: TextUnit, edit: &TranslatedAtomEdit) -> TranslatedOffset {
if offset <= edit.delete.start() {
Some((TranslatedPos::Before, offset))
} else if offset <= edit.delete.end() {
None
} else {
let diff = edit.insert.len() as i64 - edit.delete.len().to_usize() as i64;
let after = TextUnit::from((offset.to_usize() as i64 + diff) as u32);
Some((TranslatedPos::After, after))
}
}
trait TranslatedNewlineIterator {
fn translate(&self, offset: TextUnit) -> TextUnit;
fn translate_range(&self, range: TextRange) -> TextRange {
TextRange::from_to(self.translate(range.start()), self.translate(range.end()))
}
fn next_translated(&mut self) -> Option<TextUnit>;
fn boxed<'a>(self) -> Box<TranslatedNewlineIterator + 'a>
where
Self: 'a + Sized,
{
Box::new(self)
}
}
struct TranslatedAtomEdit<'a> {
delete: TextRange,
insert: &'a str,
}
struct TranslatedNewlines<'a, T: TranslatedNewlineIterator> {
inner: T,
next_inner: Option<TranslatedOffset>,
edit: TranslatedAtomEdit<'a>,
insert: OffsetNewlineIter<'a>,
}
impl<'a, T: TranslatedNewlineIterator> TranslatedNewlines<'a, T> {
fn from(inner: T, edit: &'a AtomTextEdit) -> Self {
let delete = inner.translate_range(edit.delete);
let mut res = TranslatedNewlines {
inner,
next_inner: None,
edit: TranslatedAtomEdit {
delete,
insert: &edit.insert,
},
insert: OffsetNewlineIter {
offset: delete.start(),
text: &edit.insert,
},
};
// prepare next_inner
res.advance_inner();
res
}
fn advance_inner(&mut self) {
self.next_inner = self
.inner
.next_translated()
.map(|x| translate_offset(x, &self.edit));
}
}
impl<'a, T: TranslatedNewlineIterator> TranslatedNewlineIterator for TranslatedNewlines<'a, T> {
fn translate(&self, offset: TextUnit) -> TextUnit {
let offset = self.inner.translate(offset);
let (_, offset) =
translate_offset(offset, &self.edit).expect("translate_unit returned None");
offset
}
fn next_translated(&mut self) -> Option<TextUnit> {
match self.next_inner {
None => self.insert.next(),
Some(next) => match next {
None => self.insert.next().or_else(|| {
self.advance_inner();
self.next_translated()
}),
Some((TranslatedPos::Before, next)) => {
self.advance_inner();
Some(next)
}
Some((TranslatedPos::After, next)) => self.insert.next().or_else(|| {
self.advance_inner();
Some(next)
}),
},
}
}
}
impl<'a> Iterator for Box<dyn TranslatedNewlineIterator + 'a> {
type Item = TextUnit;
fn next(&mut self) -> Option<TextUnit> {
self.next_translated()
}
}
impl<T: TranslatedNewlineIterator + ?Sized> TranslatedNewlineIterator for Box<T> {
fn translate(&self, offset: TextUnit) -> TextUnit {
self.as_ref().translate(offset)
}
fn next_translated(&mut self) -> Option<TextUnit> {
self.as_mut().next_translated()
}
}
struct IteratorWrapper<T: Iterator<Item = TextUnit>>(T);
impl<T: Iterator<Item = TextUnit>> TranslatedNewlineIterator for IteratorWrapper<T> {
fn translate(&self, offset: TextUnit) -> TextUnit {
offset
}
fn next_translated(&mut self) -> Option<TextUnit> {
self.0.next()
}
}
impl<T: Iterator<Item = TextUnit>> Iterator for IteratorWrapper<T> {
type Item = TextUnit;
fn next(&mut self) -> Option<TextUnit> {
self.0.next()
}
}
fn translate_newlines<'a>(
mut newlines: Box<TranslatedNewlineIterator + 'a>,
edits: &'a [AtomTextEdit],
) -> Box<TranslatedNewlineIterator + 'a> {
for edit in edits {
newlines = TranslatedNewlines::from(newlines, edit).boxed();
}
newlines
}
pub fn translate_offset_with_edit(
pre_edit_index: &LineIndex,
offset: TextUnit,
edits: &[AtomTextEdit],
) -> LineCol {
let mut newlines: Box<TranslatedNewlineIterator> = Box::new(IteratorWrapper(
pre_edit_index.newlines().iter().map(|x| *x),
));
newlines = translate_newlines(newlines, edits);
let mut line = 0;
for n in newlines {
if n > offset {
break;
}
line += 1;
}
LineCol {
line: line,
col_utf16: 0, // TODO not implemented yet
}
}
#[cfg(test)]
mod test {
use proptest::{prelude::*, proptest, proptest_helper};
use super::*;
use ra_text_edit::test_utils::{arb_text, arb_offset, arb_edits};
#[derive(Debug)]
struct ArbTextWithOffsetAndEdits {
text: String,
offset: TextUnit,
edits: Vec<AtomTextEdit>,
}
fn arb_text_with_offset_and_edits() -> BoxedStrategy<ArbTextWithOffsetAndEdits> {
arb_text()
.prop_flat_map(|text| {
(arb_offset(&text), arb_edits(&text), Just(text)).prop_map(
|(offset, edits, text)| ArbTextWithOffsetAndEdits {
text,
offset,
edits,
},
)
})
.boxed()
}
fn edit_text(pre_edit_text: &str, mut edits: Vec<AtomTextEdit>) -> String {
// apply edits ordered from last to first
// since they should not overlap we can just use start()
edits.sort_by_key(|x| -(x.delete.start().to_usize() as isize));
let mut text = pre_edit_text.to_owned();
for edit in &edits {
let range = edit.delete.start().to_usize()..edit.delete.end().to_usize();
text.replace_range(range, &edit.insert);
}
text
}
fn translate_after_edit(
pre_edit_text: &str,
offset: TextUnit,
edits: Vec<AtomTextEdit>,
) -> LineCol {
let text = edit_text(pre_edit_text, edits);
let line_index = LineIndex::new(&text);
line_index.line_col(offset)
}
proptest! {
#[test]
fn test_translate_offset_with_edit(x in arb_text_with_offset_and_edits()) {
let line_index = LineIndex::new(&x.text);
let expected = translate_after_edit(&x.text, x.offset, x.edits.clone());
let actual = translate_offset_with_edit(&line_index, x.offset, &x.edits);
assert_eq!(actual.line, expected.line);
}
}
}

View file

@ -34,8 +34,6 @@ ra_analysis = { path = "../ra_analysis" }
gen_lsp_server = { path = "../gen_lsp_server" } gen_lsp_server = { path = "../gen_lsp_server" }
ra_vfs = { path = "../ra_vfs" } ra_vfs = { path = "../ra_vfs" }
proptest = "0.8.7"
[dev-dependencies] [dev-dependencies]
tempdir = "0.3.7" tempdir = "0.3.7"
test_utils = { path = "../test_utils" } test_utils = { path = "../test_utils" }

View file

@ -296,202 +296,6 @@ fn translate_offset_with_edit(
} }
} }
#[derive(Debug)]
struct OffsetNewlineIter<'a> {
text: &'a str,
offset: TextUnit,
}
impl<'a> Iterator for OffsetNewlineIter<'a> {
type Item = TextUnit;
fn next(&mut self) -> Option<TextUnit> {
let next_idx = self
.text
.char_indices()
.filter_map(|(i, c)| if c == '\n' { Some(i + 1) } else { None })
.next()?;
let next = self.offset + TextUnit::from_usize(next_idx);
self.text = &self.text[next_idx..];
self.offset = next;
Some(next)
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum TranslatedPos {
Before,
After,
}
/// None means it was deleted
type TranslatedOffset = Option<(TranslatedPos, TextUnit)>;
fn translate_offset(offset: TextUnit, edit: &TranslatedAtomEdit) -> TranslatedOffset {
if offset <= edit.delete.start() {
Some((TranslatedPos::Before, offset))
} else if offset <= edit.delete.end() {
None
} else {
let diff = edit.insert.len() as i64 - edit.delete.len().to_usize() as i64;
let after = TextUnit::from((offset.to_usize() as i64 + diff) as u32);
Some((TranslatedPos::After, after))
}
}
trait TranslatedNewlineIterator {
fn translate(&self, offset: TextUnit) -> TextUnit;
fn translate_range(&self, range: TextRange) -> TextRange {
TextRange::from_to(self.translate(range.start()), self.translate(range.end()))
}
fn next_translated(&mut self) -> Option<TextUnit>;
fn boxed<'a>(self) -> Box<TranslatedNewlineIterator + 'a>
where
Self: 'a + Sized,
{
Box::new(self)
}
}
struct TranslatedAtomEdit<'a> {
delete: TextRange,
insert: &'a str,
}
struct TranslatedNewlines<'a, T: TranslatedNewlineIterator> {
inner: T,
next_inner: Option<TranslatedOffset>,
edit: TranslatedAtomEdit<'a>,
insert: OffsetNewlineIter<'a>,
}
impl<'a, T: TranslatedNewlineIterator> TranslatedNewlines<'a, T> {
fn from(inner: T, edit: &'a AtomTextEdit) -> Self {
let delete = inner.translate_range(edit.delete);
let mut res = TranslatedNewlines {
inner,
next_inner: None,
edit: TranslatedAtomEdit {
delete,
insert: &edit.insert,
},
insert: OffsetNewlineIter {
offset: delete.start(),
text: &edit.insert,
},
};
// prepare next_inner
res.advance_inner();
res
}
fn advance_inner(&mut self) {
self.next_inner = self
.inner
.next_translated()
.map(|x| translate_offset(x, &self.edit));
}
}
impl<'a, T: TranslatedNewlineIterator> TranslatedNewlineIterator for TranslatedNewlines<'a, T> {
fn translate(&self, offset: TextUnit) -> TextUnit {
let offset = self.inner.translate(offset);
let (_, offset) =
translate_offset(offset, &self.edit).expect("translate_unit returned None");
offset
}
fn next_translated(&mut self) -> Option<TextUnit> {
match self.next_inner {
None => self.insert.next(),
Some(next) => match next {
None => self.insert.next().or_else(|| {
self.advance_inner();
self.next_translated()
}),
Some((TranslatedPos::Before, next)) => {
self.advance_inner();
Some(next)
}
Some((TranslatedPos::After, next)) => self.insert.next().or_else(|| {
self.advance_inner();
Some(next)
}),
},
}
}
}
impl<'a> Iterator for Box<dyn TranslatedNewlineIterator + 'a> {
type Item = TextUnit;
fn next(&mut self) -> Option<TextUnit> {
self.next_translated()
}
}
impl<T: TranslatedNewlineIterator + ?Sized> TranslatedNewlineIterator for Box<T> {
fn translate(&self, offset: TextUnit) -> TextUnit {
self.as_ref().translate(offset)
}
fn next_translated(&mut self) -> Option<TextUnit> {
self.as_mut().next_translated()
}
}
struct IteratorWrapper<T: Iterator<Item = TextUnit>>(T);
impl<T: Iterator<Item = TextUnit>> TranslatedNewlineIterator for IteratorWrapper<T> {
fn translate(&self, offset: TextUnit) -> TextUnit {
offset
}
fn next_translated(&mut self) -> Option<TextUnit> {
self.0.next()
}
}
impl<T: Iterator<Item = TextUnit>> Iterator for IteratorWrapper<T> {
type Item = TextUnit;
fn next(&mut self) -> Option<TextUnit> {
self.0.next()
}
}
fn translate_newlines<'a>(
mut newlines: Box<TranslatedNewlineIterator + 'a>,
edits: &'a [AtomTextEdit],
) -> Box<TranslatedNewlineIterator + 'a> {
for edit in edits {
newlines = TranslatedNewlines::from(newlines, edit).boxed();
}
newlines
}
#[allow(dead_code)]
fn translate_offset_with_edit_fast(
pre_edit_index: &LineIndex,
offset: TextUnit,
edits: &[AtomTextEdit],
) -> LineCol {
// println!("{:?}", pre_edit_index.newlines());
let mut newlines: Box<TranslatedNewlineIterator> = Box::new(IteratorWrapper(
pre_edit_index.newlines().iter().map(|x| *x),
));
newlines = translate_newlines(newlines, edits);
let mut line = 0;
for n in newlines {
if n > offset {
break;
}
line += 1;
}
LineCol {
line: line,
col_utf16: 0,
}
}
impl TryConvWith for SourceFileEdit { impl TryConvWith for SourceFileEdit {
type Ctx = ServerWorld; type Ctx = ServerWorld;
type Output = TextDocumentEdit; type Output = TextDocumentEdit;
@ -582,112 +386,3 @@ where
self.iter.next().map(|item| item.conv_with(self.ctx)) self.iter.next().map(|item| item.conv_with(self.ctx))
} }
} }
#[cfg(test)]
mod test {
use proptest::{prelude::*, proptest, proptest_helper};
use super::*;
use ra_text_edit::test_utils::{arb_text, arb_offset, arb_edits};
#[derive(Debug)]
struct ArbTextWithOffsetAndEdits {
text: String,
offset: TextUnit,
edits: Vec<AtomTextEdit>,
}
fn arb_text_with_offset_and_edits() -> BoxedStrategy<ArbTextWithOffsetAndEdits> {
arb_text()
.prop_flat_map(|text| {
(arb_offset(&text), arb_edits(&text), Just(text)).prop_map(
|(offset, edits, text)| ArbTextWithOffsetAndEdits {
text,
offset,
edits,
},
)
})
.boxed()
}
fn edit_text(pre_edit_text: &str, mut edits: Vec<AtomTextEdit>) -> String {
// apply edits ordered from last to first
// since they should not overlap we can just use start()
edits.sort_by_key(|x| -(x.delete.start().to_usize() as isize));
let mut text = pre_edit_text.to_owned();
for edit in &edits {
let range = edit.delete.start().to_usize()..edit.delete.end().to_usize();
text.replace_range(range, &edit.insert);
}
text
}
fn translate_after_edit(
pre_edit_text: &str,
offset: TextUnit,
edits: Vec<AtomTextEdit>,
) -> LineCol {
let text = edit_text(pre_edit_text, edits);
let line_index = LineIndex::new(&text);
line_index.line_col(offset)
}
proptest! {
#[test]
fn test_translate_offset_with_edit(x in arb_text_with_offset_and_edits()) {
let line_index = LineIndex::new(&x.text);
let expected = translate_after_edit(&x.text, x.offset, x.edits.clone());
let actual = translate_offset_with_edit_fast(&line_index, x.offset, &x.edits);
assert_eq!(actual.line, expected.line);
}
}
#[test]
fn test_translate_offset_with_edit_1() {
let x = ArbTextWithOffsetAndEdits {
text: "jbnan".to_owned(),
offset: 3.into(),
edits: vec![
AtomTextEdit::delete(TextRange::from_to(1.into(), 3.into())),
AtomTextEdit::insert(4.into(), "\n".into()),
],
};
let line_index = LineIndex::new(&x.text);
let expected = translate_after_edit(&x.text, x.offset, x.edits.clone());
let actual = translate_offset_with_edit_fast(&line_index, x.offset, &x.edits);
// assert_eq!(actual, expected);
assert_eq!(actual.line, expected.line);
}
#[test]
fn test_translate_offset_with_edit_2() {
let x = ArbTextWithOffsetAndEdits {
text: "aa\n".to_owned(),
offset: 1.into(),
edits: vec![AtomTextEdit::delete(TextRange::from_to(0.into(), 2.into()))],
};
let line_index = LineIndex::new(&x.text);
let expected = translate_after_edit(&x.text, x.offset, x.edits.clone());
let actual = translate_offset_with_edit_fast(&line_index, x.offset, &x.edits);
// assert_eq!(actual, expected);
assert_eq!(actual.line, expected.line);
}
#[test]
fn test_translate_offset_with_edit_3() {
let x = ArbTextWithOffsetAndEdits {
text: "".to_owned(),
offset: 0.into(),
edits: vec![AtomTextEdit::insert(0.into(), "\n".into())],
};
let line_index = LineIndex::new(&x.text);
let expected = translate_after_edit(&x.text, x.offset, x.edits.clone());
let actual = translate_offset_with_edit_fast(&line_index, x.offset, &x.edits);
// assert_eq!(actual, expected);
assert_eq!(actual.line, expected.line);
}
}