From f2d01748bebe281d9d502af556edd10dc7cb0615 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sat, 16 May 2020 23:06:52 +0200 Subject: [PATCH 1/3] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Change=20default=20she?= =?UTF-8?q?ll=20for=20commands,=20cleanup=20files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The shell that is now invoked is /bin/sh as opposed to /bin/bash, which has proven to be faster in various cases. --- phpmon/Extensions/DateExtension.swift | 3 +-- phpmon/Extensions/StringExtension.swift | 7 ++++++- phpmon/Singletons/Command.swift | 23 ----------------------- phpmon/Singletons/Shell.swift | 12 +++--------- 4 files changed, 10 insertions(+), 35 deletions(-) diff --git a/phpmon/Extensions/DateExtension.swift b/phpmon/Extensions/DateExtension.swift index 86a330b..10279b5 100644 --- a/phpmon/Extensions/DateExtension.swift +++ b/phpmon/Extensions/DateExtension.swift @@ -10,8 +10,7 @@ import Cocoa extension Date { - func toString() -> String - { + func toString() -> String { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss" return dateFormatter.string(from: self) diff --git a/phpmon/Extensions/StringExtension.swift b/phpmon/Extensions/StringExtension.swift index d2f46db..4217b4c 100644 --- a/phpmon/Extensions/StringExtension.swift +++ b/phpmon/Extensions/StringExtension.swift @@ -10,13 +10,18 @@ import Foundation extension String { func countInstances(of stringToFind: String) -> Int { - assert(!stringToFind.isEmpty) + if (stringToFind.isEmpty) { + return 0 + } + var count = 0 var searchRange: Range? + while let foundRange = range(of: stringToFind, options: [], range: searchRange) { count += 1 searchRange = Range(uncheckedBounds: (lower: foundRange.upperBound, upper: endIndex)) } + return count } } diff --git a/phpmon/Singletons/Command.swift b/phpmon/Singletons/Command.swift index 7c1e4d6..2931a6b 100644 --- a/phpmon/Singletons/Command.swift +++ b/phpmon/Singletons/Command.swift @@ -24,27 +24,4 @@ class Command { return output; } - public static func experiment() { - /* - print("Running '/usr/local/bin/php -v' directly...") - print("========================================") - var start = DispatchTime.now() - print(Command.execute(path: "/usr/local/bin/php", arguments: ["-v"])) - var end = DispatchTime.now() - var nanoTime = end.uptimeNanoseconds - start.uptimeNanoseconds - var timeInterval = Double(nanoTime) / 1_000_000_000 - print("Time to run command directly: \(timeInterval) seconds") - - print("") - print("Running 'bash -> php -v'...") - print("========================================") - start = DispatchTime.now() - print(Shell.user.pipe("php -v")) - end = DispatchTime.now() - nanoTime = end.uptimeNanoseconds - start.uptimeNanoseconds - timeInterval = Double(nanoTime) / 1_000_000_000 - print("Time to run command via bash: \(timeInterval) seconds") - */ - } - } diff --git a/phpmon/Singletons/Shell.swift b/phpmon/Singletons/Shell.swift index 5db6269..61729cd 100644 --- a/phpmon/Singletons/Shell.swift +++ b/phpmon/Singletons/Shell.swift @@ -40,9 +40,9 @@ class Shell { } /// Runs a shell command and returns the output. - public func pipe(_ command: String) -> String { + public func pipe(_ command: String, shell: String = "/bin/sh") -> String { let task = Process() - task.launchPath = "/bin/bash" + task.launchPath = shell task.arguments = ["--login", "-c", command] let pipe = Pipe() @@ -51,13 +51,7 @@ class Shell { let data = pipe.fileHandleForReading.readDataToEndOfFile() - let output: String = NSString( - data: data, - encoding: String.Encoding.utf8.rawValue - )!.replacingOccurrences( - of: "\u{1B}(B\u{1B}[m", - with: "" - ) as String + let output: String = NSString(data: data, encoding: String.Encoding.utf8.rawValue)! as String let historyItem = ShellHistoryItem(command: command, output: output) From 1c455ea05ab56c85f988283094252a8fbc266982 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sat, 16 May 2020 23:32:34 +0200 Subject: [PATCH 2/3] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Use=20Localizable.stri?= =?UTF-8?q?ngs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 4 ++ phpmon/Classes/Commands/Actions.swift | 11 ++- phpmon/Classes/Helpers/PhpVersion.swift | 4 +- phpmon/Extensions/StringExtension.swift | 5 ++ phpmon/Localizable.strings | 17 +++++ phpmon/Singletons/MainMenu.swift | 96 ++++++++++++------------- 6 files changed, 83 insertions(+), 54 deletions(-) create mode 100644 phpmon/Localizable.strings diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 9b0cec9..7367a73 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -16,6 +16,7 @@ C41C1B4D22B0215A00E7CF16 /* Actions.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41C1B4C22B0215A00E7CF16 /* Actions.swift */; }; C42295DD2358D02000E263B2 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42295DC2358D02000E263B2 /* Command.swift */; }; C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA23E246C358E00944F05 /* StringExtension.swift */; }; + C473319F2470923A009A0597 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; }; C476FF9822B0DD830098105B /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C476FF9722B0DD830098105B /* Alert.swift */; }; C4811D2422D70A4700B5F6B3 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2322D70A4700B5F6B3 /* App.swift */; }; C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */; }; @@ -37,6 +38,7 @@ C41C1B4C22B0215A00E7CF16 /* Actions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Actions.swift; sourceTree = ""; }; C42295DC2358D02000E263B2 /* Command.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = ""; }; C46FA23E246C358E00944F05 /* StringExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtension.swift; sourceTree = ""; }; + C473319E2470923A009A0597 /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = ""; }; C476FF9722B0DD830098105B /* Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = ""; }; C4811D2322D70A4700B5F6B3 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainMenu.swift; sourceTree = ""; }; @@ -86,6 +88,7 @@ C41C1B3F22B0098000E7CF16 /* Info.plist */, C41C1B4022B0098000E7CF16 /* phpmon.entitlements */, C41C1B3A22B0098000E7CF16 /* Assets.xcassets */, + C473319E2470923A009A0597 /* Localizable.strings */, ); path = phpmon; sourceTree = ""; @@ -206,6 +209,7 @@ files = ( C41C1B3B22B0098000E7CF16 /* Assets.xcassets in Resources */, C41C1B3E22B0098000E7CF16 /* Main.storyboard in Resources */, + C473319F2470923A009A0597 /* Localizable.strings in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/phpmon/Classes/Commands/Actions.swift b/phpmon/Classes/Commands/Actions.swift index c1bc361..5e28446 100644 --- a/phpmon/Classes/Commands/Actions.swift +++ b/phpmon/Classes/Commands/Actions.swift @@ -65,6 +65,11 @@ class Actions { } } + public static func openGenericPhpConfigFolder() { + let files = [NSURL(fileURLWithPath: "/usr/local/etc/php")]; + NSWorkspace.shared.activateFileViewerSelecting(files as [URL]) + } + public static func openPhpConfigFolder(version: String) { let files = [NSURL(fileURLWithPath: "/usr/local/etc/php/\(version)/php.ini")]; NSWorkspace.shared.activateFileViewerSelecting(files as [URL]) @@ -75,7 +80,7 @@ class Actions { NSWorkspace.shared.activateFileViewerSelecting(files as [URL]) } - public static func XdebugFound(_ version: String) -> Bool { + public static func didFindXdebug(_ version: String) -> Bool { let command = """ grep -q 'zend_extension="xdebug.so"' /usr/local/etc/php/\(version)/php.ini; [ $? -eq 0 ] && echo "YES" || echo "NO" """ @@ -83,7 +88,7 @@ class Actions { return (output == "YES") } - public static func XdebugEnabled(_ version: String) -> Bool { + public static func didEnableXdebug(_ version: String) -> Bool { let command = """ grep -q '; zend_extension="xdebug.so"' /usr/local/etc/php/\(version)/php.ini; [ $? -eq 0 ] && echo "YES" || echo "NO" """ @@ -96,7 +101,7 @@ class Actions { var command = """ sed -i '' 's/; zend_extension="xdebug.so"/zend_extension="xdebug.so"/g' /usr/local/etc/php/\(version!)/php.ini """ - if (self.XdebugEnabled(version!)) { + if (self.didEnableXdebug(version!)) { command = """ sed -i '' 's/zend_extension="xdebug.so"/; zend_extension="xdebug.so"/g' /usr/local/etc/php/\(version!)/php.ini """ diff --git a/phpmon/Classes/Helpers/PhpVersion.swift b/phpmon/Classes/Helpers/PhpVersion.swift index bf6c350..ec9ed8b 100644 --- a/phpmon/Classes/Helpers/PhpVersion.swift +++ b/phpmon/Classes/Helpers/PhpVersion.swift @@ -38,9 +38,9 @@ class PhpVersion { self.short = segments[0...1].joined(separator: ".") // Load xdebug support - self.xdebugFound = Actions.XdebugFound(self.short) + self.xdebugFound = Actions.didFindXdebug(self.short) if (self.xdebugFound) { - self.xdebugEnabled = Actions.XdebugEnabled(self.short) + self.xdebugEnabled = Actions.didEnableXdebug(self.short) } self.error = false; diff --git a/phpmon/Extensions/StringExtension.swift b/phpmon/Extensions/StringExtension.swift index 4217b4c..35c049e 100644 --- a/phpmon/Extensions/StringExtension.swift +++ b/phpmon/Extensions/StringExtension.swift @@ -9,6 +9,11 @@ import Foundation extension String { + + var localized: String { + return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "") + } + func countInstances(of stringToFind: String) -> Int { if (stringToFind.isEmpty) { return 0 diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings new file mode 100644 index 0000000..2ef96a5 --- /dev/null +++ b/phpmon/Localizable.strings @@ -0,0 +1,17 @@ +/* + Strings.strings + PHP Monitor + + Created by Nico Verbruggen on 16/05/2020. + Copyright © 2020 Nico Verbruggen. All rights reserved. +*/ + +// ALERTS + +// Force Reload Started +"alert.force_reload.title" = "PHP Monitor will force reload the latest version of PHP"; +"alert.force_reload.info" = "This can take a while. You'll get another alert when the force reload has completed."; + +// Force Reload Done +"alert.force_reload_done.title" = "PHP has been force reloaded"; +"alert.force_reload_done.info" = "All appropriate services have been restarted, and the latest version of PHP is now active. You can now try switching to another version of PHP."; diff --git a/phpmon/Singletons/MainMenu.swift b/phpmon/Singletons/MainMenu.swift index a3f68b9..f733d76 100644 --- a/phpmon/Singletons/MainMenu.swift +++ b/phpmon/Singletons/MainMenu.swift @@ -69,7 +69,7 @@ class MainMenu: NSObject, NSWindowDelegate { menu.addItem(NSMenuItem(title: "Active Services", action: nil, keyEquivalent: "")) menu.addItem(NSMenuItem(title: "Restart php-fpm service", action: #selector(self.restartPhpFpm), keyEquivalent: "f")) menu.addItem(NSMenuItem(title: "Restart nginx service", action: #selector(self.restartNginx), keyEquivalent: "n")) - menu.addItem(NSMenuItem(title: "Force load latest PHP version", action: #selector(self.fixMyPhp), keyEquivalent: "")) + menu.addItem(NSMenuItem(title: "Force load latest PHP version", action: #selector(self.forceRestartLatestPhp), keyEquivalent: "")) menu.addItem(NSMenuItem.separator()) } if (App.shared.busy) { @@ -129,6 +129,24 @@ class MainMenu: NSObject, NSWindowDelegate { } } + // MARK: - Invokable Logic + + private func waitAndExecute(_ execute: @escaping () -> Void, _ completion: @escaping () -> Void = {}) + { + App.shared.busy = true + self.setBusyImage() + DispatchQueue.global(qos: .userInitiated).async { [unowned self] in + self.update() + execute() + App.shared.busy = false + DispatchQueue.main.async { + self.updatePhpVersionInStatusBar() + self.update() + completion() + } + } + } + // MARK: - Callable via Obj-C (#selector) @objc func updatePhpVersionInStatusBar() { @@ -151,21 +169,6 @@ class MainMenu: NSObject, NSWindowDelegate { } } - private func waitAndExecute(_ execute: @escaping () -> Void) - { - App.shared.busy = true - self.setBusyImage() - DispatchQueue.global(qos: .userInitiated).async { [unowned self] in - self.update() - execute() - App.shared.busy = false - DispatchQueue.main.async { - self.updatePhpVersionInStatusBar() - self.update() - } - } - } - @objc public func restartPhpFpm() { self.waitAndExecute({ Actions.restartPhpFpm() @@ -178,13 +181,34 @@ class MainMenu: NSObject, NSWindowDelegate { }) } - @objc public func openAbout() { - NSApplication.shared.activate(ignoringOtherApps: true) - NSApplication.shared.orderFrontStandardAboutPanel() + @objc public func toggleXdebug() { + self.waitAndExecute({ + Actions.toggleXdebug() + }) + } + + @objc public func forceRestartLatestPhp() { + Alert.present( + messageText: "alert.force_reload.title".localized, + informativeText: "alert.force_reload.info".localized + ) + self.waitAndExecute({ Actions.fixMyPhp() }, { + Alert.present( + messageText: "alert.force_reload_done.title".localized, + informativeText: "alert.force_reload_done.info".localized + ) + }) } @objc public func openActiveConfigFolder() { - Actions.openPhpConfigFolder(version: App.shared.currentVersion!.short) + if (App.shared.currentVersion!.error) { + // php version was not identified + Actions.openGenericPhpConfigFolder() + } else { + // php version was identified + Actions.openPhpConfigFolder(version: App.shared.currentVersion!.short) + } + } @objc public func openValetConfigFolder() { @@ -216,35 +240,9 @@ class MainMenu: NSObject, NSWindowDelegate { } } - @objc public func toggleXdebug() { - DispatchQueue.global(qos: .userInitiated).async { [unowned self] in - DispatchQueue.main.async { - self.setStatusBar(image: NSImage(named: NSImage.Name("StatusBarIcon"))!) - } - Actions.toggleXdebug() - DispatchQueue.main.async { - self.updatePhpVersionInStatusBar() - self.update() - } - } - } - - @objc public func fixMyPhp() { - DispatchQueue.global(qos: .userInitiated).async { [unowned self] in - DispatchQueue.main.async { - Alert.present(messageText: "PHP Monitor will force reload the latest version of PHP", informativeText: "This can take a while. You'll get another alert when the force reload has completed.") - App.shared.busy = true - self.updatePhpVersionInStatusBar() - self.update() - } - Actions.fixMyPhp() - DispatchQueue.main.async { - Alert.present(messageText: "PHP has been force reloaded", informativeText: "All appropriate services have been restarted, and the latest version of PHP is now active. You can now try switching to another version of PHP.") - App.shared.busy = false - self.updatePhpVersionInStatusBar() - self.update() - } - } + @objc public func openAbout() { + NSApplication.shared.activate(ignoringOtherApps: true) + NSApplication.shared.orderFrontStandardAboutPanel() } func windowWillClose(_ notification: Notification) { From bc8a572072f7d6bf2db60b040dedc874f574bc5f Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Sat, 16 May 2020 23:55:04 +0200 Subject: [PATCH 3/3] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Menu=20refactor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PHP Monitor.xcodeproj/project.pbxproj | 12 ++++ phpmon/Classes/Menu/StatusMenu.swift | 88 ++++++++++++++++++++++++ phpmon/Singletons/MainMenu.swift | 96 ++++++++------------------- 3 files changed, 127 insertions(+), 69 deletions(-) create mode 100644 phpmon/Classes/Menu/StatusMenu.swift diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 7367a73..bf15bdf 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -17,6 +17,7 @@ C42295DD2358D02000E263B2 /* Command.swift in Sources */ = {isa = PBXBuildFile; fileRef = C42295DC2358D02000E263B2 /* Command.swift */; }; C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C46FA23E246C358E00944F05 /* StringExtension.swift */; }; C473319F2470923A009A0597 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = C473319E2470923A009A0597 /* Localizable.strings */; }; + C47331A2247093B7009A0597 /* StatusMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C47331A1247093B7009A0597 /* StatusMenu.swift */; }; C476FF9822B0DD830098105B /* Alert.swift in Sources */ = {isa = PBXBuildFile; fileRef = C476FF9722B0DD830098105B /* Alert.swift */; }; C4811D2422D70A4700B5F6B3 /* App.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2322D70A4700B5F6B3 /* App.swift */; }; C4811D2A22D70F9A00B5F6B3 /* MainMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */; }; @@ -39,6 +40,7 @@ C42295DC2358D02000E263B2 /* Command.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Command.swift; sourceTree = ""; }; C46FA23E246C358E00944F05 /* StringExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtension.swift; sourceTree = ""; }; C473319E2470923A009A0597 /* Localizable.strings */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; path = Localizable.strings; sourceTree = ""; }; + C47331A1247093B7009A0597 /* StatusMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusMenu.swift; sourceTree = ""; }; C476FF9722B0DD830098105B /* Alert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Alert.swift; sourceTree = ""; }; C4811D2322D70A4700B5F6B3 /* App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = App.swift; sourceTree = ""; }; C4811D2922D70F9A00B5F6B3 /* MainMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainMenu.swift; sourceTree = ""; }; @@ -96,6 +98,7 @@ C41E181722CB61EB0072CF09 /* Classes */ = { isa = PBXGroup; children = ( + C47331A0247093AC009A0597 /* Menu */, C4811D2722D70D8E00B5F6B3 /* Commands */, C4811D2822D70D9C00B5F6B3 /* Helpers */, ); @@ -110,6 +113,14 @@ path = "View Controllers"; sourceTree = ""; }; + C47331A0247093AC009A0597 /* Menu */ = { + isa = PBXGroup; + children = ( + C47331A1247093B7009A0597 /* StatusMenu.swift */, + ); + path = Menu; + sourceTree = ""; + }; C4811D2622D70CEF00B5F6B3 /* Singletons */ = { isa = PBXGroup; children = ( @@ -231,6 +242,7 @@ C41C1B3722B0097F00E7CF16 /* AppDelegate.swift in Sources */, C41C1B4B22B019FF00E7CF16 /* PhpVersion.swift in Sources */, C476FF9822B0DD830098105B /* Alert.swift in Sources */, + C47331A2247093B7009A0597 /* StatusMenu.swift in Sources */, C46FA23F246C358E00944F05 /* StringExtension.swift in Sources */, C4EE188422D3386B00E126E5 /* Constants.swift in Sources */, ); diff --git a/phpmon/Classes/Menu/StatusMenu.swift b/phpmon/Classes/Menu/StatusMenu.swift new file mode 100644 index 0000000..d49e66f --- /dev/null +++ b/phpmon/Classes/Menu/StatusMenu.swift @@ -0,0 +1,88 @@ +// +// MainMenuBuilder.swift +// PHP Monitor +// +// Created by Nico Verbruggen on 16/05/2020. +// Copyright © 2020 Nico Verbruggen. All rights reserved. +// + +import Cocoa + +class StatusMenu : NSMenu { + + public func addPhpVersionMenuItems() + { + var string = "We are not sure what version of PHP you are running." + if (App.shared.currentVersion != nil) { + if (!App.shared.currentVersion!.error) { + string = "You are running PHP \(App.shared.currentVersion!.long)" + self.addItem(NSMenuItem(title: string, action: nil, keyEquivalent: "")) + } else { + // in case of an error show the error message + self.addItem(NSMenuItem(title: "Oof! It appears your PHP installation is broken...", action: nil, keyEquivalent: "")) + self.addItem(NSMenuItem(title: "Try running `php -v` in your terminal.", action: nil, keyEquivalent: "")) + self.addItem(NSMenuItem(title: "You could also try switching to another version.", action: nil, keyEquivalent: "")) + self.addItem(NSMenuItem(title: "Running `brew reinstall php` (or for the equivalent version) might help.", action: nil, keyEquivalent: "")) + } + } + } + + public func addPhpActionMenuItems() + { + if (App.shared.availablePhpVersions.count > 0 && !App.shared.busy) { + var shortcutKey = 1 + for index in (0.. 0 && !App.shared.busy) { - var shortcutKey = 1 - for index in (0.. Void, _ completion: @escaping () -> Void = {}) { @@ -147,7 +99,7 @@ class MainMenu: NSObject, NSWindowDelegate { } } - // MARK: - Callable via Obj-C (#selector) + // MARK: - Actions & Menu Manipulation @objc func updatePhpVersionInStatusBar() { App.shared.currentVersion = PhpVersion() @@ -245,6 +197,12 @@ class MainMenu: NSObject, NSWindowDelegate { NSApplication.shared.orderFrontStandardAboutPanel() } + @objc public func terminateApp() { + NSApplication.shared.terminate(nil) + } + + // MARK: - Cleanup when window closes + func windowWillClose(_ notification: Notification) { App.shared.windowController = nil Shell.user.delegate = nil