diff --git a/src/command.ts b/src/command.ts index e11e641..c005819 100644 --- a/src/command.ts +++ b/src/command.ts @@ -1,7 +1,7 @@ 'use strict'; import * as path from 'path'; -import { exec, ExecOptions } from 'child_process'; +import { exec, execSync, ExecOptionsWithStringEncoding } from 'child_process'; import * as constants from './constants'; import * as utils from './utils'; @@ -21,26 +21,42 @@ export class Command { this.rcPath = path.join(rootPath, `${constants.direnv.rc}`); } // Private methods - private exec(options: CommandExecOptions): Thenable { + private execAsync(options: CommandExecOptions): Thenable { + return >this.exec(false, options) + } + + private execSync(options: CommandExecOptions): string { + return this.exec(true, options) + } + + private exec(sync: boolean, options: CommandExecOptions): Thenable | string { let direnvCmd = [constants.direnv.cmd, options.cmd].join(' '); - let execOptions: ExecOptions = {}; + let execOptions: ExecOptionsWithStringEncoding = { encoding: 'utf8' }; if (options.cwd == null || options.cwd) { execOptions.cwd = this.rootPath; } - return new Promise((resolve, reject) => { - exec(direnvCmd, execOptions, (err, stdout, stderr) => { - if (err) { - err.message = stderr; - reject(err); - } else { - resolve(stdout); - } + if (sync) { + console.log("NOTE: executing command synchronously", direnvCmd) + return execSync(direnvCmd, execOptions); + } else { + return new Promise((resolve, reject) => { + exec(direnvCmd, execOptions, (err, stdout, stderr) => { + if (err) { + err.message = stderr; + reject(err); + } else { + resolve(stdout); + } + }); }); - }); + } } // Public methods - version = () => this.exec({ cmd: 'version' }); - allow = () => this.exec({ cmd: 'allow' }); - deny = () => this.exec({ cmd: 'deny' }); - exportJSON = () => this.exec({ cmd: 'export json' }).then((o) => o ? JSON.parse(o) : {}); + version = () => this.execAsync({ cmd: 'version' }); + allow = () => this.execAsync({ cmd: 'allow' }); + deny = () => this.execAsync({ cmd: 'deny' }); + exportJSONSync = () => { + const o = this.execSync({ cmd: 'export json' }) + return o ? JSON.parse(o) : {} + }; } diff --git a/src/main.ts b/src/main.ts index d4db4aa..45b4560 100644 --- a/src/main.ts +++ b/src/main.ts @@ -5,7 +5,7 @@ import * as utils from './utils'; import * as constants from './constants'; import { Command } from './command'; -let oldEnvDiff = {}; +let restartExtensionHost= () => vscode.commands.executeCommand("workbench.action.restartExtensionHost") let command = new Command(vscode.workspace.rootPath); let watcher = vscode.workspace.createFileSystemWatcher(command.rcPath, true); let displayError = (e) => @@ -15,40 +15,55 @@ let version = () => command.version().then(v => vscode.window.showInformationMessage(constants.messages.version(v)), displayError); let revertFromOption = (option) => { if (option === constants.vscode.extension.actions.revert) { - utils.assign(process.env, oldEnvDiff); - oldEnvDiff = {}; - vscode.window.showInformationMessage(constants.messages.reverted); + restartExtensionHost() } }; -let assignEnvDiff = (options: { showSuccess: boolean }) => { - return command.exportJSON().then((envDiff) => { - Object.keys(envDiff).forEach((key) => { - if (key.indexOf('DIRENV_') === -1 && oldEnvDiff[key] !== envDiff[key]) { - oldEnvDiff[key] = process.env[key]; - } - }); - return utils.assign(process.env, envDiff); - }).then(() => { + +let initialize: () => Thenable = () => { + try { + const envDiff = command.exportJSONSync() + console.log("loaded direnv diff:", envDiff) + return Promise.resolve(utils.assign(process.env, envDiff)) + } catch(err) { + return Promise.reject(err) + } +} + +let postInitialize = (result: Thenable, options: { showSuccess: boolean }) => { + return result.then(() => { if (options.showSuccess) { return vscode.window.showInformationMessage(constants.messages.assign.success); } }, (err) => { if (err.message.indexOf(`${constants.direnv.rc} is blocked`) !== -1) { return vscode.window.showWarningMessage(constants.messages.assign.warn, - constants.vscode.extension.actions.allow, constants.vscode.extension.actions.view); + constants.vscode.extension.actions.allow, constants.vscode.extension.actions.view + ).then((option) => { + if (option === constants.vscode.extension.actions.allow) { + return allow(); + } else if (option === constants.vscode.extension.actions.view) { + return viewThenAllow(); + } + }); } else { return displayError(err); } - }).then((option) => { - if (option === constants.vscode.extension.actions.allow) { - return allow(); - } else if (option === constants.vscode.extension.actions.view) { - return viewThenAllow(); + }) +} + +let reinitialize = (options: { showSuccess: boolean }) => { + const result = initialize() + result.then((changes) => { + if (changes > 0) { + postInitialize(result, options).then(() => restartExtensionHost()) + } else { + return postInitialize(result, options) } - }); + }) }; + let allow = () => { - return command.allow().then(() => assignEnvDiff({ showSuccess: true }), (err) => { + return command.allow().then(() => reinitialize({ showSuccess: true }), (err) => { if (err.message.indexOf(`${constants.direnv.rc} file not found`) !== -1) { return vscode.commands.executeCommand(constants.vscode.commands.open, vscode.Uri.file(command.rcPath)); } else { @@ -72,11 +87,15 @@ watcher.onDidChange((e) => vscode.window.showWarningMessage(constants.messages.r watcher.onDidDelete((e) => vscode.window.showWarningMessage(constants.messages.rc.deleted, constants.vscode.extension.actions.revert).then(revertFromOption)); +// NOTE: we apply synchronously on extension import to ensure it takes effect for all extensions. +// This means plugin activation state isn't actually respected. +let initializeResult = initialize() + export function activate(context: vscode.ExtensionContext) { context.subscriptions.push(vscode.commands.registerCommand('direnv.version', version)); context.subscriptions.push(vscode.commands.registerCommand('direnv.view', view)); context.subscriptions.push(vscode.commands.registerCommand('direnv.allow', allow)); - assignEnvDiff({ showSuccess: false }); + postInitialize(initializeResult, { showSuccess: false }) } export function deactivate() { diff --git a/src/utils.ts b/src/utils.ts index 2aec007..9d448d9 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -1,6 +1,16 @@ 'use strict'; -export function assign(destination: any, ...sources: any[]): any { - sources.forEach(source => Object.keys(source).forEach((key) => destination[key] = source[key])); - return destination; +export function assign(destination: any, ...sources: any[]): number { + let changes = 0 + sources.forEach(source => Object.keys(source).forEach((key) => { + if (source[key] != destination[key]) { + changes += 1 + if (source[key] == null) { + delete destination[key] + } else { + destination[key] = source[key] + } + } + })); + return changes }