Region and paragraph commands: indent, reflow, counted deletes

- Indent-region skips empty lines. With the whole buffer selected, the
  empty row after the final newline became a tab-only last line.
- Reflow no longer wraps before a line's first word: a first word longer
  than the width (a URL) got a prefix-only line before it, a blank line
  or a bullet marker split from its text, and another on every reflow.
- A whitespace-only line (spaces, tabs, a CR) separates paragraphs for
  reflow; such paragraphs were merged.
- Reflow of CRLF lines strips the CR before wrapping and restores it on
  every output line; the CRs ended up in the middle of joined lines.
- A counted backspace or delete-char is one undo step even when it joins
  lines.
- The C++ highlighter stops looking for a raw-string '(' after 16
  delimiter characters; scanning the whole line at every R" was
  quadratic.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015rcVQiiWyTzoNrsvLSaAfB
This commit is contained in:
Claude
2026-09-24 09:16:04 -07:00
committed by Kyle Isom
parent b9f91b0ed2
commit 790fbc90e1
4 changed files with 152 additions and 16 deletions
+85
View File
@@ -1532,3 +1532,88 @@ TEST(Audit_Swap_LongBasenameStillJournaled)
sm.Detach(&a, true);
ASSERT_TRUE(!std::filesystem::exists(swp));
}
// Indenting a region leaves empty lines alone: with the whole buffer
// selected, the empty row after the final newline became a tab-only line.
TEST(Audit_IndentRegion_SkipsEmptyLines)
{
TestHarness h;
h.Buf().insert_text(0, 0, "a\n\nb\n");
h.Buf().SetCursor(0, 0);
ASSERT_TRUE(h.Exec(CommandId::MarkAllAndJumpEnd));
ASSERT_TRUE(h.Exec(CommandId::IndentRegion));
ASSERT_EQ(h.Text(), std::string("\ta\n\n\tb\n"));
ASSERT_TRUE(h.Undo());
ASSERT_EQ(h.Text(), std::string("a\n\nb\n"));
}
// Reflow never wraps before a line's first word: a first word longer than
// the width produced a prefix-only line (another one on each reflow).
TEST(Audit_Reflow_LongFirstWordNoBlankLine)
{
const std::string url = "https://example.com/" + std::string(70, 'x');
{
TestHarness h;
h.Buf().insert_text(0, 0, "see:\n\n" + url + " is the link\n");
h.Buf().SetCursor(0, 2);
ASSERT_TRUE(h.Exec(CommandId::ReflowParagraph));
ASSERT_TRUE(h.Exec(CommandId::ReflowParagraph));
ASSERT_EQ(h.Text(), std::string("see:\n\n" + url + "\nis the link\n"));
}
{
TestHarness h;
h.Buf().insert_text(0, 0, "- " + url + " x\n");
h.Buf().SetCursor(0, 0);
ASSERT_TRUE(h.Exec(CommandId::ReflowParagraph));
ASSERT_EQ(h.Text(), std::string("- " + url + "\n x\n"));
}
}
// A whitespace-only line separates paragraphs, and CRLF lines keep their
// CR at the end of each reflowed line.
TEST(Audit_Reflow_BlankishSeparatorAndCrlf)
{
{
TestHarness h;
h.Buf().insert_text(0, 0, "First paragraph.\n \nSecond paragraph.\n");
h.Buf().SetCursor(0, 0);
ASSERT_TRUE(h.Exec(CommandId::ReflowParagraph));
ASSERT_EQ(h.Text(), std::string("First paragraph.\n \nSecond paragraph.\n"));
}
{
TestHarness h;
h.Buf().insert_text(0, 0, "one two\r\nthree four\r\nfive\r\n\r\nnext\r\n");
h.Buf().SetCursor(0, 0);
ASSERT_TRUE(h.Exec(CommandId::ReflowParagraph));
ASSERT_EQ(h.Text(), std::string("one two three four five\r\n\r\nnext\r\n"));
ASSERT_TRUE(h.Undo());
ASSERT_EQ(h.Text(), std::string("one two\r\nthree four\r\nfive\r\n\r\nnext\r\n"));
}
}
// A counted backspace or delete that joins lines undoes in one step.
TEST(Audit_CountedDelete_AcrossJoin_OneUndo)
{
{
TestHarness h;
h.Buf().insert_text(0, 0, "abc\nde");
h.Buf().SetCursor(1, 1);
ASSERT_TRUE(h.Exec(CommandId::Backspace, "", 3));
ASSERT_EQ(h.Text(), std::string("abe"));
ASSERT_TRUE(h.Undo());
ASSERT_EQ(h.Text(), std::string("abc\nde"));
}
{
TestHarness h;
h.Buf().insert_text(0, 0, "abc\nde");
h.Buf().SetCursor(2, 0);
ASSERT_TRUE(h.Exec(CommandId::DeleteChar, "", 3));
ASSERT_EQ(h.Text(), std::string("abe"));
ASSERT_TRUE(h.Undo());
ASSERT_EQ(h.Text(), std::string("abc\nde"));
}
}