Skip to content

run synchronously on process load, and restart extension host on change #14

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
}