Skip to main content

lax_markup/
format_text.rs

1use std::path::Path;
2
3use anyhow::Result;
4use dprint_core::configuration::resolve_new_line_kind;
5use dprint_core::formatting::PrintOptions;
6
7use crate::configuration::Configuration;
8use crate::generation;
9
10/// Formats the contents of an embedded block, like CSS in a style element or
11/// a script body. Receives the language hint (the element's `lang` or `type`
12/// attribute value, or `css`/`js` by element kind), the raw inner text, and
13/// the remaining print width. Returning `Ok(None)` keeps the contents
14/// verbatim.
15pub type ExternalFormatter<'a> = dyn Fn(&str, &str, u32) -> Result<Option<String>> + 'a;
16
17pub fn format_text(_path: &Path, text: &str, config: &Configuration) -> Result<Option<String>> {
18  let result = format_text_inner(text, config, None)?;
19  if result == text { Ok(None) } else { Ok(Some(result)) }
20}
21
22pub fn format_text_with_external(
23  _path: &Path,
24  text: &str,
25  config: &Configuration,
26  external: &ExternalFormatter,
27) -> Result<Option<String>> {
28  let result = format_text_inner(text, config, Some(external))?;
29  if result == text { Ok(None) } else { Ok(Some(result)) }
30}
31
32fn format_text_inner(text: &str, config: &Configuration, external: Option<&ExternalFormatter>) -> Result<String> {
33  let text = text.strip_prefix('\u{FEFF}').unwrap_or(text);
34  let events = generation::tokenize(text);
35  if has_ignore_file_comment(&events, &config.ignore_file_comment_text) {
36    return Ok(text.to_string());
37  }
38  let nodes = generation::parse(events);
39  if nodes.is_empty() {
40    return Ok(String::new());
41  }
42  let external_error = std::cell::RefCell::new(None);
43  let formatted = dprint_core::formatting::format(
44    || generation::generate(&nodes, text, config, external, &external_error),
45    PrintOptions {
46      indent_width: config.indent_width,
47      max_width: config.line_width,
48      use_tabs: config.use_tabs,
49      new_line_text: resolve_new_line_kind(text, config.new_line_kind),
50    },
51  );
52  if let Some(error) = external_error.into_inner() {
53    return Err(error);
54  }
55  // exactly one trailing newline, so verbatim regions at the end of the
56  // file cannot accumulate blank lines across passes
57  Ok(format!("{}\n", formatted.trim_end()))
58}
59
60fn has_ignore_file_comment(events: &[generation::Event], directive: &str) -> bool {
61  lax_core::has_ignore_file_comment(
62    events.iter().map(|event| match &event.kind {
63      generation::EventKind::Whitespace { newlines } => lax_core::HeaderToken::Whitespace { newlines: *newlines },
64      generation::EventKind::Comment { text } => lax_core::HeaderToken::Comment(text),
65      _ => lax_core::HeaderToken::Other,
66    }),
67    directive,
68  )
69}