From 8504bdbba8f5c07be1339ab3d0ab14160371d652 Mon Sep 17 00:00:00 2001 From: Tae Won Ha Date: Tue, 3 Jul 2018 07:47:58 +0200 Subject: [PATCH] Refactor nvim launching - First execute env in login shell and use the resulting env vars to launch nvim --- NvimView/NvimView.xcodeproj/project.pbxproj | 4 + NvimView/NvimView/ProcessUtils.swift | 51 ++++++++++ NvimView/NvimView/SimpleCache.swift | 9 +- NvimView/NvimView/UiBridge.swift | 105 ++++++++------------ 4 files changed, 103 insertions(+), 66 deletions(-) create mode 100644 NvimView/NvimView/ProcessUtils.swift diff --git a/NvimView/NvimView.xcodeproj/project.pbxproj b/NvimView/NvimView.xcodeproj/project.pbxproj index a5461b59..c72f7d87 100644 --- a/NvimView/NvimView.xcodeproj/project.pbxproj +++ b/NvimView/NvimView.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 1929B8CA73D3702364903BB7 /* SimpleCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BD86668017F804408E3A /* SimpleCache.swift */; }; 1929BA70C221E3C199833B8C /* UiBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B52174EC68D2974B5BAE /* UiBridge.swift */; }; 1929BA93BDEA029011F034FF /* RxSwiftCommons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929B6F4B70B90F7CFB7B523 /* RxSwiftCommons.swift */; }; + 1929BCA615324C58582BFC3C /* ProcessUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1929BD167BE7C6BB788DAE2A /* ProcessUtils.swift */; }; 4B177886201220F300E32FF0 /* SharedTypes.h in Headers */ = {isa = PBXBuildFile; fileRef = 1929B4F32708E99C40A57020 /* SharedTypes.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4B17E549209E3E4100265C1D /* RxNeovimApi.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4B17E548209E3E4100265C1D /* RxNeovimApi.framework */; }; 4B8662E81FDC3F9F007F490D /* vimr.vim in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4B8662E41FDC3D4F007F490D /* vimr.vim */; }; @@ -100,6 +101,7 @@ 1929B52174EC68D2974B5BAE /* UiBridge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UiBridge.swift; sourceTree = ""; }; 1929B6F4B70B90F7CFB7B523 /* RxSwiftCommons.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RxSwiftCommons.swift; sourceTree = ""; }; 1929BBD7F88AE4F01E626691 /* NvimApiExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NvimApiExtension.swift; sourceTree = ""; }; + 1929BD167BE7C6BB788DAE2A /* ProcessUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProcessUtils.swift; sourceTree = ""; }; 1929BD86668017F804408E3A /* SimpleCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SimpleCache.swift; sourceTree = ""; }; 4B17E548209E3E4100265C1D /* RxNeovimApi.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = RxNeovimApi.framework; path = ../Carthage/Build/Mac/RxNeovimApi.framework; sourceTree = ""; }; 4B8662E41FDC3D4F007F490D /* vimr.vim */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = vimr.vim; sourceTree = ""; }; @@ -228,6 +230,7 @@ 1929B6F4B70B90F7CFB7B523 /* RxSwiftCommons.swift */, 1929B44323D6611E2927EC3B /* MessagePackCommons.swift */, 1929BD86668017F804408E3A /* SimpleCache.swift */, + 1929BD167BE7C6BB788DAE2A /* ProcessUtils.swift */, ); path = NvimView; sourceTree = ""; @@ -409,6 +412,7 @@ 1929BA93BDEA029011F034FF /* RxSwiftCommons.swift in Sources */, 1929B30D6C4175835D1F5B21 /* MessagePackCommons.swift in Sources */, 1929B8CA73D3702364903BB7 /* SimpleCache.swift in Sources */, + 1929BCA615324C58582BFC3C /* ProcessUtils.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/NvimView/NvimView/ProcessUtils.swift b/NvimView/NvimView/ProcessUtils.swift new file mode 100644 index 00000000..a5a80f62 --- /dev/null +++ b/NvimView/NvimView/ProcessUtils.swift @@ -0,0 +1,51 @@ +/** + * Tae Won Ha - http://taewon.de - @hataewon + * See LICENSE + */ + +import Foundation + +class ProcessUtils { + + static func envVars(of shellPath: URL, usingInteractiveMode: Bool) -> [String: String] { + let shellName = shellPath.lastPathComponent + var shellArgs = [String]() + + if shellName != "tcsh" { + shellArgs.append("-l") + } + + if usingInteractiveMode { + shellArgs.append("-i") + } + + shellArgs.append(contentsOf: ["-c", "env"]) + + let outputPipe = Pipe() + let errorPipe = Pipe() + + let process = Process() + process.launchPath = shellPath.path + process.arguments = shellArgs + process.standardOutput = outputPipe + process.standardError = errorPipe + process.currentDirectoryPath = NSHomeDirectory() + process.launch() + + let readHandle = outputPipe.fileHandleForReading + guard let output = String(data: readHandle.readDataToEndOfFile(), encoding: .utf8) else { + return [:] + } + readHandle.closeFile() + + process.terminate() + process.waitUntilExit() + + return output + .split(separator: "\n") + .reduce(into: [:]) { result, entry in + let split = entry.split(separator: "=", maxSplits: 1, omittingEmptySubsequences: false).map { String($0) } + result[split[0]] = split[1] + } + } +} \ No newline at end of file diff --git a/NvimView/NvimView/SimpleCache.swift b/NvimView/NvimView/SimpleCache.swift index 7d5b72e4..96a874db 100644 --- a/NvimView/NvimView/SimpleCache.swift +++ b/NvimView/NvimView/SimpleCache.swift @@ -1,7 +1,8 @@ -// -// Created by Tae Won Ha on 21.05.18. -// Copyright (c) 2018 Tae Won Ha. All rights reserved. -// +/** + * Tae Won Ha - http://taewon.de - @hataewon + * See LICENSE + */ + import Foundation diff --git a/NvimView/NvimView/UiBridge.swift b/NvimView/NvimView/UiBridge.swift index 7805b28d..3088c290 100644 --- a/NvimView/NvimView/UiBridge.swift +++ b/NvimView/NvimView/UiBridge.swift @@ -71,13 +71,13 @@ class UiBridge { self.server.queue = self.queue self.server.stream - .subscribe(onNext: { message in - self.handleMessage(msgId: message.msgid, data: message.data) - }, onError: { error in - self.logger.error("There was an error on the local message port server: \(error)") - self.streamSubject.onError(Error.ipc(error)) - }) - .disposed(by: self.disposeBag) + .subscribe(onNext: { message in + self.handleMessage(msgId: message.msgid, data: message.data) + }, onError: { error in + self.logger.error("There was an error on the local message port server: \(error)") + self.streamSubject.onError(Error.ipc(error)) + }) + .disposed(by: self.disposeBag) } func runLocalServerAndNvim(width: Int, height: Int) -> Completable { @@ -85,15 +85,15 @@ class UiBridge { self.initialHeight = height return self.server - .run(as: self.localServerName) - .andThen(Completable.create { completable in - self.runLocalServerAndNvimCompletable = completable - self.launchNvimUsingLoginShell() + .run(as: self.localServerName) + .andThen(Completable.create { completable in + self.runLocalServerAndNvimCompletable = completable + self.launchNvimUsingLoginShellEnv() - // This will be completed in .nvimReady branch of handleMessage() - return Disposables.create() - }) - .timeout(timeout, scheduler: self.scheduler) + // This will be completed in .nvimReady branch of handleMessage() + return Disposables.create() + }) + .timeout(timeout, scheduler: self.scheduler) } func vimInput(_ str: String) -> Completable { @@ -150,11 +150,11 @@ class UiBridge { case .serverReady: self - .establishNvimConnection() - .subscribe(onError: { error in - self.streamSubject.onError(Error.ipc(error)) - }) - .disposed(by: self.disposeBag) + .establishNvimConnection() + .subscribe(onError: { error in + self.streamSubject.onError(Error.ipc(error)) + }) + .disposed(by: self.disposeBag) case .nvimReady: self.runLocalServerAndNvimCompletable?(.completed) @@ -310,7 +310,7 @@ class UiBridge { let dict = (try? unpack(d))?.value.dictionaryValue, let key = dict.keys.first?.stringValue, let value = dict.values.first - else { + else { return } @@ -331,31 +331,31 @@ class UiBridge { private func closePorts() -> Completable { return self.client - .stop() - .andThen(self.server.stop()) + .stop() + .andThen(self.server.stop()) } private func quit(using body: @escaping () -> Void) -> Completable { return self - .closePorts() - .andThen(Completable.create { completable in - body() + .closePorts() + .andThen(Completable.create { completable in + body() - completable(.completed) - return Disposables.create() - }) + completable(.completed) + return Disposables.create() + }) } private func establishNvimConnection() -> Completable { return self.client - .connect(to: self.remoteServerName) - .andThen(self.sendMessage(msgId: .agentReady, data: [self.initialWidth, self.initialHeight].data())) + .connect(to: self.remoteServerName) + .andThen(self.sendMessage(msgId: .agentReady, data: [self.initialWidth, self.initialHeight].data())) } private func sendMessage(msgId: NvimBridgeMsgId, data: Data?) -> Completable { return self.client - .send(msgid: Int32(msgId.rawValue), data: data, expectsReply: false) - .asCompletable() + .send(msgid: Int32(msgId.rawValue), data: data, expectsReply: false) + .asCompletable() } private func forceExitNvimServer() { @@ -363,47 +363,28 @@ class UiBridge { self.nvimServerProc?.terminate() } - private func launchNvimUsingLoginShell() { + private func launchNvimUsingLoginShellEnv() { let selfEnv = ProcessInfo.processInfo.environment + let shellUrl = URL(fileURLWithPath: selfEnv["SHELL"] ?? "/bin/bash") - let shellPath = URL(fileURLWithPath: selfEnv["SHELL"] ?? "/bin/bash") - let shellName = shellPath.lastPathComponent - var shellArgs = [String]() - if shellName != "tcsh" { - // tcsh does not like the -l option - shellArgs.append("-l") - } - if self.useInteractiveZsh && shellName == "zsh" { - shellArgs.append("-i") - } + 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 = selfEnv env["NVIM_LISTEN_ADDRESS"] = listenAddress.path - let inputPipe = Pipe() + let outPipe = Pipe() + let errorPipe = Pipe() let process = Process() process.environment = env - process.standardInput = inputPipe + process.standardError = errorPipe + process.standardOutput = outPipe process.currentDirectoryPath = self.cwd.path - process.launchPath = shellPath.path - process.arguments = shellArgs + process.launchPath = self.nvimServerExecutablePath() + process.arguments = [self.localServerName, self.remoteServerName] + self.nvimArgs + ["--headless"] process.launch() self.nvimServerProc = process - - nvimArgs.append("--headless") - let cmd = "exec '\(self.nvimServerExecutablePath())' '\(self.localServerName)' '\(self.remoteServerName)' " - .appending(self.nvimArgs.map { "'\($0)'" }.joined(separator: " ")) - - self.logger.debug(cmd) - - let writeHandle = inputPipe.fileHandleForWriting - guard let cmdData = cmd.data(using: .utf8) else { - preconditionFailure("Could not get Data from the string '\(cmd)'") - } - writeHandle.write(cmdData) - writeHandle.closeFile() } private func nvimServerExecutablePath() -> String {