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