Skip to content

Improvements and fixes to NSTextList-related code #297

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
50 changes: 39 additions & 11 deletions Source/NSAttributedString.m
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@
#import "AppKit/NSFont.h"
#import "AppKit/NSFontDescriptor.h"
#import "AppKit/NSFontManager.h"
#import "AppKit/NSTextList.h"
// For the colour name spaces
#import "AppKit/NSGraphics.h"
#import "AppKit/NSTextTable.h"
Expand Down Expand Up @@ -1095,20 +1096,43 @@ - (NSFileWrapper *) fileWrapperFromRange: (NSRange)range
- (NSInteger) itemNumberInTextList: (NSTextList *)list
atIndex: (NSUInteger)location
{
NSParagraphStyle *style = [self attribute: NSParagraphStyleAttributeName
atIndex: location
effectiveRange: NULL];
if (style != nil)
NSRange listRange = [self rangeOfTextList: list atIndex: location];
if (listRange.location == NSNotFound)
{
NSArray *textLists = [style textLists];
return 0;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This matches the return value in Cocoa (I'd have expected NSNotFound but it seems to return 0 instead).

}

if (textLists != nil)
NSRange subRange = NSMakeRange(listRange.location, location-listRange.location+1);
unichar buffer[subRange.length];

[[self string] getCharacters: buffer range: subRange];

NSCharacterSet *newlineCharacterSet = [NSCharacterSet newlineCharacterSet];

NSUInteger itemNumber = 1;
NSUInteger index;
for (index=1; index<subRange.length; index++)
{
if ([newlineCharacterSet characterIsMember: buffer[index-1]])
{
return [textLists indexOfObject: list];
NSParagraphStyle *style = [self attribute: NSParagraphStyleAttributeName
atIndex: subRange.location + index
effectiveRange: NULL];

NSArray *textLists = [style textLists];
if ([list isEqual: [textLists lastObject]])
{
itemNumber++;
}

if (buffer[index-1] == '\r' && buffer[index] == '\n')
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and the use of NSCharacterSet newlineCharacterSet may be overkill, would we use '\n' internally or do we need to support Windows CR+LF combinations?

{
index++;
}
}
}

return NSNotFound;
return itemNumber;
}

- (NSRange) rangeOfTextBlock: (NSTextBlock *)block
Expand Down Expand Up @@ -1183,7 +1207,7 @@ - (NSRange) rangeOfTextList: (NSTextList *)list
NSRange newEffRange;
NSUInteger len = [self length];

while ((effRange.location > 0) && style && textLists)
while (effRange.location > 0)
{
style = [self attribute: NSParagraphStyleAttributeName
atIndex: effRange.location - 1
Expand All @@ -1196,11 +1220,13 @@ - (NSRange) rangeOfTextList: (NSTextList *)list
{
effRange.location = newEffRange.location;
effRange.length += newEffRange.length;
continue;
}
}
break;
}

while (NSMaxRange(effRange) < len && style && textLists)
while (NSMaxRange(effRange) < len)
{
style = [self attribute: NSParagraphStyleAttributeName
atIndex: NSMaxRange(effRange)
Expand All @@ -1212,8 +1238,10 @@ - (NSRange) rangeOfTextList: (NSTextList *)list
if ((textLists != nil) && [textLists containsObject: list])
{
effRange.length += newEffRange.length;
continue;
}
}
break;
}

return effRange;
Expand Down
12 changes: 11 additions & 1 deletion Source/NSParagraphStyle.m
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,8 @@ - (id) init
[_tabStops addObject: tab];
RELEASE(tab);
}

ASSIGN(_textLists, [NSArray array]);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instances in Cocoa always have an empty array set for the default value, so I included this here and below.

We weren't even saving the textLists in some of the serialization paths, and other fields like text blocks and text tables are ignored as well. I'd suggest deferring any work on that part unless we make the changes as a batch and bump the object version; I'm not that familiar with any of this yet, and I don't want the PR to get out of hand.

}
return self;
}
Expand Down Expand Up @@ -494,6 +496,9 @@ - (id) initWithCoder: (NSCoder*)aCoder
[aCoder decodeValueOfObjCType: @encode(float) at: &_paragraphSpacing];
[aCoder decodeValueOfObjCType: @encode(float) at: &_tailIndent];

// Text lists were not included for non-keyed encoding, use a default
ASSIGN(_textLists, [NSArray array]);

/*
* Tab stops don't conform to NSCoding - so we do it the long way.
*/
Expand Down Expand Up @@ -621,7 +626,12 @@ - (BOOL) isEqual: (id)aother
C(_headerLevel);
#undef C

return [_tabStops isEqualToArray: other->_tabStops];
#define C(x) if (![x isEqualToArray: other->x]) return NO;
C(_tabStops);
C(_textLists);
#undef C

return YES;
}

- (NSUInteger) hash
Expand Down
15 changes: 0 additions & 15 deletions Source/NSTextList.m
Original file line number Diff line number Diff line change
Expand Up @@ -51,21 +51,6 @@ - (void) dealloc
[super dealloc];
}

- (BOOL) isEqual: (id)anObject
{
if (anObject == self)
{
return YES;
}
if (anObject == nil || [anObject isKindOfClass: [NSTextList class]] == NO)
{
return NO;
}

return ([anObject listOptions] == _listOptions)
&& [_markerFormat isEqualToString: [anObject markerFormat]];
}

- (unsigned int) listOptions
{
return _listOptions;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
#include "Testing.h"

#include <Foundation/NSAutoreleasePool.h>
#include <AppKit/NSAttributedString.h>
#include <AppKit/NSParagraphStyle.h>
#include <AppKit/NSTextList.h>

int main(int argc, char **argv)
{
CREATE_AUTORELEASE_POOL(arp);

START_SET("NSAttributedString itemNumberInTextList:atIndex: category method");

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

NSMutableParagraphStyle *style1 = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
NSMutableParagraphStyle *style2 = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];

[style1 setTextLists: [NSArray arrayWithObject: list1]];
[style2 setTextLists: [NSArray arrayWithObjects: list1, list2, nil]];

NSDictionary *attrs1 = [NSDictionary dictionaryWithObject: style1
forKey: NSParagraphStyleAttributeName];
NSDictionary *attrs2 = [NSDictionary dictionaryWithObject: style2
forKey: NSParagraphStyleAttributeName];
NSDictionary *attrs3 = [NSDictionary dictionaryWithObject: [NSParagraphStyle defaultParagraphStyle]
forKey: NSParagraphStyleAttributeName];

NSMutableAttributedString *storage = [[NSMutableAttributedString alloc] init];

NSUInteger index1 = [storage length];
[storage appendAttributedString:
[[NSMutableAttributedString alloc] initWithString: @"item 1\r\n" attributes: attrs1]];

NSUInteger index2 = [storage length];
[storage appendAttributedString:
[[NSMutableAttributedString alloc] initWithString: @"item 2\n" attributes: attrs1]];

NSUInteger index3 = [storage length];
[storage appendAttributedString:
[[NSMutableAttributedString alloc] initWithString: @"item 3\n" attributes: attrs1]];

NSUInteger index4 = [storage length];
[storage appendAttributedString:
[[NSMutableAttributedString alloc] initWithString: @"subitem 1\n" attributes: attrs2]];

NSUInteger index5 = [storage length];
[storage appendAttributedString:
[[NSMutableAttributedString alloc] initWithString: @"subitem 2\n" attributes: attrs2]];

NSUInteger index6 = [storage length];
[storage appendAttributedString:
[[NSMutableAttributedString alloc] initWithString: @"item 4\n" attributes: attrs1]];

NSUInteger index7 = [storage length];
[storage appendAttributedString:
[[NSMutableAttributedString alloc] initWithString: @"extra text\n" attributes: attrs3]];

pass([storage itemNumberInTextList: list1 atIndex: index1] == 1, "Index for first list item");
pass([storage itemNumberInTextList: list1 atIndex: index2] == 2, "Index with CR+LF sequence");
pass([storage itemNumberInTextList: list1 atIndex: index3 - 1] == 2, "Index on boundary");
pass([storage itemNumberInTextList: list1 atIndex: index3] == 3, "Index for third list item");
pass([storage itemNumberInTextList: list1 atIndex: index4] == 3, "Index for third list item (sublist 1)");
pass([storage itemNumberInTextList: list1 atIndex: index5] == 3, "Index for third list item (sublist 2");
pass([storage itemNumberInTextList: list1 atIndex: index6] == 4, "Index for fourth list item");

pass([storage itemNumberInTextList: list2 atIndex: index4] == 1, "Index for first sublist item");
pass([storage itemNumberInTextList: list2 atIndex: index5] == 2, "Index for second sublist item");

pass([storage itemNumberInTextList: list2 atIndex: index1] == 0, "Index in other list is zero");
pass([storage itemNumberInTextList: list1 atIndex: index7] == 0, "Index in nonlist is zero");

END_SET("NSAttributedString itemNumberInTextList:atIndex: category method");

DESTROY(arp);

return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#include "Testing.h"

#include <Foundation/NSAutoreleasePool.h>
#include <AppKit/NSAttributedString.h>
#include <AppKit/NSParagraphStyle.h>
#include <AppKit/NSTextList.h>

int main(int argc, char **argv)
{
CREATE_AUTORELEASE_POOL(arp);

START_SET("NSAttributedString attribute merging");

NSMutableParagraphStyle *style1 = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
NSMutableParagraphStyle *style2 = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
NSMutableParagraphStyle *style3 = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
NSMutableParagraphStyle *style4 = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];

NSTextList *list1 = [[NSTextList alloc] initWithMarkerFormat: @"{box}" options: 0];
NSTextList *list2 = [[NSTextList alloc] initWithMarkerFormat: @"{box}" options: 0];

[style3 setTextLists: [NSArray arrayWithObject: list1]];
[style4 setTextLists: [NSArray arrayWithObject: list2]];

NSAttributedString *str1 = [[NSAttributedString alloc]
initWithString: @"string 1"
attributes: [NSDictionary dictionaryWithObject: style1 forKey: NSParagraphStyleAttributeName]];
NSAttributedString *str2 = [[NSAttributedString alloc]
initWithString: @"string 2"
attributes: [NSDictionary dictionaryWithObject: style2 forKey: NSParagraphStyleAttributeName]];
NSAttributedString *str3 = [[NSAttributedString alloc]
initWithString: @"string 3"
attributes: [NSDictionary dictionaryWithObject: style3 forKey: NSParagraphStyleAttributeName]];
NSAttributedString *str4 = [[NSAttributedString alloc]
initWithString: @"string 4"
attributes: [NSDictionary dictionaryWithObject: style4 forKey: NSParagraphStyleAttributeName]];

NSMutableAttributedString *storage = [[NSMutableAttributedString alloc] init];

NSUInteger pos1 = [storage length];
[storage appendAttributedString: str1];

NSUInteger pos2 = [storage length];
[storage appendAttributedString: str2];

NSUInteger pos3 = [storage length];
[storage appendAttributedString: str3];

NSUInteger pos4 = [storage length];
[storage appendAttributedString: str4];

NSParagraphStyle *result1 = [storage attribute: NSParagraphStyleAttributeName
atIndex: pos1
effectiveRange: NULL];
NSParagraphStyle *result2 = [storage attribute: NSParagraphStyleAttributeName
atIndex: pos2
effectiveRange: NULL];
NSParagraphStyle *result3 = [storage attribute: NSParagraphStyleAttributeName
atIndex: pos3
effectiveRange: NULL];
NSParagraphStyle *result4 = [storage attribute: NSParagraphStyleAttributeName
atIndex: pos4
effectiveRange: NULL];

pass(result1 == result2, "Did merge equal paragraph styles");
pass(result3 != result4, "Did not merge equal paragraph styles with text lists");

END_SET("NSAttributedString attribute merging");

DESTROY(arp);

return 0;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
#include "Testing.h"

#include <Foundation/NSAutoreleasePool.h>
#include <AppKit/NSAttributedString.h>
#include <AppKit/NSParagraphStyle.h>
#include <AppKit/NSTextList.h>

int main(int argc, char **argv)
{
CREATE_AUTORELEASE_POOL(arp);

START_SET("NSAttributedString rangeOfTextList:atIndex: category method");

NSTextList *list1 = [[NSTextList alloc] initWithMarkerFormat: @"{box}" options: 0];
NSTextList *list2 = [[NSTextList alloc] initWithMarkerFormat: @"{box}" options: 0];
NSTextList *list3 = [[NSTextList alloc] initWithMarkerFormat: @"{box}" options: 0];

NSMutableParagraphStyle *style1 = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
NSMutableParagraphStyle *style2 = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
NSMutableParagraphStyle *style3 = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
NSMutableParagraphStyle *style4 = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];

[style2 setTextLists: [NSArray arrayWithObject: list1]];
[style3 setTextLists: [NSArray arrayWithObjects: list1, list2, nil]];
[style4 setTextLists: [NSArray arrayWithObject: list3]];

NSMutableAttributedString *storage = [[NSMutableAttributedString alloc] init];

NSUInteger pos1 = [storage length];
[storage appendAttributedString: [[NSAttributedString alloc]
initWithString: @"before\n"
attributes: [NSDictionary dictionaryWithObject: style1 forKey: NSParagraphStyleAttributeName]]];

NSUInteger pos2 = [storage length];
[storage appendAttributedString: [[NSAttributedString alloc]
initWithString: @"list 1\n"
attributes: [NSDictionary dictionaryWithObject: style2 forKey: NSParagraphStyleAttributeName]]];

NSUInteger pos3 = [storage length];
[storage appendAttributedString: [[NSAttributedString alloc]
initWithString: @"sublist 1\n"
attributes: [NSDictionary dictionaryWithObject: style3 forKey: NSParagraphStyleAttributeName]]];

NSUInteger pos4 = [storage length];
[storage appendAttributedString: [[NSAttributedString alloc]
initWithString: @"list 1\n"
attributes: [NSDictionary dictionaryWithObject: style2 forKey: NSParagraphStyleAttributeName]]];

NSUInteger pos5 = [storage length];
[storage appendAttributedString: [[NSAttributedString alloc]
initWithString: @"list 2\n"
attributes: [NSDictionary dictionaryWithObject: style4 forKey: NSParagraphStyleAttributeName]]];

NSUInteger pos6 = [storage length];
[storage appendAttributedString: [[NSAttributedString alloc]
initWithString: @"ending\n"
attributes: [NSDictionary dictionaryWithObject: style1 forKey: NSParagraphStyleAttributeName]]];

NSRange expected, actual;

expected = NSMakeRange(pos3, pos4 - pos3);
actual = [storage rangeOfTextList: list2 atIndex: pos3 + 1];
pass(NSEqualRanges(expected, actual), "Found correct range of nested list");

expected = NSMakeRange(pos2, pos5 - pos2);
actual = [storage rangeOfTextList: list1 atIndex: pos3 + 1];
pass(NSEqualRanges(expected, actual), "Found correct range of enclosing list");

expected = NSMakeRange(pos2, pos5 - pos2);
actual = [storage rangeOfTextList: list1 atIndex: pos2 + 1];
pass(NSEqualRanges(expected, actual), "Found correct range including nested list");

expected = NSMakeRange(pos5, pos6 - pos5);
actual = [storage rangeOfTextList: list3 atIndex: pos5 + 1];
pass(NSEqualRanges(expected, actual), "Found correct range of an adjacent list");

actual = [storage rangeOfTextList: list1 atIndex: pos5];
pass(actual.location == NSNotFound, "Returned not found for location in different list");

actual = [storage rangeOfTextList: list1 atIndex: pos1];
pass(actual.location == NSNotFound, "Returned not found for location not in any list");

END_SET("NSAttributedString rangeOfTextList:atIndex: category method");

DESTROY(arp);

return 0;
}
Empty file.
Loading
Loading