Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge branch 'master' into jumping-jackalopes

  • Loading branch information...
commit 24779dace027b4a8ea634265c1c7ccb698b7905e 2 parents 0158fe2 + 61952f3
Antonio Salazar Cardozo Shadowfiend authored
Showing with 2,480 additions and 2,813 deletions.
  1. +1 −1  app/ExCommandCompletion.m
  2. +30 −0 ...BuildProductsLocation/Applications/Vico.app/Contents/Resources/Vico.help/Contents/Resources/shared/help.css
  3. BIN  ...ProductsLocation/Applications/Vico.app/Contents/Resources/Vico.help/Contents/Resources/shared/icon_hand.png
  4. BIN  ...ductsLocation/Applications/Vico.app/Contents/Resources/Vico.help/Contents/Resources/shared/icon_hand_32.png
  5. BIN  ...ductsLocation/Applications/Vico.app/Contents/Resources/Vico.help/Contents/Resources/shared/icon_hand_64.png
  6. +4 −4 app/Nu.m
  7. +1 −1  app/SFTPConnection.m
  8. +1 −1  app/ViBufferCompletion.m
  9. +3 −0  app/ViCommand.h
  10. +5 −0 app/ViCommand.m
  11. +19 −12 app/ViCompletionController.h
  12. +85 −85 app/ViCompletionController.m
  13. +1 −1  app/ViCompletionWindow.h
  14. +6 −2 app/ViCompletionWindow.m
  15. +33 −0 app/ViDocument.h
  16. +360 −9 app/ViDocument.m
  17. +73 −0 app/ViFold.h
  18. +147 −0 app/ViFold.m
  19. +23 −0 app/ViFoldMarginView.h
  20. +148 −0 app/ViFoldMarginView.m
  21. +13 −0 app/ViGlyphGenerator.h
  22. +75 −0 app/ViGlyphGenerator.m
  23. +12 −0 app/ViLayoutManager.m
  24. +38 −0 app/ViLineNumberView.h
  25. +346 −0 app/ViLineNumberView.m
  26. +8 −5 app/ViMap.h
  27. +5 −0 app/ViMap.m
  28. +2 −2 app/ViParser.m
  29. +8 −7 app/ViRulerView.h
  30. +65 −209 app/ViRulerView.m
  31. +1 −1  app/ViSyntaxCompletion.m
  32. +3 −1 app/ViTabController.h
  33. +13 −1 app/ViTabController.m
  34. +1 −1  app/ViTaskRunner.m
  35. +1 −1  app/ViTextStorage.h
  36. +10 −1 app/ViTextStorage.m
  37. +136 −31 app/ViTextView-vi_commands.m
  38. +13 −2 app/ViTextView.h
  39. +178 −35 app/ViTextView.m
  40. +4 −4 app/ViTheme.m
  41. +11 −0 app/ViTypesetter.h
  42. +20 −0 app/ViTypesetter.m
  43. +83 −8 app/ViWindowController.m
  44. +83 −540 app/en.lproj/CompletionWindow.xib
  45. BIN  app/en.lproj/InfoPlist.strings
  46. +199 −1,718 app/en.lproj/SFBCrashReporterWindow.xib
  47. +46 −19 app/keys.nu
  48. +1 −0  app/vico.nu
  49. +6 −6 lemon/lemon.c
  50. +1 −1  oniguruma/enc/utf16_be.c
  51. +1 −1  oniguruma/enc/utf16_le.c
  52. +15 −15 oniguruma/regcomp.c
  53. +2 −2 oniguruma/regenc.c
  54. +3 −3 oniguruma/regerror.c
  55. +24 −24 oniguruma/regexec.c
  56. +1 −1  oniguruma/regext.c
  57. +1 −1  oniguruma/reggnu.c
  58. +3 −3 oniguruma/regint.h
  59. +15 −15 oniguruma/regparse.c
  60. +1 −1  oniguruma/regposix.c
  61. +2 −2 oniguruma/st.c
  62. +1 −1  par/charset.c
  63. +6 −6 par/par.c
  64. +2 −2 par/reformat.c
  65. +81 −27 vico.xcodeproj/project.pbxproj
2  app/ExCommandCompletion.m
View
@@ -44,7 +44,7 @@ - (NSArray *)completionsForString:(NSString *)word
else if (fuzzyTrigger)
[ViCompletionController appendFilter:word toPattern:pattern fuzzyClass:@"."];
else
- pattern = [NSString stringWithFormat:@"^%@.*", word];
+ pattern = [NSMutableString stringWithFormat:@"^%@.*", word];
unsigned rx_options = ONIG_OPTION_IGNORECASE;
ViRegexp *rx = [ViRegexp regexpWithString:pattern options:rx_options];
30 ...ldProductsLocation/Applications/Vico.app/Contents/Resources/Vico.help/Contents/Resources/shared/help.css
View
@@ -0,0 +1,30 @@
+body {
+ font-family: "Lucida Grande", sans-serif;
+ font-size: small;
+ padding: 1em 1em 0em 1em;
+}
+
+kbd, pre { /* stolen from github */
+ background-color: ghostWhite;
+ border: 1px solid #DEDEDE;
+ color: #444;
+ padding: 0 0.2em;
+}
+
+kbd {
+ white-space: pre;
+}
+
+pre {
+ padding: 0.4em;
+}
+
+h1 {
+ font-size: medium;
+ margin: 0 0 0.5em 0;
+ padding-left: 42px;
+ min-height: 32px;
+ background: url(../shared/icon_hand_32.png) no-repeat bottom left;
+ -webkit-background-size: 32px 32px;
+ line-height: 32px;
+}
BIN  ...ductsLocation/Applications/Vico.app/Contents/Resources/Vico.help/Contents/Resources/shared/icon_hand.png
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  ...tsLocation/Applications/Vico.app/Contents/Resources/Vico.help/Contents/Resources/shared/icon_hand_32.png
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
BIN  ...tsLocation/Applications/Vico.app/Contents/Resources/Vico.help/Contents/Resources/shared/icon_hand_64.png
View
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 app/Nu.m
View
@@ -5930,9 +5930,9 @@ - (id) initWithMethod:(Method) method
return self;
}
-- (NSString *) name
+- (NSString *)name
{
- return m ? [NSString stringWithCString:(sel_getName(method_getName(m))) encoding:NSUTF8StringEncoding] : [NSNull null];
+ return m ? [NSString stringWithCString:(sel_getName(method_getName(m))) encoding:NSUTF8StringEncoding] : (NSString *)[NSNull null];
}
- (int) argumentCount
@@ -11248,7 +11248,7 @@ @implementation NuMarkupOperator
static NSDictionary *elementPrefixes = nil;
+ (void) initialize {
- voidHTMLElements = [[NSSet setWithObjects:
+ voidHTMLElements = [[[NSSet setWithObjects:
@"area",
@"base",
@"br",
@@ -11265,7 +11265,7 @@ + (void) initialize {
@"source",
@"track",
@"wbr",
- nil] retain];
+ nil] allObjects] retain];
elementPrefixes = [[NSDictionary dictionaryWithObjectsAndKeys:
@"<!DOCTYPE html>", @"html",
nil] retain];
2  app/SFTPConnection.m
View
@@ -268,7 +268,7 @@ - (void)waitInWindow:(NSWindow *)window
DEBUG(@"limitdate %@ reached, presenting cancellable sheet", limitDate);
/* Continue with a sheet with a cancel button. */
- [NSBundle loadNibNamed:@"WaitProgress" owner:self];
+ [[NSBundle mainBundle] loadNibNamed:@"WaitProgress" owner:self topLevelObjects:nil];
[waitLabel setStringValue:waitMessage];
[progressIndicator startAnimation:nil];
[waitWindow setTitle:[NSString stringWithFormat:@"Waiting on %@", _connection.title]];
2  app/ViBufferCompletion.m
View
@@ -44,7 +44,7 @@ - (NSArray *)completionsForString:(NSString *)word
else if (fuzzyTrigger)
[ViCompletionController appendFilter:word toPattern:pattern fuzzyClass:@"."];
else
- pattern = [NSString stringWithFormat:@"^%@.*", word];
+ pattern = [NSMutableString stringWithFormat:@"^%@.*", word];
unsigned rx_options = ONIG_OPTION_IGNORECASE;
ViRegexp *rx = [ViRegexp regexpWithString:pattern options:rx_options];
3  app/ViCommand.h
View
@@ -62,6 +62,9 @@
/** YES if the mapped action is a motion command. */
@property(nonatomic,readonly) BOOL isMotion;
+/** YES if the command's keys should not be included in the `.` command's record of keys pressed. */
+@property(nonatomic,readonly) BOOL isExcludedFromDot;
+
/** YES if the mapped action is a motion component for an operator. */
@property(nonatomic,readonly) BOOL hasOperator;
5 app/ViCommand.m
View
@@ -92,6 +92,11 @@ - (BOOL)isMotion
return [_mapping isMotion];
}
+- (BOOL)isExcludedFromDot
+{
+ return [_mapping isExcludedFromDot];
+}
+
- (BOOL)hasOperator
{
return _operator != nil;
31 app/ViCompletionController.h
View
@@ -39,6 +39,10 @@
@class ViCompletionController;
@protocol ViCompletionDelegate <NSObject>
+- (void)completionController:(ViCompletionController *)completionController
+ didTerminateWithKey:(NSInteger)keyCode
+ selectedCompletion:(ViCompletion *)selectedCompletion;
+
@optional
- (BOOL)completionController:(ViCompletionController *)completionController
shouldTerminateForKey:(NSInteger)keyCode;
@@ -61,13 +65,12 @@
ViCompletion *_onlyCompletion;
NSMutableArray *_filteredCompletions;
ViCompletion *_selection;
- ViKeyManager *_existingKeyManager;
NSMutableString *_filter;
// NSMutableParagraphStyle *_matchParagraphStyle;
id<ViCompletionDelegate> __unsafe_unretained _delegate;
NSInteger _terminatingKey;
NSRange _range;
- NSRect _prefixScreenRect;
+ NSRect _prefixWindowRect;
BOOL _upwards;
BOOL _fuzzySearch;
BOOL _autocompleting;
@@ -76,31 +79,35 @@
@property (unsafe_unretained, nonatomic, readonly) id<ViCompletionDelegate> delegate;
@property (nonatomic, readonly) NSWindow *window;
+@property (nonatomic, readonly) ViCompletionView *completionView;
@property (nonatomic, readwrite, strong) NSArray *completions;
@property (nonatomic, readonly) NSInteger terminatingKey;
@property (nonatomic, readonly) NSRange range;
@property (nonatomic, readwrite, strong) NSString *filter;
-+ (id)sharedController;
++ (ViCompletionController *)sharedController;
+ (NSString *)commonPrefixInCompletions:(NSArray *)completions;
+ (void)appendFilter:(NSString *)string
toPattern:(NSMutableString *)pattern
fuzzyClass:(NSString *)fuzzyClass;
-- (ViCompletion *)chooseFrom:(id<ViCompletionProvider>)aProvider
- range:(NSRange)aRange
- prefix:(NSString *)aPrefix
- prefixScreenRect:(NSRect)prefixRect
- delegate:(id<ViCompletionDelegate>)aDelegate
- existingKeyManager:(ViKeyManager *)existingKeyManager
- options:(NSString *)optionString
- initialFilter:(NSString *)initialFilter;
+- (BOOL)chooseFrom:(id<ViCompletionProvider>)aProvider
+ range:(NSRange)aRange
+ prefix:(NSString *)aPrefix
+ prefixWindowRect:(NSRect)prefixRect
+ forWindow:(NSWindow *)parentWindow
+ delegate:(id<ViCompletionDelegate>)aDelegate
+ options:(NSString *)optionString
+ initialFilter:(NSString *)initialFilter;
-- (void)updateBounds;
- (void)filterCompletions;
- (BOOL)complete_partially:(ViCommand *)command;
- (void)acceptByKey:(NSInteger)termKey;
- (BOOL)cancel:(ViCommand *)command;
+- (BOOL)accept:(ViCommand *)command;
+- (BOOL)accept_or_complete_partially:(ViCommand *)command;
+- (BOOL)accept_if_not_autocompleting:(ViCommand *)command;
+- (BOOL)accept_or_complete_partially:(ViCommand *)command;
- (void)updateCompletions;
- (void)reset;
170 app/ViCompletionController.m
View
@@ -41,8 +41,9 @@ @implementation ViCompletionController {
@synthesize terminatingKey = _terminatingKey;
@synthesize range = _range;
@synthesize filter = _filter;
+@synthesize completionView = tableView;
-+ (id)sharedController
++ (ViCompletionController *)sharedController
{
static ViCompletionController *__sharedController = nil;
if (__sharedController == nil)
@@ -53,16 +54,13 @@ + (id)sharedController
- (id)init
{
if ((self = [super init])) {
- if (![NSBundle loadNibNamed:@"CompletionWindow" owner:self]) {
+ if (![[NSBundle mainBundle] loadNibNamed:@"CompletionWindow" owner:self topLevelObjects:nil]) {
return nil;
}
tableView.keyManager = [ViKeyManager keyManagerWithTarget:self
defaultMap:[ViMap completionMap]];
- [window setStyleMask:NSBorderlessWindowMask];
- [window setHasShadow:YES];
-
// ViTheme *theme = [ViThemeStore defaultTheme];
// [tableView setBackgroundColor:[_theme backgroundColor]];
@@ -72,8 +70,7 @@ - (id)init
return self;
}
-
-- (void)updateBounds
+- (void)updateBoundsWithPrefixWindow:(NSWindow *)prefixWindow
{
NSSize winsz = NSMakeSize(0, 0);
for (ViCompletion *c in _filteredCompletions) {
@@ -103,7 +100,7 @@ We want to be able to show the list (either above or below the current position)
*/
NSUInteger maxNumberOfRows = (NSUInteger)((screenSize.height / 2)
- label.bounds.size.height
- - _prefixScreenRect.size.height)
+ - _prefixWindowRect.size.height)
/ tableView.rowHeight;
NSUInteger numberOfRows = MIN([_filteredCompletions count], maxNumberOfRows);
winsz.height = numberOfRows * ([tableView rowHeight] + 2) + [label bounds].size.height;
@@ -111,11 +108,11 @@ We want to be able to show the list (either above or below the current position)
/* Set the window size, which is independent of origin. */
NSRect windowFrame = [window frame];
windowFrame.size = winsz;
- windowFrame.origin = [self computeWindowOriginForSize:windowFrame.size];
-
+ windowFrame.origin = [self computeWindowOriginForSize:windowFrame.size fromPrefixWindow:prefixWindow];
+
if (!NSEqualRects(windowFrame, [window frame])) {
DEBUG(@"setting frame %@", NSStringFromRect(frame));
- [window setFrame:windowFrame display:YES];
+ [window setFrame:windowFrame display:[window isVisible]];
}
}
@@ -170,12 +167,8 @@ - (void)completionResponse:(NSArray *)array error:(NSError *)error
[self cancel:nil];
return;
}
- } else if ([_filteredCompletions count] == 1 && _aggressive) {
- if ([window isVisible]) {
- [self acceptByKey:0];
- } else {
- _onlyCompletion = [_filteredCompletions objectAtIndex:0];
- }
+ } else if ([_filteredCompletions count] == 1) {
+ _onlyCompletion = [_filteredCompletions objectAtIndex:0];
}
/* Automatically insert common prefix among all possible completions.
@@ -185,19 +178,19 @@ - (void)completionResponse:(NSArray *)array error:(NSError *)error
}
}
-- (ViCompletion *)chooseFrom:(id<ViCompletionProvider>)aProvider
- range:(NSRange)aRange
- prefix:(NSString *)aPrefix
- prefixScreenRect:(NSRect)prefixRect
- delegate:(id<ViCompletionDelegate>)aDelegate
- existingKeyManager:(ViKeyManager *)existingKeyManager
- options:(NSString *)optionString
- initialFilter:(NSString *)initialFilter {
+- (BOOL)chooseFrom:(id<ViCompletionProvider>)aProvider
+ range:(NSRange)aRange
+ prefix:(NSString *)aPrefix
+ prefixWindowRect:(NSRect)prefixRect
+ forWindow:(NSWindow *)parentWindow
+ delegate:(id<ViCompletionDelegate>)aDelegate
+ options:(NSString *)optionString
+ initialFilter:(NSString *)initialFilter {
+
_terminatingKey = 0;
[self reset];
_delegate = aDelegate;
- _existingKeyManager = existingKeyManager;
_onlyCompletion = nil;
@@ -216,7 +209,7 @@ - (ViCompletion *)chooseFrom:(id<ViCompletionProvider>)aProvider
// Aggressive means we auto-select a unique suggestion.
_aggressive = [_options rangeOfString:@"?"].location == NSNotFound;
_autocompleting = [_options rangeOfString:@"C"].location != NSNotFound;
- _prefixScreenRect = prefixRect;
+ _prefixWindowRect = prefixRect;
DEBUG(@"range is %@, with prefix [%@] and [%@] as initial filter, w/options %@",
NSStringFromRange(_range), _prefix, initialFilter, _options);
@@ -231,25 +224,23 @@ - (ViCompletion *)chooseFrom:(id<ViCompletionProvider>)aProvider
if (error) {
INFO(@"Completion provider %@ returned error %@", _provider, error);
[self reset];
- return nil;
+ return NO;
}
[self completionResponse:result error:nil];
- if (_onlyCompletion && _aggressive) {
+ if (_onlyCompletion && (_aggressive || [_onlyCompletion.content isEqualToString:_prefix])) {
DEBUG(@"returning %@ as only completion", _onlyCompletion);
_range = NSMakeRange(_range.location, _prefixLength + [_filter length]);
- [self reset];
- ViCompletion *ret = _onlyCompletion;
- _onlyCompletion = nil;
+ [self terminateWithKey:0 completion:_onlyCompletion];
- return ret;
+ return NO;
}
if ([_completions count] == 0 || [_filteredCompletions count] == 0) {
DEBUG(@"%s", "returning without completions");
[self reset];
- return nil;
+ return NO;
}
NSInteger initialSelectionIndex = 0;
@@ -257,19 +248,21 @@ - (ViCompletion *)chooseFrom:(id<ViCompletionProvider>)aProvider
initialSelectionIndex = [self numberOfRowsInTableView:tableView] - 1;
}
- [self selectCompletionRowWithDelegateCalls:initialSelectionIndex];
- [tableView scrollRowToVisible:initialSelectionIndex];
+ [self updateUIForPrefixWindow:parentWindow];
- DEBUG(@"showing window %@", window);
- [window orderFront:nil];
- NSInteger code = [NSApp runModalForWindow:window];
- [self reset];
+ [parentWindow addChildWindow:window ordered:NSWindowAbove];
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(containingWindowDidResignMain:)
+ name:NSWindowDidResignMainNotification
+ object:nil];
- if (code == NSRunAbortedResponse)
- return nil;
- ViCompletion *ret = _selection;
- _selection = nil;
- return ret;
+ return YES;
+}
+
+- (void)containingWindowDidResignMain:(NSNotification *)aNotification
+{
+ [self terminateWithKey:0 completion:nil];
}
+ (NSString *)commonPrefixInCompletions:(NSArray *)completions
@@ -371,10 +364,25 @@ - (void)filterCompletions
return (NSComparisonResult)NSOrderedSame;
}];
+ [self updateUI];
+}
+
+- (void)updateUI
+{
+ [self updateUIForPrefixWindow:nil];
+}
+
+- (void)updateUIForPrefixWindow:(NSWindow *)prefixWindow
+{
[tableView reloadData];
NSInteger selectionRow = _positionCompletionsBelowPrefix ? 0 : _filteredCompletions.count - 1;
[self selectCompletionRowWithDelegateCalls:selectionRow];
- [self updateBounds];
+
+ [tableView scrollRowToVisible:selectionRow];
+
+ if (! prefixWindow)
+ prefixWindow = [window parentWindow];
+ [self updateBoundsWithPrefixWindow:prefixWindow];
}
- (void)setFilter:(NSString *)aString
@@ -397,14 +405,11 @@ - (void)reset
_options = nil;
- _existingKeyManager = nil;
-
_delegate = nil; // delegate must be set for each completion, we don't want a lingering deallocated delegate to be called
}
- (void)acceptByKey:(NSInteger)termKey
{
- _terminatingKey = termKey;
NSInteger row = [tableView selectedRow];
if (row >= 0 && row < [_filteredCompletions count]) {
_selection = [_filteredCompletions objectAtIndex:row];
@@ -412,9 +417,8 @@ - (void)acceptByKey:(NSInteger)termKey
_range = NSMakeRange(_range.location, _prefixLength + [_filter length]);
}
- [window orderOut:nil];
- [NSApp stopModal];
- [self reset];
+
+ [self terminateWithKey:termKey completion:_selection];
}
- (BOOL)cancel:(ViCommand *)command
@@ -422,21 +426,24 @@ - (BOOL)cancel:(ViCommand *)command
if (! _delegate)
return NO;
- ViKeyManager *existingKeyManager = _existingKeyManager;
+ [self terminateWithKey:[[command.mapping.keySequence lastObject] integerValue] completion:nil];
- _terminatingKey = [[command.mapping.keySequence lastObject] integerValue];
- [window orderOut:nil];
- [NSApp abortModal];
- [self reset];
+ return YES;
+}
- // If we cancel while autocompleting, we want the invoking key manager to
- // also get the key mapping, as we want escapes and Ctrl-[s and other
- // mappings designed to get the user into insert mode to take effect no
- // matter what.
- if (command && _autocompleting)
- [existingKeyManager handleKeys:command.keySequence];
+- (void)terminateWithKey:(NSInteger)terminatingKey completion:(ViCompletion *)completion
+{
+ _terminatingKey = terminatingKey;
- return YES;
+ [_delegate completionController:self didTerminateWithKey:_terminatingKey selectedCompletion:completion];
+
+ [[NSNotificationCenter defaultCenter]
+ removeObserver:self
+ name:NSWindowDidResignMainNotification
+ object:nil];
+ [window orderOut:nil];
+
+ [self reset];
}
- (BOOL)accept_if_not_autocompleting:(ViCommand *)command
@@ -456,10 +463,9 @@ - (BOOL)accept:(ViCommand *)command
return YES;
}
-- (BOOL)filter:(ViCommand *)command
+- (BOOL)input_character:(ViCommand *)command
{
NSInteger keyCode = [[command.mapping.keySequence lastObject] integerValue];
- [_existingKeyManager handleKeys:command.keySequence];
SEL sel = @selector(completionController:shouldTerminateForKey:);
if ([_delegate respondsToSelector:sel]) {
@@ -480,33 +486,25 @@ - (BOOL)filter:(ViCommand *)command
[self filterCompletions];
if ([_filteredCompletions count] == 0) {
- _terminatingKey = keyCode;
- [window orderOut:nil];
- [NSApp abortModal];
- return YES;
+ [self terminateWithKey:keyCode completion:nil];
}
return YES;
}
-- (BOOL)backspace:(ViCommand *)command {
+- (BOOL)input_backspace:(ViCommand *)command {
NSInteger keyCode = [[command.mapping.keySequence lastObject] integerValue];
- [_existingKeyManager handleKeys:command.keySequence];
+
if (_filter.length > 0) {
[_filter deleteCharactersInRange:NSMakeRange(_filter.length - 1, 1)];
[self filterCompletions];
} else {
/* This backspace goes beyond the filter into the prefix. Dismiss the window. */
- _terminatingKey = keyCode;
- [window orderOut:nil];
- [NSApp abortModal];
+ [self terminateWithKey:keyCode completion:nil];
}
if ([_filteredCompletions count] == 0) {
- _terminatingKey = keyCode;
- [window orderOut:nil];
- [NSApp abortModal];
- return YES;
+ [self terminateWithKey:keyCode completion:nil];
}
return YES;
}
@@ -563,7 +561,7 @@ - (BOOL)accept_or_complete_partially:(ViCommand *)command
return YES;
}
-- (BOOL)move_up:(ViCommand *)command
+- (BOOL)select_previous_completion:(ViCommand *)command
{
NSUInteger row = [tableView selectedRow];
if (row == -1) {
@@ -580,7 +578,7 @@ - (BOOL)move_up:(ViCommand *)command
return selectionSuccessful;
}
-- (BOOL)move_down:(ViCommand *)command
+- (BOOL)select_next_completion:(ViCommand *)command
{
NSUInteger row = [tableView selectedRow];
if (row == -1) {
@@ -671,8 +669,10 @@ - (BOOL)selectCompletionRowWithDelegateCalls:(NSInteger)completionRow {
return YES;
}
-- (NSPoint)computeWindowOriginForSize:(NSSize)winsz {
- NSPoint origin = _prefixScreenRect.origin;
+- (NSPoint)computeWindowOriginForSize:(NSSize)winsz fromPrefixWindow:(NSWindow *)prefixWindow
+{
+ NSRect screenRect = [prefixWindow convertRectToScreen:_prefixWindowRect];
+ NSPoint origin = screenRect.origin;
NSScreen *screen = [NSScreen mainScreen];
NSSize screenSize = [window convertRectFromScreen:[screen visibleFrame]].size;
@@ -681,7 +681,7 @@ - (NSPoint)computeWindowOriginForSize:(NSSize)winsz {
the completions above or below. Default is below.
*/
if ([NSApp modalWindow] != window) {
- if (winsz.height > _prefixScreenRect.origin.y) {
+ if (winsz.height > screenRect.origin.y) {
_positionCompletionsBelowPrefix = NO;
} else {
_positionCompletionsBelowPrefix = YES;
@@ -708,7 +708,7 @@ - (NSPoint)computeWindowOriginForSize:(NSSize)winsz {
if (_positionCompletionsBelowPrefix) {
origin.y -= winsz.height;
} else {
- origin.y += _prefixScreenRect.size.height;
+ origin.y += screenRect.size.height;
}
return origin;
2  app/ViCompletionWindow.h
View
@@ -23,7 +23,7 @@
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-@interface ViCompletionWindow : NSWindow
+@interface ViCompletionWindow : NSPanel
{
}
@end
8 app/ViCompletionWindow.m
View
@@ -27,9 +27,13 @@
@implementation ViCompletionWindow
-- (BOOL)canBecomeKeyWindow
+- (ViCompletionWindow *)init
{
- return YES;
+ if (self = [super init]) {
+ [self setBecomesKeyOnlyIfNeeded:YES];
+ }
+
+ return self;
}
@end
33 app/ViDocument.h
View
@@ -32,6 +32,7 @@
@class ViTextView;
@class ExCommand;
+@class ViFold;
/** A document.
*/
@@ -208,6 +209,38 @@
- (void)registerMark:(ViMark *)mark;
- (void)unregisterMark:(ViMark *)mark;
+/** @name Dealing with manual folds */
+- (void)createFoldForRange:(NSRange)range;
+/** Closes the deepest fold at the given location, and recursively up +levels+ parent folds.
+ *
+ * @returns The range of the closed fold.
+ */
+- (NSRange)closeFoldAtLocation:(NSUInteger)aLocation levels:(NSUInteger)levels;
+/** Opens the deepest fold at the given location, and recursively up +levels+ parent folds.
+ *
+ * @returns the range of the opened fold.
+ */
+- (NSRange)openFoldAtLocation:(NSUInteger)aLocation levels:(NSUInteger)levels;
+/** Toggles the deepest fold at the given location, closing it if it is
+ * open and opening it if it is closed.
+ */
+- (void)toggleFoldAtLocation:(NSUInteger)aLocation;
+/**
+ * Look up a fold by location in the text.
+ *
+ * Returns nil if there is no fold at that location.
+ */
+- (ViFold *)foldAtLocation:(NSUInteger)aLocation;
+/**
+ * Look up a fold range by location in the text.
+ *
+ * If the location has a fold, returns a range whose location is the
+ * first character of the first line of the fold and whose length is
+ * through the first character of the last line of the fold.
+ * Returns NSRange(NSNotFound,-1) if there is no fold at that location.
+ */
+- (NSRange)foldRangeAtLocation:(NSUInteger)aLocation;
+
- (void)associateView:(ViViewController *)viewController forKey:(NSString *)key;
- (NSSet *)associatedViewsForKey:(NSString *)key;
369 app/ViDocument.m
View
@@ -47,6 +47,7 @@
#import "ViDocumentController.h"
#import "NSURL-additions.h"
#import "ViTextView.h"
+#import "ViFold.h"
BOOL __makeNewWindowInsteadOfTab = NO;
@@ -257,6 +258,7 @@ - (BOOL)readFromURL:(NSURL *)absoluteURL
returnError = error;
[self setBusy:NO];
[self setLoader:nil];
+
if (error) {
/* If the file doesn't exist, treat it as an untitled file. */
if ([error isFileNotFoundError]) {
@@ -567,7 +569,7 @@ - (ViDocumentView *)makeViewWithParser:(ViParser *)aParser
relative:[userDefaults boolForKey:@"relativenumber"]
forScrollView:[textView enclosingScrollView]];
[self updatePageGuide];
- [textView setWrapping:_wrap];
+ [textView setWrapping:_wrap duringInit:YES];
[[ViEventManager defaultManager] emit:ViEventDidMakeView for:self with:self, documentView, textView, nil];
@@ -1467,11 +1469,11 @@ - (void)textStorageDidChangeLines:(NSNotification *)notification
{
NSDictionary *userInfo = [notification userInfo];
- if (!_ignoreEditing) {
- NSUInteger lineIndex = [[userInfo objectForKey:@"lineIndex"] unsignedIntegerValue];
- NSUInteger linesRemoved = [[userInfo objectForKey:@"linesRemoved"] unsignedIntegerValue];
- NSUInteger linesAdded = [[userInfo objectForKey:@"linesAdded"] unsignedIntegerValue];
+ NSUInteger lineIndex = [[userInfo objectForKey:@"lineIndex"] unsignedIntegerValue];
+ NSUInteger linesRemoved = [[userInfo objectForKey:@"linesRemoved"] unsignedIntegerValue];
+ NSUInteger linesAdded = [[userInfo objectForKey:@"linesAdded"] unsignedIntegerValue];
+ if (!_ignoreEditing) {
NSInteger diff = linesAdded - linesRemoved;
if (diff == 0)
return;
@@ -1544,14 +1546,15 @@ - (void)textStorageDidProcessEditing:(NSNotification *)notification
- (void)enableLineNumbers:(BOOL)flag relative:(BOOL)relative forScrollView:(NSScrollView *)aScrollView
{
if (flag) {
- ViRulerView *lineNumberView = [[ViRulerView alloc] initWithScrollView:aScrollView];
- [aScrollView setVerticalRulerView:lineNumberView];
- [lineNumberView setRelative:relative];
+ ViRulerView *rulerView = [[ViRulerView alloc] initWithScrollView:aScrollView];
+ [aScrollView setVerticalRulerView:rulerView];
+ [rulerView setRelativeLineNumbers:relative];
[aScrollView setHasHorizontalRuler:NO];
[aScrollView setHasVerticalRuler:YES];
[aScrollView setRulersVisible:YES];
- } else
+ } else {
[aScrollView setRulersVisible:NO];
+ }
}
- (void)enableLineNumbers:(BOOL)flag relative:(BOOL)relative
@@ -1927,6 +1930,354 @@ - (void)unregisterMark:(ViMark *)mark
}
#pragma mark -
+#pragma mark Folding
+- (void)createFoldForRange:(NSRange)range
+{
+ // The incoming range includes the last newline; however, we don't consider
+ // the last newline part of the fold, so we're not going to operate on it
+ // at all.
+ range = NSMakeRange(range.location, range.length - 1);
+
+ ViFold *newFold = [ViFold fold];
+
+ ViFold *foldAtStart = [self.textStorage attribute:ViFoldAttributeName atIndex:range.location effectiveRange:NULL];
+ ViFold *foldAtEnd = [self.textStorage attribute:ViFoldAttributeName atIndex:NSMaxRange(range) + 1 effectiveRange:NULL];
+
+ ViFold *newFoldParent = closestCommonParentFold(foldAtStart, foldAtEnd);
+ if (newFoldParent) {
+ addChildToFold(newFoldParent, newFold);
+ }
+
+ // The list of ranges that we won't be setting to have the new fold.
+ [self.textStorage enumerateAttribute:ViFoldAttributeName
+ inRange:range
+ options:NULL
+ usingBlock:^(ViFold *overlappingFold, NSRange overlappingFoldRange, BOOL *stop) {
+ if (overlappingFold == newFold.parent) {
+ // If the overlapping fold is the same as the new fold's parent, then
+ // the fold at this location will be the new fold (note that this
+ // includes cases where the new fold is at the root level).
+ [self.textStorage addAttribute:ViFoldAttributeName value:newFold range:overlappingFoldRange];
+ } else {
+ // Otherwise, the new fold becomes a parent to the overlapping fold's
+ // topmost parent that has the new fold's parent as its parent.
+ ViFold *candidateChildFold = [overlappingFold topmostParentWithParent:newFold.parent];
+
+ addChildToFold(newFold, candidateChildFold);
+ }
+ }];
+
+ // If the fold at the start has the same start point as the new fold, mark
+ // it and all its parents as such until we hit the new fold we inserted.
+ if (foldAtStart && foldAtStart != newFoldParent) {
+ do {
+ foldAtStart.sameStartAsParent = YES;
+ } while ((foldAtStart = foldAtStart.parent) && foldAtStart != newFold);
+ }
+ // If the fold at the end has the same end point as the new fold, mark
+ // it and all its parents as such until we hit the new fold we inserted.
+ if (foldAtEnd && foldAtEnd != newFoldParent) {
+ do {
+ foldAtEnd.sameEndAsParent = YES;
+ } while ((foldAtEnd = foldAtEnd.parent) && foldAtEnd != newFold);
+ }
+
+ [[NSNotificationCenter defaultCenter] postNotificationName:ViFoldsChangedNotification object:self userInfo:nil];
+}
+
+- (NSRange)closeFoldAtLocation:(NSUInteger)aLocation levels:(NSUInteger)levels
+{
+ NSRange foldRange;
+ ViFold *fold = [self.textStorage attribute:ViFoldAttributeName
+ atIndex:aLocation
+ longestEffectiveRange:&foldRange
+ inRange:NSMakeRange(0, [self.textStorage length])];
+
+ // Pop up the hierarchy to the bottommost open fold with the same start
+ // point, if any.
+ while (fold.hasSameStartAsParent && ! fold.isOpen)
+ fold = fold.parent;
+
+ while (fold.hasSameEndAsParent && ! fold.isOpen)
+ fold = fold.parent;
+
+ if (fold) {
+ return [self closeFold:fold inRange:foldRange levels:levels];
+ } else {
+ return NSMakeRange(NSNotFound, -1);
+ }
+}
+
+- (NSRange)openFoldAtLocation:(NSUInteger)aLocation levels:(NSUInteger)levels
+{
+ NSRange foldRange;
+ ViFold *fold = [self.textStorage attribute:ViFoldAttributeName
+ atIndex:aLocation
+ longestEffectiveRange:&foldRange
+ inRange:NSMakeRange(0, [self.textStorage length])];
+
+ // Pop up the hierarchy to the topmost closed fold with the same start
+ // point, if any.
+ while (fold.hasSameStartAsParent && fold.isOpen)
+ fold = fold.parent;
+ while (fold.hasSameStartAsParent && ! fold.parent.isOpen)
+ fold = fold.parent;
+
+ while (fold.hasSameEndAsParent && fold.isOpen)
+ fold = fold.parent;
+ while (fold.hasSameEndAsParent && ! fold.parent.isOpen)
+ fold = fold.parent;
+
+ if (fold && fold.isOpen) {
+ return foldRange;
+ } else if (fold) {
+ return [self openFold:fold inRange:foldRange levels:levels];
+ } else {
+ return NSMakeRange(NSNotFound, -1);
+ }
+}
+
+- (void)toggleFoldAtLocation:(NSUInteger)aLocation
+{
+ NSRange foldRange;
+ ViFold *fold = [self.textStorage attribute:ViFoldAttributeName
+ atIndex:aLocation
+ longestEffectiveRange:&foldRange
+ inRange:NSMakeRange(0, [self.textStorage length])];
+
+ if (fold && fold.isOpen) {
+ [self closeFold:fold inRange:foldRange];
+ } else if (fold) {
+ [self openFold:fold inRange:foldRange];
+ }
+}
+
+- (NSRange)closeFold:(ViFold *)foldToClose inRange:(NSRange)foldRange
+{
+ return [self closeFold:foldToClose inRange:foldRange levels:1];
+}
+
+- (NSRange)closeFold:(ViFold *)foldToClose inRange:(NSRange)foldRange levels:(NSUInteger)levels
+{
+ NSUInteger maxCloseDepth = foldToClose.depth;
+ NSUInteger minCloseDepth = (levels - 1 > foldToClose.depth) ? 1 : foldToClose.depth - (levels - 1);
+ NSUInteger foldStart = NSNotFound;
+ ViFold *currentFold = nil;
+ do {
+ foldStart = foldRange.location;
+ currentFold = [self.textStorage attribute:ViFoldAttributeName
+ atIndex:foldStart - 1
+ longestEffectiveRange:&foldRange
+ inRange:NSMakeRange(0, foldStart)];
+ } while (currentFold &&
+ (currentFold = closestCommonParentFold(currentFold, foldToClose)) &&
+ currentFold.depth >= minCloseDepth);
+
+ __block ViFold *lastFold = nil;
+ __block NSValue *totalClosedRange = [NSValue valueWithRange:NSMakeRange(foldStart, 0)];
+ [self.textStorage enumerateAttribute:ViFoldAttributeName
+ inRange:NSMakeRange(foldStart, [self.textStorage length] - foldStart)
+ options:NULL
+ usingBlock:^(ViFold *currentFold, NSRange currentFoldRange, BOOL *stop) {
+ if (! currentFold) {
+ *stop = YES;
+
+ return;
+ }
+
+ BOOL startOfCurrentFold = ! lastFold || lastFold.depth < currentFold.depth;
+ ViFold *foldToClose = nil;
+ if (startOfCurrentFold && currentFold.depth <= maxCloseDepth) {
+ foldToClose = currentFold;
+ } else if (currentFold.hasSameStartAsParent && currentFold.depth > minCloseDepth) {
+ foldToClose = currentFold;
+ do {
+ foldToClose = foldToClose.parent;
+ } while (foldToClose &&
+ foldToClose.hasSameStartAsParent &&
+ foldToClose.depth > maxCloseDepth);
+
+ if (foldToClose && foldToClose.depth > maxCloseDepth) {
+ foldToClose = nil;
+ }
+ } else if (currentFold.hasSameEndAsParent && currentFold.depth > minCloseDepth) {
+ foldToClose = currentFold;
+ do {
+ foldToClose = foldToClose.parent;
+ } while (foldToClose &&
+ foldToClose.hasSameEndAsParent &&
+ foldToClose.depth > maxCloseDepth);
+
+ if (foldToClose && foldToClose.depth > maxCloseDepth) {
+ foldToClose = nil;
+ }
+ }
+ ViFold *foldToMark = foldToClose;
+ do {
+ foldToMark.open = NO;
+ } while (foldToMark.hasSameStartAsParent && (foldToMark = foldToMark.parent) && foldToMark.depth >= minCloseDepth);
+
+ ViFold *closestCommonParent = nil;
+ // Stop if this isn't the first fold and the current fold isn't
+ // contiguous with the previous one, or if this isn't the first fold
+ // and the current fold doesn't share a parent of depth < maxCloseDepth
+ // with the previous one.
+ if (lastFold &&
+ (currentFoldRange.location != NSMaxRange([totalClosedRange rangeValue]) ||
+ ((closestCommonParent = closestCommonParentFold(lastFold, currentFold)) &&
+ closestCommonParent.depth < minCloseDepth))) {
+ *stop = YES;
+
+ return;
+ }
+
+ if (foldToClose && currentFoldRange.location == foldStart) {
+ [self.textStorage addAttributes:@{ NSAttachmentAttributeName: [ViFold foldAttachment] }
+ range:NSMakeRange(currentFoldRange.location, 1)];
+
+ currentFoldRange = NSMakeRange(currentFoldRange.location + 1, currentFoldRange.length - 1);
+ }
+ if (startOfCurrentFold && currentFoldRange.location != foldStart) {
+ [self.textStorage removeAttribute:NSAttachmentAttributeName
+ range:NSMakeRange(currentFoldRange.location, 1)];
+ }
+
+ [self.textStorage addAttributes:@{ ViFoldedAttributeName: @YES }
+ range:currentFoldRange];
+
+ lastFold = currentFold;
+ totalClosedRange = [NSValue valueWithRange:NSUnionRange([totalClosedRange rangeValue], currentFoldRange)];
+ }];
+
+ [[NSNotificationCenter defaultCenter] postNotificationName:ViFoldClosedNotification object:self userInfo:nil];
+
+ return [totalClosedRange rangeValue];
+}
+
+- (NSRange)openFold:(ViFold *)foldToOpen inRange:(NSRange)foldRange
+{
+ return [self openFold:foldToOpen inRange:foldRange levels:1];
+}
+
+- (NSRange)openFold:(ViFold *)foldToOpen inRange:(NSRange)foldRange levels:(NSUInteger)levels
+{
+ NSUInteger maxOpenDepth = foldToOpen.depth + levels;
+ NSUInteger minOpenDepth = foldToOpen.depth;
+ NSUInteger foldStart = NSNotFound;
+ __block ViFold *currentFold = nil;
+ do {
+ foldStart = foldRange.location;
+ currentFold = [self.textStorage attribute:ViFoldAttributeName
+ atIndex:foldStart - 1
+ longestEffectiveRange:&foldRange
+ inRange:NSMakeRange(0, foldStart)];
+ } while (currentFold &&
+ (currentFold = closestCommonParentFold(currentFold, foldToOpen)) &&
+ currentFold.depth >= minOpenDepth);
+
+ currentFold = nil;
+ __block ViFold *lastFold = nil;
+ __block NSValue *totalOpenedRange = [NSValue valueWithRange:NSMakeRange(foldStart, 0)];
+ [self.textStorage enumerateAttribute:ViFoldAttributeName
+ inRange:NSMakeRange(foldStart, [self.textStorage length] - foldStart)
+ options:NULL
+ usingBlock:^(ViFold *currentFold, NSRange currentFoldRange, BOOL *stop) {
+ if (! currentFold) {
+ *stop = YES;
+
+ return;
+ }
+
+ BOOL startOfCurrentFold = ! lastFold || lastFold.depth < currentFold.depth;
+ BOOL openCurrentFold = currentFold.isOpen || currentFold.depth < maxOpenDepth;
+
+ ViFold *closestCommonParent = nil;
+ // Stop if this isn't the first fold and the current fold isn't
+ // contiguous with the previous one, or if this isn't the first fold
+ // and the current fold doesn't share a parent of depth <= maxCloseDepth
+ // with the previous one.
+ if (lastFold &&
+ (currentFoldRange.location != NSMaxRange([totalOpenedRange rangeValue]) ||
+ ((closestCommonParent = closestCommonParentFold(lastFold, currentFold)) &&
+ closestCommonParent.depth < minOpenDepth))) {
+ *stop = YES;
+
+ return;
+ }
+
+ if (startOfCurrentFold && openCurrentFold) {
+ [self.textStorage removeAttribute:NSAttachmentAttributeName
+ range:NSMakeRange(currentFoldRange.location, 1)];
+ } else if (startOfCurrentFold && lastFold.isOpen && ! currentFold.isOpen) {
+ NSRange attachmentRange = NSMakeRange(currentFoldRange.location, 1);
+
+ // If this is the start of the current fold and it's not a fold we're going to
+ // be opening, go ahead and mark its first character with the fold attachment.
+ [self.textStorage addAttribute:NSAttachmentAttributeName
+ value:[ViFold foldAttachment]
+ range:attachmentRange];
+
+ // We won't be opening the rest of this range, but we do want to
+ // drop the folded attribute from the attachment character.
+ [self.textStorage removeAttribute:ViFoldedAttributeName
+ range:attachmentRange];
+ }
+
+ if (openCurrentFold) {
+ currentFold.open = YES;
+
+ [self.textStorage removeAttribute:ViFoldedAttributeName
+ range:currentFoldRange];
+ }
+
+ lastFold = currentFold;
+ totalOpenedRange = [NSValue valueWithRange:NSUnionRange([totalOpenedRange rangeValue], currentFoldRange)];
+ }];
+
+ [[NSNotificationCenter defaultCenter] postNotificationName:ViFoldOpenedNotification object:self userInfo:nil];
+
+ return [totalOpenedRange rangeValue];
+}
+
+- (ViFold *)foldAtLocation:(NSUInteger)aLocation
+{
+ return [self.textStorage attribute:ViFoldAttributeName atIndex:aLocation effectiveRange:NULL];
+}
+
+- (NSRange)foldRangeAtLocation:(NSUInteger)aLocation
+{
+ __block NSRange foldRange = NSMakeRange(NSNotFound, -1);
+ __block ViFold *lastFold = nil;
+ [self.textStorage enumerateAttribute:ViFoldAttributeName
+ inRange:NSMakeRange(aLocation, [self.textStorage length] - aLocation)
+ options:NULL
+ usingBlock:^(ViFold *currentFold, NSRange currentFoldRange, BOOL *stop) {
+ // Stop if this isn't the first fold and the current fold isn't
+ // contiguous with the previous one, or if this isn't the first fold
+ // and the current fold doesn't share a parent with the previous one.
+ // We also stop if we've reached a point with no fold.
+ if (! currentFold ||
+ (lastFold &&
+ (currentFoldRange.location - 1 != NSMaxRange(foldRange) ||
+ ! closestCommonParentFold(lastFold, currentFold)))) {
+ *stop = YES;
+
+ return;
+ }
+
+ if (foldRange.location == NSNotFound) {
+ foldRange = currentFoldRange;
+ } else {
+ foldRange = NSUnionRange(foldRange, currentFoldRange);
+ }
+
+ lastFold = currentFold;
+ }];
+
+ return foldRange;
+}
+
+#pragma mark -
- (NSString *)description
{
73 app/ViFold.h
View
@@ -0,0 +1,73 @@
+extern NSString *const ViFoldedAttributeName;
+extern NSString *const ViFoldAttributeName;
+
+#define ViFoldsChangedNotification @"ViFoldsChangedNotification"
+#define ViFoldOpenedNotification @"ViFoldOpenedNotification"
+#define ViFoldClosedNotification @"ViFoldClosedNotification"
+
+/**
+ * ViFold contains information about a given fold in a document. Its most
+ * important information is the start and end of the fold, and the parent
+ * fold (if any), as well as the child folds (if any). It also stores the
+ * fold depth, which is a number indicating how many parents exist higher
+ * in the fold hierarchy above this fold. A top-level fold should have a
+ * depth of 1.
+ *
+ * It also stores state information; specifically, whether the fold is open
+ * or closed.
+ */
+@interface ViFold : NSObject
+{
+ NSMutableSet *_children;
+}
+
+@property (nonatomic,getter=isOpen) BOOL open;
+@property (nonatomic,getter=hasSameStartAsParent) BOOL sameStartAsParent;
+@property (nonatomic,getter=hasSameEndAsParent) BOOL sameEndAsParent;
+@property (nonatomic) ViFold *parent;
+@property (nonatomic) NSUInteger depth;
+@property (nonatomic,readonly) NSSet *children;
+
++ (NSTextAttachment *)foldAttachment;
+
++ (ViFold *)fold;
+- (ViFold *)init;
+
+- (void)addChild:(ViFold *)childFold;
+- (void)removeChild:(ViFold *)childFold;
+
+- (BOOL)hasParent:(ViFold *)aFold;
+/** @return The topmost parent fold of this fold. If this fold has no parent, returns this fold. */
+- (ViFold *)topmostParent;
+/**
+ * Stops one step before a typical search for a parent, returning instead the
+ * last parent that *isn't* +markerParent+.
+ *
+ * @return The topmost parent fold of this fold whose parent is +markerParent+.
+ */
+- (ViFold *)topmostParentWithParent:(ViFold *)markerParent;
+
+@end
+
+/**
+ * Checks `firstFold` and `secondFold` to find their closest common
+ * parent. Notably, this can return either of the two folds depending
+ * on how they related to each other. If they are equal, it will return
+ * the first fold. If the second is a parent of the first, it will return
+ * the second. If the first is a parent of the second, it will return the
+ * first.
+ *
+ * Returns nil if there is no common parent.
+ */
+ViFold *closestCommonParentFold(ViFold *firstFold, ViFold *secondFold);
+/**
+ * Adds `childFold` to `parentFold` by updating both `childFold`'s parent
+ * pointer AND `parentFold`'s child set.
+ */
+void addChildToFold(ViFold *parentFold, ViFold *childFold);
+/**
+ * Finds the topmost parent of `nestedChildFold`, which may be that fold
+ * itself, and then adds it as a child to `parentFold`, as per
+ * `addChildToFold`.
+ */
+void addTopmostParentToFold(ViFold *parentFold, ViFold *nestedChildFold);
147 app/ViFold.m
View
@@ -0,0 +1,147 @@
+#import "ViFold.h"
+
+NSString *const ViFoldedAttributeName = @"ViFoldedAttribute";
+NSString *const ViFoldAttributeName = @"ViFoldAttribute";
+
+inline ViFold *closestCommonParentFold(ViFold *firstFold, ViFold *secondFold)
+{
+ if (firstFold == secondFold) {
+ return firstFold;
+ }
+ if ([firstFold hasParent:secondFold]) {
+ return secondFold;
+ }
+ if ([secondFold hasParent:firstFold]) {
+ return firstFold;
+ }
+
+ ViFold *currentFold = secondFold;
+ while (currentFold && ! [firstFold hasParent:currentFold]) {
+ currentFold = currentFold.parent;
+ }
+
+ if (! currentFold) {
+ currentFold = firstFold;
+ while (currentFold && ! [secondFold hasParent:currentFold]) {
+ currentFold = currentFold.parent;
+ }
+ }
+
+ return currentFold;
+}
+
+inline void addChildToFold(ViFold *parentFold, ViFold *childFold)
+{
+ if (parentFold == childFold)
+ return;
+
+ [parentFold addChild:childFold];
+}
+
+inline void addTopmostParentToFold(ViFold *parentFold, ViFold *nestedChildFold)
+{
+ ViFold *topmostFold = nestedChildFold;
+ while (topmostFold.parent)
+ topmostFold = topmostFold.parent;
+
+ addChildToFold(parentFold, topmostFold);
+}
+
+static NSTextAttachment *foldAttachment = nil;
+
+@implementation ViFold
+
++ (NSTextAttachment *)foldAttachment
+{
+ if (! foldAttachment) {
+ NSURL *foldImageURL = [[NSBundle mainBundle] URLForResource:@"tag" withExtension:@"png"];
+ NSError *error = nil;
+ NSFileWrapper *foldImageFile = [[NSFileWrapper alloc] initWithURL:foldImageURL options:0 error:&error];
+ if (! error)
+ foldAttachment = [[NSTextAttachment alloc] initWithFileWrapper:foldImageFile];
+ }
+
+ return foldAttachment;
+}
+
++ (ViFold *)fold
+{
+ return [[super alloc] init];
+}
+
+- (ViFold *)init
+{
+ if (self = [super init]) {
+ _depth = 1;
+ _open = YES;
+ _parent = nil;
+ _children = [NSMutableSet set];
+ }
+
+ return self;
+}
+
+- (void)addChild:(ViFold *)childFold
+{
+ [_children addObject:childFold];
+
+ childFold.parent = self;
+ childFold.depth = self.depth + 1;
+}
+
+- (void)removeChild:(ViFold *)childFold
+{
+ [_children removeObject:childFold];
+
+ childFold.parent = nil;
+ childFold.depth -= self.depth;
+}
+
+- (void)setDepth:(NSUInteger)depth
+{
+ _depth = depth;
+
+ [_children enumerateObjectsUsingBlock:^(ViFold *child, BOOL *s) {
+ child.depth = _depth + 1;
+ }];
+}
+
+- (BOOL)hasParent:(ViFold *)aFold
+{
+ ViFold *currentFold = _parent;
+ while (currentFold && currentFold != aFold)
+ currentFold = currentFold.parent;
+
+ return currentFold == aFold;
+}
+
+- (ViFold *)topmostParent
+{
+ ViFold *currentFold = self;
+ while (currentFold.parent)
+ currentFold = currentFold.parent;
+
+ return currentFold;
+}
+
+- (ViFold *)topmostParentWithParent:(ViFold *)markerParent
+{
+ ViFold *currentFold = self;
+ while (currentFold && currentFold.parent != markerParent)
+ currentFold = currentFold.parent;
+
+ return currentFold;
+}
+
+- (NSString *)description
+{
+ if (_parent)
+ return [NSString stringWithFormat:@"<ViFold %p: parent %@>",
+ self,
+ _parent];
+ else
+ return [NSString stringWithFormat:@"<ViFold %p>",
+ self];
+}
+
+@end
23 app/ViFoldMarginView.h
View
@@ -0,0 +1,23 @@
+@class ViTextView;
+
+/**
+ * A view that presents fold margin indicators in a ViRulerView.
+ *
+ * A given fold indicator can be clicked to close or open that fold.
+ */
+@interface ViFoldMarginView : NSView
+{
+ ViTextView *_textView;
+}
+
+- (ViFoldMarginView *)initWithTextView:(ViTextView *)aTextView;
+
+- (void)setTextView:(ViTextView *)aTextView;
+
+- (void)updateViewFrame;
+
+- (void)drawFoldsInRect:(NSRect)aRect visibleRect:(NSRect)visibleRect;
+
+- (void)foldMarginMouseUp:(NSEvent *)theEvent;
+
+@end
148 app/ViFoldMarginView.m
View
@@ -0,0 +1,148 @@
+#import "ViFoldMarginView.h"
+
+#import "ViFold.h"
+#import "ViTextView.h"
+
+#define FOLD_MARGIN_WIDTH 10
+
+@implementation ViFoldMarginView
+
+- (ViFoldMarginView *)initWithTextView:(ViTextView *)aTextView
+{
+ if (self = [super init]) {
+ [self setTextView:aTextView];
+
+ self.autoresizingMask = NSViewMinXMargin;
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+- (void)setTextView:(ViTextView *)aTextView
+{
+ NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
+ if (_textView != aTextView) {
+ [notificationCenter removeObserver:self
+ name:ViTextStorageChangedLinesNotification
+ object:_textView.textStorage];
+ }
+
+ if (aTextView != nil) {
+ [notificationCenter addObserver:self
+ selector:@selector(textStorageDidChangeLines:)
+ name:ViTextStorageChangedLinesNotification
+ object:_textView.textStorage];
+ }
+
+ _textView = aTextView;
+}
+
+- (void)textStorageDidChangeLines:(NSNotification *)notification
+{
+ NSDictionary *userInfo = [notification userInfo];
+
+ NSUInteger linesRemoved = [[userInfo objectForKey:@"linesRemoved"] unsignedIntegerValue];
+ NSUInteger linesAdded = [[userInfo objectForKey:@"linesAdded"] unsignedIntegerValue];
+
+ NSInteger diff = linesAdded - linesRemoved;
+ if (diff == 0)
+ return;
+
+ [self updateViewFrame];
+
+ [self setNeedsDisplay:YES];
+}
+
+// We override this because due to how we do drawing here, simply
+// saying the line numbers need display isn't enough; we need to
+// tell the ruler view it needs display as well.
+- (void)setNeedsDisplay:(BOOL)needsDisplay
+{
+ [super setNeedsDisplay:needsDisplay];
+
+ [[self superview] setNeedsDisplay:needsDisplay];
+}
+
+- (void)updateViewFrame
+{
+ [self setFrameSize:NSMakeSize(FOLD_MARGIN_WIDTH, _textView.bounds.size.height)];
+}
+
+- (void)drawFoldsInRect:(NSRect)aRect visibleRect:(NSRect)visibleRect
+{
+ NSLayoutManager *layoutManager;
+ NSTextContainer *container;
+ ViTextStorage *textStorage;
+ ViDocument *document;
+ NSRange range, glyphRange;
+ CGFloat ypos, yinset;
+
+ layoutManager = _textView.layoutManager;
+ container = _textView.textContainer;
+ textStorage = _textView.textStorage;
+ document = _textView.document;
+ yinset = _textView.textContainerInset.height;
+
+ if (layoutManager == nil)
+ return;
+
+ // Find the characters that are currently visible
+ glyphRange = [layoutManager glyphRangeForBoundingRect:visibleRect
+ inTextContainer:container];
+ range = [layoutManager characterRangeForGlyphRange:glyphRange
+ actualGlyphRange:NULL];
+
+ NSUInteger line = [textStorage lineNumberAtLocation:range.location];
+ NSUInteger location = range.location;
+
+ for (; location < NSMaxRange(range); line++) {
+ NSUInteger glyphIndex = [layoutManager glyphIndexForCharacterAtIndex:location];
+ NSRect rect = [layoutManager lineFragmentRectForGlyphAtIndex:glyphIndex effectiveRange:NULL];
+
+ ViFold *fold = [document foldAtLocation:location];
+ if (fold) {
+ // Note that the ruler view is only as tall as the visible
+ // portion. Need to compensate for the clipview's coordinates.
+ ypos = yinset + NSMinY(rect) - NSMinY(visibleRect);
+
+ // Draw digits flush right, centered vertically within the line
+ NSRect r;
+ r.origin.x = NSMaxX(self.superview.bounds) - FOLD_MARGIN_WIDTH;
+ r.origin.y = ypos;
+ r.size = NSMakeSize(FOLD_MARGIN_WIDTH, rect.size.height);
+
+ CGFloat alpha = 0.1 * (fold.depth + 1);
+ NSColor *foldColor = [NSColor colorWithCalibratedWhite:0.42 alpha:alpha];
+ [foldColor set];
+ NSRectFillUsingOperation(r, NSCompositeSourceOver);
+ }
+
+ /* Protect against an improbable (but possible due to
+ * preceeding exceptions in undo manager) out-of-bounds
+ * reference here.
+ */
+ if (location >= [textStorage length]) {
+ break;
+ }
+ [[textStorage string] getLineStart:NULL
+ end:&location
+ contentsEnd:NULL
+ forRange:NSMakeRange(location, 0)];
+ }
+}
+
+- (void)foldMarginMouseUp:(NSEvent *)theEvent
+{
+ NSPoint upPoint = [_textView convertPoint:[theEvent locationInWindow] fromView:nil];
+ upPoint.x = 0;
+
+ [self setNeedsDisplay:YES];
+ [_textView toggleFoldAtPoint:upPoint];
+}
+
+@end
13 app/ViGlyphGenerator.h
View
@@ -0,0 +1,13 @@
+/**
+ * This glyph generator does custom work for Vico. Its main use currently is to
+ * produce null glyphs for code that has been folded. Code that has been folded
+ * is marked with the ViFoldedAttributeName attribute set to @YES.
+ *
+ * This is pulled largely from WWDC 2010 Session 114: Advanced Cocoa Text Tips
+ * and Tricks, as linked to on cocoa-dev at
+ * http://lists.apple.com/archives/cocoa-dev/2012/Nov/msg00378.html .
+ */
+@interface ViGlyphGenerator : NSGlyphGenerator <NSGlyphStorage> {
+ id<NSGlyphStorage> _originalStorage; // the original glyph generation requester
+}
+@end
75 app/ViGlyphGenerator.m
View
@@ -0,0 +1,75 @@
+#import "ViGlyphGenerator.h"
+#import "ViFold.h"
+
+@implementation ViGlyphGenerator
+
+#pragma mark -
+#pragma mark NSGlyphGenerator interface
+
+- (void)generateGlyphsForGlyphStorage:(id<NSGlyphStorage>)destinationStorage
+ desiredNumberOfCharacters:(NSUInteger)numberOfCharacters
+ glyphIndex:(NSUInteger *)glyphIndex
+ characterIndex:(NSUInteger *)characterIndex
+{
+ // Store the original destination (likely a ViLayoutManager).
+ _originalStorage = destinationStorage;
+
+ // Call the usual glyph generator to generate the glyphs, but tell it the requesting
+ // NSGlyphStorage object is us, so that we can intercept the changes and do whatever
+ // we need to there.
+ [[NSGlyphGenerator sharedGlyphGenerator] generateGlyphsForGlyphStorage:self
+ desiredNumberOfCharacters:numberOfCharacters
+ glyphIndex:glyphIndex
+ characterIndex:characterIndex];
+
+ _originalStorage = nil;
+}
+
+#pragma mark -
+#pragma mark NSGlyphStorage interface
+
+- (void)insertGlyphs:(const NSGlyph *)glyphs length:(NSUInteger)incomingGlyphLength forStartingGlyphAtIndex:(NSUInteger)glyphIndex characterIndex:(NSUInteger)characterIndex
+{
+ NSNumber *foldedAttribute;
+ NSRange effectiveRange;
+ NSGlyph *buffer = NULL;
+
+ foldedAttribute =
+ (NSNumber *)[[self attributedString] attribute:ViFoldedAttributeName
+ atIndex:characterIndex
+ longestEffectiveRange:&effectiveRange
+ inRange:NSMakeRange(0, characterIndex + incomingGlyphLength)];
+
+ // Fill in all folded characters with NSNullGlyphs.
+ if (foldedAttribute && [foldedAttribute boolValue]) {
+ NSInteger size = sizeof(NSGlyph) * incomingGlyphLength;
+ NSGlyph aGlyph = NSNullGlyph;
+ buffer = NSZoneMalloc(NULL, size);
+ memset_pattern4(buffer, &aGlyph, size);
+
+ if (effectiveRange.location == characterIndex) buffer[0] = NSControlGlyph;
+ glyphs = buffer;
+
+ }
+
+ [_originalStorage insertGlyphs:glyphs length:incomingGlyphLength forStartingGlyphAtIndex:glyphIndex characterIndex:characterIndex];
+
+ if (buffer)
+ free(buffer);
+}
+
+- (void)setIntAttribute:(NSInteger)attributeTag value:(NSInteger)value forGlyphAtIndex:(NSUInteger)glyphIndex
+{
+ [_originalStorage setIntAttribute:attributeTag value:value forGlyphAtIndex:glyphIndex];
+}
+
+- (NSAttributedString *)attributedString
+{
+ return [_originalStorage attributedString];
+}
+
+- (NSUInteger)layoutOptions {
+ return [_originalStorage layoutOptions];
+}
+
+@end
12 app/ViLayoutManager.m
View
@@ -24,6 +24,8 @@
*/
#import "ViLayoutManager.h"
+#import "ViGlyphGenerator.h"
+#import "ViTypesetter.h"
#import "ViThemeStore.h"
#include "logging.h"
@@ -31,6 +33,16 @@ @implementation ViLayoutManager
@synthesize invisiblesAttributes = _invisiblesAttributes;
+- (ViLayoutManager *)init
+{
+ if (self = [super init]) {
+ self.glyphGenerator = [[ViGlyphGenerator alloc] init];
+ self.typesetter = [[ViTypesetter alloc] init];
+ }
+
+ return self;
+}
+
- (void)setInvisiblesAttributes:(NSDictionary *)attributes
{
_invisiblesAttributes = [attributes mutableCopy];
38 app/ViLineNumberView.h
View
@@ -0,0 +1,38 @@
+@class ViTextView;
+
+/**
+ * A view that presents line numbers in a ViRulerView.
+ *
+ * The line numbers can be clicked and dragged to select one or more lines
+ * in the text view.
+ */
+@interface ViLineNumberView : NSView
+{
+ NSDictionary *_textAttributes;
+ NSPoint _fromPoint;
+ NSImage *_digits[10];
+ NSSize _digitSize;
+
+ BOOL _relative;
+
+ ViTextView *_textView;
+}
+
+@property (nonatomic,readwrite) NSColor *backgroundColor;
+
+- (ViLineNumberView *)initWithTextView:(ViTextView *)aTextView backgroundColor:(NSColor *)aColor;
+
+- (void)setRelative:(BOOL)flag;
+- (void)setTextView:(ViTextView *)aTextView;
+
+- (void)updateViewFrame;
+- (CGFloat)requiredThickness;
+
+- (void)resetTextAttributes;
+
+- (void)drawLineNumbersInRect:(NSRect)aRect visibleRect:(NSRect)visibleRect;
+
+- (void)lineNumberMouseDown:(NSEvent *)theEvent;
+- (void)lineNumberMouseDragged:(NSEvent *)theEvent;
+
+@end
346 app/ViLineNumberView.m
View
@@ -0,0 +1,346 @@
+#import "ViLineNumberView.h"
+
+#import "ViFold.h"
+#import "ViTextView.h"
+#import "ViThemeStore.h"
+
+#define DEFAULT_THICKNESS 22.0
+#define LINE_NUMBER_MARGIN 5.0
+
+// Created by Paul Kim on 9/28/08.
+// Copyright (c) 2008 Noodlesoft, LLC. All rights reserved.
+//
+// Permission is hereby granted, free of charge, to any person
+// obtaining a copy of this software and associated documentation
+// files (the "Software"), to deal in the Software without
+// restriction, including without limitation the rights to use,
+// copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the
+// Software is furnished to do so, subject to the following
+// conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+// OTHER DEALINGS IN THE SOFTWARE.
+
+@implementation ViLineNumberView
+
+- (ViLineNumberView *)initWithTextView:(ViTextView *)aTextView backgroundColor:(NSColor *)aColor
+{
+ if (self = [super init]) {
+ _backgroundColor = aColor;
+ _relative = NO;
+
+ self.autoresizingMask = NSViewMinXMargin | NSViewMaxXMargin;
+
+ [self setTextView:aTextView];
+ [self resetTextAttributes];
+ }
+
+ return self;
+}
+
+- (void)dealloc
+{
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+}
+
+// We override this because due to how we do drawing here, simply
+// saying the line numbers need display isn't enough; we need to
+// tell the ruler view it needs display as well.
+- (void)setNeedsDisplay:(BOOL)needsDisplay
+{
+ [super setNeedsDisplay:needsDisplay];
+
+ [[self superview] setNeedsDisplay:needsDisplay];
+}
+
+- (void)setRelative:(BOOL)flag
+{
+ if (_relative != flag) {
+ _relative = flag;
+
+ [[self superview] setNeedsDisplay:YES];
+ }
+}
+
+- (void)setTextView:(ViTextView *)aTextView
+{
+ NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
+ if (_textView != aTextView) {
+ [notificationCenter removeObserver:self
+ name:ViTextStorageChangedLinesNotification
+ object:_textView.textStorage];
+ [notificationCenter removeObserver:self
+ name:ViCaretChangedNotification
+ object:_textView];
+ }
+
+ if (aTextView != nil) {
+ [notificationCenter addObserver:self
+ selector:@selector(textStorageDidChangeLines:)
+ name:ViTextStorageChangedLinesNotification
+ object:_textView.textStorage];
+ [notificationCenter addObserver:self
+ selector:@selector(caretDidChange:)
+ name:ViCaretChangedNotification
+ object:_textView];
+
+ [notificationCenter addObserver:self
+ selector:@selector(foldsDidUpdate:)
+ name:ViFoldsChangedNotification
+ object:_textView.document];
+ [notificationCenter addObserver:self
+ selector:@selector(foldsDidUpdate:)
+ name:ViFoldOpenedNotification
+ object:_textView.document];
+ [notificationCenter addObserver:self
+ selector:@selector(foldsDidUpdate:)
+ name:ViFoldClosedNotification
+ object:_textView.document];
+ }
+
+ _textView = aTextView;
+}
+
+- (CGFloat)requiredThickness
+{
+ NSUInteger lineCount, digits;
+
+ lineCount = _textView.textStorage.lineCount;
+ digits = (unsigned)log10(lineCount) + 1;
+ return ceilf(MAX(DEFAULT_THICKNESS, _digitSize.width * digits + LINE_NUMBER_MARGIN * 2));
+}
+
+- (void)updateViewFrame
+{
+ [self setFrameSize:NSMakeSize([self requiredThickness], _textView.bounds.size.height)];
+}
+
+#pragma mark -
+#pragma mark String image initialization
+
+- (void)drawString:(NSString *)string intoImage:(NSImage *)image
+{
+ [image lockFocusFlipped:NO];
+ [_backgroundColor set];
+ NSRectFill(NSMakeRect(0, 0, _digitSize.width, _digitSize.height));
+ [string drawAtPoint:NSMakePoint(0.5,0.5) withAttributes:_textAttributes];
+ [image unlockFocus];
+}
+
+- (void)resetTextAttributes
+{
+ _textAttributes = @{
+ NSFontAttributeName: [NSFont labelFontOfSize:0.8 * [[ViThemeStore font] pointSize]],
+ NSForegroundColorAttributeName: [NSColor colorWithCalibratedWhite:0.42 alpha:1.0]
+ };
+
+ _digitSize = [@"8" sizeWithAttributes:_textAttributes];
+ _digitSize.width += 1.0;
+ _digitSize.height += 1.0;
+
+ [self updateViewFrame];
+
+ for (int i = 0; i < 10; i++) {
+ NSString *lineNumberString = [NSString stringWithFormat:@"%i", i];
+ _digits[i] = [[NSImage alloc] initWithSize:_digitSize];
+
+ [self drawString:lineNumberString intoImage:_digits[i]];
+ }
+
+ [self setNeedsDisplay:YES];
+}
+
+#pragma mark -
+#pragma mark Notification handlers
+
+- (void)textStorageDidChangeLines:(NSNotification *)notification
+{
+ NSDictionary *userInfo = [notification userInfo];
+
+ NSUInteger linesRemoved = [[userInfo objectForKey:@"linesRemoved"] unsignedIntegerValue];
+ NSUInteger linesAdded = [[userInfo objectForKey:@"linesAdded"] unsignedIntegerValue];
+
+ NSInteger diff = linesAdded - linesRemoved;
+ if (diff == 0)
+ return;
+
+ [self updateViewFrame];
+
+ [self setNeedsDisplay:YES];
+}
+
+- (void)caretDidChange:(NSNotification *)notification
+{
+ if (_relative)
+ [self setNeedsDisplay:YES];
+}
+
+- (void)foldsDidUpdate:(NSNotification *)notification
+{
+ [self setNeedsDisplay:YES];
+}
+
+#pragma mark -
+#pragma mark Line number drawing
+
+- (void)drawLineNumber:(NSInteger)line inRect:(NSRect)rect
+{
+ NSUInteger absoluteLine = ABS(line);
+
+ do {
+ NSUInteger remainder = absoluteLine % 10;
+ absoluteLine /= 10;
+
+ rect.origin.x -= _digitSize.width;
+
+ [_digits[remainder] drawInRect:rect
+ fromRect:NSZeroRect
+ operation:NSCompositeSourceOver
+ fraction:1.0
+ respectFlipped:YES
+ hints:nil];
+ } while (absoluteLine > 0);
+}
+
+- (NSInteger)logicalLineForLine:(NSUInteger)line location:(NSUInteger)location
+{
+ __block NSInteger logicalLine = line;
+
+ if (location > 0) {
+ ViTextStorage *textStorage = _textView.textStorage;
+ [textStorage enumerateAttribute:ViFoldedAttributeName
+ inRange:NSMakeRange(0u, location)
+ options:NULL
+ usingBlock:^(ViFold *fold, NSRange foldedRange, BOOL *s) {
+ // Unfolded ranges don't affect the logical line.
+ if (! fold) return;
+
+ // We go through each line in the folded range except the first one
+ // and subtract that line from the logical line. This makes each
+ // folded range count for one line.
+ NSUInteger currentLocation = NSMaxRange([textStorage rangeOfLineAtLocation:foldedRange.location]);
+ while (currentLocation < NSMaxRange(foldedRange) &&
+ (currentLocation = NSMaxRange([textStorage rangeOfLineAtLocation:currentLocation + 1]))) {
+ logicalLine--;
+ }
+ }];
+ }
+
+ return logicalLine;
+}
+
+- (NSInteger)currentLogicalLine
+{
+ return [self logicalLineForLine:[_textView currentLine] location:[_textView caret]];
+}
+
+- (void)drawLineNumbersInRect:(NSRect)aRect visibleRect:(NSRect)visibleRect
+{
+ NSRect bounds = [self bounds];
+
+ NSLayoutManager *layoutManager;
+ NSTextContainer *container;
+ ViTextStorage *textStorage;
+ NSRange range, glyphRange;
+ CGFloat ypos, yinset;
+
+ layoutManager = _textView.layoutManager;
+ container = _textView.textContainer;
+ textStorage = _textView.textStorage;
+ yinset = _textView.textContainerInset.height;
+
+ if (layoutManager == nil)
+ return;
+
+ // Find the characters that are currently visible
+ glyphRange = [layoutManager glyphRangeForBoundingRect:visibleRect
+ inTextContainer:container];
+ range = [layoutManager characterRangeForGlyphRange:glyphRange
+ actualGlyphRange:NULL];
+
+ NSUInteger line = [textStorage lineNumberAtLocation:range.location];
+ NSUInteger location = range.location;
+
+ if (location >= NSMaxRange(range)) {
+ // Draw line number "0" in empty documents
+
+ ypos = yinset - NSMinY(visibleRect);
+ // Draw digits flush right, centered vertically within the line
+ NSRect r;
+ r.origin.x = NSWidth(bounds) - LINE_NUMBER_MARGIN;
+ r.origin.y = ypos + 2.0;
+ r.size = _digitSize;
+
+ [self drawLineNumber:0 inRect:r];
+ return;
+ }
+
+ NSUInteger logicalLine = [self logicalLineForLine:line location:location];
+ NSInteger currentLogicalLine = _relative ? [self currentLogicalLine] : 0;
+ for (; location < NSMaxRange(range); line++) {
+ NSUInteger glyphIndex = [layoutManager glyphIndexForCharacterAtIndex:location];
+ NSRect rect = [layoutManager lineFragmentRectForGlyphAtIndex:glyphIndex effectiveRange:NULL];
+
+ NSDictionary *attributesAtLineStart = [textStorage attributesAtIndex:location effectiveRange:NULL];
+
+ if (! attributesAtLineStart[ViFoldedAttributeName]) {
+ // Note that the ruler view is only as tall as the visible
+ // portion. Need to compensate for the clipview's coordinates.
+ ypos = yinset + NSMinY(rect) - NSMinY(visibleRect);
+
+ // Draw digits flush right, centered vertically within the line
+ NSRect r;
+ r.origin.x = floor(NSWidth(bounds) - LINE_NUMBER_MARGIN);
+ r.origin.y = floor(ypos + (NSHeight(rect) - _digitSize.height) / 2.0 + 1.0);
+ r.size = _digitSize;
+
+ NSInteger numberToDraw = _relative ? logicalLine - currentLogicalLine : line;
+
+ [self drawLineNumber:numberToDraw inRect:r];
+
+ logicalLine++;
+ }
+
+ /* Protect against an improbable (but possible due to
+ * preceeding exceptions in undo manager) out-of-bounds
+ * reference here.
+ */
+ if (location >= [textStorage length]) {
+ break;
+ }
+
+ [textStorage.string getLineStart:NULL
+ end:&location
+ contentsEnd:NULL
+ forRange:NSMakeRange(location, 0)];
+ }
+}
+
+#pragma mark -
+#pragma mark Mouse handling
+
+- (void)lineNumberMouseDown:(NSEvent *)theEvent
+{
+ _fromPoint = [_textView convertPoint:[theEvent locationInWindow] fromView:nil];
+ _fromPoint.x = 0;
+ [_textView rulerView:(NSRulerView *)[self superview] selectFromPoint:_fromPoint toPoint:_fromPoint];
+}
+
+- (void)lineNumberMouseDragged:(NSEvent *)theEvent
+{
+ NSPoint toPoint = [_textView convertPoint:[theEvent locationInWindow] fromView:nil];
+ toPoint.x = 0;
+ [_textView rulerView:(NSRulerView *)[self superview] selectFromPoint:_fromPoint toPoint:toPoint];
+}
+
+@end
13 app/ViMap.h
View
@@ -26,17 +26,13 @@
#import "Nu.h"
#import "ViScope.h"
-// TODO ViMapNeedArgumentBeforeToggle
-// TODO This means we need an argument for the first invocation
-// TODO but not the second, and then we need one again.
-// TODO This describes the way macro recording works as q<register>
-// TODO followed by just q to end recording.
#define ViMapSetsDot 1ULL
#define ViMapNeedMotion 2ULL
#define ViMapIsMotion 4ULL
#define ViMapLineMode 8ULL
#define ViMapNeedArgument 16ULL
#define ViMapNoArgumentOnToggle 32ULL
+#define ViMapExcludedFromDot 64ULL
/** A mapping of a key sequence to an editor action, macro or Nu expression.
*
@@ -51,6 +47,10 @@
* - `ViMapIsMotion`: This is a motion command
* - `ViMapLineMode`: This command operates on whole lines
* - `ViMapNeedArgument`: This command needs a following character argument
+ * - `ViMapNoArgumentOnToggle`: This command needs a following character argument, but only every other time.
+ * This is how the q command for macro recording works.
+ * - `ViMapExcludedFromDot`: Explicitly indicates this command's keys should not be included in the record
+ * of keystrokes executed in a dot command. <cr> in completions, for example.
*/
@interface ViMapping : NSObject
{
@@ -123,6 +123,9 @@
/** YES if the mapping is an editor action that does not need a character argument every other invocation, like the vim `q` command. */
- (BOOL)noArgumentOnToggle;
+/** YES if the mapping's keys should not be included in the `.` command's record of keys pressed. */
+- (BOOL)isExcludedFromDot;
+
- (BOOL)wantsKeys;
+ (ViMapping *)mappingWithKeySequence:(NSArray *)aKeySequence
5 app/ViMap.m
View
@@ -166,6 +166,11 @@ - (BOOL)noArgumentOnToggle
return has_flag(ViMapNoArgumentOnToggle);
}
+- (BOOL)isExcludedFromDot
+{
+ return has_flag(ViMapExcludedFromDot);
+}
+
- (BOOL)wantsKeys
{
return [self isOperator] || [self needsArgument];
4 app/ViParser.m
View
@@ -427,9 +427,9 @@ - (id)handleKeySequenceInScope:(ViScope *)scope
_map.name];
[self setMap:_map.operatorMap];
DEBUG(@"%@ is an operator, using operatorMap %@", mapping, _map);
- } else if ([mapping needsArgument] && (! [mapping noArgumentOnToggle] || ! _lastToggleCommand || (! [[[_lastToggleCommand mapping] keySequence] isEqual:[mapping keySequence]])))
+ } else if ([mapping needsArgument] && (! [mapping noArgumentOnToggle] || ! _lastToggleCommand || (! [[[_lastToggleCommand mapping] keySequence] isEqual:[mapping keySequence]]))) {
_state = ViParserNeedChar;
- else {
+ } else {
if (_remainingExcessKeysPtr)
*_remainingExcessKeysPtr = excessKeys;
return [self completeWithError:outError];
15 app/ViRulerView.h
View
@@ -23,20 +23,21 @@
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
+#import "ViFoldMarginView.h"
+#import "ViLineNumberView.h"
+
@interface ViRulerView : NSRulerView
{
- NSDictionary *_textAttributes;
- NSColor *_backgroundColor;
- NSPoint _fromPoint;
- NSImage *_digits[10];
- NSSize _digitSize;
+ NSColor *_backgroundColor;
- BOOL _relative;
+ ViLineNumberView *_lineNumberView;
+ ViFoldMarginView *_foldMarginView;
}
-- (void)setRelative:(BOOL)flag;
+- (void)setRelativeLineNumbers:(BOOL)flag;
- (id)initWithScrollView:(NSScrollView *)aScrollView;
+
- (void)resetTextAttributes;
@end
274 app/ViRulerView.m
View
@@ -22,31 +22,9 @@
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
-// Created by Paul Kim on 9/28/08.
-// Copyright (c) 2008 Noodlesoft, LLC. All rights reserved.
-//
-// Permission is hereby granted, free of charge, to any person
-// obtaining a copy of this software and associated documentation
-// files (the "Software"), to deal in the Software without
-// restriction, including without limitation the rights to use,
-// copy, modify, merge, publish, distribute, sublicense, and/or sell
-// copies of the Software, and to permit persons to whom the
-// Software is furnished to do so, subject to the following
-// conditions:
-//
-// The above copyright notice and this permission notice shall be
-// included in all copies or substantial portions of the Software.
-//
-// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
-// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-// OTHER DEALINGS IN THE SOFTWARE.
#import "ViRulerView.h"
+#include "ViFold.h"
#import "ViTextView.h"
#import "ViThemeStore.h"
#import "NSObject+SPInvocationGrabbing.h"
@@ -60,152 +38,97 @@ @implementation ViRulerView
- (id)initWithScrollView:(NSScrollView *)aScrollView
{
if ((self = [super initWithScrollView:aScrollView orientation:NSVerticalRuler]) != nil) {
- [self setClientView:[[self scrollView] documentView]];
_backgroundColor = [NSColor colorWithDeviceRed:(float)0xED/0xFF
green:(float)0xED/0xFF
blue:(float)0xED/0xFF
alpha:1.0];
- [self resetTextAttributes];
- _relative = NO;
+
+ [self setClientView:[[self scrollView] documentView]];
}
+
return self;
}
- (void)dealloc
{
- DEBUG_DEALLOC();
[[NSNotificationCenter defaultCenter] removeObserver:self];
- for (int i = 0; i < 10; i++)
- ;
}
-- (void)setRelative:(BOOL)flag
+- (void)setRelativeLineNumbers:(BOOL)flag
{
- if (_relative != flag) {
- _relative = flag;
+ if (_lineNumberView)
+ [_lineNumberView setRelative:flag];
+}
- [self setNeedsDisplay:YES];
- }
+- (void)lineViewBoundsDidChange:(NSNotification *)aNotification
+{
+ [self setRuleThickness:[_lineNumberView requiredThickness]];
}
- (void)resetTextAttributes
{
- _textAttributes = [[NSDictionary alloc] initWithObjectsAndKeys:
- [NSFont labelFontOfSize:0.8 * [[ViThemeStore font] pointSize]], NSFontAttributeName,
- [NSColor colorWithCalibratedWhite:0.42 alpha:1.0], NSForegroundColorAttributeName,
- nil];
-
- _digitSize = [@"8" sizeWithAttributes:_textAttributes];
- _digitSize.width += 1.0;
- _digitSize.height += 1.0;
-
- [self setRuleThickness:[self requiredThickness]];
-
- for (int i = 0; i < 10; i++) {
- NSString *s = [NSString stringWithFormat:@"%i", i];
- NSImage *img = [[NSImage alloc] initWithSize:_digitSize];
- [img lockFocusFlipped:NO];
- [_backgroundColor set];
- NSRectFill(NSMakeRect(0, 0, _digitSize.width, _digitSize.height));
- [s drawAtPoint:NSMakePoint(0.5,0.5) withAttributes:_textAttributes];
- [img unlockFocus];
- _digits[i] = img;
- }
-
- [self setNeedsDisplay:YES];
+ [_lineNumberView resetTextAttributes];
}
+#pragma mark -
+#pragma mark NSRulerView interface
+
- (void)setClientView:(NSView *)aView
{
- NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
- id oldClientView = [self clientView];
-
- if (oldClientView != aView &&
- [oldClientView isKindOfClass:[NSTextView class]]) {
- [notificationCenter removeObserver:self
- name:ViTextStorageChangedLinesNotification
- object:[(NSTextView *)oldClientView textStorage]];
- [notificationCenter removeObserver:self
- name:ViCaretChangedNotification
- object:oldClientView];
- }
-
[super setClientView:aView];
- if (aView != nil && [aView isKindOfClass:[NSTextView class]]) {
- [notificationCenter addObserver:self
- selector:@selector(textStorageDidChangeLines:)
- name:ViTextStorageChangedLinesNotification
- object:[(NSTextView *)aView textStorage]];
- [notificationCenter addObserver:self
- selector:@selector(caretDidChange:)
- name:ViCaretChangedNotification
- object:aView];
- }
-}
+ if (aView != nil && [aView isKindOfClass:[ViTextView class]]) {
+ if (_lineNumberView) {
+ [_lineNumberView setTextView:(ViTextView *)aView];
+ } else {
+ _lineNumberView = [[ViLineNumberView alloc] initWithTextView:(ViTextView *)aView
+ backgroundColor:_backgroundColor];
-- (void)textStorageDidChangeLines:(NSNotification *)notification
-{
- NSDictionary *userInfo = [notification userInfo];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(subviewFrameDidChange:)
+ name:NSViewFrameDidChangeNotification
+ object:_lineNumberView];
- NSUInteger linesRemoved = [[userInfo objectForKey:@"linesRemoved"] unsignedIntegerValue];
- NSUInteger linesAdded = [[userInfo objectForKey:@"linesAdded"] unsignedIntegerValue];
+ [self addSubview:_lineNumberView];
+ [_lineNumberView updateViewFrame];
+ }
- NSInteger diff = linesAdded - linesRemoved;
- if (diff == 0)
- return;
+ if (_foldMarginView) {
+ [_foldMarginView setTextView:(ViTextView *)aView];
+ } else {
+ _foldMarginView = [[ViFoldMarginView alloc] initWithTextView:(ViTextView *)aView];
- [self setNeedsDisplay:YES];
+ [[NSNotificationCenter defaultCenter] addObserver:self
+ selector:@selector(subviewFrameDidChange:)
+ name:NSViewFrameDidChangeNotification
+ object:_foldMarginView];
- CGFloat thickness;
- thickness = [self requiredThickness];
- if (thickness != [self ruleThickness])
- [[self nextRunloop] setRuleThickness:thickness];
-}
+ [self addSubview:_foldMarginView];
+ [_foldMarginView updateViewFrame];
+ }
-- (void)caretDidChange:(NSNotification *)notification
-{
- if (_relative) {
- [self setNeedsDisplay:YES];
+ [self updateRuleThickness];
}
}
-- (CGFloat)requiredThickness
+- (void)subviewFrameDidChange:(NSNotification *)aNotification
{
- NSUInteger lineCount, digits;
-
- id view = [self clientView];
- if ([view isKindOfClass:[ViTextView class]]) {
- lineCount = [[(ViTextView *)view textStorage] lineCount];
- digits = (unsigned)log10(lineCount) + 1;
- return ceilf(MAX(DEFAULT_THICKNESS, _digitSize.width * digits + RULER_MARGIN * 2));
- }
-
- return 0;
+ [self updateRuleThickness];
}
-- (void)drawLineNumber:(NSInteger)line inRect:(NSRect)rect
+- (void)updateRuleThickness
{
- NSUInteger absoluteLine = ABS(line);
-
- do {
- NSUInteger rem = absoluteLine % 10;
- absoluteLine = absoluteLine / 10;
-
- rect.origin.x -= _digitSize.width;
-
- [_digits[rem] drawInRect:rect
- fromRect:NSZeroRect
- operation:NSCompositeSourceOver
- fraction:1.0
- respectFlipped:YES
- hints:nil];
- } while (absoluteLine > 0);
+ CGFloat newThickness = _lineNumberView.frame.size.width + _foldMarginView.frame.size.width;
+
+ if (newThickness != [self ruleThickness]) {
+ [[self nextRunloop] setRuleThickness:newThickness];
+ }
}
- (void)drawHashMarksAndLabelsInRect:(NSRect)aRect
{
NSRect bounds = [self bounds];
+ NSRect visibleRect = [[[self scrollView] contentView] bounds];
[_backgroundColor set];
NSRectFill(bounds);
@@ -214,101 +137,34 @@ - (void)drawHashMarksAndLabelsInRect:(NSRect)aRect
[NSBezierPath strokeLineFromPoint:NSMakePoint(NSMaxX(bounds) - 0.5, NSMinY(bounds))
toPoint:NSMakePoint(NSMaxX(bounds) - 0.5, NSMaxY(bounds))];
- id view = [self clientView];
- if (![view isKindOfClass:[ViTextView class]])
- return;
-
- ViTextView *textView = (ViTextView *)view;
- NSLayoutManager *layoutManager;
- NSTextContainer *container;
- ViTextStorage *textStorage;
- NSRect visibleRect;
- NSRange range, glyphRange;
- CGFloat ypos, yinset;
-
- layoutManager = [view layoutManager];
- container = [view textContainer];
- textStorage = [textView textStorage];
- yinset = [view textContainerInset].height;