Merge VS Code 1.21 source code (#1067)

* Initial VS Code 1.21 file copy with patches

* A few more merges

* Post npm install

* Fix batch of build breaks

* Fix more build breaks

* Fix more build errors

* Fix more build breaks

* Runtime fixes 1

* Get connection dialog working with some todos

* Fix a few packaging issues

* Copy several node_modules to package build to fix loader issues

* Fix breaks from master

* A few more fixes

* Make tests pass

* First pass of license header updates

* Second pass of license header updates

* Fix restore dialog issues

* Remove add additional themes menu items

* fix select box issues where the list doesn't show up

* formatting

* Fix editor dispose issue

* Copy over node modules to correct location on all platforms
This commit is contained in:
Karl Burtram
2018-04-04 15:27:51 -07:00
committed by GitHub
parent 5fba3e31b4
commit dafb780987
9412 changed files with 141255 additions and 98813 deletions

View File

@@ -72,14 +72,14 @@ export class BackupModelTracker implements IWorkbenchContribution {
// Do not backup when auto save after delay is configured
if (!this.configuredAutoSaveAfterDelay) {
const model = this.textFileService.models.get(event.resource);
this.backupFileService.backupResource(model.getResource(), model.getValue(), model.getVersionId()).done(null, errors.onUnexpectedError);
this.backupFileService.backupResource(model.getResource(), model.createSnapshot(), model.getVersionId()).done(null, errors.onUnexpectedError);
}
}
}
private onUntitledModelChanged(resource: Uri): void {
if (this.untitledEditorService.isDirty(resource)) {
this.untitledEditorService.loadOrCreate({ resource }).then(model => this.backupFileService.backupResource(resource, model.getValue(), model.getVersionId())).done(null, errors.onUnexpectedError);
this.untitledEditorService.loadOrCreate({ resource }).then(model => this.backupFileService.backupResource(resource, model.createSnapshot(), model.getVersionId())).done(null, errors.onUnexpectedError);
} else {
this.discardBackup(resource);
}

View File

@@ -7,7 +7,7 @@
import URI from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
import { IUntitledEditorService, UNTITLED_SCHEMA } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import errors = require('vs/base/common/errors');
import { IBackupFileService } from 'vs/workbench/services/backup/common/backup';
@@ -17,6 +17,7 @@ import { Position, IResourceInput, IUntitledResourceInput } from 'vs/platform/ed
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { Schemas } from 'vs/base/common/network';
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { IFileService } from 'vs/platform/files/common/files';
export class BackupRestorer implements IWorkbenchContribution {
@@ -30,7 +31,8 @@ export class BackupRestorer implements IWorkbenchContribution {
@IBackupFileService private backupFileService: IBackupFileService,
@ITextFileService private textFileService: ITextFileService,
@IEditorGroupService private groupService: IEditorGroupService,
@ILifecycleService private lifecycleService: ILifecycleService
@ILifecycleService private lifecycleService: ILifecycleService,
@IFileService private fileService: IFileService
) {
this.restoreBackups();
}
@@ -69,9 +71,9 @@ export class BackupRestorer implements IWorkbenchContribution {
backups.forEach(backup => {
if (stacks.isOpen(backup)) {
if (backup.scheme === Schemas.file) {
if (this.fileService.canHandleResource(backup)) {
restorePromises.push(this.textFileService.models.loadOrCreate(backup).then(null, () => unresolved.push(backup)));
} else if (backup.scheme === UNTITLED_SCHEMA) {
} else if (backup.scheme === Schemas.untitled) {
restorePromises.push(this.untitledEditorService.loadOrCreate({ resource: backup }).then(null, () => unresolved.push(backup)));
}
} else {
@@ -95,11 +97,9 @@ export class BackupRestorer implements IWorkbenchContribution {
const options = { pinned: true, preserveFocus: true, inactive: index > 0 || hasOpenedEditors };
// {{SQL CARBON EDIT}}
if (resource.scheme === UNTITLED_SCHEMA
if (resource.scheme === Schemas.untitled
&& !BackupRestorer.UNTITLED_REGEX.test(resource.fsPath)
&& !BackupRestorer.SQLQUERY_REGEX.test(resource.fsPath)) {
// TODO@Ben debt: instead of guessing if an untitled file has an associated file path or not
// this information should be provided by the backup service and stored as meta data within
return { filePath: resource.fsPath, options };
}

View File

@@ -35,7 +35,7 @@ export class NodeCachedDataManager implements IWorkbenchContribution {
if (err) {
/* __GDPR__
"cachedDataError" : {
"errorCode" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"errorCode" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"path": { "classification": "CustomerContent", "purpose": "PerformanceAndHealth" }
}
*/

View File

@@ -7,6 +7,7 @@ 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';
import * as platform from 'vs/base/common/platform';
import { nfcall } from 'vs/base/common/async';
import { TPromise } from 'vs/base/common/winjs.base';
import URI from 'vs/base/common/uri';
@@ -14,29 +15,40 @@ import { Action } from 'vs/base/common/actions';
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
import { Registry } from 'vs/platform/registry/common/platform';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { IMessageService, Severity } from 'vs/platform/message/common/message';
import product from 'vs/platform/node/product';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IChoiceService, Choice } from 'vs/platform/dialogs/common/dialogs';
import Severity from 'vs/base/common/severity';
import { ILogService } from '../../../../platform/log/common/log';
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);
}
const root = URI.parse(require.toUrl('')).fsPath;
const source = path.resolve(root, '..', 'bin', 'code');
let _source: string = null;
function getSource(): string {
if (!_source) {
const root = URI.parse(require.toUrl('')).fsPath;
_source = path.resolve(root, '..', 'bin', 'code');
}
return _source;
}
function isAvailable(): TPromise<boolean> {
return pfs.exists(source);
return pfs.exists(getSource());
}
class InstallAction extends Action {
static ID = 'workbench.action.installCommandLine';
static readonly ID = 'workbench.action.installCommandLine';
static LABEL = nls.localize('install', "Install '{0}' command in PATH", product.applicationName);
constructor(
id: string,
label: string,
@IMessageService private messageService: IMessageService
@INotificationService private notificationService: INotificationService,
@IChoiceService private choiceService: IChoiceService,
@ILogService private logService: ILogService
) {
super(id, label);
}
@@ -49,7 +61,7 @@ class InstallAction extends Action {
return isAvailable().then(isAvailable => {
if (!isAvailable) {
const message = nls.localize('not available', "This command is not available");
this.messageService.show(Severity.Info, message);
this.notificationService.info(message);
return undefined;
}
@@ -61,7 +73,7 @@ class InstallAction extends Action {
const createSymlink = () => {
return pfs.unlink(this.target)
.then(null, ignore('ENOENT'))
.then(() => pfs.symlink(source, this.target));
.then(() => pfs.symlink(getSource(), this.target));
};
return createSymlink().then(null, err => {
@@ -75,7 +87,8 @@ class InstallAction extends Action {
}
})
.then(() => {
this.messageService.show(Severity.Info, nls.localize('successIn', "Shell command '{0}' successfully installed in PATH.", product.applicationName));
this.logService.trace('cli#install', this.target);
this.notificationService.info(nls.localize('successIn', "Shell command '{0}' successfully installed in PATH.", product.applicationName));
});
});
}
@@ -84,40 +97,42 @@ class InstallAction extends Action {
return pfs.lstat(this.target)
.then(stat => stat.isSymbolicLink())
.then(() => pfs.readlink(this.target))
.then(link => link === source)
.then(link => link === getSource())
.then(null, ignore('ENOENT', false));
}
private createBinFolder(): TPromise<void> {
return new TPromise<void>((c, e) => {
const message = nls.localize('warnEscalation', "Code will now prompt with 'osascript' for Administrator privileges to install the shell command.");
const actions = [
new Action('ok', nls.localize('ok', "OK"), '', true, () => {
const command = 'osascript -e "do shell script \\"mkdir -p /usr/local/bin && chown \\" & (do shell script (\\"whoami\\")) & \\" /usr/local/bin\\" with administrator privileges"';
const choices: Choice[] = [nls.localize('ok', "OK"), nls.localize('cancel2', "Cancel")];
nfcall(cp.exec, command, {})
.then(null, _ => TPromise.wrapError(new Error(nls.localize('cantCreateBinFolder', "Unable to create '/usr/local/bin'."))))
.done(c, e);
this.choiceService.choose(Severity.Info, nls.localize('warnEscalation', "Code will now prompt with 'osascript' for Administrator privileges to install the shell command."), choices, 1, true).then(choice => {
switch (choice) {
case 0 /* OK */:
const command = 'osascript -e "do shell script \\"mkdir -p /usr/local/bin && chown \\" & (do shell script (\\"whoami\\")) & \\" /usr/local/bin\\" with administrator privileges"';
return null;
}),
new Action('cancel2', nls.localize('cancel2', "Cancel"), '', true, () => { e(new Error(nls.localize('aborted', "Aborted"))); return null; })
];
this.messageService.show(Severity.Info, { message, actions });
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;
}
});
});
}
}
class UninstallAction extends Action {
static ID = 'workbench.action.uninstallCommandLine';
static readonly ID = 'workbench.action.uninstallCommandLine';
static LABEL = nls.localize('uninstall', "Uninstall '{0}' command from PATH", product.applicationName);
constructor(
id: string,
label: string,
@IMessageService private messageService: IMessageService
@INotificationService private notificationService: INotificationService,
@ILogService private logService: ILogService
) {
super(id, label);
}
@@ -130,20 +145,21 @@ class UninstallAction extends Action {
return isAvailable().then(isAvailable => {
if (!isAvailable) {
const message = nls.localize('not available', "This command is not available");
this.messageService.show(Severity.Info, message);
this.notificationService.info(message);
return undefined;
}
return pfs.unlink(this.target)
.then(null, ignore('ENOENT'))
.then(() => {
this.messageService.show(Severity.Info, nls.localize('successFrom', "Shell command '{0}' successfully uninstalled from PATH.", product.applicationName));
this.logService.trace('cli#uninstall', this.target);
this.notificationService.info(nls.localize('successFrom', "Shell command '{0}' successfully uninstalled from PATH.", product.applicationName));
});
});
}
}
if (process.platform === 'darwin') {
if (platform.isMacintosh) {
const category = nls.localize('shellCommand', "Shell Command");
const workbenchActionsRegistry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);

View File

@@ -13,4 +13,10 @@ import './electron-browser/toggleMultiCursorModifier';
import './electron-browser/toggleRenderControlCharacter';
import './electron-browser/toggleRenderWhitespace';
import './electron-browser/toggleWordWrap';
import './electron-browser/wordWrapMigration';
import { OPTIONS, TextBufferType } from 'vs/editor/common/model/textModel';
// Configure text buffer implementation
if (process.env['VSCODE_PIECE_TREE']) {
console.log(`Using TextBufferType.PieceTree (env variable VSCODE_PIECE_TREE)`);
OPTIONS.TEXT_BUFFER_IMPLEMENTATION = TextBufferType.PieceTree;
}

View File

@@ -82,10 +82,10 @@ class AccessibilityHelpWidget extends Widget implements IOverlayWidget {
constructor(
editor: ICodeEditor,
@IContextKeyService private _contextKeyService: IContextKeyService,
@IKeybindingService private _keybindingService: IKeybindingService,
@IConfigurationService private _configurationService: IConfigurationService,
@IOpenerService private _openerService: IOpenerService
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IKeybindingService private readonly _keybindingService: IKeybindingService,
@IConfigurationService private readonly _configurationService: IConfigurationService,
@IOpenerService private readonly _openerService: IOpenerService
) {
super();

View File

@@ -9,7 +9,8 @@ import { clipboard } from 'electron';
import * as platform from 'vs/base/common/platform';
import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser';
import { Disposable } from 'vs/base/common/lifecycle';
import { EndOfLinePreference, IEditorContribution } from 'vs/editor/common/editorCommon';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { EndOfLinePreference } from 'vs/editor/common/model';
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { RunOnceScheduler } from 'vs/base/common/async';

View File

@@ -11,23 +11,22 @@ import { Disposable } from 'vs/base/common/lifecycle';
import { escape } from 'vs/base/common/strings';
import { KeyCode } from 'vs/base/common/keyCodes';
import { Position } from 'vs/editor/common/core/position';
import { IEditorContribution, IModel } from 'vs/editor/common/editorCommon';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { ITextModel } from 'vs/editor/common/model';
import { registerEditorAction, registerEditorContribution, EditorAction, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
import { ICodeEditor, ContentWidgetPositionPreference, IContentWidget, IContentWidgetPosition } from 'vs/editor/browser/editorBrowser';
import { TPromise } from 'vs/base/common/winjs.base';
import { IGrammar, StackElement, IToken } from 'vscode-textmate';
import { ITextMateService } from 'vs/workbench/services/textMate/electron-browser/textMateService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { TokenMetadata } from 'vs/editor/common/model/tokensBinaryEncoding';
import { TokenizationRegistry, LanguageIdentifier, FontStyle, StandardTokenType } from 'vs/editor/common/modes';
import { TokenizationRegistry, LanguageIdentifier, FontStyle, StandardTokenType, TokenMetadata } from 'vs/editor/common/modes';
import { CharCode } from 'vs/base/common/charCode';
import { findMatchingThemeRule } from 'vs/workbench/services/textMate/electron-browser/TMHelper';
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
import { Color } from 'vs/base/common/color';
import { IMessageService } from 'vs/platform/message/common/message';
import Severity from 'vs/base/common/severity';
import { registerThemingParticipant, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService';
import { editorHoverBackground, editorHoverBorder } from 'vs/platform/theme/common/colorRegistry';
import { INotificationService } from 'vs/platform/notification/common/notification';
class InspectTMScopesController extends Disposable implements IEditorContribution {
@@ -41,7 +40,7 @@ class InspectTMScopesController extends Disposable implements IEditorContributio
private _textMateService: ITextMateService;
private _themeService: IWorkbenchThemeService;
private _modeService: IModeService;
private _messageService: IMessageService;
private _notificationService: INotificationService;
private _widget: InspectTMScopesWidget;
constructor(
@@ -49,14 +48,14 @@ class InspectTMScopesController extends Disposable implements IEditorContributio
@ITextMateService textMateService: ITextMateService,
@IModeService modeService: IModeService,
@IWorkbenchThemeService themeService: IWorkbenchThemeService,
@IMessageService messageService: IMessageService,
@INotificationService notificationService: INotificationService
) {
super();
this._editor = editor;
this._textMateService = textMateService;
this._themeService = themeService;
this._modeService = modeService;
this._messageService = messageService;
this._notificationService = notificationService;
this._widget = null;
this._register(this._editor.onDidChangeModel((e) => this.stop()));
@@ -80,7 +79,7 @@ class InspectTMScopesController extends Disposable implements IEditorContributio
if (!this._editor.getModel()) {
return;
}
this._widget = new InspectTMScopesWidget(this._editor, this._textMateService, this._modeService, this._themeService, this._messageService);
this._widget = new InspectTMScopesWidget(this._editor, this._textMateService, this._modeService, this._themeService, this._notificationService);
}
public stop(): void {
@@ -179,8 +178,8 @@ class InspectTMScopesWidget extends Disposable implements IContentWidget {
private readonly _editor: ICodeEditor;
private readonly _modeService: IModeService;
private readonly _themeService: IWorkbenchThemeService;
private readonly _messageService: IMessageService;
private readonly _model: IModel;
private readonly _notificationService: INotificationService;
private readonly _model: ITextModel;
private readonly _domNode: HTMLElement;
private readonly _grammar: TPromise<IGrammar>;
@@ -189,14 +188,14 @@ class InspectTMScopesWidget extends Disposable implements IContentWidget {
textMateService: ITextMateService,
modeService: IModeService,
themeService: IWorkbenchThemeService,
messageService: IMessageService
notificationService: INotificationService
) {
super();
this._isDisposed = false;
this._editor = editor;
this._modeService = modeService;
this._themeService = themeService;
this._messageService = messageService;
this._notificationService = notificationService;
this._model = this._editor.getModel();
this._domNode = document.createElement('div');
this._domNode.className = 'tm-inspect-widget';
@@ -222,7 +221,7 @@ class InspectTMScopesWidget extends Disposable implements IContentWidget {
this._grammar.then(
(grammar) => this._compute(grammar, position),
(err) => {
this._messageService.show(Severity.Warning, err);
this._notificationService.warn(err);
setTimeout(() => {
InspectTMScopesController.get(this._editor).stop();
});
@@ -273,7 +272,7 @@ class InspectTMScopesWidget extends Disposable implements IContentWidget {
let theme = this._themeService.getColorTheme();
result += `<hr class="tm-metadata-separator"/>`;
let matchingRule = findMatchingThemeRule(theme, data.tokens1[token1Index].scopes);
let matchingRule = findMatchingThemeRule(theme, data.tokens1[token1Index].scopes, false);
if (matchingRule) {
result += `<code class="tm-theme-selector">${matchingRule.rawSelector}\n${JSON.stringify(matchingRule.settings, null, '\t')}</code>`;
} else {

View File

@@ -4,21 +4,20 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import 'vs/css!./media/codeEditor';
import * as nls from 'vs/nls';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { IEditorContribution, IModel } from 'vs/editor/common/editorCommon';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { ITextModel } from 'vs/editor/common/model';
import { registerEditorAction, ServicesAccessor, EditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { Disposable } from 'vs/base/common/lifecycle';
import { IMessageService } from 'vs/platform/message/common/message';
import Severity from 'vs/base/common/severity';
import URI from 'vs/base/common/uri';
import { InternalEditorOptions, EDITOR_DEFAULTS } from 'vs/editor/common/config/editorOptions';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { INotificationService } from 'vs/platform/notification/common/notification';
const transientWordWrapState = 'transientWordWrapState';
const isWordWrapMinifiedKey = 'isWordWrapMinified';
@@ -42,18 +41,18 @@ interface IWordWrapState {
/**
* Store (in memory) the word wrap state for a particular model.
*/
function writeTransientState(model: IModel, state: IWordWrapTransientState, codeEditorService: ICodeEditorService): void {
function writeTransientState(model: ITextModel, state: IWordWrapTransientState, codeEditorService: ICodeEditorService): void {
codeEditorService.setTransientModelProperty(model, transientWordWrapState, state);
}
/**
* Read (in memory) the word wrap state for a particular model.
*/
function readTransientState(model: IModel, codeEditorService: ICodeEditorService): IWordWrapTransientState {
function readTransientState(model: ITextModel, codeEditorService: ICodeEditorService): IWordWrapTransientState {
return codeEditorService.getTransientModelProperty(model, transientWordWrapState);
}
function readWordWrapState(model: IModel, configurationService: ITextResourceConfigurationService, codeEditorService: ICodeEditorService): IWordWrapState {
function readWordWrapState(model: ITextModel, configurationService: ITextResourceConfigurationService, codeEditorService: ICodeEditorService): IWordWrapState {
const editorConfig = configurationService.getValue(model.uri, 'editor') as { wordWrap: 'on' | 'off' | 'wordWrapColumn' | 'bounded'; wordWrapMinified: boolean };
let _configuredWordWrap = editorConfig && (typeof editorConfig.wordWrap === 'string' || typeof editorConfig.wordWrap === 'boolean') ? editorConfig.wordWrap : void 0;
@@ -150,8 +149,8 @@ class ToggleWordWrapAction extends EditorAction {
const editorConfiguration = editor.getConfiguration();
if (editorConfiguration.wrappingInfo.inDiffEditor) {
// Cannot change wrapping settings inside the diff editor
const messageService = accessor.get(IMessageService);
messageService.show(Severity.Info, nls.localize('wordWrap.notInDiffEditor', "Cannot toggle word wrap in a diff editor."));
const notificationService = accessor.get(INotificationService);
notificationService.info(nls.localize('wordWrap.notInDiffEditor', "Cannot toggle word wrap in a diff editor."));
return;
}
@@ -258,7 +257,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
command: {
id: 'editor.action.toggleWordWrap',
title: nls.localize('unwrapMinified', "Disable wrapping for this file"),
iconClass: 'toggle-word-wrap-action'
iconPath: { dark: URI.parse(require.toUrl('vs/workbench/parts/codeEditor/electron-browser/media/WordWrap_16x.svg')).fsPath }
},
group: 'navigation',
order: 1,
@@ -272,7 +271,7 @@ MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
command: {
id: 'editor.action.toggleWordWrap',
title: nls.localize('wrapMinified', "Enable wrapping for this file"),
iconClass: 'toggle-word-wrap-action'
iconPath: { dark: URI.parse(require.toUrl('vs/workbench/parts/codeEditor/electron-browser/media/WordWrap_16x.svg')).fsPath }
},
group: 'navigation',
order: 1,

View File

@@ -1,143 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as nls from 'vs/nls';
import { Disposable } from 'vs/base/common/lifecycle';
import { TPromise } from 'vs/base/common/winjs.base';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IMessageService } from 'vs/platform/message/common/message';
import { IPreferencesService } from 'vs/workbench/parts/preferences/common/preferences';
import { Action } from 'vs/base/common/actions';
import Severity from 'vs/base/common/severity';
interface IStorageData {
dontShowPrompt: boolean;
}
class WordWrapMigrationStorage {
private static readonly KEY = 'wordWrapMigration';
private _storageService: IStorageService;
private _value: IStorageData;
constructor(storageService: IStorageService) {
this._storageService = storageService;
this._value = this._read();
}
private _read(): IStorageData {
let jsonValue = this._storageService.get(WordWrapMigrationStorage.KEY, StorageScope.GLOBAL);
if (!jsonValue) {
return null;
}
try {
return JSON.parse(jsonValue);
} catch (err) {
return null;
}
}
public get(): IStorageData {
return this._value;
}
public set(data: IStorageData): void {
this._value = data;
this._storageService.store(WordWrapMigrationStorage.KEY, JSON.stringify(this._value), StorageScope.GLOBAL);
}
}
class WordWrapMigrationController extends Disposable implements IEditorContribution {
private static readonly ID = 'editor.contrib.wordWrapMigrationController';
private static _checked = false;
constructor(
editor: ICodeEditor,
@IConfigurationService private configurationService: IConfigurationService,
@IMessageService private messageService: IMessageService,
@IStorageService private storageService: IStorageService,
@IPreferencesService private preferencesService: IPreferencesService
) {
super();
this._promptIfNecessary();
}
public getId(): string {
return WordWrapMigrationController.ID;
}
private _promptIfNecessary(): void {
if (WordWrapMigrationController._checked) {
// Already checked
return;
}
WordWrapMigrationController._checked = true;
let result = this.configurationService.inspect('editor.wrappingColumn');
if (typeof result.value === 'undefined') {
// Setting is not used
return;
}
const storage = new WordWrapMigrationStorage(this.storageService);
const storedData = storage.get();
if (storedData && storedData.dontShowPrompt) {
// Do not prompt stored
return;
}
let isUserSetting = (typeof result.user !== 'undefined');
this._prompt(storage, isUserSetting);
}
private _prompt(storage: WordWrapMigrationStorage, userSettings: boolean): void {
const okAction = new Action(
'wordWrapMigration.ok',
nls.localize('wordWrapMigration.ok', "OK"),
null,
true,
() => TPromise.as(true)
);
const dontShowAgainAction = new Action(
'wordWrapMigration.dontShowAgain',
nls.localize('wordWrapMigration.dontShowAgain', "Don't show again"),
null,
true,
() => {
storage.set({
dontShowPrompt: true
});
return TPromise.as(true);
}
);
const openSettings = new Action(
'wordWrapMigration.openSettings',
nls.localize('wordWrapMigration.openSettings', "Open Settings"),
null,
true,
() => {
if (userSettings) {
this.preferencesService.openGlobalSettings();
} else {
this.preferencesService.openWorkspaceSettings();
}
return TPromise.as(true);
}
);
this.messageService.show(Severity.Info, {
message: nls.localize('wordWrapMigration.prompt', "The setting `editor.wrappingColumn` has been deprecated in favor of `editor.wordWrap`."),
actions: [okAction, openSettings, dontShowAgainAction]
});
}
}
registerEditorContribution(WordWrapMigrationController);

View File

@@ -5,7 +5,7 @@
import * as dom from 'vs/base/browser/dom';
import { IExpression, IDebugService, IEnablement } from 'vs/workbench/parts/debug/common/debug';
import { Expression, FunctionBreakpoint, Variable } from 'vs/workbench/parts/debug/common/debugModel';
import { Expression, Variable } from 'vs/workbench/parts/debug/common/debugModel';
import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { ITree, ContextMenuEvent, IActionProvider } from 'vs/base/parts/tree/browser/tree';
@@ -13,14 +13,15 @@ import { InputBox, IInputValidationOptions } from 'vs/base/browser/ui/inputbox/i
import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { once } from 'vs/base/common/functional';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IMenuService, MenuId, IMenu } from 'vs/platform/actions/common/actions';
import { ClickBehavior, DefaultController } from 'vs/base/parts/tree/browser/treeDefaults';
import { IControllerOptions } from 'vs/base/parts/tree/browser/treeDefaults';
import { fillInActions } from 'vs/platform/actions/browser/menuItemActionItem';
import { KeyCode } from 'vs/base/common/keyCodes';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { onUnexpectedError } from 'vs/base/common/errors';
import { WorkbenchTreeController } from 'vs/platform/list/browser/listService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
export const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024;
export const twistiePixels = 20;
@@ -135,7 +136,6 @@ export function renderRenameBox(debugService: IDebugService, contextViewService:
});
const styler = attachInputBoxStyler(inputBox, themeService);
tree.setHighlight();
inputBox.value = options.initialValue ? options.initialValue : '';
inputBox.focus();
inputBox.select();
@@ -147,13 +147,11 @@ export function renderRenameBox(debugService: IDebugService, contextViewService:
if (!disposed) {
disposed = true;
if (element instanceof Expression && renamed && inputBox.value) {
debugService.renameWatchExpression(element.getId(), inputBox.value).done(null, onUnexpectedError);
debugService.renameWatchExpression(element.getId(), inputBox.value);
debugService.getViewModel().setSelectedExpression(undefined);
} else if (element instanceof Expression && !element.name) {
debugService.removeWatchExpressions(element.getId());
} else if (element instanceof FunctionBreakpoint && inputBox.value) {
debugService.renameFunctionBreakpoint(element.getId(), renamed ? inputBox.value : element.name).done(null, onUnexpectedError);
} else if (element instanceof FunctionBreakpoint && !element.name) {
debugService.removeFunctionBreakpoints(element.getId()).done(null, onUnexpectedError);
debugService.getViewModel().setSelectedExpression(undefined);
} else if (element instanceof Variable) {
element.errorMessage = null;
if (renamed && element.value !== inputBox.value) {
@@ -161,12 +159,12 @@ export function renderRenameBox(debugService: IDebugService, contextViewService:
// if everything went fine we need to refresh ui elements since the variable update can change watch and variables view
.done(() => {
tree.refresh(element, false);
debugService.evaluateWatchExpressions();
// Need to force watch expressions to update since a variable change can have an effect on watches
debugService.focusStackFrame(debugService.getViewModel().focusedStackFrame);
}, onUnexpectedError);
}
}
tree.clearHighlight();
tree.DOMFocus();
tree.setFocus(element);
@@ -190,20 +188,21 @@ export function renderRenameBox(debugService: IDebugService, contextViewService:
}));
}
export class BaseDebugController extends DefaultController {
export class BaseDebugController extends WorkbenchTreeController {
private contributedContextMenu: IMenu;
constructor(
private actionProvider: IActionProvider,
menuId: MenuId,
options: IControllerOptions,
@IDebugService protected debugService: IDebugService,
@IWorkbenchEditorService protected editorService: IWorkbenchEditorService,
@IContextMenuService private contextMenuService: IContextMenuService,
@IContextKeyService contextKeyService: IContextKeyService,
@IMenuService menuService: IMenuService
@IMenuService menuService: IMenuService,
@IConfigurationService configurationService: IConfigurationService
) {
super({ clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: false });
super(options, configurationService);
this.contributedContextMenu = menuService.createMenu(menuId, contextKeyService);
}
@@ -225,7 +224,7 @@ export class BaseDebugController extends DefaultController {
this.contextMenuService.showContextMenu({
getAnchor: () => anchor,
getActions: () => this.actionProvider.getSecondaryActions(tree, element).then(actions => {
fillInActions(this.contributedContextMenu, { arg: this.getContext(element) }, actions);
fillInActions(this.contributedContextMenu, { arg: this.getContext(element) }, actions, this.contextMenuService);
return actions;
}),
onHide: (wasCancelled?: boolean) => {

View File

@@ -15,7 +15,7 @@ import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IDebugService, IBreakpoint, IRawBreakpoint } from 'vs/workbench/parts/debug/common/debug';
import { IDebugService, IBreakpoint } from 'vs/workbench/parts/debug/common/debug';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { once } from 'vs/base/common/functional';
import { attachInputBoxStyler, attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
@@ -71,7 +71,7 @@ export class BreakpointWidget extends ZoneWidget {
this.hitCountContext = breakpoint && breakpoint.hitCondition && !breakpoint.condition;
const selected = this.hitCountContext ? 1 : 0;
const selectBox = new SelectBox([nls.localize('expression', "Expression"), nls.localize('hitCount', "Hit Count")], selected);
const selectBox = new SelectBox([nls.localize('expression', "Expression"), nls.localize('hitCount', "Hit Count")], selected, this.contextViewService);
this.toDispose.push(attachSelectBoxStyler(selectBox, this.themeService));
selectBox.render(dom.append(container, $('.breakpoint-select-container')));
selectBox.onDidSelect(e => {
@@ -109,31 +109,38 @@ export class BreakpointWidget extends ZoneWidget {
const oldBreakpoint = this.debugService.getModel().getBreakpoints()
.filter(bp => bp.lineNumber === this.lineNumber && bp.column === this.column && bp.uri.toString() === uri.toString()).pop();
const raw: IRawBreakpoint = {
lineNumber: this.lineNumber,
column: oldBreakpoint ? oldBreakpoint.column : undefined,
enabled: true,
condition: oldBreakpoint && oldBreakpoint.condition,
hitCondition: oldBreakpoint && oldBreakpoint.hitCondition
};
let condition = oldBreakpoint && oldBreakpoint.condition;
let hitCondition = oldBreakpoint && oldBreakpoint.hitCondition;
if (this.hitCountContext) {
raw.hitCondition = this.inputBox.value;
hitCondition = this.inputBox.value;
if (this.conditionInput) {
raw.condition = this.conditionInput;
condition = this.conditionInput;
}
} else {
raw.condition = this.inputBox.value;
condition = this.inputBox.value;
if (this.hitCountInput) {
raw.hitCondition = this.hitCountInput;
hitCondition = this.hitCountInput;
}
}
if (oldBreakpoint) {
this.debugService.removeBreakpoints(oldBreakpoint.getId()).done(null, errors.onUnexpectedError);
this.debugService.updateBreakpoints(oldBreakpoint.uri, {
[oldBreakpoint.getId()]: {
condition,
hitCondition,
verified: oldBreakpoint.verified
}
}, false);
} else {
this.debugService.addBreakpoints(uri, [{
lineNumber: this.lineNumber,
column: oldBreakpoint ? oldBreakpoint.column : undefined,
enabled: true,
condition,
hitCondition
}]).done(null, errors.onUnexpectedError);
}
this.debugService.addBreakpoints(uri, [raw]).done(null, errors.onUnexpectedError);
}
this.dispose();

View File

@@ -26,13 +26,14 @@ import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { IDelegate, IListContextMenuEvent, IRenderer } from 'vs/base/browser/ui/list/list';
import { IEditorService, IEditor } from 'vs/platform/editor/common/editor';
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeyCode } from 'vs/base/common/keyCodes';
import { WorkbenchList, IListService } from 'vs/platform/list/browser/listService';
import { WorkbenchList } from 'vs/platform/list/browser/listService';
import { ViewsViewletPanel, IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
const $ = dom.$;
@@ -50,13 +51,12 @@ export class BreakpointsView extends ViewsViewletPanel {
@IDebugService private debugService: IDebugService,
@IKeybindingService keybindingService: IKeybindingService,
@IInstantiationService private instantiationService: IInstantiationService,
@IListService private listService: IListService,
@IThemeService private themeService: IThemeService,
@IEditorService private editorService: IEditorService,
@IContextViewService private contextViewService: IContextViewService,
@IContextKeyService private contextKeyService: IContextKeyService
@IConfigurationService configurationService: IConfigurationService
) {
super(options, keybindingService, contextMenuService);
super(options, keybindingService, contextMenuService, configurationService);
this.minimumBodySize = this.maximumBodySize = this.getExpandedBodySize();
this.settings = options.viewletSettings;
@@ -67,42 +67,41 @@ export class BreakpointsView extends ViewsViewletPanel {
dom.addClass(container, 'debug-breakpoints');
const delegate = new BreakpointsDelegate(this.debugService);
this.list = new WorkbenchList<IEnablement>(container, delegate, [
this.list = this.instantiationService.createInstance(WorkbenchList, container, delegate, [
this.instantiationService.createInstance(BreakpointsRenderer),
new ExceptionBreakpointsRenderer(this.debugService),
new FunctionBreakpointsRenderer(this.debugService),
this.instantiationService.createInstance(FunctionBreakpointsRenderer),
new FunctionBreakpointInputRenderer(this.debugService, this.contextViewService, this.themeService)
], {
identityProvider: element => element.getId(),
multipleSelectionSupport: false
}, this.contextKeyService, this.listService, this.themeService);
}) as WorkbenchList<IEnablement>;
CONTEXT_BREAKPOINTS_FOCUSED.bindTo(this.list.contextKeyService);
this.list.onContextMenu(this.onListContextMenu, this, this.disposables);
const handleBreakpointFocus = (preserveFocuse: boolean, sideBySide: boolean, selectFunctionBreakpoint: boolean) => {
this.disposables.push(this.list.onOpen(e => {
let isSingleClick = false;
let isDoubleClick = false;
let openToSide = false;
const browserEvent = e.browserEvent;
if (browserEvent instanceof MouseEvent) {
isSingleClick = browserEvent.detail === 1;
isDoubleClick = browserEvent.detail === 2;
openToSide = (browserEvent.ctrlKey || browserEvent.metaKey || browserEvent.altKey);
}
const focused = this.list.getFocusedElements();
const element = focused.length ? focused[0] : undefined;
if (element instanceof Breakpoint) {
openBreakpointSource(element, sideBySide, preserveFocuse, this.debugService, this.editorService).done(undefined, onUnexpectedError);
openBreakpointSource(element, openToSide, isSingleClick, this.debugService, this.editorService).done(undefined, onUnexpectedError);
}
if (selectFunctionBreakpoint && element instanceof FunctionBreakpoint && element !== this.debugService.getViewModel().getSelectedFunctionBreakpoint()) {
if (isDoubleClick && element instanceof FunctionBreakpoint && element !== this.debugService.getViewModel().getSelectedFunctionBreakpoint()) {
this.debugService.getViewModel().setSelectedFunctionBreakpoint(element);
this.onBreakpointsChange();
}
};
this.disposables.push(this.list.onKeyUp(e => {
const event = new StandardKeyboardEvent(e);
if (event.equals(KeyCode.Enter)) {
handleBreakpointFocus(false, event && (event.ctrlKey || event.metaKey), false);
}
}));
this.disposables.push(this.list.onMouseDblClick(e => {
handleBreakpointFocus(false, false, true);
}));
this.disposables.push(this.list.onMouseClick(e => {
handleBreakpointFocus(true, false, false);
}));
this.list.splice(0, this.list.length, this.elements);
@@ -118,14 +117,20 @@ export class BreakpointsView extends ViewsViewletPanel {
const actions: IAction[] = [];
const element = e.element;
if (element instanceof Breakpoint) {
if (element instanceof Breakpoint || element instanceof FunctionBreakpoint) {
actions.push(new Action('workbench.action.debug.openEditorAndEditBreakpoint', nls.localize('editConditionalBreakpoint', "Edit Breakpoint..."), undefined, true, () => {
return openBreakpointSource(element, false, false, this.debugService, this.editorService).then(editor => {
const codeEditor = editor.getControl();
if (isCodeEditor(codeEditor)) {
codeEditor.getContribution<IDebugEditorContribution>(EDITOR_CONTRIBUTION_ID).showBreakpointWidget(element.lineNumber, element.column);
}
});
if (element instanceof Breakpoint) {
return openBreakpointSource(element, false, false, this.debugService, this.editorService).then(editor => {
const codeEditor = editor.getControl();
if (isCodeEditor(codeEditor)) {
codeEditor.getContribution<IDebugEditorContribution>(EDITOR_CONTRIBUTION_ID).showBreakpointWidget(element.lineNumber, element.column);
}
});
}
this.debugService.getViewModel().setSelectedFunctionBreakpoint(element);
this.onBreakpointsChange();
return undefined;
}));
actions.push(new Separator());
}
@@ -245,7 +250,11 @@ interface IBaseBreakpointTemplateData {
toDispose: IDisposable[];
}
interface IBreakpointTemplateData extends IBaseBreakpointTemplateData {
interface IBaseBreakpointWithIconTemplateData extends IBaseBreakpointTemplateData {
icon: HTMLElement;
}
interface IBreakpointTemplateData extends IBaseBreakpointWithIconTemplateData {
lineNumber: HTMLElement;
filePath: HTMLElement;
}
@@ -262,12 +271,13 @@ class BreakpointsRenderer implements IRenderer<IBreakpoint, IBreakpointTemplateD
constructor(
@IDebugService private debugService: IDebugService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IEnvironmentService private environmentService: IEnvironmentService
@IEnvironmentService private environmentService: IEnvironmentService,
@ITextFileService private textFileService: ITextFileService
) {
// noop
}
static ID = 'breakpoints';
static readonly ID = 'breakpoints';
get templateId() {
return BreakpointsRenderer.ID;
@@ -277,6 +287,7 @@ class BreakpointsRenderer implements IRenderer<IBreakpoint, IBreakpointTemplateD
const data: IBreakpointTemplateData = Object.create(null);
data.breakpoint = dom.append(container, $('.breakpoint'));
data.icon = $('.icon');
data.checkbox = <HTMLInputElement>$('input');
data.checkbox.type = 'checkbox';
data.toDispose = [];
@@ -284,6 +295,7 @@ class BreakpointsRenderer implements IRenderer<IBreakpoint, IBreakpointTemplateD
this.debugService.enableOrDisableBreakpoints(!data.context.enabled, data.context);
}));
dom.append(data.breakpoint, data.icon);
dom.append(data.breakpoint, data.checkbox);
data.name = dom.append(data.breakpoint, $('span.name'));
@@ -307,6 +319,10 @@ class BreakpointsRenderer implements IRenderer<IBreakpoint, IBreakpointTemplateD
data.filePath.textContent = getPathLabel(resources.dirname(breakpoint.uri), this.contextService, this.environmentService);
data.checkbox.checked = breakpoint.enabled;
const { message, className } = getBreakpointMessageAndClassName(this.debugService, this.textFileService, breakpoint);
data.icon.className = className + ' icon';
data.icon.title = message ? message : '';
const debugActive = this.debugService.state === State.Running || this.debugService.state === State.Stopped;
if (debugActive && !breakpoint.verified) {
dom.addClass(data.breakpoint, 'disabled');
@@ -331,7 +347,7 @@ class ExceptionBreakpointsRenderer implements IRenderer<IExceptionBreakpoint, IB
// noop
}
static ID = 'exceptionbreakpoints';
static readonly ID = 'exceptionbreakpoints';
get templateId() {
return ExceptionBreakpointsRenderer.ID;
@@ -368,24 +384,26 @@ class ExceptionBreakpointsRenderer implements IRenderer<IExceptionBreakpoint, IB
}
}
class FunctionBreakpointsRenderer implements IRenderer<IFunctionBreakpoint, IBaseBreakpointTemplateData> {
class FunctionBreakpointsRenderer implements IRenderer<FunctionBreakpoint, IBaseBreakpointWithIconTemplateData> {
constructor(
private debugService: IDebugService
@IDebugService private debugService: IDebugService,
@ITextFileService private textFileService: ITextFileService
) {
// noop
}
static ID = 'functionbreakpoints';
static readonly ID = 'functionbreakpoints';
get templateId() {
return FunctionBreakpointsRenderer.ID;
}
renderTemplate(container: HTMLElement): IBaseBreakpointTemplateData {
renderTemplate(container: HTMLElement): IBaseBreakpointWithIconTemplateData {
const data: IBreakpointTemplateData = Object.create(null);
data.breakpoint = dom.append(container, $('.breakpoint'));
data.icon = $('.icon');
data.checkbox = <HTMLInputElement>$('input');
data.checkbox.type = 'checkbox';
data.toDispose = [];
@@ -393,6 +411,7 @@ class FunctionBreakpointsRenderer implements IRenderer<IFunctionBreakpoint, IBas
this.debugService.enableOrDisableBreakpoints(!data.context.enabled, data.context);
}));
dom.append(data.breakpoint, data.icon);
dom.append(data.breakpoint, data.checkbox);
data.name = dom.append(data.breakpoint, $('span.name'));
@@ -400,9 +419,12 @@ class FunctionBreakpointsRenderer implements IRenderer<IFunctionBreakpoint, IBas
return data;
}
renderElement(functionBreakpoint: IFunctionBreakpoint, index: number, data: IBaseBreakpointTemplateData): void {
renderElement(functionBreakpoint: FunctionBreakpoint, index: number, data: IBaseBreakpointWithIconTemplateData): void {
data.context = functionBreakpoint;
data.name.textContent = functionBreakpoint.name;
const { className, message } = getBreakpointMessageAndClassName(this.debugService, this.textFileService, functionBreakpoint);
data.icon.className = className + ' icon';
data.icon.title = message ? message : '';
data.checkbox.checked = functionBreakpoint.enabled;
data.breakpoint.title = functionBreakpoint.name;
@@ -414,7 +436,7 @@ class FunctionBreakpointsRenderer implements IRenderer<IFunctionBreakpoint, IBas
}
}
disposeTemplate(templateData: IBaseBreakpointTemplateData): void {
disposeTemplate(templateData: IBaseBreakpointWithIconTemplateData): void {
dispose(templateData.toDispose);
}
}
@@ -429,7 +451,7 @@ class FunctionBreakpointInputRenderer implements IRenderer<IFunctionBreakpoint,
// noop
}
static ID = 'functionbreakpointinput';
static readonly ID = 'functionbreakpointinput';
get templateId() {
return FunctionBreakpointInputRenderer.ID;
@@ -467,7 +489,9 @@ class FunctionBreakpointInputRenderer implements IRenderer<IFunctionBreakpoint,
}
}));
toDispose.push(dom.addDisposableListener(inputBox.inputElement, 'blur', () => {
wrapUp(true);
if (!template.breakpoint.name) {
wrapUp(true);
}
}));
template.inputBox = inputBox;
@@ -488,7 +512,7 @@ class FunctionBreakpointInputRenderer implements IRenderer<IFunctionBreakpoint,
}
}
function openBreakpointSource(breakpoint: Breakpoint, sideBySide: boolean, preserveFocus: boolean, debugService: IDebugService, editorService: IEditorService): TPromise<IEditor> {
export function openBreakpointSource(breakpoint: Breakpoint, sideBySide: boolean, preserveFocus: boolean, debugService: IDebugService, editorService: IEditorService): TPromise<IEditor> {
if (breakpoint.uri.scheme === DEBUG_SCHEME && debugService.state === State.Inactive) {
return TPromise.as(null);
}
@@ -516,3 +540,78 @@ function openBreakpointSource(breakpoint: Breakpoint, sideBySide: boolean, prese
}
}, sideBySide);
}
export function getBreakpointMessageAndClassName(debugService: IDebugService, textFileService: ITextFileService, breakpoint: IBreakpoint | FunctionBreakpoint): { message?: string, className: string } {
const state = debugService.state;
const debugActive = state === State.Running || state === State.Stopped;
if (!breakpoint.enabled || !debugService.getModel().areBreakpointsActivated()) {
return {
className: breakpoint instanceof FunctionBreakpoint ? 'debug-function-breakpoint-disabled' : 'debug-breakpoint-disabled',
message: nls.localize('breakpointDisabledHover', "Disabled breakpoint"),
};
}
const appendMessage = (text: string): string => {
return !(breakpoint instanceof FunctionBreakpoint) && breakpoint.message ? text.concat(', ' + breakpoint.message) : text;
};
if (debugActive && !breakpoint.verified) {
return {
className: breakpoint instanceof FunctionBreakpoint ? 'debug-function-breakpoint-unverified' : 'debug-breakpoint-unverified',
message: appendMessage(nls.localize('breakpointUnverifieddHover', "Unverified breakpoint")),
};
}
const process = debugService.getViewModel().focusedProcess;
if (breakpoint instanceof FunctionBreakpoint) {
if (process && !process.session.capabilities.supportsFunctionBreakpoints) {
return {
className: 'debug-function-breakpoint-unverified',
message: nls.localize('functionBreakpointUnsupported', "Function breakpoints not supported by this debug type"),
};
}
return {
className: 'debug-function-breakpoint',
};
}
if (debugActive && textFileService.isDirty(breakpoint.uri)) {
return {
className: 'debug-breakpoint-unverified',
message: appendMessage(nls.localize('breakpointDirtydHover', "Unverified breakpoint. File is modified, please restart debug session.")),
};
}
if (breakpoint.condition || breakpoint.hitCondition) {
if (process && breakpoint.condition && !process.session.capabilities.supportsConditionalBreakpoints) {
return {
className: 'debug-breakpoint-unsupported',
message: nls.localize('conditionalBreakpointUnsupported', "Conditional breakpoints not supported by this debug type"),
};
}
if (process && breakpoint.hitCondition && !process.session.capabilities.supportsHitConditionalBreakpoints) {
return {
className: 'debug-breakpoint-unsupported',
message: nls.localize('hitBreakpointUnsupported', "Hit conditional breakpoints not supported by this debug type"),
};
}
if (breakpoint.condition && breakpoint.hitCondition) {
return {
className: 'debug-breakpoint-conditional',
message: appendMessage(`Expression: ${breakpoint.condition}\nHitCount: ${breakpoint.hitCondition}`)
};
}
return {
className: 'debug-breakpoint-conditional',
message: appendMessage(breakpoint.condition ? breakpoint.condition : breakpoint.hitCondition)
};
}
return {
className: 'debug-breakpoint',
message: breakpoint.message
};
}

View File

@@ -19,6 +19,8 @@ import { IThemeService } from 'vs/platform/theme/common/themeService';
import { attachSelectBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler';
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
import { selectBorder } from 'vs/platform/theme/common/colorRegistry';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
const $ = dom.$;
@@ -40,10 +42,12 @@ export class StartDebugActionItem implements IActionItem {
@IDebugService private debugService: IDebugService,
@IThemeService private themeService: IThemeService,
@IConfigurationService private configurationService: IConfigurationService,
@ICommandService private commandService: ICommandService
@ICommandService private commandService: ICommandService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IContextViewService contextViewService: IContextViewService,
) {
this.toDispose = [];
this.selectBox = new SelectBox([], -1);
this.selectBox = new SelectBox([], -1, contextViewService);
this.toDispose.push(attachSelectBoxStyler(this.selectBox, themeService, {
selectBackground: SIDE_BAR_BACKGROUND
}));
@@ -151,12 +155,13 @@ export class StartDebugActionItem implements IActionItem {
this.options = [];
const manager = this.debugService.getConfigurationManager();
const launches = manager.getLaunches();
manager.getLaunches().forEach(launch =>
const inWorkspace = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE;
launches.forEach(launch =>
launch.getConfigurationNames().forEach(name => {
if (name === manager.selectedName && launch === manager.selectedLaunch) {
if (name === manager.selectedConfiguration.name && launch === manager.selectedConfiguration.launch) {
this.selected = this.options.length;
}
const label = launches.length > 1 ? `${name} (${launch.workspace.name})` : name;
const label = inWorkspace ? `${name} (${launch.name})` : name;
this.options.push({ label, handler: () => { manager.selectConfiguration(launch, name); return true; } });
}));
@@ -166,11 +171,11 @@ export class StartDebugActionItem implements IActionItem {
this.options.push({ label: StartDebugActionItem.SEPARATOR, handler: undefined });
const disabledIdx = this.options.length - 1;
launches.forEach(l => {
const label = launches.length > 1 ? nls.localize("addConfigTo", "Add Config ({0})...", l.workspace.name) : nls.localize('addConfiguration', "Add Configuration...");
launches.filter(l => !l.hidden).forEach(l => {
const label = inWorkspace ? nls.localize("addConfigTo", "Add Config ({0})...", l.name) : nls.localize('addConfiguration', "Add Configuration...");
this.options.push({
label, handler: () => {
this.commandService.executeCommand('debug.addConfiguration', l.workspace.uri.toString()).done(undefined, errors.onUnexpectedError);
this.commandService.executeCommand('debug.addConfiguration', l.uri.toString()).done(undefined, errors.onUnexpectedError);
return false;
}
});
@@ -184,9 +189,10 @@ export class FocusProcessActionItem extends SelectActionItem {
constructor(
action: IAction,
@IDebugService private debugService: IDebugService,
@IThemeService themeService: IThemeService
@IThemeService themeService: IThemeService,
@IContextViewService contextViewService: IContextViewService
) {
super(null, action, [], -1);
super(null, action, [], -1, contextViewService);
this.toDispose.push(attachSelectBoxStyler(this.selectBox, themeService));

View File

@@ -6,14 +6,12 @@
import * as nls from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import * as lifecycle from 'vs/base/common/lifecycle';
import severity from 'vs/base/common/severity';
import { TPromise } from 'vs/base/common/winjs.base';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IFileService } from 'vs/platform/files/common/files';
import { IMessageService } from 'vs/platform/message/common/message';
import { IDebugService, State, IProcess, IThread, IEnablement, IBreakpoint, IStackFrame, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID, IExpression, REPL_ID, ProcessState }
from 'vs/workbench/parts/debug/common/debug';
import { Variable, Expression, Thread, Breakpoint, Process } from 'vs/workbench/parts/debug/common/debugModel';
@@ -22,6 +20,9 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { TogglePanelAction } from 'vs/workbench/browser/panel';
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { CollapseAction } from 'vs/workbench/browser/viewlet';
import { ITree } from 'vs/base/parts/tree/browser/tree';
export abstract class AbstractDebugAction extends Action {
@@ -71,13 +72,14 @@ export abstract class AbstractDebugAction extends Action {
}
export class ConfigureAction extends AbstractDebugAction {
static ID = 'workbench.action.debug.configure';
static readonly ID = 'workbench.action.debug.configure';
static LABEL = nls.localize('openLaunchJson', "Open {0}", 'launch.json');
constructor(id: string, label: string,
@IDebugService debugService: IDebugService,
@IKeybindingService keybindingService: IKeybindingService,
@IMessageService private messageService: IMessageService
@INotificationService private notificationService: INotificationService,
@IWorkspaceContextService private contextService: IWorkspaceContextService
) {
super(id, label, 'debug-action configure', debugService, keybindingService);
this.toDispose.push(debugService.getConfigurationManager().onDidSelectConfiguration(() => this.updateClass()));
@@ -85,7 +87,7 @@ export class ConfigureAction extends AbstractDebugAction {
}
public get tooltip(): string {
if (this.debugService.getConfigurationManager().selectedName) {
if (this.debugService.getConfigurationManager().selectedConfiguration.name) {
return ConfigureAction.LABEL;
}
@@ -93,17 +95,17 @@ export class ConfigureAction extends AbstractDebugAction {
}
private updateClass(): void {
this.class = this.debugService.getConfigurationManager().selectedName ? 'debug-action configure' : 'debug-action configure notification';
this.class = this.debugService.getConfigurationManager().selectedConfiguration.name ? 'debug-action configure' : 'debug-action configure notification';
}
public run(event?: any): TPromise<any> {
if (!this.debugService.getConfigurationManager().selectedLaunch) {
this.messageService.show(severity.Info, nls.localize('noFolderDebugConfig', "Please first open a folder in order to do advanced debug configuration."));
if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
this.notificationService.info(nls.localize('noFolderDebugConfig', "Please first open a folder in order to do advanced debug configuration."));
return TPromise.as(null);
}
const sideBySide = !!(event && (event.ctrlKey || event.metaKey));
return this.debugService.getConfigurationManager().selectedLaunch.openConfigFile(sideBySide);
return this.debugService.getConfigurationManager().selectedConfiguration.launch.openConfigFile(sideBySide);
}
}
@@ -124,8 +126,8 @@ export class StartAction extends AbstractDebugAction {
}
public run(): TPromise<any> {
const launch = this.debugService.getConfigurationManager().selectedLaunch;
return this.debugService.startDebugging(launch ? launch.workspace : undefined, undefined, this.isNoDebug());
const launch = this.debugService.getConfigurationManager().selectedConfiguration.launch;
return this.debugService.startDebugging(launch, undefined, this.isNoDebug());
}
protected isNoDebug(): boolean {
@@ -134,7 +136,7 @@ export class StartAction extends AbstractDebugAction {
public static isEnabled(debugService: IDebugService, contextService: IWorkspaceContextService, configName: string) {
const processes = debugService.getModel().getProcesses();
const launch = debugService.getConfigurationManager().selectedLaunch;
const launch = debugService.getConfigurationManager().selectedConfiguration.launch;
if (debugService.state === State.Initializing) {
return false;
@@ -142,7 +144,7 @@ export class StartAction extends AbstractDebugAction {
if (contextService && contextService.getWorkbenchState() === WorkbenchState.EMPTY && processes.length > 0) {
return false;
}
if (processes.some(p => p.getName(false) === configName && (!launch || p.session.root.uri.toString() === launch.workspace.uri.toString()))) {
if (processes.some(p => p.getName(false) === configName && (!launch || !launch.workspace || !p.session.root || p.session.root.uri.toString() === launch.workspace.uri.toString()))) {
return false;
}
const compound = launch && launch.getCompound(configName);
@@ -155,12 +157,12 @@ export class StartAction extends AbstractDebugAction {
// Disabled if the launch drop down shows the launch config that is already running.
protected isEnabled(state: State): boolean {
return StartAction.isEnabled(this.debugService, this.contextService, this.debugService.getConfigurationManager().selectedName);
return StartAction.isEnabled(this.debugService, this.contextService, this.debugService.getConfigurationManager().selectedConfiguration.name);
}
}
export class RunAction extends StartAction {
static ID = 'workbench.action.debug.run';
static readonly ID = 'workbench.action.debug.run';
static LABEL = nls.localize('startWithoutDebugging', "Start Without Debugging");
protected isNoDebug(): boolean {
@@ -169,7 +171,7 @@ export class RunAction extends StartAction {
}
export class SelectAndStartAction extends AbstractDebugAction {
static ID = 'workbench.action.debug.selectandstart';
static readonly ID = 'workbench.action.debug.selectandstart';
static LABEL = nls.localize('selectAndStartDebugging', "Select and Start Debugging");
constructor(id: string, label: string,
@@ -190,7 +192,7 @@ export class SelectAndStartAction extends AbstractDebugAction {
}
export class RestartAction extends AbstractDebugAction {
static ID = 'workbench.action.debug.restart';
static readonly ID = 'workbench.action.debug.restart';
static LABEL = nls.localize('restartDebug', "Restart");
static RECONNECT_LABEL = nls.localize('reconnectDebug', "Reconnect");
@@ -224,7 +226,7 @@ export class RestartAction extends AbstractDebugAction {
}
export class StepOverAction extends AbstractDebugAction {
static ID = 'workbench.action.debug.stepOver';
static readonly ID = 'workbench.action.debug.stepOver';
static LABEL = nls.localize('stepOverDebug', "Step Over");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
@@ -245,7 +247,7 @@ export class StepOverAction extends AbstractDebugAction {
}
export class StepIntoAction extends AbstractDebugAction {
static ID = 'workbench.action.debug.stepInto';
static readonly ID = 'workbench.action.debug.stepInto';
static LABEL = nls.localize('stepIntoDebug', "Step Into");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
@@ -266,7 +268,7 @@ export class StepIntoAction extends AbstractDebugAction {
}
export class StepOutAction extends AbstractDebugAction {
static ID = 'workbench.action.debug.stepOut';
static readonly ID = 'workbench.action.debug.stepOut';
static LABEL = nls.localize('stepOutDebug', "Step Out");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
@@ -287,7 +289,7 @@ export class StepOutAction extends AbstractDebugAction {
}
export class StopAction extends AbstractDebugAction {
static ID = 'workbench.action.debug.stop';
static readonly ID = 'workbench.action.debug.stop';
static LABEL = nls.localize('stopDebug', "Stop");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
@@ -308,7 +310,7 @@ export class StopAction extends AbstractDebugAction {
}
export class DisconnectAction extends AbstractDebugAction {
static ID = 'workbench.action.debug.disconnect';
static readonly ID = 'workbench.action.debug.disconnect';
static LABEL = nls.localize('disconnectDebug', "Disconnect");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
@@ -326,7 +328,7 @@ export class DisconnectAction extends AbstractDebugAction {
}
export class ContinueAction extends AbstractDebugAction {
static ID = 'workbench.action.debug.continue';
static readonly ID = 'workbench.action.debug.continue';
static LABEL = nls.localize('continueDebug', "Continue");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
@@ -347,7 +349,7 @@ export class ContinueAction extends AbstractDebugAction {
}
export class PauseAction extends AbstractDebugAction {
static ID = 'workbench.action.debug.pause';
static readonly ID = 'workbench.action.debug.pause';
static LABEL = nls.localize('pauseDebug', "Pause");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
@@ -368,7 +370,7 @@ export class PauseAction extends AbstractDebugAction {
}
export class RestartFrameAction extends AbstractDebugAction {
static ID = 'workbench.action.debug.restartFrame';
static readonly ID = 'workbench.action.debug.restartFrame';
static LABEL = nls.localize('restartFrame', "Restart Frame");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
@@ -385,7 +387,7 @@ export class RestartFrameAction extends AbstractDebugAction {
}
export class RemoveBreakpointAction extends AbstractDebugAction {
static ID = 'workbench.debug.viewlet.action.removeBreakpoint';
static readonly ID = 'workbench.debug.viewlet.action.removeBreakpoint';
static LABEL = nls.localize('removeBreakpoint', "Remove Breakpoint");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
@@ -399,7 +401,7 @@ export class RemoveBreakpointAction extends AbstractDebugAction {
}
export class RemoveAllBreakpointsAction extends AbstractDebugAction {
static ID = 'workbench.debug.viewlet.action.removeAllBreakpoints';
static readonly ID = 'workbench.debug.viewlet.action.removeAllBreakpoints';
static LABEL = nls.localize('removeAllBreakpoints', "Remove All Breakpoints");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
@@ -418,7 +420,7 @@ export class RemoveAllBreakpointsAction extends AbstractDebugAction {
}
export class EnableBreakpointAction extends AbstractDebugAction {
static ID = 'workbench.debug.viewlet.action.enableBreakpoint';
static readonly ID = 'workbench.debug.viewlet.action.enableBreakpoint';
static LABEL = nls.localize('enableBreakpoint', "Enable Breakpoint");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
@@ -431,7 +433,7 @@ export class EnableBreakpointAction extends AbstractDebugAction {
}
export class DisableBreakpointAction extends AbstractDebugAction {
static ID = 'workbench.debug.viewlet.action.disableBreakpoint';
static readonly ID = 'workbench.debug.viewlet.action.disableBreakpoint';
static LABEL = nls.localize('disableBreakpoint', "Disable Breakpoint");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
@@ -444,7 +446,7 @@ export class DisableBreakpointAction extends AbstractDebugAction {
}
export class EnableAllBreakpointsAction extends AbstractDebugAction {
static ID = 'workbench.debug.viewlet.action.enableAllBreakpoints';
static readonly ID = 'workbench.debug.viewlet.action.enableAllBreakpoints';
static LABEL = nls.localize('enableAllBreakpoints', "Enable All Breakpoints");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
@@ -463,7 +465,7 @@ export class EnableAllBreakpointsAction extends AbstractDebugAction {
}
export class DisableAllBreakpointsAction extends AbstractDebugAction {
static ID = 'workbench.debug.viewlet.action.disableAllBreakpoints';
static readonly ID = 'workbench.debug.viewlet.action.disableAllBreakpoints';
static LABEL = nls.localize('disableAllBreakpoints', "Disable All Breakpoints");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
@@ -482,7 +484,7 @@ export class DisableAllBreakpointsAction extends AbstractDebugAction {
}
export class ToggleBreakpointsActivatedAction extends AbstractDebugAction {
static ID = 'workbench.debug.viewlet.action.toggleBreakpointsActivatedAction';
static readonly ID = 'workbench.debug.viewlet.action.toggleBreakpointsActivatedAction';
static ACTIVATE_LABEL = nls.localize('activateBreakpoints', "Activate Breakpoints");
static DEACTIVATE_LABEL = nls.localize('deactivateBreakpoints', "Deactivate Breakpoints");
@@ -506,7 +508,7 @@ export class ToggleBreakpointsActivatedAction extends AbstractDebugAction {
}
export class ReapplyBreakpointsAction extends AbstractDebugAction {
static ID = 'workbench.debug.viewlet.action.reapplyBreakpointsAction';
static readonly ID = 'workbench.debug.viewlet.action.reapplyBreakpointsAction';
static LABEL = nls.localize('reapplyAllBreakpoints', "Reapply All Breakpoints");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
@@ -526,7 +528,7 @@ export class ReapplyBreakpointsAction extends AbstractDebugAction {
}
export class AddFunctionBreakpointAction extends AbstractDebugAction {
static ID = 'workbench.debug.viewlet.action.addFunctionBreakpointAction';
static readonly ID = 'workbench.debug.viewlet.action.addFunctionBreakpointAction';
static LABEL = nls.localize('addFunctionBreakpoint', "Add Function Breakpoint");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
@@ -546,7 +548,7 @@ export class AddFunctionBreakpointAction extends AbstractDebugAction {
}
export class AddConditionalBreakpointAction extends AbstractDebugAction {
static ID = 'workbench.debug.viewlet.action.addConditionalBreakpointAction';
static readonly ID = 'workbench.debug.viewlet.action.addConditionalBreakpointAction';
static LABEL = nls.localize('addConditionalBreakpoint', "Add Conditional Breakpoint...");
constructor(id: string, label: string,
@@ -565,7 +567,7 @@ export class AddConditionalBreakpointAction extends AbstractDebugAction {
}
export class EditConditionalBreakpointAction extends AbstractDebugAction {
static ID = 'workbench.debug.viewlet.action.editConditionalBreakpointAction';
static readonly ID = 'workbench.debug.viewlet.action.editConditionalBreakpointAction';
static LABEL = nls.localize('editConditionalBreakpoint', "Edit Breakpoint...");
constructor(id: string, label: string,
@@ -584,7 +586,7 @@ export class EditConditionalBreakpointAction extends AbstractDebugAction {
export class SetValueAction extends AbstractDebugAction {
static ID = 'workbench.debug.viewlet.action.setValue';
static readonly ID = 'workbench.debug.viewlet.action.setValue';
static LABEL = nls.localize('setValue', "Set Value");
constructor(id: string, label: string, private variable: Variable, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
@@ -607,7 +609,7 @@ export class SetValueAction extends AbstractDebugAction {
export class AddWatchExpressionAction extends AbstractDebugAction {
static ID = 'workbench.debug.viewlet.action.addWatchExpression';
static readonly ID = 'workbench.debug.viewlet.action.addWatchExpression';
static LABEL = nls.localize('addWatchExpression', "Add Expression");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
@@ -616,7 +618,8 @@ export class AddWatchExpressionAction extends AbstractDebugAction {
}
public run(): TPromise<any> {
return this.debugService.addWatchExpression();
this.debugService.addWatchExpression();
return TPromise.as(undefined);
}
protected isEnabled(state: State): boolean {
@@ -625,7 +628,7 @@ export class AddWatchExpressionAction extends AbstractDebugAction {
}
export class EditWatchExpressionAction extends AbstractDebugAction {
static ID = 'workbench.debug.viewlet.action.editWatchExpression';
static readonly ID = 'workbench.debug.viewlet.action.editWatchExpression';
static LABEL = nls.localize('editWatchExpression', "Edit Expression");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
@@ -639,7 +642,7 @@ export class EditWatchExpressionAction extends AbstractDebugAction {
}
export class AddToWatchExpressionsAction extends AbstractDebugAction {
static ID = 'workbench.debug.viewlet.action.addToWatchExpressions';
static readonly ID = 'workbench.debug.viewlet.action.addToWatchExpressions';
static LABEL = nls.localize('addToWatchExpressions', "Add to Watch");
constructor(id: string, label: string, private expression: IExpression, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
@@ -648,12 +651,14 @@ export class AddToWatchExpressionsAction extends AbstractDebugAction {
public run(): TPromise<any> {
const name = this.expression instanceof Variable ? this.expression.evaluateName : this.expression.name;
return this.debugService.addWatchExpression(name);
this.debugService.addWatchExpression(name);
return TPromise.as(undefined);
}
}
export class RemoveWatchExpressionAction extends AbstractDebugAction {
static ID = 'workbench.debug.viewlet.action.removeWatchExpression';
static readonly ID = 'workbench.debug.viewlet.action.removeWatchExpression';
static LABEL = nls.localize('removeWatchExpression', "Remove Expression");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
@@ -667,7 +672,7 @@ export class RemoveWatchExpressionAction extends AbstractDebugAction {
}
export class RemoveAllWatchExpressionsAction extends AbstractDebugAction {
static ID = 'workbench.debug.viewlet.action.removeAllWatchExpressions';
static readonly ID = 'workbench.debug.viewlet.action.removeAllWatchExpressions';
static LABEL = nls.localize('removeAllWatchExpressions', "Remove All Expressions");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
@@ -686,7 +691,7 @@ export class RemoveAllWatchExpressionsAction extends AbstractDebugAction {
}
export class ClearReplAction extends AbstractDebugAction {
static ID = 'workbench.debug.panel.action.clearReplAction';
static readonly ID = 'workbench.debug.panel.action.clearReplAction';
static LABEL = nls.localize('clearRepl', "Clear Console");
constructor(id: string, label: string,
@@ -706,7 +711,7 @@ export class ClearReplAction extends AbstractDebugAction {
}
export class ToggleReplAction extends TogglePanelAction {
static ID = 'workbench.debug.action.toggleRepl';
static readonly ID = 'workbench.debug.action.toggleRepl';
static LABEL = nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugConsoleAction' }, 'Debug Console');
private toDispose: lifecycle.IDisposable[];
@@ -748,7 +753,7 @@ export class ToggleReplAction extends TogglePanelAction {
export class FocusReplAction extends Action {
static ID = 'workbench.debug.action.focusRepl';
static readonly ID = 'workbench.debug.action.focusRepl';
static LABEL = nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugFocusConsole' }, 'Focus Debug Console');
@@ -764,7 +769,7 @@ export class FocusReplAction extends Action {
}
export class FocusProcessAction extends AbstractDebugAction {
static ID = 'workbench.action.debug.focusProcess';
static readonly ID = 'workbench.action.debug.focusProcess';
static LABEL = nls.localize('focusProcess', "Focus Process");
constructor(id: string, label: string,
@@ -778,19 +783,19 @@ export class FocusProcessAction extends AbstractDebugAction {
public run(processName: string): TPromise<any> {
const isMultiRoot = this.debugService.getConfigurationManager().getLaunches().length > 1;
const process = this.debugService.getModel().getProcesses().filter(p => p.getName(isMultiRoot) === processName).pop();
return this.debugService.focusStackFrameAndEvaluate(null, process, true).then(() => {
const stackFrame = this.debugService.getViewModel().focusedStackFrame;
if (stackFrame) {
return stackFrame.openInEditor(this.editorService, true);
}
return undefined;
});
this.debugService.focusStackFrame(undefined, undefined, process, true);
const stackFrame = this.debugService.getViewModel().focusedStackFrame;
if (stackFrame) {
return stackFrame.openInEditor(this.editorService, true);
}
return TPromise.as(undefined);
}
}
// Actions used by the chakra debugger
export class StepBackAction extends AbstractDebugAction {
static ID = 'workbench.action.debug.stepBack';
static readonly ID = 'workbench.action.debug.stepBack';
static LABEL = nls.localize('stepBackDebug', "Step Back");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
@@ -813,7 +818,7 @@ export class StepBackAction extends AbstractDebugAction {
}
export class ReverseContinueAction extends AbstractDebugAction {
static ID = 'workbench.action.debug.reverseContinue';
static readonly ID = 'workbench.action.debug.reverseContinue';
static LABEL = nls.localize('reverseContinue', "Reverse");
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
@@ -834,3 +839,15 @@ export class ReverseContinueAction extends AbstractDebugAction {
process && process.session.capabilities.supportsStepBack;
}
}
export class ReplCollapseAllAction extends CollapseAction {
constructor(viewer: ITree, private toFocus: { focus(): void; }) {
super(viewer, true, undefined);
}
public run(event?: any): TPromise<any> {
return super.run(event).then(() => {
this.toFocus.focus();
});
}
}

View File

@@ -3,11 +3,10 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!vs/workbench/parts/debug/browser/media/debugActionsWidget';
import 'vs/css!./media/debugActionsWidget';
import * as errors from 'vs/base/common/errors';
import * as strings from 'vs/base/common/strings';
import * as browser from 'vs/base/browser/browser';
import severity from 'vs/base/common/severity';
import * as builder from 'vs/base/browser/builder';
import * as dom from 'vs/base/browser/dom';
import * as arrays from 'vs/base/common/arrays';
@@ -21,7 +20,6 @@ import { AbstractDebugAction, PauseAction, ContinueAction, StepBackAction, Rever
import { FocusProcessActionItem } from 'vs/workbench/parts/debug/browser/debugActionItems';
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IMessageService } from 'vs/platform/message/common/message';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { Themable } from 'vs/workbench/common/theme';
import { IThemeService } from 'vs/platform/theme/common/themeService';
@@ -29,6 +27,8 @@ import { registerColor, contrastBorder, widgetShadow } from 'vs/platform/theme/c
import { localize } from 'vs/nls';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { INotificationService } from 'vs/platform/notification/common/notification';
const $ = builder.$;
const DEBUG_ACTIONS_WIDGET_POSITION_KEY = 'debug.actionswidgetposition';
@@ -38,6 +38,11 @@ export const debugToolBarBackground = registerColor('debugToolBar.background', {
light: '#F3F3F3',
hc: '#000000'
}, localize('debugToolBarBackground', "Debug toolbar background color."));
export const debugToolBarBorder = registerColor('debugToolBar.border', {
dark: null,
light: null,
hc: null
}, localize('debugToolBarBorder', "Debug toolbar border color."));
export class DebugActionsWidget extends Themable implements IWorkbenchContribution {
@@ -51,7 +56,7 @@ export class DebugActionsWidget extends Themable implements IWorkbenchContributi
private isBuilt: boolean;
constructor(
@IMessageService private messageService: IMessageService,
@INotificationService private notificationService: INotificationService,
@ITelemetryService private telemetryService: ITelemetryService,
@IDebugService private debugService: IDebugService,
@IPartService private partService: IPartService,
@@ -59,7 +64,8 @@ export class DebugActionsWidget extends Themable implements IWorkbenchContributi
@IConfigurationService private configurationService: IConfigurationService,
@IThemeService themeService: IThemeService,
@IKeybindingService private keybindingService: IKeybindingService,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IContextViewService contextViewService: IContextViewService
) {
super(themeService);
@@ -75,7 +81,7 @@ export class DebugActionsWidget extends Themable implements IWorkbenchContributi
orientation: ActionsOrientation.HORIZONTAL,
actionItemProvider: (action: IAction) => {
if (action.id === FocusProcessAction.ID) {
return new FocusProcessActionItem(action, this.debugService, this.themeService);
return new FocusProcessActionItem(action, this.debugService, this.themeService, contextViewService);
}
return null;
@@ -97,7 +103,7 @@ export class DebugActionsWidget extends Themable implements IWorkbenchContributi
this.toUnbind.push(this.actionBar.actionRunner.onDidRun((e: IRunEvent) => {
// check for error
if (e.error && !errors.isPromiseCanceledError(e.error)) {
this.messageService.show(severity.Error, e.error);
this.notificationService.error(e.error);
}
// log in telemetry
@@ -159,9 +165,16 @@ export class DebugActionsWidget extends Themable implements IWorkbenchContributi
this.$el.style('box-shadow', widgetShadowColor ? `0 5px 8px ${widgetShadowColor}` : null);
const contrastBorderColor = this.getColor(contrastBorder);
this.$el.style('border-style', contrastBorderColor ? 'solid' : null);
this.$el.style('border-width', contrastBorderColor ? '1px' : null);
this.$el.style('border-color', contrastBorderColor);
const borderColor = this.getColor(debugToolBarBorder);
if (contrastBorderColor) {
this.$el.style('border', `1px solid ${contrastBorderColor}`);
} else {
this.$el.style({
'border': borderColor ? `solid ${borderColor}` : 'none',
'border-width': '1px 0'
});
}
}
}
@@ -282,4 +295,4 @@ export class DebugActionsWidget extends Themable implements IWorkbenchContributi
delete this.$el;
}
}
}
}

View File

@@ -6,40 +6,30 @@
import * as nls from 'vs/nls';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import { TPromise } from 'vs/base/common/winjs.base';
import severity from 'vs/base/common/severity';
import { List } from 'vs/base/browser/ui/list/listWidget';
import * as errors from 'vs/base/common/errors';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IListService } from 'vs/platform/list/browser/listService';
import { IMessageService } from 'vs/platform/message/common/message';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IDebugService, IEnablement, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_VARIABLES_FOCUSED, EDITOR_CONTRIBUTION_ID, IDebugEditorContribution } from 'vs/workbench/parts/debug/common/debug';
import { IDebugService, IEnablement, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_VARIABLES_FOCUSED, EDITOR_CONTRIBUTION_ID, IDebugEditorContribution, CONTEXT_IN_DEBUG_MODE, CONTEXT_NOT_IN_DEBUG_REPL, CONTEXT_EXPRESSION_SELECTED, CONTEXT_BREAKPOINT_SELECTED } from 'vs/workbench/parts/debug/common/debug';
import { Expression, Variable, Breakpoint, FunctionBreakpoint } from 'vs/workbench/parts/debug/common/debugModel';
import { IExtensionsViewlet, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/parts/extensions/common/extensions';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { openBreakpointSource } from 'vs/workbench/parts/debug/browser/breakpointsView';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { InputFocusedContext } from 'vs/platform/workbench/common/contextkeys';
export function registerCommands(): void {
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'debug.logToDebugConsole',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
handler: (accessor: ServicesAccessor, value: string) => {
if (typeof value === 'string') {
const debugService = accessor.get(IDebugService);
// Use warning as severity to get the orange color for messages coming from the debug extension
debugService.logToRepl(value, severity.Warning);
}
},
when: undefined,
primary: undefined
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'debug.toggleBreakpoint',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(5),
when: CONTEXT_BREAKPOINTS_FOCUSED,
when: ContextKeyExpr.and(CONTEXT_BREAKPOINTS_FOCUSED, InputFocusedContext.toNegated()),
primary: KeyCode.Space,
handler: (accessor) => {
const listService = accessor.get(IListService);
@@ -100,7 +90,7 @@ export function registerCommands(): void {
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'debug.removeWatchExpression',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: CONTEXT_WATCH_EXPRESSIONS_FOCUSED,
when: ContextKeyExpr.and(CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_EXPRESSION_SELECTED.toNegated()),
primary: KeyCode.Delete,
mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace },
handler: (accessor) => {
@@ -121,7 +111,7 @@ export function registerCommands(): void {
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'debug.removeBreakpoint',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: CONTEXT_BREAKPOINTS_FOCUSED,
when: ContextKeyExpr.and(CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_BREAKPOINT_SELECTED.toNegated()),
primary: KeyCode.Delete,
mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace },
handler: (accessor) => {
@@ -162,13 +152,13 @@ export function registerCommands(): void {
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: undefined,
primary: undefined,
handler: (accessor, workspaceUri: string) => {
handler: (accessor, launchUri: string) => {
const manager = accessor.get(IDebugService).getConfigurationManager();
if (accessor.get(IWorkspaceContextService).getWorkbenchState() === WorkbenchState.EMPTY) {
accessor.get(IMessageService).show(severity.Info, nls.localize('noFolderDebugConfig', "Please first open a folder in order to do advanced debug configuration."));
accessor.get(INotificationService).info(nls.localize('noFolderDebugConfig', "Please first open a folder in order to do advanced debug configuration."));
return TPromise.as(null);
}
const launch = manager.getLaunches().filter(l => l.workspace.uri.toString() === workspaceUri).pop() || manager.selectedLaunch;
const launch = manager.getLaunches().filter(l => l.uri.toString() === launchUri).pop() || manager.selectedConfiguration.launch;
return launch.openConfigFile(false).done(editor => {
if (editor) {
@@ -182,4 +172,70 @@ export function registerCommands(): void {
});
}
});
const COLUMN_BREAKPOINT_COMMAND_ID = 'editor.debug.action.toggleColumnBreakpoint';
KeybindingsRegistry.registerCommandAndKeybindingRule({
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
primary: KeyMod.Shift | KeyCode.F9,
when: EditorContextKeys.textFocus,
id: COLUMN_BREAKPOINT_COMMAND_ID,
handler: (accessor) => {
const debugService = accessor.get(IDebugService);
const editorService = accessor.get(IWorkbenchEditorService);
const editor = editorService.getActiveEditor();
const control = editor && <ICodeEditor>editor.getControl();
if (control) {
const position = control.getPosition();
const modelUri = control.getModel().uri;
const bp = debugService.getModel().getBreakpoints()
.filter(bp => bp.lineNumber === position.lineNumber && bp.column === position.column && bp.uri.toString() === modelUri.toString()).pop();
if (bp) {
return TPromise.as(null);
}
if (debugService.getConfigurationManager().canSetBreakpointsIn(control.getModel())) {
return debugService.addBreakpoints(modelUri, [{ lineNumber: position.lineNumber, column: position.column }]);
}
}
return TPromise.as(null);
}
});
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: COLUMN_BREAKPOINT_COMMAND_ID,
title: nls.localize('columnBreakpoint', "Column Breakpoint"),
category: nls.localize('debug', "Debug")
}
});
MenuRegistry.appendMenuItem(MenuId.EditorContext, {
command: {
id: COLUMN_BREAKPOINT_COMMAND_ID,
title: nls.localize('addColumnBreakpoint', "Add Column Breakpoint")
},
when: ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, CONTEXT_NOT_IN_DEBUG_REPL, EditorContextKeys.writable),
group: 'debug',
order: 1
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'debug.openBreakpointToSide',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: CONTEXT_BREAKPOINTS_FOCUSED,
primary: KeyMod.CtrlCmd | KeyCode.Enter,
secondary: [KeyMod.Alt | KeyCode.Enter],
handler: (accessor) => {
const listService = accessor.get(IListService);
const list = listService.lastFocusedList;
if (list instanceof List) {
const focus = list.getFocusedElements();
if (focus.length && focus[0] instanceof Breakpoint) {
return openBreakpointSource(focus[0], true, false, accessor.get(IDebugService), accessor.get(IWorkbenchEditorService));
}
}
return TPromise.as(undefined);
}
});
}

View File

@@ -7,7 +7,7 @@ import uri from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import { guessMimeTypes, MIME_TEXT } from 'vs/base/common/mime';
import { IModel } from 'vs/editor/common/editorCommon';
import { ITextModel } from 'vs/editor/common/model';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService';
@@ -39,7 +39,7 @@ export class DebugContentProvider implements IWorkbenchContribution, ITextModelC
textModelResolverService.registerTextModelContentProvider(DEBUG_SCHEME, this);
}
public provideTextContent(resource: uri): TPromise<IModel> {
public provideTextContent(resource: uri): TPromise<ITextModel> {
let process: IProcess;
let sourceRef: number;
@@ -56,7 +56,7 @@ export class DebugContentProvider implements IWorkbenchContribution, ITextModelC
}
if (!process) {
return TPromise.wrapError<IModel>(new Error(localize('unable', "Unable to resolve the resource without a debug session")));
return TPromise.wrapError<ITextModel>(new Error(localize('unable', "Unable to resolve the resource without a debug session")));
}
const source = process.sources.get(resource.toString());
let rawSource: DebugProtocol.Source;
@@ -73,20 +73,24 @@ export class DebugContentProvider implements IWorkbenchContribution, ITextModelC
};
}
const createErrModel = (message: string) => {
this.debugService.sourceIsNotAvailable(resource);
const modePromise = this.modeService.getOrCreateMode(MIME_TEXT);
const model = this.modelService.createModel(message, modePromise, resource);
return model;
};
return process.session.source({ sourceReference: sourceRef, source: rawSource }).then(response => {
if (!response) {
return createErrModel(localize('canNotResolveSource', "Could not resolve resource {0}, no response from debug extension.", resource.toString()));
}
const mime = response.body.mimeType || guessMimeTypes(resource.path)[0];
const modePromise = this.modeService.getOrCreateMode(mime);
const model = this.modelService.createModel(response.body.content, modePromise, resource);
return model;
}, (err: DebugProtocol.ErrorResponse) => {
this.debugService.sourceIsNotAvailable(resource);
const modePromise = this.modeService.getOrCreateMode(MIME_TEXT);
const model = this.modelService.createModel(err.message, modePromise, resource);
return model;
});
}, (err: DebugProtocol.ErrorResponse) => createErrModel(err.message));
}
}

View File

@@ -49,63 +49,6 @@ class ToggleBreakpointAction extends EditorAction {
}
}
function addColumnBreakpoint(accessor: ServicesAccessor, editor: ICodeEditor, remove: boolean): TPromise<any> {
const debugService = accessor.get(IDebugService);
const position = editor.getPosition();
const modelUri = editor.getModel().uri;
const bp = debugService.getModel().getBreakpoints()
.filter(bp => bp.lineNumber === position.lineNumber && bp.column === position.column && bp.uri.toString() === modelUri.toString()).pop();
if (bp) {
return remove ? debugService.removeBreakpoints(bp.getId()) : TPromise.as(null);
}
if (debugService.getConfigurationManager().canSetBreakpointsIn(editor.getModel())) {
return debugService.addBreakpoints(modelUri, [{ lineNumber: position.lineNumber, column: position.column }]);
}
return TPromise.as(null);
}
class ToggleColumnBreakpointAction extends EditorAction {
constructor() {
super({
id: 'editor.debug.action.toggleColumnBreakpoint',
label: nls.localize('columnBreakpointAction', "Debug: Column Breakpoint"),
alias: 'Debug: Column Breakpoint',
precondition: null,
kbOpts: {
kbExpr: EditorContextKeys.textFocus,
primary: KeyMod.Shift | KeyCode.F9
}
});
}
public run(accessor: ServicesAccessor, editor: ICodeEditor): TPromise<any> {
return addColumnBreakpoint(accessor, editor, true);
}
}
// TODO@Isidor merge two column breakpoints actions together
class ToggleColumnBreakpointContextMenuAction extends EditorAction {
constructor() {
super({
id: 'editor.debug.action.toggleColumnBreakpointContextMenu',
label: nls.localize('columnBreakpoint', "Add Column Breakpoint"),
alias: 'Toggle Column Breakpoint',
precondition: ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, CONTEXT_NOT_IN_DEBUG_REPL, EditorContextKeys.writable),
menuOpts: {
group: 'debug',
order: 1
}
});
}
public run(accessor: ServicesAccessor, editor: ICodeEditor): TPromise<any> {
return addColumnBreakpoint(accessor, editor, false);
}
}
class ConditionalBreakpointAction extends EditorAction {
constructor() {
@@ -268,12 +211,9 @@ class CloseBreakpointWidgetCommand extends EditorCommand {
}
registerEditorAction(ToggleBreakpointAction);
registerEditorAction(ToggleColumnBreakpointAction);
registerEditorAction(ToggleColumnBreakpointContextMenuAction);
registerEditorAction(ConditionalBreakpointAction);
registerEditorAction(RunToCursorAction);
registerEditorAction(SelectionToReplAction);
registerEditorAction(SelectionToWatchExpressionsAction);
registerEditorAction(ShowDebugHoverAction);
registerEditorCommand(new CloseBreakpointWidgetCommand());

View File

@@ -3,16 +3,16 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as objects from 'vs/base/common/objects';
import * as lifecycle from 'vs/base/common/lifecycle';
import { Constants } from 'vs/editor/common/core/uint';
import { Range } from 'vs/editor/common/core/range';
import { IModel, TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationOptions } from 'vs/editor/common/editorCommon';
import { ITextModel, TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationOptions } from 'vs/editor/common/model';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IDebugService, IBreakpoint, State } from 'vs/workbench/parts/debug/common/debug';
import { IModelService } from 'vs/editor/common/services/modelService';
import { MarkdownString } from 'vs/base/common/htmlContent';
import { getBreakpointMessageAndClassName } from 'vs/workbench/parts/debug/browser/breakpointsView';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
interface IBreakpointDecoration {
decorationId: string;
@@ -21,26 +21,24 @@ interface IBreakpointDecoration {
}
interface IDebugEditorModelData {
model: IModel;
model: ITextModel;
toDispose: lifecycle.IDisposable[];
breakpointDecorations: IBreakpointDecoration[];
currentStackDecorations: string[];
dirty: boolean;
topStackFrameRange: Range;
}
const stickiness = TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges;
export class DebugEditorModelManager implements IWorkbenchContribution {
static ID = 'breakpointManager';
static readonly ID = 'breakpointManager';
static readonly STICKINESS = TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges;
private modelDataMap: Map<string, IDebugEditorModelData>;
private toDispose: lifecycle.IDisposable[];
private ignoreDecorationsChangedEvent: boolean;
constructor(
@IModelService private modelService: IModelService,
@IDebugService private debugService: IDebugService
@IDebugService private debugService: IDebugService,
@ITextFileService private textFileService: ITextFileService
) {
this.modelDataMap = new Map<string, IDebugEditorModelData>();
this.toDispose = [];
@@ -68,14 +66,13 @@ export class DebugEditorModelManager implements IWorkbenchContribution {
this.toDispose.push(this.debugService.onDidChangeState(state => {
if (state === State.Inactive) {
this.modelDataMap.forEach(modelData => {
modelData.dirty = false;
modelData.topStackFrameRange = undefined;
});
}
}));
}
private onModelAdded(model: IModel): void {
private onModelAdded(model: ITextModel): void {
const modelUrlStr = model.uri.toString();
const breakpoints = this.debugService.getModel().getBreakpoints().filter(bp => bp.uri.toString() === modelUrlStr);
@@ -89,12 +86,11 @@ export class DebugEditorModelManager implements IWorkbenchContribution {
toDispose: toDispose,
breakpointDecorations: breakpointDecorationIds.map((decorationId, index) => ({ decorationId, modelId: breakpoints[index].getId(), range: desiredDecorations[index].range })),
currentStackDecorations: currentStackDecorations,
dirty: false,
topStackFrameRange: undefined
});
}
private onModelRemoved(model: IModel): void {
private onModelRemoved(model: ITextModel): void {
const modelUriStr = model.uri.toString();
if (this.modelDataMap.has(modelUriStr)) {
lifecycle.dispose(this.modelDataMap.get(modelUriStr).toDispose);
@@ -220,9 +216,8 @@ export class DebugEditorModelManager implements IWorkbenchContribution {
}
}
}
modelData.dirty = this.debugService.state !== State.Inactive;
this.debugService.updateBreakpoints(modelUri, data);
this.debugService.updateBreakpoints(modelUri, data, true);
}
private onBreakpointsChange(): void {
@@ -262,121 +257,73 @@ export class DebugEditorModelManager implements IWorkbenchContribution {
({ decorationId, modelId: newBreakpoints[index].getId(), range: desiredDecorations[index].range }));
}
private createBreakpointDecorations(model: IModel, breakpoints: IBreakpoint[]): { range: Range; options: IModelDecorationOptions; }[] {
return breakpoints.map((breakpoint) => {
const column = model.getLineFirstNonWhitespaceColumn(breakpoint.lineNumber);
const range = model.validateRange(
breakpoint.column ? new Range(breakpoint.lineNumber, breakpoint.column, breakpoint.lineNumber, breakpoint.column + 1)
: new Range(breakpoint.lineNumber, column, breakpoint.lineNumber, column + 1) // Decoration has to have a width #20688
);
return {
options: this.getBreakpointDecorationOptions(breakpoint),
range
};
private createBreakpointDecorations(model: ITextModel, breakpoints: IBreakpoint[]): { range: Range; options: IModelDecorationOptions; }[] {
const result: { range: Range; options: IModelDecorationOptions; }[] = [];
breakpoints.forEach((breakpoint) => {
if (breakpoint.lineNumber <= model.getLineCount()) {
const column = model.getLineFirstNonWhitespaceColumn(breakpoint.lineNumber);
const range = model.validateRange(
breakpoint.column ? new Range(breakpoint.lineNumber, breakpoint.column, breakpoint.lineNumber, breakpoint.column + 1)
: new Range(breakpoint.lineNumber, column, breakpoint.lineNumber, column + 1) // Decoration has to have a width #20688
);
result.push({
options: this.getBreakpointDecorationOptions(breakpoint),
range
});
}
});
return result;
}
private getBreakpointDecorationOptions(breakpoint: IBreakpoint): IModelDecorationOptions {
const activated = this.debugService.getModel().areBreakpointsActivated();
const state = this.debugService.state;
const debugActive = state === State.Running || state === State.Stopped;
const modelData = this.modelDataMap.get(breakpoint.uri.toString());
const { className, message } = getBreakpointMessageAndClassName(this.debugService, this.textFileService, breakpoint);
let glyphMarginHoverMessage: MarkdownString;
let result = (!breakpoint.enabled || !activated) ? DebugEditorModelManager.BREAKPOINT_DISABLED_DECORATION :
debugActive && modelData && modelData.dirty && !breakpoint.verified ? DebugEditorModelManager.BREAKPOINT_DIRTY_DECORATION :
debugActive && !breakpoint.verified ? DebugEditorModelManager.BREAKPOINT_UNVERIFIED_DECORATION :
!breakpoint.condition && !breakpoint.hitCondition ? DebugEditorModelManager.BREAKPOINT_DECORATION : null;
if (result) {
result = objects.deepClone(result);
if (breakpoint.message) {
result.glyphMarginHoverMessage = new MarkdownString().appendText(breakpoint.message);
if (message) {
if (breakpoint.condition || breakpoint.hitCondition) {
const modelData = this.modelDataMap.get(breakpoint.uri.toString());
const modeId = modelData ? modelData.model.getLanguageIdentifier().language : '';
glyphMarginHoverMessage = new MarkdownString().appendCodeblock(modeId, message);
} else {
glyphMarginHoverMessage = new MarkdownString().appendText(message);
}
if (breakpoint.column) {
result.beforeContentClassName = `debug-breakpoint-column ${result.glyphMarginClassName}-column`;
}
return result;
}
const process = this.debugService.getViewModel().focusedProcess;
if (process && !process.session.capabilities.supportsConditionalBreakpoints) {
return DebugEditorModelManager.BREAKPOINT_UNSUPPORTED_DECORATION;
}
const modeId = modelData ? modelData.model.getLanguageIdentifier().language : '';
let condition: string;
if (breakpoint.condition && breakpoint.hitCondition) {
condition = `Expression: ${breakpoint.condition}\nHitCount: ${breakpoint.hitCondition}`;
} else {
condition = breakpoint.condition ? breakpoint.condition : breakpoint.hitCondition;
}
const glyphMarginHoverMessage = new MarkdownString().appendCodeblock(modeId, condition);
const glyphMarginClassName = 'debug-breakpoint-conditional-glyph';
const beforeContentClassName = breakpoint.column ? `debug-breakpoint-column ${glyphMarginClassName}-column` : undefined;
return {
glyphMarginClassName,
glyphMarginClassName: className,
glyphMarginHoverMessage,
stickiness,
beforeContentClassName
stickiness: DebugEditorModelManager.STICKINESS,
beforeContentClassName: breakpoint.column ? `debug-breakpoint-column ${className}-column` : undefined
};
}
// editor decorations
private static BREAKPOINT_DECORATION: IModelDecorationOptions = {
glyphMarginClassName: 'debug-breakpoint-glyph',
stickiness
};
private static BREAKPOINT_DISABLED_DECORATION: IModelDecorationOptions = {
glyphMarginClassName: 'debug-breakpoint-disabled-glyph',
glyphMarginHoverMessage: new MarkdownString().appendText(nls.localize('breakpointDisabledHover', "Disabled Breakpoint")),
stickiness
};
private static BREAKPOINT_UNVERIFIED_DECORATION: IModelDecorationOptions = {
glyphMarginClassName: 'debug-breakpoint-unverified-glyph',
glyphMarginHoverMessage: new MarkdownString().appendText(nls.localize('breakpointUnverifieddHover', "Unverified Breakpoint")),
stickiness
};
private static BREAKPOINT_DIRTY_DECORATION: IModelDecorationOptions = {
glyphMarginClassName: 'debug-breakpoint-unverified-glyph',
glyphMarginHoverMessage: new MarkdownString().appendText(nls.localize('breakpointDirtydHover', "Unverified breakpoint. File is modified, please restart debug session.")),
stickiness
};
private static BREAKPOINT_UNSUPPORTED_DECORATION: IModelDecorationOptions = {
glyphMarginClassName: 'debug-breakpoint-unsupported-glyph',
glyphMarginHoverMessage: new MarkdownString().appendText(nls.localize('breakpointUnsupported', "Conditional breakpoints not supported by this debug type")),
stickiness
};
// we need a separate decoration for glyph margin, since we do not want it on each line of a multi line statement.
private static TOP_STACK_FRAME_MARGIN: IModelDecorationOptions = {
glyphMarginClassName: 'debug-top-stack-frame-glyph',
stickiness
glyphMarginClassName: 'debug-top-stack-frame',
stickiness: DebugEditorModelManager.STICKINESS
};
private static FOCUSED_STACK_FRAME_MARGIN: IModelDecorationOptions = {
glyphMarginClassName: 'debug-focused-stack-frame-glyph',
stickiness
glyphMarginClassName: 'debug-focused-stack-frame',
stickiness: DebugEditorModelManager.STICKINESS
};
private static TOP_STACK_FRAME_DECORATION: IModelDecorationOptions = {
isWholeLine: true,
inlineClassName: 'debug-remove-token-colors',
className: 'debug-top-stack-frame-line',
stickiness
stickiness: DebugEditorModelManager.STICKINESS
};
private static TOP_STACK_FRAME_EXCEPTION_DECORATION: IModelDecorationOptions = {
isWholeLine: true,
inlineClassName: 'debug-remove-token-colors',
className: 'debug-top-stack-frame-exception-line',
stickiness
stickiness: DebugEditorModelManager.STICKINESS
};
private static TOP_STACK_FRAME_INLINE_DECORATION: IModelDecorationOptions = {
@@ -387,6 +334,6 @@ export class DebugEditorModelManager implements IWorkbenchContribution {
isWholeLine: true,
inlineClassName: 'debug-remove-token-colors',
className: 'debug-focused-stack-frame-line',
stickiness
stickiness: DebugEditorModelManager.STICKINESS
};
}

View File

@@ -15,8 +15,7 @@ import * as errors from 'vs/base/common/errors';
import { QuickOpenEntry, QuickOpenEntryGroup } from 'vs/base/parts/quickopen/browser/quickOpenModel';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { StartAction } from 'vs/workbench/parts/debug/browser/debugActions';
import { IMessageService } from 'vs/platform/message/common/message';
import { Severity } from 'vs/workbench/services/message/browser/messageList';
import { INotificationService } from 'vs/platform/notification/common/notification';
class AddConfigEntry extends Model.QuickOpenEntry {
@@ -29,7 +28,7 @@ class AddConfigEntry extends Model.QuickOpenEntry {
}
public getDescription(): string {
return this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? this.launch.workspace.name : '';
return this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? this.launch.name : '';
}
public getAriaLabel(): string {
@@ -40,7 +39,7 @@ class AddConfigEntry extends Model.QuickOpenEntry {
if (mode === QuickOpen.Mode.PREVIEW) {
return false;
}
this.commandService.executeCommand('debug.addConfiguration', this.launch.workspace.uri.toString()).done(undefined, errors.onUnexpectedError);
this.commandService.executeCommand('debug.addConfiguration', this.launch.uri.toString()).done(undefined, errors.onUnexpectedError);
return true;
}
@@ -48,7 +47,7 @@ class AddConfigEntry extends Model.QuickOpenEntry {
class StartDebugEntry extends Model.QuickOpenEntry {
constructor(private debugService: IDebugService, private contextService: IWorkspaceContextService, private messageService: IMessageService, private launch: ILaunch, private configurationName: string, highlights: Model.IHighlight[] = []) {
constructor(private debugService: IDebugService, private contextService: IWorkspaceContextService, private notificationService: INotificationService, private launch: ILaunch, private configurationName: string, highlights: Model.IHighlight[] = []) {
super(highlights);
}
@@ -57,7 +56,7 @@ class StartDebugEntry extends Model.QuickOpenEntry {
}
public getDescription(): string {
return this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? this.launch.workspace.name : '';
return this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? this.launch.name : '';
}
public getAriaLabel(): string {
@@ -70,7 +69,7 @@ class StartDebugEntry extends Model.QuickOpenEntry {
}
// Run selected debug configuration
this.debugService.getConfigurationManager().selectConfiguration(this.launch, this.configurationName);
this.debugService.startDebugging(this.launch.workspace).done(undefined, e => this.messageService.show(Severity.Error, e));
this.debugService.startDebugging(this.launch).done(undefined, e => this.notificationService.error(e));
return true;
}
@@ -85,7 +84,7 @@ export class DebugQuickOpenHandler extends Quickopen.QuickOpenHandler {
@IDebugService private debugService: IDebugService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@ICommandService private commandService: ICommandService,
@IMessageService private messageService: IMessageService
@INotificationService private notificationService: INotificationService
) {
super();
}
@@ -103,14 +102,15 @@ export class DebugQuickOpenHandler extends Quickopen.QuickOpenHandler {
launch.getConfigurationNames().map(config => ({ config: config, highlights: Filters.matchesContiguousSubString(input, config) }))
.filter(({ highlights }) => !!highlights)
.forEach(({ config, highlights }) => {
if (launch === configManager.selectedLaunch && config === configManager.selectedName) {
if (launch === configManager.selectedConfiguration.launch && config === configManager.selectedConfiguration.name) {
this.autoFocusIndex = configurations.length;
}
configurations.push(new StartDebugEntry(this.debugService, this.contextService, this.messageService, launch, config, highlights));
configurations.push(new StartDebugEntry(this.debugService, this.contextService, this.notificationService, launch, config, highlights));
});
}
launches.forEach((l, index) => {
const label = launches.length > 1 ? nls.localize("addConfigTo", "Add Config ({0})...", l.workspace.name) : nls.localize('addConfiguration', "Add Configuration...");
launches.filter(l => !l.hidden).forEach((l, index) => {
const label = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? nls.localize("addConfigTo", "Add Config ({0})...", l.name) : nls.localize('addConfiguration', "Add Configuration...");
const entry = new AddConfigEntry(label, l, this.commandService, this.contextService, Filters.matchesContiguousSubString(input, label));
if (index === 0) {
configurations.push(new QuickOpenEntryGroup(entry, undefined, true));

View File

@@ -92,8 +92,13 @@ export class DebugStatus extends Themable implements IStatusbarItem {
private setLabel(): void {
if (this.label && this.statusBarItem) {
const manager = this.debugService.getConfigurationManager();
const name = manager.selectedName || '';
this.label.textContent = manager.getLaunches().length > 1 ? `${name} (${manager.selectedLaunch.workspace.name})` : name;
const name = manager.selectedConfiguration.name;
if (name) {
this.statusBarItem.style.display = 'block';
this.label.textContent = manager.getLaunches().length > 1 ? `${name} (${manager.selectedConfiguration.launch.name})` : name;
} else {
this.statusBarItem.style.display = 'none';
}
}
}

View File

@@ -15,17 +15,18 @@ import { IDebugService, VIEWLET_ID, State, VARIABLES_VIEW_ID, WATCH_VIEW_ID, CAL
import { StartAction, ToggleReplAction, ConfigureAction } from 'vs/workbench/parts/debug/browser/debugActions';
import { StartDebugActionItem } from 'vs/workbench/parts/debug/browser/debugActionItems';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IProgressService, IProgressRunner } from 'vs/platform/progress/common/progress';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { ViewLocation } from 'vs/workbench/browser/parts/views/viewsRegistry';
import { ViewLocation } from 'vs/workbench/common/views';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IPartService } from 'vs/workbench/services/part/common/partService';
export class DebugViewlet extends PersistentViewsViewlet {
@@ -35,6 +36,7 @@ export class DebugViewlet extends PersistentViewsViewlet {
private panelListeners = new Map<string, IDisposable>();
constructor(
@IPartService partService: IPartService,
@ITelemetryService telemetryService: ITelemetryService,
@IProgressService private progressService: IProgressService,
@IDebugService private debugService: IDebugService,
@@ -46,7 +48,7 @@ export class DebugViewlet extends PersistentViewsViewlet {
@IContextMenuService contextMenuService: IContextMenuService,
@IExtensionService extensionService: IExtensionService
) {
super(VIEWLET_ID, ViewLocation.Debug, `${VIEWLET_ID}.state`, false, telemetryService, storageService, instantiationService, themeService, contextService, contextKeyService, contextMenuService, extensionService);
super(VIEWLET_ID, ViewLocation.Debug, `${VIEWLET_ID}.state`, false, partService, telemetryService, storageService, instantiationService, themeService, contextService, contextKeyService, contextMenuService, extensionService);
this.progressRunner = null;
@@ -82,7 +84,7 @@ export class DebugViewlet extends PersistentViewsViewlet {
}
public getActionItem(action: IAction): IActionItem {
if (action.id === StartAction.ID && !!this.debugService.getConfigurationManager().selectedLaunch) {
if (action.id === StartAction.ID) {
this.startDebugActionItem = this.instantiationService.createInstance(StartDebugActionItem, null, action);
return this.startDebugActionItem;
}
@@ -138,7 +140,7 @@ export class DebugViewlet extends PersistentViewsViewlet {
export class FocusVariablesViewAction extends Action {
static ID = 'workbench.debug.action.focusVariablesView';
static readonly ID = 'workbench.debug.action.focusVariablesView';
static LABEL = nls.localize('debugFocusVariablesView', 'Focus Variables');
constructor(id: string, label: string,
@@ -156,7 +158,7 @@ export class FocusVariablesViewAction extends Action {
export class FocusWatchViewAction extends Action {
static ID = 'workbench.debug.action.focusWatchView';
static readonly ID = 'workbench.debug.action.focusWatchView';
static LABEL = nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugFocusWatchView' }, 'Focus Watch');
constructor(id: string, label: string,
@@ -174,7 +176,7 @@ export class FocusWatchViewAction extends Action {
export class FocusCallStackViewAction extends Action {
static ID = 'workbench.debug.action.focusCallStackView';
static readonly ID = 'workbench.debug.action.focusCallStackView';
static LABEL = nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugFocusCallStackView' }, 'Focus CallStack');
constructor(id: string, label: string,
@@ -192,7 +194,7 @@ export class FocusCallStackViewAction extends Action {
export class FocusBreakpointsViewAction extends Action {
static ID = 'workbench.debug.action.focusBreakpointsView';
static readonly ID = 'workbench.debug.action.focusBreakpointsView';
static LABEL = nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugFocusBreakpointsView' }, 'Focus Breakpoints');
constructor(id: string, label: string,

View File

@@ -11,6 +11,7 @@ import * as nls from 'vs/nls';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
export class LinkDetector {
private static readonly MAX_LENGTH = 500;
private static FILE_LOCATION_PATTERNS: RegExp[] = [
// group 0: full path with line and column
// group 1: full path without line and column, matched by `*.*` in the end to work only on paths with extensions in the end (s.t. node:10352 would not match)
@@ -34,8 +35,11 @@ export class LinkDetector {
* If no links were detected, returns the original string.
*/
public handleLinks(text: string): HTMLElement | string {
let linkContainer: HTMLElement;
if (text.length > LinkDetector.MAX_LENGTH) {
return text;
}
let linkContainer: HTMLElement;
for (let pattern of LinkDetector.FILE_LOCATION_PATTERNS) {
pattern.lastIndex = 0; // the holy grail of software development
let lastMatchIndex = 0;

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-red{fill:#c10;}</style></defs><title>BreakpointFunction_16xMD</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14.7,14H1.3L8,1.941Z"/></g><g id="iconBg"><path class="icon-vs-red" d="M13,13H3L8,4Z"/></g></svg>

After

Width:  |  Height:  |  Size: 482 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-disabled-grey{fill:#848484;}</style></defs><title>BreakpointFunction_disabled_16xMD</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14.7,14H1.3L8,1.941Z"/></g><g id="iconBg"><path class="icon-disabled-grey" d="M13,13H3L8,4Z"/></g></svg>

After

Width:  |  Height:  |  Size: 508 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-disabled-grey{fill:#848484;}</style></defs><title>BreakpointFunction_disabled_16xMD</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14.7,14H1.3L8,1.941Z"/></g><g id="iconBg"><path class="icon-disabled-grey" d="M13,13H3L8,4Z"/></g></svg>

After

Width:  |  Height:  |  Size: 508 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-red{fill:#848484;}</style></defs><title>BreakpointFunctionUnverified_16xMD</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14.7,14H1.3L8,1.941Z"/></g><g id="iconBg"><path class="icon-vs-red" d="M8,4,3,13H13ZM8,6.059,11.3,12H4.7Z"/></g></svg>

After

Width:  |  Height:  |  Size: 516 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-red{fill:#848484;}</style></defs><title>BreakpointFunctionUnverified_16xMD</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14.7,14H1.3L8,1.941Z"/></g><g id="iconBg"><path class="icon-vs-red" d="M8,4,3,13H13ZM8,6.059,11.3,12H4.7Z"/></g></svg>

After

Width:  |  Height:  |  Size: 516 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-red{fill:#e51400;}</style></defs><title>BreakpointFunction_16xMD</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14.7,14H1.3L8,1.941Z"/></g><g id="iconBg"><path class="icon-vs-red" d="M13,13H3L8,4Z"/></g></svg>

After

Width:  |  Height:  |  Size: 484 B

View File

@@ -14,7 +14,7 @@
}
.monaco-editor .debug-top-stack-frame-range {
background: #ffeca0;
background: rgba(255, 255, 102, 0.6);
}
.monaco-editor .debug-top-stack-frame-column::before {
@@ -29,30 +29,30 @@
background: rgba(206, 231, 206, 1);
}
.monaco-editor .debug-breakpoint-hint-glyph {
.debug-breakpoint-hint {
background: url('breakpoint-hint.svg') center center no-repeat;
}
.monaco-editor .debug-breakpoint-disabled-glyph,
.monaco-editor .debug-breakpoint-column.debug-breakpoint-disabled-glyph-column::before {
.debug-breakpoint-disabled,
.monaco-editor .debug-breakpoint-column.debug-breakpoint-disabled-column::before {
background: url('breakpoint-disabled.svg') center center no-repeat;
}
.monaco-editor .debug-breakpoint-unverified-glyph,
.monaco-editor .debug-breakpoint-column.debug-breakpoint-unverified-glyph-column::before {
.debug-breakpoint-unverified,
.monaco-editor .debug-breakpoint-column.debug-breakpoint-unverified-column::before {
background: url('breakpoint-unverified.svg') center center no-repeat;
}
.monaco-editor .debug-top-stack-frame-glyph {
.monaco-editor .debug-top-stack-frame {
background: url('current-arrow.svg') center center no-repeat;
}
.monaco-editor .debug-focused-stack-frame-glyph {
.monaco-editor .debug-focused-stack-frame {
background: url('stackframe-arrow.svg') center center no-repeat;
}
.monaco-editor .debug-breakpoint-glyph,
.monaco-editor .debug-breakpoint-column.debug-breakpoint-glyph-column::before {
.debug-breakpoint,
.monaco-editor .debug-breakpoint-column.debug-breakpoint-column::before {
background: url('breakpoint.svg') center center no-repeat;
}
@@ -68,27 +68,40 @@
background-position: initial !important;
}
.monaco-editor .debug-breakpoint-conditional-glyph,
.monaco-editor .debug-breakpoint-column.debug-breakpoint-conditional-glyph-column::before {
.debug-function-breakpoint {
background: url('breakpoint-function.svg') center center no-repeat;
}
.debug-function-breakpoint-unverified {
background: url('breakpoint-function-unverified.svg') center center no-repeat;
}
.debug-function-breakpoint-disabled {
background: url('breakpoint-function-disabled.svg') center center no-repeat;
}
.debug-breakpoint-conditional,
.monaco-editor .debug-breakpoint-column.debug-breakpoint-conditional-column::before {
background: url('breakpoint-conditional.svg') center center no-repeat;
}
.monaco-editor .debug-breakpoint-unsupported-glyph,
.monaco-editor .debug-breakpoint-column.debug-breakpoint-unsupported-glyph-column::before {
.debug-breakpoint-unsupported,
.monaco-editor .debug-breakpoint-column.debug-breakpoint-unsupported-column::before {
background: url('breakpoint-unsupported.svg') center center no-repeat;
}
.monaco-editor .debug-top-stack-frame-glyph.debug-breakpoint-glyph,
.monaco-editor .debug-top-stack-frame-glyph.debug-breakpoint-conditional-glyph,
.monaco-editor .debug-breakpoint-column.debug-breakpoint-glyph-column.debug-top-stack-frame-column::before,
.monaco-editor.vs-dark .debug-top-stack-frame-glyph.debug-breakpoint-glyph,
.monaco-editor.vs-dark .debug-top-stack-frame-glyph.debug-breakpoint-conditional-glyph,
.monaco-editor.vs-dark .debug-breakpoint-column.debug-breakpoint-glyph-column.debug-top-stack-frame-column::before {
.monaco-editor .debug-top-stack-frame.debug-breakpoint,
.monaco-editor .debug-top-stack-frame.debug-breakpoint-conditional,
.monaco-editor .debug-breakpoint-column.debug-breakpoint-column.debug-top-stack-frame-column::before,
.monaco-editor.vs-dark .debug-top-stack-frame.debug-breakpoint,
.monaco-editor.vs-dark .debug-top-stack-frame.debug-breakpoint-conditional,
.monaco-editor.vs-dark .debug-breakpoint-column.debug-breakpoint-column.debug-top-stack-frame-column::before {
background: url('current-and-breakpoint.svg') center center no-repeat;
}
.monaco-editor .debug-focused-stack-frame-glyph.debug-breakpoint-glyph,
.monaco-editor .debug-focused-stack-frame-glyph.debug-breakpoint-conditional-glyph {
.monaco-editor .debug-focused-stack-frame.debug-breakpoint,
.monaco-editor .debug-focused-stack-frame.debug-breakpoint-conditional {
background: url('stackframe-and-breakpoint.svg') center center no-repeat;
}
@@ -233,40 +246,61 @@
background-color: rgba(255, 255, 0, 0.3)
}
.monaco-editor.vs-dark .debug-breakpoint-glyph,
.monaco-editor.vs-dark .debug-breakpoint-column.debug-breakpoint-glyph-column::before {
.vs-dark .debug-breakpoint,
.hc-black .debug-breakpoint,
.monaco-editor.vs-dark .debug-breakpoint-column.debug-breakpoint-column::before {
background: url('breakpoint-dark.svg') center center no-repeat;
}
.monaco-editor.vs-dark .debug-breakpoint-conditional-glyph,
.monaco-editor.vs-dark .debug-breakpoint-column.debug-breakpoint-conditional-glyph-column::before {
.vs-dark .debug-breakpoint-conditional,
.hc-black .debug-breakpoint-conditional,
.monaco-editor.vs-dark .debug-breakpoint-column.debug-breakpoint-conditional-column::before {
background: url('breakpoint-conditional-dark.svg') center center no-repeat;
}
.monaco-editor.vs-dark .debug-breakpoint-unsupported-glyph,
.monaco-editor.vs-dark .debug-breakpoint-column.debug-breakpoint-unsupported-glyph-column::before {
.vs-dark .debug-breakpoint-unsupported,
.hc-black .debug-breakpoint-unsupported,
.monaco-editor.vs-dark .debug-breakpoint-column.debug-breakpoint-unsupported-column::before {
background: url('breakpoint-unsupported-dark.svg') center center no-repeat;
}
.monaco-editor.vs-dark .debug-breakpoint-disabled-glyph,
.monaco-editor.vs-dark .debug-breakpoint-column.debug-breakpoint-disabled-glyph-column::before {
.vs-dark .debug-breakpoint-disabled,
.hc-black .debug-breakpoint-disabled,
.monaco-editor.vs-dark .debug-breakpoint-column.debug-breakpoint-disabled-column::before {
background: url('breakpoint-disabled-dark.svg') center center no-repeat;
}
.monaco-editor.vs-dark .debug-breakpoint-unverified-glyph,
.monaco-editor.vs-dark .debug-breakpoint-column.debug-breakpoint-unverified-glyph-column::before {
.vs-dark .debug-breakpoint-unverified,
.hc-black .debug-breakpoint-unverified,
.monaco-editor.vs-dark .debug-breakpoint-column.debug-breakpoint-unverified-column::before {
background: url('breakpoint-unverified-dark.svg') center center no-repeat;
}
.monaco-editor.vs-dark .debug-focused-stack-frame-glyph {
.monaco-editor.vs-dark .debug-focused-stack-frame {
background: url('stackframe-arrow-dark.svg') center center no-repeat;
}
.monaco-editor.vs-dark .debug-focused-stack-frame-glyph.debug-breakpoint-glyph,
.monaco-editor.vs-dark .debug-focused-stack-frame-glyph.debug-breakpoint-conditional-glyph {
.monaco-editor.vs-dark .debug-focused-stack-frame.debug-breakpoint,
.monaco-editor.vs-dark .debug-focused-stack-frame.debug-breakpoint-conditional {
background: url('stackframe-and-breakpoint-dark.svg') center center no-repeat;
}
.vs-dark .debug-function-breakpoint,
.hc-black .debug-function-breakpoint {
background: url('breakpoint-function-dark.svg') center center no-repeat;
}
.vs-dark .debug-function-breakpoint-unverified,
.hc-black .debug-function-breakpoint-unverified {
background: url('breakpoint-function-unverified.svg') center center no-repeat;
}
.vs-dark .debug-function-breakpoint-disabled,
.hc-black .debug-function-breakpoint-disabled {
background: url('breakpoint-function-disabled.svg') center center no-repeat;
}
/* High Contrast Theming */
.monaco-editor.hc-black .debug-focused-stack-frame-line {

View File

@@ -348,7 +348,11 @@
/* Breakpoints */
.debug-viewlet .debug-breakpoints .monaco-list-row {
.debug-viewlet .debug-breakpoints .monaco-list-row .breakpoint {
padding-left: 2px;
}
.debug-viewlet .debug-breakpoints .breakpoint.exception {
padding-left: 20px;
}
@@ -363,6 +367,11 @@
flex-shrink: 0;
}
.debug-viewlet .debug-breakpoints .breakpoint > .icon {
width: 18px;
height: 18px;
}
.debug-viewlet .debug-breakpoints .breakpoint > .file-path {
opacity: 0.7;
font-size: 0.9em;

View File

@@ -11,7 +11,7 @@ import { IPartService, Parts } from 'vs/workbench/services/part/common/partServi
import { IDebugService, State } from 'vs/workbench/parts/debug/common/debug';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_BACKGROUND, Themable, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_BORDER, STATUS_BAR_BORDER } from 'vs/workbench/common/theme';
import { addClass, removeClass } from 'vs/base/browser/dom';
import { addClass, removeClass, createStyleSheet } from 'vs/base/browser/dom';
// colors for theming
@@ -34,6 +34,7 @@ export const STATUS_BAR_DEBUGGING_BORDER = registerColor('statusBar.debuggingBor
}, localize('statusBarDebuggingBorder', "Status bar border color separating to the sidebar and editor when a program is being debugged. The status bar is shown in the bottom of the window"));
export class StatusBarColorProvider extends Themable implements IWorkbenchContribution {
private styleElement: HTMLStyleElement;
constructor(
@IThemeService themeService: IThemeService,
@@ -61,13 +62,23 @@ export class StatusBarColorProvider extends Themable implements IWorkbenchContri
removeClass(container, 'debugging');
}
container.style.backgroundColor = this.getColor(this.getColorKey(STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_DEBUGGING_BACKGROUND, STATUS_BAR_BACKGROUND));
// Container Colors
const backgroundColor = this.getColor(this.getColorKey(STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_DEBUGGING_BACKGROUND, STATUS_BAR_BACKGROUND));
container.style.backgroundColor = backgroundColor;
container.style.color = this.getColor(this.getColorKey(STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_DEBUGGING_FOREGROUND, STATUS_BAR_FOREGROUND));
// Border Color
const borderColor = this.getColor(this.getColorKey(STATUS_BAR_NO_FOLDER_BORDER, STATUS_BAR_DEBUGGING_BORDER, STATUS_BAR_BORDER)) || this.getColor(contrastBorder);
container.style.borderTopWidth = borderColor ? '1px' : null;
container.style.borderTopStyle = borderColor ? 'solid' : null;
container.style.borderTopColor = borderColor;
// Notification Beak
if (!this.styleElement) {
this.styleElement = createStyleSheet(container);
}
this.styleElement.innerHTML = `.monaco-workbench > .part.statusbar > .statusbar-item.has-beak:before { border-bottom-color: ${backgroundColor} !important; }`;
}
private getColorKey(noFolderColor: string, debuggingColor: string, normalColor: string): string {

View File

@@ -10,7 +10,8 @@ import severity from 'vs/base/common/severity';
import Event from 'vs/base/common/event';
import { IJSONSchemaSnippet } from 'vs/base/common/jsonSchema';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IModel as EditorIModel, IEditorContribution } from 'vs/editor/common/editorCommon';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { ITextModel as EditorIModel } from 'vs/editor/common/model';
import { IEditor } from 'vs/platform/editor/common/editor';
import { Position } from 'vs/editor/common/core/position';
import { ISuggestion } from 'vs/editor/common/modes';
@@ -39,6 +40,8 @@ export const CONTEXT_BREAKPOINT_WIDGET_VISIBLE = new RawContextKey<boolean>('bre
export const CONTEXT_BREAKPOINTS_FOCUSED = new RawContextKey<boolean>('breakpointsFocused', true);
export const CONTEXT_WATCH_EXPRESSIONS_FOCUSED = new RawContextKey<boolean>('watchExpressionsFocused', true);
export const CONTEXT_VARIABLES_FOCUSED = new RawContextKey<boolean>('variablesFocused', true);
export const CONTEXT_EXPRESSION_SELECTED = new RawContextKey<boolean>('expressionSelected', false);
export const CONTEXT_BREAKPOINT_SELECTED = new RawContextKey<boolean>('breakpointSelected', false);
export const EDITOR_CONTRIBUTION_ID = 'editor.contrib.debug';
export const DEBUG_SCHEME = 'debug';
@@ -221,7 +224,8 @@ export interface IEnablement extends ITreeElement {
enabled: boolean;
}
export interface IRawBreakpoint {
export interface IBreakpointData {
id?: string;
lineNumber: number;
column?: number;
enabled?: boolean;
@@ -229,6 +233,11 @@ export interface IRawBreakpoint {
hitCondition?: string;
}
export interface IBreakpointUpdateData extends DebugProtocol.Breakpoint {
condition?: string;
hitCondition?: string;
}
export interface IBreakpoint extends IEnablement {
uri: uri;
lineNumber: number;
@@ -327,12 +336,12 @@ export enum State {
export interface IDebugConfiguration {
allowBreakpointsEverywhere: boolean;
openDebug: string;
openDebug: 'neverOpen' | 'openOnSessionStart' | 'openOnFirstSessionStart';
openExplorerOnEnd: boolean;
inlineValues: boolean;
hideActionBar: boolean;
showInStatusBar: string;
internalConsoleOptions: string;
showInStatusBar: 'never' | 'always' | 'onFirstSessionStart';
internalConsoleOptions: 'neverOpen' | 'openOnSessionStart' | 'openOnFirstSessionStart';
}
export interface IGlobalConfig {
@@ -345,7 +354,7 @@ export interface IEnvConfig {
name?: string;
type: string;
request: string;
internalConsoleOptions?: string;
internalConsoleOptions?: 'neverOpen' | 'openOnSessionStart' | 'openOnFirstSessionStart';
preLaunchTask?: string;
__restart?: any;
__sessionId?: string;
@@ -362,7 +371,7 @@ export interface IConfig extends IEnvConfig {
export interface ICompound {
name: string;
configurations: string[];
configurations: (string | { name: string, folder: string })[];
}
export interface IAdapterExecutable {
@@ -400,6 +409,7 @@ export interface IDebugConfigurationProvider {
handle: number;
resolveDebugConfiguration?(folderUri: uri | undefined, debugConfiguration: IConfig): TPromise<IConfig>;
provideDebugConfigurations?(folderUri: uri | undefined): TPromise<IConfig[]>;
debugAdapterExecutable(folderUri: uri | undefined): TPromise<IAdapterExecutable>;
}
export interface IConfigurationManager {
@@ -409,16 +419,19 @@ export interface IConfigurationManager {
canSetBreakpointsIn(model: EditorIModel): boolean;
/**
* Returns null for no folder workspace. Otherwise returns a launch object corresponding to the selected debug configuration.
* Returns an object containing the selected launch configuration and the selected configuration name. Both these fields can be null (no folder workspace).
*/
selectedLaunch: ILaunch;
selectedName: string;
selectedConfiguration: {
launch: ILaunch;
name: string;
};
selectConfiguration(launch: ILaunch, name?: string, debugStarted?: boolean): void;
getLaunches(): ILaunch[];
getLaunch(workspaceUri: uri): ILaunch | undefined;
/**
* Allows to register on change of selected debug configuration.
*/
@@ -426,7 +439,9 @@ export interface IConfigurationManager {
registerDebugConfigurationProvider(handle: number, debugConfigurationProvider: IDebugConfigurationProvider): void;
unregisterDebugConfigurationProvider(handle: number): void;
resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: any): TPromise<any>;
debugAdapterExecutable(folderUri: uri | undefined, type: string): TPromise<IAdapterExecutable | undefined>;
}
export interface ILaunch {
@@ -436,8 +451,21 @@ export interface ILaunch {
*/
uri: uri;
/**
* Name of the launch.
*/
name: string;
/**
* Workspace of the launch. Can be null.
*/
workspace: IWorkspaceFolder;
/**
* Should this launch be shown in the debug dropdown.
*/
hidden: boolean;
/**
* Returns a configuration with the specified name.
* Returns null if there is no configuration with the specified name.
@@ -454,7 +482,7 @@ export interface ILaunch {
* Returns the names of all configurations and compounds.
* Ignores configurations which are invalid.
*/
getConfigurationNames(): string[];
getConfigurationNames(includeCompounds?: boolean): string[];
/**
* Returns the resolved configuration.
@@ -512,17 +540,17 @@ export interface IDebugService {
/**
* Sets the focused stack frame and evaluates all expressions against the newly focused stack frame,
*/
focusStackFrameAndEvaluate(focusedStackFrame: IStackFrame, process?: IProcess, explicit?: boolean): TPromise<void>;
focusStackFrame(focusedStackFrame: IStackFrame, thread?: IThread, process?: IProcess, explicit?: boolean): void;
/**
* Adds new breakpoints to the model for the file specified with the uri. Notifies debug adapter of breakpoint changes.
*/
addBreakpoints(uri: uri, rawBreakpoints: IRawBreakpoint[]): TPromise<void>;
addBreakpoints(uri: uri, rawBreakpoints: IBreakpointData[]): TPromise<void>;
/**
* Updates the breakpoints and notifies the debug adapter of breakpoint changes.
* Updates the breakpoints.
*/
updateBreakpoints(uri: uri, data: { [id: string]: DebugProtocol.Breakpoint }): TPromise<void>;
updateBreakpoints(uri: uri, data: { [id: string]: IBreakpointUpdateData }, sendOnResourceSaved: boolean): void;
/**
* Enables or disables all breakpoints. If breakpoint is passed only enables or disables the passed breakpoint.
@@ -543,9 +571,9 @@ export interface IDebugService {
removeBreakpoints(id?: string): TPromise<any>;
/**
* Adds a new no name function breakpoint. The function breakpoint should be renamed once user enters the name.
* Adds a new function breakpoint for the given name.
*/
addFunctionBreakpoint(): void;
addFunctionBreakpoint(name?: string, id?: string): void;
/**
* Renames an already existing function breakpoint.
@@ -577,12 +605,12 @@ export interface IDebugService {
/**
* Adds a new watch expression and evaluates it against the debug adapter.
*/
addWatchExpression(name?: string): TPromise<void>;
addWatchExpression(name?: string): void;
/**
* Renames a watch expression and evaluates it against the debug adapter.
*/
renameWatchExpression(id: string, newName: string): TPromise<void>;
renameWatchExpression(id: string, newName: string): void;
/**
* Moves a watch expression to a new possition. Used for reordering watch expressions.
@@ -594,17 +622,12 @@ export interface IDebugService {
*/
removeWatchExpressions(id?: string): void;
/**
* Evaluates all watch expression.
*/
evaluateWatchExpressions(): TPromise<void>;
/**
* Starts debugging. If the configOrName is not passed uses the selected configuration in the debug dropdown.
* Also saves all files, manages if compounds are present in the configuration
* and resolveds configurations via DebugConfigurationProviders.
*/
startDebugging(root: IWorkspaceFolder, configOrName?: IConfig | string, noDebug?: boolean): TPromise<any>;
startDebugging(launch: ILaunch, configOrName?: IConfig | string, noDebug?: boolean): TPromise<any>;
/**
* Restarts a process or creates a new one if there is no active session.

View File

@@ -20,10 +20,11 @@ import { ISuggestion } from 'vs/editor/common/modes';
import { Position } from 'vs/editor/common/core/position';
import {
ITreeElement, IExpression, IExpressionContainer, IProcess, IStackFrame, IExceptionBreakpoint, IBreakpoint, IFunctionBreakpoint, IModel, IReplElementSource,
IConfig, ISession, IThread, IRawModelUpdate, IScope, IRawStoppedDetails, IEnablement, IRawBreakpoint, IExceptionInfo, IReplElement, ProcessState, IBreakpointsChangeEvent
IConfig, ISession, IThread, IRawModelUpdate, IScope, IRawStoppedDetails, IEnablement, IBreakpointData, IExceptionInfo, IReplElement, ProcessState, IBreakpointsChangeEvent, IBreakpointUpdateData
} from 'vs/workbench/parts/debug/common/debug';
import { Source } from 'vs/workbench/parts/debug/common/debugSource';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { mixin } from 'vs/base/common/objects';
const MAX_REPL_LENGTH = 10000;
@@ -380,9 +381,9 @@ export class StackFrame implements IStackFrame {
return `${this.name} (${this.source.inMemory ? this.source.name : this.source.uri.fsPath}:${this.range.startLineNumber})`;
}
public openInEditor(editorService: IWorkbenchEditorService, preserveFocus?: boolean, sideBySide?: boolean): TPromise<any> {
public openInEditor(editorService: IWorkbenchEditorService, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): TPromise<any> {
return !this.source.available ? TPromise.as(null) :
this.source.openInEditor(editorService, this.range, preserveFocus, sideBySide);
this.source.openInEditor(editorService, this.range, preserveFocus, sideBySide, pinned);
}
}
@@ -545,7 +546,7 @@ export class Process implements IProcess {
}
public getName(includeRoot: boolean): string {
return includeRoot ? `${this.configuration.name} (${resources.basenameOrAuthority(this.session.root.uri)})` : this.configuration.name;
return includeRoot && this.session.root ? `${this.configuration.name} (${resources.basenameOrAuthority(this.session.root.uri)})` : this.configuration.name;
}
public get state(): ProcessState {
@@ -560,6 +561,11 @@ export class Process implements IProcess {
let source = new Source(raw, this.getId());
if (this.sources.has(source.uri.toString())) {
source = this.sources.get(source.uri.toString());
source.raw = mixin(source.raw, raw);
if (source.raw && raw) {
// Always take the latest presentation hint from adapter #42139
source.raw.presentationHint = raw.presentationHint;
}
} else {
this.sources.set(source.uri.toString(), source);
}
@@ -674,7 +680,6 @@ export class Breakpoint implements IBreakpoint {
public message: string;
public endLineNumber: number;
public endColumn: number;
private id: string;
constructor(
public uri: uri,
@@ -683,13 +688,13 @@ export class Breakpoint implements IBreakpoint {
public enabled: boolean,
public condition: string,
public hitCondition: string,
public adapterData: any
public adapterData: any,
private id = generateUuid()
) {
if (enabled === undefined) {
this.enabled = true;
}
this.verified = false;
this.id = generateUuid();
}
public getId(): string {
@@ -699,13 +704,11 @@ export class Breakpoint implements IBreakpoint {
export class FunctionBreakpoint implements IFunctionBreakpoint {
private id: string;
public verified: boolean;
public idFromAdapter: number;
constructor(public name: string, public enabled: boolean, public hitCondition: string) {
constructor(public name: string, public enabled: boolean, public hitCondition: string, private id = generateUuid()) {
this.verified = false;
this.id = generateUuid();
}
public getId(): string {
@@ -852,6 +855,7 @@ export class Model implements IModel {
const ebp = this.exceptionBreakpoints.filter(ebp => ebp.filter === d.filter).pop();
return new ExceptionBreakpoint(d.filter, d.label, ebp ? ebp.enabled : d.default);
});
this._onDidChangeBreakpoints.fire();
}
}
@@ -864,8 +868,8 @@ export class Model implements IModel {
this._onDidChangeBreakpoints.fire();
}
public addBreakpoints(uri: uri, rawData: IRawBreakpoint[], fireEvent = true): Breakpoint[] {
const newBreakpoints = rawData.map(rawBp => new Breakpoint(uri, rawBp.lineNumber, rawBp.column, rawBp.enabled, rawBp.condition, rawBp.hitCondition, undefined));
public addBreakpoints(uri: uri, rawData: IBreakpointData[], fireEvent = true): Breakpoint[] {
const newBreakpoints = rawData.map(rawBp => new Breakpoint(uri, rawBp.lineNumber, rawBp.column, rawBp.enabled, rawBp.condition, rawBp.hitCondition, undefined, rawBp.id));
this.breakpoints = this.breakpoints.concat(newBreakpoints);
this.breakpointsActivated = true;
this.sortAndDeDup();
@@ -882,7 +886,7 @@ export class Model implements IModel {
this._onDidChangeBreakpoints.fire({ removed: toRemove });
}
public updateBreakpoints(data: { [id: string]: DebugProtocol.Breakpoint }): void {
public updateBreakpoints(data: { [id: string]: IBreakpointUpdateData }): void {
const updated: IBreakpoint[] = [];
this.breakpoints.forEach(bp => {
const bpData = data[bp.getId()];
@@ -895,6 +899,8 @@ export class Model implements IModel {
bp.idFromAdapter = bpData.id;
bp.message = bpData.message;
bp.adapterData = bpData.source ? bpData.source.adapterData : bp.adapterData;
bp.condition = bpData.condition || bp.condition;
bp.hitCondition = bpData.hitCondition || bp.hitCondition;
updated.push(bp);
}
});
@@ -955,8 +961,8 @@ export class Model implements IModel {
this._onDidChangeBreakpoints.fire({ changed: changed });
}
public addFunctionBreakpoint(functionName: string): FunctionBreakpoint {
const newFunctionBreakpoint = new FunctionBreakpoint(functionName, true, null);
public addFunctionBreakpoint(functionName: string, id: string): FunctionBreakpoint {
const newFunctionBreakpoint = new FunctionBreakpoint(functionName, true, null, id);
this.functionBreakpoints.push(newFunctionBreakpoint);
this._onDidChangeBreakpoints.fire({ added: [newFunctionBreakpoint] });
@@ -1046,45 +1052,20 @@ export class Model implements IModel {
return this.watchExpressions;
}
public addWatchExpression(process: IProcess, stackFrame: IStackFrame, name: string): TPromise<void> {
public addWatchExpression(process: IProcess, stackFrame: IStackFrame, name: string): IExpression {
const we = new Expression(name);
this.watchExpressions.push(we);
if (!name) {
this._onDidChangeWatchExpressions.fire(we);
return TPromise.as(null);
}
this._onDidChangeWatchExpressions.fire(we);
return this.evaluateWatchExpressions(process, stackFrame, we.getId());
return we;
}
public renameWatchExpression(process: IProcess, stackFrame: IStackFrame, id: string, newName: string): TPromise<void> {
public renameWatchExpression(process: IProcess, stackFrame: IStackFrame, id: string, newName: string): void {
const filtered = this.watchExpressions.filter(we => we.getId() === id);
if (filtered.length === 1) {
filtered[0].name = newName;
// Evaluate all watch expressions again since the new watch expression might have changed some.
return this.evaluateWatchExpressions(process, stackFrame).then(() => {
this._onDidChangeWatchExpressions.fire(filtered[0]);
});
this._onDidChangeWatchExpressions.fire(filtered[0]);
}
return TPromise.as(null);
}
public evaluateWatchExpressions(process: IProcess, stackFrame: IStackFrame, id: string = null): TPromise<void> {
if (id) {
const filtered = this.watchExpressions.filter(we => we.getId() === id);
if (filtered.length !== 1) {
return TPromise.as(null);
}
return filtered[0].evaluate(process, stackFrame, 'watch').then(() => {
this._onDidChangeWatchExpressions.fire(filtered[0]);
});
}
return TPromise.join(this.watchExpressions.map(we => we.evaluate(process, stackFrame, 'watch'))).then(() => {
this._onDidChangeWatchExpressions.fire();
});
}
public removeWatchExpressions(id: string = null): void {

View File

@@ -233,6 +233,20 @@ declare module DebugProtocol {
};
}
/** Event message for 'capabilities' event type.
The event indicates that one or more capabilities have changed.
Since the capabilities are dependent on the frontend and its UI, it might not be possible to change that at random times (or too late).
Consequently this event has a hint characteristic: a frontend can only be expected to make a 'best effort' in honouring individual capabilities but there are no guarantees.
Only changed capabilities need to be included, all other capabilities keep their values.
*/
export interface CapabilitiesEvent extends Event {
// event: 'capabilities';
body: {
/** The set of updated capabilities. */
capabilities: Capabilities;
};
}
/** runInTerminal request; value of command field is 'runInTerminal'.
With this request a debug adapter can run a command in a terminal.
*/
@@ -1073,6 +1087,8 @@ declare module DebugProtocol {
supportsDelayedStackTraceLoading?: boolean;
/** The debug adapter supports the 'loadedSources' request. */
supportsLoadedSourcesRequest?: boolean;
/** The debug adapter supports logpoints by interpreting the 'logMessage' attribute of the SourceBreakpoint. */
supportsLogPoints?: boolean;
}
/** An ExceptionBreakpointsFilter is shown in the UI as an option for configuring how exceptions are dealt with. */
@@ -1307,9 +1323,9 @@ declare module DebugProtocol {
visibility?: string;
}
/** Properties of a breakpoint passed to the setBreakpoints request. */
/** Properties of a breakpoint or logpoint passed to the setBreakpoints request. */
export interface SourceBreakpoint {
/** The source line of the breakpoint. */
/** The source line of the breakpoint or logpoint. */
line: number;
/** An optional source column of the breakpoint. */
column?: number;
@@ -1317,6 +1333,8 @@ declare module DebugProtocol {
condition?: string;
/** An optional expression that controls how many hits of the breakpoint are ignored. The backend is expected to interpret the expression as needed. */
hitCondition?: string;
/** If this attribute exists and is non-empty, the backend must not 'break' (stop) but log the message instead. Expressions within {} are interpolated. */
logMessage?: string;
}
/** Properties of a breakpoint passed to the setFunctionBreakpoints request. */

View File

@@ -11,6 +11,7 @@ import * as resources from 'vs/base/common/resources';
import { DEBUG_SCHEME } from 'vs/workbench/parts/debug/common/debug';
import { IRange } from 'vs/editor/common/core/range';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { Schemas } from 'vs/base/common/network';
const UNKNOWN_SOURCE_LABEL = nls.localize('unknownSource', "Unknown Source");
@@ -19,7 +20,7 @@ export class Source {
public readonly uri: uri;
public available: boolean;
constructor(public readonly raw: DebugProtocol.Source, sessionId: string) {
constructor(public raw: DebugProtocol.Source, sessionId: string) {
if (!raw) {
this.raw = { name: UNKNOWN_SOURCE_LABEL };
}
@@ -56,7 +57,7 @@ export class Source {
return this.uri.scheme === DEBUG_SCHEME;
}
public openInEditor(editorService: IWorkbenchEditorService, selection: IRange, preserveFocus?: boolean, sideBySide?: boolean): TPromise<any> {
public openInEditor(editorService: IWorkbenchEditorService, selection: IRange, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): TPromise<any> {
return !this.available ? TPromise.as(null) : editorService.openEditor({
resource: this.uri,
description: this.origin,
@@ -65,7 +66,7 @@ export class Source {
selection,
revealIfVisible: true,
revealInCenterIfOutsideViewport: true,
pinned: !preserveFocus && !this.inMemory
pinned: pinned || (!preserveFocus && !this.inMemory)
}
}, sideBySide);
}
@@ -76,7 +77,7 @@ export class Source {
let processId: string;
switch (modelUri.scheme) {
case 'file':
case Schemas.file:
path = paths.normalize(modelUri.fsPath, true);
break;
case DEBUG_SCHEME:
@@ -110,4 +111,4 @@ export class Source {
processId
};
}
}
}

View File

@@ -4,78 +4,92 @@
*--------------------------------------------------------------------------------------------*/
import Event, { Emitter } from 'vs/base/common/event';
import * as debug from 'vs/workbench/parts/debug/common/debug';
import { CONTEXT_EXPRESSION_SELECTED, IViewModel, IStackFrame, IProcess, IThread, IExpression, IFunctionBreakpoint, CONTEXT_BREAKPOINT_SELECTED } from 'vs/workbench/parts/debug/common/debug';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
export class ViewModel implements debug.IViewModel {
export class ViewModel implements IViewModel {
private _focusedStackFrame: debug.IStackFrame;
private _focusedProcess: debug.IProcess;
private selectedExpression: debug.IExpression;
private selectedFunctionBreakpoint: debug.IFunctionBreakpoint;
private _onDidFocusProcess: Emitter<debug.IProcess | undefined>;
private _onDidFocusStackFrame: Emitter<{ stackFrame: debug.IStackFrame, explicit: boolean }>;
private _onDidSelectExpression: Emitter<debug.IExpression>;
private _focusedStackFrame: IStackFrame;
private _focusedProcess: IProcess;
private _focusedThread: IThread;
private selectedExpression: IExpression;
private selectedFunctionBreakpoint: IFunctionBreakpoint;
private _onDidFocusProcess: Emitter<IProcess | undefined>;
private _onDidFocusStackFrame: Emitter<{ stackFrame: IStackFrame, explicit: boolean }>;
private _onDidSelectExpression: Emitter<IExpression>;
private multiProcessView: boolean;
private expressionSelectedContextKey: IContextKey<boolean>;
private breakpointSelectedContextKey: IContextKey<boolean>;
constructor() {
this._onDidFocusProcess = new Emitter<debug.IProcess | undefined>();
this._onDidFocusStackFrame = new Emitter<{ stackFrame: debug.IStackFrame, explicit: boolean }>();
this._onDidSelectExpression = new Emitter<debug.IExpression>();
constructor(contextKeyService: IContextKeyService) {
this._onDidFocusProcess = new Emitter<IProcess | undefined>();
this._onDidFocusStackFrame = new Emitter<{ stackFrame: IStackFrame, explicit: boolean }>();
this._onDidSelectExpression = new Emitter<IExpression>();
this.multiProcessView = false;
this.expressionSelectedContextKey = CONTEXT_EXPRESSION_SELECTED.bindTo(contextKeyService);
this.breakpointSelectedContextKey = CONTEXT_BREAKPOINT_SELECTED.bindTo(contextKeyService);
}
public getId(): string {
return 'root';
}
public get focusedProcess(): debug.IProcess {
public get focusedProcess(): IProcess {
return this._focusedProcess;
}
public get focusedThread(): debug.IThread {
public get focusedThread(): IThread {
return this._focusedStackFrame ? this._focusedStackFrame.thread : (this._focusedProcess ? this._focusedProcess.getAllThreads().pop() : null);
}
public get focusedStackFrame(): debug.IStackFrame {
public get focusedStackFrame(): IStackFrame {
return this._focusedStackFrame;
}
public setFocusedStackFrame(stackFrame: debug.IStackFrame, process: debug.IProcess, explicit: boolean): void {
this._focusedStackFrame = stackFrame;
if (process !== this._focusedProcess) {
public setFocus(stackFrame: IStackFrame, thread: IThread, process: IProcess, explicit: boolean): void {
let shouldEmit = this._focusedProcess !== process || this._focusedThread !== thread || this._focusedStackFrame !== stackFrame;
if (this._focusedProcess !== process) {
this._focusedProcess = process;
this._onDidFocusProcess.fire(process);
}
this._onDidFocusStackFrame.fire({ stackFrame, explicit });
this._focusedThread = thread;
this._focusedStackFrame = stackFrame;
if (shouldEmit) {
this._onDidFocusStackFrame.fire({ stackFrame, explicit });
}
}
public get onDidFocusProcess(): Event<debug.IProcess> {
public get onDidFocusProcess(): Event<IProcess> {
return this._onDidFocusProcess.event;
}
public get onDidFocusStackFrame(): Event<{ stackFrame: debug.IStackFrame, explicit: boolean }> {
public get onDidFocusStackFrame(): Event<{ stackFrame: IStackFrame, explicit: boolean }> {
return this._onDidFocusStackFrame.event;
}
public getSelectedExpression(): debug.IExpression {
public getSelectedExpression(): IExpression {
return this.selectedExpression;
}
public setSelectedExpression(expression: debug.IExpression) {
public setSelectedExpression(expression: IExpression) {
this.selectedExpression = expression;
this.expressionSelectedContextKey.set(!!expression);
this._onDidSelectExpression.fire(expression);
}
public get onDidSelectExpression(): Event<debug.IExpression> {
public get onDidSelectExpression(): Event<IExpression> {
return this._onDidSelectExpression.event;
}
public getSelectedFunctionBreakpoint(): debug.IFunctionBreakpoint {
public getSelectedFunctionBreakpoint(): IFunctionBreakpoint {
return this.selectedFunctionBreakpoint;
}
public setSelectedFunctionBreakpoint(functionBreakpoint: debug.IFunctionBreakpoint): void {
public setSelectedFunctionBreakpoint(functionBreakpoint: IFunctionBreakpoint): void {
this.selectedFunctionBreakpoint = functionBreakpoint;
this.breakpointSelectedContextKey.set(!!functionBreakpoint);
}
public isMultiProcessView(): boolean {

View File

@@ -15,10 +15,8 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { MenuId } from 'vs/platform/actions/common/actions';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { BaseDebugController, twistiePixels, renderViewTree } from 'vs/workbench/parts/debug/electron-browser/baseDebugView';
import { BaseDebugController, twistiePixels, renderViewTree } from 'vs/workbench/parts/debug/browser/baseDebugView';
import { ITree, IActionProvider, IDataSource, IRenderer, IAccessibilityProvider } from 'vs/base/parts/tree/browser/tree';
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
import { IAction, IActionItem } from 'vs/base/common/actions';
import { RestartAction, StopAction, ContinueAction, StepOverAction, StepIntoAction, StepOutAction, PauseAction, RestartFrameAction } from 'vs/workbench/parts/debug/browser/debugActions';
import { CopyStackTraceAction } from 'vs/workbench/parts/debug/electron-browser/electronDebugActions';
@@ -26,8 +24,9 @@ import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { Source } from 'vs/workbench/parts/debug/common/debugSource';
import { basenameOrAuthority } from 'vs/base/common/resources';
import { WorkbenchTree, IListService } from 'vs/platform/list/browser/listService';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { TreeResourceNavigator, WorkbenchTree } from 'vs/platform/list/browser/listService';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
const $ = dom.$;
@@ -39,18 +38,19 @@ export class CallStackView extends TreeViewsViewletPanel {
private onCallStackChangeScheduler: RunOnceScheduler;
private settings: any;
private needsRefresh: boolean;
private ignoreSelectionChangedEvent: boolean;
private treeContainer: HTMLElement;
constructor(
private options: IViewletViewOptions,
@IContextMenuService contextMenuService: IContextMenuService,
@IContextKeyService private contextKeyService: IContextKeyService,
@IDebugService private debugService: IDebugService,
@IKeybindingService keybindingService: IKeybindingService,
@IInstantiationService private instantiationService: IInstantiationService,
@IThemeService private themeService: IThemeService,
@IListService private listService: IListService
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IConfigurationService configurationService: IConfigurationService,
) {
super({ ...(options as IViewOptions), ariaHeaderLabel: nls.localize('callstackSection', "Call Stack Section") }, keybindingService, contextMenuService);
super({ ...(options as IViewOptions), ariaHeaderLabel: nls.localize('callstackSection', "Call Stack Section") }, keybindingService, contextMenuService, configurationService);
this.settings = options.viewletSettings;
// Create scheduler to prevent unnecessary flashing of tree when reacting to changes
@@ -95,26 +95,42 @@ export class CallStackView extends TreeViewsViewletPanel {
dom.addClass(container, 'debug-call-stack');
this.treeContainer = renderViewTree(container);
const actionProvider = new CallStackActionProvider(this.debugService, this.keybindingService);
const controller = this.instantiationService.createInstance(CallStackController, actionProvider, MenuId.DebugCallStackContext);
const controller = this.instantiationService.createInstance(CallStackController, actionProvider, MenuId.DebugCallStackContext, {});
this.tree = new WorkbenchTree(this.treeContainer, {
this.tree = this.instantiationService.createInstance(WorkbenchTree, this.treeContainer, {
dataSource: new CallStackDataSource(),
renderer: this.instantiationService.createInstance(CallStackRenderer),
accessibilityProvider: this.instantiationService.createInstance(CallstackAccessibilityProvider),
controller
}, {
ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'callStackAriaLabel' }, "Debug Call Stack"),
twistiePixels,
keyboardSupport: false
}, this.contextKeyService, this.listService, this.themeService);
twistiePixels
});
this.disposables.push(this.tree.onDidChangeSelection(event => {
if (event && event.payload && event.payload.origin === 'keyboard') {
const element = this.tree.getFocus();
if (element instanceof ThreadAndProcessIds) {
controller.showMoreStackFrames(this.tree, element);
} else if (element instanceof StackFrame) {
controller.focusStackFrame(element, event, false);
const callstackNavigator = new TreeResourceNavigator(this.tree);
this.disposables.push(callstackNavigator);
this.disposables.push(callstackNavigator.openResource(e => {
if (this.ignoreSelectionChangedEvent) {
return;
}
const element = e.element;
if (element instanceof StackFrame) {
this.debugService.focusStackFrame(element, element.thread, element.thread.process, true);
element.openInEditor(this.editorService, e.editorOptions.preserveFocus, e.sideBySide, e.editorOptions.pinned).done(undefined, errors.onUnexpectedError);
}
if (element instanceof Thread) {
this.debugService.focusStackFrame(undefined, element, element.process, true);
}
if (element instanceof Process) {
this.debugService.focusStackFrame(undefined, undefined, element, true);
}
if (element instanceof ThreadAndProcessIds) {
const process = this.debugService.getModel().getProcesses().filter(p => p.getId() === element.processId).pop();
const thread = process && process.getThread(element.threadId);
if (thread) {
(<Thread>thread).fetchCallStack()
.done(() => this.tree.refresh(), errors.onUnexpectedError);
}
}
}));
@@ -129,8 +145,14 @@ export class CallStackView extends TreeViewsViewletPanel {
this.onCallStackChangeScheduler.schedule();
}
}));
this.disposables.push(this.debugService.getViewModel().onDidFocusStackFrame(() =>
this.updateTreeSelection().done(undefined, errors.onUnexpectedError)));
this.disposables.push(this.debugService.getViewModel().onDidFocusStackFrame(() => {
if (!this.isVisible) {
this.needsRefresh = true;
return;
}
this.updateTreeSelection().done(undefined, errors.onUnexpectedError);
}));
// Schedule the update of the call stack tree if the viewlet is opened after a session started #14684
if (this.debugService.state === State.Stopped) {
@@ -138,6 +160,13 @@ export class CallStackView extends TreeViewsViewletPanel {
}
}
layoutBody(size: number): void {
if (this.treeContainer) {
this.treeContainer.style.height = size + 'px';
}
super.layoutBody(size);
}
private updateTreeSelection(): TPromise<void> {
if (!this.tree.getInput()) {
// Tree not initialized yet
@@ -147,13 +176,22 @@ export class CallStackView extends TreeViewsViewletPanel {
const stackFrame = this.debugService.getViewModel().focusedStackFrame;
const thread = this.debugService.getViewModel().focusedThread;
const process = this.debugService.getViewModel().focusedProcess;
const updateSelection = (element: IStackFrame | IProcess) => {
this.ignoreSelectionChangedEvent = true;
try {
this.tree.setSelection([element]);
} finally {
this.ignoreSelectionChangedEvent = false;
}
};
if (!thread) {
if (!process) {
this.tree.clearSelection();
return TPromise.as(null);
}
this.tree.setSelection([process]);
updateSelection(process);
return this.tree.reveal(process);
}
@@ -162,7 +200,7 @@ export class CallStackView extends TreeViewsViewletPanel {
return TPromise.as(null);
}
this.tree.setSelection([stackFrame]);
updateSelection(stackFrame);
return this.tree.reveal(stackFrame);
});
}
@@ -182,20 +220,6 @@ export class CallStackView extends TreeViewsViewletPanel {
}
class CallStackController extends BaseDebugController {
protected onLeftClick(tree: ITree, element: any, event: IMouseEvent): boolean {
if (element instanceof ThreadAndProcessIds) {
return this.showMoreStackFrames(tree, element);
}
if (element instanceof StackFrame) {
super.onLeftClick(tree, element, event);
this.focusStackFrame(element, event, event.detail !== 2);
return true;
}
return super.onLeftClick(tree, element, event);
}
protected getContext(element: any): any {
if (element instanceof StackFrame) {
if (element.source.inMemory) {
@@ -208,25 +232,6 @@ class CallStackController extends BaseDebugController {
return element.threadId;
}
}
// user clicked / pressed on 'Load More Stack Frames', get those stack frames and refresh the tree.
public showMoreStackFrames(tree: ITree, threadAndProcessIds: ThreadAndProcessIds): boolean {
const process = this.debugService.getModel().getProcesses().filter(p => p.getId() === threadAndProcessIds.processId).pop();
const thread = process && process.getThread(threadAndProcessIds.threadId);
if (thread) {
(<Thread>thread).fetchCallStack()
.done(() => tree.refresh(), errors.onUnexpectedError);
}
return true;
}
public focusStackFrame(stackFrame: IStackFrame, event: any, preserveFocus: boolean): void {
this.debugService.focusStackFrameAndEvaluate(stackFrame, undefined, true).then(() => {
const sideBySide = (event && (event.ctrlKey || event.metaKey));
return stackFrame.openInEditor(this.editorService, preserveFocus, sideBySide);
}, errors.onUnexpectedError);
}
}

View File

@@ -17,7 +17,7 @@ import { ToggleViewletAction, Extensions as ViewletExtensions, ViewletRegistry,
import { TogglePanelAction, Extensions as PanelExtensions, PanelRegistry, PanelDescriptor } from 'vs/workbench/browser/panel';
import { StatusbarItemDescriptor, StatusbarAlignment, IStatusbarRegistry, Extensions as StatusExtensions } from 'vs/workbench/browser/parts/statusbar/statusbar';
import { VariablesView } from 'vs/workbench/parts/debug/electron-browser/variablesView';
import { BreakpointsView } from 'vs/workbench/parts/debug/electron-browser/breakpointsView';
import { BreakpointsView } from 'vs/workbench/parts/debug/browser/breakpointsView';
import { WatchExpressionsView } from 'vs/workbench/parts/debug/electron-browser/watchExpressionsView';
import { CallStackView } from 'vs/workbench/parts/debug/electron-browser/callStackView';
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
@@ -38,10 +38,10 @@ import { DebugContentProvider } from 'vs/workbench/parts/debug/browser/debugCont
import 'vs/workbench/parts/debug/electron-browser/debugEditorContribution';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import * as debugCommands from 'vs/workbench/parts/debug/electron-browser/debugCommands';
import { registerCommands } from 'vs/workbench/parts/debug/browser/debugCommands';
import { IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen';
import { StatusBarColorProvider } from 'vs/workbench/parts/debug/electron-browser/statusbarColorProvider';
import { ViewLocation, ViewsRegistry } from 'vs/workbench/browser/parts/views/viewsRegistry';
import { StatusBarColorProvider } from 'vs/workbench/parts/debug/browser/statusbarColorProvider';
import { ViewLocation, ViewsRegistry } from 'vs/workbench/common/views';
import { isMacintosh } from 'vs/base/common/platform';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import URI from 'vs/base/common/uri';
@@ -50,6 +50,7 @@ import { Repl } from 'vs/workbench/parts/debug/electron-browser/repl';
import { DebugQuickOpenHandler } from 'vs/workbench/parts/debug/browser/debugQuickOpen';
import { DebugStatus } from 'vs/workbench/parts/debug/browser/debugStatus';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration';
class OpenDebugViewletAction extends ToggleViewletAction {
public static readonly ID = VIEWLET_ID;
@@ -107,10 +108,10 @@ Registry.as<PanelRegistry>(PanelExtensions.Panels).registerPanel(new PanelDescri
Registry.as<PanelRegistry>(PanelExtensions.Panels).setDefaultPanelId(REPL_ID);
// Register default debug views
ViewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize('variables', "Variables"), ctor: VariablesView, order: 10, size: 40, location: ViewLocation.Debug, canToggleVisibility: true }]);
ViewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize('watch', "Watch"), ctor: WatchExpressionsView, order: 20, size: 10, location: ViewLocation.Debug, canToggleVisibility: true }]);
ViewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize('callStack', "Call Stack"), ctor: CallStackView, order: 30, size: 30, location: ViewLocation.Debug, canToggleVisibility: true }]);
ViewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), ctor: BreakpointsView, order: 40, size: 20, location: ViewLocation.Debug, canToggleVisibility: true }]);
ViewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize('variables', "Variables"), ctor: VariablesView, order: 10, weight: 40, location: ViewLocation.Debug, canToggleVisibility: true }]);
ViewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize('watch', "Watch"), ctor: WatchExpressionsView, order: 20, weight: 10, location: ViewLocation.Debug, canToggleVisibility: true }]);
ViewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize('callStack', "Call Stack"), ctor: CallStackView, order: 30, weight: 30, location: ViewLocation.Debug, canToggleVisibility: true }]);
ViewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), ctor: BreakpointsView, order: 40, weight: 20, location: ViewLocation.Debug, canToggleVisibility: true }]);
// register action to open viewlet
const registry = Registry.as<IWorkbenchActionRegistry>(WorkbenchActionRegistryExtensions.WorkbenchActions);
@@ -201,17 +202,18 @@ configurationRegistry.registerConfiguration({
'debug.openDebug': {
enum: ['neverOpen', 'openOnSessionStart', 'openOnFirstSessionStart'],
default: 'openOnFirstSessionStart',
description: nls.localize('openDebug', "Controls whether debug viewlet should be open on debugging session start.")
description: nls.localize('openDebug', "Controls whether debug view should be open on debugging session start.")
},
'launch': {
type: 'object',
description: nls.localize({ comment: ['This is the description for a setting'], key: 'launch' }, "Global debug launch configuration. Should be used as an alternative to 'launch.json' that is shared across workspaces"),
default: {}
default: { configurations: [], compounds: [] },
$ref: launchSchemaId
}
}
});
debugCommands.registerCommands();
registerCommands();
// Register Debug Status
const statusBar = Registry.as<IStatusbarRegistry>(StatusExtensions.Statusbar);
@@ -223,7 +225,7 @@ if (isMacintosh) {
const registerTouchBarEntry = (id: string, title: string, order, when: ContextKeyExpr, icon: string) => {
MenuRegistry.appendMenuItem(MenuId.TouchBarContext, {
command: {
id, title, iconPath: URI.parse(require.toUrl(`vs/workbench/parts/debug/electron-browser/media/${icon}`)).fsPath
id, title, iconPath: { dark: URI.parse(require.toUrl(`vs/workbench/parts/debug/electron-browser/media/${icon}`)).fsPath }
},
when,
group: '9_debug',
@@ -239,4 +241,4 @@ if (isMacintosh) {
registerTouchBarEntry(StepOutAction.ID, StepOutAction.LABEL, 4, CONTEXT_DEBUG_STATE.isEqualTo('stopped'), 'stepout-tb.png');
registerTouchBarEntry(RestartAction.ID, RestartAction.LABEL, 5, CONTEXT_IN_DEBUG_MODE, 'restart-tb.png');
registerTouchBarEntry(StopAction.ID, StopAction.LABEL, 6, CONTEXT_IN_DEBUG_MODE, 'stop-tb.png');
}
}

View File

@@ -14,25 +14,27 @@ import * as objects from 'vs/base/common/objects';
import uri from 'vs/base/common/uri';
import * as paths from 'vs/base/common/paths';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { IModel } from 'vs/editor/common/editorCommon';
import { ITextModel } from 'vs/editor/common/model';
import { IEditor } from 'vs/platform/editor/common/editor';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import * as extensionsRegistry from 'vs/platform/extensions/common/extensionsRegistry';
import * as extensionsRegistry from 'vs/workbench/services/extensions/common/extensionsRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IFileService } from 'vs/platform/files/common/files';
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IDebugConfigurationProvider, IRawAdapter, ICompound, IDebugConfiguration, IConfig, IEnvConfig, IGlobalConfig, IConfigurationManager, ILaunch } from 'vs/workbench/parts/debug/common/debug';
import { IDebugConfigurationProvider, IRawAdapter, ICompound, IDebugConfiguration, IConfig, IEnvConfig, IGlobalConfig, IConfigurationManager, ILaunch, IAdapterExecutable } from 'vs/workbench/parts/debug/common/debug';
import { Adapter } from 'vs/workbench/parts/debug/node/debugAdapter';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver';
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration';
import { IPreferencesService } from 'vs/workbench/parts/preferences/common/preferences';
// debuggers extension point
export const debuggersExtPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint<IRawAdapter[]>('debuggers', [], {
@@ -102,11 +104,11 @@ export const debuggersExtPoint = extensionsRegistry.ExtensionsRegistry.registerE
}
},
osx: {
description: nls.localize('vscode.extension.contributes.debuggers.osx', "OS X specific settings."),
description: nls.localize('vscode.extension.contributes.debuggers.osx', "macOS specific settings."),
type: 'object',
properties: {
runtime: {
description: nls.localize('vscode.extension.contributes.debuggers.osx.runtime', "Runtime used for OSX."),
description: nls.localize('vscode.extension.contributes.debuggers.osx.runtime', "Runtime used for macOS."),
type: 'string'
}
}
@@ -147,14 +149,12 @@ const breakpointsExtPoint = extensionsRegistry.ExtensionsRegistry.registerExtens
});
// debug general schema
export const schemaId = 'vscode://schemas/launch';
const defaultCompound: ICompound = { name: 'Compound', configurations: [] };
const schema: IJSONSchema = {
id: schemaId,
id: launchSchemaId,
type: 'object',
title: nls.localize('app.launch.json.title', "Launch"),
required: ['version', 'configurations'],
required: [],
default: { version: '0.2.0', configurations: [], compounds: [] },
properties: {
version: {
@@ -186,7 +186,23 @@ const schema: IJSONSchema = {
type: 'array',
default: [],
items: {
type: 'string'
oneOf: [{
enum: [],
description: nls.localize('useUniqueNames', "Please use unique configuration names.")
}, {
type: 'object',
required: ['name'],
properties: {
name: {
enum: [],
description: nls.localize('app.launch.json.compound.name', "Name of compound. Appears in the launch configuration drop down menu.")
},
folder: {
enum: [],
description: nls.localize('app.launch.json.compound.folder', "Name of folder in which the compound is located.")
}
}
}]
},
description: nls.localize('app.launch.json.compounds.configurations', "Names of configurations that will be started as part of this compound.")
}
@@ -201,7 +217,7 @@ const schema: IJSONSchema = {
};
const jsonRegistry = <IJSONContributionRegistry>Registry.as(JSONExtensions.JSONContribution);
jsonRegistry.registerSchema(schemaId, schema);
jsonRegistry.registerSchema(launchSchemaId, schema);
const DEBUG_SELECTED_CONFIG_NAME_KEY = 'debug.selectedconfigname';
const DEBUG_SELECTED_ROOT = 'debug.selectedroot';
@@ -209,8 +225,8 @@ export class ConfigurationManager implements IConfigurationManager {
private adapters: Adapter[];
private breakpointModeIdsSet = new Set<string>();
private launches: ILaunch[];
private _selectedName: string;
private _selectedLaunch: ILaunch;
private selectedName: string;
private selectedLaunch: ILaunch;
private toDispose: IDisposable[];
private _onDidSelectConfigurationName = new Emitter<void>();
private providers: IDebugConfigurationProvider[];
@@ -232,7 +248,7 @@ export class ConfigurationManager implements IConfigurationManager {
this.registerListeners(lifecycleService);
this.initLaunches();
const previousSelectedRoot = this.storageService.get(DEBUG_SELECTED_ROOT, StorageScope.WORKSPACE);
const filtered = this.launches.filter(l => l.workspace.uri.toString() === previousSelectedRoot);
const filtered = this.launches.filter(l => l.uri.toString() === previousSelectedRoot);
this.selectConfiguration(filtered.length ? filtered[0] : undefined, this.storageService.get(DEBUG_SELECTED_CONFIG_NAME_KEY, StorageScope.WORKSPACE));
}
@@ -276,6 +292,14 @@ export class ConfigurationManager implements IConfigurationManager {
.then(results => results.reduce((first, second) => first.concat(second), []));
}
public debugAdapterExecutable(folderUri: uri | undefined, type: string): TPromise<IAdapterExecutable | undefined> {
const providers = this.providers.filter(p => p.type === type && p.debugAdapterExecutable);
if (providers.length === 1) {
return providers[0].debugAdapterExecutable(folderUri);
}
return TPromise.as(undefined);
}
private registerListeners(lifecycleService: ILifecycleService): void {
debuggersExtPoint.setHandler((extensions) => {
extensions.forEach(extension => {
@@ -293,7 +317,7 @@ export class ConfigurationManager implements IConfigurationManager {
if (duplicate) {
duplicate.merge(rawAdapter, extension.description);
} else {
this.adapters.push(new Adapter(rawAdapter, extension.description, this.configurationService, this.commandService));
this.adapters.push(new Adapter(this, rawAdapter, extension.description, this.configurationService, this.commandService));
}
});
});
@@ -310,6 +334,8 @@ export class ConfigurationManager implements IConfigurationManager {
items.defaultSnippets.push(...configurationSnippets);
}
});
this.setCompoundSchemaValues();
});
breakpointsExtPoint.setHandler(extensions => {
@@ -323,10 +349,12 @@ export class ConfigurationManager implements IConfigurationManager {
this.toDispose.push(this.contextService.onDidChangeWorkspaceFolders(() => {
this.initLaunches();
this.selectConfiguration();
this.setCompoundSchemaValues();
}));
this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => {
if (e.affectsConfiguration('launch')) {
this.selectConfiguration();
this.setCompoundSchemaValues();
}
}));
@@ -335,42 +363,75 @@ export class ConfigurationManager implements IConfigurationManager {
private initLaunches(): void {
this.launches = this.contextService.getWorkspace().folders.map(folder => this.instantiationService.createInstance(Launch, this, folder));
if (this.launches.indexOf(this._selectedLaunch) === -1) {
this._selectedLaunch = undefined;
if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
this.launches.push(this.instantiationService.createInstance(WorkspaceLaunch, this));
}
this.launches.push(this.instantiationService.createInstance(UserLaunch, this));
if (this.launches.indexOf(this.selectedLaunch) === -1) {
this.selectedLaunch = undefined;
}
}
private setCompoundSchemaValues(): void {
const compoundConfigurationsSchema = (<IJSONSchema>schema.properties['compounds'].items).properties['configurations'];
const launchNames = this.launches.map(l =>
l.getConfigurationNames(false)).reduce((first, second) => first.concat(second), []);
(<IJSONSchema>compoundConfigurationsSchema.items).oneOf[0].enum = launchNames;
(<IJSONSchema>compoundConfigurationsSchema.items).oneOf[1].properties.name.enum = launchNames;
const folderNames = this.contextService.getWorkspace().folders.map(f => f.name);
(<IJSONSchema>compoundConfigurationsSchema.items).oneOf[1].properties.folder.enum = folderNames;
jsonRegistry.registerSchema(launchSchemaId, schema);
}
public getLaunches(): ILaunch[] {
return this.launches;
}
public get selectedLaunch(): ILaunch {
return this._selectedLaunch;
public getLaunch(workspaceUri: uri): ILaunch {
if (!uri.isUri(workspaceUri)) {
return undefined;
}
return this.launches.filter(l => l.workspace && l.workspace.uri.toString() === workspaceUri.toString()).pop();
}
public get selectedName(): string {
return this._selectedName;
public get selectedConfiguration(): { launch: ILaunch, name: string } {
return {
launch: this.selectedLaunch,
name: this.selectedName
};
}
public get onDidSelectConfiguration(): Event<void> {
return this._onDidSelectConfigurationName.event;
}
public getWorkspaceLaunch(): ILaunch {
if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
return this.launches[this.launches.length - 1];
}
return undefined;
}
public selectConfiguration(launch?: ILaunch, name?: string, debugStarted?: boolean): void {
const previousLaunch = this._selectedLaunch;
const previousName = this._selectedName;
const previousLaunch = this.selectedLaunch;
const previousName = this.selectedName;
if (!launch) {
launch = this.selectedLaunch && this.selectedLaunch.getConfigurationNames().length ? this.selectedLaunch : first(this.launches, l => !!l.getConfigurationNames().length, this.launches.length ? this.launches[0] : undefined);
}
this._selectedLaunch = launch;
this.selectedLaunch = launch;
const names = launch ? launch.getConfigurationNames() : [];
if (name && names.indexOf(name) >= 0) {
this._selectedName = name;
this.selectedName = name;
}
if (names.indexOf(this.selectedName) === -1) {
this._selectedName = names.length ? names[0] : undefined;
this.selectedName = names.length ? names[0] : undefined;
}
if (this.selectedLaunch !== previousLaunch || this.selectedName !== previousName) {
@@ -378,10 +439,10 @@ export class ConfigurationManager implements IConfigurationManager {
}
}
public canSetBreakpointsIn(model: IModel): boolean {
public canSetBreakpointsIn(model: ITextModel): boolean {
const modeId = model ? model.getLanguageIdentifier().language : null;
if (!modeId || modeId === 'jsonc') {
// do not allow breakpoints in our settings files
if (!modeId || modeId === 'jsonc' || modeId === 'log') {
// do not allow breakpoints in our settings files and output
return false;
}
if (this.configurationService.getValue<IDebugConfiguration>('debug').allowBreakpointsEverywhere) {
@@ -403,6 +464,7 @@ export class ConfigurationManager implements IConfigurationManager {
}
const editor = this.editorService.getActiveEditor();
let candidates: Adapter[];
if (editor) {
const codeEditor = editor.getControl();
if (isCodeEditor(codeEditor)) {
@@ -412,10 +474,16 @@ export class ConfigurationManager implements IConfigurationManager {
if (adapters.length === 1) {
return TPromise.as(adapters[0]);
}
if (adapters.length > 1) {
candidates = adapters;
}
}
}
return this.quickOpenService.pick([...this.adapters.filter(a => a.hasInitialConfiguration() || a.hasConfigurationProvider), { label: 'More...', separator: { border: true } }], { placeHolder: nls.localize('selectDebug', "Select Environment") })
if (!candidates) {
candidates = this.adapters.filter(a => a.hasInitialConfiguration() || a.hasConfigurationProvider);
}
return this.quickOpenService.pick([...candidates, { label: 'More...', separator: { border: true } }], { placeHolder: nls.localize('selectDebug', "Select Environment") })
.then(picked => {
if (picked instanceof Adapter) {
return picked;
@@ -430,8 +498,8 @@ export class ConfigurationManager implements IConfigurationManager {
private store(): void {
this.storageService.store(DEBUG_SELECTED_CONFIG_NAME_KEY, this.selectedName, StorageScope.WORKSPACE);
if (this._selectedLaunch) {
this.storageService.store(DEBUG_SELECTED_ROOT, this._selectedLaunch.workspace.uri.toString(), StorageScope.WORKSPACE);
if (this.selectedLaunch) {
this.storageService.store(DEBUG_SELECTED_ROOT, this.selectedLaunch.uri.toString(), StorageScope.WORKSPACE);
}
}
@@ -446,15 +514,32 @@ class Launch implements ILaunch {
private configurationManager: ConfigurationManager,
public workspace: IWorkspaceFolder,
@IFileService private fileService: IFileService,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IConfigurationService private configurationService: IConfigurationService,
@IConfigurationResolverService private configurationResolverService: IConfigurationResolverService
@IWorkbenchEditorService protected editorService: IWorkbenchEditorService,
@IConfigurationService protected configurationService: IConfigurationService,
@IConfigurationResolverService private configurationResolverService: IConfigurationResolverService,
@IWorkspaceContextService protected contextService: IWorkspaceContextService
) {
// noop
}
public get uri(): uri {
return this.workspace.uri.with({ path: paths.join(this.workspace.uri.path, '/.vscode/launch.json') });
}
public get name(): string {
return this.workspace.name;
}
public get hidden(): boolean {
return false;
}
protected getConfig(): IGlobalConfig {
return this.configurationService.inspect<IGlobalConfig>('launch', { resource: this.workspace.uri }).workspaceFolder;
}
public getCompound(name: string): ICompound {
const config = this.configurationService.getValue<IGlobalConfig>('launch', { resource: this.workspace.uri });
const config = this.getConfig();
if (!config || !config.compounds) {
return null;
}
@@ -462,13 +547,13 @@ class Launch implements ILaunch {
return config.compounds.filter(compound => compound.name === name).pop();
}
public getConfigurationNames(): string[] {
const config = this.configurationService.getValue<IGlobalConfig>('launch', { resource: this.workspace.uri });
public getConfigurationNames(includeCompounds = true): string[] {
const config = this.getConfig();
if (!config || !config.configurations || !Array.isArray(config.configurations)) {
return [];
} else {
const names = config.configurations.filter(cfg => cfg && typeof cfg.name === 'string').map(cfg => cfg.name);
if (names.length > 0 && config.compounds) {
if (includeCompounds && config.compounds) {
if (config.compounds) {
names.push(...config.compounds.filter(compound => typeof compound.name === 'string' && compound.configurations && compound.configurations.length)
.map(compound => compound.name));
@@ -480,7 +565,8 @@ class Launch implements ILaunch {
}
public getConfiguration(name: string): IConfig {
const config = objects.deepClone(this.configurationService.getValue<IGlobalConfig>('launch', { resource: this.workspace.uri }));
// We need to clone the configuration in order to be able to make changes to it #42198
const config = objects.deepClone(this.getConfig());
if (!config || !config.configurations) {
return null;
}
@@ -488,6 +574,18 @@ class Launch implements ILaunch {
return config.configurations.filter(config => config && config.name === name).shift();
}
protected getWorkspaceForResolving(): IWorkspaceFolder {
if (this.workspace) {
return this.workspace;
}
if (this.contextService.getWorkspace().folders.length === 1) {
return this.contextService.getWorkspace().folders[0];
}
return undefined;
}
public resolveConfiguration(config: IConfig): TPromise<IConfig> {
const result = objects.deepClone(config) as IConfig;
// Set operating system specific properties #1873
@@ -504,22 +602,18 @@ class Launch implements ILaunch {
// massage configuration attributes - append workspace path to relatvie paths, substitute variables in paths.
Object.keys(result).forEach(key => {
result[key] = this.configurationResolverService.resolveAny(this.workspace, result[key]);
result[key] = this.configurationResolverService.resolveAny(this.getWorkspaceForResolving(), result[key]);
});
const adapter = this.configurationManager.getAdapter(result.type);
return this.configurationResolverService.resolveInteractiveVariables(result, adapter ? adapter.variables : null);
}
public get uri(): uri {
return this.workspace.uri.with({ path: paths.join(this.workspace.uri.path, '/.vscode/launch.json') });
}
public openConfigFile(sideBySide: boolean, type?: string): TPromise<IEditor> {
const resource = this.uri;
let configFileCreated = false;
let pinned = false;
return this.fileService.resolveContent(resource).then(content => content, err => {
return this.fileService.resolveContent(resource).then(content => content.value, err => {
// launch.json not found: create one by collecting launch configs from debugConfigProviders
@@ -537,20 +631,20 @@ class Launch implements ILaunch {
return undefined;
}
configFileCreated = true;
pinned = true; // pin only if config file is created #8727
return this.fileService.updateContent(resource, content).then(() => {
// convert string into IContent; see #32135
return { value: content };
return content;
});
});
}).then(content => {
if (!content) {
return undefined;
}
const index = content.value.indexOf(`"${this.configurationManager.selectedName}"`);
const index = content.indexOf(`"${this.configurationManager.selectedConfiguration.name}"`);
let startLineNumber = 1;
for (let i = 0; i < index; i++) {
if (content.value.charAt(i) === '\n') {
if (content.charAt(i) === '\n') {
startLineNumber++;
}
}
@@ -561,7 +655,7 @@ class Launch implements ILaunch {
options: {
forceOpen: true,
selection,
pinned: configFileCreated, // pin only if config file is created #8727
pinned,
revealIfVisible: true
},
}, sideBySide);
@@ -570,3 +664,68 @@ class Launch implements ILaunch {
});
}
}
class WorkspaceLaunch extends Launch implements ILaunch {
constructor(
configurationManager: ConfigurationManager,
@IFileService fileService: IFileService,
@IWorkbenchEditorService editorService: IWorkbenchEditorService,
@IConfigurationService configurationService: IConfigurationService,
@IConfigurationResolverService configurationResolverService: IConfigurationResolverService,
@IWorkspaceContextService contextService: IWorkspaceContextService
) {
super(configurationManager, undefined, fileService, editorService, configurationService, configurationResolverService, contextService);
}
get uri(): uri {
return this.contextService.getWorkspace().configuration;
}
get name(): string {
return nls.localize('workspace', "workspace");
}
protected getConfig(): IGlobalConfig {
return this.configurationService.inspect<IGlobalConfig>('launch').workspace;
}
openConfigFile(sideBySide: boolean, type?: string): TPromise<IEditor> {
return this.editorService.openEditor({ resource: this.contextService.getWorkspace().configuration });
}
}
class UserLaunch extends Launch implements ILaunch {
constructor(
configurationManager: ConfigurationManager,
@IFileService fileService: IFileService,
@IWorkbenchEditorService editorService: IWorkbenchEditorService,
@IConfigurationService configurationService: IConfigurationService,
@IConfigurationResolverService configurationResolverService: IConfigurationResolverService,
@IPreferencesService private preferencesService: IPreferencesService,
@IWorkspaceContextService contextService: IWorkspaceContextService
) {
super(configurationManager, undefined, fileService, editorService, configurationService, configurationResolverService, contextService);
}
get uri(): uri {
return this.preferencesService.userSettingsResource;
}
get name(): string {
return nls.localize('user settings', "user settings");
}
public get hidden(): boolean {
return true;
}
protected getConfig(): IGlobalConfig {
return this.configurationService.inspect<IGlobalConfig>('launch').user;
}
openConfigFile(sideBySide: boolean, type?: string): TPromise<IEditor> {
return this.preferencesService.openGlobalSettings();
}
}

View File

@@ -19,7 +19,8 @@ import { StandardTokenType } from 'vs/editor/common/modes';
import { DEFAULT_WORD_REGEXP } from 'vs/editor/common/model/wordHelper';
import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser';
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { IDecorationOptions, IModelDecorationOptions, IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/editorCommon';
import { IDecorationOptions } from 'vs/editor/common/editorCommon';
import { IModelDecorationOptions, IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/model';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { Range } from 'vs/editor/common/core/range';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -27,7 +28,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService, ContextSubMenu } from 'vs/platform/contextview/browser/contextView';
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { DebugHoverWidget } from 'vs/workbench/parts/debug/electron-browser/debugHover';
import { RemoveBreakpointAction, EditConditionalBreakpointAction, EnableBreakpointAction, DisableBreakpointAction, AddConditionalBreakpointAction } from 'vs/workbench/parts/debug/browser/debugActions';
import { IDebugEditorContribution, IDebugService, State, IBreakpoint, EDITOR_CONTRIBUTION_ID, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, IStackFrame, IDebugConfiguration, IExpression, IExceptionInfo } from 'vs/workbench/parts/debug/common/debug';
@@ -40,7 +41,7 @@ import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands';
import { first } from 'vs/base/common/arrays';
import { IMarginData } from 'vs/editor/browser/controller/mouseTarget';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IListService } from 'vs/platform/list/browser/listService';
import { ContextSubMenu } from 'vs/base/browser/contextmenu';
const HOVER_DELAY = 300;
const LAUNCH_JSON_REGEX = /launch\.json$/;
@@ -77,13 +78,12 @@ export class DebugEditorContribution implements IDebugEditorContribution {
@ICommandService private commandService: ICommandService,
@ICodeEditorService private codeEditorService: ICodeEditorService,
@ITelemetryService private telemetryService: ITelemetryService,
@IListService listService: IListService,
@IConfigurationService private configurationService: IConfigurationService,
@IThemeService themeService: IThemeService,
@IKeybindingService private keybindingService: IKeybindingService
) {
this.breakpointHintDecoration = [];
this.hoverWidget = new DebugHoverWidget(this.editor, this.debugService, this.instantiationService, themeService, contextKeyService, listService);
this.hoverWidget = new DebugHoverWidget(this.editor, this.debugService, this.instantiationService, themeService);
this.toDispose = [];
this.showHoverScheduler = new RunOnceScheduler(() => this.showHover(this.hoverRange, false), HOVER_DELAY);
this.hideHoverScheduler = new RunOnceScheduler(() => this.hoverWidget.hide(), HOVER_DELAY);
@@ -409,12 +409,13 @@ export class DebugEditorContribution implements IDebugEditorContribution {
// configuration widget
private updateConfigurationWidgetVisibility(): void {
const model = this.editor.getModel();
if (this.configurationWidget) {
this.configurationWidget.dispose();
}
if (model && LAUNCH_JSON_REGEX.test(model.uri.toString())) {
this.configurationWidget = this.instantiationService.createInstance(FloatingClickWidget, this.editor, nls.localize('addConfiguration', "Add Configuration..."), null);
this.configurationWidget.render();
this.toDispose.push(this.configurationWidget.onClick(() => this.addLaunchConfiguration().done(undefined, errors.onUnexpectedError)));
} else if (this.configurationWidget) {
this.configurationWidget.dispose();
}
}
@@ -468,7 +469,7 @@ export class DebugEditorContribution implements IDebugEditorContribution {
}
private static BREAKPOINT_HELPER_DECORATION: IModelDecorationOptions = {
glyphMarginClassName: 'debug-breakpoint-hint-glyph',
glyphMarginClassName: 'debug-breakpoint-hint',
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
};
@@ -581,6 +582,10 @@ export class DebugEditorContribution implements IDebugEditorContribution {
if (!this.wordToLineNumbersMap) {
this.wordToLineNumbersMap = new Map<string, Position[]>();
const model = this.editor.getModel();
if (!model) {
return this.wordToLineNumbersMap;
}
// For every word in every line, map its ranges for fast lookup
for (let lineNumber = 1, len = model.getLineCount(); lineNumber <= len; ++lineNumber) {
const lineContent = model.getLineContent(lineNumber);
@@ -592,11 +597,14 @@ export class DebugEditorContribution implements IDebugEditorContribution {
model.forceTokenization(lineNumber);
const lineTokens = model.getLineTokens(lineNumber);
for (let token = lineTokens.firstToken(); !!token; token = token.next()) {
const tokenStr = lineContent.substring(token.startOffset, token.endOffset);
for (let tokenIndex = 0, tokenCount = lineTokens.getCount(); tokenIndex < tokenCount; tokenIndex++) {
const tokenStartOffset = lineTokens.getStartOffset(tokenIndex);
const tokenEndOffset = lineTokens.getEndOffset(tokenIndex);
const tokenType = lineTokens.getStandardTokenType(tokenIndex);
const tokenStr = lineContent.substring(tokenStartOffset, tokenEndOffset);
// Token is a word and not a comment
if (token.tokenType === StandardTokenType.Other) {
if (tokenType === StandardTokenType.Other) {
DEFAULT_WORD_REGEXP.lastIndex = 0; // We assume tokens will usually map 1:1 to words if they match
const wordMatch = DEFAULT_WORD_REGEXP.exec(tokenStr);
@@ -606,7 +614,7 @@ export class DebugEditorContribution implements IDebugEditorContribution {
this.wordToLineNumbersMap.set(word, []);
}
this.wordToLineNumbersMap.get(word).push(new Position(lineNumber, token.startOffset));
this.wordToLineNumbersMap.get(word).push(new Position(lineNumber, tokenStartOffset));
}
}
}

View File

@@ -11,7 +11,7 @@ import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import * as dom from 'vs/base/browser/dom';
import { ITree } from 'vs/base/parts/tree/browser/tree';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { DefaultController, ICancelableEvent, ClickBehavior } from 'vs/base/parts/tree/browser/treeDefaults';
import { ICancelableEvent, OpenMode } from 'vs/base/parts/tree/browser/treeDefaults';
import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
@@ -19,14 +19,14 @@ import { IContentWidget, ICodeEditor, IContentWidgetPosition, ContentWidgetPosit
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IDebugService, IExpression, IExpressionContainer } from 'vs/workbench/parts/debug/common/debug';
import { Expression } from 'vs/workbench/parts/debug/common/debugModel';
import { renderExpressionValue } from 'vs/workbench/parts/debug/electron-browser/baseDebugView';
import { renderExpressionValue } from 'vs/workbench/parts/debug/browser/baseDebugView';
import { VariablesDataSource, VariablesRenderer } from 'vs/workbench/parts/debug/electron-browser/variablesView';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { attachStylerCallback } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { editorHoverBackground, editorHoverBorder } from 'vs/platform/theme/common/colorRegistry';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { WorkbenchTree, IListService } from 'vs/platform/list/browser/listService';
import { WorkbenchTree, WorkbenchTreeController } from 'vs/platform/list/browser/listService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
const $ = dom.$;
const MAX_ELEMENTS_SHOWN = 18;
@@ -54,9 +54,7 @@ export class DebugHoverWidget implements IContentWidget {
private editor: ICodeEditor,
private debugService: IDebugService,
private instantiationService: IInstantiationService,
private themeService: IThemeService,
private contextKeyService: IContextKeyService,
private listService: IListService
private themeService: IThemeService
) {
this.toDispose = [];
@@ -71,16 +69,15 @@ export class DebugHoverWidget implements IContentWidget {
this.complexValueTitle = dom.append(this.complexValueContainer, $('.title'));
this.treeContainer = dom.append(this.complexValueContainer, $('.debug-hover-tree'));
this.treeContainer.setAttribute('role', 'tree');
this.tree = new WorkbenchTree(this.treeContainer, {
this.tree = this.instantiationService.createInstance(WorkbenchTree, this.treeContainer, {
dataSource: new VariablesDataSource(),
renderer: this.instantiationService.createInstance(VariablesHoverRenderer),
controller: new DebugHoverController(this.editor)
controller: this.instantiationService.createInstance(DebugHoverController, this.editor)
}, {
indentPixels: 6,
twistiePixels: 15,
ariaLabel: nls.localize('treeAriaLabel', "Debug Hover"),
keyboardSupport: false
}, this.contextKeyService, this.listService, this.themeService);
ariaLabel: nls.localize('treeAriaLabel', "Debug Hover")
});
this.valueContainer = $('.value');
this.valueContainer.tabIndex = 0;
@@ -337,10 +334,13 @@ export class DebugHoverWidget implements IContentWidget {
}
}
class DebugHoverController extends DefaultController {
class DebugHoverController extends WorkbenchTreeController {
constructor(private editor: ICodeEditor) {
super({ clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: false });
constructor(
private editor: ICodeEditor,
@IConfigurationService configurationService: IConfigurationService
) {
super({ openMode: OpenMode.SINGLE_CLICK }, configurationService);
}
protected onLeftClick(tree: ITree, element: any, eventish: ICancelableEvent, origin = 'mouse'): boolean {

View File

@@ -11,7 +11,6 @@ import * as strings from 'vs/base/common/strings';
import { generateUuid } from 'vs/base/common/uuid';
import uri from 'vs/base/common/uri';
import * as platform from 'vs/base/common/platform';
import { Action } from 'vs/base/common/actions';
import { first, distinct } from 'vs/base/common/arrays';
import { isObject, isUndefinedOrNull } from 'vs/base/common/types';
import * as errors from 'vs/base/common/errors';
@@ -22,10 +21,9 @@ import { Client as TelemetryClient } from 'vs/base/parts/ipc/node/ipc.cp';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IMarkerService } from 'vs/platform/markers/common/markers';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { FileChangesEvent, FileChangeType, IFileService } from 'vs/platform/files/common/files';
import { IMessageService, CloseAction } from 'vs/platform/message/common/message';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
@@ -37,7 +35,7 @@ import { Model, ExceptionBreakpoint, FunctionBreakpoint, Breakpoint, Expression,
import { ViewModel } from 'vs/workbench/parts/debug/common/debugViewModel';
import * as debugactions from 'vs/workbench/parts/debug/browser/debugActions';
import { ConfigurationManager } from 'vs/workbench/parts/debug/electron-browser/debugConfigurationManager';
import { ToggleMarkersPanelAction } from 'vs/workbench/parts/markers/browser/markersPanelActions';
import Constants from 'vs/workbench/parts/markers/common/constants';
import { ITaskService, ITaskSummary } from 'vs/workbench/parts/tasks/common/taskService';
import { TaskError } from 'vs/workbench/parts/tasks/common/taskSystem';
import { VIEWLET_ID as EXPLORER_VIEWLET_ID } from 'vs/workbench/parts/files/common/files';
@@ -53,6 +51,10 @@ import { IBroadcastService, IBroadcast } from 'vs/platform/broadcast/electron-br
import { IRemoteConsoleLog, parse, getFirstFrame } from 'vs/base/node/console';
import { Source } from 'vs/workbench/parts/debug/common/debugSource';
import { TaskEvent, TaskEventKind } from 'vs/workbench/parts/tasks/common/tasks';
import { IChoiceService } from 'vs/platform/dialogs/common/dialogs';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IAction, Action } from 'vs/base/common/actions';
import { normalizeDriveLetter } from 'vs/base/common/labels';
const DEBUG_BREAKPOINTS_KEY = 'debug.breakpoint';
const DEBUG_BREAKPOINTS_ACTIVATED_KEY = 'debug.breakpointactivated';
@@ -88,7 +90,8 @@ export class DebugService implements debug.IDebugService {
@ITextFileService private textFileService: ITextFileService,
@IViewletService private viewletService: IViewletService,
@IPanelService private panelService: IPanelService,
@IMessageService private messageService: IMessageService,
@INotificationService private notificationService: INotificationService,
@IChoiceService private choiceService: IChoiceService,
@IPartService private partService: IPartService,
@IWindowService private windowService: IWindowService,
@IBroadcastService private broadcastService: IBroadcastService,
@@ -122,7 +125,7 @@ export class DebugService implements debug.IDebugService {
this.model = new Model(this.loadBreakpoints(), this.storageService.getBoolean(DEBUG_BREAKPOINTS_ACTIVATED_KEY, StorageScope.WORKSPACE, true), this.loadFunctionBreakpoints(),
this.loadExceptionBreakpoints(), this.loadWatchExpressions());
this.toDispose.push(this.model);
this.viewModel = new ViewModel();
this.viewModel = new ViewModel(contextKeyService);
this.firstSessionStart = true;
this.registerListeners();
@@ -252,7 +255,7 @@ export class DebugService implements debug.IDebugService {
return TPromise.as(null);
}
this.focusStackFrameAndEvaluate(stackFrameToFocus).done(null, errors.onUnexpectedError);
this.focusStackFrame(stackFrameToFocus);
if (thread.stoppedDetails) {
this.windowService.focusWindow();
aria.alert(nls.localize('debuggingPaused', "Debugging paused, reason {0}, {1} {2}", thread.stoppedDetails.reason, stackFrameToFocus.source ? stackFrameToFocus.source.name : '', stackFrameToFocus.range.startLineNumber));
@@ -274,7 +277,7 @@ export class DebugService implements debug.IDebugService {
if (session) {
session.disconnect().done(null, errors.onUnexpectedError);
}
this.messageService.show(severity.Error, e.message);
this.notificationService.error(e.message);
});
}
};
@@ -309,7 +312,7 @@ export class DebugService implements debug.IDebugService {
aria.status(nls.localize('debuggingStopped', "Debugging stopped."));
if (session && session.getId() === event.sessionId) {
if (event.body && event.body.restart && process) {
this.restartProcess(process, event.body.restart).done(null, err => this.messageService.show(severity.Error, err.message));
this.restartProcess(process, event.body.restart).done(null, err => this.notificationService.error(err.message));
} else {
session.disconnect().done(null, errors.onUnexpectedError);
}
@@ -320,7 +323,7 @@ export class DebugService implements debug.IDebugService {
const threadId = event.body.allThreadsContinued !== false ? undefined : event.body.threadId;
this.model.clearThreads(session.getId(), false, threadId);
if (this.viewModel.focusedProcess.getId() === session.getId()) {
this.focusStackFrameAndEvaluate(null, this.viewModel.focusedProcess).done(null, errors.onUnexpectedError);
this.focusStackFrame(undefined, this.viewModel.focusedThread, this.viewModel.focusedProcess);
}
this.updateStateAndEmit(session.getId(), debug.State.Running);
}));
@@ -534,21 +537,34 @@ export class DebugService implements debug.IDebugService {
}
}
public focusStackFrameAndEvaluate(stackFrame: debug.IStackFrame, process?: debug.IProcess, explicit?: boolean): TPromise<void> {
public focusStackFrame(stackFrame: debug.IStackFrame, thread?: debug.IThread, process?: debug.IProcess, explicit?: boolean): void {
if (!process) {
const processes = this.model.getProcesses();
process = stackFrame ? stackFrame.thread.process : processes.length ? processes[0] : null;
if (stackFrame || thread) {
process = stackFrame ? stackFrame.thread.process : thread.process;
} else {
const processes = this.model.getProcesses();
process = processes.length ? processes[0] : undefined;
}
}
if (!thread) {
if (stackFrame) {
thread = stackFrame.thread;
} else {
const threads = process ? process.getAllThreads() : undefined;
thread = threads && threads.length ? threads[0] : undefined;
}
}
if (!stackFrame) {
const threads = process ? process.getAllThreads() : null;
const callStack = threads && threads.length === 1 ? threads[0].getCallStack() : null;
stackFrame = callStack && callStack.length ? callStack[0] : null;
if (thread) {
const callStack = thread.getCallStack();
stackFrame = callStack && callStack.length ? callStack[0] : null;
}
}
this.viewModel.setFocusedStackFrame(stackFrame, process, explicit);
this.viewModel.setFocus(stackFrame, thread, process, explicit);
this.updateStateAndEmit();
return this.model.evaluateWatchExpressions(process, stackFrame);
}
public enableOrDisableBreakpoints(enable: boolean, breakpoint?: debug.IEnablement): TPromise<void> {
@@ -567,16 +583,20 @@ export class DebugService implements debug.IDebugService {
return this.sendAllBreakpoints();
}
public addBreakpoints(uri: uri, rawBreakpoints: debug.IRawBreakpoint[]): TPromise<void> {
public addBreakpoints(uri: uri, rawBreakpoints: debug.IBreakpointData[]): TPromise<void> {
this.model.addBreakpoints(uri, rawBreakpoints);
rawBreakpoints.forEach(rbp => aria.status(nls.localize('breakpointAdded', "Added breakpoint, line {0}, file {1}", rbp.lineNumber, uri.fsPath)));
return this.sendBreakpoints(uri);
}
public updateBreakpoints(uri: uri, data: { [id: string]: DebugProtocol.Breakpoint }): TPromise<void> {
public updateBreakpoints(uri: uri, data: { [id: string]: DebugProtocol.Breakpoint }, sendOnResourceSaved: boolean): void {
this.model.updateBreakpoints(data);
return this.sendBreakpoints(uri);
if (sendOnResourceSaved) {
this.breakpointsToSendOnResourceSaved.add(uri.toString());
} else {
this.sendBreakpoints(uri);
}
}
public removeBreakpoints(id?: string): TPromise<any> {
@@ -594,8 +614,8 @@ export class DebugService implements debug.IDebugService {
return this.sendAllBreakpoints();
}
public addFunctionBreakpoint(): void {
const newFunctionBreakpoint = this.model.addFunctionBreakpoint('');
public addFunctionBreakpoint(name?: string, id?: string): void {
const newFunctionBreakpoint = this.model.addFunctionBreakpoint(name || '', id);
this.viewModel.setSelectedFunctionBreakpoint(newFunctionBreakpoint);
}
@@ -612,7 +632,7 @@ export class DebugService implements debug.IDebugService {
public addReplExpression(name: string): TPromise<void> {
return this.model.addReplExpression(this.viewModel.focusedProcess, this.viewModel.focusedStackFrame, name)
// Evaluate all watch expressions and fetch variables again since repl evaluation might have changed some.
.then(() => this.focusStackFrameAndEvaluate(this.viewModel.focusedStackFrame, this.viewModel.focusedProcess));
.then(() => this.focusStackFrame(this.viewModel.focusedStackFrame, this.viewModel.focusedThread, this.viewModel.focusedProcess));
}
public removeReplExpressions(): void {
@@ -628,11 +648,12 @@ export class DebugService implements debug.IDebugService {
}
}
public addWatchExpression(name: string): TPromise<void> {
return this.model.addWatchExpression(this.viewModel.focusedProcess, this.viewModel.focusedStackFrame, name);
public addWatchExpression(name: string): void {
const we = this.model.addWatchExpression(this.viewModel.focusedProcess, this.viewModel.focusedStackFrame, name);
this.viewModel.setSelectedExpression(we);
}
public renameWatchExpression(id: string, newName: string): TPromise<void> {
public renameWatchExpression(id: string, newName: string): void {
return this.model.renameWatchExpression(this.viewModel.focusedProcess, this.viewModel.focusedStackFrame, id, newName);
}
@@ -644,14 +665,10 @@ export class DebugService implements debug.IDebugService {
this.model.removeWatchExpressions(id);
}
public evaluateWatchExpressions(): TPromise<void> {
return this.model.evaluateWatchExpressions(this.viewModel.focusedProcess, this.viewModel.focusedStackFrame);
}
public startDebugging(root: IWorkspaceFolder, configOrName?: debug.IConfig | string, noDebug = false, topCompoundName?: string): TPromise<any> {
public startDebugging(launch: debug.ILaunch, configOrName?: debug.IConfig | string, noDebug = false): TPromise<any> {
// make sure to save all files and that the configuration is up to date
return this.extensionService.activateByEvent('onDebug').then(() => this.textFileService.saveAll().then(() => this.configurationService.reloadConfiguration(root).then(() =>
return this.extensionService.activateByEvent('onDebug').then(() => this.textFileService.saveAll().then(() => this.configurationService.reloadConfiguration(launch ? launch.workspace : undefined).then(() =>
this.extensionService.whenInstalledExtensionsRegistered().then(() => {
if (this.model.getProcesses().length === 0) {
this.removeReplExpressions();
@@ -659,12 +676,10 @@ export class DebugService implements debug.IDebugService {
this.model.getBreakpoints().forEach(bp => bp.verified = false);
}
this.launchJsonChanged = false;
const manager = this.getConfigurationManager();
const launch = root ? manager.getLaunches().filter(l => l.workspace.uri.toString() === root.uri.toString()).pop() : undefined;
let config: debug.IConfig, compound: debug.ICompound;
if (!configOrName) {
configOrName = this.configurationManager.selectedName;
configOrName = this.configurationManager.selectedConfiguration.name;
}
if (typeof configOrName === 'string' && launch) {
config = launch.getConfiguration(configOrName);
@@ -672,10 +687,6 @@ export class DebugService implements debug.IDebugService {
} else if (typeof configOrName !== 'string') {
config = configOrName;
}
if (launch) {
// in the drop down the name of the top most compound takes precedence over the launch config name
manager.selectConfiguration(launch, topCompoundName || (typeof configOrName === 'string' ? configOrName : undefined), true);
}
if (compound) {
if (!compound.configurations) {
@@ -683,7 +694,35 @@ export class DebugService implements debug.IDebugService {
"Compound must have \"configurations\" attribute set in order to start multiple configurations.")));
}
return TPromise.join(compound.configurations.map(name => name !== compound.name ? this.startDebugging(root, name, noDebug, topCompoundName || compound.name) : TPromise.as(null)));
return TPromise.join(compound.configurations.map(configData => {
const name = typeof configData === 'string' ? configData : configData.name;
if (name === compound.name) {
return TPromise.as(null);
}
let launchForName: debug.ILaunch;
if (typeof configData === 'string') {
const launchesContainingName = this.configurationManager.getLaunches().filter(l => !!l.getConfiguration(name));
if (launchesContainingName.length === 1) {
launchForName = launchesContainingName[0];
} else if (launchesContainingName.length > 1 && launchesContainingName.indexOf(launch) >= 0) {
// If there are multiple launches containing the configuration give priority to the configuration in the current launch
launchForName = launch;
} else {
return TPromise.wrapError(new Error(launchesContainingName.length === 0 ? nls.localize('noConfigurationNameInWorkspace', "Could not find launch configuration '{0}' in the workspace.", name)
: nls.localize('multipleConfigurationNamesInWorkspace', "There are multiple launch configurations '{0}' in the workspace. Use folder name to qualify the configuration.", name)));
}
} else if (configData.folder) {
const launchesMatchingConfigData = this.configurationManager.getLaunches().filter(l => l.workspace && l.workspace.name === configData.folder && !!l.getConfiguration(configData.name));
if (launchesMatchingConfigData.length === 1) {
launchForName = launchesMatchingConfigData[0];
} else {
return TPromise.wrapError(new Error(nls.localize('noFolderWithName', "Can not find folder with name '{0}' for configuration '{1}' in compound '{2}'.", configData.folder, configData.name, compound.name)));
}
}
return this.startDebugging(launchForName, name, noDebug);
}));
}
if (configOrName && !config) {
const message = !!launch ? nls.localize('configMissing', "Configuration '{0}' is missing in 'launch.json'.", configOrName) :
@@ -714,16 +753,15 @@ export class DebugService implements debug.IDebugService {
return (type ? TPromise.as(null) : this.configurationManager.guessAdapter().then(a => type = a && a.type)).then(() =>
(type ? this.extensionService.activateByEvent(`onDebugResolve:${type}`) : TPromise.as(null)).then(() =>
this.configurationManager.resolveConfigurationByProviders(launch ? launch.workspace.uri : undefined, type, config).then(config => {
this.configurationManager.resolveConfigurationByProviders(launch && launch.workspace ? launch.workspace.uri : undefined, type, config).then(config => {
// a falsy config indicates an aborted launch
if (config && config.type) {
return this.createProcess(root, config, sessionId);
}
if (launch) {
return launch.openConfigFile(false, type).then(editor => undefined);
return this.createProcess(launch, config, sessionId);
}
return undefined;
if (launch) {
return launch.openConfigFile(false, type).done(undefined, errors.onUnexpectedError);
}
})
).then(() => wrapUpState(), err => {
wrapUpState();
@@ -733,9 +771,9 @@ export class DebugService implements debug.IDebugService {
)));
}
private createProcess(root: IWorkspaceFolder, config: debug.IConfig, sessionId: string): TPromise<void> {
private createProcess(launch: debug.ILaunch, config: debug.IConfig, sessionId: string): TPromise<void> {
return this.textFileService.saveAll().then(() =>
(this.configurationManager.selectedLaunch ? this.configurationManager.selectedLaunch.resolveConfiguration(config) : TPromise.as(config)).then(resolvedConfig => {
(launch ? launch.resolveConfiguration(config) : TPromise.as(config)).then(resolvedConfig => {
if (!resolvedConfig) {
// User canceled resolving of interactive variables, silently return
return undefined;
@@ -744,65 +782,50 @@ export class DebugService implements debug.IDebugService {
if (!this.configurationManager.getAdapter(resolvedConfig.type) || (config.request !== 'attach' && config.request !== 'launch')) {
let message: string;
if (config.request !== 'attach' && config.request !== 'launch') {
message = config.request ? nls.localize('debugRequestNotSupported', "Attribute `{0}` has an unsupported value '{1}' in the chosen debug configuration.", 'request', config.request)
message = config.request ? nls.localize('debugRequestNotSupported', "Attribute '{0}' has an unsupported value '{1}' in the chosen debug configuration.", 'request', config.request)
: nls.localize('debugRequesMissing', "Attribute '{0}' is missing from the chosen debug configuration.", 'request');
} else {
message = resolvedConfig.type ? nls.localize('debugTypeNotSupported', "Configured debug type '{0}' is not supported.", resolvedConfig.type) :
nls.localize('debugTypeMissing', "Missing property `type` for the chosen launch configuration.");
nls.localize('debugTypeMissing', "Missing property 'type' for the chosen launch configuration.");
}
return TPromise.wrapError(errors.create(message, { actions: [this.instantiationService.createInstance(debugactions.ConfigureAction, debugactions.ConfigureAction.ID, debugactions.ConfigureAction.LABEL), CloseAction] }));
return this.showError(message);
}
this.toDisposeOnSessionEnd.set(sessionId, []);
const debugAnywayAction = new Action('debug.continue', nls.localize('debugAnyway', "Debug Anyway"), null, true, () => {
this.messageService.hideAll();
return this.doCreateProcess(root, resolvedConfig, sessionId);
const workspace = launch ? launch.workspace : undefined;
const debugAnywayAction = new Action('debug.debugAnyway', nls.localize('debugAnyway', "Debug Anyway"), undefined, true, () => {
return this.doCreateProcess(workspace, resolvedConfig, sessionId);
});
return this.runPreLaunchTask(sessionId, root, resolvedConfig.preLaunchTask).then((taskSummary: ITaskSummary) => {
return this.runPreLaunchTask(sessionId, workspace, resolvedConfig.preLaunchTask).then((taskSummary: ITaskSummary) => {
const errorCount = resolvedConfig.preLaunchTask ? this.markerService.getStatistics().errors : 0;
const successExitCode = taskSummary && taskSummary.exitCode === 0;
const failureExitCode = taskSummary && taskSummary.exitCode !== undefined && taskSummary.exitCode !== 0;
if (successExitCode || (errorCount === 0 && !failureExitCode)) {
return this.doCreateProcess(root, resolvedConfig, sessionId);
return this.doCreateProcess(workspace, resolvedConfig, sessionId);
}
this.messageService.show(severity.Error, {
message: errorCount > 1 ? nls.localize('preLaunchTaskErrors', "Build errors have been detected during preLaunchTask '{0}'.", resolvedConfig.preLaunchTask) :
errorCount === 1 ? nls.localize('preLaunchTaskError', "Build error has been detected during preLaunchTask '{0}'.", resolvedConfig.preLaunchTask) :
nls.localize('preLaunchTaskExitCode', "The preLaunchTask '{0}' terminated with exit code {1}.", resolvedConfig.preLaunchTask, taskSummary.exitCode),
actions: [
debugAnywayAction,
this.instantiationService.createInstance(ToggleMarkersPanelAction, ToggleMarkersPanelAction.ID, ToggleMarkersPanelAction.LABEL),
CloseAction
]
const message = errorCount > 1 ? nls.localize('preLaunchTaskErrors', "Build errors have been detected during preLaunchTask '{0}'.", resolvedConfig.preLaunchTask) :
errorCount === 1 ? nls.localize('preLaunchTaskError', "Build error has been detected during preLaunchTask '{0}'.", resolvedConfig.preLaunchTask) :
nls.localize('preLaunchTaskExitCode', "The preLaunchTask '{0}' terminated with exit code {1}.", resolvedConfig.preLaunchTask, taskSummary.exitCode);
const showErrorsAction = new Action('debug.showErrors', nls.localize('showErrors', "Show Errors"), undefined, true, () => {
return this.panelService.openPanel(Constants.MARKERS_PANEL_ID).then(() => undefined);
});
return undefined;
return this.showError(message, [debugAnywayAction, showErrorsAction]);
}, (err: TaskError) => {
this.messageService.show(err.severity, {
message: err.message,
actions: [
debugAnywayAction,
this.instantiationService.createInstance(debugactions.ConfigureAction, debugactions.ConfigureAction.ID, debugactions.ConfigureAction.LABEL),
this.taskService.configureAction(),
CloseAction
]
});
return this.showError(err.message, [debugAnywayAction, this.taskService.configureAction()]);
});
}, err => {
if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
this.messageService.show(severity.Error, nls.localize('noFolderWorkspaceDebugError', "The active file can not be debugged. Make sure it is saved on disk and that you have a debug extension installed for that file type."));
return undefined;
return this.showError(nls.localize('noFolderWorkspaceDebugError', "The active file can not be debugged. Make sure it is saved on disk and that you have a debug extension installed for that file type."));
}
return this.configurationManager.selectedLaunch.openConfigFile(false).then(openend => {
if (openend) {
this.messageService.show(severity.Info, nls.localize('NewLaunchConfig', "Please set up the launch configuration file for your application. {0}", err.message));
}
return undefined;
});
return launch && launch.openConfigFile(false).then(editor => void 0);
})
);
}
@@ -870,8 +893,8 @@ export class DebugService implements debug.IDebugService {
if (session.disconnected) {
return TPromise.as(null);
}
this.focusStackFrame(undefined, undefined, process);
this._onDidNewProcess.fire(process);
this.focusStackFrameAndEvaluate(null, process);
const internalConsoleOptions = configuration.internalConsoleOptions || this.configurationService.getValue<debug.IDebugConfiguration>('debug').internalConsoleOptions;
if (internalConsoleOptions === 'openOnSessionStart' || (this.firstSessionStart && internalConsoleOptions === 'openOnFirstSessionStart')) {
@@ -911,8 +934,8 @@ export class DebugService implements debug.IDebugService {
isBuiltin: adapter.extensionDescription.isBuiltin,
launchJsonExists: root && !!this.configurationService.getValue<debug.IGlobalConfig>('launch', { resource: root.uri })
});
}).then(() => process, (error: any) => {
if (error instanceof Error && error.message === 'Canceled') {
}).then(() => process, (error: Error | string) => {
if (errors.isPromiseCanceledError(error)) {
// Do not show 'canceled' error messages to the user #7906
return TPromise.as(null);
}
@@ -921,7 +944,7 @@ export class DebugService implements debug.IDebugService {
/* __GDPR__
"debugMisconfiguration" : {
"type" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"error": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
"error": { "classification": "CallstackOrException", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('debugMisconfiguration', { type: configuration ? configuration.type : undefined, error: errorMessage });
@@ -940,14 +963,24 @@ export class DebugService implements debug.IDebugService {
this.inDebugMode.reset();
}
const configureAction = this.instantiationService.createInstance(debugactions.ConfigureAction, debugactions.ConfigureAction.ID, debugactions.ConfigureAction.LABEL);
const actions = (error.actions && error.actions.length) ? error.actions.concat([configureAction]) : [CloseAction, configureAction];
this.messageService.show(severity.Error, { message: errorMessage, actions });
this.showError(errorMessage, errors.isErrorWithActions(error) ? error.actions : []);
return undefined;
});
});
}
private showError(message: string, actions: IAction[] = []): TPromise<any> {
const configureAction = this.instantiationService.createInstance(debugactions.ConfigureAction, debugactions.ConfigureAction.ID, debugactions.ConfigureAction.LABEL);
actions.push(configureAction);
return this.choiceService.choose(severity.Error, message, actions.map(a => a.label).concat(nls.localize('cancel', "Cancel")), actions.length, true).then(choice => {
if (choice < actions.length) {
return actions[choice].run();
}
return TPromise.as(null);
});
}
private runPreLaunchTask(sessionId: string, root: IWorkspaceFolder, taskName: string): TPromise<ITaskSummary> {
if (!taskName) {
return TPromise.as(null);
@@ -1020,7 +1053,7 @@ export class DebugService implements debug.IDebugService {
const preserveFocus = focusedProcess && process.getId() === focusedProcess.getId();
return process.session.disconnect(true).then(() => {
if (strings.equalsIgnoreCase(process.configuration.type, 'extensionHost')) {
if (strings.equalsIgnoreCase(process.configuration.type, 'extensionHost') && process.session.root) {
return this.broadcastService.broadcast({
channel: EXTENSION_RELOAD_BROADCAST_CHANNEL,
payload: [process.session.root.uri.fsPath]
@@ -1031,15 +1064,17 @@ export class DebugService implements debug.IDebugService {
setTimeout(() => {
// Read the configuration again if a launch.json has been changed, if not just use the inmemory configuration
let config = process.configuration;
if (this.launchJsonChanged && this.configurationManager.selectedLaunch) {
const launch = process.session.root ? this.configurationManager.getLaunch(process.session.root.uri) : undefined;
if (this.launchJsonChanged && launch) {
this.launchJsonChanged = false;
config = this.configurationManager.selectedLaunch.getConfiguration(process.configuration.name) || config;
config = launch.getConfiguration(process.configuration.name) || config;
// Take the type from the process since the debug extension might overwrite it #21316
config.type = process.configuration.type;
config.noDebug = process.configuration.noDebug;
}
config.__restart = restartData;
this.createProcess(process.session.root, config, process.getId()).then(() => c(null), err => e(err));
this.startDebugging(launch, config).then(() => c(null), err => e(err));
}, 300);
});
}).then(() => {
@@ -1047,7 +1082,7 @@ export class DebugService implements debug.IDebugService {
// Restart should preserve the focused process
const restartedProcess = this.model.getProcesses().filter(p => p.configuration.name === process.configuration.name).pop();
if (restartedProcess && restartedProcess !== this.viewModel.focusedProcess) {
this.focusStackFrameAndEvaluate(null, restartedProcess);
this.focusStackFrame(undefined, undefined, restartedProcess);
}
}
});
@@ -1090,7 +1125,7 @@ export class DebugService implements debug.IDebugService {
});
this.model.removeProcess(session.getId());
if (process && process.state !== debug.ProcessState.INACTIVE) {
if (process) {
process.inactive = true;
this._onDidEndProcess.fire(process);
}
@@ -1098,7 +1133,7 @@ export class DebugService implements debug.IDebugService {
this.toDisposeOnSessionEnd.set(session.getId(), lifecycle.dispose(this.toDisposeOnSessionEnd.get(session.getId())));
const focusedProcess = this.viewModel.focusedProcess;
if (focusedProcess && focusedProcess.getId() === session.getId()) {
this.focusStackFrameAndEvaluate(null).done(null, errors.onUnexpectedError);
this.focusStackFrame(null);
}
this.updateStateAndEmit(session.getId(), debug.State.Inactive);
@@ -1146,11 +1181,6 @@ export class DebugService implements debug.IDebugService {
if (!session.readyForBreakpoints) {
return TPromise.as(null);
}
if (this.textFileService.isDirty(modelUri)) {
// Only send breakpoints for a file once it is not dirty #8077
this.breakpointsToSendOnResourceSaved.add(modelUri.toString());
return TPromise.as(null);
}
const breakpointsToSend = this.model.getBreakpoints().filter(bp => this.model.areBreakpointsActivated() && bp.enabled && bp.uri.toString() === modelUri.toString());
@@ -1166,6 +1196,8 @@ export class DebugService implements debug.IDebugService {
if (breakpointsToSend.length && !rawSource.adapterData) {
rawSource.adapterData = breakpointsToSend[0].adapterData;
}
// Normalize all drive letters going out from vscode to debug adapters so we are consistent with our resolving #43959
rawSource.path = normalizeDriveLetter(rawSource.path);
return session.setBreakpoints({
source: rawSource,

View File

@@ -13,7 +13,7 @@ import { IDebugService, IStackFrame } from 'vs/workbench/parts/debug/common/debu
import { clipboard } from 'electron';
export class CopyValueAction extends Action {
static ID = 'workbench.debug.viewlet.action.copyValue';
static readonly ID = 'workbench.debug.viewlet.action.copyValue';
static LABEL = nls.localize('copyValue', "Copy Value");
constructor(id: string, label: string, private value: any, @IDebugService private debugService: IDebugService) {
@@ -34,8 +34,25 @@ export class CopyValueAction extends Action {
}
}
export class CopyEvaluatePathAction extends Action {
static readonly ID = 'workbench.debug.viewlet.action.copyEvaluatePath';
static LABEL = nls.localize('copyAsExpression', "Copy as Expression");
constructor(id: string, label: string, private value: any) {
super(id, label);
}
public run(): TPromise<any> {
if (this.value instanceof Variable) {
clipboard.writeText(this.value.evaluateName);
}
return TPromise.as(null);
}
}
export class CopyAction extends Action {
static ID = 'workbench.debug.action.copy';
static readonly ID = 'workbench.debug.action.copy';
static LABEL = nls.localize('copy', "Copy");
public run(): TPromise<any> {
@@ -45,7 +62,7 @@ export class CopyAction extends Action {
}
export class CopyAllAction extends Action {
static ID = 'workbench.debug.action.copyAll';
static readonly ID = 'workbench.debug.action.copyAll';
static LABEL = nls.localize('copyAll', "Copy All");
constructor(id: string, label: string, private tree: ITree) {
@@ -69,7 +86,7 @@ export class CopyAllAction extends Action {
}
export class CopyStackTraceAction extends Action {
static ID = 'workbench.action.debug.copyStackTrace';
static readonly ID = 'workbench.action.debug.copyStackTrace';
static LABEL = nls.localize('copyStackTrace', "Copy Call Stack");
public run(frame: IStackFrame): TPromise<any> {

View File

@@ -12,9 +12,7 @@ import objects = require('vs/base/common/objects');
import { Action } from 'vs/base/common/actions';
import errors = require('vs/base/common/errors');
import { TPromise } from 'vs/base/common/winjs.base';
import severity from 'vs/base/common/severity';
import stdfork = require('vs/base/node/stdFork');
import { IMessageService, CloseAction } from 'vs/platform/message/common/message';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { ITerminalService } from 'vs/workbench/parts/terminal/common/terminal';
import { ITerminalService as IExternalTerminalService } from 'vs/workbench/parts/execution/common/execution';
@@ -26,6 +24,7 @@ import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { ExtensionsChannelId } from 'vs/platform/extensionManagement/common/extensionManagement';
import { TerminalSupport } from 'vs/workbench/parts/debug/electron-browser/terminalSupport';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { INotificationService } from 'vs/platform/notification/common/notification';
export interface SessionExitedEvent extends debug.DebugEvent {
body: {
@@ -72,7 +71,7 @@ export class RawDebugSession extends V8Protocol implements debug.ISession {
private adapter: Adapter,
public customTelemetryService: ITelemetryService,
public root: IWorkspaceFolder,
@IMessageService private messageService: IMessageService,
@INotificationService private notificationService: INotificationService,
@ITelemetryService private telemetryService: ITelemetryService,
@IOutputService private outputService: IOutputService,
@ITerminalService private terminalService: ITerminalService,
@@ -166,7 +165,7 @@ export class RawDebugSession extends V8Protocol implements debug.ISession {
if (error && error.sendTelemetry) {
/* __GDPR__
"debugProtocolErrorResponse" : {
"error" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
"error" : { "classification": "CallstackOrException", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('debugProtocolErrorResponse', { error: telemetryMessage });
@@ -183,7 +182,7 @@ export class RawDebugSession extends V8Protocol implements debug.ISession {
if (error && error.url) {
const label = error.urlLabel ? error.urlLabel : nls.localize('moreInfo', "More Info");
return TPromise.wrapError<R>(errors.create(userMessage, {
actions: [CloseAction, new Action('debug.moreInfo', label, null, true, () => {
actions: [new Action('debug.moreInfo', label, null, true, () => {
window.open(error.url);
return TPromise.as(null);
})]
@@ -206,6 +205,9 @@ export class RawDebugSession extends V8Protocol implements debug.ISession {
if (event.event === 'initialized') {
this.readyForBreakpoints = true;
this._onDidInitialize.fire(event);
} else if (event.event === 'capabilities' && event.body) {
const capabilites = (<DebugProtocol.CapabilitiesEvent>event).body.capabilities;
this._capabilities = objects.mixin(this._capabilities, capabilites);
} else if (event.event === 'stopped') {
this.emittedStopped = true;
this._onDidStop.fire(<DebugProtocol.StoppedEvent>event);
@@ -524,7 +526,7 @@ export class RawDebugSession extends V8Protocol implements debug.ISession {
}
protected onServerError(err: Error): void {
this.messageService.show(severity.Error, nls.localize('stoppingDebugAdapter', "{0}. Stopping the debug adapter.", err.message));
this.notificationService.error(nls.localize('stoppingDebugAdapter', "{0}. Stopping the debug adapter.", err.message));
this.stopServer().done(null, errors.onUnexpectedError);
}
@@ -532,7 +534,7 @@ export class RawDebugSession extends V8Protocol implements debug.ISession {
this.serverProcess = null;
this.cachedInitServer = null;
if (!this.disconnected) {
this.messageService.show(severity.Error, nls.localize('debugAdapterCrash', "Debug adapter process has terminated unexpectedly"));
this.notificationService.error(nls.localize('debugAdapterCrash', "Debug adapter process has terminated unexpectedly"));
}
this.onEvent({ event: 'exit', type: 'event', seq: 0 });
}

View File

@@ -18,7 +18,7 @@ import { KeyCode } from 'vs/base/common/keyCodes';
import { ITree, ITreeOptions } from 'vs/base/parts/tree/browser/tree';
import { Context as SuggestContext } from 'vs/editor/contrib/suggest/suggest';
import { SuggestController } from 'vs/editor/contrib/suggest/suggestController';
import { IReadOnlyModel } from 'vs/editor/common/editorCommon';
import { ITextModel } from 'vs/editor/common/model';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { Position } from 'vs/editor/common/core/position';
import * as modes from 'vs/editor/common/modes';
@@ -41,15 +41,16 @@ import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { clipboard } from 'electron';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { WorkbenchTree, IListService } from 'vs/platform/list/browser/listService';
import { WorkbenchTree } from 'vs/platform/list/browser/listService';
import { memoize } from 'vs/base/common/decorators';
import { dispose } from 'vs/base/common/lifecycle';
import { OpenMode, ClickBehavior } from 'vs/base/parts/tree/browser/treeDefaults';
const $ = dom.$;
const replTreeOptions: ITreeOptions = {
twistiePixels: 20,
ariaLabel: nls.localize('replAriaLabel', "Read Eval Print Loop Panel"),
keyboardSupport: false
ariaLabel: nls.localize('replAriaLabel', "Read Eval Print Loop Panel")
};
const HISTORY_STORAGE_KEY = 'debug.repl.history';
@@ -82,6 +83,7 @@ export class Repl extends Panel implements IPrivateReplService {
private actions: IAction[];
private dimension: Dimension;
private replInputHeight: number;
private model: ITextModel;
constructor(
@debug.IDebugService private debugService: debug.IDebugService,
@@ -91,8 +93,7 @@ export class Repl extends Panel implements IPrivateReplService {
@IPanelService private panelService: IPanelService,
@IThemeService protected themeService: IThemeService,
@IModelService private modelService: IModelService,
@IContextKeyService private contextKeyService: IContextKeyService,
@IListService private listService: IListService
@IContextKeyService private contextKeyService: IContextKeyService
) {
super(debug.REPL_ID, telemetryService, themeService);
@@ -118,7 +119,7 @@ export class Repl extends Panel implements IPrivateReplService {
this.refreshTimeoutHandle = null;
const previousScrollPosition = this.tree.getScrollPosition();
this.tree.refresh().then(() => {
if (previousScrollPosition === 1 || previousScrollPosition === 0) {
if (previousScrollPosition === 1) {
// Only scroll if we were scrolled all the way down before tree refreshed #10486
this.tree.setScrollPosition(1);
}
@@ -134,15 +135,15 @@ export class Repl extends Panel implements IPrivateReplService {
this.createReplInput(this.container);
this.renderer = this.instantiationService.createInstance(ReplExpressionsRenderer);
const controller = this.instantiationService.createInstance(ReplExpressionsController, new ReplExpressionsActionProvider(this.instantiationService), MenuId.DebugConsoleContext);
const controller = this.instantiationService.createInstance(ReplExpressionsController, new ReplExpressionsActionProvider(this.instantiationService, this.replInput), MenuId.DebugConsoleContext, { openMode: OpenMode.SINGLE_CLICK, clickBehavior: ClickBehavior.ON_MOUSE_UP /* do not change, to preserve focus behaviour in input field */ });
controller.toFocusOnClick = this.replInput;
this.tree = new WorkbenchTree(this.treeContainer, {
this.tree = this.instantiationService.createInstance(WorkbenchTree, this.treeContainer, {
dataSource: new ReplExpressionsDataSource(),
renderer: this.renderer,
accessibilityProvider: new ReplExpressionsAccessibilityProvider(),
controller
}, replTreeOptions, this.contextKeyService, this.listService, this.themeService);
}, replTreeOptions);
if (!Repl.HISTORY) {
Repl.HISTORY = new ReplHistory(JSON.parse(this.storageService.get(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')));
@@ -151,6 +152,17 @@ export class Repl extends Panel implements IPrivateReplService {
return this.tree.setInput(this.debugService.getModel());
}
public setVisible(visible: boolean): TPromise<void> {
if (!visible) {
dispose(this.model);
} else {
this.model = this.modelService.createModel('', null, uri.parse(`${debug.DEBUG_SCHEME}:input`));
this.replInput.setModel(this.model);
}
return super.setVisible(visible);
}
private createReplInput(container: HTMLElement): void {
this.replInputContainer = dom.append(container, $('.repl-input-wrapper'));
@@ -165,12 +177,10 @@ export class Repl extends Panel implements IPrivateReplService {
const scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection(
[IContextKeyService, scopedContextKeyService], [IPrivateReplService, this]));
this.replInput = scopedInstantiationService.createInstance(ReplInputEditor, this.replInputContainer, this.getReplInputOptions());
const model = this.modelService.createModel('', null, uri.parse(`${debug.DEBUG_SCHEME}:input`));
this.replInput.setModel(model);
modes.SuggestRegistry.register({ scheme: debug.DEBUG_SCHEME }, {
triggerCharacters: ['.'],
provideCompletionItems: (model: IReadOnlyModel, position: Position, _context: modes.SuggestContext, token: CancellationToken): Thenable<modes.ISuggestResult> => {
provideCompletionItems: (model: ITextModel, position: Position, _context: modes.SuggestContext, token: CancellationToken): Thenable<modes.ISuggestResult> => {
const word = this.replInput.getModel().getWordAtPosition(position);
const overwriteBefore = word ? word.word.length : 0;
const text = this.replInput.getModel().getLineContent(position.lineNumber);

View File

@@ -9,7 +9,7 @@ import { IAction } from 'vs/base/common/actions';
import * as lifecycle from 'vs/base/common/lifecycle';
import * as errors from 'vs/base/common/errors';
import { isFullWidthCharacter, removeAnsiEscapeCodes, endsWith } from 'vs/base/common/strings';
import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { IActionItem, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import * as dom from 'vs/base/browser/dom';
import severity from 'vs/base/common/severity';
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
@@ -17,8 +17,8 @@ import { ITree, IAccessibilityProvider, ContextMenuEvent, IDataSource, IRenderer
import { ICancelableEvent } from 'vs/base/parts/tree/browser/treeDefaults';
import { IExpressionContainer, IExpression, IReplElementSource } from 'vs/workbench/parts/debug/common/debug';
import { Model, RawObjectReplElement, Expression, SimpleReplElement, Variable } from 'vs/workbench/parts/debug/common/debugModel';
import { renderVariable, renderExpressionValue, IVariableTemplateData, BaseDebugController } from 'vs/workbench/parts/debug/electron-browser/baseDebugView';
import { ClearReplAction } from 'vs/workbench/parts/debug/browser/debugActions';
import { renderVariable, renderExpressionValue, IVariableTemplateData, BaseDebugController } from 'vs/workbench/parts/debug/browser/baseDebugView';
import { ClearReplAction, ReplCollapseAllAction } from 'vs/workbench/parts/debug/browser/debugActions';
import { CopyAction, CopyAllAction } from 'vs/workbench/parts/debug/electron-browser/electronDebugActions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
@@ -417,7 +417,7 @@ export class ReplExpressionsAccessibilityProvider implements IAccessibilityProvi
export class ReplExpressionsActionProvider implements IActionProvider {
constructor(private instantiationService: IInstantiationService) {
constructor(private instantiationService: IInstantiationService, private toFocus: { focus(): void }) {
// noop
}
@@ -437,6 +437,8 @@ export class ReplExpressionsActionProvider implements IActionProvider {
const actions: IAction[] = [];
actions.push(new CopyAction(CopyAction.ID, CopyAction.LABEL));
actions.push(new CopyAllAction(CopyAllAction.ID, CopyAllAction.LABEL, tree));
actions.push(new ReplCollapseAllAction(tree, this.toFocus));
actions.push(new Separator());
actions.push(this.instantiationService.createInstance(ClearReplAction, ClearReplAction.ID, ClearReplAction.LABEL));
return TPromise.as(actions);

View File

@@ -5,6 +5,7 @@
import * as nls from 'vs/nls';
import * as platform from 'vs/base/common/platform';
import cp = require('child_process');
import { IDisposable } from 'vs/base/common/lifecycle';
import { TPromise } from 'vs/base/common/winjs.base';
import { ITerminalService, ITerminalInstance, ITerminalConfiguration } from 'vs/workbench/parts/terminal/common/terminal';
@@ -24,11 +25,6 @@ export class TerminalSupport {
return nativeTerminalService.runInTerminal(args.title, args.cwd, args.args, args.env || {});
}
let delay = 0;
if (!TerminalSupport.integratedTerminalInstance) {
TerminalSupport.integratedTerminalInstance = terminalService.createInstance({ name: args.title || nls.localize('debug.terminal.title', "debuggee") });
delay = 2000; // delay the first sendText so that the newly created terminal is ready.
}
if (!TerminalSupport.terminalDisposedListener) {
// React on terminal disposed and check if that is the debug terminal #12956
TerminalSupport.terminalDisposedListener = terminalService.onInstanceDisposed(terminal => {
@@ -37,22 +33,49 @@ export class TerminalSupport {
}
});
}
terminalService.setActiveInstance(TerminalSupport.integratedTerminalInstance);
let t = TerminalSupport.integratedTerminalInstance;
if ((t && this.isBusy(t)) || !t) {
t = terminalService.createInstance({ name: args.title || nls.localize('debug.terminal.title', "debuggee") });
TerminalSupport.integratedTerminalInstance = t;
}
terminalService.setActiveInstance(t);
terminalService.showPanel(true);
return new TPromise<void>((c, e) => {
const command = this.prepareCommand(args, configurationService);
t.sendText(command, true);
setTimeout(() => {
if (TerminalSupport.integratedTerminalInstance) {
const command = this.prepareCommand(args, configurationService);
TerminalSupport.integratedTerminalInstance.sendText(command, true);
c(void 0);
return TPromise.as(void 0);
}
private static isBusy(t: ITerminalInstance): boolean {
if (t.processId) {
try {
// if shell has at least one child process, assume that shell is busy
if (platform.isWindows) {
const result = cp.spawnSync('wmic', ['process', 'get', 'ParentProcessId']);
if (result.stdout) {
const pids = result.stdout.toString().split('\r\n');
if (!pids.some(p => parseInt(p) === t.processId)) {
return false;
}
}
} else {
e(new Error(nls.localize('debug.terminal.not.available.error', "Integrated terminal not available")));
const result = cp.spawnSync('/usr/bin/pgrep', ['-lP', String(t.processId)]);
if (result.stdout) {
const r = result.stdout.toString().trim();
if (r.length === 0 || r.indexOf(' tmux') >= 0) { // ignore 'tmux'; see #43683
return false;
}
}
}
}, delay);
});
}
catch (e) {
// silently ignore
}
}
// fall back to safe side
return true;
}
private static prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments, configurationService: IConfigurationService): string {
@@ -94,7 +117,8 @@ export class TerminalSupport {
quote = (s: string) => {
s = s.replace(/\'/g, '\'\'');
return s.indexOf(' ') >= 0 || s.indexOf('\'') >= 0 || s.indexOf('"') >= 0 ? `'${s}'` : s;
return `'${s}'`;
//return s.indexOf(' ') >= 0 || s.indexOf('\'') >= 0 || s.indexOf('"') >= 0 ? `'${s}'` : s;
};
if (args.cwd) {

View File

@@ -7,8 +7,7 @@ import * as nls from 'vs/nls';
import { RunOnceScheduler, sequence } from 'vs/base/common/async';
import * as dom from 'vs/base/browser/dom';
import * as errors from 'vs/base/common/errors';
import { prepareActions } from 'vs/workbench/browser/actions';
import { IHighlightEvent, IActionProvider, ITree, IDataSource, IRenderer, IAccessibilityProvider } from 'vs/base/parts/tree/browser/tree';
import { IActionProvider, ITree, IDataSource, IRenderer, IAccessibilityProvider } from 'vs/base/parts/tree/browser/tree';
import { CollapseAction } from 'vs/workbench/browser/viewlet';
import { TreeViewsViewletPanel, IViewletViewOptions, IViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { IDebugService, State, CONTEXT_VARIABLES_FOCUSED, IExpression } from 'vs/workbench/parts/debug/common/debug';
@@ -17,19 +16,19 @@ import { IContextMenuService, IContextViewService } from 'vs/platform/contextvie
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { MenuId } from 'vs/platform/actions/common/actions';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { once } from 'vs/base/common/event';
import { twistiePixels, renderViewTree, IVariableTemplateData, BaseDebugController, renderRenameBox, renderVariable } from 'vs/workbench/parts/debug/electron-browser/baseDebugView';
import { twistiePixels, renderViewTree, IVariableTemplateData, BaseDebugController, renderRenameBox, renderVariable } from 'vs/workbench/parts/debug/browser/baseDebugView';
import { TPromise } from 'vs/base/common/winjs.base';
import { IAction, IActionItem } from 'vs/base/common/actions';
import { SetValueAction, AddToWatchExpressionsAction } from 'vs/workbench/parts/debug/browser/debugActions';
import { CopyValueAction } from 'vs/workbench/parts/debug/electron-browser/electronDebugActions';
import { CopyValueAction, CopyEvaluatePathAction } from 'vs/workbench/parts/debug/electron-browser/electronDebugActions';
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { ViewModel } from 'vs/workbench/parts/debug/common/debugViewModel';
import { equalsIgnoreCase } from 'vs/base/common/strings';
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
import { WorkbenchTree, IListService } from 'vs/platform/list/browser/listService';
import { WorkbenchTree } from 'vs/platform/list/browser/listService';
import { OpenMode } from 'vs/base/parts/tree/browser/treeDefaults';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
const $ = dom.$;
@@ -40,6 +39,7 @@ export class VariablesView extends TreeViewsViewletPanel {
private settings: any;
private expandedElements: any[];
private needsRefresh: boolean;
private treeContainer: HTMLElement;
constructor(
options: IViewletViewOptions,
@@ -47,11 +47,9 @@ export class VariablesView extends TreeViewsViewletPanel {
@IDebugService private debugService: IDebugService,
@IKeybindingService keybindingService: IKeybindingService,
@IInstantiationService private instantiationService: IInstantiationService,
@IListService private listService: IListService,
@IContextKeyService private contextKeyService: IContextKeyService,
@IThemeService private themeService: IThemeService
@IConfigurationService configurationService: IConfigurationService
) {
super({ ...(options as IViewOptions), ariaHeaderLabel: nls.localize('variablesSection', "Variables Section") }, keybindingService, contextMenuService);
super({ ...(options as IViewOptions), ariaHeaderLabel: nls.localize('variablesSection', "Variables Section") }, keybindingService, contextMenuService, configurationService);
this.settings = options.viewletSettings;
this.expandedElements = [];
@@ -63,8 +61,6 @@ export class VariablesView extends TreeViewsViewletPanel {
this.expandedElements = expanded;
}
// Always clear tree highlight to avoid ending up in a broken state #12203
this.tree.clearHighlight();
this.needsRefresh = false;
this.tree.refresh().then(() => {
const stackFrame = this.debugService.getViewModel().focusedStackFrame;
@@ -88,16 +84,15 @@ export class VariablesView extends TreeViewsViewletPanel {
dom.addClass(container, 'debug-variables');
this.treeContainer = renderViewTree(container);
this.tree = new WorkbenchTree(this.treeContainer, {
this.tree = this.instantiationService.createInstance(WorkbenchTree, this.treeContainer, {
dataSource: new VariablesDataSource(),
renderer: this.instantiationService.createInstance(VariablesRenderer),
accessibilityProvider: new VariablesAccessibilityProvider(),
controller: this.instantiationService.createInstance(VariablesController, new VariablesActionProvider(this.debugService, this.keybindingService), MenuId.DebugVariablesContext)
controller: this.instantiationService.createInstance(VariablesController, new VariablesActionProvider(this.debugService, this.keybindingService), MenuId.DebugVariablesContext, { openMode: OpenMode.SINGLE_CLICK })
}, {
ariaLabel: nls.localize('variablesAriaTreeLabel', "Debug Variables"),
twistiePixels,
keyboardSupport: false
}, this.contextKeyService, this.listService, this.themeService);
twistiePixels
});
CONTEXT_VARIABLES_FOCUSED.bindTo(this.tree.contextKeyService);
@@ -106,7 +101,7 @@ export class VariablesView extends TreeViewsViewletPanel {
this.tree.setInput(viewModel);
const collapseAction = new CollapseAction(this.tree, false, 'explorer-action collapse-explorer');
this.toolbar.setActions(prepareActions([collapseAction]))();
this.toolbar.setActions([collapseAction])();
this.disposables.push(viewModel.onDidFocusStackFrame(sf => {
if (!this.isVisible() || !this.isExpanded()) {
@@ -127,21 +122,19 @@ export class VariablesView extends TreeViewsViewletPanel {
}));
this.disposables.push(this.debugService.getViewModel().onDidSelectExpression(expression => {
if (!expression || !(expression instanceof Variable)) {
return;
if (expression instanceof Variable) {
this.tree.refresh(expression, false).done(null, errors.onUnexpectedError);
}
this.tree.refresh(expression, false).then(() => {
this.tree.setHighlight(expression);
once(this.tree.onDidChangeHighlight)((e: IHighlightEvent) => {
if (!e.highlight) {
this.debugService.getViewModel().setSelectedExpression(null);
}
});
}).done(null, errors.onUnexpectedError);
}));
}
layoutBody(size: number): void {
if (this.treeContainer) {
this.treeContainer.style.height = size + 'px';
}
super.layoutBody(size);
}
public setExpanded(expanded: boolean): void {
super.setExpanded(expanded);
if (expanded && this.needsRefresh) {
@@ -187,6 +180,7 @@ class VariablesActionProvider implements IActionProvider {
const variable = <Variable>element;
actions.push(new SetValueAction(SetValueAction.ID, SetValueAction.LABEL, variable, this.debugService, this.keybindingService));
actions.push(new CopyValueAction(CopyValueAction.ID, CopyValueAction.LABEL, variable, this.debugService));
actions.push(new CopyEvaluatePathAction(CopyEvaluatePathAction.ID, CopyEvaluatePathAction.LABEL, variable));
actions.push(new Separator());
actions.push(new AddToWatchExpressionsAction(AddToWatchExpressionsAction.ID, AddToWatchExpressionsAction.LABEL, variable, this.debugService, this.keybindingService));

View File

@@ -9,8 +9,7 @@ import * as dom from 'vs/base/browser/dom';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { TPromise } from 'vs/base/common/winjs.base';
import * as errors from 'vs/base/common/errors';
import { prepareActions } from 'vs/workbench/browser/actions';
import { IHighlightEvent, IActionProvider, ITree, IDataSource, IRenderer, IAccessibilityProvider, IDragAndDropData, IDragOverReaction, DRAG_OVER_REJECT } from 'vs/base/parts/tree/browser/tree';
import { IActionProvider, ITree, IDataSource, IRenderer, IAccessibilityProvider, IDragAndDropData, IDragOverReaction, DRAG_OVER_REJECT } from 'vs/base/parts/tree/browser/tree';
import { CollapseAction } from 'vs/workbench/browser/viewlet';
import { TreeViewsViewletPanel, IViewletViewOptions, IViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { IDebugService, IExpression, CONTEXT_WATCH_EXPRESSIONS_FOCUSED } from 'vs/workbench/parts/debug/common/debug';
@@ -20,17 +19,16 @@ import { IContextMenuService, IContextViewService } from 'vs/platform/contextvie
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { MenuId } from 'vs/platform/actions/common/actions';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { once } from 'vs/base/common/event';
import { IAction, IActionItem } from 'vs/base/common/actions';
import { CopyValueAction } from 'vs/workbench/parts/debug/electron-browser/electronDebugActions';
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { equalsIgnoreCase } from 'vs/base/common/strings';
import { IMouseEvent, DragMouseEvent } from 'vs/base/browser/mouseEvent';
import { DefaultDragAndDrop } from 'vs/base/parts/tree/browser/treeDefaults';
import { IVariableTemplateData, renderVariable, renderRenameBox, renderExpressionValue, BaseDebugController, twistiePixels, renderViewTree } from 'vs/workbench/parts/debug/electron-browser/baseDebugView';
import { WorkbenchTree, IListService } from 'vs/platform/list/browser/listService';
import { DefaultDragAndDrop, OpenMode, ClickBehavior } from 'vs/base/parts/tree/browser/treeDefaults';
import { IVariableTemplateData, renderVariable, renderRenameBox, renderExpressionValue, BaseDebugController, twistiePixels, renderViewTree } from 'vs/workbench/parts/debug/browser/baseDebugView';
import { WorkbenchTree } from 'vs/platform/list/browser/listService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
const $ = dom.$;
const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024;
@@ -39,7 +37,7 @@ export class WatchExpressionsView extends TreeViewsViewletPanel {
private static readonly MEMENTO = 'watchexpressionsview.memento';
private onWatchExpressionsUpdatedScheduler: RunOnceScheduler;
private toReveal: IExpression;
private treeContainer: HTMLElement;
private settings: any;
private needsRefresh: boolean;
@@ -48,26 +46,15 @@ export class WatchExpressionsView extends TreeViewsViewletPanel {
@IContextMenuService contextMenuService: IContextMenuService,
@IDebugService private debugService: IDebugService,
@IKeybindingService keybindingService: IKeybindingService,
@IContextKeyService private contextKeyService: IContextKeyService,
@IListService private listService: IListService,
@IInstantiationService private instantiationService: IInstantiationService,
@IThemeService private themeService: IThemeService
@IConfigurationService configurationService: IConfigurationService
) {
super({ ...(options as IViewOptions), ariaHeaderLabel: nls.localize('expressionsSection', "Expressions Section") }, keybindingService, contextMenuService);
super({ ...(options as IViewOptions), ariaHeaderLabel: nls.localize('expressionsSection', "Expressions Section") }, keybindingService, contextMenuService, configurationService);
this.settings = options.viewletSettings;
this.disposables.push(this.debugService.getModel().onDidChangeWatchExpressions(we => {
// only expand when a new watch expression is added.
if (we instanceof Expression) {
this.setExpanded(true);
}
}));
this.onWatchExpressionsUpdatedScheduler = new RunOnceScheduler(() => {
this.needsRefresh = false;
this.tree.refresh().done(() => {
return this.toReveal instanceof Expression ? this.tree.reveal(this.toReveal) : TPromise.as(true);
}, errors.onUnexpectedError);
this.tree.refresh().done(undefined, errors.onUnexpectedError);
}, 50);
}
@@ -76,17 +63,16 @@ export class WatchExpressionsView extends TreeViewsViewletPanel {
this.treeContainer = renderViewTree(container);
const actionProvider = new WatchExpressionsActionProvider(this.debugService, this.keybindingService);
this.tree = new WorkbenchTree(this.treeContainer, {
dataSource: new WatchExpressionsDataSource(),
this.tree = this.instantiationService.createInstance(WorkbenchTree, this.treeContainer, {
dataSource: new WatchExpressionsDataSource(this.debugService),
renderer: this.instantiationService.createInstance(WatchExpressionsRenderer),
accessibilityProvider: new WatchExpressionsAccessibilityProvider(),
controller: this.instantiationService.createInstance(WatchExpressionsController, actionProvider, MenuId.DebugWatchContext),
controller: this.instantiationService.createInstance(WatchExpressionsController, actionProvider, MenuId.DebugWatchContext, { clickBehavior: ClickBehavior.ON_MOUSE_UP /* do not change to not break DND */, openMode: OpenMode.SINGLE_CLICK }),
dnd: new WatchExpressionsDragAndDrop(this.debugService)
}, {
ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'watchAriaTreeLabel' }, "Debug Watch Expressions"),
twistiePixels,
keyboardSupport: false
}, this.contextKeyService, this.listService, this.themeService);
twistiePixels
});
CONTEXT_WATCH_EXPRESSIONS_FOCUSED.bindTo(this.tree.contextKeyService);
@@ -95,7 +81,7 @@ export class WatchExpressionsView extends TreeViewsViewletPanel {
const addWatchExpressionAction = new AddWatchExpressionAction(AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL, this.debugService, this.keybindingService);
const collapseAction = new CollapseAction(this.tree, true, 'explorer-action collapse-explorer');
const removeAllWatchExpressionsAction = new RemoveAllWatchExpressionsAction(RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL, this.debugService, this.keybindingService);
this.toolbar.setActions(prepareActions([addWatchExpressionAction, collapseAction, removeAllWatchExpressionsAction]))();
this.toolbar.setActions([addWatchExpressionAction, collapseAction, removeAllWatchExpressionsAction])();
this.disposables.push(this.debugService.getModel().onDidChangeWatchExpressions(we => {
if (!this.isExpanded() || !this.isVisible()) {
@@ -103,28 +89,35 @@ export class WatchExpressionsView extends TreeViewsViewletPanel {
return;
}
this.tree.refresh().done(() => {
return we instanceof Expression ? this.tree.reveal(we) : TPromise.as(true);
}, errors.onUnexpectedError);
}));
this.disposables.push(this.debugService.getViewModel().onDidFocusStackFrame(() => {
if (!this.isExpanded() || !this.isVisible()) {
this.needsRefresh = true;
return;
}
if (!this.onWatchExpressionsUpdatedScheduler.isScheduled()) {
this.onWatchExpressionsUpdatedScheduler.schedule();
}
this.toReveal = we;
}));
this.disposables.push(this.debugService.getViewModel().onDidSelectExpression(expression => {
if (!expression || !(expression instanceof Expression)) {
return;
if (expression instanceof Expression) {
this.tree.refresh(expression, false).done(null, errors.onUnexpectedError);
}
this.tree.refresh(expression, false).then(() => {
this.tree.setHighlight(expression);
once(this.tree.onDidChangeHighlight)((e: IHighlightEvent) => {
if (!e.highlight) {
this.debugService.getViewModel().setSelectedExpression(null);
}
});
}).done(null, errors.onUnexpectedError);
}));
}
layoutBody(size: number): void {
if (this.treeContainer) {
this.treeContainer.style.height = size + 'px';
}
super.layoutBody(size);
}
public setExpanded(expanded: boolean): void {
super.setExpanded(expanded);
if (expanded && this.needsRefresh) {
@@ -200,6 +193,10 @@ class WatchExpressionsActionProvider implements IActionProvider {
class WatchExpressionsDataSource implements IDataSource {
constructor(private debugService: IDebugService) {
// noop
}
public getId(tree: ITree, element: any): string {
return element.getId();
}
@@ -215,7 +212,9 @@ class WatchExpressionsDataSource implements IDataSource {
public getChildren(tree: ITree, element: any): TPromise<any> {
if (element instanceof Model) {
return TPromise.as((<Model>element).getWatchExpressions());
const viewModel = this.debugService.getViewModel();
return TPromise.join(element.getWatchExpressions().map(we =>
we.name ? we.evaluate(viewModel.focusedProcess, viewModel.focusedStackFrame, 'watch').then(() => we) : TPromise.as(we)));
}
let expression = <Expression>element;
@@ -291,7 +290,7 @@ class WatchExpressionsRenderer implements IRenderer {
private renderWatchExpression(tree: ITree, watchExpression: IExpression, data: IWatchExpressionTemplateData): void {
let selectedExpression = this.debugService.getViewModel().getSelectedExpression();
if ((selectedExpression instanceof Expression && selectedExpression.getId() === watchExpression.getId()) || (watchExpression instanceof Expression && !watchExpression.name)) {
if ((selectedExpression instanceof Expression && selectedExpression.getId() === watchExpression.getId())) {
renderRenameBox(this.debugService, this.contextViewService, this.themeService, tree, watchExpression, data.expression, {
initialValue: watchExpression.name,
placeholder: nls.localize('watchExpressionPlaceholder', "Expression to watch"),
@@ -344,6 +343,10 @@ class WatchExpressionsController extends BaseDebugController {
const expression = <IExpression>element;
this.debugService.getViewModel().setSelectedExpression(expression);
return true;
} else if (element instanceof Model && event.detail === 2) {
// Double click in watch panel triggers to add a new watch expression
this.debugService.addWatchExpression();
return true;
}
return super.onLeftClick(tree, element, event);

View File

@@ -13,14 +13,14 @@ import * as paths from 'vs/base/common/paths';
import * as platform from 'vs/base/common/platform';
import { IJSONSchema, IJSONSchemaSnippet } from 'vs/base/common/jsonSchema';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IConfig, IRawAdapter, IAdapterExecutable, INTERNAL_CONSOLE_OPTIONS_SCHEMA } from 'vs/workbench/parts/debug/common/debug';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { IConfig, IRawAdapter, IAdapterExecutable, INTERNAL_CONSOLE_OPTIONS_SCHEMA, IConfigurationManager } from 'vs/workbench/parts/debug/common/debug';
import { IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ICommandService } from 'vs/platform/commands/common/commands';
export class Adapter {
constructor(private rawAdapter: IRawAdapter, public extensionDescription: IExtensionDescription,
constructor(private configurationManager: IConfigurationManager, private rawAdapter: IRawAdapter, public extensionDescription: IExtensionDescription,
@IConfigurationService private configurationService: IConfigurationService,
@ICommandService private commandService: ICommandService
) {
@@ -33,23 +33,32 @@ export class Adapter {
public getAdapterExecutable(root: IWorkspaceFolder, verifyAgainstFS = true): TPromise<IAdapterExecutable> {
if (this.rawAdapter.adapterExecutableCommand && root) {
return this.commandService.executeCommand<IAdapterExecutable>(this.rawAdapter.adapterExecutableCommand, root.uri.toString()).then(ad => {
return this.verifyAdapterDetails(ad, verifyAgainstFS);
});
}
return this.configurationManager.debugAdapterExecutable(root ? root.uri : undefined, this.rawAdapter.type).then(adapterExecutable => {
const adapterExecutable = <IAdapterExecutable>{
command: this.getProgram(),
args: this.getAttributeBasedOnPlatform('args')
};
const runtime = this.getRuntime();
if (runtime) {
const runtimeArgs = this.getAttributeBasedOnPlatform('runtimeArgs');
adapterExecutable.args = (runtimeArgs || []).concat([adapterExecutable.command]).concat(adapterExecutable.args || []);
adapterExecutable.command = runtime;
}
return this.verifyAdapterDetails(adapterExecutable, verifyAgainstFS);
if (adapterExecutable) {
return this.verifyAdapterDetails(adapterExecutable, verifyAgainstFS);
}
// try deprecated command based extension API
if (this.rawAdapter.adapterExecutableCommand && root) {
return this.commandService.executeCommand<IAdapterExecutable>(this.rawAdapter.adapterExecutableCommand, root.uri.toString()).then(ad => {
return this.verifyAdapterDetails(ad, verifyAgainstFS);
});
}
// fallback: executable contribution specified in package.json
adapterExecutable = <IAdapterExecutable>{
command: this.getProgram(),
args: this.getAttributeBasedOnPlatform('args')
};
const runtime = this.getRuntime();
if (runtime) {
const runtimeArgs = this.getAttributeBasedOnPlatform('runtimeArgs');
adapterExecutable.args = (runtimeArgs || []).concat([adapterExecutable.command]).concat(adapterExecutable.args || []);
adapterExecutable.command = runtime;
}
return this.verifyAdapterDetails(adapterExecutable, verifyAgainstFS);
});
}
private verifyAdapterDetails(details: IAdapterExecutable, verifyAgainstFS: boolean): TPromise<IAdapterExecutable> {
@@ -79,7 +88,7 @@ export class Adapter {
}
return TPromise.wrapError(new Error(nls.localize({ key: 'debugAdapterCannotDetermineExecutable', comment: ['Adapter executable file not found'] },
"Cannot determine executable for debug adapter '{0}'.", details.command)));
"Cannot determine executable for debug adapter '{0}'.", this.type)));
}
private getRuntime(): string {

View File

@@ -21,7 +21,7 @@ export abstract class V8Protocol {
this.sequence = 1;
this.contentLength = -1;
this.pendingRequests = new Map<number, (e: DebugProtocol.Response) => void>();
this.rawData = new Buffer(0);
this.rawData = Buffer.allocUnsafe(0);
}
public getId(): string {

View File

@@ -7,12 +7,13 @@ import * as assert from 'assert';
import { ViewModel } from 'vs/workbench/parts/debug/common/debugViewModel';
import { StackFrame, Expression, Thread, Process } from 'vs/workbench/parts/debug/common/debugModel';
import { MockSession } from 'vs/workbench/parts/debug/test/common/mockDebug';
import { MockContextKeyService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
suite('Debug - View Model', () => {
let model: ViewModel;
setup(() => {
model = new ViewModel();
model = new ViewModel(new MockContextKeyService());
});
teardown(() => {
@@ -26,7 +27,7 @@ suite('Debug - View Model', () => {
const process = new Process({ name: 'mockProcess', type: 'node', request: 'launch' }, mockSession);
const thread = new Thread(process, 'myThread', 1);
const frame = new StackFrame(thread, 1, null, 'app.js', 'normal', { startColumn: 1, startLineNumber: 1, endColumn: undefined, endLineNumber: undefined }, 0);
model.setFocusedStackFrame(frame, process, false);
model.setFocus(frame, thread, process, false);
assert.equal(model.focusedStackFrame.getId(), frame.getId());
assert.equal(model.focusedThread.threadId, 1);

View File

@@ -6,47 +6,44 @@
import uri from 'vs/base/common/uri';
import Event, { Emitter } from 'vs/base/common/event';
import { TPromise } from 'vs/base/common/winjs.base';
import * as debug from 'vs/workbench/parts/debug/common/debug';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { ILaunch, IDebugService, State, DebugEvent, IProcess, IConfigurationManager, IStackFrame, IBreakpointData, IBreakpointUpdateData, IConfig, IModel, IViewModel, ISession } from 'vs/workbench/parts/debug/common/debug';
export class MockDebugService implements debug.IDebugService {
export class MockDebugService implements IDebugService {
public _serviceBrand: any;
public get state(): debug.State {
public get state(): State {
return null;
}
public get onDidCustomEvent(): Event<debug.DebugEvent> {
public get onDidCustomEvent(): Event<DebugEvent> {
return null;
}
public get onDidNewProcess(): Event<debug.IProcess> {
public get onDidNewProcess(): Event<IProcess> {
return null;
}
public get onDidEndProcess(): Event<debug.IProcess> {
public get onDidEndProcess(): Event<IProcess> {
return null;
}
public get onDidChangeState(): Event<debug.State> {
public get onDidChangeState(): Event<State> {
return null;
}
public getConfigurationManager(): debug.IConfigurationManager {
public getConfigurationManager(): IConfigurationManager {
return null;
}
public focusStackFrameAndEvaluate(focusedStackFrame: debug.IStackFrame): TPromise<void> {
public focusStackFrame(focusedStackFrame: IStackFrame): void {
}
public addBreakpoints(uri: uri, rawBreakpoints: IBreakpointData[]): TPromise<void> {
return TPromise.as(null);
}
public addBreakpoints(uri: uri, rawBreakpoints: debug.IRawBreakpoint[]): TPromise<void> {
return TPromise.as(null);
}
public updateBreakpoints(uri: uri, data: { [id: string]: DebugProtocol.Breakpoint }): TPromise<void> {
return TPromise.as(null);
}
public updateBreakpoints(uri: uri, data: { [id: string]: IBreakpointUpdateData }, sendOnResourceSaved: boolean): void { }
public enableOrDisableBreakpoints(enabled: boolean): TPromise<void> {
return TPromise.as(null);
@@ -88,11 +85,7 @@ export class MockDebugService implements debug.IDebugService {
public removeWatchExpressions(id?: string): void { }
public evaluateWatchExpressions(): TPromise<void> {
return TPromise.as(null);
}
public startDebugging(root: IWorkspaceFolder, configOrName?: debug.IConfig | string, noDebug?: boolean): TPromise<any> {
public startDebugging(launch: ILaunch, configOrName?: IConfig | string, noDebug?: boolean): TPromise<any> {
return TPromise.as(null);
}
@@ -104,11 +97,11 @@ export class MockDebugService implements debug.IDebugService {
return TPromise.as(null);
}
public getModel(): debug.IModel {
public getModel(): IModel {
return null;
}
public getViewModel(): debug.IViewModel {
public getViewModel(): IViewModel {
return null;
}
@@ -117,7 +110,7 @@ export class MockDebugService implements debug.IDebugService {
public sourceIsNotAvailable(uri: uri): void { }
}
export class MockSession implements debug.ISession {
export class MockSession implements ISession {
public readyForBreakpoints = true;
public emittedStopped = true;
@@ -173,7 +166,7 @@ export class MockSession implements debug.ISession {
return {};
}
public get onDidEvent(): Event<debug.DebugEvent> {
public get onDidEvent(): Event<DebugEvent> {
return null;
}
@@ -182,8 +175,8 @@ export class MockSession implements debug.ISession {
return emitter.event;
}
public get onDidExitAdapter(): Event<debug.DebugEvent> {
const emitter = new Emitter<debug.DebugEvent>();
public get onDidExitAdapter(): Event<DebugEvent> {
const emitter = new Emitter<DebugEvent>();
return emitter.event;
}

View File

@@ -6,9 +6,12 @@
import * as assert from 'assert';
import * as paths from 'vs/base/common/paths';
import * as platform from 'vs/base/common/platform';
import { IRawAdapter } from 'vs/workbench/parts/debug/common/debug';
import { IRawAdapter, IAdapterExecutable, IConfigurationManager } from 'vs/workbench/parts/debug/common/debug';
import { Adapter } from 'vs/workbench/parts/debug/node/debugAdapter';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import uri from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
suite('Debug - Adapter', () => {
let adapter: Adapter;
@@ -41,9 +44,14 @@ suite('Debug - Adapter', () => {
}
]
};
const configurationManager = {
debugAdapterExecutable(folderUri: uri | undefined, type: string): TPromise<IAdapterExecutable | undefined> {
return TPromise.as(undefined);
}
};
setup(() => {
adapter = new Adapter(rawAdapter, { extensionFolderPath, id: 'adapter', name: 'myAdapter', version: '1.0.0', publisher: 'vscode', isBuiltin: false, engines: null },
adapter = new Adapter(<IConfigurationManager>configurationManager, rawAdapter, { extensionFolderPath, id: 'adapter', name: 'myAdapter', version: '1.0.0', publisher: 'vscode', isBuiltin: false, engines: null },
new TestConfigurationService(), null);
});

View File

@@ -305,16 +305,15 @@ suite('Debug - Model', () => {
const process = new Process({ name: 'mockProcess', type: 'node', request: 'launch' }, rawSession);
const thread = new Thread(process, 'mockthread', 1);
const stackFrame = new StackFrame(thread, 1, null, 'app.js', 'normal', { startLineNumber: 1, startColumn: 1, endLineNumber: undefined, endColumn: undefined }, 0);
model.addWatchExpression(process, stackFrame, 'console').done();
model.addWatchExpression(process, stackFrame, 'console').done();
model.addWatchExpression(process, stackFrame, 'console');
model.addWatchExpression(process, stackFrame, 'console');
let watchExpressions = model.getWatchExpressions();
assertWatchExpressions(watchExpressions, 'console');
model.renameWatchExpression(process, stackFrame, watchExpressions[0].getId(), 'new_name').done();
model.renameWatchExpression(process, stackFrame, watchExpressions[1].getId(), 'new_name').done();
model.renameWatchExpression(process, stackFrame, watchExpressions[0].getId(), 'new_name');
model.renameWatchExpression(process, stackFrame, watchExpressions[1].getId(), 'new_name');
assertWatchExpressions(model.getWatchExpressions(), 'new_name');
model.evaluateWatchExpressions(process, null);
assertWatchExpressions(model.getWatchExpressions(), 'new_name');
model.addWatchExpression(process, stackFrame, 'mockExpression');

View File

@@ -8,7 +8,7 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { EditorAction, ServicesAccessor, IActionOptions } from 'vs/editor/browser/editorExtensions';
import { grammarsExtPoint, ITMSyntaxExtensionPoint } from 'vs/workbench/services/textMate/electron-browser/TMGrammars';
import { IModeService } from 'vs/editor/common/services/modeService';
import { IExtensionService, ExtensionPointContribution } from 'vs/platform/extensions/common/extensions';
import { IExtensionService, ExtensionPointContribution } from 'vs/workbench/services/extensions/common/extensions';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { LanguageId, LanguageIdentifier } from 'vs/editor/common/modes';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';

View File

@@ -6,29 +6,28 @@
import * as nls from 'vs/nls';
import * as env from 'vs/base/common/platform';
import { TPromise } from 'vs/base/common/winjs.base';
import { Registry } from 'vs/platform/registry/common/platform';
import { IAction, Action } from 'vs/base/common/actions';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
import paths = require('vs/base/common/paths');
import resources = require('vs/base/common/resources');
import { Scope, IActionBarRegistry, Extensions as ActionBarExtensions, ActionBarContributor } from 'vs/workbench/browser/actions';
import uri from 'vs/base/common/uri';
import { explorerItemToFileResource } from 'vs/workbench/parts/files/common/files';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { ITerminalService } from 'vs/workbench/parts/execution/common/execution';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { toResource } from 'vs/workbench/common/editor';
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { ITerminalService as IIntegratedTerminalService, KEYBINDING_CONTEXT_TERMINAL_NOT_FOCUSED } from 'vs/workbench/parts/terminal/common/terminal';
import { DEFAULT_TERMINAL_WINDOWS, DEFAULT_TERMINAL_LINUX_READY, DEFAULT_TERMINAL_OSX, ITerminalConfiguration } from 'vs/workbench/parts/execution/electron-browser/terminal';
import { getDefaultTerminalWindows, getDefaultTerminalLinuxReady, DEFAULT_TERMINAL_OSX, ITerminalConfiguration } from 'vs/workbench/parts/execution/electron-browser/terminal';
import { WinTerminalService, MacTerminalService, LinuxTerminalService } from 'vs/workbench/parts/execution/electron-browser/terminalService';
import { IHistoryService } from 'vs/workbench/services/history/common/history';
import { ResourceContextKey } from 'vs/workbench/common/resources';
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IFileService } from 'vs/platform/files/common/files';
import { IListService } from 'vs/platform/list/browser/listService';
import { getMultiSelectedResources } from 'vs/workbench/parts/files/browser/files';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { Schemas } from 'vs/base/common/network';
import { distinct } from 'vs/base/common/arrays';
if (env.isWindows) {
registerSingleton(ITerminalService, WinTerminalService);
@@ -38,7 +37,7 @@ if (env.isWindows) {
registerSingleton(ITerminalService, LinuxTerminalService);
}
DEFAULT_TERMINAL_LINUX_READY.then(defaultTerminalLinux => {
getDefaultTerminalLinuxReady().then(defaultTerminalLinux => {
let configurationRegistry = <IConfigurationRegistry>Registry.as(Extensions.Configuration);
configurationRegistry.registerConfiguration({
'id': 'externalTerminal',
@@ -59,7 +58,7 @@ DEFAULT_TERMINAL_LINUX_READY.then(defaultTerminalLinux => {
'terminal.external.windowsExec': {
'type': 'string',
'description': nls.localize('terminal.external.windowsExec', "Customizes which terminal to run on Windows."),
'default': DEFAULT_TERMINAL_WINDOWS,
'default': getDefaultTerminalWindows(),
'isExecutable': true
},
'terminal.external.osxExec': {
@@ -78,152 +77,73 @@ DEFAULT_TERMINAL_LINUX_READY.then(defaultTerminalLinux => {
});
});
const OPEN_IN_TERMINAL_COMMAND_ID = 'openInTerminal';
CommandsRegistry.registerCommand({
id: OPEN_IN_TERMINAL_COMMAND_ID,
handler: (accessor, resource: uri) => {
const configurationService = accessor.get(IConfigurationService);
const editorService = accessor.get(IWorkbenchEditorService);
const fileService = accessor.get(IFileService);
const integratedTerminalService = accessor.get(IIntegratedTerminalService);
const terminalService = accessor.get(ITerminalService);
const resources = getMultiSelectedResources(resource, accessor.get(IListService), editorService);
export abstract class AbstractOpenInTerminalAction extends Action {
private resource: uri;
constructor(
id: string,
label: string,
@IWorkbenchEditorService protected editorService: IWorkbenchEditorService,
@IWorkspaceContextService protected contextService: IWorkspaceContextService,
@IHistoryService protected historyService: IHistoryService
) {
super(id, label);
this.order = 49; // Allow other actions to position before or after
return fileService.resolveFiles(resources.map(r => ({ resource: r }))).then(stats => {
const directoriesToOpen = distinct(stats.map(({ stat }) => stat.isDirectory ? stat.resource.fsPath : paths.dirname(stat.resource.fsPath)));
return directoriesToOpen.map(dir => {
if (configurationService.getValue<ITerminalConfiguration>().terminal.explorerKind === 'integrated') {
const instance = integratedTerminalService.createInstance({ cwd: dir }, true);
if (instance && (resources.length === 1 || !resource || dir === resource.fsPath || dir === paths.dirname(resource.fsPath))) {
integratedTerminalService.setActiveInstance(instance);
integratedTerminalService.showPanel(true);
}
} else {
terminalService.openTerminal(dir);
}
});
});
}
});
public setResource(resource: uri): void {
this.resource = resource;
this.enabled = !paths.isUNC(this.resource.fsPath);
}
public getPathToOpen(): string {
let pathToOpen: string;
// Try workspace path first
const root = this.historyService.getLastActiveWorkspaceRoot('file');
pathToOpen = this.resource ? this.resource.fsPath : (root && root.fsPath);
// Otherwise check if we have an active file open
if (!pathToOpen) {
const file = toResource(this.editorService.getActiveEditorInput(), { supportSideBySide: true, filter: 'file' });
if (file) {
pathToOpen = paths.dirname(file.fsPath); // take parent folder of file
}
}
return pathToOpen;
}
}
export class OpenConsoleAction extends AbstractOpenInTerminalAction {
public static readonly ID = 'workbench.action.terminal.openNativeConsole';
public static readonly Label = env.isWindows ? nls.localize('globalConsoleActionWin', "Open New Command Prompt") :
nls.localize('globalConsoleActionMacLinux', "Open New Terminal");
public static readonly ScopedLabel = env.isWindows ? nls.localize('scopedConsoleActionWin', "Open in Command Prompt") :
nls.localize('scopedConsoleActionMacLinux', "Open in Terminal");
constructor(
id: string,
label: string,
@ITerminalService private terminalService: ITerminalService,
@IWorkbenchEditorService editorService: IWorkbenchEditorService,
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IHistoryService historyService: IHistoryService
) {
super(id, label, editorService, contextService, historyService);
}
public run(event?: any): TPromise<any> {
let pathToOpen = this.getPathToOpen();
this.terminalService.openTerminal(pathToOpen);
return TPromise.as(null);
}
}
export class OpenIntegratedTerminalAction extends AbstractOpenInTerminalAction {
public static readonly ID = 'workbench.action.terminal.openFolderInIntegratedTerminal';
public static readonly Label = nls.localize('openFolderInIntegratedTerminal', "Open in Terminal");
constructor(
id: string,
label: string,
@IIntegratedTerminalService private integratedTerminalService: IIntegratedTerminalService,
@IWorkbenchEditorService editorService: IWorkbenchEditorService,
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IHistoryService historyService: IHistoryService
) {
super(id, label, editorService, contextService, historyService);
}
public run(event?: any): TPromise<any> {
let pathToOpen = this.getPathToOpen();
const instance = this.integratedTerminalService.createInstance({ cwd: pathToOpen }, true);
if (instance) {
this.integratedTerminalService.setActiveInstance(instance);
this.integratedTerminalService.showPanel(true);
}
return TPromise.as(null);
}
}
export class ExplorerViewerActionContributor extends ActionBarContributor {
constructor(
@IInstantiationService private instantiationService: IInstantiationService,
@IConfigurationService private configurationService: IConfigurationService
) {
super();
}
public hasSecondaryActions(context: any): boolean {
const fileResource = explorerItemToFileResource(context.element);
return fileResource && fileResource.resource.scheme === 'file';
}
public getSecondaryActions(context: any): IAction[] {
let fileResource = explorerItemToFileResource(context.element);
let resource = fileResource.resource;
// We want the parent unless this resource is a directory
if (!fileResource.isDirectory) {
resource = resources.dirname(resource);
}
const configuration = this.configurationService.getValue<ITerminalConfiguration>();
const explorerKind = configuration.terminal.explorerKind;
if (explorerKind === 'integrated') {
let action = this.instantiationService.createInstance(OpenIntegratedTerminalAction, OpenIntegratedTerminalAction.ID, OpenIntegratedTerminalAction.Label);
action.setResource(resource);
return [action];
} else {
let action = this.instantiationService.createInstance(OpenConsoleAction, OpenConsoleAction.ID, OpenConsoleAction.ScopedLabel);
action.setResource(resource);
return [action];
const OPEN_NATIVE_CONSOLE_COMMAND_ID = 'workbench.action.terminal.openNativeConsole';
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: OPEN_NATIVE_CONSOLE_COMMAND_ID,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_C,
when: KEYBINDING_CONTEXT_TERMINAL_NOT_FOCUSED,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
handler: (accessor) => {
const historyService = accessor.get(IHistoryService);
const terminalService = accessor.get(ITerminalService);
const root = historyService.getLastActiveWorkspaceRoot(Schemas.file);
if (root) {
terminalService.openTerminal(root.fsPath);
}
}
}
});
const actionBarRegistry = Registry.as<IActionBarRegistry>(ActionBarExtensions.Actionbar);
actionBarRegistry.registerActionBarContributor(Scope.VIEWER, ExplorerViewerActionContributor);
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
command: {
id: OPEN_NATIVE_CONSOLE_COMMAND_ID,
title: env.isWindows ? nls.localize('globalConsoleActionWin', "Open New Command Prompt") :
nls.localize('globalConsoleActionMacLinux', "Open New Terminal")
}
});
// Register Global Action to Open Console
Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions).registerWorkbenchAction(
new SyncActionDescriptor(
OpenConsoleAction,
OpenConsoleAction.ID,
OpenConsoleAction.Label,
{ primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_C },
KEYBINDING_CONTEXT_TERMINAL_NOT_FOCUSED
),
env.isWindows ? 'Open New Command Prompt' : 'Open New Terminal'
);
const openConsoleCommand = {
id: OPEN_IN_TERMINAL_COMMAND_ID,
title: env.isWindows ? nls.localize('scopedConsoleActionWin', "Open in Command Prompt") :
nls.localize('scopedConsoleActionMacLinux', "Open in Terminal")
};
MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, {
group: 'navigation',
order: 30,
command: openConsoleCommand,
when: ResourceContextKey.Scheme.isEqualTo(Schemas.file)
});
MenuRegistry.appendMenuItem(MenuId.ExplorerContext, {
group: 'navigation',
order: 30,
command: openConsoleCommand,
when: ResourceContextKey.Scheme.isEqualTo(Schemas.file)
});

View File

@@ -8,32 +8,44 @@ import env = require('vs/base/common/platform');
import * as pfs from 'vs/base/node/pfs';
import { TPromise } from 'vs/base/common/winjs.base';
export const DEFAULT_TERMINAL_LINUX_READY = new TPromise<string>(c => {
if (env.isLinux) {
TPromise.join([pfs.exists('/etc/debian_version'), process.lazyEnv]).then(([isDebian]) => {
if (isDebian) {
c('x-terminal-emulator');
} else if (process.env.DESKTOP_SESSION === 'gnome' || process.env.DESKTOP_SESSION === 'gnome-classic') {
c('gnome-terminal');
} else if (process.env.DESKTOP_SESSION === 'kde-plasma') {
c('konsole');
} else if (process.env.COLORTERM) {
c(process.env.COLORTERM);
} else if (process.env.TERM) {
c(process.env.TERM);
} else {
c('xterm');
let _DEFAULT_TERMINAL_LINUX_READY: TPromise<string> = null;
export function getDefaultTerminalLinuxReady(): TPromise<string> {
if (!_DEFAULT_TERMINAL_LINUX_READY) {
_DEFAULT_TERMINAL_LINUX_READY = new TPromise<string>(c => {
if (env.isLinux) {
TPromise.join([pfs.exists('/etc/debian_version'), process.lazyEnv]).then(([isDebian]) => {
if (isDebian) {
c('x-terminal-emulator');
} else if (process.env.DESKTOP_SESSION === 'gnome' || process.env.DESKTOP_SESSION === 'gnome-classic') {
c('gnome-terminal');
} else if (process.env.DESKTOP_SESSION === 'kde-plasma') {
c('konsole');
} else if (process.env.COLORTERM) {
c(process.env.COLORTERM);
} else if (process.env.TERM) {
c(process.env.TERM);
} else {
c('xterm');
}
});
return;
}
});
return;
}
c('xterm');
});
c('xterm');
});
}
return _DEFAULT_TERMINAL_LINUX_READY;
}
export const DEFAULT_TERMINAL_OSX = 'Terminal.app';
export const DEFAULT_TERMINAL_WINDOWS = `${process.env.windir}\\${process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432') ? 'Sysnative' : 'System32'}\\cmd.exe`;
let _DEFAULT_TERMINAL_WINDOWS: string = null;
export function getDefaultTerminalWindows(): string {
if (!_DEFAULT_TERMINAL_WINDOWS) {
_DEFAULT_TERMINAL_WINDOWS = `${process.env.windir}\\${process.env.hasOwnProperty('PROCESSOR_ARCHITEW6432') ? 'Sysnative' : 'System32'}\\cmd.exe`;
}
return _DEFAULT_TERMINAL_WINDOWS;
}
export interface ITerminalConfiguration {
terminal: {

View File

@@ -14,7 +14,7 @@ import { assign } from 'vs/base/common/objects';
import { TPromise } from 'vs/base/common/winjs.base';
import { ITerminalService } from 'vs/workbench/parts/execution/common/execution';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ITerminalConfiguration, DEFAULT_TERMINAL_WINDOWS, DEFAULT_TERMINAL_LINUX_READY, DEFAULT_TERMINAL_OSX } from 'vs/workbench/parts/execution/electron-browser/terminal';
import { ITerminalConfiguration, getDefaultTerminalWindows, getDefaultTerminalLinuxReady, DEFAULT_TERMINAL_OSX } from 'vs/workbench/parts/execution/electron-browser/terminal';
import uri from 'vs/base/common/uri';
import { IProcessEnvironment } from 'vs/base/common/platform';
@@ -31,7 +31,7 @@ export class WinTerminalService implements ITerminalService {
private static readonly CMD = 'cmd.exe';
constructor(
@IConfigurationService private _configurationService: IConfigurationService
@IConfigurationService private readonly _configurationService: IConfigurationService
) {
}
@@ -46,7 +46,7 @@ export class WinTerminalService implements ITerminalService {
const configuration = this._configurationService.getValue<ITerminalConfiguration>();
const terminalConfig = configuration.terminal.external;
const exec = terminalConfig.windowsExec || DEFAULT_TERMINAL_WINDOWS;
const exec = terminalConfig.windowsExec || getDefaultTerminalWindows();
return new TPromise<void>((c, e) => {
@@ -78,7 +78,7 @@ export class WinTerminalService implements ITerminalService {
private spawnTerminal(spawner, configuration: ITerminalConfiguration, command: string, cwd?: string): TPromise<void> {
const terminalConfig = configuration.terminal.external;
const exec = terminalConfig.windowsExec || DEFAULT_TERMINAL_WINDOWS;
const exec = terminalConfig.windowsExec || getDefaultTerminalWindows();
const spawnType = this.getSpawnType(exec);
// Make the drive letter uppercase on Windows (see #9448)
@@ -120,7 +120,7 @@ export class MacTerminalService implements ITerminalService {
private static readonly OSASCRIPT = '/usr/bin/osascript'; // osascript is the AppleScript interpreter on OS X
constructor(
@IConfigurationService private _configurationService: IConfigurationService
@IConfigurationService private readonly _configurationService: IConfigurationService
) { }
public openTerminal(cwd?: string): void {
@@ -211,7 +211,7 @@ export class LinuxTerminalService implements ITerminalService {
private static readonly WAIT_MESSAGE = nls.localize('press.any.key', "Press any key to continue...");
constructor(
@IConfigurationService private _configurationService: IConfigurationService
@IConfigurationService private readonly _configurationService: IConfigurationService
) { }
@@ -226,7 +226,7 @@ export class LinuxTerminalService implements ITerminalService {
const configuration = this._configurationService.getValue<ITerminalConfiguration>();
const terminalConfig = configuration.terminal.external;
const execPromise = terminalConfig.linuxExec ? TPromise.as(terminalConfig.linuxExec) : DEFAULT_TERMINAL_LINUX_READY;
const execPromise = terminalConfig.linuxExec ? TPromise.as(terminalConfig.linuxExec) : getDefaultTerminalLinuxReady();
return new TPromise<void>((c, e) => {
@@ -280,7 +280,7 @@ export class LinuxTerminalService implements ITerminalService {
private spawnTerminal(spawner, configuration: ITerminalConfiguration, cwd?: string): TPromise<void> {
const terminalConfig = configuration.terminal.external;
const execPromise = terminalConfig.linuxExec ? TPromise.as(terminalConfig.linuxExec) : DEFAULT_TERMINAL_LINUX_READY;
const execPromise = terminalConfig.linuxExec ? TPromise.as(terminalConfig.linuxExec) : getDefaultTerminalLinuxReady();
const env = cwd ? { cwd: cwd } : void 0;
return new TPromise<void>((c, e) => {

View File

@@ -7,7 +7,7 @@
import { deepEqual, equal } from 'assert';
import { WinTerminalService, LinuxTerminalService, MacTerminalService } from 'vs/workbench/parts/execution/electron-browser/terminalService';
import { DEFAULT_TERMINAL_WINDOWS, DEFAULT_TERMINAL_LINUX_READY, DEFAULT_TERMINAL_OSX } from 'vs/workbench/parts/execution/electron-browser/terminal';
import { getDefaultTerminalWindows, getDefaultTerminalLinuxReady, DEFAULT_TERMINAL_OSX } from 'vs/workbench/parts/execution/electron-browser/terminal';
suite('Execution - TerminalService', () => {
let mockOnExit: Function;
@@ -61,7 +61,7 @@ suite('Execution - TerminalService', () => {
let mockSpawner = {
spawn: (command: any, args: any, opts: any) => {
// assert
equal(args[args.length - 1], DEFAULT_TERMINAL_WINDOWS, 'terminal should equal expected');
equal(args[args.length - 1], getDefaultTerminalWindows(), 'terminal should equal expected');
done();
return {
on: (evt: any) => evt
@@ -197,7 +197,7 @@ suite('Execution - TerminalService', () => {
});
test(`LinuxTerminalService - uses default terminal when configuration.terminal.external.linuxExec is undefined`, done => {
DEFAULT_TERMINAL_LINUX_READY.then(defaultTerminalLinux => {
getDefaultTerminalLinuxReady().then(defaultTerminalLinux => {
let testCwd = 'path/to/workspace';
let mockSpawner = {
spawn: (command: any, args: any, opts: any) => {
@@ -220,4 +220,4 @@ suite('Execution - TerminalService', () => {
);
});
});
});
});

View File

@@ -9,13 +9,14 @@ import { IMouseEvent } from 'vs/base/browser/mouseEvent';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { TPromise, Promise } from 'vs/base/common/winjs.base';
import { IDataSource, ITree, IRenderer } from 'vs/base/parts/tree/browser/tree';
import { DefaultController, ClickBehavior } from 'vs/base/parts/tree/browser/treeDefaults';
import { Action } from 'vs/base/common/actions';
import { IExtensionDependencies, IExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/common/extensions';
import { once } from 'vs/base/common/event';
import { domEvent } from 'vs/base/browser/event';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { WorkbenchTreeController } from 'vs/platform/list/browser/listService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
export interface IExtensionTemplateData {
icon: HTMLImageElement;
@@ -156,21 +157,24 @@ export class Renderer implements IRenderer {
}
}
export class Controller extends DefaultController {
export class Controller extends WorkbenchTreeController {
constructor( @IExtensionsWorkbenchService private extensionsWorkdbenchService: IExtensionsWorkbenchService) {
super({ clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: false });
constructor(
@IExtensionsWorkbenchService private extensionsWorkdbenchService: IExtensionsWorkbenchService,
@IConfigurationService configurationService: IConfigurationService
) {
super({}, configurationService);
// TODO@Sandeep this should be a command
this.downKeyBindingDispatcher.set(KeyMod.CtrlCmd | KeyCode.Enter, (tree: ITree, event: any) => this.openExtension(tree, true));
}
protected onLeftClick(tree: ITree, element: IExtensionDependencies, event: IMouseEvent): boolean {
let currentFoucssed = tree.getFocus();
let currentFocused = tree.getFocus();
if (super.onLeftClick(tree, element, event)) {
if (element.dependent === null) {
if (currentFoucssed) {
tree.setFocus(currentFoucssed);
if (currentFocused) {
tree.setFocus(currentFocused);
} else {
tree.focusFirst();
}

View File

@@ -16,29 +16,27 @@ import Event, { Emitter, once, chain } from 'vs/base/common/event';
import Cache from 'vs/base/common/cache';
import { Action } from 'vs/base/common/actions';
import { isPromiseCanceledError } from 'vs/base/common/errors';
import Severity from 'vs/base/common/severity';
import { IDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
import { Builder } from 'vs/base/browser/builder';
import { domEvent } from 'vs/base/browser/event';
import { append, $, addClass, removeClass, finalHandler, join } from 'vs/base/browser/dom';
import { append, $, addClass, removeClass, finalHandler, join, toggleClass } from 'vs/base/browser/dom';
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IExtensionManifest, IKeyBinding, IView, IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionManifest, IKeyBinding, IView, IExtensionTipsService, LocalExtensionType } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ResolvedKeybinding, KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensionsInput';
import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, IExtension, IExtensionDependencies } from 'vs/workbench/parts/extensions/common/extensions';
import { Renderer, DataSource, Controller } from 'vs/workbench/parts/extensions/browser/dependenciesViewer';
import { RatingsWidget, InstallWidget } from 'vs/workbench/parts/extensions/browser/extensionsWidgets';
import { RatingsWidget, InstallCountWidget } from 'vs/workbench/parts/extensions/browser/extensionsWidgets';
import { EditorOptions } from 'vs/workbench/common/editor';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { CombinedInstallAction, UpdateAction, EnableAction, DisableAction, BuiltinStatusLabelAction, ReloadAction, MaliciousStatusLabelAction } from 'vs/workbench/parts/extensions/browser/extensionsActions';
import WebView from 'vs/workbench/parts/html/browser/webview';
import { CombinedInstallAction, UpdateAction, EnableAction, DisableAction, ReloadAction, MaliciousStatusLabelAction } from 'vs/workbench/parts/extensions/browser/extensionsActions';
import { Webview } from 'vs/workbench/parts/html/browser/webview';
import { KeybindingIO } from 'vs/workbench/services/keybinding/common/keybindingIO';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { IMessageService } from 'vs/platform/message/common/message';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
import { Position } from 'vs/platform/editor/common/editor';
@@ -46,31 +44,34 @@ import { IPartService, Parts } from 'vs/workbench/services/part/common/partServi
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { KeybindingLabel } from 'vs/base/browser/ui/keybindingLabel/keybindingLabel';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IContextKeyService, RawContextKey, ContextKeyExpr, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { Command, ICommandOptions } from 'vs/editor/browser/editorExtensions';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { Color } from 'vs/base/common/color';
import { WorkbenchTree, IListService } from 'vs/platform/list/browser/listService';
import { WorkbenchTree } from 'vs/platform/list/browser/listService';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { assign } from 'vs/base/common/objects';
import { INotificationService } from 'vs/platform/notification/common/notification';
/** A context key that is set when an extension editor webview has focus. */
export const KEYBINDING_CONTEXT_EXTENSIONEDITOR_WEBVIEW_FOCUS = new RawContextKey<boolean>('extensionEditorWebviewFocus', undefined);
/** A context key that is set when an extension editor webview not have focus. */
export const KEYBINDING_CONTEXT_EXTENSIONEDITOR_WEBVIEW_NOT_FOCUSED: ContextKeyExpr = KEYBINDING_CONTEXT_EXTENSIONEDITOR_WEBVIEW_FOCUS.toNegated();
/** A context key that is set when the find widget find input in extension editor webview is focused. */
export const KEYBINDING_CONTEXT_EXTENSIONEDITOR_FIND_WIDGET_INPUT_FOCUSED = new RawContextKey<boolean>('extensionEditorFindWidgetInputFocused', false);
/** A context key that is set when the find widget find input in extension editor webview is not focused. */
export const KEYBINDING_CONTEXT_EXTENSIONEDITOR_FIND_WIDGET_INPUT_NOT_FOCUSED: ContextKeyExpr = KEYBINDING_CONTEXT_EXTENSIONEDITOR_FIND_WIDGET_INPUT_FOCUSED.toNegated();
function renderBody(body: string): string {
const styleSheetPath = require.toUrl('./media/markdown.css').replace('file://', 'vscode-core-resource://');
return `<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src https:; media-src https:; script-src 'none'; style-src file:; child-src 'none'; frame-src 'none';">
<link rel="stylesheet" type="text/css" href="${require.toUrl('./media/markdown.css')}">
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; img-src https: data:; media-src https:; script-src 'none'; style-src vscode-core-resource:; child-src 'none'; frame-src 'none';">
<link rel="stylesheet" type="text/css" href="${styleSheetPath}">
</head>
<body>${body}</body>
<body>
<a id="scroll-to-top" role="button" aria-label="scroll to top" href="#"><span class="icon"></span></a>
${body}
</body>
</html>`;
}
@@ -147,12 +148,13 @@ interface ILayoutParticipant {
export class ExtensionEditor extends BaseEditor {
static ID: string = 'workbench.editor.extension';
static readonly ID: string = 'workbench.editor.extension';
private icon: HTMLImageElement;
private name: HTMLElement;
private identifier: HTMLElement;
private preview: HTMLElement;
private builtin: HTMLElement;
private license: HTMLElement;
private publisher: HTMLElement;
private installCount: HTMLElement;
@@ -176,22 +178,23 @@ export class ExtensionEditor extends BaseEditor {
private contentDisposables: IDisposable[] = [];
private transientDisposables: IDisposable[] = [];
private disposables: IDisposable[];
private activeWebview: WebView;
private activeWebview: Webview;
constructor(
@ITelemetryService telemetryService: ITelemetryService,
@IInstantiationService private instantiationService: IInstantiationService,
@IViewletService private viewletService: IViewletService,
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IViewletService private readonly viewletService: IViewletService,
@IExtensionsWorkbenchService private readonly extensionsWorkbenchService: IExtensionsWorkbenchService,
@IThemeService protected themeService: IThemeService,
@IKeybindingService private keybindingService: IKeybindingService,
@IMessageService private messageService: IMessageService,
@IOpenerService private openerService: IOpenerService,
@IListService private listService: IListService,
@IPartService private partService: IPartService,
@IContextViewService private contextViewService: IContextViewService,
@IContextKeyService private contextKeyService: IContextKeyService,
@IExtensionTipsService private extensionTipsService: IExtensionTipsService
@IKeybindingService private readonly keybindingService: IKeybindingService,
@INotificationService private readonly notificationService: INotificationService,
@IOpenerService private readonly openerService: IOpenerService,
@IPartService private readonly partService: IPartService,
@IContextViewService private readonly contextViewService: IContextViewService,
@IContextKeyService private readonly contextKeyService: IContextKeyService,
@IExtensionTipsService private readonly extensionTipsService: IExtensionTipsService,
@IEnvironmentService private readonly environmentService: IEnvironmentService
) {
super(ExtensionEditor.ID, telemetryService, themeService);
this.disposables = [];
@@ -215,7 +218,12 @@ export class ExtensionEditor extends BaseEditor {
const title = append(details, $('.title'));
this.name = append(title, $('span.name.clickable', { title: localize('name', "Extension name") }));
this.identifier = append(title, $('span.identifier', { title: localize('extension id', "Extension identifier") }));
this.preview = append(title, $('span.preview', { title: localize('preview', "Preview") }));
this.preview.textContent = localize('preview', "Preview");
this.builtin = append(title, $('span.builtin'));
this.builtin.textContent = localize('builtin', "Built-in");
const subtitle = append(details, $('.subtitle'));
this.publisher = append(subtitle, $('span.publisher.clickable', { title: localize('publisher', "Publisher name") }));
@@ -267,15 +275,6 @@ export class ExtensionEditor extends BaseEditor {
this.transientDisposables = dispose(this.transientDisposables);
/* __GDPR__
"extensionGallery:openExtension" : {
"${include}": [
"${GalleryExtensionTelemetryData}"
]
}
*/
this.telemetryService.publicLog('extensionGallery:openExtension', extension.telemetryData);
this.extensionReadme = new Cache(() => extension.getReadme());
this.extensionChangelog = new Cache(() => extension.getChangelog());
this.extensionManifest = new Cache(() => extension.getManifest());
@@ -287,23 +286,36 @@ export class ExtensionEditor extends BaseEditor {
this.name.textContent = extension.displayName;
this.identifier.textContent = extension.id;
if (extension.preview) {
this.preview.textContent = localize('preview', "Preview");
} else {
this.preview.textContent = null;
}
this.preview.style.display = extension.preview ? 'inherit' : 'none';
this.builtin.style.display = extension.type === LocalExtensionType.System ? 'inherit' : 'none';
this.publisher.textContent = extension.publisherDisplayName;
this.description.textContent = extension.description;
const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason();
this.recommendation.textContent = extRecommendations[extension.id.toLowerCase()];
let recommendationsData = {};
if (extRecommendations[extension.id.toLowerCase()]) {
addClass(this.header, 'recommended');
this.recommendation.textContent = extRecommendations[extension.id.toLowerCase()].reasonText;
recommendationsData = { recommendationReason: extRecommendations[extension.id.toLowerCase()].reasonId };
} else {
removeClass(this.header, 'recommended');
this.recommendation.textContent = '';
}
/* __GDPR__
"extensionGallery:openExtension" : {
"recommendationReason": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"${include}": [
"${GalleryExtensionTelemetryData}"
]
}
*/
this.telemetryService.publicLog('extensionGallery:openExtension', assign(extension.telemetryData, recommendationsData));
toggleClass(this.name, 'clickable', !!extension.url);
toggleClass(this.publisher, 'clickable', !!extension.url);
toggleClass(this.rating, 'clickable', !!extension.url);
if (extension.url) {
this.name.onclick = finalHandler(() => window.open(extension.url));
this.rating.onclick = finalHandler(() => window.open(`${extension.url}#review-details`));
@@ -320,6 +332,10 @@ export class ExtensionEditor extends BaseEditor {
this.license.onclick = null;
this.license.style.display = 'none';
}
} else {
this.name.onclick = null;
this.rating.onclick = null;
this.publisher.onclick = null;
}
if (extension.repository) {
@@ -338,7 +354,6 @@ export class ExtensionEditor extends BaseEditor {
//const ratings = this.instantiationService.createInstance(RatingsWidget, this.rating, { extension });
//this.transientDisposables.push(ratings);
const builtinStatusAction = this.instantiationService.createInstance(BuiltinStatusLabelAction);
const maliciousStatusAction = this.instantiationService.createInstance(MaliciousStatusLabelAction, true);
const installAction = this.instantiationService.createInstance(CombinedInstallAction);
const updateAction = this.instantiationService.createInstance(UpdateAction);
@@ -347,7 +362,6 @@ export class ExtensionEditor extends BaseEditor {
const reloadAction = this.instantiationService.createInstance(ReloadAction);
installAction.extension = extension;
builtinStatusAction.extension = extension;
maliciousStatusAction.extension = extension;
updateAction.extension = extension;
enableAction.extension = extension;
@@ -355,8 +369,10 @@ export class ExtensionEditor extends BaseEditor {
reloadAction.extension = extension;
this.extensionActionBar.clear();
this.extensionActionBar.push([reloadAction, updateAction, enableAction, disableAction, installAction, builtinStatusAction, maliciousStatusAction], { icon: true, label: true });
this.transientDisposables.push(enableAction, updateAction, reloadAction, disableAction, installAction, builtinStatusAction, maliciousStatusAction);
this.extensionActionBar.push([reloadAction, updateAction, enableAction, disableAction, installAction, maliciousStatusAction], { icon: true, label: true });
this.transientDisposables.push(enableAction, updateAction, reloadAction, disableAction, installAction, maliciousStatusAction);
this.content.innerHTML = ''; // Clear content before setting navbar actions.
this.navbar.clear();
this.navbar.onChange(this.onNavbarChange.bind(this, extension), this, this.transientDisposables);
@@ -365,8 +381,6 @@ export class ExtensionEditor extends BaseEditor {
this.navbar.push(NavbarSection.Changelog, localize('changelog', "Changelog"));
this.navbar.push(NavbarSection.Dependencies, localize('dependencies', "Dependencies"));
this.content.innerHTML = '';
return super.setInput(input, options);
}
@@ -412,13 +426,11 @@ export class ExtensionEditor extends BaseEditor {
.then(removeEmbeddedSVGs)
.then<void>(body => {
const allowedBadgeProviders = this.extensionsWorkbenchService.allowedBadgeProviders;
const webViewOptions = allowedBadgeProviders.length > 0 ? { allowScripts: false, allowSvgs: false, svgWhiteList: allowedBadgeProviders } : undefined;
this.activeWebview = new WebView(this.content, this.partService.getContainer(Parts.EDITOR_PART), this.contextViewService, this.contextKey, this.findInputFocusContextKey, webViewOptions);
const webViewOptions = allowedBadgeProviders.length > 0 ? { allowScripts: false, allowSvgs: false, svgWhiteList: allowedBadgeProviders } : {};
this.activeWebview = new Webview(this.content, this.partService.getContainer(Parts.EDITOR_PART), this.themeService, this.environmentService, this.contextViewService, this.contextKey, this.findInputFocusContextKey, webViewOptions);
const removeLayoutParticipant = arrays.insert(this.layoutParticipants, this.activeWebview);
this.contentDisposables.push(toDisposable(removeLayoutParticipant));
this.activeWebview.style(this.themeService.getTheme());
this.activeWebview.contents = [body];
this.activeWebview.contents = body;
this.activeWebview.onDidClickLink(link => {
// Whitelist supported schemes for links
@@ -426,7 +438,6 @@ export class ExtensionEditor extends BaseEditor {
this.openerService.open(link);
}
}, null, this.contentDisposables);
this.themeService.onThemeChange(theme => this.activeWebview.style(theme), null, this.contentDisposables);
this.contentDisposables.push(this.activeWebview);
})
.then(null, () => {
@@ -462,7 +473,8 @@ export class ExtensionEditor extends BaseEditor {
this.renderColors(content, manifest, layout),
this.renderJSONValidation(content, manifest, layout),
this.renderDebuggers(content, manifest, layout),
this.renderViews(content, manifest, layout)
this.renderViews(content, manifest, layout),
this.renderLocalizations(content, manifest, layout)
];
const isEmpty = !renders.reduce((v, r) => r || v, false);
@@ -506,7 +518,7 @@ export class ExtensionEditor extends BaseEditor {
scrollableContent.scanDomNode();
}, error => {
append(this.content, $('p.nocontent')).textContent = error;
this.messageService.show(Severity.Error, error);
this.notificationService.error(error);
});
});
}
@@ -514,15 +526,14 @@ export class ExtensionEditor extends BaseEditor {
private renderDependencies(container: HTMLElement, extensionDependencies: IExtensionDependencies): Tree {
const renderer = this.instantiationService.createInstance(Renderer);
const controller = this.instantiationService.createInstance(Controller);
const tree = new WorkbenchTree(container, {
const tree = this.instantiationService.createInstance(WorkbenchTree, container, {
dataSource: new DataSource(),
renderer,
controller
}, {
indentPixels: 40,
twistiePixels: 20,
keyboardSupport: false
}, this.contextKeyService, this.listService, this.themeService);
twistiePixels: 20
});
tree.setInput(extensionDependencies);
@@ -618,6 +629,26 @@ export class ExtensionEditor extends BaseEditor {
return true;
}
private renderLocalizations(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
const contributes = manifest.contributes;
const localizations = contributes && contributes.localizations || [];
if (!localizations.length) {
return false;
}
const details = $('details', { open: true, ontoggle: onDetailsToggle },
$('summary', null, localize('localizations', "Localizations ({0})", localizations.length)),
$('table', null,
$('tr', null, $('th', null, localize('localizations language id', "Language Id")), $('th', null, localize('localizations language name', "Langauge Name")), $('th', null, localize('localizations localized language name', "Langauge Name (Localized)"))),
...localizations.map(localization => $('tr', null, $('td', null, localization.languageId), $('td', null, localization.languageName), $('td', null, localization.languageNameLocalized)))
)
);
append(container, details);
return true;
}
private renderColorThemes(container: HTMLElement, manifest: IExtensionManifest, onDetailsToggle: Function): boolean {
const contributes = manifest.contributes;
const contrib = contributes && contributes.themes || [];
@@ -707,8 +738,15 @@ export class ExtensionEditor extends BaseEditor {
const details = $('details', { open: true, ontoggle: onDetailsToggle },
$('summary', null, localize('JSON Validation', "JSON Validation ({0})", contrib.length)),
$('ul', null, ...contrib.map(v => $('li', null, v.fileMatch)))
);
$('table', null,
$('tr', null,
$('th', null, localize('fileMatch', "File Match")),
$('th', null, localize('schema', "Schema"))
),
...contrib.map(v => $('tr', null,
$('td', null, $('code', null, v.fileMatch)),
$('td', null, v.url)
))));
append(container, details);
return true;
@@ -898,7 +936,7 @@ export class ExtensionEditor extends BaseEditor {
return;
}
this.messageService.show(Severity.Error, err);
this.notificationService.error(err);
}
dispose(): void {

View File

@@ -17,18 +17,17 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
import { IExtension, ExtensionState, IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewlet, AutoUpdateConfigurationKey } from 'vs/workbench/parts/extensions/common/extensions';
import { ExtensionsConfigurationInitialContent } from 'vs/workbench/parts/extensions/common/extensionsFileTemplate';
import { LocalExtensionType, IExtensionEnablementService, IExtensionTipsService, EnablementState, ExtensionsLabel } from 'vs/platform/extensionManagement/common/extensionManagement';
import { LocalExtensionType, IExtensionEnablementService, IExtensionTipsService, EnablementState, ExtensionsLabel, IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IMessageService } from 'vs/platform/message/common/message';
import { ToggleViewletAction } from 'vs/workbench/browser/viewlet';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { Query } from 'vs/workbench/parts/extensions/common/extensionQuery';
import { IFileService, IContent } from 'vs/platform/files/common/files';
import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IExtensionService, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows';
import { IExtensionService, IExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
import URI from 'vs/base/common/uri';
import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
@@ -38,12 +37,43 @@ import { Color } from 'vs/base/common/color';
import { IJSONEditingService } from 'vs/workbench/services/configuration/common/jsonEditing';
import { ITextEditorSelection } from 'vs/platform/editor/common/editor';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { PICK_WORKSPACE_FOLDER_COMMAND } from 'vs/workbench/browser/actions/workspaceActions';
import Severity from 'vs/base/common/severity';
import { PagedModel } from 'vs/base/common/paging';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IChoiceService } from 'vs/platform/dialogs/common/dialogs';
import { mnemonicButtonLabel } from 'vs/base/common/labels';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IQuickOpenService, IPickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen';
class DownloadExtensionAction extends Action {
constructor(
private extension: IExtension,
@IOpenerService private openerService: IOpenerService,
@INotificationService private notificationService: INotificationService,
@IInstantiationService private instantiationService: IInstantiationService
) {
super('extensions.download', localize('download', "Download Manually"), '', true);
}
run(): TPromise<void> {
return this.openerService.open(URI.parse(this.extension.downloadUrl)).then(() => {
this.notificationService.notify({
severity: Severity.Info,
message: localize('install vsix', 'Once downloaded, please manually install the downloaded VSIX of \'{0}\'.', this.extension.id),
actions: {
primary: [
this.instantiationService.createInstance(InstallVSIXAction, InstallVSIXAction.ID, InstallVSIXAction.LABEL)
]
}
});
});
}
}
export class InstallAction extends Action {
@@ -59,7 +89,9 @@ export class InstallAction extends Action {
set extension(extension: IExtension) { this._extension = extension; this.update(); }
constructor(
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService,
@IInstantiationService private instantiationService: IInstantiationService,
@INotificationService private notificationService: INotificationService
) {
super('extensions.install', InstallAction.InstallLabel, InstallAction.Class, false);
@@ -90,7 +122,27 @@ export class InstallAction extends Action {
run(): TPromise<any> {
this.extensionsWorkbenchService.open(this.extension);
return this.extensionsWorkbenchService.install(this.extension);
return this.install(this.extension);
}
private install(extension: IExtension): TPromise<void> {
return this.extensionsWorkbenchService.install(extension).then(null, err => {
if (!extension.downloadUrl) {
return this.notificationService.error(err);
}
console.error(err);
this.notificationService.notify({
severity: Severity.Error,
message: localize('failedToInstall', "Failed to install \'{0}\'.", extension.id),
actions: {
primary: [
this.instantiationService.createInstance(DownloadExtensionAction, extension)
]
}
});
});
}
dispose(): void {
@@ -252,7 +304,9 @@ export class UpdateAction extends Action {
set extension(extension: IExtension) { this._extension = extension; this.update(); }
constructor(
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService,
@IInstantiationService private instantiationService: IInstantiationService,
@INotificationService private notificationService: INotificationService
) {
super('extensions.update', UpdateAction.Label, UpdateAction.DisabledClass, false);
@@ -284,7 +338,26 @@ export class UpdateAction extends Action {
}
run(): TPromise<any> {
return this.extensionsWorkbenchService.install(this.extension);
return this.install(this.extension);
}
private install(extension: IExtension): TPromise<void> {
return this.extensionsWorkbenchService.install(extension).then(null, err => {
if (!extension.downloadUrl) {
return this.notificationService.error(err);
}
console.error(err);
this.notificationService.notify({
severity: Severity.Error,
message: localize('failedToUpdate', "Failed to update \'{0}\'.", extension.id),
actions: {
primary: [
this.instantiationService.createInstance(DownloadExtensionAction, extension)
]
}
});
});
}
dispose(): void {
@@ -350,7 +423,7 @@ export class DropDownMenuActionItem extends ActionItem {
export class ManageExtensionAction extends Action {
static ID = 'extensions.manage';
static readonly ID = 'extensions.manage';
private static readonly Class = 'extension-action manage';
private static readonly HideManageExtensionClass = `${ManageExtensionAction.Class} hide`;
@@ -371,12 +444,12 @@ export class ManageExtensionAction extends Action {
this._actionItem = this.instantiationService.createInstance(DropDownMenuActionItem, this, [
[
instantiationService.createInstance(EnableForWorkspaceAction, EnableForWorkspaceAction.LABEL),
instantiationService.createInstance(EnableGloballyAction, EnableGloballyAction.LABEL)
instantiationService.createInstance(EnableGloballyAction, EnableGloballyAction.LABEL),
instantiationService.createInstance(EnableForWorkspaceAction, EnableForWorkspaceAction.LABEL)
],
[
instantiationService.createInstance(DisableForWorkspaceAction, DisableForWorkspaceAction.LABEL),
instantiationService.createInstance(DisableGloballyAction, DisableGloballyAction.LABEL)
instantiationService.createInstance(DisableGloballyAction, DisableGloballyAction.LABEL),
instantiationService.createInstance(DisableForWorkspaceAction, DisableForWorkspaceAction.LABEL)
],
[
instantiationService.createInstance(UninstallAction)
@@ -392,7 +465,7 @@ export class ManageExtensionAction extends Action {
this.class = ManageExtensionAction.HideManageExtensionClass;
this.tooltip = '';
this.enabled = false;
if (this.extension && this.extension.type !== LocalExtensionType.System) {
if (this.extension) {
const state = this.extension.state;
this.enabled = state === ExtensionState.Installed;
this.class = this.enabled || state === ExtensionState.Uninstalling ? ManageExtensionAction.Class : ManageExtensionAction.HideManageExtensionClass;
@@ -413,7 +486,7 @@ export class ManageExtensionAction extends Action {
export class EnableForWorkspaceAction extends Action implements IExtensionAction {
static ID = 'extensions.enableForWorkspace';
static readonly ID = 'extensions.enableForWorkspace';
static LABEL = localize('enableForWorkspaceAction', "Enable (Workspace)");
private disposables: IDisposable[] = [];
@@ -437,7 +510,7 @@ export class EnableForWorkspaceAction extends Action implements IExtensionAction
private update(): void {
this.enabled = false;
if (this.extension) {
this.enabled = (this.extension.enablementState === EnablementState.Disabled || this.extension.enablementState === EnablementState.WorkspaceDisabled) && this.extensionEnablementService.canChangeEnablement();
this.enabled = (this.extension.enablementState === EnablementState.Disabled || this.extension.enablementState === EnablementState.WorkspaceDisabled) && this.extension.local && this.extensionEnablementService.canChangeEnablement(this.extension.local);
}
}
@@ -453,7 +526,7 @@ export class EnableForWorkspaceAction extends Action implements IExtensionAction
export class EnableGloballyAction extends Action implements IExtensionAction {
static ID = 'extensions.enableGlobally';
static readonly ID = 'extensions.enableGlobally';
static LABEL = localize('enableGloballyAction', "Enable");
private disposables: IDisposable[] = [];
@@ -475,7 +548,7 @@ export class EnableGloballyAction extends Action implements IExtensionAction {
private update(): void {
this.enabled = false;
if (this.extension) {
this.enabled = (this.extension.enablementState === EnablementState.Disabled || this.extension.enablementState === EnablementState.WorkspaceDisabled) && this.extensionEnablementService.canChangeEnablement();
this.enabled = (this.extension.enablementState === EnablementState.Disabled || this.extension.enablementState === EnablementState.WorkspaceDisabled) && this.extension.local && this.extensionEnablementService.canChangeEnablement(this.extension.local);
}
}
@@ -491,7 +564,7 @@ export class EnableGloballyAction extends Action implements IExtensionAction {
export class EnableAction extends Action {
static ID = 'extensions.enable';
static readonly ID = 'extensions.enable';
private static readonly EnabledClass = 'extension-action prominent enable';
private static readonly DisabledClass = `${EnableAction.EnabledClass} disabled`;
@@ -514,8 +587,8 @@ export class EnableAction extends Action {
super(EnableAction.ID, localize('enableAction', "Enable"), EnableAction.DisabledClass, false);
this._enableActions = [
instantiationService.createInstance(EnableForWorkspaceAction, EnableForWorkspaceAction.LABEL),
instantiationService.createInstance(EnableGloballyAction, EnableGloballyAction.LABEL)
instantiationService.createInstance(EnableGloballyAction, EnableGloballyAction.LABEL),
instantiationService.createInstance(EnableForWorkspaceAction, EnableForWorkspaceAction.LABEL)
];
this._actionItem = this.instantiationService.createInstance(DropDownMenuActionItem, this, [this._enableActions]);
this.disposables.push(this._actionItem);
@@ -549,7 +622,7 @@ export class EnableAction extends Action {
export class DisableForWorkspaceAction extends Action implements IExtensionAction {
static ID = 'extensions.disableForWorkspace';
static readonly ID = 'extensions.disableForWorkspace';
static LABEL = localize('disableForWorkspaceAction', "Disable (Workspace)");
private disposables: IDisposable[] = [];
@@ -560,7 +633,8 @@ export class DisableForWorkspaceAction extends Action implements IExtensionActio
constructor(label: string,
@IWorkspaceContextService private workspaceContextService: IWorkspaceContextService,
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService,
@IExtensionEnablementService private extensionEnablementService: IExtensionEnablementService
) {
super(DisableForWorkspaceAction.ID, label);
@@ -572,7 +646,7 @@ export class DisableForWorkspaceAction extends Action implements IExtensionActio
private update(): void {
this.enabled = false;
if (this.extension && this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY) {
this.enabled = this.extension.type !== LocalExtensionType.System && (this.extension.enablementState === EnablementState.Enabled || this.extension.enablementState === EnablementState.WorkspaceEnabled);
this.enabled = (this.extension.enablementState === EnablementState.Enabled || this.extension.enablementState === EnablementState.WorkspaceEnabled) && this.extension.local && this.extensionEnablementService.canChangeEnablement(this.extension.local);
}
}
@@ -588,7 +662,7 @@ export class DisableForWorkspaceAction extends Action implements IExtensionActio
export class DisableGloballyAction extends Action implements IExtensionAction {
static ID = 'extensions.disableGlobally';
static readonly ID = 'extensions.disableGlobally';
static LABEL = localize('disableGloballyAction', "Disable");
private disposables: IDisposable[] = [];
@@ -598,7 +672,8 @@ export class DisableGloballyAction extends Action implements IExtensionAction {
set extension(extension: IExtension) { this._extension = extension; this.update(); }
constructor(label: string,
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService,
@IExtensionEnablementService private extensionEnablementService: IExtensionEnablementService
) {
super(DisableGloballyAction.ID, label);
@@ -609,7 +684,7 @@ export class DisableGloballyAction extends Action implements IExtensionAction {
private update(): void {
this.enabled = false;
if (this.extension) {
this.enabled = this.extension.type !== LocalExtensionType.System && (this.extension.enablementState === EnablementState.Enabled || this.extension.enablementState === EnablementState.WorkspaceEnabled);
this.enabled = (this.extension.enablementState === EnablementState.Enabled || this.extension.enablementState === EnablementState.WorkspaceEnabled) && this.extension.local && this.extensionEnablementService.canChangeEnablement(this.extension.local);
}
}
@@ -625,7 +700,7 @@ export class DisableGloballyAction extends Action implements IExtensionAction {
export class DisableAction extends Action {
static ID = 'extensions.disable';
static readonly ID = 'extensions.disable';
private static readonly EnabledClass = 'extension-action disable';
private static readonly DisabledClass = `${DisableAction.EnabledClass} disabled`;
@@ -646,8 +721,8 @@ export class DisableAction extends Action {
) {
super(DisableAction.ID, localize('disableAction', "Disable"), DisableAction.DisabledClass, false);
this._disableActions = [
instantiationService.createInstance(DisableForWorkspaceAction, DisableForWorkspaceAction.LABEL),
instantiationService.createInstance(DisableGloballyAction, DisableGloballyAction.LABEL)
instantiationService.createInstance(DisableGloballyAction, DisableGloballyAction.LABEL),
instantiationService.createInstance(DisableForWorkspaceAction, DisableForWorkspaceAction.LABEL)
];
this._actionItem = this.instantiationService.createInstance(DropDownMenuActionItem, this, [this._disableActions]);
this.disposables.push(this._actionItem);
@@ -663,7 +738,7 @@ export class DisableAction extends Action {
return;
}
this.enabled = this.extension.state === ExtensionState.Installed && this.extension.type !== LocalExtensionType.System && this._disableActions.some(a => a.enabled);
this.enabled = this.extension.state === ExtensionState.Installed && this._disableActions.some(a => a.enabled);
this.class = this.enabled ? DisableAction.EnabledClass : DisableAction.DisabledClass;
}
@@ -680,7 +755,7 @@ export class DisableAction extends Action {
export class CheckForUpdatesAction extends Action {
static ID = 'workbench.extensions.action.checkForUpdates';
static readonly ID = 'workbench.extensions.action.checkForUpdates';
static LABEL = localize('checkForUpdates', "Check for Updates");
constructor(
@@ -720,7 +795,7 @@ export class ToggleAutoUpdateAction extends Action {
export class EnableAutoUpdateAction extends ToggleAutoUpdateAction {
static ID = 'workbench.extensions.action.enableAutoUpdate';
static readonly ID = 'workbench.extensions.action.enableAutoUpdate';
static LABEL = localize('enableAutoUpdate', "Enable Auto Updating Extensions");
constructor(
@@ -734,7 +809,7 @@ export class EnableAutoUpdateAction extends ToggleAutoUpdateAction {
export class DisableAutoUpdateAction extends ToggleAutoUpdateAction {
static ID = 'workbench.extensions.action.disableAutoUpdate';
static readonly ID = 'workbench.extensions.action.disableAutoUpdate';
static LABEL = localize('disableAutoUpdate', "Disable Auto Updating Extensions");
constructor(
@@ -748,7 +823,7 @@ export class DisableAutoUpdateAction extends ToggleAutoUpdateAction {
export class UpdateAllAction extends Action {
static ID = 'workbench.extensions.action.updateAllExtensions';
static readonly ID = 'workbench.extensions.action.updateAllExtensions';
static LABEL = localize('updateAll', "Update All Extensions");
private disposables: IDisposable[] = [];
@@ -756,7 +831,9 @@ export class UpdateAllAction extends Action {
constructor(
id = UpdateAllAction.ID,
label = UpdateAllAction.LABEL,
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService,
@INotificationService private notificationService: INotificationService,
@IInstantiationService private instantiationService: IInstantiationService
) {
super(id, label, '', false);
@@ -773,7 +850,26 @@ export class UpdateAllAction extends Action {
}
run(): TPromise<any> {
return TPromise.join(this.outdated.map(e => this.extensionsWorkbenchService.install(e)));
return TPromise.join(this.outdated.map(e => this.install(e)));
}
private install(extension: IExtension): TPromise<void> {
return this.extensionsWorkbenchService.install(extension).then(null, err => {
if (!extension.downloadUrl) {
return this.notificationService.error(err);
}
console.error(err);
this.notificationService.notify({
severity: Severity.Error,
message: localize('failedToUpdate', "Failed to update \'{0}\'.", extension.id),
actions: {
primary: [
this.instantiationService.createInstance(DownloadExtensionAction, extension)
]
}
});
});
}
dispose(): void {
@@ -820,17 +916,17 @@ export class ReloadAction extends Action {
if (state === ExtensionState.Installing || state === ExtensionState.Uninstalling) {
return TPromise.wrap<void>(null);
}
return this.extensionService.getExtensions()
.then(runningExtensions => this.computeReloadState(runningExtensions));
return TPromise.join([this.extensionService.getExtensions(), this.extensionEnablementService.getDisabledExtensions()])
.then(([runningExtensions, disabledExtensions]) => this.computeReloadState(runningExtensions, disabledExtensions));
}).done(() => {
this.class = this.enabled ? ReloadAction.EnabledClass : ReloadAction.DisabledClass;
});
}
private computeReloadState(runningExtensions: IExtensionDescription[]): void {
private computeReloadState(runningExtensions: IExtensionDescription[], disabledExtensions: IExtensionIdentifier[]): void {
const isInstalled = this.extensionsWorkbenchService.local.some(e => e.id === this.extension.id);
const isUninstalled = this.extension.state === ExtensionState.Uninstalled;
const isDisabled = !this.extensionEnablementService.isEnabled({ id: this.extension.id, uuid: this.extension.uuid });
const isDisabled = this.isDisabled(disabledExtensions);
const filteredExtensions = runningExtensions.filter(e => areSameExtensions(e, this.extension));
const isExtensionRunning = filteredExtensions.length > 0;
@@ -872,6 +968,14 @@ export class ReloadAction extends Action {
}
}
private isDisabled(disabledExtensions: IExtensionIdentifier[]): boolean {
const identifier = { id: this.extension.id, uuid: this.extension.uuid };
if (this.extension.type === LocalExtensionType.System) {
return disabledExtensions.some(d => areSameExtensions(d, identifier));
}
return !this.extensionEnablementService.isEnabled({ id: this.extension.id, uuid: this.extension.uuid });
}
run(): TPromise<any> {
return this.windowService.reloadWindow();
}
@@ -899,7 +1003,7 @@ export class InstallExtensionsAction extends OpenExtensionsViewletAction {
export class ShowEnabledExtensionsAction extends Action {
static ID = 'workbench.extensions.action.showEnabledExtensions';
static readonly ID = 'workbench.extensions.action.showEnabledExtensions';
static LABEL = localize('showEnabledExtensions', 'Show Enabled Extensions');
constructor(
@@ -922,7 +1026,7 @@ export class ShowEnabledExtensionsAction extends Action {
export class ShowInstalledExtensionsAction extends Action {
static ID = 'workbench.extensions.action.showInstalledExtensions';
static readonly ID = 'workbench.extensions.action.showInstalledExtensions';
static LABEL = localize('showInstalledExtensions', "Show Installed Extensions");
constructor(
@@ -945,7 +1049,7 @@ export class ShowInstalledExtensionsAction extends Action {
export class ShowDisabledExtensionsAction extends Action {
static ID = 'workbench.extensions.action.showDisabledExtensions';
static readonly ID = 'workbench.extensions.action.showDisabledExtensions';
static LABEL = localize('showDisabledExtensions', "Show Disabled Extensions");
constructor(
@@ -968,7 +1072,7 @@ export class ShowDisabledExtensionsAction extends Action {
export class ClearExtensionsInputAction extends Action {
static ID = 'workbench.extensions.action.clearExtensionsInput';
static readonly ID = 'workbench.extensions.action.clearExtensionsInput';
static LABEL = localize('clearExtensionsInput', "Clear Extensions Input");
private disposables: IDisposable[] = [];
@@ -1002,9 +1106,32 @@ export class ClearExtensionsInputAction extends Action {
}
}
export class ShowBuiltInExtensionsAction extends Action {
static readonly ID = 'workbench.extensions.action.listBuiltInExtensions';
static LABEL = localize('showBuiltInExtensions', "Show Built-in Extensions");
constructor(
id: string,
label: string,
@IViewletService private viewletService: IViewletService
) {
super(id, label, null, true);
}
run(): TPromise<void> {
return this.viewletService.openViewlet(VIEWLET_ID, true)
.then(viewlet => viewlet as IExtensionsViewlet)
.then(viewlet => {
viewlet.search('@builtin ');
viewlet.focus();
});
}
}
export class ShowOutdatedExtensionsAction extends Action {
static ID = 'workbench.extensions.action.listOutdatedExtensions';
static readonly ID = 'workbench.extensions.action.listOutdatedExtensions';
static LABEL = localize('showOutdatedExtensions', "Show Outdated Extensions");
constructor(
@@ -1027,7 +1154,7 @@ export class ShowOutdatedExtensionsAction extends Action {
export class ShowPopularExtensionsAction extends Action {
static ID = 'workbench.extensions.action.showPopularExtensions';
static readonly ID = 'workbench.extensions.action.showPopularExtensions';
static LABEL = localize('showPopularExtensions', "Show Popular Extensions");
constructor(
@@ -1050,7 +1177,7 @@ export class ShowPopularExtensionsAction extends Action {
export class ShowRecommendedExtensionsAction extends Action {
static ID = 'workbench.extensions.action.showRecommendedExtensions';
static readonly ID = 'workbench.extensions.action.showRecommendedExtensions';
static LABEL = localize('showRecommendedExtensions', "Show Recommended Extensions");
constructor(
@@ -1073,7 +1200,7 @@ export class ShowRecommendedExtensionsAction extends Action {
export class InstallWorkspaceRecommendedExtensionsAction extends Action {
static ID = 'workbench.extensions.action.installWorkspaceRecommendedExtensions';
static readonly ID = 'workbench.extensions.action.installWorkspaceRecommendedExtensions';
static LABEL = localize('installWorkspaceRecommendedExtensions', "Install All Workspace Recommended Extensions");
private disposables: IDisposable[] = [];
@@ -1085,7 +1212,8 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action {
@IViewletService private viewletService: IViewletService,
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService,
@IExtensionTipsService private extensionTipsService: IExtensionTipsService,
@IMessageService private messageService: IMessageService
@INotificationService private notificationService: INotificationService,
@IInstantiationService private instantiationService: IInstantiationService
) {
super(id, label, 'extension-action');
this.extensionsWorkbenchService.onChange(() => this.update(), this, this.disposables);
@@ -1112,7 +1240,7 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action {
.then(viewlet => {
if (!toInstall.length) {
this.enabled = false;
this.messageService.show(Severity.Info, localize('allExtensionsInstalled', "All extensions recommended for this workspace have already been installed"));
this.notificationService.info(localize('allExtensionsInstalled', "All extensions recommended for this workspace have already been installed"));
viewlet.focus();
return TPromise.as(null);
}
@@ -1130,18 +1258,37 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action {
extensionsWithDependencies.push(e);
return TPromise.as(null);
} else {
return this.extensionsWorkbenchService.install(e);
return this.install(e);
}
}));
}
return TPromise.join(installPromises).then(() => {
return TPromise.join(extensionsWithDependencies.map(e => this.extensionsWorkbenchService.install(e)));
return TPromise.join(extensionsWithDependencies.map(e => this.install(e)));
});
});
});
});
}
private install(extension: IExtension): TPromise<void> {
return this.extensionsWorkbenchService.install(extension).then(null, err => {
if (!extension.downloadUrl) {
return this.notificationService.error(err);
}
console.error(err);
this.notificationService.notify({
severity: Severity.Error,
message: localize('failedToInstall', "Failed to install \'{0}\'.", extension.id),
actions: {
primary: [
this.instantiationService.createInstance(DownloadExtensionAction, extension)
]
}
});
});
}
dispose(): void {
this.disposables = dispose(this.disposables);
super.dispose();
@@ -1150,7 +1297,7 @@ export class InstallWorkspaceRecommendedExtensionsAction extends Action {
export class InstallRecommendedExtensionAction extends Action {
static ID = 'workbench.extensions.action.installRecommendedExtension';
static readonly ID = 'workbench.extensions.action.installRecommendedExtension';
static LABEL = localize('installRecommendedExtension', "Install Recommended Extension");
private extensionId: string;
@@ -1160,7 +1307,8 @@ export class InstallRecommendedExtensionAction extends Action {
extensionId: string,
@IViewletService private viewletService: IViewletService,
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService,
@IMessageService private messageService: IMessageService
@INotificationService private notificationService: INotificationService,
@IInstantiationService private instantiationService: IInstantiationService
) {
super(InstallRecommendedExtensionAction.ID, InstallRecommendedExtensionAction.LABEL, null);
this.extensionId = extensionId;
@@ -1177,7 +1325,7 @@ export class InstallRecommendedExtensionAction extends Action {
.then(viewlet => {
if (this.extensionsWorkbenchService.local.some(x => x.id.toLowerCase() === this.extensionId.toLowerCase())) {
this.enabled = false;
this.messageService.show(Severity.Info, localize('extensionInstalled', "The recommended extension has already been installed"));
this.notificationService.info(localize('extensionInstalled', "The recommended extension has already been installed"));
viewlet.focus();
return TPromise.as(null);
}
@@ -1186,11 +1334,30 @@ export class InstallRecommendedExtensionAction extends Action {
viewlet.focus();
return this.extensionsWorkbenchService.queryGallery({ names: [this.extensionId], source: 'install-recommendation' }).then(pager => {
return (pager && pager.firstPage && pager.firstPage.length) ? this.extensionsWorkbenchService.install(pager.firstPage[0]) : TPromise.as(null);
return (pager && pager.firstPage && pager.firstPage.length) ? this.install(pager.firstPage[0]) : TPromise.as(null);
});
});
}
private install(extension: IExtension): TPromise<void> {
return this.extensionsWorkbenchService.install(extension).then(null, err => {
if (!extension.downloadUrl) {
return this.notificationService.error(err);
}
console.error(err);
this.notificationService.notify({
severity: Severity.Error,
message: localize('failedToInstall', "Failed to install \'{0}\'.", extension.id),
actions: {
primary: [
this.instantiationService.createInstance(DownloadExtensionAction, extension)
]
}
});
});
}
dispose(): void {
this.disposables = dispose(this.disposables);
super.dispose();
@@ -1200,7 +1367,7 @@ export class InstallRecommendedExtensionAction extends Action {
export class ShowRecommendedKeymapExtensionsAction extends Action {
static ID = 'workbench.extensions.action.showRecommendedKeymapExtensions';
static readonly ID = 'workbench.extensions.action.showRecommendedKeymapExtensions';
static SHORT_LABEL = localize('showRecommendedKeymapExtensionsShort', "Keymaps");
constructor(
@@ -1223,7 +1390,7 @@ export class ShowRecommendedKeymapExtensionsAction extends Action {
export class ShowLanguageExtensionsAction extends Action {
static ID = 'workbench.extensions.action.showLanguageExtensions';
static readonly ID = 'workbench.extensions.action.showLanguageExtensions';
static SHORT_LABEL = localize('showLanguageExtensionsShort', "Language Extensions");
constructor(
@@ -1246,7 +1413,7 @@ export class ShowLanguageExtensionsAction extends Action {
export class ShowAzureExtensionsAction extends Action {
static ID = 'workbench.extensions.action.showAzureExtensions';
static readonly ID = 'workbench.extensions.action.showAzureExtensions';
static SHORT_LABEL = localize('showAzureExtensionsShort', "Azure Extensions");
constructor(
@@ -1445,7 +1612,7 @@ export abstract class AbstractConfigureRecommendedExtensionsAction extends Actio
export class ConfigureWorkspaceRecommendedExtensionsAction extends AbstractConfigureRecommendedExtensionsAction {
static ID = 'workbench.extensions.action.configureWorkspaceRecommendedExtensions';
static readonly ID = 'workbench.extensions.action.configureWorkspaceRecommendedExtensions';
static LABEL = localize('configureWorkspaceRecommendedExtensions', "Configure Recommended Extensions (Workspace)");
private disposables: IDisposable[] = [];
@@ -1487,7 +1654,7 @@ export class ConfigureWorkspaceRecommendedExtensionsAction extends AbstractConfi
export class ConfigureWorkspaceFolderRecommendedExtensionsAction extends AbstractConfigureRecommendedExtensionsAction {
static ID = 'workbench.extensions.action.configureWorkspaceFolderRecommendedExtensions';
static readonly ID = 'workbench.extensions.action.configureWorkspaceFolderRecommendedExtensions';
static LABEL = localize('configureWorkspaceFolderRecommendedExtensions', "Configure Recommended Extensions (Workspace Folder)");
private disposables: IDisposable[] = [];
@@ -1513,7 +1680,7 @@ export class ConfigureWorkspaceFolderRecommendedExtensionsAction extends Abstrac
public run(): TPromise<any> {
const folderCount = this.contextService.getWorkspace().folders.length;
const pickFolderPromise = folderCount === 1 ? TPromise.as(this.contextService.getWorkspace().folders[0]) : this.commandService.executeCommand<IWorkspaceFolder>(PICK_WORKSPACE_FOLDER_COMMAND);
const pickFolderPromise = folderCount === 1 ? TPromise.as(this.contextService.getWorkspace().folders[0]) : this.commandService.executeCommand<IWorkspaceFolder>(PICK_WORKSPACE_FOLDER_COMMAND_ID);
return pickFolderPromise
.then(workspaceFolder => {
if (workspaceFolder) {
@@ -1530,31 +1697,6 @@ export class ConfigureWorkspaceFolderRecommendedExtensionsAction extends Abstrac
}
}
export class BuiltinStatusLabelAction extends Action {
private static readonly Class = 'built-in-status';
private _extension: IExtension;
get extension(): IExtension { return this._extension; }
set extension(extension: IExtension) { this._extension = extension; this.update(); }
constructor() {
super('extensions.install', localize('builtin', "Built-in"), '', false);
}
private update(): void {
if (this.extension && this.extension.type === LocalExtensionType.System) {
this.class = `${BuiltinStatusLabelAction.Class} system`;
} else {
this.class = `${BuiltinStatusLabelAction.Class} user`;
}
}
run(): TPromise<any> {
return TPromise.as(null);
}
}
export class MaliciousStatusLabelAction extends Action {
private static readonly Class = 'malicious-status';
@@ -1564,10 +1706,10 @@ export class MaliciousStatusLabelAction extends Action {
set extension(extension: IExtension) { this._extension = extension; this.update(); }
constructor(long: boolean) {
const tooltip = localize('malicious tooltip', "This extension was reported to be malicious.");
const tooltip = localize('malicious tooltip', "This extension was reported to be problematic.");
const label = long ? tooltip : localize('malicious', "Malicious");
super('extensions.install', label, '', false);
this.tooltip = localize('malicious tooltip', "This extension was reported to be malicious.");
this.tooltip = localize('malicious tooltip', "This extension was reported to be problematic.");
}
private update(): void {
@@ -1585,7 +1727,7 @@ export class MaliciousStatusLabelAction extends Action {
export class DisableAllAction extends Action {
static ID = 'workbench.extensions.action.disableAll';
static readonly ID = 'workbench.extensions.action.disableAll';
static LABEL = localize('disableAll', "Disable All Installed Extensions");
private disposables: IDisposable[] = [];
@@ -1604,7 +1746,7 @@ export class DisableAllAction extends Action {
}
run(): TPromise<any> {
return TPromise.join(this.extensionsWorkbenchService.local.map(e => this.extensionsWorkbenchService.setEnablement(e, EnablementState.Disabled)));
return TPromise.join(this.extensionsWorkbenchService.local.filter(e => e.type === LocalExtensionType.User).map(e => this.extensionsWorkbenchService.setEnablement(e, EnablementState.Disabled)));
}
dispose(): void {
@@ -1615,7 +1757,7 @@ export class DisableAllAction extends Action {
export class DisableAllWorkpsaceAction extends Action {
static ID = 'workbench.extensions.action.disableAllWorkspace';
static readonly ID = 'workbench.extensions.action.disableAllWorkspace';
static LABEL = localize('disableAllWorkspace', "Disable All Installed Extensions for this Workspace");
private disposables: IDisposable[] = [];
@@ -1636,7 +1778,7 @@ export class DisableAllWorkpsaceAction extends Action {
}
run(): TPromise<any> {
return TPromise.join(this.extensionsWorkbenchService.local.map(e => this.extensionsWorkbenchService.setEnablement(e, EnablementState.WorkspaceDisabled)));
return TPromise.join(this.extensionsWorkbenchService.local.filter(e => e.type === LocalExtensionType.User).map(e => this.extensionsWorkbenchService.setEnablement(e, EnablementState.WorkspaceDisabled)));
}
dispose(): void {
@@ -1647,8 +1789,8 @@ export class DisableAllWorkpsaceAction extends Action {
export class EnableAllAction extends Action {
static ID = 'workbench.extensions.action.enableAll';
static LABEL = localize('enableAll', "Enable All Installed Extensions");
static readonly ID = 'workbench.extensions.action.enableAll';
static LABEL = localize('enableAll', "Enable All Extensions");
private disposables: IDisposable[] = [];
@@ -1663,7 +1805,7 @@ export class EnableAllAction extends Action {
}
private update(): void {
this.enabled = this.extensionsWorkbenchService.local.some(e => this.extensionEnablementService.canChangeEnablement() && (e.enablementState === EnablementState.Disabled || e.enablementState === EnablementState.WorkspaceDisabled));
this.enabled = this.extensionsWorkbenchService.local.some(e => e.local && this.extensionEnablementService.canChangeEnablement(e.local) && (e.enablementState === EnablementState.Disabled || e.enablementState === EnablementState.WorkspaceDisabled));
}
run(): TPromise<any> {
@@ -1678,8 +1820,8 @@ export class EnableAllAction extends Action {
export class EnableAllWorkpsaceAction extends Action {
static ID = 'workbench.extensions.action.enableAllWorkspace';
static LABEL = localize('enableAllWorkspace', "Enable All Installed Extensions for this Workspace");
static readonly ID = 'workbench.extensions.action.enableAllWorkspace';
static LABEL = localize('enableAllWorkspace', "Enable All Extensions for this Workspace");
private disposables: IDisposable[] = [];
@@ -1696,7 +1838,7 @@ export class EnableAllWorkpsaceAction extends Action {
}
private update(): void {
this.enabled = this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.extensionsWorkbenchService.local.some(e => this.extensionEnablementService.canChangeEnablement() && (e.enablementState === EnablementState.Disabled || e.enablementState === EnablementState.WorkspaceDisabled));
this.enabled = this.workspaceContextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.extensionsWorkbenchService.local.some(e => e.local && this.extensionEnablementService.canChangeEnablement(e.local) && (e.enablementState === EnablementState.Disabled || e.enablementState === EnablementState.WorkspaceDisabled));
}
run(): TPromise<any> {
@@ -1709,6 +1851,137 @@ export class EnableAllWorkpsaceAction extends Action {
}
}
export class OpenExtensionsFolderAction extends Action {
static readonly ID = 'workbench.extensions.action.openExtensionsFolder';
static LABEL = localize('openExtensionsFolder', "Open Extensions Folder");
constructor(
id: string,
label: string,
@IWindowsService private windowsService: IWindowsService,
@IFileService private fileService: IFileService,
@IEnvironmentService private environmentService: IEnvironmentService
) {
super(id, label, null, true);
}
run(): TPromise<void> {
const extensionsHome = this.environmentService.extensionsPath;
return this.fileService.resolveFile(URI.file(extensionsHome)).then(file => {
let itemToShow: string;
if (file.children && file.children.length > 0) {
itemToShow = file.children[0].resource.fsPath;
} else {
itemToShow = paths.normalize(extensionsHome, true);
}
return this.windowsService.showItemInFolder(itemToShow);
});
}
}
export class InstallVSIXAction extends Action {
static readonly ID = 'workbench.extensions.action.installVSIX';
static LABEL = localize('installVSIX', "Install from VSIX...");
constructor(
id = InstallVSIXAction.ID,
label = InstallVSIXAction.LABEL,
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService,
@IChoiceService private choiceService: IChoiceService,
@IWindowService private windowsService: IWindowService
) {
super(id, label, 'extension-action install-vsix', true);
}
run(): TPromise<any> {
return this.windowsService.showOpenDialog({
title: localize('installFromVSIX', "Install from VSIX"),
filters: [{ name: 'VSIX Extensions', extensions: ['vsix'] }],
properties: ['openFile'],
buttonLabel: mnemonicButtonLabel(localize({ key: 'installButton', comment: ['&& denotes a mnemonic'] }, "&&Install"))
}).then(result => {
if (!result) {
return TPromise.as(null);
}
return TPromise.join(result.map(vsix => this.extensionsWorkbenchService.install(vsix))).then(() => {
return this.choiceService.choose(Severity.Info, localize('InstallVSIXAction.success', "Successfully installed the extension. Reload to enable it."), [localize('InstallVSIXAction.reloadNow', "Reload Now")]).then(choice => {
if (choice === 0) {
return this.windowsService.reloadWindow();
}
return TPromise.as(undefined);
});
});
});
}
}
export class ReinstallAction extends Action {
static readonly ID = 'workbench.extensions.action.reinstall';
static LABEL = localize('reinstall', "Reinstall Extension...");
constructor(
id: string = ReinstallAction.ID, label: string = ReinstallAction.LABEL,
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService,
@IQuickOpenService private quickOpenService: IQuickOpenService,
@INotificationService private notificationService: INotificationService,
@IWindowService private windowService: IWindowService
) {
super(id, label);
}
get enabled(): boolean {
return this.extensionsWorkbenchService.local.filter(l => l.type === LocalExtensionType.User && l.local).length > 0;
}
run(): TPromise<any> {
return this.quickOpenService.pick(this.getEntries(), { placeHolder: localize('selectExtension', "Select Extension to Reinstall") });
}
private getEntries(): TPromise<IPickOpenEntry[]> {
return this.extensionsWorkbenchService.queryLocal()
.then(local => {
const entries: IPickOpenEntry[] = local
.filter(extension => extension.type === LocalExtensionType.User)
.map(extension => {
return <IPickOpenEntry>{
id: extension.id,
label: extension.displayName,
description: extension.id,
run: () => this.reinstallExtension(extension),
};
});
return entries;
});
}
private reinstallExtension(extension: IExtension): TPromise<void> {
return this.extensionsWorkbenchService.reinstall(extension)
.then(() => {
this.notificationService.notify({
message: localize('ReinstallAction.success', "Successfully reinstalled the extension."),
severity: Severity.Info,
actions: {
primary: [<IAction>{
id: 'reload',
label: localize('ReinstallAction.reloadNow', "Reload Now"),
enabled: true,
run: () => this.windowService.reloadWindow(),
dispose: () => null
}]
}
});
}, error => this.notificationService.error(error));
}
}
CommandsRegistry.registerCommand('workbench.extensions.action.showExtensionsForLanguage', function (accessor: ServicesAccessor, fileExtension: string) {
const viewletService = accessor.get(IViewletService);
@@ -1720,6 +1993,17 @@ CommandsRegistry.registerCommand('workbench.extensions.action.showExtensionsForL
});
});
CommandsRegistry.registerCommand('workbench.extensions.action.showExtensionsWithId', function (accessor: ServicesAccessor, extensionId: string) {
const viewletService = accessor.get(IViewletService);
return viewletService.openViewlet(VIEWLET_ID, true)
.then(viewlet => viewlet as IExtensionsViewlet)
.then(viewlet => {
viewlet.search(`@id:${extensionId}`);
viewlet.focus();
});
});
export const extensionButtonProminentBackground = registerColor('extensionButton.prominentBackground', {
dark: '#327e36',
light: '#327e36',

View File

@@ -10,18 +10,18 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { Action } from 'vs/base/common/actions';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IMessageService, Severity } from 'vs/platform/message/common/message';
import { IDelegate } from 'vs/base/browser/ui/list/list';
import { IPagedRenderer } from 'vs/base/browser/ui/list/listPaging';
import { once } from 'vs/base/common/event';
import { domEvent } from 'vs/base/browser/event';
import { IExtension, IExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/common/extensions';
import { InstallAction, UpdateAction, BuiltinStatusLabelAction, ManageExtensionAction, ReloadAction, extensionButtonProminentBackground, MaliciousStatusLabelAction } from 'vs/workbench/parts/extensions/browser/extensionsActions';
import { InstallAction, UpdateAction, ManageExtensionAction, ReloadAction, extensionButtonProminentBackground, extensionButtonProminentForeground, MaliciousStatusLabelAction } from 'vs/workbench/parts/extensions/browser/extensionsActions';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { Label, RatingsWidget, InstallWidget } from 'vs/workbench/parts/extensions/browser/extensionsWidgets';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { Label, RatingsWidget, InstallCountWidget } from 'vs/workbench/parts/extensions/browser/extensionsWidgets';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IExtensionTipsService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { INotificationService } from 'vs/platform/notification/common/notification';
export interface ITemplateData {
root: HTMLElement;
@@ -48,7 +48,7 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
constructor(
@IInstantiationService private instantiationService: IInstantiationService,
@IMessageService private messageService: IMessageService,
@INotificationService private notificationService: INotificationService,
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService,
@IExtensionService private extensionService: IExtensionService,
@IExtensionTipsService private extensionTipsService: IExtensionTipsService,
@@ -61,8 +61,10 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
const bookmark = append(root, $('span.bookmark'));
append(bookmark, $('span.octicon.octicon-star'));
const applyBookmarkStyle = (theme) => {
const borderColor = theme.getColor(extensionButtonProminentBackground);
bookmark.style.borderTopColor = borderColor ? borderColor.toString() : 'transparent';
const bgColor = theme.getColor(extensionButtonProminentBackground);
const fgColor = theme.getColor(extensionButtonProminentForeground);
bookmark.style.borderTopColor = bgColor ? bgColor.toString() : 'transparent';
bookmark.style.color = fgColor ? fgColor.toString() : 'white';
};
applyBookmarkStyle(this.themeService.getTheme());
const bookmarkStyler = this.themeService.onThemeChange(applyBookmarkStyle.bind(this));
@@ -88,21 +90,20 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
return null;
}
});
actionbar.onDidRun(({ error }) => error && this.messageService.show(Severity.Error, error));
actionbar.onDidRun(({ error }) => error && this.notificationService.error(error));
const versionWidget = this.instantiationService.createInstance(Label, version, (e: IExtension) => e.version);
const installCountWidget = this.instantiationService.createInstance(InstallWidget, installCount, { small: true });
const installCountWidget = this.instantiationService.createInstance(InstallCountWidget, installCount, { small: true });
const ratingsWidget = this.instantiationService.createInstance(RatingsWidget, ratings, { small: true });
const builtinStatusAction = this.instantiationService.createInstance(BuiltinStatusLabelAction);
const maliciousStatusAction = this.instantiationService.createInstance(MaliciousStatusLabelAction, false);
const installAction = this.instantiationService.createInstance(InstallAction);
const updateAction = this.instantiationService.createInstance(UpdateAction);
const reloadAction = this.instantiationService.createInstance(ReloadAction);
const manageAction = this.instantiationService.createInstance(ManageExtensionAction);
actionbar.push([reloadAction, updateAction, installAction, builtinStatusAction, maliciousStatusAction, manageAction], actionOptions);
const disposables = [versionWidget, installCountWidget, ratingsWidget, builtinStatusAction, maliciousStatusAction, updateAction, reloadAction, manageAction, actionbar, bookmarkStyler];
actionbar.push([reloadAction, updateAction, installAction, maliciousStatusAction, manageAction], actionOptions);
const disposables = [versionWidget, installCountWidget, ratingsWidget, maliciousStatusAction, updateAction, reloadAction, manageAction, actionbar, bookmarkStyler];
return {
root, element, icon, name, installCount, ratings, author, description, disposables,
@@ -111,7 +112,6 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
versionWidget.extension = extension;
installCountWidget.extension = extension;
ratingsWidget.extension = extension;
builtinStatusAction.extension = extension;
maliciousStatusAction.extension = extension;
installAction.extension = extension;
updateAction.extension = extension;
@@ -165,7 +165,7 @@ export class Renderer implements IPagedRenderer<IExtension, ITemplateData> {
if (extRecommendations[extension.id.toLowerCase()]) {
data.root.setAttribute('aria-label', extension.displayName + '. ' + extRecommendations[extension.id]);
addClass(data.root, 'recommended');
data.root.title = extRecommendations[extension.id.toLowerCase()];
data.root.title = extRecommendations[extension.id.toLowerCase()].reasonText;
}
data.name.textContent = extension.displayName;

View File

@@ -11,7 +11,7 @@ import { QuickOpenHandler } from 'vs/workbench/browser/quickopen';
import { IExtensionsViewlet, VIEWLET_ID } from 'vs/workbench/parts/extensions/common/extensions';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IExtensionGalleryService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IMessageService, Severity } from 'vs/platform/message/common/message';
import { INotificationService } from 'vs/platform/notification/common/notification';
class SimpleEntry extends QuickOpenEntry {
@@ -77,7 +77,7 @@ export class GalleryExtensionsHandler extends QuickOpenHandler {
@IViewletService private viewletService: IViewletService,
@IExtensionGalleryService private galleryService: IExtensionGalleryService,
@IExtensionManagementService private extensionsService: IExtensionManagementService,
@IMessageService private messageService: IMessageService
@INotificationService private notificationService: INotificationService
) {
super();
}
@@ -100,7 +100,7 @@ export class GalleryExtensionsHandler extends QuickOpenHandler {
.then(viewlet => viewlet as IExtensionsViewlet)
.then(viewlet => viewlet.search(`@id:${text}`))
.then(() => this.extensionsService.installFromGallery(galleryExtension))
.done(null, err => this.messageService.show(Severity.Error, err));
.done(null, err => this.notificationService.error(err));
};
entries.push(new SimpleEntry(label, action));

View File

@@ -42,7 +42,7 @@ export class Label implements IDisposable {
}
}
export class InstallWidget implements IDisposable {
export class InstallCountWidget implements IDisposable {
private disposables: IDisposable[] = [];
private _extension: IExtension;

View File

@@ -30,12 +30,10 @@
.monaco-action-bar .action-item.disabled .action-label.extension-action.enable,
.monaco-action-bar .action-item.disabled .action-label.extension-action.disable,
.monaco-action-bar .action-item.disabled .action-label.extension-action.reload,
.monaco-action-bar .action-item.disabled .action-label.built-in-status.user,
.monaco-action-bar .action-item.disabled .action-label.malicious-status.not-malicious {
display: none;
}
.monaco-action-bar .action-item .action-label.built-in-status
.monaco-action-bar .action-item .action-label.malicious-status {
border-radius: 4px;
color: inherit;
@@ -46,7 +44,6 @@
line-height: initial;
}
.extension-editor>.header>.details>.actions>.monaco-action-bar .action-item .action-label.built-in-status,
.extension-editor>.header>.details>.actions>.monaco-action-bar .action-item .action-label.malicious-status {
font-weight: normal;
}

View File

@@ -61,6 +61,12 @@
white-space: nowrap;
}
.extension-editor > .header > .details > .title > .builtin {
font-size: 10px;
font-style: italic;
margin-left: 10px;
}
.vs .extension-editor > .header > .details > .title > .preview {
color: white;
}
@@ -171,6 +177,7 @@
height: calc(100% - 36px);
position: relative;
overflow: hidden;
user-select: text;
}
.extension-editor > .body > .content.loading {
@@ -336,4 +343,4 @@
font-size: 90%;
font-weight: 600;
opacity: 0.6;
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View File

@@ -91,6 +91,54 @@ code > div {
overflow: auto;
}
#scroll-to-top {
position: fixed;
width: 40px;
height: 40px;
right: 25px;
bottom: 25px;
background-color:#444444;
border-radius: 50%;
cursor: pointer;
box-shadow: 1px 1px 1px rgba(0,0,0,.25);
outline: none;
display: flex;
justify-content: center;
align-items: center;
}
#scroll-to-top:hover {
background-color:#007acc;
box-shadow: 2px 2px 2px rgba(0,0,0,.25);
}
body.vscode-light #scroll-to-top {
background-color: #949494;
}
body.vscode-high-contrast #scroll-to-top:hover {
background-color: #007acc;
}
body.vscode-high-contrast #scroll-to-top {
background-color: black;
border: 2px solid #6fc3df;
box-shadow: none;
}
body.vscode-high-contrast #scroll-to-top:hover {
background-color: #007acc;
}
#scroll-to-top span.icon::before {
content: "";
display: block;
/* Chevron up icon */
background:url('data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDE5LjIuMCwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAxNiAxNiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMTYgMTY7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCS5zdDB7ZmlsbDojRkZGRkZGO30KCS5zdDF7ZmlsbDpub25lO30KPC9zdHlsZT4KPHRpdGxlPnVwY2hldnJvbjwvdGl0bGU+CjxwYXRoIGNsYXNzPSJzdDAiIGQ9Ik04LDUuMWwtNy4zLDcuM0wwLDExLjZsOC04bDgsOGwtMC43LDAuN0w4LDUuMXoiLz4KPHJlY3QgY2xhc3M9InN0MSIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2Ii8+Cjwvc3ZnPgo=');
width: 16px;
height: 16px;
}
/** Theming */
.vscode-light {

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

View File

@@ -8,7 +8,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
import Event from 'vs/base/common/event';
import { TPromise } from 'vs/base/common/winjs.base';
import { IPager } from 'vs/base/common/paging';
import { IQueryOptions, IExtensionManifest, LocalExtensionType, EnablementState } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IQueryOptions, IExtensionManifest, LocalExtensionType, EnablementState, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
export const VIEWLET_ID = 'workbench.view.extensions';
@@ -36,6 +36,7 @@ export interface IExtension {
latestVersion: string;
description: string;
url: string;
downloadUrl: string;
repository: string;
iconUrl: string;
iconUrlFallback: string;
@@ -51,6 +52,7 @@ export interface IExtension {
getManifest(): TPromise<IExtensionManifest>;
getReadme(): TPromise<string>;
getChangelog(): TPromise<string>;
local?: ILocalExtension;
isMalicious: boolean;
}
@@ -76,6 +78,7 @@ export interface IExtensionsWorkbenchService {
install(vsix: string): TPromise<void>;
install(extension: IExtension, promptToInstallDependencies?: boolean): TPromise<void>;
uninstall(extension: IExtension): TPromise<void>;
reinstall(extension: IExtension): TPromise<void>;
setEnablement(extension: IExtension, enablementState: EnablementState): TPromise<void>;
loadDependencies(extension: IExtension): TPromise<IExtensionDependencies>;
open(extension: IExtension, sideByside?: boolean): TPromise<any>;
@@ -85,8 +88,10 @@ export interface IExtensionsWorkbenchService {
export const ConfigurationKey = 'extensions';
export const AutoUpdateConfigurationKey = 'extensions.autoUpdate';
export const ShowRecommendationsOnlyOnDemandKey = 'extensions.showRecommendationsOnlyOnDemand';
export interface IExtensionsConfiguration {
autoUpdate: boolean;
ignoreRecommendations: boolean;
showRecommendationsOnlyOnDemand: boolean;
}

View File

@@ -13,7 +13,7 @@ import URI from 'vs/base/common/uri';
export class ExtensionsInput extends EditorInput {
static get ID() { return 'workbench.extensions.input2'; }
static readonly ID = 'workbench.extensions.input2';
get extension(): IExtension { return this._extension; }
constructor(private _extension: IExtension) {

View File

@@ -8,7 +8,7 @@
import * as nls from 'vs/nls';
import Event, { Emitter } from 'vs/base/common/event';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IExtensionHostProfile, ProfileSession, IExtensionService } from 'vs/platform/extensions/common/extensions';
import { IExtensionHostProfile, ProfileSession, IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { Disposable, IDisposable, dispose } from 'vs/base/common/lifecycle';
import { onUnexpectedError } from 'vs/base/common/errors';
import { append, $, addDisposableListener } from 'vs/base/browser/dom';
@@ -16,6 +16,10 @@ import { StatusbarAlignment, IStatusbarRegistry, StatusbarItemDescriptor, Extens
import { Registry } from 'vs/platform/registry/common/platform';
import { IExtensionHostProfileService, ProfileSessionState, RuntimeExtensionsInput } from 'vs/workbench/parts/extensions/electron-browser/runtimeExtensionsEditor';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IWindowsService } from 'vs/platform/windows/common/windows';
import { IConfirmationService } from 'vs/platform/dialogs/common/dialogs';
import { randomPort } from 'vs/base/node/ports';
import product from 'vs/platform/node/product';
export class ExtensionHostProfileService extends Disposable implements IExtensionHostProfileService {
@@ -38,6 +42,8 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio
@IExtensionService private readonly _extensionService: IExtensionService,
@IWorkbenchEditorService private readonly _editorService: IWorkbenchEditorService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IWindowsService private readonly _windowsService: IWindowsService,
@IConfirmationService private readonly _confirmationService: IConfirmationService
) {
super();
this._profile = null;
@@ -63,13 +69,28 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio
this._onDidChangeState.fire(void 0);
}
public startProfiling(): void {
public startProfiling(): Thenable<any> {
if (this._state !== ProfileSessionState.None) {
return;
return null;
}
if (!this._extensionService.canProfileExtensionHost()) {
return this._confirmationService.confirm({
type: 'info',
message: nls.localize('restart1', "Profile Extensions"),
detail: nls.localize('restart2', "In order to profile extensions a restart is required. Do you want to restart '{0}' now?", product.nameLong),
primaryButton: nls.localize('restart3', "Restart"),
secondaryButton: nls.localize('cancel', "Cancel")
}).then(restart => {
if (restart) {
this._windowsService.relaunch({ addArgs: [`--inspect-extensions=${randomPort()}`] });
}
});
}
this._setState(ProfileSessionState.Starting);
this._extensionService.startExtensionHostProfile().then((value) => {
return this._extensionService.startExtensionHostProfile().then((value) => {
this._profileSession = value;
this._setState(ProfileSessionState.Running);
}, (err) => {

View File

@@ -10,24 +10,33 @@ import { forEach } from 'vs/base/common/collections';
import { IDisposable, dispose, Disposable } from 'vs/base/common/lifecycle';
import { match } from 'vs/base/common/glob';
import * as json from 'vs/base/common/json';
import { IExtensionManagementService, IExtensionGalleryService, IExtensionTipsService, LocalExtensionType, EXTENSION_IDENTIFIER_PATTERN } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionManagementService, IExtensionGalleryService, IExtensionTipsService, ExtensionRecommendationReason, LocalExtensionType, EXTENSION_IDENTIFIER_PATTERN } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModel } from 'vs/editor/common/editorCommon';
import { ITextModel } from 'vs/editor/common/model';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import product from 'vs/platform/node/product';
import { IChoiceService, IMessageService } from 'vs/platform/message/common/message';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ShowRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction, InstallRecommendedExtensionAction } from 'vs/workbench/parts/extensions/browser/extensionsActions';
import Severity from 'vs/base/common/severity';
import { IWorkspaceContextService, IWorkspaceFolder, IWorkspace, IWorkspaceFoldersChangeEvent } from 'vs/platform/workspace/common/workspace';
import { IWorkspaceContextService, IWorkspaceFolder, IWorkspace, IWorkspaceFoldersChangeEvent, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { Schemas } from 'vs/base/common/network';
import { IFileService } from 'vs/platform/files/common/files';
import { IExtensionsConfiguration, ConfigurationKey } from 'vs/workbench/parts/extensions/common/extensions';
import { IExtensionsConfiguration, ConfigurationKey, ShowRecommendationsOnlyOnDemandKey, IExtensionsViewlet } from 'vs/workbench/parts/extensions/common/extensions';
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import * as pfs from 'vs/base/node/pfs';
import * as os from 'os';
import { flatten, distinct } from 'vs/base/common/arrays';
import { flatten, distinct, shuffle } from 'vs/base/common/arrays';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { guessMimeTypes, MIME_UNKNOWN } from 'vs/base/common/mime';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { getHashedRemotesFromUri } from 'vs/workbench/parts/stats/node/workspaceStats';
import { IRequestService } from 'vs/platform/request/node/request';
import { asJson } from 'vs/base/node/request';
import { isNumber } from 'vs/base/common/types';
import { IChoiceService, Choice } from 'vs/platform/dialogs/common/dialogs';
import { language, LANGUAGE_DEFAULT } from 'vs/base/common/platform';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
interface IExtensionsContent {
recommendations: string[];
@@ -35,6 +44,14 @@ interface IExtensionsContent {
const empty: { [key: string]: any; } = Object.create(null);
const milliSecondsInADay = 1000 * 60 * 60 * 24;
const choiceNever = localize('neverShowAgain', "Don't Show Again");
const searchMarketplace = localize('searchMarketplace', "Search Marketplace");
const coreLanguages = ['de', 'es', 'fr', 'it', 'ja', 'ko', 'ru', 'tr', 'zh-cn', 'zh-tw'];
interface IDynamicWorkspaceRecommendations {
remoteSet: string[];
recommendations: string[];
}
export class ExtensionTipsService extends Disposable implements IExtensionTipsService {
@@ -45,16 +62,16 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
private _recommendations: string[] = Object.create(null);
private _exeBasedRecommendations: { [id: string]: string; } = Object.create(null);
private _availableRecommendations: { [pattern: string]: string[] } = Object.create(null);
private importantRecommendations: { [id: string]: { name: string; pattern: string; } } = Object.create(null);
private importantRecommendationsIgnoreList: string[];
private _allRecommendations: string[] = [];
private _disposables: IDisposable[] = [];
private _allWorkspaceRecommendedExtensions: string[] = [];
private _dynamicWorkspaceRecommendations: string[] = [];
private _extensionsRecommendationsUrl: string;
private _disposables: IDisposable[] = [];
public promptWorkspaceRecommendationsPromise: TPromise<any>;
private proactiveRecommendationsFetched: boolean = false;
constructor(
@IExtensionGalleryService private _galleryService: IExtensionGalleryService,
@IModelService private _modelService: IModelService,
@IExtensionGalleryService private readonly _galleryService: IExtensionGalleryService,
@IModelService private readonly _modelService: IModelService,
@IStorageService private storageService: IStorageService,
@IChoiceService private choiceService: IChoiceService,
@IExtensionManagementService private extensionsService: IExtensionManagementService,
@@ -62,36 +79,186 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
@IFileService private fileService: IFileService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IConfigurationService private configurationService: IConfigurationService,
@IMessageService private messageService: IMessageService,
@ITelemetryService private telemetryService: ITelemetryService
@ITelemetryService private telemetryService: ITelemetryService,
@IEnvironmentService private environmentService: IEnvironmentService,
@IExtensionService private extensionService: IExtensionService,
@IRequestService private requestService: IRequestService,
@IViewletService private viewletService: IViewletService
) {
super();
if (!this._galleryService.isEnabled()) {
if (!this.isEnabled()) {
return;
}
if (product.extensionsGallery && product.extensionsGallery.recommendationsUrl) {
this._extensionsRecommendationsUrl = product.extensionsGallery.recommendationsUrl;
}
this._suggestTips();
this._suggestWorkspaceRecommendations();
this.getLanguageExtensionRecommendations();
this.getCachedDynamicWorkspaceRecommendations();
this._suggestFileBasedRecommendations();
this.promptWorkspaceRecommendationsPromise = this._suggestWorkspaceRecommendations();
if (!this.configurationService.getValue<boolean>(ShowRecommendationsOnlyOnDemandKey)) {
this.fetchProactiveRecommendations(true);
}
// Executable based recommendations carry out a lot of file stats, so run them after 10 secs
// So that the startup is not affected
setTimeout(() => this._suggestBasedOnExecutables(this._exeBasedRecommendations), 10000);
this._register(this.contextService.onDidChangeWorkspaceFolders(e => this.onWorkspaceFoldersChanged(e)));
this._register(this.configurationService.onDidChangeConfiguration(e => {
if (!this.proactiveRecommendationsFetched && !this.configurationService.getValue<boolean>(ShowRecommendationsOnlyOnDemandKey)) {
this.fetchProactiveRecommendations();
}
}));
}
getAllRecommendationsWithReason(): { [id: string]: string; } {
let output: { [id: string]: string; } = Object.create(null);
Object.keys(this._fileBasedRecommendations).forEach(x => output[x.toLowerCase()] = localize('fileBasedRecommendation', "This extension is recommended based on the files you recently opened."));
this._allWorkspaceRecommendedExtensions.forEach(x => output[x.toLowerCase()] = localize('workspaceRecommendation', "This extension is recommended by users of the current workspace."));
private fetchProactiveRecommendations(calledDuringStartup?: boolean): TPromise<void> {
let fetchPromise = TPromise.as(null);
if (!this.proactiveRecommendationsFetched) {
this.proactiveRecommendationsFetched = true;
// Executable based recommendations carry out a lot of file stats, so run them after 10 secs
// So that the startup is not affected
fetchPromise = new TPromise((c, e) => {
setTimeout(() => {
TPromise.join([this._suggestBasedOnExecutables(), this.getDynamicWorkspaceRecommendations()]).then(() => c(null));
}, calledDuringStartup ? 10000 : 0);
});
}
return fetchPromise;
}
private isEnabled(): boolean {
return this._galleryService.isEnabled() && !this.environmentService.extensionDevelopmentPath;
}
private getLanguageExtensionRecommendations() {
const config = this.configurationService.getValue<IExtensionsConfiguration>(ConfigurationKey);
const languagePackSuggestionIgnoreList = <string[]>JSON.parse(this.storageService.get
('extensionsAssistant/languagePackSuggestionIgnore', StorageScope.GLOBAL, '[]'));
if (!language
|| language === LANGUAGE_DEFAULT
|| coreLanguages.some(x => language === x || language.indexOf(x + '-') === 0)
|| config.ignoreRecommendations
|| config.showRecommendationsOnlyOnDemand
|| languagePackSuggestionIgnoreList.indexOf(language) > -1) {
return;
}
this.extensionsService.getInstalled(LocalExtensionType.User).then(locals => {
for (var i = 0; i < locals.length; i++) {
if (locals[i].manifest
&& locals[i].manifest.contributes
&& Array.isArray(locals[i].manifest.contributes.localizations)
&& locals[i].manifest.contributes.localizations.some(x => x.languageId === language)) {
return;
}
}
this._galleryService.query({ text: `tag:lp-${language}` }).then(pager => {
if (!pager || !pager.firstPage || !pager.firstPage.length) {
return;
}
const message = localize('showLanguagePackExtensions', "The Marketplace has extensions that can help localizing VS Code to '{0}' locale", language);
const options: Choice[] = [
searchMarketplace,
{ label: choiceNever }
];
this.choiceService.choose(Severity.Info, message, options).done(choice => {
switch (choice) {
case 0 /* Search Marketplace */:
/* __GDPR__
"languagePackSuggestion:popup" : {
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('languagePackSuggestion:popup', { userReaction: 'ok', language });
this.viewletService.openViewlet('workbench.view.extensions', true)
.then(viewlet => viewlet as IExtensionsViewlet)
.then(viewlet => {
viewlet.search(`tag:lp-${language}`);
viewlet.focus();
});
break;
case 1 /* Never show again */:
languagePackSuggestionIgnoreList.push(language);
this.storageService.store(
'extensionsAssistant/languagePackSuggestionIgnore',
JSON.stringify(languagePackSuggestionIgnoreList),
StorageScope.GLOBAL
);
/* __GDPR__
"languagePackSuggestion:popup" : {
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('languagePackSuggestion:popup', { userReaction: 'neverShowAgain', language });
break;
}
}, () => {
/* __GDPR__
"languagePackSuggestion:popup" : {
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"language": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('languagePackSuggestion:popup', { userReaction: 'cancelled', language });
});
});
});
}
getAllRecommendationsWithReason(): { [id: string]: { reasonId: ExtensionRecommendationReason, reasonText: string }; } {
let output: { [id: string]: { reasonId: ExtensionRecommendationReason, reasonText: string }; } = Object.create(null);
if (!this.proactiveRecommendationsFetched) {
return output;
}
if (this.contextService.getWorkspace().folders && this.contextService.getWorkspace().folders.length === 1) {
const currentRepo = this.contextService.getWorkspace().folders[0].name;
this._dynamicWorkspaceRecommendations.forEach(x => output[x.toLowerCase()] = {
reasonId: ExtensionRecommendationReason.DynamicWorkspace,
reasonText: localize('dynamicWorkspaceRecommendation', "This extension may interest you because it's popular among users of the {0} repository.", currentRepo)
});
}
forEach(this._exeBasedRecommendations, entry => output[entry.key.toLowerCase()] = {
reasonId: ExtensionRecommendationReason.Executable,
reasonText: localize('exeBasedRecommendation', "This extension is recommended because you have {0} installed.", entry.value)
});
Object.keys(this._fileBasedRecommendations).forEach(x => output[x.toLowerCase()] = {
reasonId: ExtensionRecommendationReason.File,
reasonText: localize('fileBasedRecommendation', "This extension is recommended based on the files you recently opened.")
});
this._allWorkspaceRecommendedExtensions.forEach(x => output[x.toLowerCase()] = {
reasonId: ExtensionRecommendationReason.Workspace,
reasonText: localize('workspaceRecommendation', "This extension is recommended by users of the current workspace.")
});
// {{SQL CARBON EDIT}}
this._recommendations.forEach(x => output[x.toLowerCase()] = localize('defaultRecommendations', "This extension is recommended by SQL Operations Studio."));
forEach(this._exeBasedRecommendations, entry => output[entry.key.toLowerCase()] = localize('exeBasedRecommendation', "This extension is recommended because you have {0} installed.", entry.value));
this._recommendations.forEach(x => output[x.toLowerCase()] = {
reasonId: ExtensionRecommendationReason.Executable,
reasonText: localize('defaultRecommendations', "This extension is recommended by SQL Operations Studio.")
});
return output;
}
getWorkspaceRecommendations(): TPromise<string[]> {
if (!this.isEnabled()) {
return TPromise.as([]);
}
const workspace = this.contextService.getWorkspace();
return TPromise.join([this.resolveWorkspaceRecommendations(workspace), ...workspace.folders.map(workspaceFolder => this.resolveWorkspaceFolderRecommendations(workspaceFolder))])
.then(recommendations => {
@@ -109,19 +276,62 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
}
private resolveWorkspaceFolderRecommendations(workspaceFolder: IWorkspaceFolder): TPromise<string[]> {
// {{SQL CARBON EDIT}}
return this.fileService.resolveContent(workspaceFolder.toResource(paths.join('.sqlops', 'extensions.json')))
.then(content => this.processWorkspaceRecommendations(json.parse(content.value, [])), err => []);
// {{SQL CARBON EDIT}}
const extensionsJsonUri = workspaceFolder.toResource(paths.join('.sqlops', 'extensions.json'));
return this.fileService.resolveFile(extensionsJsonUri).then(() => {
return this.fileService.resolveContent(extensionsJsonUri)
.then(content => this.processWorkspaceRecommendations(json.parse(content.value, [])), err => []);
}, err => []);
}
private processWorkspaceRecommendations(extensionsContent: IExtensionsContent): string[] {
if (extensionsContent && extensionsContent.recommendations) {
const regEx = new RegExp(EXTENSION_IDENTIFIER_PATTERN);
return extensionsContent.recommendations.filter((element, position) => {
return extensionsContent.recommendations.indexOf(element) === position && regEx.test(element);
private processWorkspaceRecommendations(extensionsContent: IExtensionsContent): TPromise<string[]> {
const regEx = new RegExp(EXTENSION_IDENTIFIER_PATTERN);
if (extensionsContent && extensionsContent.recommendations && extensionsContent.recommendations.length) {
let countBadRecommendations = 0;
let badRecommendationsString = '';
let filteredRecommendations = extensionsContent.recommendations.filter((element, position) => {
if (extensionsContent.recommendations.indexOf(element) !== position) {
// This is a duplicate entry, it doesn't hurt anybody
// but it shouldn't be sent in the gallery query
return false;
} else if (!regEx.test(element)) {
countBadRecommendations++;
badRecommendationsString += `${element} (bad format) Expected: <provider>.<name>\n`;
return false;
}
return true;
});
return this._galleryService.query({ names: filteredRecommendations }).then(pager => {
let page = pager.firstPage;
let validRecommendations = page.map(extension => {
return extension.identifier.id.toLowerCase();
});
if (validRecommendations.length !== filteredRecommendations.length) {
filteredRecommendations.forEach(element => {
if (validRecommendations.indexOf(element.toLowerCase()) === -1) {
countBadRecommendations++;
badRecommendationsString += `${element} (not found in marketplace)\n`;
}
});
}
if (countBadRecommendations > 0) {
console.log('The below ' +
countBadRecommendations +
' extension(s) in workspace recommendations have issues:\n' +
badRecommendationsString);
}
return validRecommendations;
});
}
return [];
return TPromise.as([]);
}
private onWorkspaceFoldersChanged(event: IWorkspaceFoldersChangeEvent): void {
@@ -135,13 +345,14 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
}
});
}
this._dynamicWorkspaceRecommendations = [];
}
getFileBasedRecommendations(): string[] {
const fileBased = Object.keys(this._fileBasedRecommendations)
.sort((a, b) => {
if (this._fileBasedRecommendations[a] === this._fileBasedRecommendations[b]) {
if (product.extensionImportantTips[a]) {
if (!product.extensionImportantTips || product.extensionImportantTips[a]) {
return -1;
}
if (product.extensionImportantTips[b]) {
@@ -153,27 +364,23 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
return fileBased;
}
getOtherRecommendations(): string[] {
getOtherRecommendations(): TPromise<string[]> {
// {{SQL CARBON EDIT}}
let recommendations = Object.keys(this._exeBasedRecommendations);
return recommendations.concat(this._recommendations);
return TPromise.as(recommendations.concat(this._recommendations));
}
getKeymapRecommendations(): string[] {
return product.keymapExtensionTips || [];
}
private _suggestTips() {
private _suggestFileBasedRecommendations() {
const extensionTips = product.extensionTips;
// {{SQL CARBON EDIT}}
this._recommendations = product.recommendedExtensions;
if (!extensionTips) {
return;
}
this.importantRecommendations = product.extensionImportantTips || Object.create(null);
this.importantRecommendationsIgnoreList = <string[]>JSON.parse(this.storageService.get('extensionsAssistant/importantRecommendationsIgnore', StorageScope.GLOBAL, '[]'));
// group ids by pattern, like {**/*.md} -> [ext.foo1, ext.bar2]
this._availableRecommendations = Object.create(null);
@@ -198,8 +405,9 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
}
});
const allRecommendations: string[] = [];
forEach(this._availableRecommendations, ({ value: ids }) => {
this._allRecommendations.push(...ids);
allRecommendations.push(...ids);
});
// retrieve ids of previous recommendations
@@ -207,7 +415,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
if (Array.isArray<string>(storedRecommendationsJson)) {
for (let id of <string[]>storedRecommendationsJson) {
if (this._allRecommendations.indexOf(id) > -1) {
if (allRecommendations.indexOf(id) > -1) {
this._fileBasedRecommendations[id] = Date.now();
}
}
@@ -216,7 +424,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
forEach(storedRecommendationsJson, entry => {
if (typeof entry.value === 'number') {
const diff = (now - entry.value) / milliSecondsInADay;
if (diff <= 7 && this._allRecommendations.indexOf(entry.key) > -1) {
if (diff <= 7 && allRecommendations.indexOf(entry.key) > -1) {
this._fileBasedRecommendations[entry.key] = entry.value;
}
}
@@ -227,8 +435,15 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
this._modelService.getModels().forEach(model => this._suggest(model));
}
private _suggest(model: IModel): void {
private getMimeTypes(path: string): TPromise<string[]> {
return this.extensionService.whenInstalledExtensionsRegistered().then(() => {
return guessMimeTypes(path);
});
}
private _suggest(model: ITextModel): void {
const uri = model.uri;
let hasSuggestion = false;
if (!uri || uri.scheme !== Schemas.file) {
return;
@@ -255,112 +470,175 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
);
const config = this.configurationService.getValue<IExtensionsConfiguration>(ConfigurationKey);
if (config.ignoreRecommendations) {
if (config.ignoreRecommendations || config.showRecommendationsOnlyOnDemand) {
return;
}
this.extensionsService.getInstalled(LocalExtensionType.User).done(local => {
Object.keys(this.importantRecommendations)
.filter(id => this.importantRecommendationsIgnoreList.indexOf(id) === -1)
.filter(id => local.every(local => `${local.manifest.publisher}.${local.manifest.name}` !== id))
.forEach(id => {
const { pattern, name } = this.importantRecommendations[id];
const importantRecommendationsIgnoreList = <string[]>JSON.parse(this.storageService.get('extensionsAssistant/importantRecommendationsIgnore', StorageScope.GLOBAL, '[]'));
let recommendationsToSuggest = Object.keys(product.extensionImportantTips || [])
.filter(id => importantRecommendationsIgnoreList.indexOf(id) === -1 && match(product.extensionImportantTips[id]['pattern'], uri.fsPath));
if (!match(pattern, uri.fsPath)) {
return;
}
const importantTipsPromise = recommendationsToSuggest.length === 0 ? TPromise.as(null) : this.extensionsService.getInstalled(LocalExtensionType.User).then(local => {
recommendationsToSuggest = recommendationsToSuggest.filter(id => local.every(local => `${local.manifest.publisher}.${local.manifest.name}` !== id));
if (!recommendationsToSuggest.length) {
return;
}
const id = recommendationsToSuggest[0];
const name = product.extensionImportantTips[id]['name'];
let message = localize('reallyRecommended2', "The '{0}' extension is recommended for this file type.", name);
// Temporary fix for the only extension pack we recommend. See https://github.com/Microsoft/vscode/issues/35364
if (id === 'vscjava.vscode-java-pack') {
message = localize('reallyRecommendedExtensionPack', "The '{0}' extension pack is recommended for this file type.", name);
}
// Indicates we have a suggested extension via the whitelist
hasSuggestion = true;
const recommendationsAction = this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, localize('showRecommendations', "Show Recommendations"));
const installAction = this.instantiationService.createInstance(InstallRecommendedExtensionAction, id);
const options = [
localize('install', 'Install'),
recommendationsAction.label,
localize('neverShowAgain', "Don't show again"),
localize('close', "Close")
];
let message = localize('reallyRecommended2', "The '{0}' extension is recommended for this file type.", name);
// Temporary fix for the only extension pack we recommend. See https://github.com/Microsoft/vscode/issues/35364
if (id === 'vscjava.vscode-java-pack') {
message = localize('reallyRecommendedExtensionPack', "The '{0}' extension pack is recommended for this file type.", name);
}
this.choiceService.choose(Severity.Info, message, options, 3).done(choice => {
switch (choice) {
case 0:
/* __GDPR__
"extensionRecommendations:popup" : {
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('extensionRecommendations:popup', { userReaction: 'install', extensionId: name });
return installAction.run();
case 1:
/* __GDPR__
"extensionRecommendations:popup" : {
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('extensionRecommendations:popup', { userReaction: 'show', extensionId: name });
return recommendationsAction.run();
case 2: this.importantRecommendationsIgnoreList.push(id);
this.storageService.store(
'extensionsAssistant/importantRecommendationsIgnore',
JSON.stringify(this.importantRecommendationsIgnoreList),
StorageScope.GLOBAL
);
/* __GDPR__
"extensionRecommendations:popup" : {
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('extensionRecommendations:popup', { userReaction: 'neverShowAgain', extensionId: name });
return this.ignoreExtensionRecommendations();
case 3:
/* __GDPR__
"extensionRecommendations:popup" : {
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('extensionRecommendations:popup', { userReaction: 'close', extensionId: name });
}
}, () => {
const recommendationsAction = this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, localize('showRecommendations', "Show Recommendations"));
const installAction = this.instantiationService.createInstance(InstallRecommendedExtensionAction, id);
const options: Choice[] = [
localize('install', 'Install'),
recommendationsAction.label,
{ label: choiceNever }
];
this.choiceService.choose(Severity.Info, message, options).done(choice => {
switch (choice) {
case 0 /* Install */:
/* __GDPR__
"extensionRecommendations:popup" : {
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('extensionRecommendations:popup', { userReaction: 'cancelled', extensionId: name });
});
this.telemetryService.publicLog('extensionRecommendations:popup', { userReaction: 'install', extensionId: name });
return installAction.run();
case 1 /* Show Recommendations */:
/* __GDPR__
"extensionRecommendations:popup" : {
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('extensionRecommendations:popup', { userReaction: 'show', extensionId: name });
return recommendationsAction.run();
case 2 /* Never show again */:
importantRecommendationsIgnoreList.push(id);
this.storageService.store(
'extensionsAssistant/importantRecommendationsIgnore',
JSON.stringify(importantRecommendationsIgnoreList),
StorageScope.GLOBAL
);
/* __GDPR__
"extensionRecommendations:popup" : {
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('extensionRecommendations:popup', { userReaction: 'neverShowAgain', extensionId: name });
return this.ignoreExtensionRecommendations();
}
}, () => {
/* __GDPR__
"extensionRecommendations:popup" : {
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('extensionRecommendations:popup', { userReaction: 'cancelled', extensionId: name });
});
});
const mimeTypesPromise = this.getMimeTypes(uri.fsPath);
TPromise.join([importantTipsPromise, mimeTypesPromise]).then(result => {
const fileExtensionSuggestionIgnoreList = <string[]>JSON.parse(this.storageService.get
('extensionsAssistant/fileExtensionsSuggestionIgnore', StorageScope.GLOBAL, '[]'));
const mimeTypes = result[1];
let fileExtension = paths.extname(uri.fsPath);
if (fileExtension) {
fileExtension = fileExtension.substr(1); // Strip the dot
}
if (hasSuggestion ||
!fileExtension ||
mimeTypes.length !== 1 ||
mimeTypes[0] !== MIME_UNKNOWN ||
fileExtensionSuggestionIgnoreList.indexOf(fileExtension) > -1
) {
return;
}
const keywords = this.getKeywordsForExtension(fileExtension);
this._galleryService.query({ text: `tag:"__ext_${fileExtension}" ${keywords.map(tag => `tag:"${tag}"`)}` }).then(pager => {
if (!pager || !pager.firstPage || !pager.firstPage.length) {
return;
}
const message = localize('showLanguageExtensions', "The Marketplace has extensions that can help with '.{0}' files", fileExtension);
const options: Choice[] = [
searchMarketplace,
{ label: choiceNever }
];
this.choiceService.choose(Severity.Info, message, options).done(choice => {
switch (choice) {
case 0 /* Search Marketplace */:
/* __GDPR__
"fileExtensionSuggestion:popup" : {
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('fileExtensionSuggestion:popup', { userReaction: 'ok', fileExtension: fileExtension });
this.viewletService.openViewlet('workbench.view.extensions', true)
.then(viewlet => viewlet as IExtensionsViewlet)
.then(viewlet => {
viewlet.search(`ext:${fileExtension}`);
viewlet.focus();
});
break;
case 1 /* Never show again */:
fileExtensionSuggestionIgnoreList.push(fileExtension);
this.storageService.store(
'extensionsAssistant/fileExtensionsSuggestionIgnore',
JSON.stringify(fileExtensionSuggestionIgnoreList),
StorageScope.GLOBAL
);
/* __GDPR__
"fileExtensionSuggestion:popup" : {
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('fileExtensionSuggestion:popup', { userReaction: 'neverShowAgain', fileExtension: fileExtension });
break;
}
}, () => {
/* __GDPR__
"fileExtensionSuggestion:popup" : {
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"extensionId": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('fileExtensionSuggestion:popup', { userReaction: 'cancelled', fileExtension: fileExtension });
});
});
});
});
}
private _suggestWorkspaceRecommendations() {
private _suggestWorkspaceRecommendations(): TPromise<any> {
const storageKey = 'extensionsAssistant/workspaceRecommendationsIgnore';
if (this.storageService.getBoolean(storageKey, StorageScope.WORKSPACE, false)) {
return;
}
const config = this.configurationService.getValue<IExtensionsConfiguration>(ConfigurationKey);
if (config.ignoreRecommendations) {
return;
}
this.getWorkspaceRecommendations().done(allRecommendations => {
if (!allRecommendations.length) {
return this.getWorkspaceRecommendations().then(allRecommendations => {
if (!allRecommendations.length || config.ignoreRecommendations || config.showRecommendationsOnlyOnDemand || this.storageService.getBoolean(storageKey, StorageScope.WORKSPACE, false)) {
return;
}
this.extensionsService.getInstalled(LocalExtensionType.User).done(local => {
return this.extensionsService.getInstalled(LocalExtensionType.User).done(local => {
const recommendations = allRecommendations
.filter(id => local.every(local => `${local.manifest.publisher.toLowerCase()}.${local.manifest.name.toLowerCase()}` !== id));
@@ -372,16 +650,15 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
const showAction = this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, localize('showRecommendations', "Show Recommendations"));
const installAllAction = this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction.ID, localize('installAll', "Install All"));
const options = [
const options: Choice[] = [
installAllAction.label,
showAction.label,
localize('neverShowAgain', "Don't show again"),
localize('close', "Close")
{ label: choiceNever }
];
this.choiceService.choose(Severity.Info, message, options, 3).done(choice => {
return this.choiceService.choose(Severity.Info, message, options).done(choice => {
switch (choice) {
case 0:
case 0 /* Install */:
/* __GDPR__
"extensionRecommendations:popup" : {
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
@@ -389,7 +666,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
*/
this.telemetryService.publicLog('extensionWorkspaceRecommendations:popup', { userReaction: 'install' });
return installAllAction.run();
case 1:
case 1 /* Show Recommendations */:
/* __GDPR__
"extensionRecommendations:popup" : {
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
@@ -397,7 +674,7 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
*/
this.telemetryService.publicLog('extensionWorkspaceRecommendations:popup', { userReaction: 'show' });
return showAction.run();
case 2:
case 2 /* Never show again */:
/* __GDPR__
"extensionRecommendations:popup" : {
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
@@ -405,13 +682,6 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
*/
this.telemetryService.publicLog('extensionWorkspaceRecommendations:popup', { userReaction: 'neverShowAgain' });
return this.storageService.store(storageKey, true, StorageScope.WORKSPACE);
case 3:
/* __GDPR__
"extensionRecommendations:popup" : {
"userReaction" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('extensionWorkspaceRecommendations:popup', { userReaction: 'close' });
}
}, () => {
/* __GDPR__
@@ -429,39 +699,38 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
const message = localize('ignoreExtensionRecommendations', "Do you want to ignore all extension recommendations?");
const options = [
localize('ignoreAll', "Yes, Ignore All"),
localize('no', "No"),
localize('cancel', "Cancel")
localize('no', "No")
];
this.choiceService.choose(Severity.Info, message, options, 2).done(choice => {
this.choiceService.choose(Severity.Info, message, options).done(choice => {
switch (choice) {
case 0: // If the user ignores the current message and selects different file type
// we should hide all the stacked up messages as he has selected Yes, Ignore All
this.messageService.hideAll();
return this.setIgnoreRecommendationsConfig(true);
case 1: return this.setIgnoreRecommendationsConfig(false);
case 1:
return this.setIgnoreRecommendationsConfig(false);
}
});
}
private _suggestBasedOnExecutables(recommendations: { [id: string]: string; }): void {
private _suggestBasedOnExecutables(): TPromise<any> {
const homeDir = os.homedir();
let foundExecutables: Set<string> = new Set<string>();
let findExecutable = (exeName, path) => {
let findExecutable = (exeName: string, path: string) => {
return pfs.fileExists(path).then(exists => {
if (exists && !foundExecutables.has(exeName)) {
foundExecutables.add(exeName);
(product.exeBasedExtensionTips[exeName]['recommendations'] || [])
.forEach(x => {
if (product.exeBasedExtensionTips[exeName]['friendlyName']) {
recommendations[x] = product.exeBasedExtensionTips[exeName]['friendlyName'];
this._exeBasedRecommendations[x] = product.exeBasedExtensionTips[exeName]['friendlyName'];
}
});
}
});
};
let promises: TPromise<any>[] = [];
// Loop through recommended extensions
forEach(product.exeBasedExtensionTips, entry => {
if (typeof entry.value !== 'object' || !Array.isArray(entry.value['recommendations'])) {
@@ -478,12 +747,14 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
.replace('%ProgramFiles(x86)%', process.env['ProgramFiles(x86)'])
.replace('%ProgramFiles%', process.env['ProgramFiles'])
.replace('%APPDATA%', process.env['APPDATA']);
findExecutable(exeName, windowsPath);
promises.push(findExecutable(exeName, windowsPath));
} else {
findExecutable(exeName, paths.join('/usr/local/bin', exeName));
findExecutable(exeName, paths.join(homeDir, exeName));
promises.push(findExecutable(exeName, paths.join('/usr/local/bin', exeName)));
promises.push(findExecutable(exeName, paths.join(homeDir, exeName)));
}
});
return TPromise.join(promises);
}
private setIgnoreRecommendationsConfig(configVal: boolean) {
@@ -494,6 +765,84 @@ export class ExtensionTipsService extends Disposable implements IExtensionTipsSe
}
}
private getCachedDynamicWorkspaceRecommendations() {
if (this.contextService.getWorkbenchState() !== WorkbenchState.FOLDER) {
return;
}
const storageKey = 'extensionsAssistant/dynamicWorkspaceRecommendations';
let storedRecommendationsJson = {};
try {
storedRecommendationsJson = JSON.parse(this.storageService.get(storageKey, StorageScope.WORKSPACE, '{}'));
} catch (e) {
this.storageService.remove(storageKey, StorageScope.WORKSPACE);
}
if (Array.isArray(storedRecommendationsJson['recommendations'])
&& isNumber(storedRecommendationsJson['timestamp'])
&& storedRecommendationsJson['timestamp'] > 0
&& (Date.now() - storedRecommendationsJson['timestamp']) / milliSecondsInADay < 14) {
this._dynamicWorkspaceRecommendations = storedRecommendationsJson['recommendations'];
/* __GDPR__
"dynamicWorkspaceRecommendations" : {
"count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"cache" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('dynamicWorkspaceRecommendations', { count: this._dynamicWorkspaceRecommendations.length, cache: 1 });
}
}
private getDynamicWorkspaceRecommendations(): TPromise<void> {
if (this.contextService.getWorkbenchState() !== WorkbenchState.FOLDER
|| this._dynamicWorkspaceRecommendations.length
|| !this._extensionsRecommendationsUrl) {
return TPromise.as(null);
}
const storageKey = 'extensionsAssistant/dynamicWorkspaceRecommendations';
const workspaceUri = this.contextService.getWorkspace().folders[0].uri;
return TPromise.join([getHashedRemotesFromUri(workspaceUri, this.fileService, false), getHashedRemotesFromUri(workspaceUri, this.fileService, true)]).then(([hashedRemotes1, hashedRemotes2]) => {
const hashedRemotes = (hashedRemotes1 || []).concat(hashedRemotes2 || []);
if (!hashedRemotes.length) {
return null;
}
return this.requestService.request({ type: 'GET', url: this._extensionsRecommendationsUrl }).then(context => {
if (context.res.statusCode !== 200) {
return TPromise.as(null);
}
return asJson(context).then((result) => {
const allRecommendations: IDynamicWorkspaceRecommendations[] = Array.isArray(result['workspaceRecommendations']) ? result['workspaceRecommendations'] : [];
if (!allRecommendations.length) {
return;
}
let foundRemote = false;
for (let i = 0; i < hashedRemotes.length && !foundRemote; i++) {
for (let j = 0; j < allRecommendations.length && !foundRemote; j++) {
if (Array.isArray(allRecommendations[j].remoteSet) && allRecommendations[j].remoteSet.indexOf(hashedRemotes[i]) > -1) {
foundRemote = true;
this._dynamicWorkspaceRecommendations = allRecommendations[j].recommendations || [];
this.storageService.store(storageKey, JSON.stringify({
recommendations: this._dynamicWorkspaceRecommendations,
timestamp: Date.now()
}), StorageScope.WORKSPACE);
/* __GDPR__
"dynamicWorkspaceRecommendations" : {
"count" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"cache" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('dynamicWorkspaceRecommendations', { count: this._dynamicWorkspaceRecommendations.length, cache: 0 });
}
}
}
});
});
});
}
getKeywordsForExtension(extension: string): string[] {
const keywords = product.extensionKeywords || {};
return keywords[extension] || [];

View File

@@ -21,10 +21,9 @@ import { VIEWLET_ID, IExtensionsWorkbenchService } from '../common/extensions';
import { ExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/node/extensionsWorkbenchService';
import {
OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowRecommendedKeymapExtensionsAction, ShowPopularExtensionsAction,
ShowEnabledExtensionsAction, ShowInstalledExtensionsAction, ShowDisabledExtensionsAction, UpdateAllAction,
EnableAllAction, EnableAllWorkpsaceAction, DisableAllAction, DisableAllWorkpsaceAction, CheckForUpdatesAction, ShowLanguageExtensionsAction, ShowAzureExtensionsAction, EnableAutoUpdateAction, DisableAutoUpdateAction, ConfigureRecommendedExtensionsCommandsContributor
ShowEnabledExtensionsAction, ShowInstalledExtensionsAction, ShowDisabledExtensionsAction, ShowBuiltInExtensionsAction, UpdateAllAction,
EnableAllAction, EnableAllWorkpsaceAction, DisableAllAction, DisableAllWorkpsaceAction, CheckForUpdatesAction, ShowLanguageExtensionsAction, ShowAzureExtensionsAction, EnableAutoUpdateAction, DisableAutoUpdateAction, ConfigureRecommendedExtensionsCommandsContributor, OpenExtensionsFolderAction, InstallVSIXAction, ReinstallAction
} from 'vs/workbench/parts/extensions/browser/extensionsActions';
import { OpenExtensionsFolderAction, InstallVSIXAction } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions';
import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensionsInput';
import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor } from 'vs/workbench/browser/viewlet';
import { ExtensionEditor } from 'vs/workbench/parts/extensions/browser/extensionEditor';
@@ -164,6 +163,9 @@ actionRegistry.registerWorkbenchAction(installedActionDescriptor, 'Extensions: S
const disabledActionDescriptor = new SyncActionDescriptor(ShowDisabledExtensionsAction, ShowDisabledExtensionsAction.ID, ShowDisabledExtensionsAction.LABEL);
actionRegistry.registerWorkbenchAction(disabledActionDescriptor, 'Extensions: Show Disabled Extensions', ExtensionsLabel);
const builtinActionDescriptor = new SyncActionDescriptor(ShowBuiltInExtensionsAction, ShowBuiltInExtensionsAction.ID, ShowBuiltInExtensionsAction.LABEL);
actionRegistry.registerWorkbenchAction(builtinActionDescriptor, 'Extensions: Show Show Built-in Extensions', ExtensionsLabel);
const updateAllActionDescriptor = new SyncActionDescriptor(UpdateAllAction, UpdateAllAction.ID, UpdateAllAction.LABEL);
actionRegistry.registerWorkbenchAction(updateAllActionDescriptor, 'Extensions: Update All Extensions', ExtensionsLabel);
@@ -191,6 +193,7 @@ actionRegistry.registerWorkbenchAction(checkForUpdatesAction, `Extensions: Check
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(EnableAutoUpdateAction, EnableAutoUpdateAction.ID, EnableAutoUpdateAction.LABEL), `Extensions: Enable Auto Updating Extensions`, ExtensionsLabel);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(DisableAutoUpdateAction, DisableAutoUpdateAction.ID, DisableAutoUpdateAction.LABEL), `Extensions: Disable Auto Updating Extensions`, ExtensionsLabel);
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowRuntimeExtensionsAction, ShowRuntimeExtensionsAction.ID, ShowRuntimeExtensionsAction.LABEL), 'Show Running Extensions', localize('developer', "Developer"));
actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ReinstallAction, ReinstallAction.ID, ReinstallAction.LABEL), 'Reinstall Extension...', localize('developer', "Developer"));
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
.registerConfiguration({
@@ -208,6 +211,11 @@ Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
type: 'boolean',
description: localize('extensionsIgnoreRecommendations', "If set to true, the notifications for extension recommendations will stop showing up."),
default: false
},
'extensions.showRecommendationsOnlyOnDemand': {
type: 'boolean',
description: localize('extensionsShowRecommendationsOnlyOnDemand', "If set to true, recommendations will not be fetched or shown unless specifically requested by the user."),
default: false
}
}
});
@@ -223,4 +231,4 @@ CommandsRegistry.registerCommand('_extensions.manage', (accessor: ServicesAccess
if (extension.length === 1) {
extensionService.open(extension[0]).done(null, errors.onUnexpectedError);
}
});
});

View File

@@ -5,22 +5,23 @@
import { localize } from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import { Action } from 'vs/base/common/actions';
import severity from 'vs/base/common/severity';
import { Action, IAction } from 'vs/base/common/actions';
import paths = require('vs/base/common/paths');
import { ReloadWindowAction } from 'vs/workbench/electron-browser/actions';
import { IExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/common/extensions';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IMessageService } from 'vs/platform/message/common/message';
import { IExtensionsWorkbenchService, IExtension } from 'vs/workbench/parts/extensions/common/extensions';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWindowsService, IWindowService } from 'vs/platform/windows/common/windows';
import { IFileService } from 'vs/platform/files/common/files';
import URI from 'vs/base/common/uri';
import Severity from 'vs/base/common/severity';
import { mnemonicButtonLabel } from 'vs/base/common/labels';
import { IChoiceService } from 'vs/platform/dialogs/common/dialogs';
import { IQuickOpenService, IPickOpenEntry } from 'vs/platform/quickOpen/common/quickOpen';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { LocalExtensionType } from 'vs/platform/extensionManagement/common/extensionManagement';
export class OpenExtensionsFolderAction extends Action {
static ID = 'workbench.extensions.action.openExtensionsFolder';
static readonly ID = 'workbench.extensions.action.openExtensionsFolder';
static LABEL = localize('openExtensionsFolder', "Open Extensions Folder");
constructor(
@@ -38,7 +39,7 @@ export class OpenExtensionsFolderAction extends Action {
return this.fileService.resolveFile(URI.file(extensionsHome)).then(file => {
let itemToShow: string;
if (file.hasChildren) {
if (file.children && file.children.length > 0) {
itemToShow = file.children[0].resource.fsPath;
} else {
itemToShow = paths.normalize(extensionsHome, true);
@@ -51,40 +52,99 @@ export class OpenExtensionsFolderAction extends Action {
export class InstallVSIXAction extends Action {
static ID = 'workbench.extensions.action.installVSIX';
static readonly ID = 'workbench.extensions.action.installVSIX';
static LABEL = localize('installVSIX', "Install from VSIX...");
constructor(
id = InstallVSIXAction.ID,
label = InstallVSIXAction.LABEL,
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService,
@IMessageService private messageService: IMessageService,
@IInstantiationService private instantiationService: IInstantiationService,
@IChoiceService private choiceService: IChoiceService,
@IWindowService private windowsService: IWindowService
) {
super(id, label, 'extension-action install-vsix', true);
}
run(): TPromise<any> {
const result = this.windowsService.showOpenDialog({
return this.windowsService.showOpenDialog({
title: localize('installFromVSIX', "Install from VSIX"),
filters: [{ name: 'VSIX Extensions', extensions: ['vsix'] }],
properties: ['openFile'],
buttonLabel: mnemonicButtonLabel(localize({ key: 'installButton', comment: ['&& denotes a mnemonic'] }, "&&Install"))
});
}).then(result => {
if (!result) {
return TPromise.as(null);
}
if (!result) {
return TPromise.as(null);
}
return TPromise.join(result.map(vsix => this.extensionsWorkbenchService.install(vsix))).then(() => {
return this.choiceService.choose(Severity.Info, localize('InstallVSIXAction.success', "Successfully installed the extension. Reload to enable it."), [localize('InstallVSIXAction.reloadNow', "Reload Now")]).then(choice => {
if (choice === 0) {
return this.windowsService.reloadWindow();
}
return TPromise.join(result.map(vsix => this.extensionsWorkbenchService.install(vsix))).then(() => {
this.messageService.show(
severity.Info,
{
message: localize('InstallVSIXAction.success', "Successfully installed the extension. Restart to enable it."),
actions: [this.instantiationService.createInstance(ReloadWindowAction, ReloadWindowAction.ID, localize('InstallVSIXAction.reloadNow', "Reload Now"))]
}
);
return TPromise.as(undefined);
});
});
});
}
}
export class ReinstallAction extends Action {
static readonly ID = 'workbench.extensions.action.reinstall';
static LABEL = localize('reinstall', "Reinstall Extension...");
constructor(
id: string = ReinstallAction.ID, label: string = ReinstallAction.LABEL,
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService,
@IQuickOpenService private quickOpenService: IQuickOpenService,
@INotificationService private notificationService: INotificationService,
@IWindowService private windowService: IWindowService
) {
super(id, label);
}
get enabled(): boolean {
return this.extensionsWorkbenchService.local.filter(l => l.type === LocalExtensionType.User && l.local).length > 0;
}
run(): TPromise<any> {
return this.quickOpenService.pick(this.getEntries(), { placeHolder: localize('selectExtension', "Select Extension to Reinstall") });
}
private getEntries(): TPromise<IPickOpenEntry[]> {
return this.extensionsWorkbenchService.queryLocal()
.then(local => {
const entries: IPickOpenEntry[] = local
.filter(extension => extension.type === LocalExtensionType.User)
.map(extension => {
return <IPickOpenEntry>{
id: extension.id,
label: extension.displayName,
description: extension.id,
run: () => this.reinstallExtension(extension),
};
});
return entries;
});
}
private reinstallExtension(extension: IExtension): TPromise<void> {
return this.extensionsWorkbenchService.reinstall(extension)
.then(() => {
this.notificationService.notify({
message: localize('ReinstallAction.success', "Successfully reinstalled the extension."),
severity: Severity.Info,
actions: {
primary: [<IAction>{
id: 'reload',
label: localize('ReinstallAction.reloadNow', "Reload Now"),
enabled: true,
run: () => this.windowService.reloadWindow(),
dispose: () => null
}]
}
});
}, error => this.notificationService.error(error));
}
}

View File

@@ -13,15 +13,15 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IExtensionManagementService, ILocalExtension, IExtensionEnablementService, IExtensionTipsService, LocalExtensionType, IExtensionIdentifier, EnablementState } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IMessageService, Severity, IChoiceService } from 'vs/platform/message/common/message';
import { Action } from 'vs/base/common/actions';
import { BetterMergeDisabledNowKey, BetterMergeId, areSameExtensions, adoptToGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { getIdAndVersionFromLocalExtensionId } from 'vs/platform/extensionManagement/node/extensionManagementUtil';
import { IChoiceService } from 'vs/platform/dialogs/common/dialogs';
import { Severity } from 'vs/platform/notification/common/notification';
export interface IExtensionStatus {
identifier: IExtensionIdentifier;
@@ -65,14 +65,12 @@ export class KeymapExtensions implements IWorkbenchContribution {
}
private promptForDisablingOtherKeymaps(newKeymap: IExtensionStatus, oldKeymaps: IExtensionStatus[]): TPromise<void> {
const message = localize('disableOtherKeymapsConfirmation', "Disable other keymaps ({0}) to avoid conflicts between keybindings?", oldKeymaps.map(k => `'${k.local.manifest.displayName}'`).join(', '));
const options = [
localize('yes', "Yes"),
localize('no', "No")
];
return this.choiceService.choose(Severity.Info, message, options, 1, false)
return this.choiceService.choose(Severity.Info, message, options)
.then(value => {
const confirmed = value === 0;
const telemetryData: { [key: string]: any; } = {
@@ -90,7 +88,7 @@ export class KeymapExtensions implements IWorkbenchContribution {
this.telemetryService.publicLog('disableOtherKeymaps', telemetryData);
if (confirmed) {
return TPromise.join(oldKeymaps.map(keymap => {
return this.extensionEnablementService.setEnablement(keymap.local.identifier, EnablementState.Disabled);
return this.extensionEnablementService.setEnablement(keymap.local, EnablementState.Disabled);
}));
}
return undefined;
@@ -151,7 +149,7 @@ export class BetterMergeDisabled implements IWorkbenchContribution {
constructor(
@IStorageService storageService: IStorageService,
@IMessageService messageService: IMessageService,
@IChoiceService choiceService: IChoiceService,
@IExtensionService extensionService: IExtensionService,
@IExtensionManagementService extensionManagementService: IExtensionManagementService,
@ITelemetryService telemetryService: ITelemetryService,
@@ -159,17 +157,14 @@ export class BetterMergeDisabled implements IWorkbenchContribution {
extensionService.whenInstalledExtensionsRegistered().then(() => {
if (storageService.getBoolean(BetterMergeDisabledNowKey, StorageScope.GLOBAL, false)) {
storageService.remove(BetterMergeDisabledNowKey, StorageScope.GLOBAL);
messageService.show(Severity.Info, {
message: localize('betterMergeDisabled', "The Better Merge extension is now built-in, the installed extension was disabled and can be uninstalled."),
actions: [
new Action('uninstall', localize('uninstall', "Uninstall"), null, true, () => {
return extensionManagementService.getInstalled(LocalExtensionType.User).then(extensions => {
return Promise.all(extensions.filter(e => stripVersion(e.identifier.id) === BetterMergeId)
.map(e => extensionManagementService.uninstall(e, true)));
});
}),
new Action('later', localize('later', "Later"), null, true)
]
choiceService.choose(Severity.Info, localize('betterMergeDisabled', "The Better Merge extension is now built-in, the installed extension was disabled and can be uninstalled."), [localize('uninstall', "Uninstall")]).then(choice => {
if (choice === 0) {
extensionManagementService.getInstalled(LocalExtensionType.User).then(extensions => {
return Promise.all(extensions.filter(e => stripVersion(e.identifier.id) === BetterMergeId)
.map(e => extensionManagementService.uninstall(e, true)));
});
}
});
}
});

View File

@@ -24,28 +24,26 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { append, $, addStandardDisposableListener, EventType, addClass, removeClass, toggleClass } from 'vs/base/browser/dom';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, ExtensionState, AutoUpdateConfigurationKey } from '../common/extensions';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IExtensionsWorkbenchService, IExtensionsViewlet, VIEWLET_ID, ExtensionState, AutoUpdateConfigurationKey, ShowRecommendationsOnlyOnDemandKey } from '../common/extensions';
import {
ShowEnabledExtensionsAction, ShowInstalledExtensionsAction, ShowRecommendedExtensionsAction, ShowPopularExtensionsAction, ShowDisabledExtensionsAction,
ShowOutdatedExtensionsAction, ClearExtensionsInputAction, ChangeSortAction, UpdateAllAction, CheckForUpdatesAction, DisableAllAction, EnableAllAction,
EnableAutoUpdateAction, DisableAutoUpdateAction
EnableAutoUpdateAction, DisableAutoUpdateAction, ShowBuiltInExtensionsAction, InstallVSIXAction
} from 'vs/workbench/parts/extensions/browser/extensionsActions';
import { LocalExtensionType, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { InstallVSIXAction } from 'vs/workbench/parts/extensions/electron-browser/extensionsActions';
import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensionsInput';
import { ExtensionsListView, InstalledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView } from './extensionsViews';
import { ExtensionsListView, InstalledExtensionsView, RecommendedExtensionsView, WorkspaceRecommendedExtensionsView, BuiltInExtensionsView } from './extensionsViews';
import { OpenGlobalSettingsAction } from 'vs/workbench/parts/preferences/browser/preferencesActions';
import { IProgressService } from 'vs/platform/progress/common/progress';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { IMessageService, CloseAction } from 'vs/platform/message/common/message';
import Severity from 'vs/base/common/severity';
import { IActivityService, ProgressBadge, NumberBadge } from 'vs/workbench/services/activity/common/activity';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { inputForeground, inputBackground, inputBorder } from 'vs/platform/theme/common/colorRegistry';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ViewsRegistry, ViewLocation, IViewDescriptor } from 'vs/workbench/browser/parts/views/viewsRegistry';
import { ViewsRegistry, ViewLocation, IViewDescriptor } from 'vs/workbench/common/views';
import { PersistentViewsViewlet, ViewsViewletPanel } from 'vs/workbench/browser/parts/views/viewsViewlet';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
@@ -53,7 +51,10 @@ import { IContextKeyService, ContextKeyExpr, RawContextKey, IContextKey } from '
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { getGalleryExtensionIdFromLocal, getMaliciousExtensionsSet } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { ILogService } from 'vs/platform/log/common/log';
import { ReloadWindowAction } from 'vs/workbench/electron-browser/actions';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { IChoiceService } from 'vs/platform/dialogs/common/dialogs';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IPartService } from 'vs/workbench/services/part/common/partService';
interface SearchInputEvent extends Event {
target: HTMLInputElement;
@@ -63,7 +64,9 @@ interface SearchInputEvent extends Event {
const NonEmptyWorkspaceContext = new RawContextKey<boolean>('nonEmptyWorkspace', false);
const SearchExtensionsContext = new RawContextKey<boolean>('searchExtensions', false);
const SearchInstalledExtensionsContext = new RawContextKey<boolean>('searchInstalledExtensions', false);
const SearchBuiltInExtensionsContext = new RawContextKey<boolean>('searchBuiltInExtensions', false);
const RecommendedExtensionsContext = new RawContextKey<boolean>('recommendedExtensions', false);
const DefaultRecommendedExtensionsContext = new RawContextKey<boolean>('defaultRecommendedExtensions', false);
export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtensionsViewlet {
@@ -71,7 +74,9 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens
private nonEmptyWorkspaceContextKey: IContextKey<boolean>;
private searchExtensionsContextKey: IContextKey<boolean>;
private searchInstalledExtensionsContextKey: IContextKey<boolean>;
private searchBuiltInExtensionsContextKey: IContextKey<boolean>;
private recommendedExtensionsContextKey: IContextKey<boolean>;
private defaultRecommendedExtensionsContextKey: IContextKey<boolean>;
private searchDelayer: ThrottledDelayer<any>;
private root: HTMLElement;
@@ -83,13 +88,14 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens
private disposables: IDisposable[] = [];
constructor(
@IPartService partService: IPartService,
@ITelemetryService telemetryService: ITelemetryService,
@IProgressService private progressService: IProgressService,
@IInstantiationService instantiationService: IInstantiationService,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IEditorGroupService private editorInputService: IEditorGroupService,
@IExtensionManagementService private extensionManagementService: IExtensionManagementService,
@IMessageService private messageService: IMessageService,
@INotificationService private notificationService: INotificationService,
@IViewletService private viewletService: IViewletService,
@IThemeService themeService: IThemeService,
@IConfigurationService private configurationService: IConfigurationService,
@@ -99,15 +105,17 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens
@IContextMenuService contextMenuService: IContextMenuService,
@IExtensionService extensionService: IExtensionService
) {
super(VIEWLET_ID, ViewLocation.Extensions, `${VIEWLET_ID}.state`, true, telemetryService, storageService, instantiationService, themeService, contextService, contextKeyService, contextMenuService, extensionService);
super(VIEWLET_ID, ViewLocation.Extensions, `${VIEWLET_ID}.state`, true, partService, telemetryService, storageService, instantiationService, themeService, contextService, contextKeyService, contextMenuService, extensionService);
this.registerViews();
this.searchDelayer = new ThrottledDelayer(500);
this.nonEmptyWorkspaceContextKey = NonEmptyWorkspaceContext.bindTo(contextKeyService);
this.searchExtensionsContextKey = SearchExtensionsContext.bindTo(contextKeyService);
this.searchInstalledExtensionsContextKey = SearchInstalledExtensionsContext.bindTo(contextKeyService);
this.searchBuiltInExtensionsContextKey = SearchBuiltInExtensionsContext.bindTo(contextKeyService);
this.recommendedExtensionsContextKey = RecommendedExtensionsContext.bindTo(contextKeyService);
this.defaultRecommendedExtensionsContextKey = DefaultRecommendedExtensionsContext.bindTo(contextKeyService);
this.defaultRecommendedExtensionsContextKey.set(!this.configurationService.getValue<boolean>(ShowRecommendationsOnlyOnDemandKey));
this.disposables.push(this.viewletService.onDidViewletOpen(this.onViewletOpen, this, this.disposables));
this.configurationService.onDidChangeConfiguration(e => {
@@ -115,6 +123,9 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens
this.secondaryActions = null;
this.updateTitleArea();
}
if (e.affectedKeys.indexOf(ShowRecommendationsOnlyOnDemandKey) > -1) {
this.defaultRecommendedExtensionsContextKey.set(!this.configurationService.getValue<boolean>(ShowRecommendationsOnlyOnDemandKey));
}
}, this, this.disposables);
}
@@ -123,6 +134,7 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens
viewDescriptors.push(this.createMarketPlaceExtensionsListViewDescriptor());
viewDescriptors.push(this.createInstalledExtensionsListViewDescriptor());
viewDescriptors.push(this.createSearchInstalledExtensionsListViewDescriptor());
viewDescriptors.push(this.createSearchBuiltInExtensionsListViewDescriptor());
viewDescriptors.push(this.createDefaultRecommendedExtensionsListViewDescriptor());
viewDescriptors.push(this.createOtherRecommendedExtensionsListViewDescriptor());
viewDescriptors.push(this.createWorkspaceRecommendedExtensionsListViewDescriptor());
@@ -135,8 +147,8 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens
name: localize('marketPlace', "Marketplace"),
location: ViewLocation.Extensions,
ctor: ExtensionsListView,
when: ContextKeyExpr.and(ContextKeyExpr.has('searchExtensions'), ContextKeyExpr.not('searchInstalledExtensions'), ContextKeyExpr.not('recommendedExtensions')),
size: 100
when: ContextKeyExpr.and(ContextKeyExpr.has('searchExtensions'), ContextKeyExpr.not('searchInstalledExtensions'), ContextKeyExpr.not('searchBuiltInExtensions'), ContextKeyExpr.not('recommendedExtensions')),
weight: 100
};
}
@@ -147,7 +159,7 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens
location: ViewLocation.Extensions,
ctor: InstalledExtensionsView,
when: ContextKeyExpr.and(ContextKeyExpr.not('searchExtensions')),
size: 200
weight: 30
};
}
@@ -158,7 +170,7 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens
location: ViewLocation.Extensions,
ctor: InstalledExtensionsView,
when: ContextKeyExpr.and(ContextKeyExpr.has('searchInstalledExtensions')),
size: 50
weight: 100
};
}
@@ -168,8 +180,8 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens
name: localize('recommendedExtensions', "Recommended"),
location: ViewLocation.Extensions,
ctor: RecommendedExtensionsView,
when: ContextKeyExpr.and(ContextKeyExpr.not('searchExtensions')),
size: 600,
when: ContextKeyExpr.and(ContextKeyExpr.not('searchExtensions'), ContextKeyExpr.has('defaultRecommendedExtensions')),
weight: 70,
canToggleVisibility: true
};
}
@@ -181,7 +193,7 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens
location: ViewLocation.Extensions,
ctor: RecommendedExtensionsView,
when: ContextKeyExpr.and(ContextKeyExpr.has('recommendedExtensions')),
size: 600,
weight: 50,
canToggleVisibility: true,
order: 2
};
@@ -194,12 +206,23 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens
location: ViewLocation.Extensions,
ctor: WorkspaceRecommendedExtensionsView,
when: ContextKeyExpr.and(ContextKeyExpr.has('recommendedExtensions'), ContextKeyExpr.has('nonEmptyWorkspace')),
size: 200,
weight: 50,
canToggleVisibility: true,
order: 1
};
}
private createSearchBuiltInExtensionsListViewDescriptor(): IViewDescriptor {
return {
id: 'extensions.builtInExtensionsList',
name: localize('builtInExtensions', "Built-In"),
location: ViewLocation.Extensions,
ctor: BuiltInExtensionsView,
when: ContextKeyExpr.has('searchBuiltInExtensions'),
weight: 100
};
}
async create(parent: Builder): TPromise<void> {
parent.addClass('extensions-viewlet');
this.root = parent.getHTMLElement();
@@ -292,6 +315,7 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens
this.instantiationService.createInstance(ShowOutdatedExtensionsAction, ShowOutdatedExtensionsAction.ID, ShowOutdatedExtensionsAction.LABEL),
this.instantiationService.createInstance(ShowEnabledExtensionsAction, ShowEnabledExtensionsAction.ID, ShowEnabledExtensionsAction.LABEL),
this.instantiationService.createInstance(ShowDisabledExtensionsAction, ShowDisabledExtensionsAction.ID, ShowDisabledExtensionsAction.LABEL),
this.instantiationService.createInstance(ShowBuiltInExtensionsAction, ShowBuiltInExtensionsAction.ID, ShowBuiltInExtensionsAction.LABEL),
this.instantiationService.createInstance(ShowRecommendedExtensionsAction, ShowRecommendedExtensionsAction.ID, ShowRecommendedExtensionsAction.LABEL),
this.instantiationService.createInstance(ShowPopularExtensionsAction, ShowPopularExtensionsAction.ID, ShowPopularExtensionsAction.LABEL),
new Separator(),
@@ -329,6 +353,7 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens
const value = this.searchBox.value || '';
this.searchExtensionsContextKey.set(!!value);
this.searchInstalledExtensionsContextKey.set(InstalledExtensionsView.isInsalledExtensionsQuery(value));
this.searchBuiltInExtensionsContextKey.set(ExtensionsListView.isBuiltInExtensionsQuery(value));
this.recommendedExtensionsContextKey.set(ExtensionsListView.isRecommendedExtensionsQuery(value));
this.nonEmptyWorkspaceContextKey.set(this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY);
@@ -405,16 +430,15 @@ export class ExtensionsViewlet extends PersistentViewsViewlet implements IExtens
if (/ECONNREFUSED/.test(message)) {
const error = createError(localize('suggestProxyError', "Marketplace returned 'ECONNREFUSED'. Please check the 'http.proxy' setting."), {
actions: [
this.instantiationService.createInstance(OpenGlobalSettingsAction, OpenGlobalSettingsAction.ID, OpenGlobalSettingsAction.LABEL),
CloseAction
this.instantiationService.createInstance(OpenGlobalSettingsAction, OpenGlobalSettingsAction.ID, OpenGlobalSettingsAction.LABEL)
]
});
this.messageService.show(Severity.Error, error);
this.notificationService.error(error);
return;
}
this.messageService.show(Severity.Error, err);
this.notificationService.error(err);
}
dispose(): void {
@@ -463,9 +487,9 @@ export class MaliciousExtensionChecker implements IWorkbenchContribution {
constructor(
@IExtensionManagementService private extensionsManagementService: IExtensionManagementService,
@IInstantiationService private instantiationService: IInstantiationService,
@IWindowService private windowService: IWindowService,
@ILogService private logService: ILogService,
@IMessageService private messageService: IMessageService
@IChoiceService private choiceService: IChoiceService
) {
this.loopCheckForMaliciousExtensions();
}
@@ -486,9 +510,12 @@ export class MaliciousExtensionChecker implements IWorkbenchContribution {
if (maliciousExtensions.length) {
return TPromise.join(maliciousExtensions.map(e => this.extensionsManagementService.uninstall(e, true).then(() => {
this.messageService.show(Severity.Warning, {
message: localize('malicious warning', "We have uninstalled '{0}' which was reported to be malicious.", getGalleryExtensionIdFromLocal(e)),
actions: [this.instantiationService.createInstance(ReloadWindowAction, ReloadWindowAction.ID, localize('reloadNow', "Reload Now"))]
return this.choiceService.choose(Severity.Warning, localize('malicious warning', "We have uninstalled '{0}' which was reported to be problematic.", getGalleryExtensionIdFromLocal(e)), [localize('reloadNow', "Reload Now")]).then(choice => {
if (choice === 0) {
return this.windowService.reloadWindow();
}
return TPromise.as(undefined);
});
})));
} else {

View File

@@ -11,9 +11,7 @@ import { dispose } from 'vs/base/common/lifecycle';
import { assign } from 'vs/base/common/objects';
import { chain } from 'vs/base/common/event';
import { isPromiseCanceledError, create as createError } from 'vs/base/common/errors';
import Severity from 'vs/base/common/severity';
import { PagedModel, IPagedModel, mergePagers, IPager } from 'vs/base/common/paging';
import { IMessageService, CloseAction } from 'vs/platform/message/common/message';
import { PagedModel, IPagedModel, IPager } from 'vs/base/common/paging';
import { SortBy, SortOrder, IQueryOptions, LocalExtensionType, IExtensionTipsService, EnablementState } from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
@@ -23,7 +21,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { Delegate, Renderer } from 'vs/workbench/parts/extensions/browser/extensionsList';
import { IExtension, IExtensionsWorkbenchService } from '../common/extensions';
import { Query } from '../common/extensionQuery';
import { IExtensionService } from 'vs/platform/extensions/common/extensions';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { attachBadgeStyler } from 'vs/platform/theme/common/styler';
import { IViewletViewOptions, IViewOptions, ViewsViewletPanel } from 'vs/workbench/browser/parts/views/viewsViewlet';
@@ -35,8 +33,9 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { InstallWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction } from 'vs/workbench/parts/extensions/browser/extensionsActions';
import { WorkbenchPagedList, IListService } from 'vs/platform/list/browser/listService';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { WorkbenchPagedList } from 'vs/platform/list/browser/listService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { INotificationService } from 'vs/platform/notification/common/notification';
export class ExtensionsListView extends ViewsViewletPanel {
@@ -48,11 +47,10 @@ export class ExtensionsListView extends ViewsViewletPanel {
constructor(
private options: IViewletViewOptions,
@IMessageService protected messageService: IMessageService,
@INotificationService protected notificationService: INotificationService,
@IKeybindingService keybindingService: IKeybindingService,
@IContextMenuService contextMenuService: IContextMenuService,
@IInstantiationService protected instantiationService: IInstantiationService,
@IListService private listService: IListService,
@IThemeService private themeService: IThemeService,
@IExtensionService private extensionService: IExtensionService,
@IExtensionsWorkbenchService private extensionsWorkbenchService: IExtensionsWorkbenchService,
@@ -61,9 +59,9 @@ export class ExtensionsListView extends ViewsViewletPanel {
@IExtensionTipsService private tipsService: IExtensionTipsService,
@IModeService private modeService: IModeService,
@ITelemetryService private telemetryService: ITelemetryService,
@IContextKeyService private contextKeyService: IContextKeyService
@IConfigurationService configurationService: IConfigurationService
) {
super({ ...(options as IViewOptions), ariaHeaderLabel: options.name }, keybindingService, contextMenuService);
super({ ...(options as IViewOptions), ariaHeaderLabel: options.name }, keybindingService, contextMenuService, configurationService);
}
renderHeader(container: HTMLElement): void {
@@ -80,12 +78,11 @@ export class ExtensionsListView extends ViewsViewletPanel {
this.messageBox = append(container, $('.message'));
const delegate = new Delegate();
const renderer = this.instantiationService.createInstance(Renderer);
this.list = new WorkbenchPagedList(this.extensionsList, delegate, [renderer], {
ariaLabel: localize('extensions', "Extensions"),
keyboardSupport: false
}, this.contextKeyService, this.listService, this.themeService);
this.list = this.instantiationService.createInstance(WorkbenchPagedList, this.extensionsList, delegate, [renderer], {
ariaLabel: localize('extensions', "Extensions")
}) as WorkbenchPagedList<IExtension>;
chain(this.list.onSelectionChange)
chain(this.list.onOpen)
.map(e => e.elements[0])
.filter(e => !!e)
.on(this.openExtension, this, this.disposables);
@@ -148,6 +145,27 @@ export class ExtensionsListView extends ViewsViewletPanel {
case 'name': options = assign(options, { sortBy: SortBy.Title }); break;
}
if (!value || ExtensionsListView.isBuiltInExtensionsQuery(value)) {
// Show installed extensions
value = value ? value.replace(/@builtin/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase() : '';
let result = await this.extensionsWorkbenchService.queryLocal();
result = result
.filter(e => e.type === LocalExtensionType.System && (e.name.toLowerCase().indexOf(value) > -1 || e.displayName.toLowerCase().indexOf(value) > -1));
const themesExtensions = result.filter(e => {
return e.local.manifest
&& e.local.manifest.contributes
&& Array.isArray(e.local.manifest.contributes.themes)
&& e.local.manifest.contributes.themes.length;
});
const themesExtensionsIds = themesExtensions.map(e => e.id);
const others = result.filter(e => themesExtensionsIds.indexOf(e.id) === -1);
return new PagedModel([...this.sortExtensions(others, options), ...this.sortExtensions(themesExtensions, options)]);
}
if (!value || ExtensionsListView.isInstalledExtensionsQuery(value)) {
// Show installed extensions
value = value ? value.replace(/@installed/g, '').replace(/@sort:(\w+)(-\w*)?/g, '').trim().toLowerCase() : '';
@@ -218,15 +236,11 @@ export class ExtensionsListView extends ViewsViewletPanel {
return this.getRecommendationsModel(query, options);
}
const pagerPromises: TPromise<IPager<IExtension>>[] = [];
let text = query.value;
const extensionRegex = /\bext:([^\s]+)\b/g;
if (extensionRegex.test(query.value)) {
let names: string[] = [];
text = query.value.replace(extensionRegex, (m, ext) => {
names.push(...this.tipsService.getRecommendationsForExtension(ext));
// Get curated keywords
const keywords = this.tipsService.getKeywordsForExtension(ext);
@@ -237,12 +251,13 @@ export class ExtensionsListView extends ViewsViewletPanel {
const languageTag = languageName ? ` tag:"${languageName}"` : '';
// Construct a rich query
return `tag:"__ext_${ext}"${keywords.map(tag => ` tag:${tag}`)}${languageTag}`;
return `tag:"__ext_${ext}" tag:"__ext_.${ext}" ${keywords.map(tag => `tag:"${tag}"`).join(' ')}${languageTag} tag:"${ext}"`;
});
if (names.length) {
const namesOptions = assign({}, options, { names, source: 'extRegex' });
pagerPromises.push(this.extensionsWorkbenchService.queryGallery(namesOptions));
if (text !== query.value) {
options = assign(options, { text: text.substr(0, 350), source: 'file-extension-tags' });
const pager = await this.extensionsWorkbenchService.queryGallery(options);
return new PagedModel(pager);
}
}
@@ -252,11 +267,7 @@ export class ExtensionsListView extends ViewsViewletPanel {
options.source = 'viewlet';
}
pagerPromises.push(this.extensionsWorkbenchService.queryGallery(options));
const pagers = await TPromise.join(pagerPromises);
const pager = pagers.length === 2 ? mergePagers(pagers[0], pagers[1]) : pagers[0];
const pager = await this.extensionsWorkbenchService.queryGallery(options);
return new PagedModel(pager);
}
@@ -287,10 +298,11 @@ export class ExtensionsListView extends ViewsViewletPanel {
.then(local => {
const installedExtensions = local.map(x => `${x.publisher}.${x.name}`);
let fileBasedRecommendations = this.tipsService.getFileBasedRecommendations();
let others = this.tipsService.getOtherRecommendations();
const othersPromise = this.tipsService.getOtherRecommendations();
const workspacePromise = this.tipsService.getWorkspaceRecommendations();
return this.tipsService.getWorkspaceRecommendations()
.then(workspaceRecommendations => {
return TPromise.join([othersPromise, workspacePromise])
.then(([others, workspaceRecommendations]) => {
const names = this.getTrimmedRecommendations(installedExtensions, value, fileBasedRecommendations, others, workspaceRecommendations);
/* __GDPR__
@@ -320,10 +332,11 @@ export class ExtensionsListView extends ViewsViewletPanel {
.then(local => {
const installedExtensions = local.map(x => `${x.publisher}.${x.name}`);
let fileBasedRecommendations = this.tipsService.getFileBasedRecommendations();
let others = this.tipsService.getOtherRecommendations();
const othersPromise = this.tipsService.getOtherRecommendations();
const workspacePromise = this.tipsService.getWorkspaceRecommendations();
return this.tipsService.getWorkspaceRecommendations()
.then(workspaceRecommendations => {
return TPromise.join([othersPromise, workspacePromise])
.then(([others, workspaceRecommendations]) => {
workspaceRecommendations = workspaceRecommendations.map(x => x.toLowerCase());
fileBasedRecommendations = fileBasedRecommendations.filter(x => workspaceRecommendations.indexOf(x.toLowerCase()) === -1);
others = others.filter(x => workspaceRecommendations.indexOf(x.toLowerCase()) === -1);
@@ -446,6 +459,7 @@ export class ExtensionsListView extends ViewsViewletPanel {
const activeEditorInput = this.editorService.getActiveEditorInput();
this.editorInputService.pinEditor(activeEditor.position, activeEditorInput);
activeEditor.focus();
}
@@ -459,16 +473,15 @@ export class ExtensionsListView extends ViewsViewletPanel {
if (/ECONNREFUSED/.test(message)) {
const error = createError(localize('suggestProxyError', "Marketplace returned 'ECONNREFUSED'. Please check the 'http.proxy' setting."), {
actions: [
this.instantiationService.createInstance(OpenGlobalSettingsAction, OpenGlobalSettingsAction.ID, OpenGlobalSettingsAction.LABEL),
CloseAction
this.instantiationService.createInstance(OpenGlobalSettingsAction, OpenGlobalSettingsAction.ID, OpenGlobalSettingsAction.LABEL)
]
});
this.messageService.show(Severity.Error, error);
this.notificationService.error(error);
return;
}
this.messageService.show(Severity.Error, err);
this.notificationService.error(err);
}
dispose(): void {
@@ -476,6 +489,10 @@ export class ExtensionsListView extends ViewsViewletPanel {
super.dispose();
}
static isBuiltInExtensionsQuery(query: string): boolean {
return /@builtin/i.test(query);
}
static isInstalledExtensionsQuery(query: string): boolean {
return /@installed/i.test(query);
}
@@ -528,6 +545,18 @@ export class InstalledExtensionsView extends ExtensionsListView {
}
}
export class BuiltInExtensionsView extends ExtensionsListView {
async show(query: string): TPromise<IPagedModel<IExtension>> {
if (!ExtensionsListView.isBuiltInExtensionsQuery(query)) {
return super.show(query);
}
let searchBuiltInQuery = '@builtin';
searchBuiltInQuery = query ? searchBuiltInQuery + ' ' + query : searchBuiltInQuery;
return super.show(searchBuiltInQuery);
}
}
export class RecommendedExtensionsView extends ExtensionsListView {
async show(query: string): TPromise<IPagedModel<IExtension>> {
@@ -546,7 +575,7 @@ export class WorkspaceRecommendedExtensionsView extends ExtensionsListView {
const actionbar = new ActionBar(listActionBar, {
animated: false
});
actionbar.onDidRun(({ error }) => error && this.messageService.show(Severity.Error, error));
actionbar.onDidRun(({ error }) => error && this.notificationService.error(error));
const installAllAction = this.instantiationService.createInstance(InstallWorkspaceRecommendedExtensionsAction, InstallWorkspaceRecommendedExtensionsAction.ID, InstallWorkspaceRecommendedExtensionsAction.LABEL);
const configureWorkspaceFolderAction = this.instantiationService.createInstance(ConfigureWorkspaceFolderRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction.ID, ConfigureWorkspaceFolderRecommendedExtensionsAction.LABEL);

View File

@@ -27,10 +27,6 @@
height: calc(100% - 38px);
}
.extensions-viewlet > .extensions .extensions-list > .monaco-list {
height: auto;
}
.extensions-viewlet > .extensions .list-actionbar-container .monaco-action-bar .action-item > .octicon {
font-size: 12px;
line-height: 1;
@@ -77,7 +73,6 @@
position: absolute;
top: 1px;
left: 1px;
color: white;
font-size: 90%;
}

View File

@@ -3,12 +3,8 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
.runtime-extensions-editor .monaco-list .monaco-list-rows > .monaco-list-row.odd {
background-color: #f5f5f5;
}
.runtime-extensions-editor .monaco-list .monaco-list-rows > .monaco-list-row:hover:not(.odd) {
background-color: transparent;
.runtime-extensions-editor .monaco-list .monaco-list-rows > .monaco-list-row.odd:not(:hover):not(.focused) {
background-color: rgba(130, 130, 130, 0.08);
}
.runtime-extensions-editor .extension {
@@ -86,11 +82,6 @@
background: url('save-inverse.svg') center center no-repeat;
}
.vs-dark .runtime-extensions-editor .monaco-list .monaco-list-rows > .monaco-list-row.odd,
.hc-black .runtime-extensions-editor .monaco-list .monaco-list-rows > .monaco-list-row.odd {
background-color: #262829;
}
.runtime-extensions-editor .monaco-action-bar {
padding-top: 21px;
flex-shrink: 0;

View File

@@ -20,14 +20,12 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IExtensionsWorkbenchService, IExtension } from 'vs/workbench/parts/extensions/common/extensions';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IExtensionService, IExtensionDescription, IExtensionsStatus, IExtensionHostProfile } from 'vs/platform/extensions/common/extensions';
import { IExtensionService, IExtensionDescription, IExtensionsStatus, IExtensionHostProfile } from 'vs/workbench/services/extensions/common/extensions';
import { IDelegate, IRenderer } from 'vs/base/browser/ui/list/list';
import { WorkbenchList, IListService } from 'vs/platform/list/browser/listService';
import { WorkbenchList } from 'vs/platform/list/browser/listService';
import { append, $, addClass, toggleClass } from 'vs/base/browser/dom';
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
import { IMessageService, Severity } from 'vs/platform/message/common/message';
import { ActionBar, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import { RunOnceScheduler } from 'vs/base/common/async';
import { clipboard } from 'electron';
@@ -39,6 +37,8 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'
import { memoize } from 'vs/base/common/decorators';
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
import Event from 'vs/base/common/event';
import { DisableForWorkspaceAction, DisableGloballyAction } from 'vs/workbench/parts/extensions/browser/extensionsActions';
import { INotificationService } from 'vs/platform/notification/common/notification';
export const IExtensionHostProfileService = createDecorator<IExtensionHostProfileService>('extensionHostProfileService');
@@ -88,7 +88,7 @@ interface IRuntimeExtension {
export class RuntimeExtensionsEditor extends BaseEditor {
static ID: string = 'workbench.editor.runtimeExtensions';
static readonly ID: string = 'workbench.editor.runtimeExtensions';
private _list: WorkbenchList<IRuntimeExtension>;
private _profileInfo: IExtensionHostProfile;
@@ -102,9 +102,7 @@ export class RuntimeExtensionsEditor extends BaseEditor {
@IThemeService themeService: IThemeService,
@IExtensionsWorkbenchService private readonly _extensionsWorkbenchService: IExtensionsWorkbenchService,
@IExtensionService private readonly _extensionService: IExtensionService,
@IListService private readonly _listService: IListService,
@IContextKeyService private readonly _contextKeyService: IContextKeyService,
@IMessageService private readonly _messageService: IMessageService,
@INotificationService private readonly _notificationService: INotificationService,
@IContextMenuService private readonly _contextMenuService: IContextMenuService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
@IExtensionHostProfileService private readonly _extensionHostProfileService: IExtensionHostProfileService,
@@ -269,7 +267,7 @@ export class RuntimeExtensionsEditor extends BaseEditor {
const actionbar = new ActionBar(element, {
animated: false
});
actionbar.onDidRun(({ error }) => error && this._messageService.show(Severity.Error, error));
actionbar.onDidRun(({ error }) => error && this._notificationService.error(error));
actionbar.push(new ReportExtensionIssueAction(), { icon: true, label: true });
const disposables = [actionbar];
@@ -364,16 +362,22 @@ export class RuntimeExtensionsEditor extends BaseEditor {
}
};
this._list = new WorkbenchList<IRuntimeExtension>(container, delegate, [renderer], {
this._list = this._instantiationService.createInstance(WorkbenchList, container, delegate, [renderer], {
multipleSelectionSupport: false
}, this._contextKeyService, this._listService, this.themeService);
}) as WorkbenchList<IRuntimeExtension>;
this._list.splice(0, this._list.length, this._elements);
this._list.onContextMenu((e) => {
const actions: IAction[] = [];
actions.push(this.saveExtensionHostProfileAction, this.extensionHostProfileAction);
if (e.element.marketplaceInfo.type === LocalExtensionType.User) {
actions.push(this._instantiationService.createInstance(DisableForWorkspaceAction, DisableForWorkspaceAction.LABEL));
actions.push(this._instantiationService.createInstance(DisableGloballyAction, DisableGloballyAction.LABEL));
actions.forEach((a: DisableForWorkspaceAction | DisableGloballyAction) => a.extension = e.element.marketplaceInfo);
actions.push(new Separator());
}
actions.push(this.extensionHostProfileAction, this.saveExtensionHostProfileAction);
this._contextMenuService.showContextMenu({
getAnchor: () => e.anchor,
@@ -406,7 +410,7 @@ export class RuntimeExtensionsEditor extends BaseEditor {
export class RuntimeExtensionsInput extends EditorInput {
static ID = 'workbench.runtimeExtensions.input';
static readonly ID = 'workbench.runtimeExtensions.input';
constructor() {
super();
@@ -444,7 +448,7 @@ export class RuntimeExtensionsInput extends EditorInput {
}
export class ShowRuntimeExtensionsAction extends Action {
static ID = 'workbench.action.showRuntimeExtensions';
static readonly ID = 'workbench.action.showRuntimeExtensions';
static LABEL = nls.localize('showRuntimeExtensions', "Show Running Extensions");
constructor(
@@ -461,7 +465,7 @@ export class ShowRuntimeExtensionsAction extends Action {
}
class ReportExtensionIssueAction extends Action {
static ID = 'workbench.extensions.action.reportExtensionIssue';
static readonly ID = 'workbench.extensions.action.reportExtensionIssue';
static LABEL = nls.localize('reportExtensionIssue', "Report Issue");
constructor(
@@ -499,7 +503,7 @@ class ReportExtensionIssueAction extends Action {
}
class ExtensionHostProfileAction extends Action {
static ID = 'workbench.extensions.action.extensionHostProfile';
static readonly ID = 'workbench.extensions.action.extensionHostProfile';
static LABEL_START = nls.localize('extensionHostProfileStart', "Start Extension Host Profile");
static LABEL_STOP = nls.localize('extensionHostProfileStop', "Stop Extension Host Profile");
static STOP_CSS_CLASS = 'extension-host-profile-stop';
@@ -542,7 +546,7 @@ class ExtensionHostProfileAction extends Action {
class SaveExtensionHostProfileAction extends Action {
static LABEL = nls.localize('saveExtensionHostProfile', "Save Extension Host Profile");
static ID = 'workbench.extensions.action.saveExtensionHostProfile';
static readonly ID = 'workbench.extensions.action.saveExtensionHostProfile';
constructor(
id: string = SaveExtensionHostProfileAction.ID, label: string = SaveExtensionHostProfileAction.LABEL,
@@ -558,7 +562,7 @@ class SaveExtensionHostProfileAction extends Action {
}
async run(): TPromise<any> {
let picked = this._windowService.showSaveDialog({
let picked = await this._windowService.showSaveDialog({
title: 'Save Extension Host Profile',
buttonLabel: 'Save',
defaultPath: `CPU-${new Date().toISOString().replace(/[\-:]/g, '')}.cpuprofile`,

View File

@@ -20,13 +20,12 @@ import { IPager, mapPager, singlePagePager } from 'vs/base/common/paging';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import {
IExtensionManagementService, IExtensionGalleryService, ILocalExtension, IGalleryExtension, IQueryOptions, IExtensionManifest,
InstallExtensionEvent, DidInstallExtensionEvent, LocalExtensionType, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionIdentifier, EnablementState
InstallExtensionEvent, DidInstallExtensionEvent, LocalExtensionType, DidUninstallExtensionEvent, IExtensionEnablementService, IExtensionIdentifier, EnablementState, IExtensionTipsService
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { getGalleryExtensionIdFromLocal, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, areSameExtensions, getMaliciousExtensionsSet } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IChoiceService, IMessageService } from 'vs/platform/message/common/message';
import Severity from 'vs/base/common/severity';
import URI from 'vs/base/common/uri';
import { IExtension, IExtensionDependencies, ExtensionState, IExtensionsWorkbenchService, AutoUpdateConfigurationKey } from 'vs/workbench/parts/extensions/common/extensions';
@@ -34,6 +33,10 @@ import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/edi
import { IURLService } from 'vs/platform/url/common/url';
import { ExtensionsInput } from 'vs/workbench/parts/extensions/common/extensionsInput';
import product from 'vs/platform/node/product';
import { ILogService } from 'vs/platform/log/common/log';
import { IProgressService2, ProgressLocation } from 'vs/platform/progress/common/progress';
import { IChoiceService } from 'vs/platform/dialogs/common/dialogs';
import { INotificationService } from 'vs/platform/notification/common/notification';
interface IExtensionStateProvider<T> {
(extension: Extension): T;
@@ -107,13 +110,21 @@ class Extension implements IExtension {
}
get url(): string {
if (!product.extensionsGallery) {
if (!product.extensionsGallery || !this.gallery) {
return null;
}
return `${product.extensionsGallery.itemUrl}?itemName=${this.publisher}.${this.name}`;
}
get downloadUrl(): string {
if (!product.extensionsGallery) {
return null;
}
return `${product.extensionsGallery.serviceUrl}/publishers/${this.publisher}/vsextensions/${this.name}/${this.latestVersion}/vspackage`;
}
get iconUrl(): string {
return this.galleryIconUrl || this.localIconUrl || this.defaultIconUrl;
}
@@ -136,6 +147,16 @@ class Extension implements IExtension {
}
private get defaultIconUrl(): string {
if (this.type === LocalExtensionType.System) {
if (this.local.manifest && this.local.manifest.contributes) {
if (Array.isArray(this.local.manifest.contributes.themes) && this.local.manifest.contributes.themes.length) {
return require.toUrl('../browser/media/theme-icon.png');
}
if (Array.isArray(this.local.manifest.contributes.languages) && this.local.manifest.contributes.languages.length) {
return require.toUrl('../browser/media/language-icon.png');
}
}
}
return require.toUrl('../browser/media/defaultIcon.png');
}
@@ -212,6 +233,14 @@ class Extension implements IExtension {
return readFile(uri.fsPath, 'utf8');
}
if (this.type === LocalExtensionType.System) {
return TPromise.as(`# ${this.displayName || this.name}
**Notice** This is a an extension that is bundled with Visual Studio Code.
${this.description}
`);
}
return TPromise.wrapError<string>(new Error('not available'));
}
@@ -341,11 +370,14 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
@IExtensionGalleryService private galleryService: IExtensionGalleryService,
@IConfigurationService private configurationService: IConfigurationService,
@ITelemetryService private telemetryService: ITelemetryService,
@IMessageService private messageService: IMessageService,
@INotificationService private notificationService: INotificationService,
@IChoiceService private choiceService: IChoiceService,
@IURLService urlService: IURLService,
@IExtensionEnablementService private extensionEnablementService: IExtensionEnablementService,
@IWindowService private windowService: IWindowService
@IWindowService private windowService: IWindowService,
@ILogService private logService: ILogService,
@IProgressService2 private progressService: IProgressService2,
@IExtensionTipsService private extensionTipsService: IExtensionTipsService
) {
this.stateProvider = ext => this.getExtensionState(ext);
@@ -554,7 +586,11 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
install(extension: string | IExtension): TPromise<void> {
if (typeof extension === 'string') {
return this.extensionService.install(extension);
return this.progressService.withProgress({
location: ProgressLocation.Extensions,
title: nls.localize('installingVSIXExtension', 'Installing extension from VSIX...'),
tooltip: `${extension}`
}, () => this.extensionService.install(extension));
}
if (!(extension instanceof Extension)) {
@@ -562,7 +598,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
}
if (extension.isMalicious) {
return TPromise.wrapError<void>(new Error(nls.localize('malicious', "This extension is reported to be malicious.")));
return TPromise.wrapError<void>(new Error(nls.localize('malicious', "This extension is reported to be problematic.")));
}
const ext = extension as Extension;
@@ -571,19 +607,15 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
if (!gallery) {
return TPromise.wrapError<void>(new Error('Missing gallery'));
}
if (ext.gallery.assets.downloadPage && ext.gallery.assets.downloadPage.uri) {
window.open(ext.gallery.assets.downloadPage.uri);
return TPromise.wrap<void>(void 0);
} else {
return this.extensionService.installFromGallery(gallery);
}
return this.progressService.withProgress({
location: ProgressLocation.Extensions,
title: nls.localize('installingMarketPlaceExtension', 'Installing extension from Marketplace....'),
tooltip: `${extension.id}`
}, () => this.extensionService.installFromGallery(gallery));
}
setEnablement(extension: IExtension, enablementState: EnablementState): TPromise<void> {
if (extension.type === LocalExtensionType.System) {
return TPromise.wrap<void>(void 0);
}
const enable = enablementState === EnablementState.Enabled || enablementState === EnablementState.WorkspaceEnabled;
return this.promptAndSetEnablement(extension, enablementState, enable).then(reload => {
/* __GDPR__
@@ -616,8 +648,30 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
return TPromise.wrapError<void>(new Error('Missing local'));
}
return this.extensionService.uninstall(local);
this.logService.info(`Requested uninstalling the extension ${extension.id} from window ${this.windowService.getCurrentWindowId()}`);
return this.progressService.withProgress({
location: ProgressLocation.Extensions,
title: nls.localize('uninstallingExtension', 'Uninstalling extension....'),
tooltip: `${local.identifier.id}`
}, () => this.extensionService.uninstall(local));
}
reinstall(extension: IExtension): TPromise<void> {
if (!(extension instanceof Extension)) {
return undefined;
}
const ext = extension as Extension;
const local = ext.local || this.installed.filter(e => e.id === extension.id)[0].local;
if (!local) {
return TPromise.wrapError<void>(new Error('Missing local'));
}
return this.progressService.withProgress({
location: ProgressLocation.Extensions,
tooltip: `${local.identifier.id}`
}, () => this.extensionService.reinstall(local));
}
private promptAndSetEnablement(extension: IExtension, enablementState: EnablementState, enable: boolean): TPromise<any> {
@@ -633,7 +687,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
}
private promptForDependenciesAndEnable(extension: IExtension, dependencies: IExtension[], enablementState: EnablementState, enable: boolean): TPromise<any> {
const message = nls.localize('enableDependeciesConfirmation', "Enabling '{0}' also enable its dependencies. Would you like to continue?", extension.displayName);
const message = nls.localize('enableDependeciesConfirmation', "Enabling '{0}' also enables its dependencies. Would you like to continue?", extension.displayName);
const options = [
nls.localize('enable', "Yes"),
nls.localize('doNotEnable', "No")
@@ -734,7 +788,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
}
private doSetEnablement(extension: IExtension, enablementState: EnablementState): TPromise<boolean> {
return this.extensionEnablementService.setEnablement(extension, enablementState);
return this.extensionEnablementService.setEnablement(extension.local, enablementState);
}
get allowedBadgeProviders(): string[] {
@@ -768,25 +822,25 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
private onDidInstallExtension(event: DidInstallExtensionEvent): void {
const { local, zipPath, error, gallery } = event;
const installing = gallery ? this.installing.filter(e => areSameExtensions(e.extension, gallery.identifier))[0] : null;
const extension: Extension = installing ? installing.extension : zipPath ? new Extension(this.galleryService, this.stateProvider, null, null, this.telemetryService) : null;
const installingExtension = gallery ? this.installing.filter(e => areSameExtensions(e.extension, gallery.identifier))[0] : null;
const extension: Extension = installingExtension ? installingExtension.extension : zipPath ? new Extension(this.galleryService, this.stateProvider, null, null, this.telemetryService) : null;
if (extension) {
this.installing = installing ? this.installing.filter(e => e !== installing) : this.installing;
this.installing = installingExtension ? this.installing.filter(e => e !== installingExtension) : this.installing;
if (error) {
if (extension.gallery) {
// Updating extension can be only a gallery extension
const installed = this.installed.filter(e => e.id === extension.id)[0];
if (installed && installing) {
installing.operation = Operation.Updating;
if (installed && installingExtension) {
installingExtension.operation = Operation.Updating;
}
}
} else {
extension.local = local;
const installed = this.installed.filter(e => e.id === extension.id)[0];
if (installed) {
if (installing) {
installing.operation = Operation.Updating;
if (installingExtension) {
installingExtension.operation = Operation.Updating;
}
installed.local = local;
} else {
@@ -795,13 +849,14 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
}
if (extension.gallery) {
// Report telemetry only for gallery extensions
this.reportTelemetry(installing, error);
this.reportTelemetry(installingExtension, error);
}
}
this._onChange.fire();
}
private onUninstallExtension({ id }: IExtensionIdentifier): void {
this.logService.info(`Uninstalling the extension ${id} from window ${this.windowService.getCurrentWindowId()}`);
const extension = this.installed.filter(e => e.local.identifier.id === id)[0];
const newLength = this.installed.filter(e => e.local.identifier.id !== id).length;
// TODO: Ask @Joao why is this?
@@ -864,12 +919,14 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
const data = active.extension.telemetryData;
const duration = new Date().getTime() - active.start.getTime();
const eventName = toTelemetryEventName(active.operation);
const extRecommendations = this.extensionTipsService.getAllRecommendationsWithReason() || {};
const recommendationsData = extRecommendations[active.extension.id.toLowerCase()] ? { recommendationReason: extRecommendations[active.extension.id.toLowerCase()].reasonId } : {};
/* __GDPR__
"extensionGallery:install" : {
"success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"errorcode": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"recommendationReason": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"${include}": [
"${GalleryExtensionTelemetryData}"
]
@@ -879,7 +936,8 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
"extensionGallery:update" : {
"success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"errorcode": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"recommendationReason": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"${include}": [
"${GalleryExtensionTelemetryData}"
]
@@ -889,13 +947,14 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
"extensionGallery:uninstall" : {
"success": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"duration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"errorcode": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"errorcode": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"recommendationReason": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"${include}": [
"${GalleryExtensionTelemetryData}"
]
}
*/
this.telemetryService.publicLog(eventName, assign(data, { success: !errorcode, duration, errorcode }));
this.telemetryService.publicLog(eventName, assign(data, { success: !errorcode, duration, errorcode }, recommendationsData));
}
private onError(err: any): void {
@@ -909,7 +968,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
return;
}
this.messageService.show(Severity.Error, err);
this.notificationService.error(err);
}
private onOpenExtensionUrl(uri: URI): void {
@@ -922,8 +981,11 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
const extensionId = match[1];
this.queryLocal().then(local => {
if (local.some(local => local.id === extensionId)) {
return TPromise.as(null);
const extension = local.filter(local => local.id === extensionId)[0];
if (extension) {
return this.windowService.show()
.then(() => this.open(extension));
}
return this.queryGallery({ names: [extensionId], source: 'uri' }).then(result => {
@@ -937,15 +999,14 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService {
return this.open(extension).then(() => {
const message = nls.localize('installConfirmation', "Would you like to install the '{0}' extension?", extension.displayName, extension.publisher);
const options = [
nls.localize('install', "Install"),
nls.localize('cancel', "Cancel")
nls.localize('install', "Install")
];
return this.choiceService.choose(Severity.Info, message, options, 2, false).then(value => {
if (value !== 0) {
return TPromise.as(null);
return this.choiceService.choose(Severity.Info, message, options).then(value => {
if (value === 0) {
return this.install(extension);
}
return this.install(extension);
return TPromise.as(null);
});
});
});

View File

@@ -0,0 +1,165 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import * as assert from 'assert';
import * as path from 'path';
import * as fs from 'fs';
import * as os from 'os';
import { TPromise } from 'vs/base/common/winjs.base';
import uuid = require('vs/base/common/uuid');
import { mkdirp } from 'vs/base/node/pfs';
import {
IExtensionGalleryService, IGalleryExtensionAssets, IGalleryExtension, IExtensionManagementService, LocalExtensionType,
IExtensionEnablementService, DidInstallExtensionEvent, DidUninstallExtensionEvent, InstallExtensionEvent, IExtensionIdentifier
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionTipsService } from 'vs/workbench/parts/extensions/electron-browser/extensionTipsService';
import { ExtensionGalleryService } from 'vs/platform/extensionManagement/node/extensionGalleryService';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
import { Emitter } from 'vs/base/common/event';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { TestTextResourceConfigurationService, TestContextService, TestLifecycleService, TestEnvironmentService } from 'vs/workbench/test/workbenchTestServices';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import URI from 'vs/base/common/uri';
import { testWorkspace } from 'vs/platform/workspace/test/common/testWorkspace';
import { IFileService } from 'vs/platform/files/common/files';
import { FileService } from 'vs/workbench/services/files/node/fileService';
import extfs = require('vs/base/node/extfs');
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { IPager } from 'vs/base/common/paging';
import { assign } from 'vs/base/common/objects';
import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { generateUuid } from 'vs/base/common/uuid';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { IExtensionsWorkbenchService, ConfigurationKey } from 'vs/workbench/parts/extensions/common/extensions';
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { ExtensionsWorkbenchService } from 'vs/workbench/parts/extensions/node/extensionsWorkbenchService';
import { TestExtensionEnablementService } from 'vs/platform/extensionManagement/test/common/extensionEnablementService.test';
import { IURLService } from 'vs/platform/url/common/url';
import product from 'vs/platform/node/product';
import { ITextModel } from 'vs/editor/common/model';
import { IModelService } from 'vs/editor/common/services/modelService';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IChoiceService } from 'vs/platform/dialogs/common/dialogs';
const mockExtensionGallery: IGalleryExtension[] = [
aGalleryExtension('MockExtension1', {
displayName: 'Mock Extension 1',
version: '1.5',
publisherId: 'mockPublisher1Id',
publisher: 'mockPublisher1',
publisherDisplayName: 'Mock Publisher 1',
description: 'Mock Description',
installCount: 1000,
rating: 4,
ratingCount: 100
}, {
dependencies: ['pub.1'],
}, {
manifest: { uri: 'uri:manifest', fallbackUri: 'fallback:manifest' },
readme: { uri: 'uri:readme', fallbackUri: 'fallback:readme' },
changelog: { uri: 'uri:changelog', fallbackUri: 'fallback:changlog' },
download: { uri: 'uri:download', fallbackUri: 'fallback:download' },
icon: { uri: 'uri:icon', fallbackUri: 'fallback:icon' },
license: { uri: 'uri:license', fallbackUri: 'fallback:license' },
repository: { uri: 'uri:repository', fallbackUri: 'fallback:repository' },
}),
aGalleryExtension('MockExtension2', {
displayName: 'Mock Extension 2',
version: '1.5',
publisherId: 'mockPublisher2Id',
publisher: 'mockPublisher2',
publisherDisplayName: 'Mock Publisher 2',
description: 'Mock Description',
installCount: 1000,
rating: 4,
ratingCount: 100
}, {
dependencies: ['pub.1', 'pub.2'],
}, {
manifest: { uri: 'uri:manifest', fallbackUri: 'fallback:manifest' },
readme: { uri: 'uri:readme', fallbackUri: 'fallback:readme' },
changelog: { uri: 'uri:changelog', fallbackUri: 'fallback:changlog' },
download: { uri: 'uri:download', fallbackUri: 'fallback:download' },
icon: { uri: 'uri:icon', fallbackUri: 'fallback:icon' },
license: { uri: 'uri:license', fallbackUri: 'fallback:license' },
repository: { uri: 'uri:repository', fallbackUri: 'fallback:repository' },
})
];
const mockExtensionLocal = [
{
type: LocalExtensionType.User,
identifier: mockExtensionGallery[0].identifier,
manifest: {
name: mockExtensionGallery[0].name,
publisher: mockExtensionGallery[0].publisher,
version: mockExtensionGallery[0].version
},
metadata: null,
path: 'somepath',
readmeUrl: 'some readmeUrl',
changelogUrl: 'some changelogUrl'
},
{
type: LocalExtensionType.User,
identifier: mockExtensionGallery[1].identifier,
manifest: {
name: mockExtensionGallery[1].name,
publisher: mockExtensionGallery[1].publisher,
version: mockExtensionGallery[1].version
},
metadata: null,
path: 'somepath',
readmeUrl: 'some readmeUrl',
changelogUrl: 'some changelogUrl'
}
];
const mockTestData = {
recommendedExtensions: [
'mockPublisher1.mockExtension1',
'MOCKPUBLISHER2.mockextension2',
'badlyformattedextension',
'MOCKPUBLISHER2.mockextension2',
'unknown.extension'
],
validRecommendedExtensions: [
'mockPublisher1.mockExtension1',
'MOCKPUBLISHER2.mockextension2'
]
};
function aPage<T>(...objects: T[]): IPager<T> {
return { firstPage: objects, total: objects.length, pageSize: objects.length, getPage: () => null };
}
const noAssets: IGalleryExtensionAssets = {
changelog: null,
download: null,
icon: null,
license: null,
manifest: null,
readme: null,
repository: null
};
function aGalleryExtension(name: string, properties: any = {}, galleryExtensionProperties: any = {}, assets: IGalleryExtensionAssets = noAssets): IGalleryExtension {
const galleryExtension = <IGalleryExtension>Object.create({});
assign(galleryExtension, { name, publisher: 'pub', version: '1.0.0', properties: {}, assets: {} }, properties);
assign(galleryExtension.properties, { dependencies: [] }, galleryExtensionProperties);
assign(galleryExtension.assets, assets);
galleryExtension.identifier = { id: getGalleryExtensionId(galleryExtension.publisher, galleryExtension.name), uuid: generateUuid() };
return <IGalleryExtension>galleryExtension;
}
suite('ExtensionsTipsService Test', () => {
test('ExtensionTipsService: No Prompt for valid workspace recommendations when galleryService is absent', () => {
});
});

View File

@@ -29,9 +29,13 @@ import { IPager } from 'vs/base/common/paging';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { TestContextService } from 'vs/workbench/test/workbenchTestServices';
import { IChoiceService } from 'vs/platform/message/common/message';
import { TestContextService, TestWindowService } from 'vs/workbench/test/workbenchTestServices';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService, NullLogService } from 'vs/platform/log/common/log';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { IProgressService2 } from 'vs/platform/progress/common/progress';
import { ProgressService2 } from 'vs/workbench/services/progress/browser/progressService2';
import { IChoiceService } from 'vs/platform/dialogs/common/dialogs';
suite('ExtensionsWorkbenchService Test', () => {
@@ -52,6 +56,9 @@ suite('ExtensionsWorkbenchService Test', () => {
instantiationService = new TestInstantiationService();
instantiationService.stub(IURLService, { onOpenURL: new Emitter().event });
instantiationService.stub(ITelemetryService, NullTelemetryService);
instantiationService.stub(ILogService, NullLogService);
instantiationService.stub(IWindowService, TestWindowService);
instantiationService.stub(IProgressService2, ProgressService2);
instantiationService.stub(IExtensionGalleryService, ExtensionGalleryService);
@@ -695,283 +702,374 @@ suite('ExtensionsWorkbenchService Test', () => {
});
test('test uninstalled extensions are always enabled', () => {
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.b' }, EnablementState.Disabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.c' }, EnablementState.WorkspaceDisabled);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a')));
return testObject.queryGallery().then(pagedResponse => {
const actual = pagedResponse.firstPage[0];
assert.equal(actual.enablementState, EnablementState.Enabled);
});
return instantiationService.get(IExtensionEnablementService).setEnablement(aLocalExtension('b'), EnablementState.Disabled)
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(aLocalExtension('c'), EnablementState.WorkspaceDisabled))
.then(() => {
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage(aGalleryExtension('a')));
return testObject.queryGallery().then(pagedResponse => {
const actual = pagedResponse.firstPage[0];
assert.equal(actual.enablementState, EnablementState.Enabled);
});
});
});
test('test enablement state installed enabled extension', () => {
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.b' }, EnablementState.Disabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.c' }, EnablementState.WorkspaceDisabled);
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a')]);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
return instantiationService.get(IExtensionEnablementService).setEnablement(aLocalExtension('b'), EnablementState.Disabled)
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(aLocalExtension('c'), EnablementState.WorkspaceDisabled))
.then(() => {
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a')]);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
const actual = testObject.local[0];
const actual = testObject.local[0];
assert.equal(actual.enablementState, EnablementState.Enabled);
assert.equal(actual.enablementState, EnablementState.Enabled);
});
});
test('test workspace disabled extension', () => {
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.b' }, EnablementState.Disabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.d' }, EnablementState.Disabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.e' }, EnablementState.WorkspaceDisabled);
const extensionA = aLocalExtension('a');
return instantiationService.get(IExtensionEnablementService).setEnablement(aLocalExtension('b'), EnablementState.Disabled)
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(aLocalExtension('d'), EnablementState.Disabled))
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(extensionA, EnablementState.WorkspaceDisabled))
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(aLocalExtension('e'), EnablementState.WorkspaceDisabled))
.then(() => {
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [extensionA]);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a')]);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
const actual = testObject.local[0];
const actual = testObject.local[0];
assert.equal(actual.enablementState, EnablementState.WorkspaceDisabled);
assert.equal(actual.enablementState, EnablementState.WorkspaceDisabled);
});
});
test('test globally disabled extension', () => {
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.a' }, EnablementState.Disabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.d' }, EnablementState.Disabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.c' }, EnablementState.WorkspaceDisabled);
const localExtension = aLocalExtension('a');
return instantiationService.get(IExtensionEnablementService).setEnablement(localExtension, EnablementState.Disabled)
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(aLocalExtension('d'), EnablementState.Disabled))
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(aLocalExtension('c'), EnablementState.WorkspaceDisabled))
.then(() => {
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [localExtension]);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a')]);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
const actual = testObject.local[0];
const actual = testObject.local[0];
assert.equal(actual.enablementState, EnablementState.Disabled);
assert.equal(actual.enablementState, EnablementState.Disabled);
});
});
test('test enablement state is updated for user extensions', () => {
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.c' }, EnablementState.Disabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.b' }, EnablementState.WorkspaceDisabled);
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a')]);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
testObject.setEnablement(testObject.local[0], EnablementState.WorkspaceDisabled);
const actual = testObject.local[0];
assert.equal(actual.enablementState, EnablementState.WorkspaceDisabled);
return instantiationService.get(IExtensionEnablementService).setEnablement(aLocalExtension('c'), EnablementState.Disabled)
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(aLocalExtension('b'), EnablementState.WorkspaceDisabled))
.then(() => {
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a')]);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
return testObject.setEnablement(testObject.local[0], EnablementState.WorkspaceDisabled)
.then(() => {
const actual = testObject.local[0];
assert.equal(actual.enablementState, EnablementState.WorkspaceDisabled);
});
});
});
test('test enable extension globally when extension is disabled for workspace', () => {
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.a' }, EnablementState.WorkspaceDisabled);
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a')]);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
testObject.setEnablement(testObject.local[0], EnablementState.Enabled);
const actual = testObject.local[0];
assert.equal(actual.enablementState, EnablementState.Enabled);
const localExtension = aLocalExtension('a');
return instantiationService.get(IExtensionEnablementService).setEnablement(localExtension, EnablementState.WorkspaceDisabled)
.then(() => {
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [localExtension]);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
return testObject.setEnablement(testObject.local[0], EnablementState.Enabled)
.then(() => {
const actual = testObject.local[0];
assert.equal(actual.enablementState, EnablementState.Enabled);
});
});
});
test('test disable extension globally', () => {
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a')]);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
testObject.setEnablement(testObject.local[0], EnablementState.Disabled);
const actual = testObject.local[0];
assert.equal(actual.enablementState, EnablementState.Disabled);
return testObject.setEnablement(testObject.local[0], EnablementState.Disabled)
.then(() => {
const actual = testObject.local[0];
assert.equal(actual.enablementState, EnablementState.Disabled);
});
});
test('test system extensions are always enabled', () => {
test('test system extensions can be disabled', () => {
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a', {}, { type: LocalExtensionType.System })]);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
testObject.setEnablement(testObject.local[0], EnablementState.Disabled);
const actual = testObject.local[0];
assert.equal(actual.enablementState, EnablementState.Enabled);
return testObject.setEnablement(testObject.local[0], EnablementState.Disabled)
.then(() => {
const actual = testObject.local[0];
assert.equal(actual.enablementState, EnablementState.Disabled);
});
});
test('test enablement state is updated on change from outside', () => {
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.c' }, EnablementState.Disabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.b' }, EnablementState.WorkspaceDisabled);
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a')]);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
const localExtension = aLocalExtension('a');
return instantiationService.get(IExtensionEnablementService).setEnablement(aLocalExtension('c'), EnablementState.Disabled)
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(aLocalExtension('b'), EnablementState.WorkspaceDisabled))
.then(() => {
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [localExtension]);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.a' }, EnablementState.Disabled);
const actual = testObject.local[0];
assert.equal(actual.enablementState, EnablementState.Disabled);
return instantiationService.get(IExtensionEnablementService).setEnablement(localExtension, EnablementState.Disabled)
.then(() => {
const actual = testObject.local[0];
assert.equal(actual.enablementState, EnablementState.Disabled);
});
});
});
test('test disable extension with dependencies disable only itself', () => {
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.a' }, EnablementState.Enabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.b' }, EnablementState.Enabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.c' }, EnablementState.Enabled);
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a', { extensionDependencies: ['pub.b'] }), aLocalExtension('b'), aLocalExtension('c')]);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
const extensionA = aLocalExtension('a', { extensionDependencies: ['pub.b'] });
const extensionB = aLocalExtension('b');
const extensionC = aLocalExtension('c');
testObject.setEnablement(testObject.local[0], EnablementState.Disabled);
return instantiationService.get(IExtensionEnablementService).setEnablement(extensionA, EnablementState.Enabled)
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(extensionB, EnablementState.Enabled))
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(extensionC, EnablementState.Enabled))
.then(() => {
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [extensionA, extensionB, extensionC]);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
assert.equal(testObject.local[0].enablementState, EnablementState.Disabled);
assert.equal(testObject.local[1].enablementState, EnablementState.Enabled);
return testObject.setEnablement(testObject.local[0], EnablementState.Disabled)
.then(() => {
assert.equal(testObject.local[0].enablementState, EnablementState.Disabled);
assert.equal(testObject.local[1].enablementState, EnablementState.Enabled);
});
});
});
test('test disable extension with dependencies disable all', () => {
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.a' }, EnablementState.Enabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.b' }, EnablementState.Enabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.c' }, EnablementState.Enabled);
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a', { extensionDependencies: ['pub.b'] }), aLocalExtension('b'), aLocalExtension('c')]);
instantiationService.stubPromise(IChoiceService, 'choose', 1);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
const extensionA = aLocalExtension('a', { extensionDependencies: ['pub.b'] });
const extensionB = aLocalExtension('b');
const extensionC = aLocalExtension('c');
testObject.setEnablement(testObject.local[0], EnablementState.Disabled);
return instantiationService.get(IExtensionEnablementService).setEnablement(extensionA, EnablementState.Enabled)
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(extensionB, EnablementState.Enabled))
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(extensionC, EnablementState.Enabled))
.then(() => {
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [extensionA, extensionB, extensionC]);
instantiationService.stubPromise(IChoiceService, 'choose', 1);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
assert.equal(testObject.local[0].enablementState, EnablementState.Disabled);
assert.equal(testObject.local[1].enablementState, EnablementState.Disabled);
return testObject.setEnablement(testObject.local[0], EnablementState.Disabled)
.then(() => {
assert.equal(testObject.local[0].enablementState, EnablementState.Disabled);
assert.equal(testObject.local[1].enablementState, EnablementState.Disabled);
});
});
});
test('test disable extension fails if extension is a dependent of other', () => {
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.a' }, EnablementState.Enabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.b' }, EnablementState.Enabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.c' }, EnablementState.Enabled);
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a', { extensionDependencies: ['pub.b'] }), aLocalExtension('b'), aLocalExtension('c')]);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
const extensionA = aLocalExtension('a', { extensionDependencies: ['pub.b'] });
const extensionB = aLocalExtension('b');
const extensionC = aLocalExtension('c');
return testObject.setEnablement(testObject.local[1], EnablementState.Disabled).then(() => assert.fail('Should fail'), error => assert.ok(true));
return instantiationService.get(IExtensionEnablementService).setEnablement(extensionA, EnablementState.Enabled)
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(extensionB, EnablementState.Enabled))
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(extensionC, EnablementState.Enabled))
.then(() => {
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [extensionA, extensionB, extensionC]);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
return testObject.setEnablement(testObject.local[1], EnablementState.Disabled).then(() => assert.fail('Should fail'), error => assert.ok(true));
});
});
test('test disable extension does not fail if its dependency is a dependent of other but chosen to disable only itself', () => {
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.a' }, EnablementState.Enabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.b' }, EnablementState.Enabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.c' }, EnablementState.Enabled);
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a', { extensionDependencies: ['pub.b'] }), aLocalExtension('b'), aLocalExtension('c', { extensionDependencies: ['pub.b'] })]);
const extensionA = aLocalExtension('a', { extensionDependencies: ['pub.b'] });
const extensionB = aLocalExtension('b');
const extensionC = aLocalExtension('c', { extensionDependencies: ['pub.b'] });
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
return instantiationService.get(IExtensionEnablementService).setEnablement(extensionA, EnablementState.Enabled)
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(extensionB, EnablementState.Enabled))
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(extensionC, EnablementState.Enabled))
.then(() => {
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [extensionA, extensionB, extensionC]);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
testObject.setEnablement(testObject.local[0], EnablementState.Disabled);
assert.equal(testObject.local[0].enablementState, EnablementState.Disabled);
return testObject.setEnablement(testObject.local[0], EnablementState.Disabled)
.then(() => {
assert.equal(testObject.local[0].enablementState, EnablementState.Disabled);
});
});
});
test('test disable extension fails if its dependency is a dependent of other', () => {
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.a' }, EnablementState.Enabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.b' }, EnablementState.Enabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.c' }, EnablementState.Enabled);
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a', { extensionDependencies: ['pub.b'] }), aLocalExtension('b'), aLocalExtension('c', { extensionDependencies: ['pub.b'] })]);
instantiationService.stubPromise(IChoiceService, 'choose', 1);
const extensionA = aLocalExtension('a', { extensionDependencies: ['pub.b'] });
const extensionB = aLocalExtension('b');
const extensionC = aLocalExtension('c', { extensionDependencies: ['pub.b'] });
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
return testObject.setEnablement(testObject.local[0], EnablementState.Disabled).then(() => assert.fail('Should fail'), error => assert.ok(true));
return instantiationService.get(IExtensionEnablementService).setEnablement(extensionA, EnablementState.Enabled)
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(extensionB, EnablementState.Enabled))
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(extensionC, EnablementState.Enabled))
.then(() => {
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [extensionA, extensionB, extensionC]);
instantiationService.stubPromise(IChoiceService, 'choose', 1);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
return testObject.setEnablement(testObject.local[0], EnablementState.Disabled).then(() => assert.fail('Should fail'), error => assert.ok(true));
});
});
test('test disable extension if its dependency is a dependent of other disabled extension', () => {
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.a' }, EnablementState.Enabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.b' }, EnablementState.Enabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.c' }, EnablementState.Disabled);
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a', { extensionDependencies: ['pub.b'] }), aLocalExtension('b'), aLocalExtension('c', { extensionDependencies: ['pub.b'] })]);
instantiationService.stubPromise(IChoiceService, 'choose', 1);
const extensionA = aLocalExtension('a', { extensionDependencies: ['pub.b'] });
const extensionB = aLocalExtension('b');
const extensionC = aLocalExtension('c', { extensionDependencies: ['pub.b'] });
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
return instantiationService.get(IExtensionEnablementService).setEnablement(extensionA, EnablementState.Enabled)
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(extensionB, EnablementState.Enabled))
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(extensionC, EnablementState.Disabled))
.then(() => {
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [extensionA, extensionB, extensionC]);
instantiationService.stubPromise(IChoiceService, 'choose', 1);
testObject.setEnablement(testObject.local[0], EnablementState.Disabled);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
assert.equal(testObject.local[0].enablementState, EnablementState.Disabled);
assert.equal(testObject.local[1].enablementState, EnablementState.Disabled);
return testObject.setEnablement(testObject.local[0], EnablementState.Disabled)
.then(() => {
assert.equal(testObject.local[0].enablementState, EnablementState.Disabled);
assert.equal(testObject.local[1].enablementState, EnablementState.Disabled);
});
});
});
test('test disable extension if its dependencys dependency is itself', () => {
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.a' }, EnablementState.Enabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.b' }, EnablementState.Enabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.c' }, EnablementState.Enabled);
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a', { extensionDependencies: ['pub.b'] }), aLocalExtension('b', { extensionDependencies: ['pub.a'] }), aLocalExtension('c')]);
instantiationService.stubPromise(IChoiceService, 'choose', 1);
const extensionA = aLocalExtension('a', { extensionDependencies: ['pub.b'] });
const extensionB = aLocalExtension('b', { extensionDependencies: ['pub.a'] });
const extensionC = aLocalExtension('c');
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
return instantiationService.get(IExtensionEnablementService).setEnablement(extensionA, EnablementState.Enabled)
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(extensionB, EnablementState.Enabled))
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(extensionC, EnablementState.Enabled))
.then(() => {
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [extensionA, extensionB, extensionC]);
instantiationService.stubPromise(IChoiceService, 'choose', 1);
testObject.setEnablement(testObject.local[0], EnablementState.Disabled);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
assert.equal(testObject.local[0].enablementState, EnablementState.Disabled);
assert.equal(testObject.local[1].enablementState, EnablementState.Disabled);
return testObject.setEnablement(testObject.local[0], EnablementState.Disabled)
.then(() => {
assert.equal(testObject.local[0].enablementState, EnablementState.Disabled);
assert.equal(testObject.local[1].enablementState, EnablementState.Disabled);
});
});
});
test('test disable extension if its dependency is dependent and is disabled', () => {
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.a' }, EnablementState.Enabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.b' }, EnablementState.Disabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.c' }, EnablementState.Enabled);
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a', { extensionDependencies: ['pub.b'] }), aLocalExtension('b'), aLocalExtension('c', { extensionDependencies: ['pub.b'] })]);
instantiationService.stubPromise(IChoiceService, 'choose', 1);
const extensionA = aLocalExtension('a', { extensionDependencies: ['pub.b'] });
const extensionB = aLocalExtension('b');
const extensionC = aLocalExtension('c', { extensionDependencies: ['pub.b'] });
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
return instantiationService.get(IExtensionEnablementService).setEnablement(extensionA, EnablementState.Enabled)
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(extensionB, EnablementState.Disabled))
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(extensionC, EnablementState.Enabled))
.then(() => {
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [extensionA, extensionB, extensionC]);
instantiationService.stubPromise(IChoiceService, 'choose', 1);
testObject.setEnablement(testObject.local[0], EnablementState.Disabled);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
assert.equal(testObject.local[0].enablementState, EnablementState.Disabled);
return testObject.setEnablement(testObject.local[0], EnablementState.Disabled)
.then(() => assert.equal(testObject.local[0].enablementState, EnablementState.Disabled));
});
});
test('test disable extension with cyclic dependencies', () => {
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.a' }, EnablementState.Enabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.b' }, EnablementState.Enabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.c' }, EnablementState.Enabled);
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a', { extensionDependencies: ['pub.b'] }), aLocalExtension('b', { extensionDependencies: ['pub.c'] }), aLocalExtension('c', { extensionDependencies: ['pub.a'] })]);
instantiationService.stubPromise(IChoiceService, 'choose', 1);
const extensionA = aLocalExtension('a', { extensionDependencies: ['pub.b'] });
const extensionB = aLocalExtension('b', { extensionDependencies: ['pub.c'] });
const extensionC = aLocalExtension('c', { extensionDependencies: ['pub.a'] });
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
return instantiationService.get(IExtensionEnablementService).setEnablement(extensionA, EnablementState.Enabled)
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(extensionB, EnablementState.Enabled))
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(extensionC, EnablementState.Enabled))
.then(() => {
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [extensionA, extensionB, extensionC]);
instantiationService.stubPromise(IChoiceService, 'choose', 1);
testObject.setEnablement(testObject.local[0], EnablementState.Disabled);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
assert.equal(testObject.local[0].enablementState, EnablementState.Disabled);
assert.equal(testObject.local[1].enablementState, EnablementState.Disabled);
assert.equal(testObject.local[1].enablementState, EnablementState.Disabled);
return testObject.setEnablement(testObject.local[0], EnablementState.Disabled)
.then(() => {
assert.equal(testObject.local[0].enablementState, EnablementState.Disabled);
assert.equal(testObject.local[1].enablementState, EnablementState.Disabled);
assert.equal(testObject.local[1].enablementState, EnablementState.Disabled);
});
});
});
test('test enable extension with dependencies enable all', () => {
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.a' }, EnablementState.Disabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.b' }, EnablementState.Disabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.c' }, EnablementState.Disabled);
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a', { extensionDependencies: ['pub.b'] }), aLocalExtension('b'), aLocalExtension('c')]);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
const extensionA = aLocalExtension('a', { extensionDependencies: ['pub.b'] });
const extensionB = aLocalExtension('b');
const extensionC = aLocalExtension('c');
testObject.setEnablement(testObject.local[0], EnablementState.Enabled);
return instantiationService.get(IExtensionEnablementService).setEnablement(extensionA, EnablementState.Disabled)
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(extensionB, EnablementState.Disabled))
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(extensionC, EnablementState.Disabled))
.then(() => {
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [extensionA, extensionB, extensionC]);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
assert.equal(testObject.local[0].enablementState, EnablementState.Enabled);
assert.equal(testObject.local[1].enablementState, EnablementState.Enabled);
return testObject.setEnablement(testObject.local[0], EnablementState.Enabled)
.then(() => {
assert.equal(testObject.local[0].enablementState, EnablementState.Enabled);
assert.equal(testObject.local[1].enablementState, EnablementState.Enabled);
});
});
});
test('test enable extension with cyclic dependencies', () => {
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.a' }, EnablementState.Disabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.b' }, EnablementState.Disabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.c' }, EnablementState.Disabled);
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a', { extensionDependencies: ['pub.b'] }), aLocalExtension('b', { extensionDependencies: ['pub.c'] }), aLocalExtension('c', { extensionDependencies: ['pub.a'] })]);
const extensionA = aLocalExtension('a', { extensionDependencies: ['pub.b'] });
const extensionB = aLocalExtension('b', { extensionDependencies: ['pub.c'] });
const extensionC = aLocalExtension('c', { extensionDependencies: ['pub.a'] });
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
return instantiationService.get(IExtensionEnablementService).setEnablement(extensionA, EnablementState.Disabled)
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(extensionB, EnablementState.Disabled))
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(extensionC, EnablementState.Disabled))
.then(() => {
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [extensionA, extensionB, extensionC]);
testObject.setEnablement(testObject.local[0], EnablementState.Enabled);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
assert.equal(testObject.local[0].enablementState, EnablementState.Enabled);
assert.equal(testObject.local[1].enablementState, EnablementState.Enabled);
assert.equal(testObject.local[2].enablementState, EnablementState.Enabled);
return testObject.setEnablement(testObject.local[0], EnablementState.Enabled)
.then(() => {
assert.equal(testObject.local[0].enablementState, EnablementState.Enabled);
assert.equal(testObject.local[1].enablementState, EnablementState.Enabled);
assert.equal(testObject.local[2].enablementState, EnablementState.Enabled);
});
});
});
test('test change event is fired when disablement flags are changed', () => {
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.c' }, EnablementState.Disabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.b' }, EnablementState.WorkspaceDisabled);
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a')]);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
const target = sinon.spy();
testObject.onChange(target);
return instantiationService.get(IExtensionEnablementService).setEnablement(aLocalExtension('c'), EnablementState.Disabled)
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(aLocalExtension('b'), EnablementState.WorkspaceDisabled))
.then(() => {
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a')]);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
const target = sinon.spy();
testObject.onChange(target);
testObject.setEnablement(testObject.local[0], EnablementState.Disabled);
assert.ok(target.calledOnce);
return testObject.setEnablement(testObject.local[0], EnablementState.Disabled)
.then(() => assert.ok(target.calledOnce));
});
});
test('test change event is fired when disablement flags are changed from outside', () => {
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.c' }, EnablementState.Disabled);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.b' }, EnablementState.WorkspaceDisabled);
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a')]);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
const target = sinon.spy();
testObject.onChange(target);
const localExtension = aLocalExtension('a');
return instantiationService.get(IExtensionEnablementService).setEnablement(aLocalExtension('c'), EnablementState.Disabled)
.then(() => instantiationService.get(IExtensionEnablementService).setEnablement(aLocalExtension('b'), EnablementState.WorkspaceDisabled))
.then(() => {
instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [localExtension]);
testObject = instantiationService.createInstance(ExtensionsWorkbenchService);
const target = sinon.spy();
testObject.onChange(target);
instantiationService.get(IExtensionEnablementService).setEnablement({ id: 'pub.a' }, EnablementState.Disabled);
assert.ok(target.calledOnce);
return instantiationService.get(IExtensionEnablementService).setEnablement(localExtension, EnablementState.Disabled)
.then(() => assert.ok(target.calledOnce));
});
});
function aLocalExtension(name: string = 'someext', manifest: any = {}, properties: any = {}): ILocalExtension {

View File

@@ -6,11 +6,30 @@
import { Registry } from 'vs/platform/registry/common/platform';
import { StatusbarAlignment, IStatusbarRegistry, Extensions, StatusbarItemDescriptor } from 'vs/workbench/browser/parts/statusbar/statusbar';
import { FeedbackStatusbarItem } from './feedbackStatusbarItem';
import { FeedbackStatusbarItem } from 'vs/workbench/parts/feedback/electron-browser/feedbackStatusbarItem';
import { localize } from 'vs/nls';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
// Register Statusbar item
Registry.as<IStatusbarRegistry>(Extensions.Statusbar).registerStatusbarItem(new StatusbarItemDescriptor(
FeedbackStatusbarItem,
StatusbarAlignment.RIGHT,
-100 /* Low Priority */
));
-100 /* towards the end of the right hand side */
));
// Configuration: Workbench
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
configurationRegistry.registerConfiguration({
'id': 'workbench',
'order': 7,
'title': localize('workbenchConfigurationTitle', "Workbench"),
'type': 'object',
'properties': {
'workbench.statusBar.feedback.visible': {
'type': 'boolean',
'default': true,
'description': localize('feedbackVisibility', "Controls the visibility of the Twitter feedback (smiley) in the status bar at the bottom of the workbench.")
}
}
});

View File

@@ -11,7 +11,6 @@ import { IDisposable } from 'vs/base/common/lifecycle';
import { Builder, $ } from 'vs/base/browser/builder';
import { Dropdown } from 'vs/base/browser/ui/dropdown/dropdown';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import product from 'vs/platform/node/product';
import * as dom from 'vs/base/browser/dom';
import { ICommandService } from 'vs/platform/commands/common/commands';
@@ -20,6 +19,10 @@ import { IIntegrityService } from 'vs/platform/integrity/common/integrity';
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
import { attachStylerCallback } from 'vs/platform/theme/common/styler';
import { editorWidgetBackground, widgetShadow, inputBorder, inputForeground, inputBackground, inputActiveOptionBorder, editorBackground, buttonBackground, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry';
export const FEEDBACK_VISIBLE_CONFIG = 'workbench.statusBar.feedback.visible';
export interface IFeedback {
feedback: string;
@@ -43,35 +46,35 @@ enum FormEvent {
}
export class FeedbackDropdown extends Dropdown {
protected maxFeedbackCharacters: number;
private maxFeedbackCharacters: number;
protected feedback: string;
protected sentiment: number;
protected aliasEnabled: boolean;
protected isSendingFeedback: boolean;
protected autoHideTimeout: number;
private feedback: string;
private sentiment: number;
private isSendingFeedback: boolean;
private autoHideTimeout: number;
protected feedbackService: IFeedbackService;
private feedbackService: IFeedbackService;
protected feedbackForm: HTMLFormElement;
protected feedbackDescriptionInput: HTMLTextAreaElement;
protected smileyInput: Builder;
protected frownyInput: Builder;
protected sendButton: Builder;
protected remainingCharacterCount: Builder;
private feedbackForm: HTMLFormElement;
private feedbackDescriptionInput: HTMLTextAreaElement;
private smileyInput: Builder;
private frownyInput: Builder;
private sendButton: Builder;
private hideButton: HTMLInputElement;
private remainingCharacterCount: Builder;
protected requestFeatureLink: string;
protected reportIssueLink: string;
private requestFeatureLink: string;
private _isPure: boolean;
constructor(
container: HTMLElement,
options: IFeedbackDropdownOptions,
@ITelemetryService protected telemetryService: ITelemetryService,
@ICommandService private commandService: ICommandService,
@IIntegrityService protected integrityService: IIntegrityService,
@IThemeService private themeService: IThemeService
@ITelemetryService private telemetryService: ITelemetryService,
@IIntegrityService private integrityService: IIntegrityService,
@IThemeService private themeService: IThemeService,
@IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService
) {
super(container, {
contextViewProvider: options.contextViewProvider,
@@ -106,14 +109,12 @@ export class FeedbackDropdown extends Dropdown {
this.sendButton = null;
this.reportIssueLink = product.reportIssueUrl;
this.requestFeatureLink = product.requestFeatureUrl;
this.requestFeatureLink = product.sendASmile.requestFeatureUrl;
}
protected renderContents(container: HTMLElement): IDisposable {
const $form = $('form.feedback-form').attr({
action: 'javascript:void(0);',
tabIndex: '-1'
action: 'javascript:void(0);'
}).appendTo(container);
$(container).addClass('monaco-menu-container');
@@ -171,7 +172,16 @@ export class FeedbackDropdown extends Dropdown {
$('div').append($('a').attr('target', '_blank').attr('href', '#').text(nls.localize("submit a bug", "Submit a bug")).attr('tabindex', '0'))
.on('click', event => {
dom.EventHelper.stop(event);
this.commandService.executeCommand('workbench.action.reportIssues').done(null, errors.onUnexpectedError);
const actionId = 'workbench.action.openIssueReporter';
this.commandService.executeCommand(actionId).done(null, errors.onUnexpectedError);
/* __GDPR__
"workbenchActionExecuted" : {
"id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
"from": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this.telemetryService.publicLog('workbenchActionExecuted', { id: actionId, from: 'feedback' });
})
.appendTo($contactUsContainer);
@@ -197,16 +207,23 @@ export class FeedbackDropdown extends Dropdown {
const $buttons = $('div.form-buttons').appendTo($form);
const $hideButtonContainer = $('div.hide-button-container').appendTo($buttons);
this.hideButton = $('input.hide-button').type('checkbox').attr('checked', '').id('hide-button').appendTo($hideButtonContainer).getHTMLElement() as HTMLInputElement;
$('label').attr('for', 'hide-button').text(nls.localize('showFeedback', "Show Feedback Smiley in Status Bar")).appendTo($hideButtonContainer);
this.sendButton = this.invoke($('input.send').type('submit').attr('disabled', '').value(nls.localize('tweet', "Tweet")).appendTo($buttons), () => {
if (this.isSendingFeedback) {
return;
}
this.onSubmit();
});
this.toDispose.push(attachStylerCallback(this.themeService, { widgetShadow, editorWidgetBackground, inputBackground, inputForeground, inputBorder, editorBackground, contrastBorder }, colors => {
$form.style('background-color', colors.editorWidgetBackground);
$form.style('box-shadow', colors.widgetShadow ? `0 2px 8px ${colors.widgetShadow}` : null);
$form.style('box-shadow', colors.widgetShadow ? `0 5px 8px ${colors.widgetShadow}` : null);
if (this.feedbackDescriptionInput) {
this.feedbackDescriptionInput.style.backgroundColor = colors.inputBackground;
@@ -242,7 +259,7 @@ export class FeedbackDropdown extends Dropdown {
this.feedbackDescriptionInput.value ? this.sendButton.removeAttribute('disabled') : this.sendButton.attr('disabled', '');
}
protected setSentiment(smile: boolean): void {
private setSentiment(smile: boolean): void {
if (smile) {
this.smileyInput.addClass('checked');
this.smileyInput.attr('aria-checked', 'true');
@@ -254,14 +271,16 @@ export class FeedbackDropdown extends Dropdown {
this.smileyInput.removeClass('checked');
this.smileyInput.attr('aria-checked', 'false');
}
this.sentiment = smile ? 1 : 0;
this.maxFeedbackCharacters = this.feedbackService.getCharacterLimit(this.sentiment);
this.updateCharCountText();
$(this.feedbackDescriptionInput).attr({ maxlength: this.maxFeedbackCharacters });
}
protected invoke(element: Builder, callback: () => void): Builder {
private invoke(element: Builder, callback: () => void): Builder {
element.on('click', callback);
element.on('keypress', (e) => {
if (e instanceof KeyboardEvent) {
const keyboardEvent = <KeyboardEvent>e;
@@ -270,6 +289,7 @@ export class FeedbackDropdown extends Dropdown {
}
}
});
return element;
}
@@ -283,6 +303,10 @@ export class FeedbackDropdown extends Dropdown {
this.autoHideTimeout = null;
}
if (this.hideButton && !this.hideButton.checked) {
this.configurationService.updateValue(FEEDBACK_VISIBLE_CONFIG, false).done(null, errors.onUnexpectedError);
}
super.hide();
}
@@ -295,7 +319,7 @@ export class FeedbackDropdown extends Dropdown {
}
}
protected onSubmit(): void {
private onSubmit(): void {
if ((this.feedbackForm.checkValidity && !this.feedbackForm.checkValidity())) {
return;
}
@@ -338,13 +362,13 @@ export class FeedbackDropdown extends Dropdown {
}
}
protected resetForm(): void {
private resetForm(): void {
if (this.feedbackDescriptionInput) {
this.feedbackDescriptionInput.value = '';
}
this.sentiment = 1;
this.maxFeedbackCharacters = this.feedbackService.getCharacterLimit(this.sentiment);
this.aliasEnabled = false;
}
}

View File

@@ -5,15 +5,22 @@
'use strict';
import { IDisposable } from 'vs/base/common/lifecycle';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
import { FeedbackDropdown, IFeedback, IFeedbackService } from './feedback';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { FeedbackDropdown, IFeedback, IFeedbackService, FEEDBACK_VISIBLE_CONFIG } from 'vs/workbench/parts/feedback/electron-browser/feedback';
import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import product from 'vs/platform/node/product';
import { Themable, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_FOREGROUND } from 'vs/workbench/common/theme';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { Themable, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_ITEM_HOVER_BACKGROUND } from 'vs/workbench/common/theme';
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
import { clearNode, EventHelper } from 'vs/base/browser/dom';
import { $ } from 'vs/base/browser/builder';
import { localize } from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import { Action } from 'vs/base/common/actions';
class TwitterFeedbackService implements IFeedbackService {
@@ -50,20 +57,38 @@ class TwitterFeedbackService implements IFeedbackService {
export class FeedbackStatusbarItem extends Themable implements IStatusbarItem {
private dropdown: FeedbackDropdown;
private enabled: boolean;
private container: HTMLElement;
private hideAction: HideAction;
constructor(
@IInstantiationService private instantiationService: IInstantiationService,
@IContextViewService private contextViewService: IContextViewService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IContextMenuService private contextMenuService: IContextMenuService,
@IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService,
@IThemeService themeService: IThemeService
) {
super(themeService);
this.enabled = this.configurationService.getValue(FEEDBACK_VISIBLE_CONFIG);
this.hideAction = this.instantiationService.createInstance(HideAction);
this.toUnbind.push(this.hideAction);
this.registerListeners();
}
private registerListeners(): void {
this.toUnbind.push(this.contextService.onDidChangeWorkbenchState(() => this.updateStyles()));
this.toUnbind.push(this.configurationService.onDidChangeConfiguration(e => this.onConfigurationUpdated(e)));
}
private onConfigurationUpdated(event: IConfigurationChangeEvent): void {
if (event.affectsConfiguration(FEEDBACK_VISIBLE_CONFIG)) {
this.enabled = this.configurationService.getValue(FEEDBACK_VISIBLE_CONFIG);
this.update();
}
}
protected updateStyles(): void {
@@ -75,17 +100,73 @@ export class FeedbackStatusbarItem extends Themable implements IStatusbarItem {
}
public render(element: HTMLElement): IDisposable {
if (product.sendASmile) {
this.dropdown = this.instantiationService.createInstance(FeedbackDropdown, element, {
contextViewProvider: this.contextViewService,
feedbackService: this.instantiationService.createInstance(TwitterFeedbackService)
this.container = element;
// Prevent showing dropdown on anything but left click
$(this.container).on('mousedown', (e: MouseEvent) => {
if (e.button !== 0) {
EventHelper.stop(e, true);
}
}, this.toUnbind, true);
// Offer context menu to hide status bar entry
$(this.container).on('contextmenu', e => {
EventHelper.stop(e, true);
this.contextMenuService.showContextMenu({
getAnchor: () => this.container,
getActions: () => TPromise.as([this.hideAction])
});
}, this.toUnbind);
this.updateStyles();
return this.update();
}
return this.dropdown;
private update(): IDisposable {
const enabled = product.sendASmile && this.enabled;
// Create
if (enabled) {
if (!this.dropdown) {
this.dropdown = this.instantiationService.createInstance(FeedbackDropdown, this.container, {
contextViewProvider: this.contextViewService,
feedbackService: this.instantiationService.createInstance(TwitterFeedbackService)
});
this.toUnbind.push(this.dropdown);
this.updateStyles();
return this.dropdown;
}
}
// Dispose
else {
dispose(this.dropdown);
this.dropdown = void 0;
clearNode(this.container);
}
return null;
}
}
class HideAction extends Action {
constructor(
@IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService
) {
super('feedback.hide', localize('hide', "Hide"));
}
public run(extensionId: string): TPromise<any> {
return this.configurationService.updateValue(FEEDBACK_VISIBLE_CONFIG, false);
}
}
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
const statusBarItemHoverBackground = theme.getColor(STATUS_BAR_ITEM_HOVER_BACKGROUND);
if (statusBarItemHoverBackground) {
collector.addRule(`.monaco-workbench > .part.statusbar > .statusbar-item .dropdown.send-feedback:hover { background-color: ${statusBarItemHoverBackground}; }`);
}
});

View File

@@ -88,7 +88,6 @@
position: absolute;
top: 0;
right: 0;
margin: .5em 0 0 0;
padding: .5em;
width: 22px;
height: 22px;
@@ -156,6 +155,19 @@
background-color: #eaeaea;
}
.monaco-shell .feedback-form .form-buttons {
display: flex;
}
.monaco-shell .feedback-form .form-buttons .hide-button-container {
display: flex;
}
.monaco-shell .feedback-form .form-buttons .hide-button-container input,
.monaco-shell .feedback-form .form-buttons .hide-button-container label {
align-self: center;
}
.monaco-shell .feedback-form .form-buttons .send {
color: white;
border: none;
@@ -169,6 +181,7 @@
padding-right: 12px;
border: 4px solid #007ACC;
border-radius: 4px;
margin-left: auto;
}
.monaco-shell .feedback-form .form-buttons .send.in-progress,

View File

@@ -142,8 +142,7 @@ export class FileEditorTracker implements IWorkbenchContribution {
// We have received reports of users seeing delete events even though the file still
// exists (network shares issue: https://github.com/Microsoft/vscode/issues/13665).
// Since we do not want to close an editor without reason, we have to check if the
// file is really gone and not just a faulty file event (TODO@Ben revisit when we
// have a more stable file watcher in place for this scenario).
// file is really gone and not just a faulty file event.
// This only applies to external file events, so we need to check for the isExternal
// flag.
let checkExists: TPromise<boolean>;

View File

@@ -24,7 +24,6 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
import { IStorageService } from 'vs/platform/storage/common/storage';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { CancelAction } from 'vs/platform/message/common/message';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
@@ -62,7 +61,7 @@ export class TextFileEditor extends BaseTextEditor {
private onFilesChanged(e: FileChangesEvent): void {
const deleted = e.getDeleted();
if (deleted && deleted.length) {
this.clearTextEditorViewState(deleted.map(d => d.resource.toString()));
this.clearTextEditorViewState(deleted.map(d => d.resource));
}
}
@@ -127,7 +126,7 @@ export class TextFileEditor extends BaseTextEditor {
textEditor.setModel(textFileModel.textEditorModel);
// Always restore View State if any associated
const editorViewState = this.loadTextEditorViewState(this.input.getResource().toString());
const editorViewState = this.loadTextEditorViewState(this.input.getResource());
if (editorViewState) {
textEditor.restoreViewState(editorViewState);
}
@@ -155,18 +154,13 @@ export class TextFileEditor extends BaseTextEditor {
return TPromise.wrapError<void>(errors.create(toErrorMessage(error), {
actions: [
new Action('workbench.files.action.createMissingFile', nls.localize('createFile', "Create File"), null, true, () => {
return this.fileService.updateContent(input.getResource(), '').then(() => {
// Open
return this.editorService.openEditor({
resource: input.getResource(),
options: {
pinned: true // new file gets pinned by default
}
});
});
}),
CancelAction
return this.fileService.updateContent(input.getResource(), '').then(() => this.editorService.openEditor({
resource: input.getResource(),
options: {
pinned: true // new file gets pinned by default
}
}));
})
]
}));
}
@@ -235,7 +229,7 @@ export class TextFileEditor extends BaseTextEditor {
private doSaveTextEditorViewState(input: FileEditorInput): void {
if (input && !input.isDisposed()) {
this.saveTextEditorViewState(input.getResource().toString());
this.saveTextEditorViewState(input.getResource());
}
}
}

View File

@@ -1,69 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from 'vs/base/common/lifecycle';
import Event, { Emitter } from 'vs/base/common/event';
import { IEditorOptions } from 'vs/platform/editor/common/editor';
import { ITree } from 'vs/base/parts/tree/browser/tree';
export interface IOpenFileOptions {
editorOptions: IEditorOptions;
sideBySide: boolean;
element: any;
payload: any;
}
export default class FileResultsNavigation extends Disposable {
private _openFile: Emitter<IOpenFileOptions> = new Emitter<IOpenFileOptions>();
public readonly openFile: Event<IOpenFileOptions> = this._openFile.event;
constructor(private tree: ITree) {
super();
this._register(this.tree.onDidChangeFocus(e => this.onFocus(e)));
this._register(this.tree.onDidChangeSelection(e => this.onSelection(e)));
}
private onFocus(event: any): void {
const element = this.tree.getFocus();
this.tree.setSelection([element], { fromFocus: true });
this._openFile.fire({
editorOptions: {
preserveFocus: true,
pinned: false,
revealIfVisible: true
},
sideBySide: false,
element,
payload: event.payload
});
}
private onSelection({ payload }: any): void {
if (payload && payload.fromFocus) {
return;
}
let keyboard = payload && payload.origin === 'keyboard';
let originalEvent: KeyboardEvent | MouseEvent = payload && payload.originalEvent;
let pinned = (payload && payload.origin === 'mouse' && originalEvent && originalEvent.detail === 2);
if (pinned && originalEvent) {
originalEvent.preventDefault(); // focus moves to editor, we need to prevent default
}
let sideBySide = (originalEvent && (originalEvent.ctrlKey || originalEvent.metaKey));
let preserveFocus = !((keyboard && (!payload || !payload.preserveFocus)) || pinned || (payload && payload.focusEditor));
this._openFile.fire({
editorOptions: {
preserveFocus,
pinned,
revealIfVisible: !sideBySide
},
sideBySide,
element: this.tree.getSelection()[0],
payload
});
}
}

View File

@@ -0,0 +1,68 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import URI from 'vs/base/common/uri';
import { IListService } from 'vs/platform/list/browser/listService';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { FileStat, OpenEditor } from 'vs/workbench/parts/files/common/explorerModel';
import { toResource } from 'vs/workbench/common/editor';
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
import { List } from 'vs/base/browser/ui/list/listWidget';
import { IFileStat } from 'vs/platform/files/common/files';
// Commands can get exeucted from a command pallete, from a context menu or from some list using a keybinding
// To cover all these cases we need to properly compute the resource on which the command is being executed
export function getResourceForCommand(resource: URI, listService: IListService, editorService: IWorkbenchEditorService): URI {
if (URI.isUri(resource)) {
return resource;
}
const list = listService.lastFocusedList;
if (list && list.isDOMFocused()) {
const focus = list.getFocus();
if (focus instanceof FileStat) {
return focus.resource;
} else if (focus instanceof OpenEditor) {
return focus.getResource();
}
}
return toResource(editorService.getActiveEditorInput(), { supportSideBySide: true });
}
export function getMultiSelectedResources(resource: URI, listService: IListService, editorService: IWorkbenchEditorService): URI[] {
const list = listService.lastFocusedList;
if (list && list.isDOMFocused()) {
// Explorer
if (list instanceof Tree) {
const focus: IFileStat = list.getFocus();
// If the resource is passed it has to be a part of the returned context.
if (focus && (!URI.isUri(resource) || focus.resource.toString() === resource.toString())) {
const selection = list.getSelection();
// We only respect the selection if it contains the focused element.
if (selection && selection.indexOf(focus) >= 0) {
return selection.map(fs => fs.resource);
}
}
}
// Open editors view
if (list instanceof List) {
const focus = list.getFocusedElements();
// If the resource is passed it has to be a part of the returned context.
if (focus.length && (!URI.isUri(resource) || (focus[0] instanceof OpenEditor && focus[0].getResource().toString() === resource.toString()))) {
const selection = list.getSelectedElements();
// We only respect the selection if it contains the focused element.
if (selection && selection.indexOf(focus[0]) >= 0) {
return selection.filter(s => s instanceof OpenEditor).map((oe: OpenEditor) => oe.getResource());
}
}
}
}
const result = getResourceForCommand(resource, listService, editorService);
return !!result ? [result] : [];
}

View File

@@ -21,7 +21,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IDisposable, dispose, IReference } from 'vs/base/common/lifecycle';
import { telemetryURIDescriptor } from 'vs/platform/telemetry/common/telemetryUtils';
import { Verbosity } from 'vs/platform/editor/common/editor';
import { Verbosity, IRevertOptions } from 'vs/platform/editor/common/editor';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { IHashService } from 'vs/workbench/services/hash/common/hashService';
@@ -214,7 +214,7 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput {
return model.isDirty();
}
public confirmSave(): ConfirmResult {
public confirmSave(): TPromise<ConfirmResult> {
return this.textFileService.confirmSave([this.resource]);
}
@@ -222,8 +222,8 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput {
return this.textFileService.save(this.resource);
}
public revert(): TPromise<boolean> {
return this.textFileService.revert(this.resource);
public revert(options?: IRevertOptions): TPromise<boolean> {
return this.textFileService.revert(this.resource, options);
}
public getPreferredEditorId(candidates: string[]): string {
@@ -240,7 +240,7 @@ export class FileEditorInput extends EditorInput implements IFileEditorInput {
// Resolve as text
return this.textFileService.models.loadOrCreate(this.resource, { encoding: this.preferredEncoding, reload: refresh }).then(model => {
// TODO@Ben this is a bit ugly, because we first resolve the model and then resolve a model reference. the reason being that binary
// This is a bit ugly, because we first resolve the model and then resolve a model reference. the reason being that binary
// or very large files do not resolve to a text file model but should be opened as binary files without text. First calling into
// loadOrCreate ensures we are not creating model references for these kind of resources.
// In addition we have a bit of payload to take into account (encoding, reload) that the text resolver does not handle yet.

View File

@@ -7,14 +7,16 @@
import URI from 'vs/base/common/uri';
import paths = require('vs/base/common/paths');
import resources = require('vs/base/common/resources');
import { ResourceMap } from 'vs/base/common/map';
import { isLinux } from 'vs/base/common/platform';
import { IFileStat, isParent } from 'vs/platform/files/common/files';
import { IFileStat } from 'vs/platform/files/common/files';
import { IEditorInput } from 'vs/platform/editor/common/editor';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IEditorGroup, toResource } from 'vs/workbench/common/editor';
import { IEditorGroup, toResource, IEditorIdentifier } from 'vs/workbench/common/editor';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { getPathLabel } from 'vs/base/common/labels';
import { Schemas } from 'vs/base/common/network';
export class Model {
@@ -73,17 +75,17 @@ export class FileStat implements IFileStat {
public mtime: number;
public etag: string;
private _isDirectory: boolean;
public hasChildren: boolean;
private _isSymbolicLink: boolean;
public children: FileStat[];
public parent: FileStat;
public isDirectoryResolved: boolean;
constructor(resource: URI, public root: FileStat, isDirectory?: boolean, hasChildren?: boolean, name: string = getPathLabel(resource), mtime?: number, etag?: string) {
constructor(resource: URI, public root: FileStat, isSymbolicLink?: boolean, isDirectory?: boolean, name: string = getPathLabel(resource), mtime?: number, etag?: string) {
this.resource = resource;
this.name = name;
this.isDirectory = !!isDirectory;
this.hasChildren = isDirectory && hasChildren;
this._isSymbolicLink = !!isSymbolicLink;
this.etag = etag;
this.mtime = mtime;
@@ -94,6 +96,10 @@ export class FileStat implements IFileStat {
this.isDirectoryResolved = false;
}
public get isSymbolicLink(): boolean {
return this._isSymbolicLink;
}
public get isDirectory(): boolean {
return this._isDirectory;
}
@@ -123,7 +129,7 @@ export class FileStat implements IFileStat {
}
public static create(raw: IFileStat, root: FileStat, resolveTo?: URI[]): FileStat {
const stat = new FileStat(raw.resource, root, raw.isDirectory, raw.hasChildren, raw.name, raw.mtime, raw.etag);
const stat = new FileStat(raw.resource, root, raw.isSymbolicLink, raw.isDirectory, raw.name, raw.mtime, raw.etag);
// Recursively add children if present
if (stat.isDirectory) {
@@ -132,7 +138,7 @@ export class FileStat implements IFileStat {
// the folder is fully resolved if either it has a list of children or the client requested this by using the resolveTo
// array of resource path to resolve.
stat.isDirectoryResolved = !!raw.children || (!!resolveTo && resolveTo.some((r) => {
return paths.isEqualOrParent(r.fsPath, stat.resource.fsPath, !isLinux /* ignorecase */);
return resources.isEqualOrParent(r, stat.resource, !isLinux /* ignorecase */);
}));
// Recurse into children
@@ -141,7 +147,6 @@ export class FileStat implements IFileStat {
const child = FileStat.create(raw.children[i], root, resolveTo);
child.parent = stat;
stat.children.push(child);
stat.hasChildren = stat.children.length > 0;
}
}
}
@@ -169,7 +174,6 @@ export class FileStat implements IFileStat {
local.resource = disk.resource;
local.name = disk.name;
local.isDirectory = disk.isDirectory;
local.hasChildren = disk.isDirectory && disk.hasChildren;
local.mtime = disk.mtime;
local.isDirectoryResolved = disk.isDirectoryResolved;
@@ -217,7 +221,6 @@ export class FileStat implements IFileStat {
child.updateResource(false);
this.children.push(child);
this.hasChildren = this.children.length > 0;
}
/**
@@ -230,8 +233,6 @@ export class FileStat implements IFileStat {
break;
}
}
this.hasChildren = this.children.length > 0;
}
/**
@@ -256,10 +257,9 @@ export class FileStat implements IFileStat {
private updateResource(recursive: boolean): void {
this.resource = this.parent.resource.with({ path: paths.join(this.parent.resource.path, this.name) });
// this.resource = URI.file(paths.join(this.parent.resource.fsPath, this.name));
if (recursive) {
if (this.isDirectory && this.hasChildren && this.children) {
if (this.isDirectory && this.children) {
this.children.forEach((child: FileStat) => {
child.updateResource(true);
});
@@ -288,23 +288,23 @@ export class FileStat implements IFileStat {
public find(resource: URI): FileStat {
// Return if path found
if (paths.isEqual(resource.fsPath, this.resource.fsPath, !isLinux /* ignorecase */)) {
if (resources.isEqual(resource, this.resource, !isLinux /* ignorecase */)) {
return this;
}
// Return if not having any children
if (!this.hasChildren) {
if (!this.children) {
return null;
}
for (let i = 0; i < this.children.length; i++) {
const child = this.children[i];
if (paths.isEqual(resource.fsPath, child.resource.fsPath, !isLinux /* ignorecase */)) {
if (resources.isEqual(resource, child.resource, !isLinux /* ignorecase */)) {
return child;
}
if (child.isDirectory && isParent(resource.fsPath, child.resource.fsPath, !isLinux /* ignorecase */)) {
if (child.isDirectory && resources.isEqualOrParent(resource, child.resource, !isLinux /* ignorecase */)) {
return child.find(resource);
}
}
@@ -335,7 +335,6 @@ export class NewStatPlaceholder extends FileStat {
this.isDirectoryResolved = void 0;
this.name = void 0;
this.isDirectory = void 0;
this.hasChildren = void 0;
this.mtime = void 0;
}
@@ -374,24 +373,26 @@ export class NewStatPlaceholder extends FileStat {
child.parent = parent;
parent.children.push(child);
parent.hasChildren = parent.children.length > 0;
return child;
}
}
export class OpenEditor {
export class OpenEditor implements IEditorIdentifier {
constructor(private editor: IEditorInput, private group: IEditorGroup) {
constructor(private _editor: IEditorInput, private _group: IEditorGroup) {
// noop
}
public get editorInput() {
return this.editor;
public get editor() {
return this._editor;
}
public get editorGroup() {
return this.group;
public get editorIndex() {
return this._group.indexOf(this.editor);
}
public get group() {
return this._group;
}
public getId(): string {
@@ -403,7 +404,7 @@ export class OpenEditor {
}
public isUntitled(): boolean {
return !!toResource(this.editor, { supportSideBySide: true, filter: 'untitled' });
return !!toResource(this.editor, { supportSideBySide: true, filter: Schemas.untitled });
}
public isDirty(): boolean {

View File

@@ -14,7 +14,7 @@ import { ITextModelContentProvider } from 'vs/editor/common/services/resolverSer
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { TPromise } from 'vs/base/common/winjs.base';
import { onUnexpectedError } from 'vs/base/common/errors';
import { IModel } from 'vs/editor/common/editorCommon';
import { ITextModel } from 'vs/editor/common/model';
import { IMode } from 'vs/editor/common/modes';
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModeService } from 'vs/editor/common/services/modeService';
@@ -44,9 +44,11 @@ const openEditorsVisibleId = 'openEditorsVisible';
const openEditorsFocusId = 'openEditorsFocus';
const explorerViewletFocusId = 'explorerViewletFocus';
const explorerResourceIsFolderId = 'explorerResourceIsFolder';
const explorerResourceIsRootId = 'explorerResourceIsRoot';
export const ExplorerViewletVisibleContext = new RawContextKey<boolean>(explorerViewletVisibleId, true);
export const ExplorerFolderContext = new RawContextKey<boolean>(explorerResourceIsFolderId, false);
export const ExplorerRootContext = new RawContextKey<boolean>(explorerResourceIsRootId, false);
export const FilesExplorerFocusedContext = new RawContextKey<boolean>(filesExplorerFocusId, true);
export const OpenEditorsVisibleContext = new RawContextKey<boolean>(openEditorsVisibleId, false);
export const OpenEditorsFocusedContext = new RawContextKey<boolean>(openEditorsFocusId, true);
@@ -75,7 +77,6 @@ export interface IFilesConfiguration extends IFilesConfiguration, IWorkbenchEdit
explorer: {
openEditors: {
visible: number;
dynamicHeight: boolean;
};
autoReveal: boolean;
enableDragAndDrop: boolean;
@@ -141,7 +142,7 @@ export class FileOnDiskContentProvider implements ITextModelContentProvider {
) {
}
public provideTextContent(resource: URI): TPromise<IModel> {
public provideTextContent(resource: URI): TPromise<ITextModel> {
const fileOnDiskResource = URI.file(resource.fsPath);
// Make sure our file from disk is resolved up to date
@@ -165,7 +166,7 @@ export class FileOnDiskContentProvider implements ITextModelContentProvider {
});
}
private resolveEditorModel(resource: URI, createAsNeeded = true): TPromise<IModel> {
private resolveEditorModel(resource: URI, createAsNeeded = true): TPromise<ITextModel> {
const fileOnDiskResource = URI.file(resource.fsPath);
return this.textFileService.resolveTextContent(fileOnDiskResource).then(content => {

Some files were not shown because too many files have changed in this diff Show More