Fix GUI paste of CRLF/CR text: split all line endings

The ImGui Cmd+V/Ctrl+V paste handler split clipboard text only on '\n',
leaving '\r' inside each segment. Pasting text with CRLF (Windows) or bare
CR (classic-Mac / some macOS apps) line endings enqueued an InsertText arg
containing '\r', which the InsertText command rejects with "InsertText arg
must not contain newlines".

Extract the paste-to-commands logic into a pure, SDL-free helper
(PasteSplit.h) that treats '\n', '\r\n', and bare '\r' each as one line
break, and add unit tests covering all line-ending forms plus blank-line
and trailing-newline preservation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-03 00:20:36 -07:00
co-authored by Claude Opus 4.8
parent 3126a5e523
commit 480f1390c7
4 changed files with 160 additions and 26 deletions
+48
View File
@@ -0,0 +1,48 @@
/*
* PasteSplit.h - split pasted/clipboard text into editor commands
*
* Pure logic (no SDL/GUI dependencies) so it can be unit tested and shared
* across frontends. Line breaks in the pasted text must become Newline
* commands; InsertText itself rejects embedded '\r'/'\n' (see Command.cc).
*/
#pragma once
#include <string>
#include <vector>
#include "InputHandler.h"
// Translate a block of pasted text into a sequence of editor commands.
//
// Any of "\n", "\r\n", or a bare "\r" is treated as a single line break and
// emitted as a Newline command; the text between breaks becomes InsertText
// commands. Empty text segments are skipped, but line breaks are always
// emitted so blank lines round-trip correctly.
inline std::vector<MappedInput> SplitPasteIntoCommands(const std::string &text)
{
std::vector<MappedInput> out;
std::string segment;
auto flush_segment = [&]() {
if (!segment.empty()) {
out.push_back(MappedInput{true, CommandId::InsertText, segment, 0});
segment.clear();
}
};
for (std::size_t i = 0; i < text.size(); ++i) {
const char c = text[i];
if (c == '\n' || c == '\r') {
flush_segment();
out.push_back(MappedInput{true, CommandId::Newline, std::string(), 0});
// Collapse a "\r\n" pair into one line break.
if (c == '\r' && i + 1 < text.size() && text[i + 1] == '\n')
++i;
} else {
segment.push_back(c);
}
}
flush_segment();
return out;
}