tgqe 0.0.2

The Great Qin Empire —— A centralized system for integrating and summarizing common compiler front-end library errors and span error output libraries, with optional Ariadne integration.
/**

 *  - Copyright (c) 星灿长风v(Starwindv) 2026/08/03
 *  - License: BSD-3
 *  - Author: 星灿长风v(StarWindv)
 *  - Location: tgqe/src/modules/impls/reader.rs
 */
use crate::base_types::{
    TgqeLine, TgqeLinesIter, TgqeReader,
    TgqeSourceCache,
};
use crate::singletons::SourceManager;
use std::fs::File;
use std::io;
use std::io::{BufRead, BufReader};

impl TgqeReader {
    pub fn store_file(
        filepath: &str,
    ) -> io::Result<bool> {
        let file = File::open(filepath)?;
        let mut reader =
            BufReader::new(file);

        let mut line_no: u32 = 0;
        let mut offset: u32 = 0;
        let mut full_text = String::new();

        loop {
            let mut buf = String::new();
            if reader.read_line(&mut buf)?
                == 0
            {
                break;
            }

            line_no += 1;
            if line_no > i16::MAX as u32 {
                return Err(
                    io::Error::new(
                        io::ErrorKind::Other,
                        "Source file exceeds 32,767 lines.
Your file is longer than a 16-bit integer can count.
We suggest splitting it for the sake of both our error
reporter and your colleagues' sanity.
"
                    )
                );
            }

            full_text.push_str(&buf);

            let code = buf
                .trim_end_matches([
                    '\r', '\n',
                ])
                .to_string();

            SourceManager
                .lock()
                .unwrap()
                .push_line_at(
                    filepath,
                    line_no as i16,
                    offset,
                    code,
                );

            offset += buf.len() as u32;
        }

        SourceManager
            .lock()
            .unwrap()
            .set_text(filepath, full_text);

        Ok(true)
    }
}

impl TgqeSourceCache {
    pub(crate) fn new() -> Self {
        Self {
            source: Default::default(),
            line_index: Default::default(),
            text: Default::default(),
            cursor: None,
        }
    }

    pub(crate) fn set_text(
        &mut self,
        filepath: &str,
        text: String,
    ) {
        self.text.insert(
            filepath.to_string(),
            text,
        );
    }

    pub(crate) fn push_line_at(
        &mut self,
        filepath: &str,
        line: i16,
        start_offset: u32,
        code: String,
    ) {
        self.source
            .entry(filepath.to_string())
            .or_default()
            .insert(line, code);

        let index = self
            .line_index
            .entry(filepath.to_string())
            .or_default();
        let pos = index
            .binary_search_by_key(
                &start_offset,
                |&(off, _)| off,
            )
            .unwrap_or_else(|pos| pos);
        index.insert(
            pos,
            (start_offset, line),
        );
    }

    pub fn iter_line(
        &self,
    ) -> TgqeLinesIter {
        let mut lines: Vec<TgqeLine> =
            Vec::new();

        for (filepath, sub_map) in
            &self.source
        {
            for (&line, code) in sub_map {
                lines.push(TgqeLine::new(
                    filepath,
                    line,
                    code.clone(),
                ));
            }
        }

        lines.sort_by(|a, b| {
            a.filepath
                .cmp(&b.filepath)
                .then(a.line.cmp(&b.line))
        });

        TgqeLinesIter::new(lines)
    }

    pub fn source_text(
        &self,
        filepath: &str,
    ) -> Option<String> {
        if let Some(text) =
            self.text.get(filepath)
        {
            return Some(text.clone());
        }

        let lines =
            self.source.get(filepath)?;
        let max_line =
            *lines.keys().max()?;
        let mut text = String::new();
        for line in 1..=max_line {
            if line > 1 {
                text.push('\n');
            }
            if let Some(code) =
                lines.get(&line)
            {
                text.push_str(code);
            }
        }
        Some(text)
    }

    pub fn source_window(
        &self,
        filepath: &str,
        start_offset: u32,
        end_offset: u32,
    ) -> Option<(String, usize, usize, usize)>
    {
        let lines =
            self.source.get(filepath)?;
        let index =
            self.line_index.get(filepath)?;
        if index.is_empty() {
            return None;
        }

        let start_idx = index
            .partition_point(|&(off, _)| {
                off <= start_offset
            })
            .saturating_sub(1);
        let end_idx = index
            .partition_point(|&(off, _)| {
                off <= end_offset
            })
            .saturating_sub(1)
            .max(start_idx);

        let (base, first_line) =
            index[start_idx];
        let (_, last_line) = index[end_idx];

        let mut text = String::new();
        for line in first_line..=last_line {
            if line > first_line {
                text.push('\n');
            }
            if let Some(code) =
                lines.get(&line)
            {
                text.push_str(code);
            }
        }

        let rel_start = start_offset
            .saturating_sub(base)
            .min(text.len() as u32)
            as usize;
        let rel_end = (end_offset
            .saturating_sub(base)
            .min(text.len() as u32)
            as usize)
            .max(rel_start);

        Some((
            text,
            rel_start,
            rel_end,
            (first_line - 1) as usize,
        ))
    }
}