Skip to content

Commit

Permalink
Implements a kind of live reloading (see Issue #22)
Browse files Browse the repository at this point in the history
  • Loading branch information
Ryan Goulden authored and Ryan Goulden committed Dec 26, 2013
1 parent 1a7b525 commit f4c7fe4
Show file tree
Hide file tree
Showing 6 changed files with 166 additions and 33 deletions.
12 changes: 6 additions & 6 deletions English.lproj/DiffDocument.xib
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="4510" systemVersion="12F45" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="4514" systemVersion="13B42" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none">
<dependencies>
<deployment version="1080" defaultVersion="1070" identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="4510"/>
<deployment version="1080" defaultVersion="1080" identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="4514"/>
</dependencies>
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="DiffDocument">
Expand Down Expand Up @@ -40,7 +40,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" flexibleMaxX="YES" heightSizable="YES"/>
</customView>
<customView id="7" customClass="HFTextView">
<rect key="frame" x="332" y="0.0" width="321" height="356"/>
<rect key="frame" x="331.999999966948" y="0.0" width="321" height="356"/>
<autoresizingMask key="autoresizingMask" flexibleMinX="YES" widthSizable="YES" heightSizable="YES"/>
</customView>
</subviews>
Expand Down Expand Up @@ -85,11 +85,11 @@
</subviews>
<color key="backgroundColor" name="controlBackgroundColor" catalog="System" colorSpace="catalog"/>
</clipView>
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" id="15">
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="YES" id="15">
<rect key="frame" x="0.0" y="151" width="654.99999999999989" height="16"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" id="14">
<scroller key="verticalScroller" hidden="YES" verticalHuggingPriority="750" horizontal="NO" id="14">
<rect key="frame" x="-100" y="-100" width="15" height="167"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
Expand Down
6 changes: 6 additions & 0 deletions English.lproj/MainMenu.xib
Expand Up @@ -245,6 +245,12 @@
</items>
</menu>
</menuItem>
<menuItem title="Reload Unmodified" id="bpc-ky-jvV">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="setLiveReloadFromMenuItem:" target="-1" id="HRn-F2-u4k"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="214">
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
</menuItem>
Expand Down
10 changes: 9 additions & 1 deletion app/sources/BaseDataDocument.h
Expand Up @@ -48,7 +48,11 @@ NSString * const BaseDataDocumentDidChangeStringEncodingNotification;
BOOL saveInProgress;

BOOL currentlySettingFont;
BOOL isTransient;
BOOL isTransient;

BOOL shouldLiveReload;
NSDate *liveReloadDate;
NSTimer *liveReloadTimer;
}

- (void)moveSelectionForwards:(NSMenuItem *)sender;
Expand Down Expand Up @@ -108,4 +112,8 @@ NSString * const BaseDataDocumentDidChangeStringEncodingNotification;

- (BOOL)requiresOverwriteMode;

- (BOOL)shouldLiveReload;
- (void)setShouldLiveReload:(BOOL)flag;
- (IBAction)setLiveReloadFromMenuItem:sender;

@end
107 changes: 104 additions & 3 deletions app/sources/BaseDataDocument.m
Expand Up @@ -515,8 +515,9 @@ - (id)init {

controller = [[HFController alloc] init];
[controller setShouldAntialias:[defs boolForKey:@"AntialiasText"]];
[controller setShouldShowCallouts:[defs boolForKey:@"ShowCallouts"]];
[controller setShouldColorBytes:[defs boolForKey:@"ColorBytes"]];
[controller setShouldShowCallouts:[defs boolForKey:@"ShowCallouts"]];
[controller setShouldLiveReload:[defs boolForKey:@"LiveReload"]];
[controller setUndoManager:[self undoManager]];
[controller setBytesPerColumn:[defs integerForKey:@"BytesPerColumn"]];
[controller addRepresenter:layoutRepresenter];
Expand Down Expand Up @@ -565,6 +566,10 @@ - (void)dealloc {
}
[containerView release];
[bannerDividerThumb release];

[liveReloadDate release];
[liveReloadTimer release];

[super dealloc];
}

Expand Down Expand Up @@ -823,12 +828,16 @@ - (BOOL)validateMenuItem:(NSMenuItem *)item {
[item setState:[controller shouldAntialias]];
return YES;
}
else if (action == @selector(setColorBytesFromMenuItem:)) {
[item setState:[controller shouldColorBytes]];
return YES;
}
else if (action == @selector(setShowCalloutsFromMenuItem:)) {
[item setState:[controller shouldShowCallouts]];
return YES;
}
else if (action == @selector(setColorBytesFromMenuItem:)) {
[item setState:[controller shouldColorBytes]];
else if (action == @selector(setLiveReloadFromMenuItem:)) {
[item setState:[controller shouldLiveReload]];
return YES;
}
else if (action == @selector(setOverwriteMode:)) {
Expand Down Expand Up @@ -1824,4 +1833,96 @@ - (BOOL)requiresOverwriteMode
return NO;
}

- (BOOL)shouldLiveReload {
return shouldLiveReload;
}

- (void)setShouldLiveReload:(BOOL)flag {
shouldLiveReload = flag;
if(flag) [self pollLiveReload];
}

- (IBAction)setLiveReloadFromMenuItem:(id)sender {
USE(sender);
BOOL newVal = ![controller shouldLiveReload];
[controller setShouldLiveReload:newVal];
[[NSUserDefaults standardUserDefaults] setBool:newVal forKey:@"LiveReload"];
[self setShouldLiveReload:newVal];
}

@end

// Let the compiler know about the 10.9 -[NSTimer setTolerance:] selector
// even though we're targeting several versions behind that.
@protocol MyNSTimerSetToleranceProtocol
- (void)setTolerance:(NSTimeInterval)tolerance;
@end

@implementation BaseDataDocument(LiveReloading)

// TODO: Some of the other NSFilePresenter methods could be used to make a
// more versitle live-updating option.

// Also, there may be a race conditon here: if a file being operated on
// is swapped with another file during the live reload and just before the
// live reload actually starts reverting, it may be that we revert to the
// swapped in file rather than the actual file. I'm dubious that the use
// of NSFileCoordinator here saves us from this, but it might. Even with
// the race, there's no strong data loss concern; it just might happen that,
// in extreme conditions, an unmodified document becomes a different document.
// TODO: Investigate this.

#define LiveReloadTimeTolerance 1.0 // Allow Cab some slack.
#define LiveReloadTimeThrottle 1.0 // Auto reload at most every second.

- (void)presentedItemDidChange {
// Stay in sync with changes if there are no outstanding edits and an
// update check is not already scheduled.
[self pollLiveReload];
}

- (void)pollLiveReload {
if(!shouldLiveReload) return;
if([self isDocumentEdited]) return; // Don't clobber changes.
if(liveReloadTimer && [liveReloadTimer isValid]) return; // A live reload is already scheduled.

NSDate *nextDate;
if(liveReloadDate && [liveReloadDate timeIntervalSinceNow] > -LiveReloadTimeThrottle) {
// Happened recently, throttle a bit.
nextDate = [liveReloadDate dateByAddingTimeInterval:LiveReloadTimeThrottle];
} else {
// Did not update recently, update soon.
nextDate = [NSDate date];
}

[liveReloadTimer release];
liveReloadTimer = [[NSTimer alloc] initWithFireDate:nextDate interval:0 target:self selector:@selector(tryLiveReload) userInfo:nil repeats:NO];

if([liveReloadTimer respondsToSelector:@selector(setTolerance:)]) {
[(id<MyNSTimerSetToleranceProtocol>)liveReloadTimer setTolerance:LiveReloadTimeTolerance];
}
[[NSRunLoop mainRunLoop] addTimer:liveReloadTimer forMode:NSDefaultRunLoopMode];
}

- (BOOL)tryLiveReload {
if(!shouldLiveReload) return NO;
if([self isDocumentEdited]) return NO; // Don't clobber changes.

NSError *error = nil;
NSError **errorp = &error;

[[[NSFileCoordinator alloc] initWithFilePresenter:self] coordinateReadingItemAtURL:[self fileURL] options:0 error:errorp byAccessor:^ (NSURL *url) {
NSDictionary *attrs = [[NSFileManager defaultManager] attributesOfItemAtPath:[[url filePathURL] path] error:errorp];
if(!attrs || *errorp) return;
if([[attrs objectForKey:NSFileModificationDate] isGreaterThan:[self fileModificationDate]]) {
[self revertToContentsOfURL:url ofType:[self fileType] error:errorp];
}
}];

[liveReloadDate release];
liveReloadDate = [[NSDate date] retain];

return error ? NO : YES;
}

@end
32 changes: 21 additions & 11 deletions framework/sources/HFController.h
Expand Up @@ -134,14 +134,15 @@ You create an HFController via <tt>[[HFController alloc] init]</tt>. After that

struct {
unsigned antialias:1;
unsigned showcallouts:1;
unsigned colorbytes:1;
unsigned showcallouts:1;
HFEditMode editMode:2;
unsigned editable:1;
unsigned selectable:1;
unsigned selectionInProgress:1;
unsigned shiftExtendSelection:1;
unsigned commandExtendSelection:1;
unsigned livereload:1;
} _hfflags;
}

Expand Down Expand Up @@ -328,25 +329,34 @@ You create an HFController via <tt>[[HFController alloc] init]</tt>. After that
- (void)setShouldAntialias:(BOOL)antialias;
//@}

/*! @name Byte coloring
Set and get whether the bytes should be colorized. When enabled, characters have a background color that correlates to their byte values. */
//@{
/*! Returns whether characters should be colored. */
- (BOOL)shouldColorBytes;

/*! Sets whether characters should be colored. */
- (void)setShouldColorBytes:(BOOL)colorbytes;
//@}

/*! @name Callouts
Set and get whether bookmark callouts should be shown/ */
Set and get whether bookmark callouts should be shown. */
//@{
/*! Returns whether text should be antialiased. */
/*! Returns whether bookmark callouts should be shown. */
- (BOOL)shouldShowCallouts;

/*! Sets whether text should be antialiased. */
/*! Sets whether bookmark callouts should be shown. */
- (void)setShouldShowCallouts:(BOOL)showcallouts;
//@}


/*! @name Byte coloring
Set and get whether the bytes should be colorized. When enabled, characters have a background color that correlates to their byte values. */
/*! @name Live reload
Set and get whether unmodified documents should be auto refreshed to their latest on disk state. */
//@{
/*! Returns whether characters should be colored. */
- (BOOL)shouldColorBytes;
/*! Returns whether live reload should occur. */
- (BOOL)shouldLiveReload;

/*! Sets whether characters should be colored. */
- (void)setShouldColorBytes:(BOOL)colorbytes;
/*! Sets whether live reload should occur. */
- (void)setShouldLiveReload:(BOOL)livereload;
//@}

/*! Representer initiated property changes
Expand Down
32 changes: 20 additions & 12 deletions framework/sources/HFController.m
Expand Up @@ -129,8 +129,9 @@ - (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:font forKey:@"HFFont"];
[coder encodeDouble:lineHeight forKey:@"HFLineHeight"];
[coder encodeBool:_hfflags.antialias forKey:@"HFAntialias"];
[coder encodeBool:_hfflags.showcallouts forKey:@"HFShowCallouts"];
[coder encodeBool:_hfflags.colorbytes forKey:@"HFColorBytes"];
[coder encodeBool:_hfflags.showcallouts forKey:@"HFShowCallouts"];
[coder encodeBool:_hfflags.livereload forKey:@"HFLiveReload"];
[coder encodeInt:_hfflags.editMode forKey:@"HFEditMode"];
[coder encodeBool:_hfflags.editable forKey:@"HFEditable"];
[coder encodeBool:_hfflags.selectable forKey:@"HFSelectable"];
Expand All @@ -145,8 +146,8 @@ - (id)initWithCoder:(NSCoder *)coder {
font = [[coder decodeObjectForKey:@"HFFont"] retain];
lineHeight = (CGFloat)[coder decodeDoubleForKey:@"HFLineHeight"];
_hfflags.antialias = [coder decodeBoolForKey:@"HFAntialias"];
_hfflags.showcallouts = [coder decodeBoolForKey:@"HFShowCallouts"];
_hfflags.colorbytes = [coder decodeBoolForKey:@"HFColorBytes"];
_hfflags.livereload = [coder decodeBoolForKey:@"HFLiveReload"];

if ([coder containsValueForKey:@"HFEditMode"])
_hfflags.editMode = [coder decodeIntForKey:@"HFEditMode"];
Expand Down Expand Up @@ -342,6 +343,18 @@ - (void)setShouldAntialias:(BOOL)antialias {
}
}

- (BOOL)shouldColorBytes {
return _hfflags.colorbytes;
}

- (void)setShouldColorBytes:(BOOL)colorbytes {
colorbytes = !! colorbytes;
if (colorbytes != _hfflags.colorbytes) {
_hfflags.colorbytes = colorbytes;
[self _addPropertyChangeBits:HFControllerColorBytes];
}
}

- (BOOL)shouldShowCallouts {
return _hfflags.showcallouts;
}
Expand All @@ -354,20 +367,15 @@ - (void)setShouldShowCallouts:(BOOL)showcallouts {
}
}


- (BOOL)shouldColorBytes {
return _hfflags.colorbytes;
- (BOOL)shouldLiveReload {
return _hfflags.livereload;
}

- (void)setShouldColorBytes:(BOOL)colorbytes {
colorbytes = !! colorbytes;
if (colorbytes != _hfflags.colorbytes) {
_hfflags.colorbytes = colorbytes;
[self _addPropertyChangeBits:HFControllerColorBytes];
}
- (void)setShouldLiveReload:(BOOL)livereload {
_hfflags.livereload = !!livereload;

}


- (void)setBytesPerColumn:(NSUInteger)val {
if (val != bytesPerColumn) {
bytesPerColumn = val;
Expand Down

0 comments on commit f4c7fe4

Please sign in to comment.