From c50439d6602573048add8599780a1c1b51c69165 Mon Sep 17 00:00:00 2001 From: Tae Won Ha Date: Tue, 3 Jul 2018 21:17:14 +0200 Subject: [PATCH] GH-625 Pass env vars from `vimr` to UI - Use a temporary json file --- NvimView/NvimView/NvimView.swift | 12 ++- NvimView/NvimView/UiBridge.swift | 21 +++-- VimR/VimR/AppDelegate.swift | 53 ++++++++--- VimR/VimR/AppDelegateReducer.swift | 26 ++++-- VimR/VimR/MainWindow.swift | 3 +- VimR/VimR/States.swift | 1 + VimR/VimR/vimr | 138 +++++++++++++++++------------ 7 files changed, 166 insertions(+), 88 deletions(-) diff --git a/NvimView/NvimView/NvimView.swift b/NvimView/NvimView/NvimView.swift index bb2cd60b..83f3a689 100644 --- a/NvimView/NvimView/NvimView.swift +++ b/NvimView/NvimView/NvimView.swift @@ -17,14 +17,17 @@ public class NvimView: NSView, var useInteractiveZsh: Bool var cwd: URL var nvimArgs: [String]? + var envDict: [String: String]? public init(useInteractiveZsh: Bool, - cwd: URL = URL(fileURLWithPath: NSHomeDirectory()), - nvimArgs: [String]? = nil) { + cwd: URL, + nvimArgs: [String]?, + envDict: [String: String]?) { self.useInteractiveZsh = useInteractiveZsh self.cwd = cwd self.nvimArgs = nvimArgs + self.envDict = envDict } } @@ -310,7 +313,10 @@ public class NvimView: NSView, } convenience override public init(frame rect: NSRect) { - self.init(frame: rect, config: Config(useInteractiveZsh: false)) + self.init(frame: rect, config: Config(useInteractiveZsh: false, + cwd: URL(fileURLWithPath: NSHomeDirectory()), + nvimArgs: nil, + envDict: nil)) } required public init?(coder: NSCoder) { diff --git a/NvimView/NvimView/UiBridge.swift b/NvimView/NvimView/UiBridge.swift index 3088c290..77244763 100644 --- a/NvimView/NvimView/UiBridge.swift +++ b/NvimView/NvimView/UiBridge.swift @@ -64,6 +64,17 @@ class UiBridge { self.nvimArgs = config.nvimArgs ?? [] self.cwd = config.cwd + if let envDict = config.envDict { + self.envDict = envDict + logger.debug("using envs from vimr: \(envDict)") + } else { + let selfEnv = ProcessInfo.processInfo.environment + let shellUrl = URL(fileURLWithPath: selfEnv["SHELL"] ?? "/bin/bash") + let interactiveMode = shellUrl.lastPathComponent == "zsh" && !config.useInteractiveZsh ? false : true + self.envDict = ProcessUtils.envVars(of: shellUrl, usingInteractiveMode: interactiveMode) + logger.debug("using envs from login shell: \(self.envDict)") + } + self.queue = queue self.scheduler = SerialDispatchQueueScheduler(queue: queue, internalSerialQueueName: String(reflecting: UiBridge.self)) @@ -364,13 +375,8 @@ class UiBridge { } private func launchNvimUsingLoginShellEnv() { - let selfEnv = ProcessInfo.processInfo.environment - let shellUrl = URL(fileURLWithPath: selfEnv["SHELL"] ?? "/bin/bash") - - let interactiveMode = shellUrl.lastPathComponent == "zsh" && !self.useInteractiveZsh ? false : true - var env = ProcessUtils.envVars(of: shellUrl, usingInteractiveMode: interactiveMode) - let listenAddress = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("vimr_\(self.uuid).sock") + var env = self.envDict env["NVIM_LISTEN_ADDRESS"] = listenAddress.path let outPipe = Pipe() @@ -401,7 +407,8 @@ class UiBridge { private let useInteractiveZsh: Bool private let cwd: URL - private var nvimArgs: [String] + private let nvimArgs: [String] + private let envDict: [String: String] private let server = RxMessagePortServer() private let client = RxMessagePortClient() diff --git a/VimR/VimR/AppDelegate.swift b/VimR/VimR/AppDelegate.swift index b4e0b868..11706d05 100644 --- a/VimR/VimR/AppDelegate.swift +++ b/VimR/VimR/AppDelegate.swift @@ -14,7 +14,7 @@ class AppDelegate: NSObject, NSApplicationDelegate, NSUserNotificationCenterDele enum Action { - case newMainWindow(urls: [URL], cwd: URL, nvimArgs: [String]?, cliPipePath: String?) + case newMainWindow(urls: [URL], cwd: URL, nvimArgs: [String]?, cliPipePath: String?, envDict: [String: String]?) case openInKeyWindow(urls: [URL], cwd: URL, cliPipePath: String?) case preferences @@ -188,7 +188,7 @@ extension AppDelegate { // For drag & dropping files on the App icon. func application(_ sender: NSApplication, openFiles filenames: [String]) { let urls = filenames.map { URL(fileURLWithPath: $0) } - self.emit(.newMainWindow(urls: urls, cwd: FileUtils.userHomeUrl, nvimArgs: nil, cliPipePath: nil)) + self.emit(.newMainWindow(urls: urls, cwd: FileUtils.userHomeUrl, nvimArgs: nil, cliPipePath: nil, envDict: nil)) sender.reply(toOpenOrPrint: .success) } @@ -242,6 +242,20 @@ extension AppDelegate { return } + let envDict: [String: String]? + if let envPath = queryParam(envPathPrefix, from: rawParams, transforming: identity).first { + envDict = stringDict(from: URL(fileURLWithPath: envPath)) + if FileManager.default.fileExists(atPath: envPath) { + do { + try FileManager.default.removeItem(atPath: envPath) + } catch { + fileLog.error(error.localizedDescription) + } + } + } else { + envDict = nil + } + let urls = queryParam(filePrefix, from: rawParams, transforming: { URL(fileURLWithPath: $0) }) let cwd = queryParam(cwdPrefix, from: rawParams, @@ -258,26 +272,43 @@ extension AppDelegate { switch action { case .activate, .newWindow: - self.emit(.newMainWindow(urls: urls, cwd: cwd, nvimArgs: nil, cliPipePath: pipePath)) + self.emit(.newMainWindow(urls: urls, cwd: cwd, nvimArgs: nil, cliPipePath: pipePath, envDict: envDict)) case .open: self.emit(.openInKeyWindow(urls: urls, cwd: cwd, cliPipePath: pipePath)) case .separateWindows: - urls.forEach { self.emit(.newMainWindow(urls: [$0], cwd: cwd, nvimArgs: nil, cliPipePath: pipePath)) } + urls.forEach { + self.emit(.newMainWindow(urls: [$0], cwd: cwd, nvimArgs: nil, cliPipePath: pipePath, envDict: nil)) + } case .nvim: self.emit(.newMainWindow(urls: [], cwd: cwd, nvimArgs: queryParam(nvimArgsPrefix, from: rawParams, transforming: identity), - cliPipePath: pipePath)) + cliPipePath: pipePath, + envDict: envDict)) } } + private func stringDict(from jsonUrl: URL) -> [String: String]? { + guard let data = try? Data(contentsOf: jsonUrl) else { + return nil + } + + do { + return try JSONSerialization.jsonObject(with: data) as? [String: String] + } catch { + fileLog.error(error.localizedDescription) + } + + return nil + } + private func queryParam(_ prefix: String, - from rawParams: [String], - transforming transform: (String) -> T) -> [T] { + from rawParams: [String], + transforming transform: (String) -> T) -> [T] { return rawParams .filter { $0.hasPrefix(prefix) } @@ -294,7 +325,7 @@ extension AppDelegate { } @IBAction func newDocument(_ sender: Any?) { - self.emit(.newMainWindow(urls: [], cwd: FileUtils.userHomeUrl, nvimArgs: nil, cliPipePath: nil)) + self.emit(.newMainWindow(urls: [], cwd: FileUtils.userHomeUrl, nvimArgs: nil, cliPipePath: nil, envDict: nil)) } @IBAction func openInNewWindow(_ sender: Any?) { @@ -318,7 +349,7 @@ extension AppDelegate { let urls = panel.urls let commonParentUrl = FileUtils.commonParent(of: urls) - self.emit(.newMainWindow(urls: urls, cwd: commonParentUrl, nvimArgs: nil, cliPipePath: nil)) + self.emit(.newMainWindow(urls: urls, cwd: commonParentUrl, nvimArgs: nil, cliPipePath: nil, envDict: nil)) } } } @@ -331,7 +362,7 @@ extension AppDelegate { } } -/// Keep the rawValues in sync with Action in the `vimr` Python script. +// Keep the rawValues in sync with Action in the `vimr` Python script. private enum VimRUrlAction: String { case activate = "activate" case open = "open" @@ -344,8 +375,10 @@ private let updater = SUUpdater() private let debugMenuItemIdentifier = NSUserInterfaceItemIdentifier("debug-menu-item") +// Keep in sync with QueryParamKey in the `vimr` Python script. private let filePrefix = "file=" private let cwdPrefix = "cwd=" private let nvimArgsPrefix = "nvim-args=" private let pipePathPrefix = "pipe-path=" private let waitPrefix = "wait=" +private let envPathPrefix = "env-path=" diff --git a/VimR/VimR/AppDelegateReducer.swift b/VimR/VimR/AppDelegateReducer.swift index 82810d67..1c18ce7f 100644 --- a/VimR/VimR/AppDelegateReducer.swift +++ b/VimR/VimR/AppDelegateReducer.swift @@ -18,19 +18,25 @@ class AppDelegateReducer { switch pair.action { - case let .newMainWindow(urls, cwd, nvimArgs, cliPipePath): + case let .newMainWindow(urls, cwd, nvimArgs, cliPipePath, envDict): let mainWindow: MainWindow.State if let args = nvimArgs { - mainWindow = self.newMainWindow(with: state, urls: [], cwd: cwd, nvimArgs: args, cliPipePath: cliPipePath) + mainWindow = self.newMainWindow( + with: state, urls: [], cwd: cwd, nvimArgs: args, cliPipePath: cliPipePath, envDict: envDict + ) } else { - mainWindow = self.newMainWindow(with: state, urls: urls, cwd: cwd, cliPipePath: cliPipePath) + mainWindow = self.newMainWindow( + with: state, urls: urls, cwd: cwd, nvimArgs: nil, cliPipePath: cliPipePath, envDict: envDict + ) } state.mainWindows[mainWindow.uuid] = mainWindow case let .openInKeyWindow(urls, cwd, cliPipePath): guard let uuid = state.currentMainWindowUuid, state.mainWindows[uuid] != nil else { - let mainWindow = self.newMainWindow(with: state, urls: urls, cwd: cwd, cliPipePath: cliPipePath) + let mainWindow = self.newMainWindow( + with: state, urls: urls, cwd: cwd, nvimArgs: nil, cliPipePath: cliPipePath, envDict: nil + ) state.mainWindows[mainWindow.uuid] = mainWindow break } @@ -49,10 +55,11 @@ class AppDelegateReducer { private let baseServerUrl: URL private func newMainWindow(with state: AppState, - urls: [URL], - cwd: URL, - nvimArgs: [String]? = nil, - cliPipePath: String? = nil) -> MainWindow.State { + urls: [URL], + cwd: URL, + nvimArgs: [String]?, + cliPipePath: String?, + envDict: [String: String]?) -> MainWindow.State { var mainWindow = state.mainWindowTemplate @@ -68,9 +75,10 @@ class AppDelegateReducer { mainWindow.nvimArgs = nvimArgs mainWindow.cliPipePath = cliPipePath + mainWindow.envDict = envDict mainWindow.urlsToOpen = urls.toDict { _ in MainWindow.OpenMode.default } mainWindow.frame = state.mainWindows.isEmpty ? state.mainWindowTemplate.frame - : self.frame(relativeTo: state.mainWindowTemplate.frame) + : self.frame(relativeTo: state.mainWindowTemplate.frame) return mainWindow } diff --git a/VimR/VimR/MainWindow.swift b/VimR/VimR/MainWindow.swift index 80b46196..07b4b72e 100644 --- a/VimR/VimR/MainWindow.swift +++ b/VimR/VimR/MainWindow.swift @@ -112,7 +112,8 @@ class MainWindow: NSObject, let neoVimViewConfig = NvimView.Config(useInteractiveZsh: state.useInteractiveZsh, cwd: state.cwd, - nvimArgs: state.nvimArgs) + nvimArgs: state.nvimArgs, + envDict: state.envDict) self.neoVimView = NvimView(frame: .zero, config: neoVimViewConfig) self.neoVimView.configureForAutoLayout() diff --git a/VimR/VimR/States.swift b/VimR/VimR/States.swift index adf5ef9d..932df881 100644 --- a/VimR/VimR/States.swift +++ b/VimR/VimR/States.swift @@ -260,6 +260,7 @@ extension MainWindow { var useInteractiveZsh = false var nvimArgs: [String]? var cliPipePath: String? + var envDict: [String: String]? var isLeftOptionMeta = false var isRightOptionMeta = false diff --git a/VimR/VimR/vimr b/VimR/VimR/vimr index 407f5cfb..7fed788c 100755 --- a/VimR/VimR/vimr +++ b/VimR/VimR/vimr @@ -5,6 +5,7 @@ import subprocess import argparse import os import uuid +import json class Action: @@ -17,13 +18,14 @@ class Action: class QueryParamKey: PIPE_PATH = "pipe-path" + ENV_PATH = "env-path" CWD = "cwd" FILE = "file" NVIM_ARGS = "nvim-args" WAIT = "wait" -def wait_for_ui_to_close(): +def wait_for_ui_to_close(pipe_path): with open(pipe_path, 'r') as fifo: while True: if len(fifo.read()) == 0: @@ -31,7 +33,6 @@ def wait_for_ui_to_close(): def call_open(action, query_params, args): - query_params[QueryParamKey.PIPE_PATH] = pipe_path if args.wait: query_params[QueryParamKey.WAIT] = "true" @@ -47,10 +48,8 @@ def abspath(path): return os.path.abspath(os.path.expanduser(path)) -def vimr_nvim(other_args, nvim_args): - query_params = { - QueryParamKey.CWD: os.getcwd() - } +def vimr_nvim(other_args, nvim_args, query_params): + query_params[QueryParamKey.CWD] = os.getcwd() if nvim_args: query_params[QueryParamKey.NVIM_ARGS] = nvim_args @@ -58,14 +57,12 @@ def vimr_nvim(other_args, nvim_args): call_open(Action.NVIM, query_params, other_args) -def vimr(action, args): +def vimr(action, args, query_params): cwd = os.getcwd() if args.cwd is not None: cwd = abspath(args.cwd) - query_params = { - QueryParamKey.CWD: cwd - } + query_params[QueryParamKey.CWD] = cwd files = args.file if files: @@ -74,67 +71,92 @@ def vimr(action, args): call_open(action, query_params, args) -pipe_path = "/tmp/com_qvacua_vimr_cli_pipe_{0}".format(str(uuid.uuid4())) +def main(args): + uuid_str = str(uuid.uuid4()) + pipe_path = "/tmp/com_qvacua_vimr_cli_pipe_{0}".format(uuid_str) + if os.path.exists(pipe_path): + os.remove(pipe_path) + + try: + os.mkfifo(pipe_path, 0600) + except OSError as error: + print("ERROR: {0}\n" + "{1} could not be mkfifo'ed.\n" + "Please go to https://github.com/qvacua/vimr and create an issue.".format(error, pipe_path)) + raise + + query_params = { + QueryParamKey.PIPE_PATH: pipe_path + } + + if args.cur_env: + env_file = "/tmp/com_qvacua_vimr_env_{0}".format(uuid_str) + with open(env_file, "w") as f: + f.write(json.dumps({k: v for (k, v) in os.environ.items()})) + os.chmod(env_file, 0600) + query_params[QueryParamKey.ENV_PATH] = env_file + + if args.nvim: + nvim_parser = argparse.ArgumentParser() + nvim_parser.add_argument("--nvim", action="store_true") + nvim_parser.add_argument("--wait", action="store_true") + nvim_parser.add_argument("--dry-run", action="store_true") + other_args, nvim_args = nvim_parser.parse_known_args() + vimr_nvim(other_args, nvim_args, query_params) + + else: + if not args.file: + action = Action.ACTIVATE + elif args.new_window: + action = Action.NEW_WINDOW + elif args.separate_windows: + action = Action.SEPARATE_WINDOWS + else: + action = Action.OPEN + + vimr(action, args, query_params) + + if args.dry_run: + exit(0) + + wait_for_ui_to_close(pipe_path) -if os.path.exists(pipe_path): os.remove(pipe_path) -try: - os.mkfifo(pipe_path, 0600) -except OSError as error: - print("ERROR: {0}\n" - "{1} could not be mkfifo'ed.\n" - "Please go to https://github.com/qvacua/vimr and create an issue.".format(error, pipe_path)) - raise -description = """ +def parse_args(): + description = """ Open files in VimR: By default all files are open in tabs in the front most window or in a new window if there is none. The working directory will be set to the current directory. """ -parser = argparse.ArgumentParser(description=description) + parser = argparse.ArgumentParser(description=description) -parser.add_argument("--dry-run", action="store_true", dest="dry_run", help="Just print the 'open' command.") -parser.add_argument("--cwd", action="store", help="Set the working directory.") -parser.add_argument("--wait", - action="store_true", - help="This command line tool will exit when the corresponding UI window is closed.") -parser.add_argument("--nvim", + parser.add_argument("--dry-run", action="store_true", dest="dry_run", help="Just print the 'open' command.") + parser.add_argument("--cwd", action="store", help="Set the working directory.") + parser.add_argument("--wait", + action="store_true", + help="This command line tool will exit when the corresponding UI window is closed.") + parser.add_argument("--nvim", action="store_true", help="All other arguments (except --dry-run and --wait) will be passed over to nvim.") -group = parser.add_mutually_exclusive_group() -# no option => Open files in tabs in the front most window. -group.add_argument("-n", action="store_true", dest="new_window", help="Open files in tabs in a new window.") -group.add_argument("-s", action="store_true", dest="separate_windows", help="Open files in separate windows.") + group = parser.add_mutually_exclusive_group() + # no option => Open files in tabs in the front most window. + group.add_argument("--cur-env", + action="store_true", + dest="cur_env", + help="Use the current environment variables when launching the background neovim process. " + "All files will be opened in a new window.") + group.add_argument("-n", action="store_true", dest="new_window", help="Open files in tabs in a new window.") + group.add_argument("-s", action="store_true", dest="separate_windows", help="Open files in separate windows.") -parser.add_argument("file", nargs="*") + parser.add_argument("file", nargs="*") -args, _ = parser.parse_known_args() + args, _ = parser.parse_known_args() + return args -if args.nvim: - nvim_parser = argparse.ArgumentParser() - nvim_parser.add_argument("--nvim", action="store_true") - nvim_parser.add_argument("--wait", action="store_true") - nvim_parser.add_argument("--dry-run", action="store_true") - other_args, nvim_args = nvim_parser.parse_known_args() - vimr_nvim(other_args, nvim_args) -else: - if not args.file: - action = Action.ACTIVATE - elif args.new_window: - action = Action.NEW_WINDOW - elif args.separate_windows: - action = Action.SEPARATE_WINDOWS - else: - action = Action.OPEN - - vimr(action, args) - -if args.dry_run: - exit(0) - -wait_for_ui_to_close() - -os.remove(pipe_path) +if __name__ == "__main__": + args = parse_args() + main(args)