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.