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 12use codespan_reporting::diagnostic::{Diagnostic, Label}; 13use codespan_reporting::term; 14use codespan_reporting::term::termcolor::{ColorChoice, StandardStream}; 15use std::ops::Range; 16 17fn 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 43mod 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 { 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. 88 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. 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. 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 128 fn name(&self, file_id: FileId) -> Result<&str, files::Error> { 129 Ok(self.get(file_id)?.name.as_ref()) 130 } 131 132 fn source(&self, file_id: FileId) -> Result<&str, files::Error> { 133 Ok(&self.get(file_id)?.source) 134 } 135 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 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. 158enum Message { 159 UnwantedGreetings { 160 greetings: Vec<(files::FileId, Range<usize>)>, 161 }, 162 OverTheTopExclamations { 163 exclamations: Vec<(files::FileId, Range<usize>)>, 164 }, 165} 166 167impl Message { 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