use crate::backend::EmitError;
use crate::markup::ElementName;
use crate::resolve::{Resolution, Resolver};
use std::cell::{Cell, RefCell};
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
pub struct Helpers {
pub create: bool,
pub read: bool,
pub merge_props: bool,
}
pub struct EmitContext<'a> {
source: &'a str,
line_starts: Vec<usize>,
resolver: &'a Resolver,
create: Cell<bool>,
read: Cell<bool>,
merge_props: Cell<bool>,
errors: RefCell<Vec<EmitError>>,
}
impl<'a> EmitContext<'a> {
pub fn new(source: &'a str, resolver: &'a Resolver) -> Self {
let mut line_starts = vec![0];
line_starts.extend(
source
.bytes()
.enumerate()
.filter(|(_, byte)| *byte == b'\n')
.map(|(index, _)| index + 1),
);
Self {
source,
line_starts,
resolver,
create: Cell::new(false),
read: Cell::new(false),
merge_props: Cell::new(false),
errors: RefCell::default(),
}
}
pub fn create(&self) -> &str {
self.resolver.create()
}
pub fn used_create(&self) {
self.create.set(true);
}
pub fn used_read(&self) {
self.read.set(true);
}
pub fn used_merge_props(&self) {
self.merge_props.set(true);
}
pub fn helpers(&self) -> Helpers {
Helpers {
create: self.create.get(),
read: self.read.get(),
merge_props: self.merge_props.get(),
}
}
pub fn source(&self) -> &'a str {
self.source
}
pub fn resolve(&self, name: &ElementName, offset: usize) -> Resolution {
match self.resolver.resolve(name, offset) {
Ok(resolution) => resolution,
Err(error) => {
let written = name.as_written();
self.errors.borrow_mut().push(error);
Resolution::Unresolved(written)
}
}
}
pub fn resolve_attribute(&self, class: &str, written: &str, offset: usize) -> String {
match self.resolver.resolve_attribute(class, written, offset) {
Ok(canonical) => canonical,
Err(error) => {
self.errors.borrow_mut().push(error);
written.to_string()
}
}
}
pub fn take_errors(&self) -> Vec<EmitError> {
std::mem::take(&mut self.errors.borrow_mut())
}
pub fn line_of(&self, offset: usize) -> usize {
match self.line_starts.binary_search(&offset) {
Ok(line) => line,
Err(next) => next - 1,
}
}
pub fn indent_of(&self, line: usize) -> &'a str {
let start = self.line_starts.get(line).copied().unwrap_or(0);
let end = self
.line_starts
.get(line + 1)
.copied()
.unwrap_or(self.source.len());
let text = &self.source[start..end];
let indent = text.len() - text.trim_start().len();
&text[..indent]
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::resolve::Resolver;
#[test]
fn maps_offsets_to_lines() {
let resolver = Resolver::new("", crate::Config::default());
let context = EmitContext::new("a\nbb\n\nccc", &resolver);
assert_eq!(context.line_of(0), 0);
assert_eq!(context.line_of(1), 0);
assert_eq!(context.line_of(2), 1);
assert_eq!(context.line_of(4), 1);
assert_eq!(context.line_of(5), 2);
assert_eq!(context.line_of(6), 3);
}
#[test]
fn reads_line_indentation() {
let resolver = Resolver::new("", crate::Config::default());
let context = EmitContext::new("no\n two\n\tTab\n", &resolver);
assert_eq!(context.indent_of(0), "");
assert_eq!(context.indent_of(1), " ");
assert_eq!(context.indent_of(2), "\t");
}
#[test]
fn handles_a_final_line_without_a_newline() {
let resolver = Resolver::new("", crate::Config::default());
let context = EmitContext::new("a\n b", &resolver);
assert_eq!(context.line_of(4), 1);
assert_eq!(context.indent_of(1), " ");
}
}