From 7feb13856da9eb91fdf60ba987ab8acf6477e796 Mon Sep 17 00:00:00 2001 From: Nico Verbruggen Date: Thu, 9 Dec 2021 19:39:58 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20Ensure=20editor=20binaries=20exist?= =?UTF-8?q?=20or=20notify=20user=20(#67)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Domain/Extensions/StringExtension.swift | 4 + phpmon/Domain/Helpers/Editor.swift | 83 +++++++++++++------ phpmon/Domain/SiteList/SiteListVC.swift | 22 ++++- phpmon/Localizable.strings | 18 ++++ 4 files changed, 98 insertions(+), 29 deletions(-) diff --git a/phpmon/Domain/Extensions/StringExtension.swift b/phpmon/Domain/Extensions/StringExtension.swift index 778eebb..55d94c8 100644 --- a/phpmon/Domain/Extensions/StringExtension.swift +++ b/phpmon/Domain/Extensions/StringExtension.swift @@ -12,6 +12,10 @@ extension String { return NSLocalizedString(self, tableName: nil, bundle: Bundle.main, value: "", comment: "") } + func localized(_ args: CVarArg...) -> String { + String(format: self.localized, arguments: args) + } + func countInstances(of stringToFind: String) -> Int { if (stringToFind.isEmpty) { return 0 diff --git a/phpmon/Domain/Helpers/Editor.swift b/phpmon/Domain/Helpers/Editor.swift index e067962..b286ef0 100644 --- a/phpmon/Domain/Helpers/Editor.swift +++ b/phpmon/Domain/Helpers/Editor.swift @@ -16,74 +16,103 @@ class Editor { /// Name of the editor. Used for display purposes. let name: String - /// Path to check whether the application is actually installed. - /// This was previously called `path` but the new variable name is a bit more clear. - /// To be clear, this is *not* the path to the actual binary! - let pathToVerifyInstalled: String + /// Paths to check whether the application is actually installed. + /// If the app finds any of these, the app is considered installed. + let pathsToVerifyInstalled: [String] - /// Callback that is executed to open a particular folder. Can be different from the installation path or the procedure required to determine whether the application is installed. + /// Path to the binary that actually opens the directory. + let pathToBinary: String + + /// Instruction that needs to be followed in order to ensure that the app can open with this editor. + var missingBinaryInstruction: String? = nil + + /// Callback that is executed to open a particular folder. + /// Must open the directory in the requested app, usually by using `pathToBinary`. @objc let openCallback: (String) -> Void /** - Parameter name: Name of the editor. - - Parameter path: File to verify, if this file exists here the app is considered present on the system. + - 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. + - Parameter instruction: Instruction for end user that needs to be followed in order to ensure the `binaryPath exists. */ - init(name: String, path: String, open: @escaping ((String) -> Void)) { + init(name: String, installPaths: [String], binaryPath: String, open: @escaping ((String) -> Void), instruction: String? = nil) { self.name = name - self.pathToVerifyInstalled = path.replacingOccurrences(of: " ", with: "\\ ") + self.pathsToVerifyInstalled = installPaths.map({ path in + return path.replacingOccurrences(of: " ", with: "\\ ") + }) + self.pathToBinary = binaryPath.replacingOccurrences(of: " ", with: "\\ ") self.openCallback = open + self.missingBinaryInstruction = instruction } /** - Attempt to open a specific directory in the editor of choice. This will open the editor if it isn't open yet. + Attempt to open a specific directory in the editor of choice. + This will open the editor if it isn't open yet. */ @objc public func openDirectory(file: String) { self.openCallback(file) } + /** Checks if the app is installed. */ + func isInstalled() -> Bool { + self.pathsToVerifyInstalled.map({ path in + Shell.fileExists(path) + }).contains(true) + } + + /** Checks if the correct binary required to open directories and/or files exists. */ + func hasBinary() -> Bool { + return Shell.fileExists(self.pathToBinary) + } + /** Detect which "editors" are available to open a specific directory. */ - static public func detect() -> [Editor] { + static public func detectPresetEditors() -> [Editor] { return [ Editor( name: "PhpStorm", - path: "/Applications/PhpStorm.app/Contents/Info.plist", + installPaths: [ + "~/Applications/JetBrains Toolbox/PhpStorm.app/Contents/Info.plist", + "/Applications/PhpStorm.app/Contents/Info.plist", + "/usr/local/bin/pstorm" + ], + binaryPath: "/usr/local/bin/pstorm", open: { path in - Shell.run("open -a /Applications/PhpStorm.app \(path)") - } - ), - Editor( - name: "PhpStorm (via Toolbox)", - path: "~/Applications/JetBrains Toolbox/PhpStorm.app/Contents/Info.plist", - open: { path in - Shell.run("open -a ~/Applications/JetBrains\\ Toolbox/PhpStorm.app \(path)") - } + Shell.run("/usr/local/bin/pstorm \(path)") + }, + instruction: "editors.pstorm_binary_not_linked.desc".localized ), Editor( name: "Visual Studio Code", - path: "/usr/local/bin/code", + installPaths: [ + "/Applications/Visual Studio Code.app/Contents/Info.plist", + "/usr/local/bin/code" + ], + binaryPath: "/usr/local/bin/code", open: { path in Shell.run("/usr/local/bin/code \(path)") - } + }, + instruction: "editors.code_binary_not_linked.desc".localized ), Editor( name: "Sublime Text", - path: "/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl", + installPaths: ["/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl"], + binaryPath: "/Applications/Sublime Text.app/Contents/SharedSupport/bin/subl", open: { path in Shell.run("/Applications/Sublime\\ Text.app/Contents/SharedSupport/bin/subl \(path)") } ), Editor( name: "Sublime Merge", - path: "/Applications/Sublime Merge.app/Contents/SharedSupport/bin/smerge", + 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)") } ) - ].filter { editor in - Shell.fileExists(editor.pathToVerifyInstalled) - } + ].filter { return $0.isInstalled() } } } diff --git a/phpmon/Domain/SiteList/SiteListVC.swift b/phpmon/Domain/SiteList/SiteListVC.swift index ab4e483..7ce6ad4 100644 --- a/phpmon/Domain/SiteList/SiteListVC.swift +++ b/phpmon/Domain/SiteList/SiteListVC.swift @@ -23,7 +23,7 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource { var sites: [Valet.Site] = [] /// Array that contains various editors that might open a particular site directory. - var editors: [Editor] = Editor.detect() + var editors: [Editor] = Editor.detectPresetEditors() /// String that was last searched for. Empty by default. var lastSearchedFor = "" @@ -303,7 +303,25 @@ class SiteListVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource { } @objc func openWithEditor(sender: EditorMenuItem) { - sender.editor?.openDirectory(file: selectedSite!.absolutePath!) + guard let editor = sender.editor else { return } + + if editor.hasBinary() { + editor.openDirectory(file: selectedSite!.absolutePath!) + } else { + Alert.confirm( + onWindow: self.view.window!, + messageText: "editors.binary_missing.title" + .localized( + editor.pathToBinary + ), + informativeText: editor.missingBinaryInstruction ?? "", + buttonTitle: "editors.alert.try_again".localized, + secondButtonTitle: "editors.alert.cancel".localized, + onFirstButtonPressed: { + self.openWithEditor(sender: sender) + } + ) + } } // MARK: - Deinitialization diff --git a/phpmon/Localizable.strings b/phpmon/Localizable.strings index c1adf28..cabbb51 100644 --- a/phpmon/Localizable.strings +++ b/phpmon/Localizable.strings @@ -73,6 +73,24 @@ "site_list.open_with_vs_code" = "Open with Visual Studio Code"; "site_list.open_with_pstorm" = "Open with PhpStorm"; +// EDITORS +"editors.binary_missing.title" = "`%@` missing"; +"editors.alert.try_again" = "Try Again"; +"editors.alert.cancel" = "Cancel"; + +"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. + +After this is done, PHP Monitor should be able to open the domain’s folder in PhpStorm."; +"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. + +After this is done, PHP Monitor should be able to open the domain’s folder in PhpStorm."; + // PREFERENCES "prefs.title" = "PHP Monitor";