Reflow: per-line CR handling; do not wrap before a marker-like word
- The CRLF handling applied only when every line of the paragraph ended
in CR, so the last paragraph of a CRLF file without a final newline
(and mixed CRLF/LF paragraphs) still had CRs joined into the middle of
lines. Each line's CR is now stripped; if any line had one, every new
line but the last ends in CR, and the last keeps what the paragraph's
last line had.
- Wrapping never starts a line with a word that reads as a list marker
('-', '*', '+', '1.', '2)'); the next reflow turned such a paragraph
into a list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
This commit is contained in:
+23
-6
@@ -5172,15 +5172,19 @@ cmd_reflow_paragraph(CommandContext &ctx)
|
|||||||
if (para_start > para_end)
|
if (para_start > para_end)
|
||||||
return false;
|
return false;
|
||||||
// CRLF text: reflow the lines without their CR (it would otherwise be
|
// CRLF text: reflow the lines without their CR (it would otherwise be
|
||||||
// joined into the middle of lines) and end every new line with one.
|
// joined into the middle of lines). If any line had one, every new line
|
||||||
bool crlf = true;
|
// but the last gets one; the last keeps whatever the paragraph's last
|
||||||
for (std::size_t i = para_start; i <= para_end && crlf; ++i) {
|
// line had (a CRLF file often lacks the final line ending).
|
||||||
|
bool crlf = false;
|
||||||
|
for (std::size_t i = para_start; i <= para_end && !crlf; ++i) {
|
||||||
const std::string_view lv = line_view(rows[i]);
|
const std::string_view lv = line_view(rows[i]);
|
||||||
crlf = !lv.empty() && lv.back() == '\r';
|
crlf = !lv.empty() && lv.back() == '\r';
|
||||||
}
|
}
|
||||||
|
const std::string_view last_lv = line_view(rows[para_end]);
|
||||||
|
const bool last_cr = !last_lv.empty() && last_lv.back() == '\r';
|
||||||
auto row_text = [&](std::size_t i) {
|
auto row_text = [&](std::size_t i) {
|
||||||
std::string t = static_cast<std::string>(rows[i]);
|
std::string t = static_cast<std::string>(rows[i]);
|
||||||
if (crlf)
|
if (!t.empty() && t.back() == '\r')
|
||||||
t.pop_back();
|
t.pop_back();
|
||||||
return t;
|
return t;
|
||||||
};
|
};
|
||||||
@@ -5266,6 +5270,15 @@ cmd_reflow_paragraph(CommandContext &ctx)
|
|||||||
return out.substr(start, end - start);
|
return out.substr(start, end - start);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
auto looks_like_marker = [](const std::string &wrd) {
|
||||||
|
if (wrd == "-" || wrd == "+" || wrd == "*")
|
||||||
|
return true;
|
||||||
|
std::size_t i = 0;
|
||||||
|
while (i < wrd.size() && std::isdigit(static_cast<unsigned char>(wrd[i])))
|
||||||
|
++i;
|
||||||
|
return i > 0 && i + 1 == wrd.size() && (wrd[i] == '.' || wrd[i] == ')');
|
||||||
|
};
|
||||||
|
|
||||||
auto wrap_with_prefixes = [&](const std::string &content,
|
auto wrap_with_prefixes = [&](const std::string &content,
|
||||||
const std::string &first_prefix,
|
const std::string &first_prefix,
|
||||||
const std::string &cont_prefix,
|
const std::string &cont_prefix,
|
||||||
@@ -5305,7 +5318,11 @@ cmd_reflow_paragraph(CommandContext &ctx)
|
|||||||
// first word on a line: that emitted a line holding only the
|
// first word on a line: that emitted a line holding only the
|
||||||
// prefix (a blank line, or a bullet marker split from its text)
|
// prefix (a blank line, or a bullet marker split from its text)
|
||||||
// ahead of a word longer than the width, again on every reflow.
|
// ahead of a word longer than the width, again on every reflow.
|
||||||
if (!first_word_on_line && static_cast<int>(cur_len + needed) > w) {
|
// Nor before a word that would read as a list marker at the
|
||||||
|
// start of a line ("-", "*", "1."): the next reflow would turn
|
||||||
|
// the paragraph into a list. Such a word stays on this line.
|
||||||
|
if (!first_word_on_line && static_cast<int>(cur_len + needed) > w &&
|
||||||
|
!looks_like_marker(wrd)) {
|
||||||
flush_line();
|
flush_line();
|
||||||
}
|
}
|
||||||
if (!first_word_on_line) {
|
if (!first_word_on_line) {
|
||||||
@@ -5474,7 +5491,7 @@ cmd_reflow_paragraph(CommandContext &ctx)
|
|||||||
if (i > 0)
|
if (i > 0)
|
||||||
new_text.push_back('\n');
|
new_text.push_back('\n');
|
||||||
new_text += new_lines[i];
|
new_text += new_lines[i];
|
||||||
if (crlf)
|
if (crlf && (i + 1 < new_lines.size() || last_cr))
|
||||||
new_text.push_back('\r');
|
new_text.push_back('\r');
|
||||||
}
|
}
|
||||||
if (new_text != old_text)
|
if (new_text != old_text)
|
||||||
|
|||||||
@@ -1592,6 +1592,37 @@ TEST(Audit_Reflow_BlankishSeparatorAndCrlf)
|
|||||||
ASSERT_TRUE(h.Undo());
|
ASSERT_TRUE(h.Undo());
|
||||||
ASSERT_EQ(h.Text(), std::string("one two\r\nthree four\r\nfive\r\n\r\nnext\r\n"));
|
ASSERT_EQ(h.Text(), std::string("one two\r\nthree four\r\nfive\r\n\r\nnext\r\n"));
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
// CRLF file without a final line ending: the last line has no CR.
|
||||||
|
TestHarness h;
|
||||||
|
h.Buf().insert_text(0, 0, "aaa bbb\r\nccc ddd\r\neee");
|
||||||
|
h.Buf().SetCursor(0, 0);
|
||||||
|
ASSERT_TRUE(h.Exec(CommandId::ReflowParagraph));
|
||||||
|
ASSERT_EQ(h.Text(), std::string("aaa bbb ccc ddd eee"));
|
||||||
|
}
|
||||||
|
{
|
||||||
|
TestHarness h;
|
||||||
|
h.Buf().insert_text(0, 0, "aaa bbb\r\nccc ddd\r\neee");
|
||||||
|
h.Buf().SetCursor(0, 0);
|
||||||
|
ASSERT_TRUE(h.Exec(CommandId::ReflowParagraph, "", 8));
|
||||||
|
ASSERT_EQ(h.Text(), std::string("aaa bbb\r\nccc ddd\r\neee"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Reflow does not wrap right before a word that would then read as a list
|
||||||
|
// marker ("-", "1."), which turned the paragraph into a list next time.
|
||||||
|
TEST(Audit_Reflow_NoWrapBeforeMarkerWord)
|
||||||
|
{
|
||||||
|
TestHarness h;
|
||||||
|
h.Buf().insert_text(0, 0, "alpha beta - gamma delta\n");
|
||||||
|
h.Buf().SetCursor(0, 0);
|
||||||
|
ASSERT_TRUE(h.Exec(CommandId::ReflowParagraph, "", 11));
|
||||||
|
const std::string once = h.Text();
|
||||||
|
ASSERT_EQ(once, std::string("alpha beta -\ngamma delta\n"));
|
||||||
|
h.Buf().SetCursor(0, 0);
|
||||||
|
ASSERT_TRUE(h.Exec(CommandId::ReflowParagraph, "", 11));
|
||||||
|
ASSERT_EQ(h.Text(), once);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user