cli.contribution.ts 7.1 KB
Newer Older
J
Joao Moreno 已提交
1 2 3 4 5 6 7 8 9
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

import * as nls from 'vs/nls';
import * as path from 'path';
import * as cp from 'child_process';
import * as pfs from 'vs/base/node/pfs';
A
Alex Dima 已提交
10
import * as platform from 'vs/base/common/platform';
J
Joao Moreno 已提交
11 12 13 14
import { nfcall } from 'vs/base/common/async';
import { TPromise } from 'vs/base/common/winjs.base';
import URI from 'vs/base/common/uri';
import { Action } from 'vs/base/common/actions';
B
Benjamin Pasero 已提交
15
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
16
import { Registry } from 'vs/platform/registry/common/platform';
J
Joao Moreno 已提交
17
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
18
import product from 'vs/platform/node/product';
19
import { INotificationService } from 'vs/platform/notification/common/notification';
20
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
21
import Severity from 'vs/base/common/severity';
22
import { ILogService } from 'vs/platform/log/common/log';
23

J
Joao Moreno 已提交
24 25 26 27
function ignore<T>(code: string, value: T = null): (err: any) => TPromise<T> {
	return err => err.code === code ? TPromise.as<T>(value) : TPromise.wrapError<T>(err);
}

A
Alex Dima 已提交
28 29 30 31
let _source: string = null;
function getSource(): string {
	if (!_source) {
		const root = URI.parse(require.toUrl('')).fsPath;
32
		_source = path.resolve(root, '..', 'resources', 'darwin', 'bin', 'code.sh');
A
Alex Dima 已提交
33 34 35
	}
	return _source;
}
J
Joao Moreno 已提交
36 37

function isAvailable(): TPromise<boolean> {
38
	console.log(getSource());
A
Alex Dima 已提交
39
	return pfs.exists(getSource());
J
Joao Moreno 已提交
40
}
J
Joao Moreno 已提交
41 42 43

class InstallAction extends Action {

44
	static readonly ID = 'workbench.action.installCommandLine';
45
	static LABEL = nls.localize('install', "Install '{0}' command in PATH", product.applicationName);
J
Joao Moreno 已提交
46 47 48 49

	constructor(
		id: string,
		label: string,
50
		@INotificationService private notificationService: INotificationService,
51
		@IDialogService private dialogService: IDialogService,
J
Joao Moreno 已提交
52
		@ILogService private logService: ILogService
J
Joao Moreno 已提交
53 54 55 56 57
	) {
		super(id, label);
	}

	private get target(): string {
J
Johannes Rieken 已提交
58
		return `/usr/local/bin/${product.applicationName}`;
J
Joao Moreno 已提交
59 60 61
	}

	run(): TPromise<void> {
J
Joao Moreno 已提交
62 63 64
		return isAvailable().then(isAvailable => {
			if (!isAvailable) {
				const message = nls.localize('not available', "This command is not available");
65
				this.notificationService.info(message);
66
				return undefined;
J
Joao Moreno 已提交
67
			}
J
Joao Moreno 已提交
68

69 70 71
			return this.isInstalled()
				.then(isInstalled => {
					if (!isAvailable || isInstalled) {
J
Joao Moreno 已提交
72
						return TPromise.as(null);
73
					} else {
74 75 76 77 78 79 80 81 82 83
						return pfs.unlink(this.target)
							.then(null, ignore('ENOENT'))
							.then(() => pfs.symlink(getSource(), this.target))
							.then(null, err => {
								if (err.code === 'EACCES' || err.code === 'ENOENT') {
									return this.createBinFolderAndSymlinkAsAdmin();
								}

								return TPromise.wrapError(err);
							});
84 85
					}
				})
R
Ron Buckton 已提交
86
				.then(() => {
J
Joao Moreno 已提交
87
					this.logService.trace('cli#install', this.target);
88
					this.notificationService.info(nls.localize('successIn', "Shell command '{0}' successfully installed in PATH.", product.applicationName));
R
Ron Buckton 已提交
89
				});
J
Joao Moreno 已提交
90
		});
J
Joao Moreno 已提交
91 92 93 94 95 96
	}

	private isInstalled(): TPromise<boolean> {
		return pfs.lstat(this.target)
			.then(stat => stat.isSymbolicLink())
			.then(() => pfs.readlink(this.target))
A
Alex Dima 已提交
97
			.then(link => link === getSource())
J
Joao Moreno 已提交
98 99 100
			.then(null, ignore('ENOENT', false));
	}

101
	private createBinFolderAndSymlinkAsAdmin(): TPromise<void> {
J
Joao Moreno 已提交
102
		return new TPromise<void>((c, e) => {
103
			const buttons = [nls.localize('ok', "OK"), nls.localize('cancel2', "Cancel")];
104

105
			this.dialogService.show(Severity.Info, nls.localize('warnEscalation', "Code will now prompt with 'osascript' for Administrator privileges to install the shell command."), buttons, { cancelId: 1 }).then(choice => {
106 107
				switch (choice) {
					case 0 /* OK */:
108
						const command = 'osascript -e "do shell script \\"mkdir -p /usr/local/bin && ln -sf \'' + getSource() + '\' \'' + this.target + '\'\\" with administrator privileges"';
109 110 111 112 113 114 115 116 117 118

						nfcall(cp.exec, command, {})
							.then(null, _ => TPromise.wrapError(new Error(nls.localize('cantCreateBinFolder', "Unable to create '/usr/local/bin'."))))
							.done(c, e);
						break;
					case 1 /* Cancel */:
						e(new Error(nls.localize('aborted', "Aborted")));
						break;
				}
			});
J
Joao Moreno 已提交
119
		});
J
Joao Moreno 已提交
120 121 122 123 124
	}
}

class UninstallAction extends Action {

125
	static readonly ID = 'workbench.action.uninstallCommandLine';
126
	static LABEL = nls.localize('uninstall', "Uninstall '{0}' command from PATH", product.applicationName);
J
Joao Moreno 已提交
127 128 129 130

	constructor(
		id: string,
		label: string,
J
Joao Moreno 已提交
131
		@INotificationService private notificationService: INotificationService,
132 133
		@ILogService private logService: ILogService,
		@IDialogService private dialogService: IDialogService
J
Joao Moreno 已提交
134 135 136 137 138
	) {
		super(id, label);
	}

	private get target(): string {
J
Johannes Rieken 已提交
139
		return `/usr/local/bin/${product.applicationName}`;
J
Joao Moreno 已提交
140 141 142
	}

	run(): TPromise<void> {
J
Joao Moreno 已提交
143 144 145
		return isAvailable().then(isAvailable => {
			if (!isAvailable) {
				const message = nls.localize('not available', "This command is not available");
146
				this.notificationService.info(message);
147
				return undefined;
J
Joao Moreno 已提交
148 149
			}

150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185
			const uninstall = () => {
				return pfs.unlink(this.target)
					.then(null, ignore('ENOENT'));
			};

			return uninstall().then(null, err => {
				if (err.code === 'EACCES') {
					return this.deleteSymlinkAsAdmin();
				}

				return TPromise.wrapError(err);
			}).then(() => {
				this.logService.trace('cli#uninstall', this.target);
				this.notificationService.info(nls.localize('successFrom', "Shell command '{0}' successfully uninstalled from PATH.", product.applicationName));
			});
		});
	}

	private deleteSymlinkAsAdmin(): TPromise<void> {
		return new TPromise<void>((c, e) => {
			const buttons = [nls.localize('ok', "OK"), nls.localize('cancel2', "Cancel")];

			this.dialogService.show(Severity.Info, nls.localize('warnEscalationUninstall', "Code will now prompt with 'osascript' for Administrator privileges to uninstall the shell command."), buttons, { cancelId: 1 }).then(choice => {
				switch (choice) {
					case 0 /* OK */:
						const command = 'osascript -e "do shell script \\"rm \'' + this.target + '\'\\" with administrator privileges"';

						nfcall(cp.exec, command, {})
							.then(null, _ => TPromise.wrapError(new Error(nls.localize('cantUninstall', "Unable to uninstall the shell command '{0}'.", this.target))))
							.done(c, e);
						break;
					case 1 /* Cancel */:
						e(new Error(nls.localize('aborted', "Aborted")));
						break;
				}
			});
J
Joao Moreno 已提交
186
		});
J
Joao Moreno 已提交
187 188 189
	}
}

A
Alex Dima 已提交
190
if (platform.isMacintosh) {
J
Joao Moreno 已提交
191
	const category = nls.localize('shellCommand', "Shell Command");
J
Joao Moreno 已提交
192

B
Benjamin Pasero 已提交
193
	const workbenchActionsRegistry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
194 195
	workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(InstallAction, InstallAction.ID, InstallAction.LABEL), 'Shell Command: Install \'code\' command in PATH', category);
	workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(UninstallAction, UninstallAction.ID, UninstallAction.LABEL), 'Shell Command: Uninstall \'code\' command from PATH', category);
J
Joao Moreno 已提交
196
}