use std::fmt::{Display, Formatter};
use lady_deirdre::{
arena::{Id, Identifiable},
format::{AnnotationPriority, SnippetConfig, SnippetFormatter, Style, TerminalString},
lexis::{SiteSpan, ToSpan, TokenBuffer},
};
use crate::{
format::highlight::ScriptHighlighter,
runtime::PackageMeta,
syntax::{ScriptDoc, ScriptToken},
};
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[non_exhaustive]
pub struct ScriptSnippetConfig {
pub show_outer_frame: bool,
pub show_line_numbers: bool,
pub show_module_path: bool,
pub highlight_code: bool,
pub unicode_drawing: bool,
}
impl Default for ScriptSnippetConfig {
#[inline(always)]
fn default() -> Self {
Self::new()
}
}
impl From<ScriptSnippetConfig> for SnippetConfig {
#[inline(always)]
fn from(value: ScriptSnippetConfig) -> Self {
let mut config = Self::verbose();
config.draw_frame = value.show_outer_frame;
config.show_numbers = value.show_line_numbers;
config.ascii_drawing = !value.unicode_drawing;
if !value.highlight_code {
config.dim_code = false;
config.style = false;
}
config
}
}
impl ScriptSnippetConfig {
#[inline(always)]
pub const fn new() -> Self {
Self {
show_outer_frame: true,
show_line_numbers: true,
show_module_path: true,
highlight_code: true,
unicode_drawing: true,
}
}
#[inline(always)]
pub const fn minimal() -> Self {
Self {
show_outer_frame: false,
show_line_numbers: false,
show_module_path: false,
highlight_code: false,
unicode_drawing: false,
}
}
}
pub struct ScriptSnippet<'a> {
code: SnippetCode<'a>,
config: ScriptSnippetConfig,
caption: Option<String>,
annotations: Vec<(SiteSpan, AnnotationPriority, String)>,
summary: Option<String>,
}
impl<'a> Display for ScriptSnippet<'a> {
#[inline(always)]
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
let mut caption = String::with_capacity(512);
match self.config.show_module_path {
true => {
if let Some(prefix) = &self.caption {
caption.push_str(&prefix.apply(Style::new().bold()));
caption.push_str(" [");
}
let id = match &self.code {
SnippetCode::Borrowed(code) => code.id(),
SnippetCode::Owned(code) => code.id(),
};
caption.push_str(
format_script_path(id, PackageMeta::by_id(id))
.apply(Style::new().bright_cyan())
.as_str(),
);
if self.caption.is_some() {
caption.push(']');
}
}
false => {
if let Some(prefix) = &self.caption {
caption.push_str(prefix)
}
}
}
match &self.code {
SnippetCode::Borrowed(code) => {
let config = self.config.into();
let mut snippet = formatter.snippet(*code);
snippet.set_config(&config).set_caption(caption);
if self.config.highlight_code {
snippet.set_highlighter(ScriptHighlighter::new());
}
if let Some(summary) = &self.summary {
snippet.set_summary(summary.as_str());
}
for (span, priority, message) in &self.annotations {
snippet.annotate(span, *priority, message.as_str());
}
snippet.finish()?;
}
SnippetCode::Owned(code) => {
let config = self.config.into();
let mut snippet = formatter.snippet(code);
snippet.set_config(&config).set_caption(caption);
if self.config.highlight_code {
snippet.set_highlighter(ScriptHighlighter::new());
}
if let Some(summary) = &self.summary {
snippet.set_summary(summary.as_str());
}
for (span, priority, message) in &self.annotations {
snippet.annotate(span, *priority, message.as_str());
}
snippet.finish()?;
}
}
Ok(())
}
}
impl<S: AsRef<str>> From<S> for ScriptSnippet<'static> {
#[inline(always)]
fn from(string: S) -> Self {
let buffer = TokenBuffer::from(string);
Self::new(SnippetCode::Owned(buffer))
}
}
impl<'a> ScriptSnippet<'a> {
#[inline(always)]
fn new(code: SnippetCode<'a>) -> Self {
Self {
code,
config: ScriptSnippetConfig::default(),
caption: None,
annotations: Vec::new(),
summary: None,
}
}
#[inline(always)]
pub(crate) fn from_doc(doc: &'a ScriptDoc) -> Self {
Self::new(SnippetCode::Borrowed(doc))
}
pub fn set_config(&mut self, config: ScriptSnippetConfig) -> &mut Self {
self.config = config;
self
}
pub fn set_caption(&mut self, caption: impl AsRef<str>) -> &mut Self {
self.caption = caption
.as_ref()
.lines()
.next()
.map(|line| String::from(line));
self
}
pub fn set_summary(&mut self, summary: impl AsRef<str>) -> &mut Self {
self.summary = Some(String::from(summary.as_ref()));
self
}
pub fn annotate(
&mut self,
span: impl ToSpan,
priority: AnnotationPriority,
message: impl AsRef<str>,
) -> &mut Self {
let span = match &self.code {
SnippetCode::Borrowed(code) => span.to_site_span(*code),
SnippetCode::Owned(code) => span.to_site_span(code),
};
let Some(span) = span else {
return self;
};
let message = message
.as_ref()
.lines()
.next()
.map(|line| String::from(line))
.unwrap_or(String::new());
self.annotations.push((span, priority, message));
self
}
}
#[inline(always)]
pub(crate) fn format_script_path(id: Id, package: Option<&'static PackageMeta>) -> String {
let mut path = String::with_capacity(512);
if let Some(package) = package {
path.push_str(&format!("‹{}›.", package));
}
let name = id.name();
match name.is_empty() {
true => path.push_str(&format!("‹#{}›", id.into_inner())),
false => path.push_str(&format!("‹{}›", name.escape_debug())),
}
path
}
enum SnippetCode<'a> {
Borrowed(&'a ScriptDoc),
Owned(TokenBuffer<ScriptToken>),
}