Skip to content

Commit 4d47226

Browse files
committed
Refactor the way Console Window output is appended, to try and minimise leaking/hanging when lots of output is happening. Fixes #3565
1 parent e0c79fa commit 4d47226

File tree

2 files changed

+51
-32
lines changed

2 files changed

+51
-32
lines changed

Hammerspoon/ConsoleWindow.xib

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
<?xml version="1.0" encoding="UTF-8"?>
2-
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
2+
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
33
<dependencies>
44
<deployment identifier="macosx"/>
5-
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
5+
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22690"/>
66
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
77
</dependencies>
88
<objects>
@@ -15,48 +15,47 @@
1515
</customObject>
1616
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
1717
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
18-
<window title="Hammerspoon Console" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="console" animationBehavior="default" id="P23-aL-ez6">
18+
<window title="Hammerspoon Console" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="console" animationBehavior="default" id="P23-aL-ez6">
1919
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
2020
<windowCollectionBehavior key="collectionBehavior" fullScreenPrimary="YES"/>
2121
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
2222
<rect key="contentRect" x="916" y="704" width="510" height="389"/>
23-
<rect key="screenRect" x="0.0" y="0.0" width="3200" height="1778"/>
23+
<rect key="screenRect" x="0.0" y="0.0" width="1710" height="1068"/>
2424
<value key="minSize" type="size" width="340" height="200"/>
2525
<view key="contentView" id="2jF-WS-ElT">
2626
<rect key="frame" x="0.0" y="0.0" width="510" height="389"/>
2727
<autoresizingMask key="autoresizingMask"/>
2828
<subviews>
29-
<scrollView horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gls-wS-aCi">
30-
<rect key="frame" x="20" y="51" width="470" height="318"/>
31-
<clipView key="contentView" id="vxQ-4d-mld">
32-
<rect key="frame" x="1" y="1" width="468" height="316"/>
29+
<scrollView horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" horizontalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="gls-wS-aCi">
30+
<rect key="frame" x="20" y="47" width="470" height="322"/>
31+
<clipView key="contentView" drawsBackground="NO" id="vxQ-4d-mld">
32+
<rect key="frame" x="1" y="1" width="468" height="320"/>
3333
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
3434
<subviews>
35-
<textView importsGraphics="NO" verticallyResizable="YES" usesFontPanel="YES" findStyle="panel" continuousSpellChecking="YES" allowsUndo="YES" usesRuler="YES" allowsNonContiguousLayout="YES" quoteSubstitution="YES" dashSubstitution="YES" spellingCorrection="YES" smartInsertDelete="YES" id="Jlb-eC-MmZ">
36-
<rect key="frame" x="0.0" y="0.0" width="468" height="316"/>
35+
<textView editable="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" findStyle="bar" allowsCharacterPickerTouchBarItem="NO" textCompletion="NO" id="Jlb-eC-MmZ">
36+
<rect key="frame" x="0.0" y="0.0" width="468" height="320"/>
3737
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
38-
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
39-
<size key="minSize" width="468" height="316"/>
38+
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
39+
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
40+
<size key="minSize" width="468" height="320"/>
4041
<size key="maxSize" width="612" height="10000000"/>
41-
<color key="insertionPointColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
4242
</textView>
4343
</subviews>
44-
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
4544
</clipView>
46-
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="YES" id="bBB-1O-Jf0">
45+
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="YES" id="bBB-1O-Jf0">
4746
<rect key="frame" x="-100" y="-100" width="87" height="18"/>
4847
<autoresizingMask key="autoresizingMask"/>
4948
</scroller>
50-
<scroller key="verticalScroller" verticalHuggingPriority="750" horizontal="NO" id="Kjj-rY-Bja">
51-
<rect key="frame" x="453" y="1" width="16" height="316"/>
49+
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="Kjj-rY-Bja">
50+
<rect key="frame" x="453" y="1" width="16" height="320"/>
5251
<autoresizingMask key="autoresizingMask"/>
5352
</scroller>
5453
</scrollView>
55-
<textField horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="vJ5-eP-Q1d" customClass="HSGrowingTextField">
56-
<rect key="frame" x="20" y="20" width="470" height="23"/>
54+
<textField focusRingType="none" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="vJ5-eP-Q1d" customClass="HSGrowingTextField">
55+
<rect key="frame" x="20" y="20" width="470" height="19"/>
5756
<textFieldCell key="cell" selectable="YES" editable="YES" state="on" borderStyle="bezel" drawsBackground="YES" id="CiF-1K-0Ju">
5857
<font key="font" size="12" name="Menlo-Regular"/>
59-
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
58+
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
6059
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
6160
</textFieldCell>
6261
<connections>

Hammerspoon/MJConsoleWindowController.m

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ @interface MJConsoleWindowController ()
2222
@property (weak) IBOutlet NSTextField* inputField;
2323
@property NSMutableArray* preshownStdouts;
2424
@property NSDateFormatter *dateFormatter;
25+
@property NSMutableArray *outputBuffer;
26+
@property NSTimer *outputTimer;
2527

2628
@end
2729

@@ -41,6 +43,34 @@ - (id) init {
4143
[self.dateFormatter setLocale:enUSPOSIXLocale];
4244
[self.dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss"];
4345

46+
self.outputBuffer = [[NSMutableArray alloc] initWithCapacity:1000];
47+
48+
// Strings that we want to add to the console window are batched up in self.outputBuffer and this timer drains them
49+
self.outputTimer = [NSTimer timerWithTimeInterval:0.2 repeats:YES block:^(NSTimer * _Nonnull timer) {
50+
if (self.outputBuffer.count > 0) {
51+
@autoreleasepool {
52+
NSTextStorage *storage = self.outputView.textStorage;
53+
[storage beginEditing];
54+
55+
for (NSAttributedString *attrstr in self.outputBuffer) {
56+
int curLength = (int)storage.length;
57+
int maxLength = self.maxConsoleOutputHistory.intValue;
58+
int addLength = (int)attrstr.length;
59+
60+
[storage appendAttributedString:attrstr];
61+
if (curLength > maxLength && maxLength > 0) {
62+
[storage deleteCharactersInRange:NSMakeRange(0, curLength - maxLength + addLength)];
63+
}
64+
}
65+
66+
[self.outputBuffer removeAllObjects];
67+
[storage endEditing];
68+
[self.outputView scrollToEndOfDocument:self];
69+
}
70+
}
71+
}];
72+
[[NSRunLoop mainRunLoop] addTimer:self.outputTimer forMode:NSRunLoopCommonModes];
73+
4474
[self initializeConsoleColorsAndFont] ;
4575
}
4676
return self;
@@ -141,18 +171,8 @@ - (void) appendString:(NSString*)str type:(MJReplLineType)type {
141171
NSDictionary* attrs = @{NSFontAttributeName: self.consoleFont, NSForegroundColorAttributeName: color};
142172
NSAttributedString* attrstr = [[NSAttributedString alloc] initWithString:str attributes:attrs];
143173

144-
dispatch_async(dispatch_get_main_queue(), ^{
145-
NSTextStorage *storage = self.outputView.textStorage;
146-
int curLength = (int)storage.length;
147-
int maxLength = self.maxConsoleOutputHistory.intValue;
148-
int addLength = (int)attrstr.length;
149-
150-
[storage appendAttributedString:attrstr];
151-
if (curLength > maxLength && maxLength > 0) {
152-
[storage deleteCharactersInRange:NSMakeRange(0, curLength - maxLength + addLength)];
153-
}
154-
[self.outputView scrollToEndOfDocument:self];
155-
});
174+
// We don't actually append the string immediately, it goes into a buffer that drains on a timer (see above)
175+
[self.outputBuffer addObject:attrstr];
156176
}
157177

158178
- (NSString*) run:(NSString*)command {

0 commit comments

Comments
 (0)