1 //! An example that shows how to implement a simple custom file database.
2 //! The database uses 32-bit file-ids, which could be useful for optimizing
3 //! memory usage.
4 //!
5 //! To run this example, execute the following command from the top level of
6 //! this repository:
7 //!
8 //! ```sh
9 //! cargo run --example custom_files
10 //! ```
11
12 use codespan_reporting::diagnostic::{Diagnostic, Label};
13 use codespan_reporting::term;
14 use codespan_reporting::term::termcolor::{ColorChoice, StandardStream};
15 use std::ops::Range;
16
main() -> anyhow::Result<()>17 fn main() -> anyhow::Result<()> {
18 let mut files = files::Files::new();
19
20 let file_id0 = files.add("0.greeting", "hello world!").unwrap();
21 let file_id1 = files.add("1.greeting", "bye world").unwrap();
22
23 let messages = vec![
24 Message::UnwantedGreetings {
25 greetings: vec![(file_id0, 0..5), (file_id1, 0..3)],
26 },
27 Message::OverTheTopExclamations {
28 exclamations: vec![(file_id0, 11..12)],
29 },
30 ];
31
32 let writer = StandardStream::stderr(ColorChoice::Always);
33 let config = term::Config::default();
34 for message in &messages {
35 let writer = &mut writer.lock();
36 term::emit(writer, &config, &files, &message.to_diagnostic())?;
37 }
38
39 Ok(())
40 }
41
42 /// A module containing the file implementation
43 mod files {
44 use codespan_reporting::files;
45 use std::ops::Range;
46
47 /// A file that is backed by an `Arc<String>`.
48 #[derive(Debug, Clone)]
49 struct File {
50 /// The name of the file.
51 name: String,
52 /// The source code of the file.
53 source: String,
54 /// The starting byte indices in the source code.
55 line_starts: Vec<usize>,
56 }
57
58 impl File {
line_start(&self, line_index: usize) -> Result<usize, files::Error>59 fn line_start(&self, line_index: usize) -> Result<usize, files::Error> {
60 use std::cmp::Ordering;
61
62 match line_index.cmp(&self.line_starts.len()) {
63 Ordering::Less => Ok(self
64 .line_starts
65 .get(line_index)
66 .expect("failed despite previous check")
67 .clone()),
68 Ordering::Equal => Ok(self.source.len()),
69 Ordering::Greater => Err(files::Error::LineTooLarge {
70 given: line_index,
71 max: self.line_starts.len() - 1,
72 }),
73 }
74 }
75 }
76
77 /// An opaque file identifier.
78 #[derive(Copy, Clone, PartialEq, Eq)]
79 pub struct FileId(u32);
80
81 #[derive(Debug, Clone)]
82 pub struct Files {
83 files: Vec<File>,
84 }
85
86 impl Files {
87 /// Create a new files database.
new() -> Files88 pub fn new() -> Files {
89 Files { files: Vec::new() }
90 }
91
92 /// Add a file to the database, returning the handle that can be used to
93 /// refer to it again.
add( &mut self, name: impl Into<String>, source: impl Into<String>, ) -> Option<FileId>94 pub fn add(
95 &mut self,
96 name: impl Into<String>,
97 source: impl Into<String>,
98 ) -> Option<FileId> {
99 use std::convert::TryFrom;
100
101 let file_id = FileId(u32::try_from(self.files.len()).ok()?);
102 let name = name.into();
103 let source = source.into();
104 let line_starts = files::line_starts(&source).collect();
105
106 self.files.push(File {
107 name,
108 line_starts,
109 source,
110 });
111
112 Some(file_id)
113 }
114
115 /// Get the file corresponding to the given id.
get(&self, file_id: FileId) -> Result<&File, files::Error>116 fn get(&self, file_id: FileId) -> Result<&File, files::Error> {
117 self.files
118 .get(file_id.0 as usize)
119 .ok_or(files::Error::FileMissing)
120 }
121 }
122
123 impl<'files> files::Files<'files> for Files {
124 type FileId = FileId;
125 type Name = &'files str;
126 type Source = &'files str;
127
name(&self, file_id: FileId) -> Result<&str, files::Error>128 fn name(&self, file_id: FileId) -> Result<&str, files::Error> {
129 Ok(self.get(file_id)?.name.as_ref())
130 }
131
source(&self, file_id: FileId) -> Result<&str, files::Error>132 fn source(&self, file_id: FileId) -> Result<&str, files::Error> {
133 Ok(&self.get(file_id)?.source)
134 }
135
line_index(&self, file_id: FileId, byte_index: usize) -> Result<usize, files::Error>136 fn line_index(&self, file_id: FileId, byte_index: usize) -> Result<usize, files::Error> {
137 self.get(file_id)?
138 .line_starts
139 .binary_search(&byte_index)
140 .or_else(|next_line| Ok(next_line - 1))
141 }
142
line_range( &self, file_id: FileId, line_index: usize, ) -> Result<Range<usize>, files::Error>143 fn line_range(
144 &self,
145 file_id: FileId,
146 line_index: usize,
147 ) -> Result<Range<usize>, files::Error> {
148 let file = self.get(file_id)?;
149 let line_start = file.line_start(line_index)?;
150 let next_line_start = file.line_start(line_index + 1)?;
151
152 Ok(line_start..next_line_start)
153 }
154 }
155 }
156
157 /// A Diagnostic message.
158 enum Message {
159 UnwantedGreetings {
160 greetings: Vec<(files::FileId, Range<usize>)>,
161 },
162 OverTheTopExclamations {
163 exclamations: Vec<(files::FileId, Range<usize>)>,
164 },
165 }
166
167 impl Message {
to_diagnostic(&self) -> Diagnostic<files::FileId>168 fn to_diagnostic(&self) -> Diagnostic<files::FileId> {
169 match self {
170 Message::UnwantedGreetings { greetings } => Diagnostic::error()
171 .with_message("greetings are not allowed")
172 .with_labels(
173 greetings
174 .iter()
175 .map(|(file_id, range)| {
176 Label::primary(*file_id, range.clone()).with_message("a greeting")
177 })
178 .collect(),
179 )
180 .with_notes(vec![
181 "found greetings!".to_owned(),
182 "pleas no greetings :(".to_owned(),
183 ]),
184 Message::OverTheTopExclamations { exclamations } => Diagnostic::error()
185 .with_message("over-the-top exclamations")
186 .with_labels(
187 exclamations
188 .iter()
189 .map(|(file_id, range)| {
190 Label::primary(*file_id, range.clone()).with_message("an exclamation")
191 })
192 .collect(),
193 )
194 .with_notes(vec!["ridiculous!".to_owned()]),
195 }
196 }
197 }
198