dprint_plugin_markdown/
format_text.rs1use anyhow::bail;
2use anyhow::Result;
3use dprint_core::configuration::resolve_new_line_kind;
4use dprint_core::formatting::*;
5
6use super::configuration::Configuration;
7use super::generation::file_has_ignore_file_directive;
8use super::generation::generate;
9use super::generation::parse_cmark_ast;
10use super::generation::strip_metadata_header;
11use super::generation::Context;
12
13pub fn format_text(
17 file_text: &str,
18 config: &Configuration,
19 format_code_block_text: impl for<'a> FnMut(&str, &'a str, u32) -> Result<Option<String>>,
20) -> Result<Option<String>> {
21 let result = format_text_inner(file_text, config, format_code_block_text)?;
22
23 match result {
24 Some(result) if result == file_text => Ok(None),
25 Some(result) => Ok(Some(result)),
26 None => Ok(None),
27 }
28}
29
30fn format_text_inner(
31 file_text: &str,
32 config: &Configuration,
33 format_code_block_text: impl for<'a> FnMut(&str, &'a str, u32) -> Result<Option<String>>,
34) -> Result<Option<String>> {
35 let file_text = strip_bom(file_text);
36 let (source_file, markdown_text) = match parse_source_file(file_text, config)? {
37 ParseFileResult::IgnoreFile => return Ok(None),
38 ParseFileResult::SourceFile(file) => file,
39 };
40
41 Ok(Some(dprint_core::formatting::format(
42 || {
43 let mut context = Context::new(markdown_text, config, format_code_block_text);
44 #[allow(clippy::let_and_return)]
45 let print_items = generate(&source_file.into(), &mut context);
46 print_items
48 },
49 config_to_print_options(file_text, config),
50 )))
51}
52
53#[cfg(feature = "tracing")]
54pub fn trace_file(
55 file_text: &str,
56 config: &Configuration,
57 format_code_block_text: impl for<'a> FnMut(&str, &'a str, u32) -> Result<Option<String>>,
58) -> dprint_core::formatting::TracingResult {
59 let (source_file, markdown_text) = match parse_source_file(file_text, config).unwrap() {
60 ParseFileResult::IgnoreFile => panic!("Cannot trace file because it has an ignore file comment."),
61 ParseFileResult::SourceFile(file) => file,
62 };
63 dprint_core::formatting::trace_printing(
64 || {
65 let mut context = Context::new(markdown_text, config, format_code_block_text);
66 let print_items = generate(&source_file.into(), &mut context);
67 print_items
69 },
70 config_to_print_options(file_text, config),
71 )
72}
73
74fn strip_bom(text: &str) -> &str {
75 text.strip_prefix("\u{FEFF}").unwrap_or(text)
76}
77
78enum ParseFileResult<'a> {
79 IgnoreFile,
80 SourceFile((crate::generation::common::SourceFile, &'a str)),
81}
82
83fn parse_source_file<'a>(file_text: &'a str, config: &Configuration) -> Result<ParseFileResult<'a>> {
84 if file_has_ignore_file_directive(strip_metadata_header(file_text), &config.ignore_file_directive) {
86 return Ok(ParseFileResult::IgnoreFile);
87 }
88
89 match parse_cmark_ast(file_text) {
90 Ok(source_file) => Ok(ParseFileResult::SourceFile((source_file, file_text))),
91 Err(error) => bail!(
92 "{}",
93 dprint_core::formatting::utils::string_utils::format_diagnostic(
94 Some((error.range.start, error.range.end)),
95 &error.message,
96 file_text
97 )
98 ),
99 }
100}
101
102fn config_to_print_options(file_text: &str, config: &Configuration) -> PrintOptions {
103 PrintOptions {
104 indent_width: 1, max_width: config.line_width,
106 use_tabs: false, new_line_text: resolve_new_line_kind(file_text, config.new_line_kind),
108 }
109}
110
111#[cfg(test)]
112mod test {
113 use super::*;
114 use crate::configuration::ConfigurationBuilder;
115
116 #[test]
117 fn strips_bom() {
118 for input_text in ["\u{FEFF}# Title", "\u{FEFF}# Title\n"] {
119 let config = ConfigurationBuilder::new().build();
120 let result = format_text(input_text, &config, |_, _, _| Ok(None)).unwrap();
121 assert_eq!(result, Some("# Title\n".to_string()));
122 }
123 }
124}