Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 32 additions & 16 deletions src/command.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand All @@ -21,26 +21,42 @@ export class Command {
this.rcPath = path.join(rootPath, `${constants.direnv.rc}`);
}
// Private methods
private exec(options: CommandExecOptions): Thenable<string> {
private execAsync(options: CommandExecOptions): Thenable<string> {
return <Thenable<string>>this.exec(false, options)
}

private execSync(options: CommandExecOptions): string {
return <string>this.exec(true, options)
}

private exec(sync: boolean, options: CommandExecOptions): Thenable<string> | 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) : {}
};
}
63 changes: 41 additions & 22 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) =>
Expand All @@ -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<number> = () => {
try {
const envDiff = command.exportJSONSync()
console.log("loaded direnv diff:", envDiff)
return Promise.resolve(utils.assign(process.env, envDiff))
} catch(err) {
return Promise.reject<number>(err)
}
}

let postInitialize = (result: Thenable<number>, 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 {
Expand All @@ -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() {
Expand Down
16 changes: 13 additions & 3 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -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
}