diff options
| author | Danilo M. <danix@danix.xyz> | 2026-08-24 19:54:27 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-08-24 19:54:27 +0200 |
| commit | be2534ab60d362b2f685130265f727190ccf867a (patch) | |
| tree | be7e6f2bbdd12686a2c2ac71523e34a6f7030539 /src | |
| parent | 861628a75049002683f777baf837afe434d2f7db (diff) | |
| download | qtmaildir-be2534ab60d362b2f685130265f727190ccf867a.tar.gz qtmaildir-be2534ab60d362b2f685130265f727190ccf867a.zip | |
feat(signatures): splice a signature into a buffer
Both placements over one implementation. above_quote inserts before the
attribution rather than before the first quoted line: the attribution
introduces the quote and belongs with it, and a signature between the two
would read as part of the quoted message.
A buffer with no quote makes above_quote identical to end, so a new
message needs no branch of its own.
Part of item 152.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KEcn3u19xPqv6ggD15PG4c
Diffstat (limited to 'src')
| -rw-r--r-- | src/signatures.cpp | 72 |
1 files changed, 69 insertions, 3 deletions
diff --git a/src/signatures.cpp b/src/signatures.cpp index 67bf491..ebf69f5 100644 --- a/src/signatures.cpp +++ b/src/signatures.cpp @@ -55,13 +55,79 @@ QString text(const QString &dir, const QString &name) return QString::fromUtf8(file.readAll()); } +namespace { + +/// The RFC 3676 signature separator: two hyphens, a space, end of line. +/// +/// The trailing space is part of the standard and is what receiving clients +/// match on to fold or strip a signature. It is also why `--` typed by hand +/// does not collide: an editor does not add trailing whitespace on its own. +const QLatin1String kDelimiter("-- "); + +bool isQuoted(const QString &line) +{ + return line.startsWith(QLatin1Char('>')); +} + +/// The index of the first line of the quote, or -1 when the buffer has none. +/// +/// The attribution line ("On Mon, someone wrote:") is deliberately NOT +/// included: it introduces the quote and belongs with it, so a signature +/// inserted above the quote goes above the attribution too. Returning the +/// quoted line itself would strand the signature between the attribution and +/// the text it introduces. +int quoteStart(const QStringList &lines) +{ + for (int i = 0; i < lines.size(); ++i) { + if (!isQuoted(lines.at(i))) + continue; + // Walk back over the attribution and the blank line before it, so the + // signature lands above the whole block rather than inside it. + int start = i; + while (start > 0 && !lines.at(start - 1).trimmed().isEmpty() + && !isQuoted(lines.at(start - 1))) + --start; + return start; + } + return -1; +} + +} // namespace + QString replace(const QString &buffer, const QString &signature, const QStringList &known, Position position) { - Q_UNUSED(signature); Q_UNUSED(known); - Q_UNUSED(position); - return buffer; + + if (signature.isEmpty()) + return buffer; + + const QString block = QStringLiteral("\n") + kDelimiter + + QStringLiteral("\n") + signature; + + QStringList lines = buffer.split(QLatin1Char('\n')); + const int quote = + position == Position::AboveQuote ? quoteStart(lines) : -1; + + // No quote to sit above is not a special case: it is the End placement, + // which is why a New message needs no branch of its own. + if (quote < 0) + return buffer + block; + + QStringList head = lines.mid(0, quote); + const QStringList tail = lines.mid(quote); + // The head ends in however many blank lines separated the reply from the + // attribution. Drop them all and let the block supply exactly one, so the + // spacing is the same whatever the quote was seeded with. + while (!head.isEmpty() && head.last().trimmed().isEmpty()) + head.removeLast(); + + // head.join() has no trailing newline once trimmed, so the terminator for + // its last line is supplied here; `block` then opens with the blank line, + // which is the same shape as the End placement over a buffer ending in a + // newline. + return head.join(QLatin1Char('\n')) + QStringLiteral("\n") + block + + QStringLiteral("\n\n") + tail.join(QLatin1Char('\n')); } } // namespace Signatures |
