AppKit: Add RTF list and Unicode support, and preserve rich content during internal copy-paste

The RTF producer and parser lacked support for text lists, failed to encode
non-ASCII characters properly (\uN), and treated first-line indents (\fi) as
absolute values rather than offsets relative to left indents (\li).
Additionally, paste operations ingested application-generated RTF rather than
the native internal archive, causing image attachments and tables to be stripped
during copy-paste workflows within the application.

* AppKit/CPTextView/_CPRTFProducer.j:
  - Implement RTF serialization for text lists using list tables, override tables,
    \levelmarker, \levelprepend, \lsN\ilvlK tags, and \listtext marker blocks.
  - Encode non-ASCII characters via \uc0\uN syntax and compute \fi indents
    relative to \li.
  - Omit image attachment characters from text runs and adjust run starting indices
    to accommodate list markers.

* AppKit/CPTextView/_CPRTFParser.j:
  - Implement pre-pass parsing for list and override tables.
  - Assign shared text lists to items using \ls and \ilvl, extracting marker formats
    from \levelmarker or standard number/level definitions.
  - Parse \uN sequences (skipping fallback characters) and evaluate relative \fi offsets.

* AppKit/CPTextView/CPTextView.j:
  - Configure paste handling to prioritize internal archives when clipboard RTF
    originates from the application, ensuring image attachments and tables survive internal copy-paste.

* AppKit/CPTextView/_CPTableTextAttachment.j:
  - Implement keyed archiving and decoding for table headers, rows, widths,
    and configuration settings, reconstructing cell instances upon unarchiving.

* Tests/AppKit/CPRTFTest.j:
  - Add comprehensive unit tests covering Unicode encoding, relative first-line indents,
    TextEdit and Word list interoperability, and nested list round-trip fidelity.

* Tests/AppKit/CPTextAttachmentTest.j:
  - Add test coverage for table attachment archiving.

* Tests/Manual/TextEditor/WORKLIST.md:
  - Mark intra-application copy and paste testing as completed.
  - Log external rich text exchange limitations as open items.

Intra-application copy and paste now fully preserves images, tables, and lists,
while RTF serialization supports Unicode and structured hierarchical lists.
This commit is contained in:
David Richardson committed 2026-10-24 14:37:57 -06:00
1 parent 3282323053
commit 2b77b98163
7 files changed
+804 -14

No files matched your search

@@ -556,3 +556,39 @@
}
@end
var _CPTableTextAttachmentHeadersKey = @"_CPTableTextAttachmentHeaders",
_CPTableTextAttachmentRowsKey = @"_CPTableTextAttachmentRows",
_CPTableTextAttachmentWidthKey = @"_CPTableTextAttachmentWidth",
_CPTableTextAttachmentIsEditableKey = @"_CPTableTextAttachmentIsEditable",
_CPTableTextAttachmentAcceptsRichTextKey = @"_CPTableTextAttachmentAcceptsRichText";
@implementation _CPTableTextAttachment (CPCoding)
/*
Decodes the table from its headers, rows, width and settings, and builds its cells again.
The cells are not decoded as subviews; they are made from the decoded text.
*/
- (id)initWithCoder:(CPCoder)aCoder
{
return [self initWithHeaders:[aCoder decodeObjectForKey:_CPTableTextAttachmentHeadersKey]
rows:[aCoder decodeObjectForKey:_CPTableTextAttachmentRowsKey]
width:[aCoder decodeFloatForKey:_CPTableTextAttachmentWidthKey] || 500.0
isEditable:[aCoder decodeBoolForKey:_CPTableTextAttachmentIsEditableKey]
acceptsRichText:[aCoder decodeBoolForKey:_CPTableTextAttachmentAcceptsRichTextKey]];
}
/*
Encodes the table's current text, including edits made in its cells, its width and its
settings. The cell views are not encoded.
*/
- (void)encodeWithCoder:(CPCoder)aCoder
{
[aCoder encodeObject:[self headers] forKey:_CPTableTextAttachmentHeadersKey];
[aCoder encodeObject:[self rows] forKey:_CPTableTextAttachmentRowsKey];
[aCoder encodeFloat:CGRectGetWidth([self frame]) forKey:_CPTableTextAttachmentWidthKey];
[aCoder encodeBool:_isEditable forKey:_CPTableTextAttachmentIsEditableKey];
[aCoder encodeBool:_acceptsRichText forKey:_CPTableTextAttachmentAcceptsRichTextKey];
}
@end