gnustep / libs-gui

The GNUstep gui library is a library of graphical user interface classes written completely in the Objective-C language; the classes are based upon Apple's Cocoa framework (which came from the OpenStep specification). *** Larger patches require copyright assignment to FSF. please file bugs here. ***
http://www.gnustep.org
GNU General Public License v3.0
279 stars 103 forks source link

Behavior difference in NSAttributeString itemNumberInTextList:atIndex: #293

Open fjmilens3 opened 2 months ago

fjmilens3 commented 2 months ago

Per Apple documentation, NSAttributeString's itemNumberInTextList:atIndex: should return the index of the item at the specified location within the list.

The current GNUStep implementation instead returns the index position of the list within the lists for the current paragraph style. This gives you the nesting level instead of the item number.

https://github.com/gnustep/libs-gui/blob/fb61a0f287f545f4b3dc3aa7c5244572ba016d33/Source/NSAttributedString.m#L1095-L1112

For an example, consider the following list:

For a text position anywhere in, say, "Delta," Apple's implementation would return 4 as it's the fourth item in the list. The current GNUstep implementation returns 0 as the list is the zeroth item in the paragraph style's lists array.

I've been playing around with this locally but I'd encourage someone else with access to a Mac to verify the difference as well. Note that if you write a test program of your own with Cocoa on recent Macs, you'll probably want to opt-out of TextKit 2 behavior via [textView layoutManager] for an equal comparison to GNUStep.

If it turns out to be a valid bug, some thoughts based on my current, hacky workaround: Get the entire range of the list from rangeOfTextList:atIndex:, iterate over the range looking for newline characters, and basically count paragraphs until it reaches the position we were asked about. You'd also want to check against the lists in each paragraph's style to account for things like nested lists.

fjmilens3 commented 2 months ago

For those who don't have access to a Mac, see below.

Cocoa example code for staging list data:

    // Do this to revert to TextKit 1 instead of TextKit 2
    NSLayoutManager *manager = [_textView layoutManager];

    NSTextList *list = [[NSTextList alloc] initWithMarkerFormat:@"{decimal}" options:0];

    NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];

    [style setTextLists: [NSArray arrayWithObject: list]];

    NSMutableString *str = [[NSMutableString alloc] init];
    for (int i = 0; i < 10; i++) {
        [str appendFormat: @"%@ item\n", [list markerForItemNumber: i]];
    }

    NSDictionary *attrs = [NSDictionary dictionaryWithObject: style forKey: NSParagraphStyleAttributeName];

    NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithString: str attributes: attrs];

    NSRange editRange = [_textView rangeForUserTextChange];
    NSTextStorage *storage = [_textView textStorage];
    [storage replaceCharactersInRange:editRange withAttributedString:attrStr];

Cocoa example code for obtaining the list index:

    NSRange range = [_textView rangeForUserTextChange];

    NSTextStorage *storage = [_textView textStorage];

    NSParagraphStyle *style = [storage attribute: NSParagraphStyleAttributeName atIndex: range.location effectiveRange: NULL];

    NSTextList *list = [[style textLists] lastObject];

    if (list != nil) {
        NSUInteger number = [storage itemNumberInTextList: list atIndex:range.location];

        NSLog(@"List item number: %lu", (unsigned long)number);
    } else {
        NSLog(@"No list");
    }

Cocoa Screenshot:

Screenshot 2024-09-22 at 10 57 40 PM