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,
))
}
}