squawk-syntax 2.66.0

Linter for Postgres migrations & SQL
Documentation
use rowan::{TextRange, TextSize};

#[derive(Debug)]
pub struct DecodedText {
    text: String,
    marks: Vec<Mark>,
}

#[derive(Debug)]
struct Mark {
    decoded: u32,
    pos: u32,
}

impl DecodedText {
    pub fn new(pos: TextSize) -> Self {
        Self {
            text: String::new(),
            marks: vec![Mark {
                decoded: 0,
                pos: pos.into(),
            }],
        }
    }

    pub fn text(&self) -> &str {
        &self.text
    }

    pub fn into_text(self) -> String {
        self.text
    }

    pub fn push_str(&mut self, text: &str, pos: TextSize) {
        self.sync(pos);
        self.text.push_str(text);
    }

    pub fn push_char(&mut self, c: char, pos: TextSize) {
        self.sync(pos);
        self.text.push(c);
    }

    pub fn mark_end(&mut self, pos: TextSize) {
        self.sync(pos);
    }

    fn sync(&mut self, pos: TextSize) {
        let decoded = self.text.len() as u32;
        let pos = pos.into();
        match self.marks.last_mut() {
            Some(mark) if mark.decoded == decoded => mark.pos = pos,
            Some(mark) if mark.pos + (decoded - mark.decoded) == pos => (),
            _ => self.marks.push(Mark { decoded, pos }),
        }
    }

    pub fn source_pos(&self, offset: TextSize) -> TextSize {
        let offset = u32::from(offset);
        let idx = self.marks.partition_point(|mark| mark.decoded <= offset);
        let mark = &self.marks[idx.saturating_sub(1)];
        TextSize::new(mark.pos + offset.saturating_sub(mark.decoded))
    }

    pub fn source_range(&self, range: TextRange) -> TextRange {
        TextRange::new(self.source_pos(range.start()), self.source_pos(range.end()))
    }
}