use core::fmt;
use crate::parse::Style;
use crate::syntax::Parsed;
use crate::syntax::SyntaxElement;
use crate::syntax::SyntaxKind;
use crate::syntax::SyntaxNode;
use crate::syntax::SyntaxToken;
use crate::text::TextRange;
use crate::text::TextSize;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum EditError {
Overlap {
a: TextRange,
b: TextRange,
},
OutOfBounds {
range: TextRange,
},
}
impl fmt::Display for EditError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Overlap { a, b } => write!(f, "overlapping edits at {a} and {b}"),
Self::OutOfBounds { range } => write!(f, "edit range {range} is out of bounds"),
}
}
}
impl std::error::Error for EditError {}
#[derive(Debug, Clone)]
struct SpliceEdit {
range: TextRange,
text: String,
}
#[derive(Debug)]
pub struct Edits<'a> {
parsed: &'a Parsed,
edits: Vec<SpliceEdit>,
}
impl Parsed {
pub fn edit(&self) -> Edits<'_> {
Edits {
parsed: self,
edits: Vec::new(),
}
}
}
impl<'a> Edits<'a> {
pub fn parsed(&self) -> &'a Parsed {
self.parsed
}
pub fn replace(&mut self, range: TextRange, text: impl Into<String>) -> &mut Self {
self.edits.push(SpliceEdit {
range,
text: text.into(),
});
self
}
pub fn insert(&mut self, at: TextSize, text: impl Into<String>) -> &mut Self {
self.replace(TextRange::new(at, at), text)
}
pub fn delete(&mut self, range: TextRange) -> &mut Self {
self.replace(range, String::new())
}
pub fn replace_node(&mut self, node: &SyntaxNode, text: impl Into<String>) -> &mut Self {
self.replace(*node.range(), text)
}
pub fn replace_token(&mut self, token: &SyntaxToken, text: impl Into<String>) -> &mut Self {
self.replace(*token.range(), text)
}
pub fn remove_lines(&mut self, node: &SyntaxNode) -> &mut Self {
let source = self.parsed.source();
let bytes = source.as_bytes();
let mut start = usize::from(node.range().start());
let mut end = usize::from(node.range().end());
if start > end || end > bytes.len() || !source.is_char_boundary(start) || !source.is_char_boundary(end) {
return self.delete(*node.range());
}
let line_start = source[..start].rfind('\n').map_or(0, |i| i + 1);
if bytes[line_start..start].iter().all(|&b| b == b' ' || b == b'\t') {
start = line_start;
}
let mut scan = end;
while scan < bytes.len() && (bytes[scan] == b' ' || bytes[scan] == b'\t') {
scan += 1;
}
if scan + 1 < bytes.len() && bytes[scan] == b'\r' && bytes[scan + 1] == b'\n' {
end = scan + 2;
} else if scan < bytes.len() && bytes[scan] == b'\n' {
end = scan + 1;
} else if scan == bytes.len() {
end = scan;
}
if let Some(blank_end) = blank_line_end_at(self.parsed.root(), TextSize::from(end)) {
end = usize::from(blank_end);
}
self.delete(TextRange::new(TextSize::from(start), TextSize::from(end)))
}
pub fn apply(&self) -> Result<String, EditError> {
let source = self.parsed.source();
for edit in &self.edits {
let start = usize::from(edit.range.start());
let end = usize::from(edit.range.end());
if start > end || end > source.len() || !source.is_char_boundary(start) || !source.is_char_boundary(end) {
return Err(EditError::OutOfBounds { range: edit.range });
}
}
let mut order: Vec<usize> = (0..self.edits.len()).collect();
order.sort_by_key(|&i| (self.edits[i].range.start(), self.edits[i].range.end()));
for pair in order.windows(2) {
let a = self.edits[pair[0]].range;
let b = self.edits[pair[1]].range;
if a.end() > b.start() {
return Err(EditError::Overlap { a, b });
}
}
let mut out = String::with_capacity(source.len());
let mut pos = 0usize;
for &i in &order {
let edit = &self.edits[i];
out.push_str(&source[pos..usize::from(edit.range.start())]);
out.push_str(&edit.text);
pos = usize::from(edit.range.end());
}
out.push_str(&source[pos..]);
Ok(out)
}
pub fn apply_reparsed(&self) -> Result<Parsed, EditError> {
let text = self.apply()?;
Ok(match self.parsed.style() {
Style::NumPy => crate::parse::numpy::parse_numpy(&text),
Style::Google => crate::parse::google::parse_google(&text),
Style::Plain => crate::parse::plain::parse_plain(&text),
})
}
}
fn blank_line_end_at(node: &SyntaxNode, at: TextSize) -> Option<TextSize> {
for child in node.children() {
match child {
SyntaxElement::Token(t) => {
if t.kind() == SyntaxKind::BLANK_LINE && t.range().start() == at {
return Some(t.range().end());
}
}
SyntaxElement::Node(n) => {
if let Some(end) = blank_line_end_at(n, at) {
return Some(end);
}
}
}
}
None
}