difftreelog
feat(fmt) reformat text block
in: master
6 files changed
crates/jrsonnet-formatter/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-formatter/src/lib.rs
+++ b/crates/jrsonnet-formatter/src/lib.rs
@@ -9,6 +9,7 @@
};
use hi_doc::{Formatting, SnippetBuilder};
use jrsonnet_rowan_parser::{
+ collect_lexed_str_block,
nodes::{
Arg, ArgsDesc, Assertion, BinaryOperator, Bind, CompSpec, Destruct, DestructArrayPart,
DestructRest, Expr, ExprBase, FieldName, ForSpec, IfSpec, ImportKind, Literal, Member,
@@ -83,6 +84,14 @@
$o.push_signal(dprint_core::formatting::Signal::FinishIndent);
pi!(@s; $o: $($t)*);
}};
+ (@s; $o:ident: >ii $($t:tt)*) => {{
+ $o.push_signal(dprint_core::formatting::Signal::StartIgnoringIndent);
+ pi!(@s; $o: $($t)*);
+ }};
+ (@s; $o:ident: <ii $($t:tt)*) => {{
+ $o.push_signal(dprint_core::formatting::Signal::FinishIgnoringIndent);
+ pi!(@s; $o: $($t)*);
+ }};
(@s; $o:ident: info($v:expr) $($t:tt)*) => {{
$o.push_info($v);
pi!(@s; $o: $($t)*);
@@ -201,14 +210,28 @@
fn print(&self, out: &mut PrintItems) {
if matches!(self.kind(), TextKind::StringBlock) {
let text = self.text();
+ let mut text = collect_lexed_str_block(&text[3..])
+ .expect("formatting is not performed on code with parsing errors");
- for (i, ele) in text.split("\n").enumerate() {
- if i != 0 {
- p!(out, nl);
+ if text.truncate && text.lines.ends_with(&[""]) {
+ text.truncate = false;
+ text.lines.pop();
+ }
+
+ p!(out, str("|||"));
+ if text.truncate {
+ p!(out, str("-"));
+ }
+ p!(out, nl > i);
+ for ele in text.lines {
+ if ele.is_empty() {
+ p!(out, >ii nl <ii);
+ } else {
+ p!(out, string(ele.to_string()) nl);
}
- // TODO: Trim and recreate whitespace
- p!(out, string(ele.to_string()));
}
+ p!(out, <i str("|||"));
+
return;
}
p!(out, string(format!("{}", self)));
crates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__snapshots@string_styles.jsonnet.snapdiffbeforeafterboth--- a/crates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__snapshots@string_styles.jsonnet.snap
+++ b/crates/jrsonnet-formatter/src/snapshots/jrsonnet_formatter__tests__snapshots@string_styles.jsonnet.snap
@@ -8,7 +8,18 @@
single_quote: 'hello world',
escaped: 'line1\nline2',
multiline: |||
- This is a
- multiline string
- |||,
+ This is a
+
+ multiline string
+ |||,
+ multiline_truncated: |||-
+ This is a
+
+ multiline string with truncated newline
+ |||,
+ multiline_to_truncated: |||
+ This is a
+
+ multiline string with to-be truncated newline
+ |||,
}
crates/jrsonnet-formatter/src/tests.rsdiffbeforeafterboth--- a/crates/jrsonnet-formatter/src/tests.rs
+++ b/crates/jrsonnet-formatter/src/tests.rs
@@ -3,7 +3,6 @@
use std::fs;
use dprint_core::formatting::{PrintItems, PrintOptions};
-use indoc::indoc;
use insta::{assert_snapshot, glob};
use crate::Printable;
crates/jrsonnet-formatter/src/tests/string_styles.jsonnetdiffbeforeafterboth--- a/crates/jrsonnet-formatter/src/tests/string_styles.jsonnet
+++ b/crates/jrsonnet-formatter/src/tests/string_styles.jsonnet
@@ -4,6 +4,18 @@
escaped: 'line1\nline2',
multiline: |||
This is a
+
multiline string
|||,
+ multiline_truncated: |||-
+ This is a
+
+ multiline string with truncated newline
+ |||,
+ multiline_to_truncated: |||-
+ This is a
+
+ multiline string with to-be truncated newline
+
+ |||,
}
crates/jrsonnet-rowan-parser/src/lib.rsdiffbeforeafterboth--- a/crates/jrsonnet-rowan-parser/src/lib.rs
+++ b/crates/jrsonnet-rowan-parser/src/lib.rs
@@ -22,6 +22,7 @@
pub use generated::{nodes, syntax_kinds::SyntaxKind};
pub use language::*;
pub use token_set::SyntaxKindSet;
+pub use string_block::{collect_lexed_str_block, CollectStrBlock};
use self::{
ast::support,
crates/jrsonnet-rowan-parser/src/string_block.rsdiffbeforeafterboth1#[derive(Clone, Copy, Debug, PartialEq, Eq)]2pub enum StringBlockError {3 UnexpectedEnd,4 MissingNewLine,5 MissingTermination,6 MissingIndent,7}89use std::ops::Range;1011use logos::Lexer;12use StringBlockError::*;1314use crate::SyntaxKind;1516pub fn lex_str_block_test(lex: &mut Lexer<SyntaxKind>) {17 let _ = lex_str_block(lex);18}1920#[allow(clippy::too_many_lines)]21pub fn lex_str_block(lex: &mut Lexer<SyntaxKind>) -> Result<(), StringBlockError> {22 struct Context<'a> {23 source: &'a str,24 index: usize,25 offset: usize,26 }2728 impl<'a> Context<'a> {29 fn rest(&self) -> &'a str {30 &self.source[self.index..]31 }3233 fn next(&mut self) -> Option<char> {34 if self.index == self.source.len() {35 return None;36 }3738 match self.rest().chars().next() {39 None => None,40 Some(c) => {41 self.index += c.len_utf8();42 Some(c)43 }44 }45 }4647 fn peek(&self) -> Option<char> {48 if self.index == self.source.len() {49 return None;50 }5152 self.rest().chars().next()53 }5455 fn eat_if(&mut self, f: impl Fn(char) -> bool) -> usize {56 if self.peek().map(f).unwrap_or(false) {57 self.index += 1;58 return 1;59 }60 061 }6263 fn eat_while(&mut self, f: impl Fn(char) -> bool) -> usize {64 if self.index == self.source.len() {65 return 0;66 }6768 let next_char = self.rest().char_indices().find(|(_, c)| !f(*c));6970 match next_char {71 None => {72 let diff = self.source.len() - self.index;73 self.index = self.source.len();74 diff75 }76 Some((idx, _)) => {77 self.index += idx;78 idx79 }80 }81 }8283 fn skip(&mut self, len: usize) {84 self.index = match self.index + len {85 n if n > self.source.len() => self.source.len(),86 n => n,87 };88 }8990 #[allow(clippy::range_plus_one)]91 fn pos(&self) -> Range<usize> {92 if self.index == self.source.len() {93 self.offset + self.index..self.offset + self.index94 } else {95 // TODO: char size96 self.offset + self.index..self.offset + self.index + 197 }98 }99 }100101 // Check that b has at least the same whitespace prefix as a and returns the102 // amount of this whitespace, otherwise returns 0. If a has no whitespace103 // prefix than return 0.104 fn check_whitespace(a: &str, b: &str) -> usize {105 let a = a.as_bytes();106 let b = b.as_bytes();107108 for i in 0..a.len() {109 if a[i] != b' ' && a[i] != b'\t' {110 // a has run out of whitespace and b matched up to this point. Return result.111 return i;112 }113114 if i >= b.len() {115 // We ran off the edge of b while a still has whitespace. Return 0 as failure.116 return 0;117 }118119 if a[i] != b[i] {120 // a has whitespace but b does not. Return 0 as failure.121 return 0;122 }123 }124125 // We ran off the end of a and b kept up126 a.len()127 }128129 fn guess_token_end_and_bump<'a>(lex: &mut Lexer<'a, SyntaxKind>, ctx: &Context<'a>) {130 let end_index = ctx131 .rest()132 .find("|||")133 .map_or_else(|| ctx.rest().len(), |v| v + 3);134 lex.bump(ctx.index + end_index);135 }136137 debug_assert_eq!(lex.slice(), "|||");138 let mut ctx = Context {139 source: lex.remainder(),140 index: 0,141 offset: lex.span().end,142 };143144 ctx.eat_if(|v| v == '-');145146 // Skip whitespaces147 ctx.eat_while(|r| r == ' ' || r == '\t' || r == '\r');148149 // Skip \n150 match ctx.next() {151 Some('\n') => (),152 None => {153 guess_token_end_and_bump(lex, &ctx);154 return Err(UnexpectedEnd);155 }156 // Text block requires new line after |||.157 Some(_) => {158 guess_token_end_and_bump(lex, &ctx);159 return Err(MissingNewLine);160 }161 }162163 // Process leading blank lines before calculating string block indent164 while ctx.peek() == Some('\n') {165 ctx.next();166 }167168 let mut num_whitespace = check_whitespace(ctx.rest(), ctx.rest());169 let str_block_indent = &ctx.rest()[..num_whitespace];170171 if num_whitespace == 0 {172 // Text block's first line must start with whitespace173 guess_token_end_and_bump(lex, &ctx);174 return Err(MissingIndent);175 }176177 loop {178 debug_assert_ne!(num_whitespace, 0, "Unexpected value for num_whitespace");179 ctx.skip(num_whitespace);180181 loop {182 match ctx.next() {183 None => {184 guess_token_end_and_bump(lex, &ctx);185 return Err(UnexpectedEnd);186 }187 Some('\n') => break,188 Some(_) => (),189 }190 }191192 // Skip any blank lines193 while ctx.peek() == Some('\n') {194 ctx.next();195 }196197 // Look at the next line198 num_whitespace = check_whitespace(str_block_indent, ctx.rest());199 if num_whitespace == 0 {200 // End of the text block201 // let mut term_indent = String::with_capacity(num_whitespace);202 while let Some(' ' | '\t') = ctx.peek() {203 // term_indent.push(204 ctx.next().unwrap();205 // );206 }207208 if !ctx.rest().starts_with("|||") {209 // Text block not terminated with |||210 let pos = ctx.pos();211 if pos.is_empty() {212 // eof213 lex.bump(ctx.index);214 return Err(UnexpectedEnd);215 }216217 guess_token_end_and_bump(lex, &ctx);218 return Err(MissingTermination);219 }220221 // Skip '|||'222 ctx.skip(3);223 break;224 }225 }226227 lex.bump(ctx.index);228 Ok(())229}