Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,9 @@ jobs:
- uses: actions/checkout@v6
with:
submodules: true
- uses: ruby/setup-ruby@v1
with:
ruby-version: '3.4'
- if: runner.os == 'macOS'
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
Expand Down
10 changes: 5 additions & 5 deletions fixtures/small/heredoc_indented_whitespace_expected.rb
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
def foo
<<~THING
awfweaf
awfweaf
awefawef



hi there
THING

<<-THING

hi there
THING
end
53 changes: 47 additions & 6 deletions librubyfmt/src/format_prism.rs
Original file line number Diff line number Diff line change
Expand Up @@ -931,9 +931,12 @@ fn format_inner_string<'src>(
.count()
- escaped_ws_count;

let raw_leading_cols = count_indent_cols(&raw[..raw_leading]);
let unescaped_leading_cols = count_indent_cols(&unescaped[..unescaped_leading]);

// The difference is the common indent (if raw has more leading whitespace)
if raw_leading > unescaped_leading {
return Some(raw_leading - unescaped_leading);
if raw_leading_cols > unescaped_leading_cols {
return Some(raw_leading_cols - unescaped_leading_cols);
}
}
None
Expand All @@ -960,11 +963,10 @@ fn format_inner_string<'src>(
.enumerate()
.map(|(line_idx, line)| {
// Strip from lines at line boundaries
let should_strip = (line_idx > 0 || prev_ended_with_newline)
&& !line.is_empty()
&& line.len() >= common_indent;
let should_strip =
(line_idx > 0 || prev_ended_with_newline) && !line.is_empty();
if should_strip {
&line[common_indent..]
strip_indent_cols(line, common_indent)
} else {
line
}
Expand Down Expand Up @@ -5167,3 +5169,42 @@ fn format_write_node<'src>(
ps.emit_space();
ps.with_start_of_line(false, |ps| format_node(ps, value));
}

/// Count leading whitespace columns. Tabs advance to the next multiple of 8.
fn count_indent_cols(line: &[u8]) -> usize {
let mut col = 0;
for &b in line {
match b {
b' ' => col += 1,
b'\t' => col = (col / 8 + 1) * 8,
_ => break,
}
}
col
}

/// Strip up to `cols` leading whitespace columns from `line`.
/// Tabs advance to the next multiple of 8.
/// If a tab would advance past `cols`, it is not stripped.
fn strip_indent_cols(line: &[u8], cols: usize) -> &[u8] {
let mut col = 0;
let mut i = 0;
while i < line.len() && col < cols {
match line[i] {
b' ' => {
col += 1;
i += 1;
}
b'\t' => {
col = (col / 8 + 1) * 8;
if col > cols {
// We are in the middle of a tab. Do not strip it.
break;
}
i += 1;
}
_ => break,
}
}
&line[i..]
}
25 changes: 17 additions & 8 deletions librubyfmt/src/heredoc_string.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,16 @@ impl<'src> HeredocString<'src> {
let indent = self.indent;

if self.kind.is_squiggly() {
// For squiggly heredocs, we need to apply indentation to Normal segments
// but not to Raw segments (which come from nested non-squiggly heredocs).
// Do not indent raw segments (nested non-squiggly heredocs).
// Do not indent when all normal segments contain only whitespace.
let should_indent = self.segments.iter().any(|seg| {
if let HeredocSegment::Normal(c) = seg {
c.iter().any(|&b| b != b' ' && b != b'\t' && b != b'\n')
} else {
false
}
});

let mut result = Vec::new();
for segment in self.segments {
match segment {
Expand All @@ -79,9 +87,10 @@ impl<'src> HeredocString<'src> {
if i > 0 {
result.push(b'\n');
}
let mut indented = get_indent(indent as usize + 2).into_owned();
indented.extend_from_slice(line);
result.extend_from_slice(indented.trim_ascii_end());
if !line.is_empty() && should_indent {
result.extend_from_slice(get_indent(indent as usize + 2).as_ref());
}
result.extend_from_slice(line);
}
}
HeredocSegment::Raw(content) => {
Expand All @@ -90,14 +99,14 @@ impl<'src> HeredocString<'src> {
if i > 0 {
result.push(b'\n');
}
result.extend_from_slice(line.trim_ascii_end());
result.extend_from_slice(line);
}
}
}
}
result
} else {
// For non-squiggly heredocs, just join segments and trim line endings
// For non-squiggly heredocs, just join segments
let mut result = Vec::new();
for segment in self.segments {
let content = match segment {
Expand All @@ -107,7 +116,7 @@ impl<'src> HeredocString<'src> {
if i > 0 {
result.push(b'\n');
}
result.extend_from_slice(line.trim_ascii_end());
result.extend_from_slice(line);
}
}
result
Expand Down
Loading
Loading