Add Swift version dropdown to Xcode extension app

This commit is contained in:
Nick Lockwood
2020-07-30 01:38:55 +01:00
parent ac3eccfc99
commit d0240cdc4a
9 changed files with 122 additions and 43 deletions
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="16096" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
<document type="com.apple.InterfaceBuilder3.Cocoa.Storyboard.XIB" version="3.0" toolsVersion="16097" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" initialViewController="B8D-0N-5wS">
<dependencies>
<deployment identifier="macosx"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="16096"/>
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="16097"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
@@ -359,7 +359,7 @@
<customObject id="Voe-Tx-rLC" customClass="AppDelegate" customModule="SwiftFormat_for_Xcode" customModuleProvider="target"/>
<customObject id="Ady-hI-5gd" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="83" y="-108"/>
<point key="canvasLocation" x="77" y="-252"/>
</scene>
<!--Window Controller-->
<scene sceneID="R2V-B0-nI4">
@@ -380,7 +380,7 @@
</windowController>
<customObject id="Oky-zY-oP4" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="75" y="250"/>
<point key="canvasLocation" x="84" y="150"/>
</scene>
<!--Tab View Controller-->
<scene sceneID="y9y-mK-zv0">
@@ -407,7 +407,7 @@
</tabViewController>
<customObject id="gwc-tk-N4m" userLabel="First Responder" customClass="NSResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="75" y="670"/>
<point key="canvasLocation" x="83" y="757"/>
</scene>
<!--Rules-->
<scene sceneID="v8f-un-92m">
@@ -420,13 +420,13 @@
<box verticalHuggingPriority="750" boxType="separator" translatesAutoresizingMaskIntoConstraints="NO" id="RZx-Ps-pFP">
<rect key="frame" x="0.0" y="571" width="476" height="5"/>
</box>
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="33" horizontalPageScroll="10" verticalLineScroll="33" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" horizontalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="vVz-rA-tRS">
<scrollView borderType="none" autohidesScrollers="YES" horizontalLineScroll="32" horizontalPageScroll="10" verticalLineScroll="32" verticalPageScroll="10" hasHorizontalScroller="NO" usesPredominantAxisScrolling="NO" horizontalScrollElasticity="none" translatesAutoresizingMaskIntoConstraints="NO" id="vVz-rA-tRS">
<rect key="frame" x="0.0" y="0.0" width="476" height="573"/>
<clipView key="contentView" drawsBackground="NO" copiesOnScroll="NO" id="1nY-Z5-cvR">
<rect key="frame" x="0.0" y="0.0" width="476" height="573"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" selectionHighlightStyle="none" columnReordering="NO" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="30" rowSizeStyle="automatic" usesAutomaticRowHeights="YES" viewBased="YES" id="JTc-wy-tJT">
<tableView verticalHuggingPriority="750" allowsExpansionToolTips="YES" selectionHighlightStyle="none" columnReordering="NO" columnResizing="NO" multipleSelection="NO" autosaveColumns="NO" typeSelect="NO" rowHeight="29" rowSizeStyle="automatic" usesAutomaticRowHeights="YES" viewBased="YES" id="JTc-wy-tJT">
<rect key="frame" x="0.0" y="0.0" width="476" height="573"/>
<autoresizingMask key="autoresizingMask"/>
<size key="intercellSpacing" width="3" height="3"/>
@@ -450,7 +450,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<button verticalHuggingPriority="750" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="Mfi-uR-j8t">
<rect key="frame" x="2" y="4" width="469" height="18"/>
<rect key="frame" x="6" y="4" width="461" height="18"/>
<buttonCell key="cell" type="check" title="Check" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="aQA-fV-2dz">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
@@ -462,16 +462,16 @@
</subviews>
<constraints>
<constraint firstAttribute="bottom" secondItem="Mfi-uR-j8t" secondAttribute="bottom" constant="6" id="GBh-TB-eJ4"/>
<constraint firstAttribute="trailing" secondItem="Mfi-uR-j8t" secondAttribute="trailing" constant="4" id="QqV-je-Yw8"/>
<constraint firstAttribute="trailing" secondItem="Mfi-uR-j8t" secondAttribute="trailing" constant="8" id="QqV-je-Yw8"/>
<constraint firstItem="Mfi-uR-j8t" firstAttribute="top" secondItem="CZN-za-xvO" secondAttribute="top" constant="6" id="dnp-3H-HEA"/>
<constraint firstItem="Mfi-uR-j8t" firstAttribute="leading" secondItem="CZN-za-xvO" secondAttribute="leading" constant="4" id="mF7-Yd-Nmg"/>
<constraint firstItem="Mfi-uR-j8t" firstAttribute="leading" secondItem="CZN-za-xvO" secondAttribute="leading" constant="8" id="mF7-Yd-Nmg"/>
</constraints>
<connections>
<outlet property="checkbox" destination="Mfi-uR-j8t" id="XlW-qO-5oC"/>
</connections>
</tableCellView>
<tableCellView identifier="FreeTextTableCellView" misplaced="YES" id="ISi-9v-fr9" customClass="FreeTextTableCellView" customModule="SwiftFormat_for_Xcode" customModuleProvider="target">
<rect key="frame" x="1" y="30" width="473" height="30"/>
<tableCellView identifier="FreeTextTableCellView" id="ISi-9v-fr9" customClass="FreeTextTableCellView" customModule="SwiftFormat_for_Xcode" customModuleProvider="target">
<rect key="frame" x="1" y="30" width="473" height="29"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="hfs-ko-uob">
@@ -508,7 +508,7 @@
</connections>
</tableCellView>
<tableCellView identifier="ListSelectionTableCellView" id="gu5-aI-ODU" customClass="ListSelectionTableCellView" customModule="SwiftFormat_for_Xcode" customModuleProvider="target">
<rect key="frame" x="1" y="63" width="473" height="29"/>
<rect key="frame" x="1" y="62" width="473" height="29"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<popUpButton horizontalHuggingPriority="500" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="uIw-Th-pkZ">
@@ -571,11 +571,11 @@
</scroller>
</scrollView>
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pHb-Fu-MRN">
<rect key="frame" x="145" y="571" width="186" height="44"/>
<rect key="frame" x="6" y="571" width="183" height="44"/>
<constraints>
<constraint firstAttribute="height" constant="40" id="qEB-fw-FrU"/>
</constraints>
<buttonCell key="cell" type="check" title="Infer Options Automatically" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="TeM-iN-9EW">
<buttonCell key="cell" type="check" title="Infer options automatically" bezelStyle="regularSquare" imagePosition="left" state="on" inset="2" id="TeM-iN-9EW">
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
<font key="font" metaFont="system"/>
</buttonCell>
@@ -583,13 +583,42 @@
<action selector="toggleInferOptions:" target="9ZR-Qg-hHT" id="Mri-hh-vbP"/>
</connections>
</button>
<popUpButton horizontalHuggingPriority="500" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="YId-jI-Kpd">
<rect key="frame" x="394" y="580" width="77" height="25"/>
<popUpButtonCell key="cell" type="push" title="Item 1" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" imageScaling="proportionallyDown" inset="2" selectedItem="nhQ-Kk-Ta3" id="JGl-KG-s99">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="menu"/>
<menu key="menu" id="gu2-yX-6cs">
<items>
<menuItem title="Item 1" state="on" id="nhQ-Kk-Ta3"/>
<menuItem title="Item 2" id="uMS-Tq-0a0"/>
<menuItem title="Item 3" id="d5E-5R-oiU"/>
</items>
</menu>
</popUpButtonCell>
<connections>
<action selector="selectVersion:" target="9ZR-Qg-hHT" id="5U3-L0-0g9"/>
</connections>
</popUpButton>
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="8vB-lt-Jpe">
<rect key="frame" x="312" y="585" width="82" height="16"/>
<textFieldCell key="cell" lineBreakMode="clipping" title="Swift version" id="pwb-qe-8jm">
<font key="font" metaFont="system"/>
<color key="textColor" name="labelColor" catalog="System" colorSpace="catalog"/>
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
</textFieldCell>
</textField>
</subviews>
<constraints>
<constraint firstItem="pHb-Fu-MRN" firstAttribute="centerX" secondItem="iDF-FY-6tf" secondAttribute="centerX" id="KMc-9S-niI"/>
<constraint firstItem="8vB-lt-Jpe" firstAttribute="centerY" secondItem="pHb-Fu-MRN" secondAttribute="centerY" id="93d-TL-9ag"/>
<constraint firstItem="YId-jI-Kpd" firstAttribute="centerY" secondItem="pHb-Fu-MRN" secondAttribute="centerY" id="Irg-0h-r8W"/>
<constraint firstItem="pHb-Fu-MRN" firstAttribute="leading" secondItem="iDF-FY-6tf" secondAttribute="leading" constant="8" id="PKw-Fg-bKp"/>
<constraint firstItem="vVz-rA-tRS" firstAttribute="leading" secondItem="iDF-FY-6tf" secondAttribute="leading" id="b3G-xy-WS8"/>
<constraint firstItem="RZx-Ps-pFP" firstAttribute="leading" secondItem="vVz-rA-tRS" secondAttribute="leading" id="bxG-eX-JCO"/>
<constraint firstItem="vVz-rA-tRS" firstAttribute="top" secondItem="pHb-Fu-MRN" secondAttribute="bottom" id="dY0-w7-cRt"/>
<constraint firstItem="RZx-Ps-pFP" firstAttribute="trailing" secondItem="vVz-rA-tRS" secondAttribute="trailing" id="eTh-8E-Cyf"/>
<constraint firstAttribute="trailing" secondItem="YId-jI-Kpd" secondAttribute="trailing" constant="8" id="gKh-h9-Bwp"/>
<constraint firstItem="YId-jI-Kpd" firstAttribute="leading" secondItem="8vB-lt-Jpe" secondAttribute="trailing" constant="4" id="h3U-99-3uV"/>
<constraint firstAttribute="bottom" secondItem="vVz-rA-tRS" secondAttribute="bottom" id="k1r-sg-eQJ"/>
<constraint firstItem="pHb-Fu-MRN" firstAttribute="top" secondItem="iDF-FY-6tf" secondAttribute="top" id="npV-YY-FNN"/>
<constraint firstAttribute="trailing" secondItem="vVz-rA-tRS" secondAttribute="trailing" id="pOR-wA-uLW"/>
@@ -598,6 +627,7 @@
</view>
<connections>
<outlet property="inferOptionsButton" destination="pHb-Fu-MRN" id="BFT-Bj-VHE"/>
<outlet property="swiftVersionDropDown" destination="YId-jI-Kpd" id="kGI-2P-LyD"/>
<outlet property="tableView" destination="JTc-wy-tJT" id="pR2-o9-CeY"/>
</connections>
</viewController>
@@ -50,6 +50,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
do {
let args = try parseConfigFile(data)
options = try Options(args, in: url.deletingLastPathComponent().path)
OptionsStore().inferOptions = Set(args.keys)
.intersection(formattingArguments)
.subtracting([FormatOptions.Descriptor.swiftVersion.argumentName])
.isEmpty
} catch {
showError(error)
return false
@@ -60,10 +64,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
Rule(name: $0, isEnabled: rules.contains($0))
})
if let formatOptions = options.formatOptions {
OptionsStore().inferOptions = false
OptionsStore().restore(formatOptions)
} else {
OptionsStore().inferOptions = true
}
return true
}
@@ -123,7 +124,10 @@ class AppDelegate: NSObject, NSApplicationDelegate {
let optionsStore = OptionsStore()
let formatOptions = optionsStore.inferOptions ? nil : optionsStore.formatOptions
let rules = RulesStore().rules.compactMap { $0.isEnabled ? $0.name : nil }
let config = serialize(options: Options(formatOptions: formatOptions, rules: Set(rules))) + "\n"
let config = serialize(
options: Options(formatOptions: formatOptions, rules: Set(rules)),
swiftVersion: optionsStore.formatOptions.swiftVersion
) + "\n"
do {
try config.write(to: url, atomically: true, encoding: .utf8)
} catch {
@@ -54,10 +54,14 @@ final class RulesViewController: NSViewController {
@IBOutlet var tableView: NSTableView!
@IBOutlet var inferOptionsButton: NSButton!
@IBOutlet var swiftVersionDropDown: NSPopUpButton!
override func viewDidLoad() {
super.viewDidLoad()
inferOptionsButton.state = optionStore.inferOptions ? .on : .off
swiftVersionDropDown.removeAllItems()
swiftVersionDropDown.addItems(withTitles: ["auto"] + swiftVersions)
updateSelectedVersion()
viewModels = buildRules()
NotificationCenter.default.addObserver(self, selector: #selector(didLoadNewConfiguration),
name: .applicationDidLoadNewConfiguration, object: nil)
@@ -67,6 +71,7 @@ final class RulesViewController: NSViewController {
viewModels = buildRules()
tableView?.reloadData()
inferOptionsButton?.state = (optionStore.inferOptions ? .on : .off)
updateSelectedVersion()
}
@IBAction private func toggleInferOptions(_ sender: NSButton) {
@@ -75,6 +80,24 @@ final class RulesViewController: NSViewController {
tableView?.reloadData()
}
@IBAction func selectVersion(_ sender: NSPopUpButton) {
var formatOptions = optionStore.formatOptions
let version = Version(rawValue: sender.selectedItem?.title ?? "0") ?? .undefined
formatOptions.swiftVersion = version
optionStore.save(formatOptions)
}
private func updateSelectedVersion() {
let currentVersion = optionStore.formatOptions.swiftVersion
var selectedIndex = 0
for (i, versionString) in (["0"] + swiftVersions).enumerated() {
if currentVersion >= Version(rawValue: versionString) ?? .undefined {
selectedIndex = i
}
}
swiftVersionDropDown.selectItem(at: selectedIndex)
}
private func buildRules() -> [UserSelectionType] {
let optionsByName = Dictionary(uniqueKeysWithValues: optionStore
.options
@@ -38,7 +38,7 @@ class FormatFileCommand: NSObject, XCSourceEditorCommand {
return completionHandler(FormatCommandError.notSwiftLanguage)
}
// Grab the selected source to format
// Grab the file source to format
let sourceToFormat = invocation.buffer.completeBuffer
let input = tokenize(sourceToFormat)
@@ -50,6 +50,7 @@ class FormatFileCommand: NSObject, XCSourceEditorCommand {
var formatOptions = store.inferOptions ? inferFormatOptions(from: input) : store.formatOptions
formatOptions.indent = invocation.buffer.indentationString
formatOptions.tabWidth = invocation.buffer.tabWidth
formatOptions.swiftVersion = store.formatOptions.swiftVersion
let output: [Token]
do {
@@ -63,7 +64,7 @@ class FormatFileCommand: NSObject, XCSourceEditorCommand {
}
// Remove all selections to avoid a crash when changing the contents of the buffer.
let selections = invocation.buffer.selections.copy() as! [XCSourceTextRange]
let selections = invocation.buffer.selections.copy() as? [XCSourceTextRange] ?? []
invocation.buffer.selections.removeAllObjects()
// Update buffer
@@ -42,7 +42,7 @@ class FormatSelectionCommand: NSObject, XCSourceEditorCommand {
return completionHandler(FormatCommandError.noSelection)
}
// Grab the selected source to format
// Grab the file source to format
let sourceToFormat = invocation.buffer.completeBuffer
let input = tokenize(sourceToFormat)
@@ -54,14 +54,14 @@ class FormatSelectionCommand: NSObject, XCSourceEditorCommand {
var formatOptions = store.inferOptions ? inferFormatOptions(from: input) : store.formatOptions
formatOptions.indent = invocation.buffer.indentationString
formatOptions.tabWidth = invocation.buffer.tabWidth
formatOptions.swiftVersion = store.formatOptions.swiftVersion
// Apply formatting for each range
var output = input
let tabWidth = invocation.buffer.tabWidth
for selection in selections {
let startOffset = SourceOffset(selection.start), endOffset = SourceOffset(selection.end)
let start = tokenIndexForOffset(startOffset, in: output, tabWidth: tabWidth)
let end = tokenIndexForOffset(endOffset, in: output, tabWidth: tabWidth)
let start = tokenIndexForOffset(startOffset, in: output, tabWidth: formatOptions.tabWidth)
let end = tokenIndexForOffset(endOffset, in: output, tabWidth: formatOptions.tabWidth)
do {
output = try format(output, rules: rules, options: formatOptions, range: start ..< end)
} catch {
@@ -29,6 +29,7 @@ class LintFileCommand: NSObject, XCSourceEditorCommand {
var formatOptions = store.inferOptions ? inferFormatOptions(from: input) : store.formatOptions
formatOptions.indent = invocation.buffer.indentationString
formatOptions.tabWidth = invocation.buffer.tabWidth
formatOptions.swiftVersion = store.formatOptions.swiftVersion
// Apply linting
do {
+13 -9
View File
@@ -56,11 +56,11 @@ extension SavedOption {
extension FormatOptions {
fileprivate init(_ rep: OptionsStore.OptionStoreRepresentation) throws {
var formatOptions = FormatOptions.default
for d in Descriptor.formatting.reversed() {
for descriptor in Descriptor.all.reversed() {
// By loading formatting options in reverse, we ensure that
// non-deprecated/renamed values will overwrite legacy values
if let value = rep[d.argumentName] {
try d.toOptions(value, &formatOptions)
if let value = rep[descriptor.argumentName] {
try descriptor.toOptions(value, &formatOptions)
}
}
self = formatOptions
@@ -95,7 +95,9 @@ struct OptionsStore {
var inferOptions: Bool {
get { return (store.object(forKey: inferOptionsKey) as? NSNumber)?.boolValue ?? true }
nonmutating set { store.set(NSNumber(booleanLiteral: newValue), forKey: inferOptionsKey) }
nonmutating set {
store.set(NSNumber(booleanLiteral: newValue), forKey: inferOptionsKey)
}
}
var options: [SavedOption] {
@@ -107,8 +109,7 @@ struct OptionsStore {
}
func save(_ options: [SavedOption]) {
let optRepresentations = options.map { (id: $0.descriptor.argumentName, arg: $0.argumentValue) }
save(optRepresentations)
save(options.map { (id: $0.descriptor.argumentName, arg: $0.argumentValue) })
}
func save(_ options: FormatOptions) {
@@ -132,7 +133,9 @@ struct OptionsStore {
func resetOptionsToDefaults() {
inferOptions = true
let options = FormatOptions.Descriptor.formatting.map { (id: $0.argumentName, arg: $0.defaultArgument) }
let options = FormatOptions.Descriptor.all.map {
(id: $0.argumentName, arg: $0.defaultArgument)
}
clear()
save(options)
}
@@ -150,7 +153,7 @@ extension OptionsStore {
}
private func addNewOptionsIfNeeded() {
let allDescriptors = FormatOptions.Descriptor.formatting
let allDescriptors = FormatOptions.Descriptor.all
var options = load()
var idsToRemove = Set(options.keys)
@@ -178,7 +181,8 @@ extension OptionsStore {
}
private func load() -> OptionStoreRepresentation {
guard let options = store.value(forKey: optionsKey) as? OptionStoreRepresentation else {
guard let options = store
.value(forKey: optionsKey) as? OptionStoreRepresentation else {
return OptionStoreRepresentation()
}
return options
+21 -8
View File
@@ -285,22 +285,35 @@ func parseConfigFile(_ data: Data) throws -> [String: String] {
// Serialize a set of options into either an arguments string or a file
func serialize(options: Options,
swiftVersion: Version = .undefined,
excludingDefaults: Bool = false,
separator: String = "\n") -> String {
var optionSets = [Options]()
var arguments = [[String: String]]()
if let fileOptions = options.fileOptions {
optionSets.append(Options(fileOptions: fileOptions))
arguments.append(argumentsFor(
Options(fileOptions: fileOptions),
excludingDefaults: excludingDefaults
))
}
if let formatOptions = options.formatOptions {
optionSets.append(Options(formatOptions: formatOptions))
arguments.append(argumentsFor(
Options(formatOptions: formatOptions),
excludingDefaults: excludingDefaults
))
} else if swiftVersion != .undefined {
let descriptor = FormatOptions.Descriptor.swiftVersion
arguments.append([descriptor.argumentName: swiftVersion.rawValue])
}
if let rules = options.rules {
optionSets.append(Options(rules: rules))
arguments.append(argumentsFor(
Options(rules: rules),
excludingDefaults: excludingDefaults
))
}
return optionSets.map {
let arguments = argumentsFor($0, excludingDefaults: excludingDefaults)
return serialize(arguments: arguments, separator: separator)
}.filter { !$0.isEmpty }.joined(separator: separator)
return arguments
.map { serialize(arguments: $0, separator: separator) }
.filter { !$0.isEmpty }
.joined(separator: separator)
}
// Serialize arguments
+3
View File
@@ -41,6 +41,9 @@ public let swiftFormatConfigurationFile = ".swiftformat"
/// The standard Swift version file name
public let swiftVersionFile = ".swift-version"
/// Supported Swift versions
public let swiftVersions = ["3.x", "4.0", "4.1", "4.2", "5.0", "5.1", "5.2", "5.3"]
/// An enumeration of the types of error that may be thrown by SwiftFormat
public enum FormatError: Error, CustomStringConvertible, LocalizedError, CustomNSError {
case reading(String)