package-json-lsp 0.1.0

Language server for package manager catalog references in package.json
Documentation
use tower_lsp::lsp_types::{Position, Range, TextDocumentContentChangeEvent, Url};

#[derive(Clone, Debug)]
pub struct Document {
    pub uri: Url,
    pub version: i32,
    text: String,
}

impl Document {
    pub fn new(uri: Url, version: i32, text: String) -> Self {
        Self { uri, version, text }
    }

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

    pub fn apply_changes(&mut self, changes: Vec<TextDocumentContentChangeEvent>) {
        for change in changes {
            if let Some(range) = change.range {
                let start = self.offset_at(range.start);
                let end = self.offset_at(range.end);
                if start <= end && end <= self.text.len() {
                    self.text.replace_range(start..end, &change.text);
                }
            } else {
                self.text = change.text;
            }
        }
    }

    pub fn range_from_byte_range(&self, start: usize, end: usize) -> Range {
        Range {
            start: self.position_at(start),
            end: self.position_at(end),
        }
    }

    pub fn position_at(&self, offset: usize) -> Position {
        let target = offset.min(self.text.len());
        let mut line = 0_u32;
        let mut character = 0_u32;

        for (idx, ch) in self.text.char_indices() {
            if idx >= target {
                break;
            }
            if ch == '\n' {
                line += 1;
                character = 0;
            } else {
                character += ch.len_utf16() as u32;
            }
        }

        Position { line, character }
    }

    pub fn offset_at(&self, position: Position) -> usize {
        let mut line = 0_u32;
        let mut character = 0_u32;

        for (idx, ch) in self.text.char_indices() {
            if line == position.line && character >= position.character {
                return idx;
            }
            if ch == '\n' {
                if line == position.line {
                    return idx;
                }
                line += 1;
                character = 0;
            } else {
                character += ch.len_utf16() as u32;
            }
        }

        self.text.len()
    }
}

pub fn position_in_range(position: Position, range: Range) -> bool {
    if position.line < range.start.line || position.line > range.end.line {
        return false;
    }
    if position.line == range.start.line && position.character < range.start.character {
        return false;
    }
    if position.line == range.end.line && position.character > range.end.character {
        return false;
    }
    true
}