Skip to content

Commit

Permalink
Refactor the way Console Window output is appended, to try and minimi…
Browse files Browse the repository at this point in the history
…se leaking/hanging when lots of output is happening. Fixes #3565 (#3659)
  • Loading branch information
cmsj authored Aug 5, 2024
1 parent e0c79fa commit 4494358
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 32 deletions.
39 changes: 19 additions & 20 deletions Hammerspoon/ConsoleWindow.xib
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="14113" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="32700.99.1234" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="14113"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="22690"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
Expand All @@ -15,48 +15,47 @@
</customObject>
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<window title="Hammerspoon Console" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" showsToolbarButton="NO" visibleAtLaunch="NO" frameAutosaveName="console" animationBehavior="default" id="P23-aL-ez6">
<window title="Hammerspoon Console" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" frameAutosaveName="console" animationBehavior="default" id="P23-aL-ez6">
<windowStyleMask key="styleMask" titled="YES" closable="YES" miniaturizable="YES" resizable="YES"/>
<windowCollectionBehavior key="collectionBehavior" fullScreenPrimary="YES"/>
<windowPositionMask key="initialPositionMask" leftStrut="YES" rightStrut="YES" topStrut="YES" bottomStrut="YES"/>
<rect key="contentRect" x="916" y="704" width="510" height="389"/>
<rect key="screenRect" x="0.0" y="0.0" width="3200" height="1778"/>
<rect key="screenRect" x="0.0" y="0.0" width="1710" height="1068"/>
<value key="minSize" type="size" width="340" height="200"/>
<view key="contentView" id="2jF-WS-ElT">
<rect key="frame" x="0.0" y="0.0" width="510" height="389"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<scrollView horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" translatesAutoresizingMaskIntoConstraints="NO" id="gls-wS-aCi">
<rect key="frame" x="20" y="51" width="470" height="318"/>
<clipView key="contentView" id="vxQ-4d-mld">
<rect key="frame" x="1" y="1" width="468" height="316"/>
<scrollView horizontalLineScroll="10" horizontalPageScroll="10" verticalLineScroll="10" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" horizontalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="gls-wS-aCi">
<rect key="frame" x="20" y="47" width="470" height="322"/>
<clipView key="contentView" drawsBackground="NO" id="vxQ-4d-mld">
<rect key="frame" x="1" y="1" width="468" height="320"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<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">
<rect key="frame" x="0.0" y="0.0" width="468" height="316"/>
<textView editable="NO" importsGraphics="NO" richText="NO" verticallyResizable="YES" findStyle="bar" allowsCharacterPickerTouchBarItem="NO" textCompletion="NO" id="Jlb-eC-MmZ">
<rect key="frame" x="0.0" y="0.0" width="468" height="320"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<size key="minSize" width="468" height="316"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
<size key="minSize" width="468" height="320"/>
<size key="maxSize" width="612" height="10000000"/>
<color key="insertionPointColor" white="0.0" alpha="1" colorSpace="calibratedWhite"/>
</textView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
</clipView>
<scroller key="horizontalScroller" hidden="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="YES" id="bBB-1O-Jf0">
<scroller key="horizontalScroller" hidden="YES" wantsLayer="YES" verticalHuggingPriority="750" doubleValue="1" horizontal="YES" id="bBB-1O-Jf0">
<rect key="frame" x="-100" y="-100" width="87" height="18"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
<scroller key="verticalScroller" verticalHuggingPriority="750" horizontal="NO" id="Kjj-rY-Bja">
<rect key="frame" x="453" y="1" width="16" height="316"/>
<scroller key="verticalScroller" wantsLayer="YES" verticalHuggingPriority="750" horizontal="NO" id="Kjj-rY-Bja">
<rect key="frame" x="453" y="1" width="16" height="320"/>
<autoresizingMask key="autoresizingMask"/>
</scroller>
</scrollView>
<textField horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="vJ5-eP-Q1d" customClass="HSGrowingTextField">
<rect key="frame" x="20" y="20" width="470" height="23"/>
<textField focusRingType="none" horizontalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="vJ5-eP-Q1d" customClass="HSGrowingTextField">
<rect key="frame" x="20" y="20" width="470" height="19"/>
<textFieldCell key="cell" selectable="YES" editable="YES" state="on" borderStyle="bezel" drawsBackground="YES" id="CiF-1K-0Ju">
<font key="font" size="12" name="Menlo-Regular"/>
<color key="textColor" name="textColor" catalog="System" colorSpace="catalog"/>
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
<connections>
Expand Down
44 changes: 32 additions & 12 deletions Hammerspoon/MJConsoleWindowController.m
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ @interface MJConsoleWindowController ()
@property (weak) IBOutlet NSTextField* inputField;
@property NSMutableArray* preshownStdouts;
@property NSDateFormatter *dateFormatter;
@property NSMutableArray *outputBuffer;
@property NSTimer *outputTimer;

@end

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

self.outputBuffer = [[NSMutableArray alloc] initWithCapacity:1000];

// Strings that we want to add to the console window are batched up in self.outputBuffer and this timer drains them
self.outputTimer = [NSTimer timerWithTimeInterval:0.2 repeats:YES block:^(NSTimer * _Nonnull timer) {
if (self.outputBuffer.count > 0) {
@autoreleasepool {
NSTextStorage *storage = self.outputView.textStorage;
[storage beginEditing];

for (NSAttributedString *attrstr in self.outputBuffer) {
int curLength = (int)storage.length;
int maxLength = self.maxConsoleOutputHistory.intValue;
int addLength = (int)attrstr.length;

[storage appendAttributedString:attrstr];
if (curLength > maxLength && maxLength > 0) {
[storage deleteCharactersInRange:NSMakeRange(0, curLength - maxLength + addLength)];
}
}

[self.outputBuffer removeAllObjects];
[storage endEditing];
[self.outputView scrollToEndOfDocument:self];
}
}
}];
[[NSRunLoop mainRunLoop] addTimer:self.outputTimer forMode:NSRunLoopCommonModes];

[self initializeConsoleColorsAndFont] ;
}
return self;
Expand Down Expand Up @@ -141,18 +171,8 @@ - (void) appendString:(NSString*)str type:(MJReplLineType)type {
NSDictionary* attrs = @{NSFontAttributeName: self.consoleFont, NSForegroundColorAttributeName: color};
NSAttributedString* attrstr = [[NSAttributedString alloc] initWithString:str attributes:attrs];

dispatch_async(dispatch_get_main_queue(), ^{
NSTextStorage *storage = self.outputView.textStorage;
int curLength = (int)storage.length;
int maxLength = self.maxConsoleOutputHistory.intValue;
int addLength = (int)attrstr.length;

[storage appendAttributedString:attrstr];
if (curLength > maxLength && maxLength > 0) {
[storage deleteCharactersInRange:NSMakeRange(0, curLength - maxLength + addLength)];
}
[self.outputView scrollToEndOfDocument:self];
});
// We don't actually append the string immediately, it goes into a buffer that drains on a timer (see above)
[self.outputBuffer addObject:attrstr];
}

- (NSString*) run:(NSString*)command {
Expand Down

0 comments on commit 4494358

Please sign in to comment.