1#![allow(missing_docs)]
2
3use std::ops::Index;
4use std::path::{Path, PathBuf};
5
6#[derive(Default, Clone, PartialEq, Eq, Debug)]
7pub struct Files {
8 pub file_names: Vec<String>,
12
13 pub file_texts: Vec<String>,
17
18 pub file_line_maps: Vec<LineMap>,
22}
23
24#[derive(Default, Clone, PartialEq, Eq, Debug)]
25pub struct LineMap {
26 line_ends: Vec<usize>,
28}
29
30impl Index<usize> for LineMap {
31 type Output = usize;
32
33 fn index(&self, index: usize) -> &Self::Output {
34 &self.line_ends[index]
35 }
36}
37
38impl LineMap {
39 pub fn from_str(text: &str) -> Self {
40 let line_ends = text.match_indices('\n').map(|(i, _)| i + 1).collect();
41 Self { line_ends }
42 }
43
44 pub fn line(&self, pos: usize) -> usize {
46 self.line_ends.partition_point(|&end| end <= pos)
47 }
48
49 pub fn get(&self, line: usize) -> Option<&usize> {
51 self.line_ends.get(line)
52 }
53}
54
55impl Files {
56 pub fn from_paths<P: AsRef<Path>>(
57 paths: impl IntoIterator<Item = P>,
58 ) -> Result<Self, (PathBuf, std::io::Error)> {
59 let mut file_names = Vec::new();
60 let mut file_texts = Vec::new();
61 let mut file_line_maps = Vec::new();
62
63 for path in paths {
64 let path = path.as_ref();
65 let contents =
66 std::fs::read_to_string(path).map_err(|err| (path.to_path_buf(), err))?;
67 let name = path.display().to_string();
68
69 file_line_maps.push(LineMap::from_str(&contents));
70 file_names.push(name);
71 file_texts.push(contents);
72 }
73
74 Ok(Self {
75 file_names,
76 file_texts,
77 file_line_maps,
78 })
79 }
80
81 pub fn from_names_and_contents(files: impl IntoIterator<Item = (String, String)>) -> Self {
82 let mut file_names = Vec::new();
83 let mut file_texts = Vec::new();
84 let mut file_line_maps = Vec::new();
85
86 for (name, contents) in files {
87 file_line_maps.push(LineMap::from_str(&contents));
88 file_names.push(name);
89 file_texts.push(contents);
90 }
91
92 Self {
93 file_names,
94 file_texts,
95 file_line_maps,
96 }
97 }
98
99 pub fn file_name(&self, file: usize) -> Option<&str> {
100 self.file_names.get(file).map(|x| x.as_str())
101 }
102
103 pub fn file_text(&self, file: usize) -> Option<&str> {
104 self.file_texts.get(file).map(|x| x.as_str())
105 }
106
107 pub fn file_line_map(&self, file: usize) -> Option<&LineMap> {
108 self.file_line_maps.get(file)
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
117 fn line_map() {
118 let line_map = LineMap::from_str("");
119 assert_eq!(line_map.line_ends, &[]);
120 assert_eq!(line_map.line(0), 0);
121 assert_eq!(line_map.line(100), 0);
122
123 let line_map = LineMap::from_str("line 0");
124 assert_eq!(line_map.line_ends, &[]);
125 assert_eq!(line_map.line(0), 0);
126 assert_eq!(line_map.line(100), 0);
127
128 let line_map = LineMap::from_str("line 0\nline 1");
129 assert_eq!(line_map.line_ends, &[7]);
130 assert_eq!(line_map.line(0), 0);
131 assert_eq!(line_map.line(100), 1);
132 }
133}