diff --git a/PHP Monitor.xcodeproj/project.pbxproj b/PHP Monitor.xcodeproj/project.pbxproj index 22b661c..a734d3a 100644 --- a/PHP Monitor.xcodeproj/project.pbxproj +++ b/PHP Monitor.xcodeproj/project.pbxproj @@ -9,8 +9,8 @@ /* Begin PBXBuildFile section */ 5420395926135DC100FB00FA /* PrefsVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395826135DC100FB00FA /* PrefsVC.swift */; }; 5420395F2613607600FB00FA /* Preferences.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5420395E2613607600FB00FA /* Preferences.swift */; }; - 54B48B5F275F66AE006D90C5 /* Editor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B48B5E275F66AE006D90C5 /* Editor.swift */; }; - 54B48B60275F66AE006D90C5 /* Editor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B48B5E275F66AE006D90C5 /* Editor.swift */; }; + 54B48B5F275F66AE006D90C5 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B48B5E275F66AE006D90C5 /* Application.swift */; }; + 54B48B60275F66AE006D90C5 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = 54B48B5E275F66AE006D90C5 /* Application.swift */; }; 54EAC806262F212B0092D14E /* GlobalKeybindPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = C41CD0282628D8EE0065BBED /* GlobalKeybindPreference.swift */; }; C405A4D024B9B9140062FAFA /* InternetAccessPolicy.strings in Resources */ = {isa = PBXBuildFile; fileRef = C405A4CE24B9B9130062FAFA /* InternetAccessPolicy.strings */; }; C405A4D124B9B9140062FAFA /* InternetAccessPolicy.plist in Resources */ = {isa = PBXBuildFile; fileRef = C405A4CF24B9B9140062FAFA /* InternetAccessPolicy.plist */; }; @@ -120,7 +120,7 @@ /* Begin PBXFileReference section */ 5420395826135DC100FB00FA /* PrefsVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PrefsVC.swift; sourceTree = ""; }; 5420395E2613607600FB00FA /* Preferences.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Preferences.swift; sourceTree = ""; }; - 54B48B5E275F66AE006D90C5 /* Editor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Editor.swift; sourceTree = ""; }; + 54B48B5E275F66AE006D90C5 /* Application.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = ""; }; C405A4CE24B9B9130062FAFA /* InternetAccessPolicy.strings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; path = InternetAccessPolicy.strings; sourceTree = ""; }; C405A4CF24B9B9140062FAFA /* InternetAccessPolicy.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = InternetAccessPolicy.plist; sourceTree = ""; }; C412E5FB25700D5300A1FB67 /* HomebrewPackage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HomebrewPackage.swift; sourceTree = ""; }; @@ -323,7 +323,7 @@ isa = PBXGroup; children = ( C476FF9722B0DD830098105B /* Alert.swift */, - 54B48B5E275F66AE006D90C5 /* Editor.swift */, + 54B48B5E275F66AE006D90C5 /* Application.swift */, C4188988275FE8CB001EF227 /* Filesystem.swift */, C474B00524C0E98C00066A22 /* LocalNotification.swift */, C41C1B4822B00A9800E7CF16 /* MenuBarImageGenerator.swift */, @@ -544,7 +544,7 @@ C4CCBA6C275C567B008C7055 /* PMWindowController.swift in Sources */, C41CD0292628D8EE0065BBED /* GlobalKeybindPreference.swift in Sources */, C42295DD2358D02000E263B2 /* Command.swift in Sources */, - 54B48B5F275F66AE006D90C5 /* Editor.swift in Sources */, + 54B48B5F275F66AE006D90C5 /* Application.swift in Sources */, C4B97B78275CF1B5003F3378 /* App+ActivationPolicy.swift in Sources */, C4811D2422D70A4700B5F6B3 /* App.swift in Sources */, C41C1B4922B00A9800E7CF16 /* MenuBarImageGenerator.swift in Sources */, @@ -577,7 +577,7 @@ files = ( 54EAC806262F212B0092D14E /* GlobalKeybindPreference.swift in Sources */, C4F780C425D80B75000DBC97 /* MainMenu.swift in Sources */, - 54B48B60275F66AE006D90C5 /* Editor.swift in Sources */, + 54B48B60275F66AE006D90C5 /* Application.swift in Sources */, C4F780C825D80B75000DBC97 /* DateExtension.swift in Sources */, C4F780CC25D80B75000DBC97 /* ActivePhpInstallation.swift in Sources */, C4F780B125D80B4D000DBC97 /* PhpExtension.swift in Sources */, diff --git a/README.md b/README.md index e8be141..2da0218 100644 --- a/README.md +++ b/README.md @@ -235,7 +235,7 @@ PHP Monitor itself doesn't do any network requests. Feel free to check the sourc When you select and right-click on a domain, you can open these directories with various applications. This can help speed up your workflow. However, for these apps to show up, they must be detected first. -The supported apps are: PhpStorm, Visual Studio Code, Sublime Text, Sublime Merge. +The supported apps are: PhpStorm, Visual Studio Code, Sublime Text, Sublime Merge, iTerm. For Visual Studio Code, you need to have `code` available in your PATH (`/usr/bin/local/code` is checked). [More info](https://code.visualstudio.com/docs/editor/command-line) here. @@ -243,7 +243,7 @@ For Visual Studio Code, you need to have `code` available in yo For PhpStorm, you need to have `pstorm` available in your PATH (`/usr/bin/local/pstorm` is checked). [More info](https://www.jetbrains.com/help/phpstorm/working-with-the-ide-features-from-command-line.html) here. -For Sublime Text (and Sublime Merge), the apps need to be in your `/Applications` directory. +For Sublime Text, Sublime Merge and iTerm, the apps need to be in your `/Applications` directory. To see which files are checked to determine availability, see [this file](./phpmon/Domain/Helpers/Editor.swift). diff --git a/phpmon/Domain/Helpers/Editor.swift b/phpmon/Domain/Helpers/Application.swift similarity index 76% rename from phpmon/Domain/Helpers/Editor.swift rename to phpmon/Domain/Helpers/Application.swift index b286ef0..8212cf7 100644 --- a/phpmon/Domain/Helpers/Editor.swift +++ b/phpmon/Domain/Helpers/Application.swift @@ -10,10 +10,10 @@ import Foundation /// An application that is capable of opening a particular directory (usually of a PHP project). /// In most cases this is going to be a code editor, but it could also be another application -/// that supports opening those directories, like a visual Git client. -class Editor { +/// that supports opening those directories, like a visual Git client or a terminal app. +class Application { - /// Name of the editor. Used for display purposes. + /// Name of the app. Used for display purposes. let name: String /// Paths to check whether the application is actually installed. @@ -31,7 +31,7 @@ class Editor { @objc let openCallback: (String) -> Void /** - - Parameter name: Name of the editor. + - Parameter name: Name of the application. - Parameter installPath: Files to verify, if any file exists here the app is considered present on the system. - Parameter binaryPath: Additional file that is used to open a specific path. - Parameter open: Callback used to open a specific directory in the editor in question. @@ -57,6 +57,9 @@ class Editor { /** Checks if the app is installed. */ func isInstalled() -> Bool { + // TODO: Alternative way to detect if an app is installed: + // mdfind "kMDItemKind == 'Application'" | grep AppName.app + // This will return the path to the application. Worth a refactor? self.pathsToVerifyInstalled.map({ path in Shell.fileExists(path) }).contains(true) @@ -68,14 +71,13 @@ class Editor { } /** - Detect which "editors" are available to open a specific directory. + Detect which apps are available to open a specific directory. */ - static public func detectPresetEditors() -> [Editor] { + static public func detectPresetApplications() -> [Application] { return [ - Editor( + Application( name: "PhpStorm", installPaths: [ - "~/Applications/JetBrains Toolbox/PhpStorm.app/Contents/Info.plist", "/Applications/PhpStorm.app/Contents/Info.plist", "/usr/local/bin/pstorm" ], @@ -85,7 +87,19 @@ class Editor { }, instruction: "editors.pstorm_binary_not_linked.desc".localized ), - Editor( + Application( + name: "PhpStorm (via Toolbox)", + installPaths: [ + "~/Applications/JetBrains Toolbox/PhpStorm.app/Contents/Info.plist", + "/usr/local/bin/phpstorm" + ], + binaryPath: "/usr/local/bin/phpstorm", + open: { path in + Shell.run("/usr/local/bin/phpstorm \(path)") + }, + instruction: "editors.phpstorm_binary_not_linked.desc".localized + ), + Application( name: "Visual Studio Code", installPaths: [ "/Applications/Visual Studio Code.app/Contents/Info.plist", @@ -97,7 +111,7 @@ class Editor { }, instruction: "editors.code_binary_not_linked.desc".localized ), - Editor( + Application( name: "Sublime Text", installPaths: ["/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl"], binaryPath: "/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl", @@ -105,13 +119,21 @@ class Editor { Shell.run("/Applications/Sublime\\ Text.app/Contents/SharedSupport/bin/subl \(path)") } ), - Editor( + Application( name: "Sublime Merge", installPaths: ["/Applications/Sublime Merge.app/Contents/SharedSupport/bin/smerge"], binaryPath: "/Applications/Sublime Merge.app/Contents/SharedSupport/bin/smerge", open: { path in Shell.run("/Applications/Sublime\\ Merge.app/Contents/SharedSupport/bin/smerge \(path)") } + ), + Application( + name: "iTerm", + installPaths: ["/Applications/iTerm.app/Contents/Info.plist"], + binaryPath: "/Applications/iTerm.app/Contents/Info.plist", + open: { path in + Shell.run("open -a iTerm \(path)") + } ) ].filter { return $0.isInstalled() } } diff --git a/phpmon/Domain/Menu/StatusMenu.swift b/phpmon/Domain/Menu/StatusMenu.swift index 0cf59eb..1474623 100644 --- a/phpmon/Domain/Menu/StatusMenu.swift +++ b/phpmon/Domain/Menu/StatusMenu.swift @@ -90,17 +90,14 @@ class StatusMenu : NSMenu { func addForceLoadLatestVersion() { if !App.shared.availablePhpVersions.contains(App.shared.brewPhpVersion) { self.addItem(NSMenuItem( - title: "mi_force_load_latest_unavailable".localized - .replacingOccurrences(of: "%@", with: App.shared.brewPhpVersion), + title: "mi_force_load_latest_unavailable".localized(App.shared.brewPhpVersion), action: nil, keyEquivalent: "f" )) } else { self.addItem(NSMenuItem( - title: "mi_force_load_latest".localized - .replacingOccurrences(of: "%@", with: App.shared.brewPhpVersion), + title: "mi_force_load_latest".localized(App.shared.brewPhpVersion), action: #selector(MainMenu.forceRestartLatestPhp), keyEquivalent: "f")) } - } func addValetMenuItems() { @@ -185,5 +182,5 @@ class ExtensionMenuItem: NSMenuItem { } class EditorMenuItem: NSMenuItem { - var editor: Editor? = nil + var editor: Application? = nil } diff --git a/phpmon/Domain/SiteList/SiteListVC.swift b/phpmon/Domain/SiteList/SiteListVC.swift index c8559d0..f73c00a 100644 --- a/phpmon/Domain/SiteList/SiteListVC.swift +++ b/phpmon/Domain/SiteList/SiteListVC.swift @@ -22,8 +22,8 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource { /// List of sites that will be displayed in this view. Originates from the `Valet` object. var sites: [Valet.Site] = [] - /// Array that contains various editors that might open a particular site directory. - var editors: [Editor] = Editor.detectPresetEditors() + /// Array that contains various apps that might open a particular site directory. + var editors: [Application] = Application.detectPresetApplications() /// String that was last searched for. Empty by default. var lastSearchedFor = "" @@ -176,16 +176,18 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource { selectedSite.determineSecured(Valet.shared.config.tld) if selectedSite.secured == originalSecureStatus { Alert.notify( - message: "site_list.alerts_status_changed.title".localized, - info: "\("site_list.alerts_status_changed.desc".localized) `\(command)`") + message: "site_list.alerts_status_not_changed.title".localized, + info: "site_list.alerts_status_not_changed.desc".localized(command) + ) } else { let newState = selectedSite.secured ? "secured" : "unsecured" LocalNotification.send( title: "site_list.alerts_status_changed.title".localized, subtitle: "site_list.alerts_status_changed.desc" - .localized - .replacingOccurrences(of: "{@1}", with: "\(selectedSite.name!).\(Valet.shared.config.tld)") - .replacingOccurrences(of: "{@2}", with: newState) + .localized( + "\(selectedSite.name!).\(Valet.shared.config.tld)", + newState + ) ) } @@ -207,6 +209,10 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource { Shell.run("open \(selectedSite!.absolutePath!)") } + @objc public func openInTerminal() { + Shell.run("open -b com.apple.terminal \(selectedSite!.absolutePath!)") + } + @objc public func unlinkSite() { guard let site = selectedSite else { return @@ -218,8 +224,7 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource { Alert.confirm( onWindow: view.window!, - messageText: "site_list.confirm_unlink".localized - .replacingOccurrences(of: "%@", with: site.name), + messageText: "site_list.confirm_unlink".localized(site.name), informativeText: "site_link.confirm_link".localized, buttonTitle: "site_list.unlink".localized, secondButtonTitle: "Cancel", @@ -280,6 +285,7 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource { if (editors.count > 0) { menu.addItem(NSMenuItem.separator()) + menu.addItem(withTitle: "site_list.detected_apps".localized, action: nil, keyEquivalent: "") for (index, editor) in editors.enumerated() { let editorMenuItem = EditorMenuItem( @@ -294,11 +300,17 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource { menu.addItem(NSMenuItem.separator()) } + menu.addItem(withTitle: "site_list.system_apps".localized, action: nil, keyEquivalent: "") menu.addItem( withTitle: "site_list.open_in_finder".localized, action: #selector(self.openInFinder), keyEquivalent: "F" ) + menu.addItem( + withTitle: "site_list.open_in_terminal".localized, + action: #selector(self.openInTerminal), + keyEquivalent: "T" + ) menu.addItem( withTitle: "site_list.open_in_browser".localized, action: #selector(self.openInBrowser), @@ -318,7 +330,7 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource { } } - private func presentAlertForMissingEditorBinary(_ editor: Editor, _ sender: EditorMenuItem) { + private func presentAlertForMissingEditorBinary(_ editor: Application, _ sender: EditorMenuItem) { Alert.confirm( onWindow: self.view.window!, messageText: "editors.binary_missing.title".localized(editor.pathToBinary), diff --git a/phpmon/Domain/Terminal/Shell.swift b/phpmon/Domain/Terminal/Shell.swift index 2bc7c25..85a8427 100644 --- a/phpmon/Domain/Terminal/Shell.swift +++ b/phpmon/Domain/Terminal/Shell.swift @@ -93,5 +93,4 @@ class Shell { public static func fileExists(_ path: String) -> Bool { return Shell.pipe("if [ -f \(path) ]; then /bin/echo -n \"0\"; fi") == "0" } - } diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings index 1e1aac1..770c906 100644 --- a/phpmon/Localizable.strings +++ b/phpmon/Localizable.strings @@ -54,11 +54,11 @@ "site_list.title" = "Domains"; "site_list.subtitle" = "Linked & Parked"; -"site_list.alerts_status_changed.title" = "SSL Status Not Changed"; -"site_list.alerts_status_changed.desc" = "Something went wrong. Try running the command in your terminal manually:"; +"site_list.alerts_status_not_changed.title" = "SSL Status Not Changed"; +"site_list.alerts_status_not_changed.desc" = "Something went wrong. Try running the command in your terminal manually: %@"; "site_list.alerts_status_changed.title" = "SSL Status Changed"; -"site_list.alerts_status_changed.desc" = "The domain '{@1}' is now {@2}."; +"site_list.alerts_status_changed.desc" = "The domain '%@' is now %@."; "site_list.confirm_unlink" = "Are you sure you want to unlink '%@'?"; "site_link.confirm_link" = "No files will be removed. If needed, the site will need to be relinked via the command line."; @@ -70,8 +70,9 @@ "site_list.unsecure" = "Unsecure"; "site_list.open_in_finder" = "Open in Finder"; "site_list.open_in_browser" = "Open in Browser"; -"site_list.open_with_vs_code" = "Open with Visual Studio Code"; -"site_list.open_with_pstorm" = "Open with PhpStorm"; +"site_list.open_in_terminal" = "Open in Terminal"; +"site_list.detected_apps" = "Detected Applications"; +"site_list.system_apps" = "System Applications"; // EDITORS "editors.binary_missing.title" = "`%@` missing"; @@ -79,10 +80,21 @@ "editors.alert.try_again" = "Try Again"; "editors.alert.cancel" = "Cancel"; +// - PHPSTORM + TOOLBOX +"editors.phpstorm_binary_not_linked.desc" = +"PHP Monitor makes use of PhpStorm’s launcher to open a specific directory. + +The required launcher does not seem to be found in the usual location. In JetBrains Toolbox go to Settings, and select 'Generate shell scripts'. + +As the directory, enter `/usr/local/bin`. + +After this is done, PHP Monitor should be able to open the domain’s folder in PhpStorm."; + +// - PHPSTORM (standalone) "editors.pstorm_binary_not_linked.desc" = "PHP Monitor makes use of PhpStorm’s launcher to open a specific directory. -The desired launcher does not seem to be found in the usual location. Please set up the launcher by going to 'Tools > Create command-line launcher' in PhpStorm. +The required launcher does not seem to be found in the usual location. Please set up the launcher by going to 'Tools > Create command-line launcher' in PhpStorm. After this is done, PHP Monitor should be able to open the domain’s folder in PhpStorm."; "editors.code_binary_not_linked.desc" = @@ -93,6 +105,15 @@ You can fix this by selecting 'Install `code` command in PATH' in Visual Studio After this is done, PHP Monitor should be able to open the domain’s folder in PhpStorm."; +// - VS CODE +"editors.code_binary_not_linked.desc" = +"PHP Monitor makes use of Visual Studio Code‘s helper binary, `code`, but it seems not to exist on your system. + +You can fix this by selecting 'Install `code` command in PATH' in Visual Studio Code’s command palette. +(Type `>code` to find it.) + +After this is done, PHP Monitor should be able to open the domain’s folder in PhpStorm."; + // PREFERENCES "prefs.title" = "PHP Monitor";