diff --git a/Cargo.lock b/Cargo.lock index 68a7ab8..68c98bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -108,9 +108,9 @@ dependencies = [ [[package]] name = "dprint-core" -version = "0.69.1" +version = "0.70.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67359cea061dd052fca921d3589a9ce59bc16fa23bfb9d06d5c669a1dbd23915" +checksum = "f6c66c17f6a0648b838e9a54fcf6ce2e237d060edbd3a65a724687d563fb8f28" dependencies = [ "bumpalo", "hashbrown 0.15.2", @@ -134,9 +134,9 @@ dependencies = [ [[package]] name = "dprint-development" -version = "0.11.0" +version = "0.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90b3b095f94733262261bfeb860a6550e1d656dd108240c43004bd0f1b419837" +checksum = "b6696b3acf01bba505d35c400115ce277e6a5208ce41defa11799c53f6d02c5e" dependencies = [ "console", "file_test_runner", diff --git a/Cargo.toml b/Cargo.toml index 9f98320..abb85ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,7 +39,7 @@ path = "tests/html_parser_spec_test.rs" harness = false [dependencies] -dprint-core = { version = "0.69.1", features = ["formatting"] } +dprint-core = { version = "0.70.0", features = ["formatting"] } dprint-core-macros = "0.1.0" serde = { version = "1.0.144", features = ["derive"] } serde_json = { version = "1.0", optional = true } @@ -47,6 +47,6 @@ thiserror = "2.0" unicode-width = "0.2" [dev-dependencies] -dprint-development = "0.11.0" +dprint-development = "0.12.1" file_test_runner = "0.12.1" serde_json = { version = "1.0" } diff --git a/src/format_range.rs b/src/format_range.rs new file mode 100644 index 0000000..d7e79f6 --- /dev/null +++ b/src/format_range.rs @@ -0,0 +1,475 @@ +use std::borrow::Cow; +use std::ops::Range; +use std::ops::RangeInclusive; + +use dprint_core::configuration::resolve_new_line_kind; +use dprint_core::configuration::NewLineKind; + +use super::configuration::Configuration; +use super::format_text::format_text; +use super::format_text::format_text_inner; +use super::format_text::strip_bom; +use super::format_text::FormatError; +use super::generation::is_ignore_comment; +use super::generation::strip_metadata_header; +use super::parser::Node; +use super::parser::Ranged; +use super::parser::SourceFile; +use super::parser::WHITESPACE; + +/// Formats only the part of the text within the provided byte range. +/// +/// The range is widened to the lines of the blocks it touches in the innermost +/// block quote or list (going by its items) that contains it, and the text +/// outside of those is left as it was. When those blocks aren't written as the +/// same blocks once formatted, or their text can't be put back in place of the +/// original without changing how the text around it reads, the block holding +/// them is formatted instead, and so on out to the whole file. A range that +/// reaches from before the first block to after the last formats the whole +/// file and one that only touches the blank lines between blocks formats +/// nothing. +/// +/// Only the code blocks within what's formatted are passed to +/// `format_code_block_text`. +pub fn format_text_range( + file_text: &str, + range: Range, + config: &Configuration, + mut format_code_block_text: impl for<'a> FnMut(&str, &'a str, u32) -> Result, FormatError>, +) -> Result, FormatError> { + let body = strip_bom(file_text); + let bom_len = file_text.len() - body.len(); + let end = range.end.saturating_sub(bom_len).min(body.len()); + let range = range.start.saturating_sub(bom_len).min(end)..end; + if is_ignore_comment(strip_metadata_header(body), &config.ignore_file_directive) { + return Ok(None); + } + // a lone carriage return breaks a line as much as a line feed does, which + // is rare enough to leave to formatting the whole file + if has_lone_carriage_return(body) { + return format_text(file_text, config, format_code_block_text); + } + let Ok(source_file) = crate::parser::parse(body) else { + return format_text(file_text, config, format_code_block_text); + }; + let levels = match find_levels(&source_file, &range) { + Target::File => return format_text(file_text, config, format_code_block_text), + Target::Nothing => return Ok(None), + Target::Levels(levels) => levels, + }; + + // the code blocks are only formatted within the innermost level at first, + // since formatting them is what's slow, and within an outer level once + // that's the one the replacement is made at + let mut depth = levels.len() - 1; + let replacement = loop { + let formatted = format_text_inner( + body, + config, + Some(levels[depth].replaced_range(body)), + &mut format_code_block_text, + )? + .expect("an ignored file should have been handled"); + match find_replacement(&levels[..=depth], body, &formatted) { + Some((replaced_depth, replacement)) if replaced_depth == depth => break replacement, + Some((replaced_depth, _)) => depth = replaced_depth, + None => return format_text(file_text, config, format_code_block_text), + } + }; + + let result = format!( + "{}{}{}", + &file_text[..bom_len + replacement.original.start], + replacement.text, + &file_text[bom_len + replacement.original.end..] + ); + if result == file_text { + Ok(None) + } else { + Ok(Some(result)) + } +} + +enum Target<'a, 'b> { + /// The range touches both the first and last block of the file. + File, + /// The range is outside of the blocks or only touches the blank lines + /// between them. + Nothing, + /// Each container from the file to the innermost one holding the range. + Levels(Vec>), +} + +/// A container along with the blocks of it to format. +struct Level<'a, 'b> { + members: Vec<&'b Node<'a>>, + /// Indexes of the members to format. For all but the innermost level, this + /// is only the member holding the next level. + indexes: RangeInclusive, +} + +impl Level<'_, '_> { + fn replaced_range(&self, text: &str) -> Range { + let first = &self.members[*self.indexes.start()]; + let last = &self.members[*self.indexes.end()]; + line_start(text, first.span().start)..member_end(text, last) + } +} + +struct Replacement { + original: Range, + text: String, +} + +fn find_levels<'a, 'b>(source_file: &'b SourceFile<'a>, range: &Range) -> Target<'a, 'b> { + let children = &source_file.children; + let (Some(first), Some(last)) = (children.first(), children.last()) else { + return Target::Nothing; + }; + if range.start <= first.span().start && range.end >= last.span().end { + return Target::File; + } + let mut levels = Vec::new(); + let mut members = children.iter().collect::>(); + loop { + let mut touched = members + .iter() + .enumerate() + .filter(|(_, member)| touches(member.span(), range)) + .map(|(index, _)| index); + let Some(first) = touched.next() else { + // a range between the blocks of a container formats the container + return if levels.is_empty() { + Target::Nothing + } else { + Target::Levels(levels) + }; + }; + let last = touched.next_back().unwrap_or(first); + let member = members[first]; + let inner = Some(member) + .filter(|member| first == last && !covers(range, member.span())) + .and_then(|member| inner_members(member)) + .filter(|children| !children.is_empty()); + levels.push(Level { + members, + indexes: first..=last, + }); + match inner { + Some(inner) => members = inner, + // the start and end of the file are only formatted along with all of it + None if levels.len() == 1 && first == 0 && last == children.len() - 1 => return Target::File, + None => return Target::Levels(levels), + } + } +} + +/// Finds the text to replace in the original and the formatted text to +/// replace it with, starting with the innermost level and moving out towards +/// the file, along with the depth of the level it's made at. +fn find_replacement(levels: &[Level], text: &str, formatted_text: &str) -> Option<(usize, Replacement)> { + let formatted_file = crate::parser::parse(formatted_text).ok()?; + let mut formatted_levels = Vec::with_capacity(levels.len()); + let mut formatted_members = formatted_file.children.iter().collect::>(); + for level in levels { + if !same_kinds(&level.members, &formatted_members) { + break; + } + let next = inner_members(formatted_members[*level.indexes.start()]).unwrap_or_default(); + formatted_levels.push(std::mem::replace(&mut formatted_members, next)); + } + + levels + .iter() + .zip(formatted_levels) + .enumerate() + .rev() + .find_map(|(depth, (level, formatted_members))| { + // the line prefixes of the containers around a nested block stay as + // they were, so they need to already be what the formatter writes, + // which is checked for every block of the container so that its blocks + // don't end up indented differently from one another + let same_prefixes = level + .members + .iter() + .zip(&formatted_members) + .all(|(original, formatted)| { + let (original_start, formatted_start) = (original.span().start, formatted.span().start); + text[line_start(text, original_start)..original_start] + == formatted_text[line_start(formatted_text, formatted_start)..formatted_start] + }); + if depth > 0 && !same_prefixes { + return None; + } + let original = level.replaced_range(text); + let formatted_range = Level { + members: formatted_members, + indexes: level.indexes.clone(), + } + .replaced_range(formatted_text); + let replacement = Replacement { + original, + text: with_file_new_lines(&formatted_text[formatted_range.clone()], text).into_owned(), + }; + reads_back(levels, depth, text, &replacement, &formatted_text[formatted_range]).then_some((depth, replacement)) + }) +} + +/// Whether the text with the replacement made holds the same blocks as before +/// along the path down to the level, with the replaced ones written as they +/// were formatted and the others written as they were. +fn reads_back(levels: &[Level], depth: usize, text: &str, replacement: &Replacement, formatted: &str) -> bool { + let result = format!( + "{}{}{}", + &text[..replacement.original.start], + replacement.text, + &text[replacement.original.end..] + ); + let Ok(source_file) = crate::parser::parse(&result) else { + return false; + }; + let mut members = source_file.children.iter().collect::>(); + for level in &levels[..depth] { + if !same_kinds(&level.members, &members) || !same_others(level, &members, text, &result) { + return false; + } + let Some(next) = inner_members(members[*level.indexes.start()]) else { + return false; + }; + members = next; + } + let level = &levels[depth]; + if !same_kinds(&level.members, &members) || !same_others(level, &members, text, &result) { + return false; + } + let replaced = Level { + members, + indexes: level.indexes.clone(), + } + .replaced_range(&result); + normalize_new_lines(&result[replaced]) == normalize_new_lines(formatted) +} + +/// The blocks within a container that can be formatted on their own. +fn inner_members<'a, 'b>(node: &'b Node<'a>) -> Option>> { + match node { + Node::List(list) => Some(list.children.iter().collect()), + Node::BlockQuote(quote) => Some(quote.children.iter().collect()), + Node::Item(item) => Some(item.children.iter().chain(&item.sub_lists).collect()), + _ => None, + } +} + +/// Whether the members of a level other than the ones to format are written +/// as they were. +fn same_others(level: &Level, members: &[&Node], text: &str, result: &str) -> bool { + level + .members + .iter() + .zip(members) + .enumerate() + .filter(|(index, _)| !level.indexes.contains(index)) + .all(|(_, (original, member))| original.span().text(text) == member.span().text(result)) +} + +fn same_kinds(original: &[&Node], formatted: &[&Node]) -> bool { + original.len() == formatted.len() + && original + .iter() + .zip(formatted) + .all(|(a, b)| std::mem::discriminant(*a) == std::mem::discriminant(*b)) +} + +fn touches(member: crate::parser::Span, range: &Range) -> bool { + if range.is_empty() { + member.start <= range.start && range.start <= member.end + } else { + range.start < member.end && range.end > member.start + } +} + +fn covers(range: &Range, span: crate::parser::Span) -> bool { + range.start <= span.start && range.end >= span.end +} + +fn line_start(text: &str, pos: usize) -> usize { + text[..pos].rfind('\n').map(|index| index + 1).unwrap_or(0) +} + +/// The end of a member's text along with any whitespace after it on its last +/// line, without the line break. +fn member_end(text: &str, member: &Node) -> usize { + let span = member.span(); + let end = span.start + span.text(text).trim_end_matches(WHITESPACE).len(); + let rest = &text[end..]; + let line = rest.find('\n').map(|index| &rest[..index]).unwrap_or(rest); + if line.trim_matches(WHITESPACE).is_empty() { + end + line.trim_end_matches('\r').len() + } else { + end + } +} + +fn has_lone_carriage_return(text: &str) -> bool { + text + .match_indices('\r') + .any(|(index, _)| text.as_bytes().get(index + 1) != Some(&b'\n')) +} + +fn normalize_new_lines(text: &str) -> Cow<'_, str> { + if text.contains("\r\n") { + Cow::Owned(text.replace("\r\n", "\n")) + } else { + Cow::Borrowed(text) + } +} + +/// Keeps the line endings of the rest of the file since changing those is up +/// to formatting the whole file. +fn with_file_new_lines<'a>(formatted: &'a str, file_text: &str) -> Cow<'a, str> { + if !file_text.contains('\n') { + return Cow::Borrowed(formatted); + } + match resolve_new_line_kind(file_text, NewLineKind::Auto) { + "\r\n" if formatted.contains('\n') && !formatted.contains("\r\n") => Cow::Owned(formatted.replace('\n', "\r\n")), + "\n" if formatted.contains("\r\n") => Cow::Owned(formatted.replace("\r\n", "\n")), + _ => Cow::Borrowed(formatted), + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::configuration::ConfigurationBuilder; + + #[test] + fn keeps_bom_outside_range() { + // the spec files can't express this since editors strip the bom + let config = ConfigurationBuilder::new().build(); + let text = "\u{FEFF}a b\n\nc d\n"; + let output = format_c(&config, text); + assert_eq!(output, "\u{FEFF}a b\n\nc d\n"); + } + + #[test] + fn keeps_file_line_endings() { + // the spec files can't express this since they normalize line endings + let config = ConfigurationBuilder::new().build(); + let output = format_c(&config, "a b\r\n\r\n- c d\r\n - e\r\n"); + assert_eq!(output, "a b\r\n\r\n- c d\r\n - e\r\n"); + + let config = ConfigurationBuilder::new() + .new_line_kind(NewLineKind::CarriageReturnLineFeed) + .build(); + let output = format_c(&config, "a b\n\n- c d\n - e\n"); + assert_eq!(output, "a b\n\n- c d\n - e\n"); + } + + #[test] + fn only_formats_code_blocks_within_range() { + let config = ConfigurationBuilder::new().build(); + let text = "```js\na\n```\n\n```js\nb\n```\n\n```js\nc\n```\n"; + let start = text.find('b').unwrap(); + let mut calls = Vec::new(); + let output = format_text_range(text, start..start + 1, &config, |_, code, _| { + calls.push(code.to_string()); + Ok(Some(format!("{}2", code))) + }) + .unwrap(); + assert_eq!(calls, vec!["b"]); + assert_eq!( + output.as_deref(), + Some("```js\na\n```\n\n```js\nb2\n```\n\n```js\nc\n```\n") + ); + } + + #[test] + fn ignores_file_with_ignore_file_directive() { + let config = ConfigurationBuilder::new().build(); + let text = "\n\nc d\n"; + let start = text.find('c').unwrap(); + let output = format_text_range(text, start..start + 1, &config, |_, _, _| Ok(None)).unwrap(); + assert_eq!(output, None); + } + + #[test] + fn formats_whole_file_with_lone_carriage_returns() { + let config = ConfigurationBuilder::new().build(); + let text = "> a\n> important text\r\r# h x\n"; + let start = text.find("h ").unwrap(); + let output = format_text_range(text, start..start + 1, &config, |_, _, _| Ok(None)).unwrap(); + assert_eq!(output.as_deref(), Some("> a\n> important text\n\n# h x\n")); + } + + #[test] + fn formats_code_blocks_of_widened_region() { + // the list markers change, so the whole list is formatted + let config = ConfigurationBuilder::new().build(); + let text = "* one\n\n ```js\n code\n ```\n* two x\n\nend\n"; + let start = text.find("two").unwrap(); + let output = format_text_range(text, start..start + 1, &config, |_, code, _| { + Ok(Some(code.to_uppercase())) + }) + .unwrap(); + assert_eq!( + output.as_deref(), + Some("- one\n\n ```js\n CODE\n ```\n- two x\n\nend\n") + ); + } + + #[test] + fn formats_unclosed_code_block_at_end_of_file() { + let config = ConfigurationBuilder::new().build(); + let text = "a b\n\n~~~js\ncode\n\n"; + let start = text.find("code").unwrap(); + let output = format_text_range(text, start..start + 1, &config, |_, code, _| { + Ok(Some(code.to_uppercase())) + }) + .unwrap(); + assert_eq!(output.as_deref(), Some("a b\n\n```js\nCODE\n```\n\n")); + } + + #[test] + fn does_not_format_front_matter_outside_range() { + let config = ConfigurationBuilder::new().build(); + let text = "---\ntitle: x\n---\n\n# Heading\n\nText here.\n\nend\n"; + let start = text.find("Text").unwrap(); + let mut calls = Vec::new(); + let output = format_text_range(text, start..start + 1, &config, |tag, _, _| { + calls.push(tag.to_string()); + Ok(None) + }) + .unwrap(); + assert!(calls.is_empty(), "{:?}", calls); + assert_eq!( + output.as_deref(), + Some("---\ntitle: x\n---\n\n# Heading\n\nText here.\n\nend\n") + ); + } + + #[test] + fn formats_whole_file_when_touching_first_and_last_blocks() { + let config = ConfigurationBuilder::new().build(); + let output = format_text_range("a b", 0..3, &config, |_, _, _| Ok(None)).unwrap(); + assert_eq!(output.as_deref(), Some("a b\n")); + } + + #[test] + fn formats_item_when_siblings_indentation_changes() { + let config = ConfigurationBuilder::new() + .list_indent_kind(crate::configuration::ListIndentKind::PythonMarkdown) + .build(); + let text = "- foo\n - bar\n\n baz\n\nend\n"; + let start = text.find("bar").unwrap(); + let output = format_text_range(text, start..start + 1, &config, |_, _, _| Ok(None)).unwrap(); + let full = crate::format_text(text, &config, |_, _, _| Ok(None)).unwrap(); + assert_eq!(output, full); + } + + fn format_c(config: &Configuration, text: &str) -> String { + let start = text.find('c').unwrap(); + format_text_range(text, start..start + 1, config, |_, _, _| Ok(None)) + .unwrap() + .unwrap() + } +} diff --git a/src/format_text.rs b/src/format_text.rs index 31fdd84..b6bf6e7 100644 --- a/src/format_text.rs +++ b/src/format_text.rs @@ -1,4 +1,5 @@ use std::cell::RefCell; +use std::ops::Range; use std::rc::Rc; use dprint_core::configuration::resolve_new_line_kind; @@ -48,7 +49,7 @@ pub fn format_text( config: &Configuration, format_code_block_text: impl for<'a> FnMut(&str, &'a str, u32) -> Result, FormatError>, ) -> Result, FormatError> { - let result = format_text_inner(file_text, config, format_code_block_text)?; + let result = format_text_inner(file_text, config, None, format_code_block_text)?; match result { Some(result) if result == file_text => Ok(None), @@ -57,9 +58,12 @@ pub fn format_text( } } -fn format_text_inner( +/// Formats a file, only formatting the code blocks within `code_block_range` +/// when there is one. +pub(crate) fn format_text_inner( file_text: &str, config: &Configuration, + code_block_range: Option>, format_code_block_text: impl for<'a> FnMut(&str, &'a str, u32) -> Result, FormatError>, ) -> Result, FormatError> { let full_text = file_text; @@ -85,6 +89,7 @@ fn format_text_inner( format_code_block_text, code_block_error.clone(), ); + context.code_block_range = code_block_range; generate(&source_file.into(), &mut context) }, config_to_print_options(file_text, config), @@ -133,7 +138,7 @@ pub fn trace_file( /// only the first would leave another at the start of the file once the /// whitespace around it was written out -- for the next run to take off in /// turn. Text that begins with no mark at all is left as it is. -fn strip_bom(text: &str) -> &str { +pub(crate) fn strip_bom(text: &str) -> &str { let mut rest = text; loop { let trimmed = rest.trim_start_matches([' ', '\t', '\n', '\r']); diff --git a/src/generation/gen_types.rs b/src/generation/gen_types.rs index 9ab3a0f..12bee61 100644 --- a/src/generation/gen_types.rs +++ b/src/generation/gen_types.rs @@ -233,6 +233,9 @@ pub struct Context<'a> { /// It's held outside the context so that it's still there to be read once /// the context has been dropped. code_block_error: Rc>>, + /// The range of the file the code blocks are formatted within, when only + /// part of the file is being formatted. + pub code_block_range: Option>, memoized_rc_paths: HashMap>, /// How much each of the paths above indents by, keyed by address, so that /// they can be told apart from the paths that hold generated content and @@ -288,6 +291,7 @@ impl<'a> Context<'a> { next_position: NodePosition::default(), format_code_block_text: Box::new(format_code_block_text), code_block_error, + code_block_range: None, memoized_rc_paths: HashMap::new(), memoized_rc_path_indents: HashMap::new(), } @@ -686,6 +690,18 @@ impl<'a> Context<'a> { } } + /// Whether the code within the span is formatted, which it isn't when only + /// part of the file is being formatted and the span starts outside of that + /// part. Only the start is checked since the span of a code block can reach + /// past the end of the lines that are formatted (ex. the blank lines an + /// unclosed fence holds at the end of the file). + pub fn is_in_code_block_range(&self, span: Span) -> bool { + self + .code_block_range + .as_ref() + .is_none_or(|range| range.start <= span.start && span.start < range.end) + } + pub fn format_text<'b>(&mut self, tag: &str, text: &'b str) -> FormatResult { let line_width = std::cmp::max(10, self.configuration.line_width as i32 - self.indent_level as i32) as u32; diff --git a/src/generation/generate.rs b/src/generation/generate.rs index c03de34..e935f0d 100644 --- a/src/generation/generate.rs +++ b/src/generation/generate.rs @@ -1076,7 +1076,7 @@ fn gen_code_block(code_block: &CodeBlock, position: NodePosition, context: &mut let start_pos = get_code_block_start_pos(code); code[start_pos..].trim_end_matches(WHITESPACE) }; - if !context.configuration.code_block_skip_format { + if !context.configuration.code_block_skip_format && context.is_in_code_block_range(code_block.span) { if let Some(tag) = code_block.tag() { // allow situations like ```rust,ignore let tag = tag.chars().take_while(|&c| c != ' ' && c != ',').collect::(); @@ -3229,10 +3229,9 @@ fn gen_metadata_block(node: &MetadataBlock, context: &mut Context) -> PrintItems items.push_signal(Signal::NewLine); match node.kind { MetadataBlockKind::YamlStyle => { - let text = context - .format_text("yaml", node.text) - .ok() - .flatten() + let text = Some(node.span) + .filter(|span| context.is_in_code_block_range(*span)) + .and_then(|_| context.format_text("yaml", node.text).ok().flatten()) .map(Cow::from) .unwrap_or_else(|| Cow::from(node.text)); items.extend(ir_helpers::gen_from_string(&trim_line_ends( diff --git a/src/lib.rs b/src/lib.rs index aca2017..634cc12 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -11,11 +11,13 @@ #![deny(clippy::print_stdout)] pub mod configuration; +mod format_range; mod format_text; mod generation; mod html; mod parser; +pub use format_range::format_text_range; pub use format_text::format_text; pub use format_text::FormatError; diff --git a/src/wasm_plugin.rs b/src/wasm_plugin.rs index 3314bb8..5bc3baa 100644 --- a/src/wasm_plugin.rs +++ b/src/wasm_plugin.rs @@ -45,6 +45,7 @@ impl SyncPluginHandler for MarkdownPluginHandler { "markdown".to_string(), ], file_names: vec![], + additive: false, }, } } @@ -92,7 +93,7 @@ impl SyncPluginHandler for MarkdownPluginHandler { ) -> FormatResult { let file_text = String::from_utf8(request.file_bytes)?; let config = request.config.clone(); - return super::format_text(&file_text, request.config, |tag, file_text, line_width| { + let format_code_block_text = |tag: &str, file_text: &str, line_width: u32| { if let Some(ext) = tag_to_extension(tag, &config) { let file_path = PathBuf::from(format!("file.{}", ext)); let mut additional_config = ConfigKeyMap::new(); @@ -118,7 +119,11 @@ impl SyncPluginHandler for MarkdownPluginHandler { } else { Ok(None) } - }) + }; + return match request.range { + Some(range) => super::format_text_range(&file_text, range, request.config, format_code_block_text), + None => super::format_text(&file_text, request.config, format_code_block_text), + } .map(|maybe_text| maybe_text.map(|t| t.into_bytes())) .map_err(CoreFormatError::new); diff --git a/tests/spec_test.rs b/tests/spec_test.rs index 51cd922..06b61f3 100644 --- a/tests/spec_test.rs +++ b/tests/spec_test.rs @@ -24,19 +24,23 @@ fn main() { }, { let global_config = global_config.clone(); - Arc::new(move |_, file_text, spec_config| { + Arc::new(move |_, file_text, range, spec_config| { let spec_config: ConfigKeyMap = serde_json::from_value(spec_config.clone().into()).unwrap(); let config_result = resolve_config(spec_config, &global_config); ensure_no_diagnostics(&config_result.diagnostics); - format_text(file_text, &config_result.config, |tag, file_text, line_width| { + let format_code_block_text = |tag: &str, file_text: &str, line_width: u32| { let end = format!("_formatted_{}", line_width); if tag == "format" && !file_text.ends_with(&end) { Ok(Some(format!("{}{}", file_text, end))) } else { Ok(None) } - }) + }; + match range { + Some(range) => format_text_range(file_text, range, &config_result.config, format_code_block_text), + None => format_text(file_text, &config_result.config, format_code_block_text), + } .map_err(Into::into) }) }, diff --git a/tests/specs/Range/Range_BlockQuotes.txt b/tests/specs/Range/Range_BlockQuotes.txt new file mode 100644 index 0000000..547f826 --- /dev/null +++ b/tests/specs/Range/Range_BlockQuotes.txt @@ -0,0 +1,173 @@ +!! should format a block within a block quote !! +> a b +> +> [|c d|] +> +> e f + +[expect] +> a b +> +[|> c d|] +> +> e f + +!! should format the block quote when its prefixes change !! +>a b +> +>[|c d|] + +[expect] +[|> a b +> +> c d|] + +!! should format only the touched block quote !! +> a b + +c d + +> [|e f|] + +g h + +[expect] +> a b + +c d + +[|> e f|] + +g h + +!! should format a block within a nested block quote !! +> a b +> +>> c d +>> +>> [|e f|] +>> +>> g h +> +> i j + +[expect] +> a b +> +>> c d +>> +[|>> e f|] +>> +>> g h +> +> i j + +!! should format the outer block quote when the prefixes of the nested one change !! +> a b +> +> > c d +> > +> > [|e f|] +> > +> > g h +> +> i j + +[expect] +[|> a b +> +>> c d +>> +>> e f +>> +>> g h +> +> i j|] + +!! should format a paragraph with a lazy continuation line !! +> a b +> +> c d +[|e|] f +> +> g h + +[expect] +> a b +> +[|> c d +> e f|] +> +> g h + +!! should format a list item within a block quote !! +> - a b +> - [|c d|] +> - e f + +[expect] +> - a b +[|> - c d|] +> - e f + +!! should format the block quote when the range is on a line between its blocks !! +> a b +[|>|] +> c d + +e f + +[expect] +[|> a b +> +> c d|] + +e f + +!! should format a block quote from its marker !! +a b + +[|>|] c d +> e f + +g h + +[expect] +a b + +[|> c d +> e f|] + +g h + +!! should format a callout from its content !! +> [!NOTE] +> a b +> +> [|c d|] + +e f + +[expect] +> [!NOTE] +> a b +> +[|> c d|] + +e f + +!! should format a callout from its title !! +a b + +> [|[!NOTE]|] +> c d + +e f + +[expect] +a b + +> [!NOTE] +[|> c d|] + +e f diff --git a/tests/specs/Range/Range_Blocks.txt b/tests/specs/Range/Range_Blocks.txt new file mode 100644 index 0000000..f117ea0 --- /dev/null +++ b/tests/specs/Range/Range_Blocks.txt @@ -0,0 +1,218 @@ +!! should format only the touched paragraph !! +# Title + +some [|text|] +here + +other text + +[expect] +# Title + +[|some text|] +here + +other text + +!! should format several touched blocks along with the blank lines between them !! +# Title + + + +[|Para one + + + +# Heading|] + + + +other text + +[expect] +# Title + + + +[|Para one + +# Heading|] + + + +other text + +!! should format blocks of different kinds !! +a b + +[|# Title + +- c d + +> e f + +~~~ +code +~~~ + +* * *|] + +g h + +[expect] +a b + +[|# Title + +- c d + +> e f + +``` +code +``` + +---|] + +g h + +!! should fix the indentation of the touched block !! +a b + + [|c|] d + +e f + +[expect] +a b + +[|c d|] + +e f + +!! should format a block under the cursor !! +a b + +c [||] d + +e f + +[expect] +a b + +[|c d|] + +e f + +!! should format nothing for a range in blank lines between blocks !! +a b + +[||] + +e f + +[expect] +a b + +[||] + +e f + +!! should format the whole file when the range covers every block !! +[|a b + +e f|] + +[expect] +[|a b + +e f|] + +!! should format the whole file when the range touches the first and last blocks !! +a [| b + +c d + +e |] f + +[expect] +[|a b + +c d + +e f|] + +!! should format the whole file when its only block is touched !! + + +a [| b|] + + + +[expect] +[|a b|] + +!! should not format the blank lines before the first block when it is touched !! + + +a [| b|] + +c d + +[expect] + + +[|a b|] + +c d + +!! should not format the blank lines after the last block when it is touched !! +a b + +c [| d|] + + + +[expect] +a b + +[|c d|] + + + +!! should format a setext heading in place !! +a b + +[|Title|] +===== + +e f + +[expect] +a b + +[|# Title|] + +e f + +!! should keep the blank lines around the touched block !! +a b + + + +c [| d|] + + + +e f + +[expect] +a b + + + +[|c d|] + + + +e f diff --git a/tests/specs/Range/Range_CodeBlocks.txt b/tests/specs/Range/Range_CodeBlocks.txt new file mode 100644 index 0000000..1325027 --- /dev/null +++ b/tests/specs/Range/Range_CodeBlocks.txt @@ -0,0 +1,223 @@ +!! should format a fenced code block !! +a b + +~~~ts +[|code|] +~~~ + +c d + +[expect] +a b + +[|```ts +code +```|] + +c d + +!! should format a code block from its opening fence !! +a b + +[|~~~|]format +code +~~~ + +c d + +[expect] +a b + +[|```format +code_formatted_80 +```|] + +c d + +!! should format a code block from its closing fence !! +a b + +~~~format +code +[|~~~|] + +c d + +[expect] +a b + +[|```format +code_formatted_80 +```|] + +c d + +!! should only format the code of the touched code block !! +```format +one +``` + +```format +[|two|] +``` + +```format +three +``` + +[expect] +```format +one +``` + +```format +[|two_formatted_80|] +``` + +```format +three +``` + +!! should not format a code block when the range is elsewhere !! +a [| b|] + +```format +code +``` + +c d + +[expect] +[|a b|] + +```format +code +``` + +c d + +!! should format the code blocks the range covers !! +```format +one +``` + +[|```format +two +``` + +c d + +```format +three +```|] + +e f + +[expect] +```format +one +``` + +```format +[|two_formatted_80 +``` + +c d + +```format +three_formatted_80|] +``` + +e f + +!! should keep an indented code block !! +a b + + [|code|] here + +c d + +[expect] +a b + + [|code|] here + +c d + +!! should format a code block without a tag !! +a b + +~~~ +[|code|] here +~~~ + +c d + +[expect] +a b + +[|``` +code here +```|] + +c d + +!! should format a code block within a list item !! +- a b + + ~~~format + [|code|] + ~~~ +- c d + +[expect] +- a b + + [|```format + code_formatted_78 + ```|] +- c d + +!! should format a code block within a block quote !! +> a b +> +> ~~~format +> [|code|] +> ~~~ +> +> c d + +[expect] +> a b +> +[|> ```format +> code_formatted_80 +> ```|] +> +> c d + +!! should format the markdown within a markdown code block !! +a b + +````md +# [|Title|] + +```format +code +``` +```` + +c d + +[expect] +a b + +````md +[|# Title + +```format +code_formatted_80|] +``` +```` + +c d diff --git a/tests/specs/Range/Range_DefinitionLists.txt b/tests/specs/Range/Range_DefinitionLists.txt new file mode 100644 index 0000000..11d6b8a --- /dev/null +++ b/tests/specs/Range/Range_DefinitionLists.txt @@ -0,0 +1,53 @@ +!! should format a definition list from its term !! +a b + +[|Apple|] +: A fruit. +: A company. + +c d + +[expect] +a b + +Apple +[|: A fruit. +: A company.|] + +c d + +!! should format a definition list from a definition !! +a b + +Apple +: A [|fruit|]. +: A company. + +Orange +: Another fruit. + +c d + +[expect] +a b + +Apple +[|: A fruit. +: A company. + +Orange +: Another fruit.|] + +c d + +!! should not format a definition list when the range is elsewhere !! +a [| b|] + +Apple +: A fruit. + +[expect] +[|a b|] + +Apple +: A fruit. diff --git a/tests/specs/Range/Range_Footnotes.txt b/tests/specs/Range/Range_Footnotes.txt new file mode 100644 index 0000000..517e714 --- /dev/null +++ b/tests/specs/Range/Range_Footnotes.txt @@ -0,0 +1,45 @@ +!! should format a paragraph with a footnote reference !! +a b + +some [|text[^1]|] here + +[^1]: The footnote. + +[expect] +a b + +[|some text[^1] here|] + +[^1]: The footnote. + +!! should format a footnote definition !! +some text[^1] here + +[^1]: The [|footnote|]. + +[^2]: Another footnote. + +[expect] +some text[^1] here + +[|[^1]: The footnote.|] + +[^2]: Another footnote. + +!! should format a footnote definition with several paragraphs !! +some text[^1] here + +[^1]: The footnote. + + Second [|paragraph|]. + +c d + +[expect] +some text[^1] here + +[|[^1]: The footnote. + + Second paragraph.|] + +c d diff --git a/tests/specs/Range/Range_Headings.txt b/tests/specs/Range/Range_Headings.txt new file mode 100644 index 0000000..f8773b1 --- /dev/null +++ b/tests/specs/Range/Range_Headings.txt @@ -0,0 +1,110 @@ +!! should format an atx heading !! +a b + +# [|Title|] here + +c d + +[expect] +a b + +[|# Title here|] + +c d + +!! should format an atx heading with closing hashes !! +a b + +## [|Title|] ## + +c d + +[expect] +a b + +[|## Title|] + +c d + +!! should format a setext heading from its underline !! +a b + +Title here +[|---|] + +c d + +[expect] +a b + +[|## Title here|] + +c d + +!! should format only the heading when a paragraph follows it directly !! +a b + +# [|Title|] +c d + +[expect] +a b + +[|# Title|] +c d + +!! should format only the paragraph that directly follows a heading !! +# Title +[|c|] d + +e f + +[expect] +# Title +[|c d|] + +e f + +!! should format a heading with inline content !! +a b + +# [|Some|] *emphasized* `code` [link](http://example.com) + +c d + +[expect] +a b + +[|# Some _emphasized_ `code` [link](http://example.com)|] + +c d + +!! should format a heading within a block quote !! +> a b +> +> # [|Title|] +> +> c d + +[expect] +> a b +> +[|> # Title|] +> +> c d + +!! should format a heading within a list item !! +- a b + + # [|Title|] + + c d +- e f + +[expect] +- a b + + [|# Title|] + + c d +- e f diff --git a/tests/specs/Range/Range_Headings_Setext.txt b/tests/specs/Range/Range_Headings_Setext.txt new file mode 100644 index 0000000..9161cf6 --- /dev/null +++ b/tests/specs/Range/Range_Headings_Setext.txt @@ -0,0 +1,45 @@ +~~ heading.kind: setext ~~ +!! should format an atx heading as setext !! +a b + +# [|Title|] + +c d + +[expect] +a b + +[|Title +=====|] + +c d + +!! should lengthen the underline of a setext heading !! +a b + +Title [|here|] +== + +c d + +[expect] +a b + +[|Title here +==========|] + +c d + +!! should format a level three heading as atx !! +a b + +### [|Title|] + +c d + +[expect] +a b + +[|### Title|] + +c d diff --git a/tests/specs/Range/Range_HorizontalRules.txt b/tests/specs/Range/Range_HorizontalRules.txt new file mode 100644 index 0000000..976a240 --- /dev/null +++ b/tests/specs/Range/Range_HorizontalRules.txt @@ -0,0 +1,43 @@ +!! should format a horizontal rule !! +a b + +[|* * *|] + +c d + +[expect] +a b + +[|---|] + +c d + +!! should format a horizontal rule under the cursor !! +a b + +__[||]___ + +c d + +[expect] +a b + +[|---|] + +c d + +!! should format a horizontal rule within a list item !! +- a b + + [|***|] + + c d +- e f + +[expect] +- a b + + [|---|] + + c d +- e f diff --git a/tests/specs/Range/Range_Html.txt b/tests/specs/Range/Range_Html.txt new file mode 100644 index 0000000..8b835c2 --- /dev/null +++ b/tests/specs/Range/Range_Html.txt @@ -0,0 +1,63 @@ +!! should format an html block !! +a b + +
+[|

text

|] +
+ +c d + +[expect] +a b + +
+ [|

text

|] +
+ +c d + +!! should keep an html comment !! +a b + + + +c d + +[expect] +a b + + + +c d + +!! should format the markdown between html blocks !! +
+ +a [| b|] + +
+ +c d + +[expect] +
+ +[|a b|] + +
+ +c d + +!! should not format an html block when the range is elsewhere !! +a [| b|] + +
+

text

+
+ +[expect] +[|a b|] + +
+

text

+
diff --git a/tests/specs/Range/Range_IgnoreComments.txt b/tests/specs/Range/Range_IgnoreComments.txt new file mode 100644 index 0000000..6c805e7 --- /dev/null +++ b/tests/specs/Range/Range_IgnoreComments.txt @@ -0,0 +1,109 @@ +!! should not format a block with an ignore comment !! +a b + + +c [| d|] + +e f + +[expect] +a b + + +c [| d|] + +e f + +!! should not format the block after an ignore comment when the range is on the comment !! +a b + +[||] +c d + +e f + +[expect] +a b + +[||] +c d + +e f + +!! should not format blocks between ignore start and end comments !! +a b + + + +c [| d + +e |] f + + + +g h + +[expect] +a b + + + +c [| d + +e |] f + + + +g h + +!! should format the blocks around ignored ones !! +a [| b + + + +c d + + + +e |] f + +g h + +[expect] +[|a b + + + +c d + + + +e f|] + +g h + +!! should format nothing in a file with an ignore file comment !! + + +a [| b|] + +c d + +[expect] + + +a [| b|] + +c d + +!! should not format an ignored list item !! +- a b + +- c [| d|] +- e f + +[expect] +- a b + +- c [| d|] +- e f diff --git a/tests/specs/Range/Range_Links.txt b/tests/specs/Range/Range_Links.txt new file mode 100644 index 0000000..91525eb --- /dev/null +++ b/tests/specs/Range/Range_Links.txt @@ -0,0 +1,57 @@ +!! should format a link reference definition !! +[a] and [b] + +[a]: [|http://example.com/a|] "Title" +[b]: http://example.com/b + +c d + +[expect] +[a] and [b] + +[|[a]: http://example.com/a "Title"|] +[b]: http://example.com/b + +c d + +!! should format a paragraph with a reference link !! +a b + +some [|[link text][a]|] and [a] text + +[a]: http://example.com/a + +[expect] +a b + +[|some [link text][a] and [a] text|] + +[a]: http://example.com/a + +!! should format a paragraph with a reference image !! +a b + +some [|![alt text][a]|] text + +[a]: image.png + +[expect] +a b + +[|some ![alt text][a] text|] + +[a]: image.png + +!! should keep text that is not a link without its definition !! +a b + +some [|[not a link][missing]|] text + +c d + +[expect] +a b + +[|some [not a link][missing] text|] + +c d diff --git a/tests/specs/Range/Range_Lists.txt b/tests/specs/Range/Range_Lists.txt new file mode 100644 index 0000000..f1e4d3d --- /dev/null +++ b/tests/specs/Range/Range_Lists.txt @@ -0,0 +1,299 @@ +!! should format only the touched list item !! +- a b +- [|c d|] +- e f + +[expect] +- a b +[|- c d|] +- e f + +!! should format the list when its items would no longer be one list !! +* a b +* [|c d +* e|] f +* g h + +[expect] +[|- a b +- c d +- e f +- g h|] + +!! should format the touched list items !! +- a b +- [|c d +- e|] f +- g h + +[expect] +- a b +[|- c d +- e f|] +- g h + +!! should format the item within a nested list !! +- a b + - [|c d|] + - e f +- g h + +[expect] +- a b + [|- c d|] + - e f +- g h + +!! should format the outer item when the nested list's indentation changes !! +- a b + - [|c d|] + - e f +- g h + +[expect] +[|- a b + - c d + - e f|] +- g h + +!! should format an item within a deeply nested list !! +- a b + - c d + - e f + - [|g h|] + - i j + - k l +- m n + +[expect] +- a b + - c d + - e f + [|- g h|] + - i j + - k l +- m n + +!! should renumber the touched item !! +1. a b +2. c d +7. [|e f|] + +[expect] +1. a b +2. c d +[|3. e f|] + +!! should format an item of an ordered list !! +1. a b +2. [|c d|] +3. e f + +[expect] +1. a b +[|2. c d|] +3. e f + +!! should format the list when the delimiter of its numbers changes !! +1) a b +2) [|c d|] +3) e f + +[expect] +[|1. a b +2. c d +3. e f|] + +!! should format an item of a list that starts at another number !! +5. a b +6. [|c d|] +7. e f + +[expect] +5. a b +[|6. c d|] +7. e f + +!! should format an item of a list numbered with ones !! +1. a b +1. [|c d|] +1. e f + +[expect] +1. a b +[|1. c d|] +1. e f + +!! should format an ordered list nested within an unordered list !! +- a b + 1. [|c d|] + 2. e f +- g h + +[expect] +- a b + [|1. c d|] + 2. e f +- g h + +!! should format a task list item !! +- [ ] a b +- [x] [|c d|] +- [ ] e f + +[expect] +- [ ] a b +[|- [x] c d|] +- [ ] e f + +!! should format a task list item from its marker !! +- [ ] a b +- [|[x]|] c d +- [ ] e f + +[expect] +- [ ] a b +[|- [x] c d|] +- [ ] e f + +!! should format the whole list when the range covers it !! +a b + +[|- c d +- e f|] + +g h + +[expect] +a b + +[|- c d +- e f|] + +g h + +!! should format a paragraph within a list item !! +- a b + + [|c d|] + + e f + +[expect] +- a b + + [|c d|] + + e f + +!! should format the first paragraph of a list item !! +- [|a|] b + + c d +- e f + +[expect] +[|- a b|] + + c d +- e f + +!! should format an item of a loose list !! +- a b + +- [|c d|] + +- e f + +[expect] +- a b + +[|- c d|] + +- e f + +!! should format an item with a lazy continuation line !! +- a b +- c d +[|e|] f +- g h + +[expect] +- a b +[|- c d + e f|] +- g h + +!! should format the item when the range is between its blocks !! +- a b +- c d +[||] + e f +- g h + +[expect] +- a b +[|- c d + + e f|] +- g h + +!! should format a list from its marker !! +a b + +[|-|] c d +- e f + +g h + +[expect] +a b + +[|- c d|] +- e f + +g h + +!! should format only the touched list of two lists !! +- a b +- c d + +text + +- [|e f|] +- g h + +[expect] +- a b +- c d + +text + +[|- e f|] +- g h + +!! should format a block quote within a list item !! +- a b + + > [|c d|] + > + > e f +- g h + +[expect] +- a b + + [|> c d|] + > + > e f +- g h + +!! should keep an empty list item !! +- a b +-[||] +- c d + +[expect] +- a b +-[||] +- c d diff --git a/tests/specs/Range/Range_Lists_IndentKind_PythonMarkdown.txt b/tests/specs/Range/Range_Lists_IndentKind_PythonMarkdown.txt new file mode 100644 index 0000000..e1499d1 --- /dev/null +++ b/tests/specs/Range/Range_Lists_IndentKind_PythonMarkdown.txt @@ -0,0 +1,38 @@ +~~ list.indentKind: pythonMarkdown ~~ +!! should format an item of a nested list indented by four spaces !! +- a b + - [|c d|] + - e f +- g h + +[expect] +- a b + [|- c d|] + - e f +- g h + +!! should format the outer item when the nested list is indented by two spaces !! +- a b + - [|c d|] + - e f +- g h + +[expect] +[|- a b + - c d + - e f|] +- g h + +!! should format an item of an ordered list !! +1. a b +2. [|c d|] + + e f +3. g h + +[expect] +1. a b +[|2. c d|] + + e f +3. g h diff --git a/tests/specs/Range/Range_Lists_UnorderedMarker_Asterisks.txt b/tests/specs/Range/Range_Lists_UnorderedMarker_Asterisks.txt new file mode 100644 index 0000000..d94dac8 --- /dev/null +++ b/tests/specs/Range/Range_Lists_UnorderedMarker_Asterisks.txt @@ -0,0 +1,24 @@ +~~ list.unorderedMarker: asterisks ~~ +!! should format only the touched item of a list with the configured marker !! +* a b +* [|c d|] +* e f + +[expect] +* a b +[|* c d|] +* e f + +!! should format the list when its marker changes !! +- a b +- [|c d|] +- e f + +g h + +[expect] +[|* a b +* c d +* e f|] + +g h diff --git a/tests/specs/Range/Range_MathBlocks.txt b/tests/specs/Range/Range_MathBlocks.txt new file mode 100644 index 0000000..ac24284 --- /dev/null +++ b/tests/specs/Range/Range_MathBlocks.txt @@ -0,0 +1,35 @@ +!! should keep a math block !! +a b + +$$ +x [|+|] y +$$ + +c d + +[expect] +a b + +$$ +x [|+|] y +$$ + +c d + +!! should format the paragraph after a math block !! +$$ +x + y +$$ + +c [| d|] + +e f + +[expect] +$$ +x + y +$$ + +[|c d|] + +e f diff --git a/tests/specs/Range/Range_Paragraphs.txt b/tests/specs/Range/Range_Paragraphs.txt new file mode 100644 index 0000000..3f323ac --- /dev/null +++ b/tests/specs/Range/Range_Paragraphs.txt @@ -0,0 +1,263 @@ +!! should format emphasis and strong !! +a b + +some *emphasis* and [|__strong__|] text + +c d + +[expect] +a b + +[|some _emphasis_ and **strong** text|] + +c d + +!! should format strikethrough !! +a b + +some [|~~deleted~~|] text + +c d + +[expect] +a b + +[|some ~~deleted~~ text|] + +c d + +!! should format inline code !! +a b + +some [|`code here`|] text + +c d + +[expect] +a b + +[|some `code here` text|] + +c d + +!! should format an inline link !! +a b + +some [|[link text](http://example.com "title")|] text + +c d + +[expect] +a b + +[|some [link text](http://example.com "title") text|] + +c d + +!! should format an autolink !! +a b + +some [||] text + +c d + +[expect] +a b + +[|some text|] + +c d + +!! should format an image !! +a b + +some [|![alt text](image.png "title")|] text + +c d + +[expect] +a b + +[|some ![alt text](image.png "title") text|] + +c d + +!! should format inline html !! +a b + +some [|inline|] text + +c d + +[expect] +a b + +[|some inline text|] + +c d + +!! should format inline math !! +a b + +some [|$x + y$|] text + +c d + +[expect] +a b + +[|some $x + y$ text|] + +c d + +!! should keep a hard break !! +a b + +some text\ +[|more|] text + +c d + +[expect] +a b + +[|some text\ +more text|] + +c d + +!! should keep a soft break !! +a b + +some text +[|more|] text + +c d + +[expect] +a b + +[|some text +more text|] + +c d + +!! should keep character escapes !! +a b + +\* not [|a|] list \# and not a heading + +c d + +[expect] +a b + +[|\* not a list \# and not a heading|] + +c d + +!! should format non-ascii text !! +a b + +日本語 [|😀|] café + +c d + +[expect] +a b + +[|日本語 😀 café|] + +c d + +!! should format the paragraphs the range starts and ends within !! +a b + +c [| d + +e |] f + +g h + +[expect] +a b + +[|c d + +e f|] + +g h + +!! should format a paragraph the range ends at the start of the line after !! +a b + +[|c d +|] +e f + +[expect] +a b + +[|c d|] + +e f + +!! should not format the paragraph the range ends at the start of !! +a b + +[|c d + +|]e f + +g h + +[expect] +a b + +[|c d|] + +e f + +g h + +!! should format a paragraph when the cursor is at its end !! +a b + +c d[||] + +e f + +[expect] +a b + +[|c d|] + +e f + +!! should format a paragraph when the cursor is at its start !! +a b + +[||]c d + +e f + +[expect] +a b + +[|c d|] + +e f + +!! should format nothing when the range is past the last block !! +a b + +c d + +[||] + +[expect] +a b + +c d + +[||] diff --git a/tests/specs/Range/Range_Paragraphs_TextWrap_Always.txt b/tests/specs/Range/Range_Paragraphs_TextWrap_Always.txt new file mode 100644 index 0000000..dfc0ae6 --- /dev/null +++ b/tests/specs/Range/Range_Paragraphs_TextWrap_Always.txt @@ -0,0 +1,59 @@ +~~ lineWidth: 20, textWrap: always ~~ +!! should wrap only the touched paragraph !! +this is a long line that would wrap + +this is a [|long|] line that would wrap + +this is a long line that would wrap + +[expect] +this is a long line that would wrap + +[|this is a long line +that would wrap|] + +this is a long line that would wrap + +!! should join the lines of the touched paragraph !! +a +b + +c +[|d|] +e + +f +g + +[expect] +a +b + +[|c d e|] + +f +g + +!! should wrap a paragraph within a block quote at its width !! +> this is a long line that would wrap +> +> this is a [|long|] line that would wrap + +[expect] +> this is a long line that would wrap +> +[|> this is a long +> line that would +> wrap|] + +!! should wrap a paragraph within a list item at its width !! +- this is a long line that would wrap +- this is a [|long|] line that would wrap +- this is a long line that would wrap + +[expect] +- this is a long line that would wrap +[|- this is a long + line that would + wrap|] +- this is a long line that would wrap diff --git a/tests/specs/Range/Range_Paragraphs_TextWrap_Never.txt b/tests/specs/Range/Range_Paragraphs_TextWrap_Never.txt new file mode 100644 index 0000000..7c921e8 --- /dev/null +++ b/tests/specs/Range/Range_Paragraphs_TextWrap_Never.txt @@ -0,0 +1,20 @@ +~~ textWrap: never ~~ +!! should join the lines of only the touched paragraph !! +a +b + +c +[|d|] +e + +f +g + +[expect] +a +b + +[|c d e|] + +f +g diff --git a/tests/specs/Range/Range_Tables.txt b/tests/specs/Range/Range_Tables.txt new file mode 100644 index 0000000..b9f6f69 --- /dev/null +++ b/tests/specs/Range/Range_Tables.txt @@ -0,0 +1,121 @@ +!! should format a table from one of its cells !! +a b + +| Name | Value | +|---|---| +| [|a|] | 1 | +| long name | 2 | + +c d + +[expect] +a b + +[|| Name | Value | +| --------- | ----- | +| a | 1 | +| long name | 2 ||] + +c d + +!! should format a table from its header !! +a b + +| [|Name|] | Value | +|:--|--:| +| a | 1 | + +c d + +[expect] +a b + +| Name | Value | +[|| :--- | ----: | +| a | 1 ||] + +c d + +!! should format a table from its delimiter row !! +a b + +| Name | Value | +|:-[|-|]:|---| +| a | 1 | + +c d + +[expect] +a b + +| Name | Value | +[|| :--: | ----- | +| a | 1 ||] + +c d + +!! should not format a table when the range is elsewhere !! +a [| b|] + +| Name | Value | +|---|---| +| a | 1 | + +[expect] +[|a b|] + +| Name | Value | +|---|---| +| a | 1 | + +!! should format the paragraph after a table !! +| Name | Value | +|---|---| +| a | 1 | + +c [| d|] + +e f + +[expect] +| Name | Value | +|---|---| +| a | 1 | + +[|c d|] + +e f + +!! should format a table within a list item !! +- a b + + | Name | Value | + |---|---| + | [|a|] | 1 | +- c d + +[expect] +- a b + + | Name | Value | + [|| ---- | ----- | + | a | 1 ||] +- c d + +!! should format a table within a block quote !! +> a b +> +> | Name | Value | +> |---|---| +> | [|a|] | 1 | +> +> c d + +[expect] +> a b +> +> | Name | Value | +[|> | ---- | ----- | +> | a | 1 ||] +> +> c d diff --git a/tests/specs/Range/Range_TextDecoration_Kinds.txt b/tests/specs/Range/Range_TextDecoration_Kinds.txt new file mode 100644 index 0000000..241d862 --- /dev/null +++ b/tests/specs/Range/Range_TextDecoration_Kinds.txt @@ -0,0 +1,14 @@ +~~ emphasisKind: asterisks, strongKind: underscores ~~ +!! should use the configured kinds in only the touched paragraph !! +_a_ and **b** + +_a_ and [|**b**|] + +_a_ and **b** + +[expect] +_a_ and **b** + +[|*a* and __b__|] + +_a_ and **b** diff --git a/tests/specs/Range/Range_YamlHeader.txt b/tests/specs/Range/Range_YamlHeader.txt new file mode 100644 index 0000000..014d706 --- /dev/null +++ b/tests/specs/Range/Range_YamlHeader.txt @@ -0,0 +1,61 @@ +!! should not format the front matter when the range is elsewhere !! +--- +title: Title +--- + +a [| b|] + +c d + +[expect] +--- +title: Title +--- + +[|a b|] + +c d + +!! should keep the front matter when the range is within it !! +--- +title: [|Title|] +--- + +a b + +[expect] +--- +title: [|Title|] +--- + +a b + +!! should format the block after the front matter !! +--- +title: Title +--- +# [|Heading|] + +a b + +[expect] +--- +title: Title +--- +[|# Heading|] + +a b + +!! should keep plus style front matter !! ++++ +title = [|"Title"|] ++++ + +a b + +[expect] ++++ +title = [|"Title"|] ++++ + +a b