Prevent a force unwrap nil crash by disabling the 'Generate' and other config buttons until Tulsi has finished initializing required components.
PiperOrigin-RevId: 232519606
diff --git a/src/Tulsi/Base.lproj/Main.storyboard b/src/Tulsi/Base.lproj/Main.storyboard
index 2539937..18230e8 100644
--- a/src/Tulsi/Base.lproj/Main.storyboard
+++ b/src/Tulsi/Base.lproj/Main.storyboard
@@ -434,17 +434,12 @@
</segmentedControl>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="TIj-CZ-t8e">
<rect key="frame" x="640" y="13" width="96" height="32"/>
- <buttonCell key="cell" type="push" title="Generate" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Bpc-vd-bqs">
+ <buttonCell key="cell" type="push" title="Generate" bezelStyle="rounded" alignment="center" enabled="NO" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="Bpc-vd-bqs">
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
<connections>
<action selector="doGenerate:" target="Pgt-4s-lhO" id="F2L-0G-7Eb"/>
- <binding destination="EPJ-qo-bmN" name="enabled" keyPath="selectionIndexes.count" id="3si-Fr-iw5">
- <dictionary key="options">
- <string key="NSValueTransformerName">IsOneValueTransformer</string>
- </dictionary>
- </binding>
</connections>
</button>
</subviews>
@@ -462,6 +457,7 @@
<connections>
<outlet property="addRemoveSegmentedControl" destination="Igh-yT-sbk" id="TWE-xZ-kHk"/>
<outlet property="configArrayController" destination="EPJ-qo-bmN" id="mIf-bC-JqH"/>
+ <outlet property="generateButton" destination="TIj-CZ-t8e" id="r34-ll-ZLf"/>
</connections>
</viewController>
<customObject id="Yqh-DQ-phg" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
diff --git a/src/Tulsi/ProjectEditorConfigManagerViewController.swift b/src/Tulsi/ProjectEditorConfigManagerViewController.swift
index fbafa10..45059ec 100644
--- a/src/Tulsi/ProjectEditorConfigManagerViewController.swift
+++ b/src/Tulsi/ProjectEditorConfigManagerViewController.swift
@@ -32,21 +32,30 @@
@IBOutlet var configArrayController: NSArrayController!
@IBOutlet weak var addRemoveSegmentedControl: NSSegmentedControl!
+ @IBOutlet var generateButton: NSButton!
@objc dynamic var numBazelPackages: Int = 0 {
didSet {
- let enableAddButton = numBazelPackages > 0
+ let enableAddButton = numBazelPackages > 0 && infoExtractorInitialized
addRemoveSegmentedControl.setEnabled(enableAddButton,
forSegment: SegmentedControlButtonIndex.add.rawValue)
}
}
+ // Whether or not the Tulsi project document is still initializing components required for
+ // generation.
+ @objc dynamic var infoExtractorInitialized: Bool = false {
+ didSet {
+ updateButtonsState()
+ Thread.doOnMainQueue() {
+ self.generateButton.title = self.infoExtractorInitialized ? "Generate" : "Initializing..."
+ }
+ }
+ }
+
@objc dynamic var numSelectedConfigs: Int = 0 {
didSet {
- addRemoveSegmentedControl.setEnabled(numSelectedConfigs > 0,
- forSegment: SegmentedControlButtonIndex.remove.rawValue)
- addRemoveSegmentedControl.setEnabled(numSelectedConfigs == 1,
- forSegment: SegmentedControlButtonIndex.action.rawValue)
+ updateButtonsState()
}
}
@@ -57,11 +66,16 @@
to: concreteRepresentedObject,
withKeyPath: "bazelPackages.@count",
options: nil)
+ bind(NSBindingName(rawValue: "infoExtractorInitialized"),
+ to: concreteRepresentedObject,
+ withKeyPath: "infoExtractorInitialized",
+ options: nil)
}
}
}
deinit {
+ NSObject.unbind(NSBindingName(rawValue: "infoExtractorInitialized"))
NSObject.unbind(NSBindingName(rawValue: "numBazelPackages"))
NSObject.unbind(NSBindingName(rawValue: "numSelectedConfigs"))
}
@@ -76,6 +90,22 @@
options: nil)
}
+ // Toggle the state of the buttons depending on the current selection as well as if any required
+ // components are still being initialized.
+ func updateButtonsState() {
+ Thread.doOnMainQueue() {
+ let numSelectedConfigs = self.numSelectedConfigs
+ let infoExtractorInitialized = self.infoExtractorInitialized
+ self.addRemoveSegmentedControl.setEnabled(self.numBazelPackages > 0 && infoExtractorInitialized,
+ forSegment: SegmentedControlButtonIndex.add.rawValue)
+ self.addRemoveSegmentedControl.setEnabled(numSelectedConfigs > 0 && infoExtractorInitialized,
+ forSegment: SegmentedControlButtonIndex.remove.rawValue)
+ self.addRemoveSegmentedControl.setEnabled(numSelectedConfigs == 1 && infoExtractorInitialized,
+ forSegment: SegmentedControlButtonIndex.action.rawValue)
+ self.generateButton.isEnabled = (numSelectedConfigs == 1 && infoExtractorInitialized)
+ }
+ }
+
@IBAction func didClickAddRemoveSegmentedControl(_ sender: NSSegmentedCell) {
// Ignore mouse up messages.
if sender.selectedSegment < 0 { return }
diff --git a/src/Tulsi/TulsiProjectDocument.swift b/src/Tulsi/TulsiProjectDocument.swift
index 94f6774..b13f75d 100644
--- a/src/Tulsi/TulsiProjectDocument.swift
+++ b/src/Tulsi/TulsiProjectDocument.swift
@@ -124,7 +124,14 @@
return fileURL?.appendingPathComponent(TulsiProjectDocument.ProjectConfigsSubpath)
}
- var infoExtractor: TulsiProjectInfoExtractor! = nil
+ /// Whether or not the document has finished initializing the info extractor.
+ @objc dynamic var infoExtractorInitialized: Bool = false
+
+ var infoExtractor: TulsiProjectInfoExtractor! = nil {
+ didSet {
+ infoExtractorInitialized = (infoExtractor != nil)
+ }
+ }
private var logEventObserver: NSObjectProtocol! = nil
/// Array of user-facing messages, generally output by the Tulsi generator.