SQL Operations Studio Public Preview 1 (0.23) release source code

This commit is contained in:
Karl Burtram
2017-11-09 14:30:27 -08:00
parent b88ecb8d93
commit 3cdac41339
8829 changed files with 759707 additions and 286 deletions

View File

@@ -0,0 +1,185 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!../browser/media/debug.contribution';
import 'vs/css!../browser/media/debugHover';
import * as nls from 'vs/nls';
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { Registry } from 'vs/platform/registry/common/platform';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { KeybindingsRegistry, IKeybindings } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
import { IWorkbenchActionRegistry, Extensions as WorkbenchActionRegistryExtensions } from 'vs/workbench/common/actionRegistry';
import { ToggleViewletAction, Extensions as ViewletExtensions, ViewletRegistry, ViewletDescriptor } from 'vs/workbench/browser/viewlet';
import { TogglePanelAction, Extensions as PanelExtensions, PanelRegistry, PanelDescriptor } from 'vs/workbench/browser/panel';
import { VariablesView, WatchExpressionsView, CallStackView, BreakpointsView } from 'vs/workbench/parts/debug/electron-browser/debugViews';
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
import { IDebugService, VIEWLET_ID, REPL_ID, CONTEXT_NOT_IN_DEBUG_MODE, CONTEXT_IN_DEBUG_MODE, INTERNAL_CONSOLE_OPTIONS_SCHEMA } from 'vs/workbench/parts/debug/common/debug';
import { IPartService } from 'vs/workbench/services/part/common/partService';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { DebugEditorModelManager } from 'vs/workbench/parts/debug/browser/debugEditorModelManager';
import {
StepOverAction, ClearReplAction, FocusReplAction, StepIntoAction, StepOutAction, StartAction, RestartAction, ContinueAction, StopAction, DisconnectAction, PauseAction, AddFunctionBreakpointAction,
ConfigureAction, DisableAllBreakpointsAction, EnableAllBreakpointsAction, RemoveAllBreakpointsAction, RunAction, ReapplyBreakpointsAction, SelectAndStartAction
} from 'vs/workbench/parts/debug/browser/debugActions';
import { DebugActionsWidget } from 'vs/workbench/parts/debug/browser/debugActionsWidget';
import * as service from 'vs/workbench/parts/debug/electron-browser/debugService';
import { DebugContentProvider } from 'vs/workbench/parts/debug/browser/debugContentProvider';
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 { 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/parts/views/browser/viewsRegistry';
class OpenDebugViewletAction extends ToggleViewletAction {
public static ID = VIEWLET_ID;
public static LABEL = nls.localize('toggleDebugViewlet', "Show Debug");
constructor(
id: string,
label: string,
@IViewletService viewletService: IViewletService,
@IWorkbenchEditorService editorService: IWorkbenchEditorService
) {
super(id, label, VIEWLET_ID, viewletService, editorService);
}
}
class OpenDebugPanelAction extends TogglePanelAction {
public static ID = 'workbench.debug.action.toggleRepl';
public static LABEL = nls.localize('toggleDebugPanel', "Debug Console");
constructor(
id: string,
label: string,
@IPanelService panelService: IPanelService,
@IPartService partService: IPartService
) {
super(id, label, REPL_ID, panelService, partService);
}
}
// register viewlet
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(new ViewletDescriptor(
'vs/workbench/parts/debug/browser/debugViewlet',
'DebugViewlet',
VIEWLET_ID,
nls.localize('debug', "Debug"),
'debug',
40
));
const openViewletKb: IKeybindings = {
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_D
};
const openPanelKb: IKeybindings = {
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Y
};
// register repl panel
Registry.as<PanelRegistry>(PanelExtensions.Panels).registerPanel(new PanelDescriptor(
'vs/workbench/parts/debug/electron-browser/repl',
'Repl',
REPL_ID,
nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugPanel' }, 'Debug Console'),
'repl',
30,
OpenDebugPanelAction.ID
));
Registry.as<PanelRegistry>(PanelExtensions.Panels).setDefaultPanelId(REPL_ID);
// Register default debug views
ViewsRegistry.registerViews([{ id: 'workbench.debug.variablesView', name: nls.localize('variables', "Variables"), ctor: VariablesView, order: 10, size: 40, location: ViewLocation.Debug, canToggleVisibility: true }]);
ViewsRegistry.registerViews([{ id: 'workbench.debug.watchExpressionsView', name: nls.localize('watch', "Watch"), ctor: WatchExpressionsView, order: 20, size: 10, location: ViewLocation.Debug, canToggleVisibility: true }]);
ViewsRegistry.registerViews([{ id: 'workbench.debug.callStackView', name: nls.localize('callStack', "Call Stack"), ctor: CallStackView, order: 30, size: 30, location: ViewLocation.Debug, canToggleVisibility: true }]);
ViewsRegistry.registerViews([{ id: 'workbench.debug.breakPointsView', name: nls.localize('breakpoints', "Breakpoints"), ctor: BreakpointsView, order: 40, size: 20, location: ViewLocation.Debug, canToggleVisibility: true }]);
// register action to open viewlet
const registry = Registry.as<IWorkbenchActionRegistry>(WorkbenchActionRegistryExtensions.WorkbenchActions);
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenDebugPanelAction, OpenDebugPanelAction.ID, OpenDebugPanelAction.LABEL, openPanelKb), 'View: Debug Console', nls.localize('view', "View"));
registry.registerWorkbenchAction(new SyncActionDescriptor(OpenDebugViewletAction, OpenDebugViewletAction.ID, OpenDebugViewletAction.LABEL, openViewletKb), 'View: Show Debug', nls.localize('view', "View"));
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugEditorModelManager);
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugActionsWidget);
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugContentProvider);
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(StatusBarColorProvider);
const debugCategory = nls.localize('debugCategory', "Debug");
registry.registerWorkbenchAction(new SyncActionDescriptor(
StartAction, StartAction.ID, StartAction.LABEL, { primary: KeyCode.F5 }, CONTEXT_NOT_IN_DEBUG_MODE), 'Debug: Start Debugging', debugCategory);
registry.registerWorkbenchAction(new SyncActionDescriptor(StepOverAction, StepOverAction.ID, StepOverAction.LABEL, { primary: KeyCode.F10 }, CONTEXT_IN_DEBUG_MODE), 'Debug: Step Over', debugCategory);
registry.registerWorkbenchAction(new SyncActionDescriptor(StepIntoAction, StepIntoAction.ID, StepIntoAction.LABEL, { primary: KeyCode.F11 }, CONTEXT_IN_DEBUG_MODE, KeybindingsRegistry.WEIGHT.workbenchContrib(1)), 'Debug: Step Into', debugCategory);
registry.registerWorkbenchAction(new SyncActionDescriptor(StepOutAction, StepOutAction.ID, StepOutAction.LABEL, { primary: KeyMod.Shift | KeyCode.F11 }, CONTEXT_IN_DEBUG_MODE), 'Debug: Step Out', debugCategory);
registry.registerWorkbenchAction(new SyncActionDescriptor(RestartAction, RestartAction.ID, RestartAction.LABEL, { primary: KeyMod.Shift | KeyMod.CtrlCmd | KeyCode.F5 }, CONTEXT_IN_DEBUG_MODE), 'Debug: Restart', debugCategory);
registry.registerWorkbenchAction(new SyncActionDescriptor(StopAction, StopAction.ID, StopAction.LABEL, { primary: KeyMod.Shift | KeyCode.F5 }, CONTEXT_IN_DEBUG_MODE), 'Debug: Stop', debugCategory);
registry.registerWorkbenchAction(new SyncActionDescriptor(DisconnectAction, DisconnectAction.ID, DisconnectAction.LABEL), 'Debug: Disconnect', debugCategory);
registry.registerWorkbenchAction(new SyncActionDescriptor(ContinueAction, ContinueAction.ID, ContinueAction.LABEL, { primary: KeyCode.F5 }, CONTEXT_IN_DEBUG_MODE), 'Debug: Continue', debugCategory);
registry.registerWorkbenchAction(new SyncActionDescriptor(PauseAction, PauseAction.ID, PauseAction.LABEL, { primary: KeyCode.F6 }, CONTEXT_IN_DEBUG_MODE), 'Debug: Pause', debugCategory);
registry.registerWorkbenchAction(new SyncActionDescriptor(ConfigureAction, ConfigureAction.ID, ConfigureAction.LABEL), 'Debug: Open launch.json', debugCategory);
registry.registerWorkbenchAction(new SyncActionDescriptor(AddFunctionBreakpointAction, AddFunctionBreakpointAction.ID, AddFunctionBreakpointAction.LABEL), 'Debug: Add Function Breakpoint', debugCategory);
registry.registerWorkbenchAction(new SyncActionDescriptor(ReapplyBreakpointsAction, ReapplyBreakpointsAction.ID, ReapplyBreakpointsAction.LABEL), 'Debug: Reapply All Breakpoints', debugCategory);
registry.registerWorkbenchAction(new SyncActionDescriptor(RunAction, RunAction.ID, RunAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.F5 }, CONTEXT_NOT_IN_DEBUG_MODE), 'Debug: Start Without Debugging', debugCategory);
registry.registerWorkbenchAction(new SyncActionDescriptor(RemoveAllBreakpointsAction, RemoveAllBreakpointsAction.ID, RemoveAllBreakpointsAction.LABEL), 'Debug: Remove All Breakpoints', debugCategory);
registry.registerWorkbenchAction(new SyncActionDescriptor(EnableAllBreakpointsAction, EnableAllBreakpointsAction.ID, EnableAllBreakpointsAction.LABEL), 'Debug: Enable All Breakpoints', debugCategory);
registry.registerWorkbenchAction(new SyncActionDescriptor(DisableAllBreakpointsAction, DisableAllBreakpointsAction.ID, DisableAllBreakpointsAction.LABEL), 'Debug: Disable All Breakpoints', debugCategory);
registry.registerWorkbenchAction(new SyncActionDescriptor(ClearReplAction, ClearReplAction.ID, ClearReplAction.LABEL), 'Debug: Clear Console', debugCategory);
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusReplAction, FocusReplAction.ID, FocusReplAction.LABEL), 'Debug: Focus Debug Console', debugCategory);
registry.registerWorkbenchAction(new SyncActionDescriptor(SelectAndStartAction, SelectAndStartAction.ID, SelectAndStartAction.LABEL), 'Debug: Select and Start Debugging', debugCategory);
// Register Quick Open
(<IQuickOpenRegistry>Registry.as(QuickOpenExtensions.Quickopen)).registerQuickOpenHandler(
new QuickOpenHandlerDescriptor(
'vs/workbench/parts/debug/browser/debugQuickOpen',
'DebugQuickOpenHandler',
'debug ',
'inLaunchConfigurationsPicker',
nls.localize('debugCommands', "Debug Configuration")
)
);
// register service
registerSingleton(IDebugService, service.DebugService);
// Register configuration
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
configurationRegistry.registerConfiguration({
id: 'debug',
order: 20,
title: nls.localize('debugConfigurationTitle', "Debug"),
type: 'object',
properties: {
'debug.allowBreakpointsEverywhere': {
type: 'boolean',
description: nls.localize({ comment: ['This is the description for a setting'], key: 'allowBreakpointsEverywhere' }, "Allows setting breakpoint in any file"),
default: false
},
'debug.openExplorerOnEnd': {
type: 'boolean',
description: nls.localize({ comment: ['This is the description for a setting'], key: 'openExplorerOnEnd' }, "Automatically open explorer view on the end of a debug session"),
default: false
},
'debug.inlineValues': {
type: 'boolean',
description: nls.localize({ comment: ['This is the description for a setting'], key: 'inlineValues' }, "Show variable values inline in editor while debugging"),
default: false
},
'debug.hideActionBar': {
type: 'boolean',
description: nls.localize({ comment: ['This is the description for a setting'], key: 'hideActionBar' }, "Controls if the floating debug action bar should be hidden"),
default: false
},
'debug.internalConsoleOptions': INTERNAL_CONSOLE_OPTIONS_SCHEMA,
'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: {}
}
}
});
debugCommands.registerCommands();

View File

@@ -0,0 +1,225 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import uri from 'vs/base/common/uri';
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 { ICommonCodeEditor } from 'vs/editor/common/editorCommon';
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 } from 'vs/platform/workspace/common/workspace';
import { IDebugService, IConfig, IEnablement, CONTEXT_NOT_IN_DEBUG_MODE, CONTEXT_IN_DEBUG_MODE, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_VARIABLES_FOCUSED, EDITOR_CONTRIBUTION_ID, IDebugEditorContribution } 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';
export function registerCommands(): void {
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: '_workbench.startDebug',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
handler(accessor: ServicesAccessor, configurationOrName: IConfig | string, folderUri?: uri) {
const debugService = accessor.get(IDebugService);
if (!configurationOrName) {
configurationOrName = debugService.getConfigurationManager().selectedName;
}
if (!folderUri) {
const selectedLaunch = debugService.getConfigurationManager().selectedLaunch;
folderUri = selectedLaunch ? selectedLaunch.workspaceUri : undefined;
}
if (typeof configurationOrName === 'string') {
debugService.startDebugging(folderUri, configurationOrName);
} else {
debugService.createProcess(folderUri, configurationOrName);
}
},
when: CONTEXT_NOT_IN_DEBUG_MODE,
primary: undefined
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.customDebugRequest',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
handler(accessor: ServicesAccessor, request: string, requestArgs: any) {
const process = accessor.get(IDebugService).getViewModel().focusedProcess;
if (process) {
return process.session.custom(request, requestArgs);
}
return undefined;
},
when: CONTEXT_IN_DEBUG_MODE,
primary: undefined
});
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,
primary: KeyCode.Space,
handler: (accessor) => {
const listService = accessor.get(IListService);
const debugService = accessor.get(IDebugService);
const focused = listService.getFocused();
// Tree only
if (!(focused instanceof List)) {
const tree = focused;
const element = <IEnablement>tree.getFocus();
debugService.enableOrDisableBreakpoints(!element.enabled, element).done(null, errors.onUnexpectedError);
}
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'debug.renameWatchExpression',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(5),
when: CONTEXT_WATCH_EXPRESSIONS_FOCUSED,
primary: KeyCode.F2,
mac: { primary: KeyCode.Enter },
handler: (accessor) => {
const listService = accessor.get(IListService);
const debugService = accessor.get(IDebugService);
const focused = listService.getFocused();
// Tree only
if (!(focused instanceof List)) {
const element = focused.getFocus();
if (element instanceof Expression) {
debugService.getViewModel().setSelectedExpression(element);
}
}
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'debug.setVariable',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(5),
when: CONTEXT_VARIABLES_FOCUSED,
primary: KeyCode.F2,
mac: { primary: KeyCode.Enter },
handler: (accessor) => {
const listService = accessor.get(IListService);
const debugService = accessor.get(IDebugService);
const focused = listService.getFocused();
// Tree only
if (!(focused instanceof List)) {
const element = focused.getFocus();
if (element instanceof Variable) {
debugService.getViewModel().setSelectedExpression(element);
}
}
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'debug.removeWatchExpression',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: CONTEXT_WATCH_EXPRESSIONS_FOCUSED,
primary: KeyCode.Delete,
mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace },
handler: (accessor) => {
const listService = accessor.get(IListService);
const debugService = accessor.get(IDebugService);
const focused = listService.getFocused();
// Tree only
if (!(focused instanceof List)) {
const element = focused.getFocus();
if (element instanceof Expression) {
debugService.removeWatchExpressions(element.getId());
}
}
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'debug.removeBreakpoint',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: CONTEXT_BREAKPOINTS_FOCUSED,
primary: KeyCode.Delete,
mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace },
handler: (accessor) => {
const listService = accessor.get(IListService);
const debugService = accessor.get(IDebugService);
const focused = listService.getFocused();
// Tree only
if (!(focused instanceof List)) {
const element = focused.getFocus();
if (element instanceof Breakpoint) {
debugService.removeBreakpoints(element.getId()).done(null, errors.onUnexpectedError);
} else if (element instanceof FunctionBreakpoint) {
debugService.removeFunctionBreakpoints(element.getId()).done(null, errors.onUnexpectedError);
}
}
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'debug.installAdditionalDebuggers',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: undefined,
primary: undefined,
handler: (accessor) => {
const viewletService = accessor.get(IViewletService);
return viewletService.openViewlet(EXTENSIONS_VIEWLET_ID, true)
.then(viewlet => viewlet as IExtensionsViewlet)
.then(viewlet => {
viewlet.search('tag:debuggers @sort:installs');
viewlet.focus();
});
}
});
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'debug.addConfiguration',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: undefined,
primary: undefined,
handler: (accessor, workspaceUri: string) => {
const manager = accessor.get(IDebugService).getConfigurationManager();
if (!accessor.get(IWorkspaceContextService).hasWorkspace()) {
accessor.get(IMessageService).show(severity.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.workspaceUri.toString() === workspaceUri).pop() || manager.selectedLaunch;
return launch.openConfigFile(false).done(editor => {
if (editor) {
const codeEditor = <ICommonCodeEditor>editor.getControl();
if (codeEditor) {
return codeEditor.getContribution<IDebugEditorContribution>(EDITOR_CONTRIBUTION_ID).addLaunchConfiguration();
}
}
return undefined;
});
}
});
}

View File

@@ -0,0 +1,593 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
import Event, { Emitter } from 'vs/base/common/event';
import { TPromise } from 'vs/base/common/winjs.base';
import * as strings from 'vs/base/common/strings';
import { first } from 'vs/base/common/arrays';
import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform';
import * as objects from 'vs/base/common/objects';
import uri from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import * as paths from 'vs/base/common/paths';
import { IJSONSchema } from 'vs/base/common/jsonSchema';
import { IModel, isCommonCodeEditor } from 'vs/editor/common/editorCommon';
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 { Registry } from 'vs/platform/registry/common/platform';
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 { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IWorkspaceContextService } 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, DEBUG_SCHEME, IConfig, IEnvConfig, IGlobalConfig, IConfigurationManager, ILaunch } 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';
// debuggers extension point
export const debuggersExtPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint<IRawAdapter[]>('debuggers', [], {
description: nls.localize('vscode.extension.contributes.debuggers', 'Contributes debug adapters.'),
type: 'array',
defaultSnippets: [{ body: [{ type: '', extensions: [] }] }],
items: {
type: 'object',
defaultSnippets: [{ body: { type: '', program: '', runtime: '', enableBreakpointsFor: { languageIds: [''] } } }],
properties: {
type: {
description: nls.localize('vscode.extension.contributes.debuggers.type', "Unique identifier for this debug adapter."),
type: 'string'
},
label: {
description: nls.localize('vscode.extension.contributes.debuggers.label', "Display name for this debug adapter."),
type: 'string'
},
program: {
description: nls.localize('vscode.extension.contributes.debuggers.program', "Path to the debug adapter program. Path is either absolute or relative to the extension folder."),
type: 'string'
},
args: {
description: nls.localize('vscode.extension.contributes.debuggers.args', "Optional arguments to pass to the adapter."),
type: 'array'
},
runtime: {
description: nls.localize('vscode.extension.contributes.debuggers.runtime', "Optional runtime in case the program attribute is not an executable but requires a runtime."),
type: 'string'
},
runtimeArgs: {
description: nls.localize('vscode.extension.contributes.debuggers.runtimeArgs', "Optional runtime arguments."),
type: 'array'
},
variables: {
description: nls.localize('vscode.extension.contributes.debuggers.variables', "Mapping from interactive variables (e.g ${action.pickProcess}) in `launch.json` to a command."),
type: 'object'
},
initialConfigurations: {
description: nls.localize('vscode.extension.contributes.debuggers.initialConfigurations', "Configurations for generating the initial \'launch.json\'."),
type: ['array', 'string'],
},
languages: {
description: nls.localize('vscode.extension.contributes.debuggers.languages', "List of languages for which the debug extension could be considered the \"default debugger\"."),
type: 'array'
},
adapterExecutableCommand: {
description: nls.localize('vscode.extension.contributes.debuggers.adapterExecutableCommand', "If specified VS Code will call this command to determine the executable path of the debug adapter and the arguments to pass."),
type: 'string'
},
startSessionCommand: {
description: nls.localize('vscode.extension.contributes.debuggers.startSessionCommand', "If specified VS Code will call this command for the \"debug\" or \"run\" actions targeted for this extension."),
type: 'string'
},
configurationSnippets: {
description: nls.localize('vscode.extension.contributes.debuggers.configurationSnippets', "Snippets for adding new configurations in \'launch.json\'."),
type: 'array'
},
configurationAttributes: {
description: nls.localize('vscode.extension.contributes.debuggers.configurationAttributes', "JSON schema configurations for validating \'launch.json\'."),
type: 'object'
},
windows: {
description: nls.localize('vscode.extension.contributes.debuggers.windows', "Windows specific settings."),
type: 'object',
properties: {
runtime: {
description: nls.localize('vscode.extension.contributes.debuggers.windows.runtime', "Runtime used for Windows."),
type: 'string'
}
}
},
osx: {
description: nls.localize('vscode.extension.contributes.debuggers.osx', "OS X specific settings."),
type: 'object',
properties: {
runtime: {
description: nls.localize('vscode.extension.contributes.debuggers.osx.runtime', "Runtime used for OSX."),
type: 'string'
}
}
},
linux: {
description: nls.localize('vscode.extension.contributes.debuggers.linux', "Linux specific settings."),
type: 'object',
properties: {
runtime: {
description: nls.localize('vscode.extension.contributes.debuggers.linux.runtime', "Runtime used for Linux."),
type: 'string'
}
}
}
}
}
});
interface IRawBreakpointContribution {
language: string;
}
// breakpoints extension point #9037
const breakpointsExtPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint<IRawBreakpointContribution[]>('breakpoints', [], {
description: nls.localize('vscode.extension.contributes.breakpoints', 'Contributes breakpoints.'),
type: 'array',
defaultSnippets: [{ body: [{ language: '' }] }],
items: {
type: 'object',
defaultSnippets: [{ body: { language: '' } }],
properties: {
language: {
description: nls.localize('vscode.extension.contributes.breakpoints.language', "Allow breakpoints for this language."),
type: 'string'
},
}
}
});
// debug general schema
export const schemaId = 'vscode://schemas/launch';
const defaultCompound: ICompound = { name: 'Compound', configurations: [] };
const schema: IJSONSchema = {
id: schemaId,
type: 'object',
title: nls.localize('app.launch.json.title', "Launch"),
required: ['version', 'configurations'],
default: { version: '0.2.0', configurations: [], compounds: [] },
properties: {
version: {
type: 'string',
description: nls.localize('app.launch.json.version', "Version of this file format."),
default: '0.2.0'
},
configurations: {
type: 'array',
description: nls.localize('app.launch.json.configurations', "List of configurations. Add new configurations or edit existing ones by using IntelliSense."),
items: {
defaultSnippets: [],
'type': 'object',
oneOf: []
}
},
compounds: {
type: 'array',
description: nls.localize('app.launch.json.compounds', "List of compounds. Each compound references multiple configurations which will get launched together."),
items: {
type: 'object',
required: ['name', 'configurations'],
properties: {
name: {
type: 'string',
description: nls.localize('app.launch.json.compound.name', "Name of compound. Appears in the launch configuration drop down menu.")
},
configurations: {
type: 'array',
default: [],
items: {
type: 'string'
},
description: nls.localize('app.launch.json.compounds.configurations', "Names of configurations that will be started as part of this compound.")
}
},
default: defaultCompound
},
default: [
defaultCompound
]
}
}
};
const jsonRegistry = <IJSONContributionRegistry>Registry.as(JSONExtensions.JSONContribution);
jsonRegistry.registerSchema(schemaId, schema);
const DEBUG_SELECTED_CONFIG_NAME_KEY = 'debug.selectedconfigname';
const DEBUG_SELECTED_ROOT = 'debug.selectedroot';
export class ConfigurationManager implements IConfigurationManager {
private adapters: Adapter[];
private breakpointModeIdsSet = new Set<string>();
private launches: ILaunch[];
private _selectedName: string;
private _selectedLaunch: ILaunch;
private toDispose: IDisposable[];
private _onDidSelectConfigurationName = new Emitter<void>();
private _providers: Map<number, IDebugConfigurationProvider>;
constructor(
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IFileService private fileService: IFileService,
@ITelemetryService private telemetryService: ITelemetryService,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IConfigurationService private configurationService: IConfigurationService,
@IQuickOpenService private quickOpenService: IQuickOpenService,
@IConfigurationResolverService private configurationResolverService: IConfigurationResolverService,
@IInstantiationService private instantiationService: IInstantiationService,
@ICommandService private commandService: ICommandService,
@IStorageService private storageService: IStorageService,
@ILifecycleService lifecycleService: ILifecycleService,
) {
this._providers = new Map<number, IDebugConfigurationProvider>();
this.adapters = [];
this.toDispose = [];
this.registerListeners(lifecycleService);
this.initLaunches();
const previousSelectedRoot = this.storageService.get(DEBUG_SELECTED_ROOT, StorageScope.WORKSPACE);
const filtered = this.launches.filter(l => l.workspaceUri.toString() === previousSelectedRoot);
const launchToSelect = filtered.length ? filtered[0] : this.launches.length ? this.launches[0] : undefined;
this.selectConfiguration(launchToSelect, this.storageService.get(DEBUG_SELECTED_CONFIG_NAME_KEY, StorageScope.WORKSPACE));
}
public registerDebugConfigurationProvider(handle: number, debugConfigurationProvider: IDebugConfigurationProvider): void {
if (!debugConfigurationProvider) {
return;
}
this._providers.set(handle, debugConfigurationProvider);
const adapter = this.getAdapter(debugConfigurationProvider.type);
if (adapter) {
adapter.hasConfigurationProvider = true;
}
}
public unregisterDebugConfigurationProvider(handle: number): boolean {
return this._providers.delete(handle);
}
public resolveDebugConfiguration(folderUri: uri | undefined, debugConfiguration: any): TPromise<any> {
// collect all candidates
const providers: IDebugConfigurationProvider[] = [];
this._providers.forEach(provider => {
if (provider.type === debugConfiguration.type && provider.resolveDebugConfiguration) {
providers.push(provider);
}
});
// pipe the config through the promises sequentially
return providers.reduce((promise, provider) => {
return promise.then(config => {
return provider.resolveDebugConfiguration(folderUri, config);
});
}, TPromise.as(debugConfiguration));
}
public provideDebugConfigurations(folderUri: uri | undefined, type: string): TPromise<any[]> {
// collect all candidates
const configs: TPromise<any[]>[] = [];
this._providers.forEach(provider => {
if (provider.type === type && provider.provideDebugConfigurations) {
configs.push(provider.provideDebugConfigurations(folderUri));
}
});
// combine all configs into one array
return TPromise.join(configs).then(results => {
return [].concat.apply([], results);
});
}
private registerListeners(lifecycleService: ILifecycleService): void {
debuggersExtPoint.setHandler((extensions) => {
extensions.forEach(extension => {
extension.value.forEach(rawAdapter => {
if (!rawAdapter.type || (typeof rawAdapter.type !== 'string')) {
extension.collector.error(nls.localize('debugNoType', "Debug adapter 'type' can not be omitted and must be of type 'string'."));
}
if (rawAdapter.enableBreakpointsFor) {
rawAdapter.enableBreakpointsFor.languageIds.forEach(modeId => {
this.breakpointModeIdsSet.add(modeId);
});
}
const duplicate = this.adapters.filter(a => a.type === rawAdapter.type).pop();
if (duplicate) {
duplicate.merge(rawAdapter, extension.description);
} else {
this.adapters.push(this.instantiationService.createInstance(Adapter, rawAdapter, extension.description));
}
});
});
// update the schema to include all attributes, snippets and types from extensions.
this.adapters.forEach(adapter => {
const items = (<IJSONSchema>schema.properties['configurations'].items);
const schemaAttributes = adapter.getSchemaAttributes();
if (schemaAttributes) {
items.oneOf.push(...schemaAttributes);
}
const configurationSnippets = adapter.configurationSnippets;
if (configurationSnippets) {
items.defaultSnippets.push(...configurationSnippets);
}
});
});
breakpointsExtPoint.setHandler(extensions => {
extensions.forEach(ext => {
ext.value.forEach(breakpoints => {
this.breakpointModeIdsSet.add(breakpoints.language);
});
});
});
this.toDispose.push(this.contextService.onDidChangeWorkspaceRoots(() => {
this.initLaunches();
const toSelect = this.selectedLaunch && this.selectedLaunch.getConfigurationNames().length ? this.selectedLaunch : first(this.launches, l => !!l.getConfigurationNames().length, this.launches.length ? this.launches[0] : undefined);
this.selectConfiguration(toSelect);
}));
this.toDispose.push(lifecycleService.onShutdown(this.store, this));
}
private initLaunches(): void {
const workspace = this.contextService.getWorkspace();
this.launches = workspace ? workspace.roots.map(root => this.instantiationService.createInstance(Launch, this, root)) : [];
if (this.launches.indexOf(this._selectedLaunch) === -1) {
this._selectedLaunch = undefined;
}
}
public getLaunches(): ILaunch[] {
return this.launches;
}
public get selectedLaunch(): ILaunch {
return this._selectedLaunch;
}
public get selectedName(): string {
return this._selectedName;
}
public get onDidSelectConfiguration(): Event<void> {
return this._onDidSelectConfigurationName.event;
}
public selectConfiguration(launch: ILaunch, name?: string, debugStarted?: boolean): void {
const previousLaunch = this._selectedLaunch;
const previousName = this._selectedName;
this._selectedLaunch = launch;
const names = launch ? launch.getConfigurationNames() : [];
if (name && names.indexOf(name) >= 0) {
this._selectedName = name;
}
if (names.indexOf(this.selectedName) === -1) {
this._selectedName = names.length ? names[0] : undefined;
}
if (this.selectedLaunch !== previousLaunch || this.selectedName !== previousName) {
this._onDidSelectConfigurationName.fire();
}
}
public canSetBreakpointsIn(model: IModel): boolean {
if (model.uri.scheme !== Schemas.file && model.uri.scheme !== DEBUG_SCHEME) {
return false;
}
if (this.configurationService.getConfiguration<IDebugConfiguration>('debug').allowBreakpointsEverywhere) {
return true;
}
const modeId = model ? model.getLanguageIdentifier().language : null;
return this.breakpointModeIdsSet.has(modeId);
}
public getAdapter(type: string): Adapter {
return this.adapters.filter(adapter => strings.equalsIgnoreCase(adapter.type, type)).pop();
}
public guessAdapter(type?: string): TPromise<Adapter> {
if (type) {
const adapter = this.getAdapter(type);
return TPromise.as(adapter);
}
const editor = this.editorService.getActiveEditor();
if (editor) {
const codeEditor = editor.getControl();
if (isCommonCodeEditor(codeEditor)) {
const model = codeEditor.getModel();
const language = model ? model.getLanguageIdentifier().language : undefined;
const adapters = this.adapters.filter(a => a.languages && a.languages.indexOf(language) >= 0);
if (adapters.length === 1) {
return TPromise.as(adapters[0]);
}
}
}
return this.quickOpenService.pick([...this.adapters.filter(a => a.hasInitialConfiguration() || a.hasConfigurationProvider), { label: 'More...', separator: { border: true } }], { placeHolder: nls.localize('selectDebug', "Select Environment") })
.then(picked => {
if (picked instanceof Adapter) {
return picked;
}
if (picked) {
this.commandService.executeCommand('debug.installAdditionalDebuggers');
}
return undefined;
});
}
public getStartSessionCommand(type?: string): TPromise<{ command: string, type: string }> {
return this.guessAdapter(type).then(adapter => {
if (adapter) {
return {
command: adapter.startSessionCommand,
type: adapter.type
};
}
return undefined;
});
}
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.workspaceUri.toString(), StorageScope.WORKSPACE);
}
}
public dispose(): void {
this.toDispose = dispose(this.toDispose);
}
}
class Launch implements ILaunch {
public name: string;
constructor(
private configurationManager: ConfigurationManager,
public workspaceUri: uri,
@IFileService private fileService: IFileService,
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IConfigurationService private configurationService: IConfigurationService,
@IConfigurationResolverService private configurationResolverService: IConfigurationResolverService,
) {
this.name = paths.basename(this.workspaceUri.fsPath);
}
public getCompound(name: string): ICompound {
const config = this.configurationService.getConfiguration<IGlobalConfig>('launch', { resource: this.workspaceUri });
if (!config || !config.compounds) {
return null;
}
return config.compounds.filter(compound => compound.name === name).pop();
}
public getConfigurationNames(): string[] {
const config = this.configurationService.getConfiguration<IGlobalConfig>('launch', { resource: this.workspaceUri });
if (!config || !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 (config.compounds) {
names.push(...config.compounds.filter(compound => typeof compound.name === 'string' && compound.configurations && compound.configurations.length)
.map(compound => compound.name));
}
}
return names;
}
}
public getConfiguration(name: string): IConfig {
const config = this.configurationService.getConfiguration<IGlobalConfig>('launch', { resource: this.workspaceUri });
if (!config || !config.configurations) {
return null;
}
return config.configurations.filter(config => config && config.name === name).shift();
}
public resolveConfiguration(config: IConfig): TPromise<IConfig> {
const result = objects.deepClone(config) as IConfig;
// Set operating system specific properties #1873
const setOSProperties = (flag: boolean, osConfig: IEnvConfig) => {
if (flag && osConfig) {
Object.keys(osConfig).forEach(key => {
result[key] = osConfig[key];
});
}
};
setOSProperties(isWindows, result.windows);
setOSProperties(isMacintosh, result.osx);
setOSProperties(isLinux, result.linux);
// massage configuration attributes - append workspace path to relatvie paths, substitute variables in paths.
Object.keys(result).forEach(key => {
result[key] = this.configurationResolverService.resolveAny(this.workspaceUri, result[key]);
});
const adapter = this.configurationManager.getAdapter(result.type);
return this.configurationResolverService.resolveInteractiveVariables(result, adapter ? adapter.variables : null);
}
public get uri(): uri {
return uri.file(paths.join(this.workspaceUri.fsPath, '/.vscode/launch.json'));
}
public openConfigFile(sideBySide: boolean, type?: string): TPromise<IEditor> {
const resource = this.uri;
let configFileCreated = false;
return this.fileService.resolveContent(resource).then(content => content, err => {
// launch.json not found: create one by collecting launch configs from debugConfigProviders
return this.configurationManager.guessAdapter(type).then(adapter => {
if (adapter) {
return this.configurationManager.provideDebugConfigurations(this.workspaceUri, adapter.type).then(initialConfigs => {
return adapter.getInitialConfigurationContent(this.workspaceUri, initialConfigs);
});
} else {
return undefined;
}
}).then(content => {
if (!content) {
return undefined;
}
configFileCreated = true;
return this.fileService.updateContent(resource, content).then(() => {
// convert string into IContent; see #32135
return { value: content };
});
});
}).then(content => {
if (!content) {
return undefined;
}
const index = content.value.indexOf(`"${this.configurationManager.selectedName}"`);
let startLineNumber = 1;
for (let i = 0; i < index; i++) {
if (content.value.charAt(i) === '\n') {
startLineNumber++;
}
}
const selection = startLineNumber > 1 ? { startLineNumber, startColumn: 4 } : undefined;
return this.editorService.openEditor({
resource: resource,
options: {
forceOpen: true,
selection,
pinned: configFileCreated, // pin only if config file is created #8727
revealIfVisible: true
},
}, sideBySide);
}, (error) => {
throw new Error(nls.localize('DebugConfig.failed', "Unable to create 'launch.json' file inside the '.vscode' folder ({0}).", error));
});
}
}

View File

@@ -0,0 +1,620 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as errors from 'vs/base/common/errors';
import { TPromise } from 'vs/base/common/winjs.base';
import { RunOnceScheduler } from 'vs/base/common/async';
import * as lifecycle from 'vs/base/common/lifecycle';
import * as env from 'vs/base/common/platform';
import uri from 'vs/base/common/uri';
import { visit } from 'vs/base/common/json';
import { Constants } from 'vs/editor/common/core/uint';
import { IAction, Action } from 'vs/base/common/actions';
import { KeyCode } from 'vs/base/common/keyCodes';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
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 { editorContribution } from 'vs/editor/browser/editorBrowserExtensions';
import { IDecorationOptions, IModelDecorationOptions, IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/editorCommon';
import { ICodeEditorService } from 'vs/editor/common/services/codeEditorService';
import { Range } from 'vs/editor/common/core/range';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
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 { 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';
import { BreakpointWidget } from 'vs/workbench/parts/debug/browser/breakpointWidget';
import { ExceptionWidget } from 'vs/workbench/parts/debug/browser/exceptionWidget';
import { FloatingClickWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets';
import { IListService } from 'vs/platform/list/browser/listService';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { Position } from 'vs/editor/common/core/position';
import { CoreEditingCommands } from 'vs/editor/common/controller/coreCommands';
import { first } from 'vs/base/common/arrays';
const HOVER_DELAY = 300;
const LAUNCH_JSON_REGEX = /launch\.json$/;
const REMOVE_INLINE_VALUES_DELAY = 100;
const INLINE_VALUE_DECORATION_KEY = 'inlinevaluedecoration';
const MAX_NUM_INLINE_VALUES = 100; // JS Global scope can have 700+ entries. We want to limit ourselves for perf reasons
const MAX_INLINE_DECORATOR_LENGTH = 150; // Max string length of each inline decorator when debugging. If exceeded ... is added
const MAX_TOKENIZATION_LINE_LEN = 500; // If line is too long, then inline values for the line are skipped
@editorContribution
export class DebugEditorContribution implements IDebugEditorContribution {
private toDispose: lifecycle.IDisposable[];
private hoverWidget: DebugHoverWidget;
private showHoverScheduler: RunOnceScheduler;
private hideHoverScheduler: RunOnceScheduler;
private removeInlineValuesScheduler: RunOnceScheduler;
private hoverRange: Range;
private breakpointHintDecoration: string[];
private breakpointWidget: BreakpointWidget;
private breakpointWidgetVisible: IContextKey<boolean>;
private wordToLineNumbersMap: Map<string, Position[]>;
private exceptionWidget: ExceptionWidget;
private configurationWidget: FloatingClickWidget;
constructor(
private editor: ICodeEditor,
@IDebugService private debugService: IDebugService,
@IContextMenuService private contextMenuService: IContextMenuService,
@IInstantiationService private instantiationService: IInstantiationService,
@IContextKeyService contextKeyService: IContextKeyService,
@ICommandService private commandService: ICommandService,
@ICodeEditorService private codeEditorService: ICodeEditorService,
@ITelemetryService private telemetryService: ITelemetryService,
@IListService listService: IListService,
@IConfigurationService private configurationService: IConfigurationService,
@IThemeService themeService: IThemeService
) {
this.breakpointHintDecoration = [];
this.hoverWidget = new DebugHoverWidget(this.editor, this.debugService, listService, 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);
this.removeInlineValuesScheduler = new RunOnceScheduler(() => this.editor.removeDecorations(INLINE_VALUE_DECORATION_KEY), REMOVE_INLINE_VALUES_DELAY);
this.registerListeners();
this.breakpointWidgetVisible = CONTEXT_BREAKPOINT_WIDGET_VISIBLE.bindTo(contextKeyService);
this.updateConfigurationWidgetVisibility();
this.codeEditorService.registerDecorationType(INLINE_VALUE_DECORATION_KEY, {});
this.toggleExceptionWidget();
}
private getContextMenuActions(breakpoints: IBreakpoint[], uri: uri, lineNumber: number): TPromise<(IAction | ContextSubMenu)[]> {
const actions: (IAction | ContextSubMenu)[] = [];
if (breakpoints.length === 1) {
actions.push(this.instantiationService.createInstance(RemoveBreakpointAction, RemoveBreakpointAction.ID, RemoveBreakpointAction.LABEL));
actions.push(this.instantiationService.createInstance(EditConditionalBreakpointAction, EditConditionalBreakpointAction.ID, EditConditionalBreakpointAction.LABEL, this.editor));
if (breakpoints[0].enabled) {
actions.push(this.instantiationService.createInstance(DisableBreakpointAction, DisableBreakpointAction.ID, DisableBreakpointAction.LABEL));
} else {
actions.push(this.instantiationService.createInstance(EnableBreakpointAction, EnableBreakpointAction.ID, EnableBreakpointAction.LABEL));
}
} else if (breakpoints.length > 1) {
const sorted = breakpoints.sort((first, second) => first.column - second.column);
actions.push(new ContextSubMenu(nls.localize('removeBreakpoints', "Remove Breakpoints"), sorted.map(bp => new Action(
'removeColumnBreakpoint',
bp.column ? nls.localize('removeBreakpointOnColumn', "Remove Breakpoint on Column {0}", bp.column) : nls.localize('removeLineBreakpoint', "Remove Line Breakpoint"),
null,
true,
() => this.debugService.removeBreakpoints(bp.getId())
))));
actions.push(new ContextSubMenu(nls.localize('editBreakpoints', "Edit Breakpoints"), sorted.map(bp =>
new Action('editBreakpoint',
bp.column ? nls.localize('editBreakpointOnColumn', "Edit Breakpoint on Column {0}", bp.column) : nls.localize('editLineBrekapoint', "Edit Line Breakpoint"),
null,
true,
() => TPromise.as(this.editor.getContribution<IDebugEditorContribution>(EDITOR_CONTRIBUTION_ID).showBreakpointWidget(bp.lineNumber, bp.column))
)
)));
actions.push(new ContextSubMenu(nls.localize('enableDisableBreakpoints', "Enable/Disable Breakpoints"), sorted.map(bp => new Action(
bp.enabled ? 'disableColumnBreakpoint' : 'enableColumnBreakpoint',
bp.enabled ? (bp.column ? nls.localize('disableColumnBreakpoint', "Disable Breakpoint on Column {0}", bp.column) : nls.localize('disableBreakpointOnLine', "Disable Line Breakpoint"))
: (bp.column ? nls.localize('enableBreakpoints', "Enable Breakpoint on Column {0}", bp.column) : nls.localize('enableBreakpointOnLine', "Enable Line Breakpoint")),
null,
true,
() => this.debugService.enableOrDisableBreakpoints(!bp.enabled, bp)
))));
} else {
actions.push(new Action(
'addBreakpoint',
nls.localize('addBreakpoint', "Add Breakpoint"),
null,
true,
() => this.debugService.addBreakpoints(uri, [{ lineNumber }])
));
actions.push(this.instantiationService.createInstance(AddConditionalBreakpointAction, AddConditionalBreakpointAction.ID, AddConditionalBreakpointAction.LABEL, this.editor, lineNumber));
}
return TPromise.as(actions);
}
private registerListeners(): void {
this.toDispose.push(this.editor.onMouseDown((e: IEditorMouseEvent) => {
if (e.target.type !== MouseTargetType.GUTTER_GLYPH_MARGIN || /* after last line */ e.target.detail || !this.marginFreeFromNonDebugDecorations(e.target.position.lineNumber)) {
return;
}
const canSetBreakpoints = this.debugService.getConfigurationManager().canSetBreakpointsIn(this.editor.getModel());
const lineNumber = e.target.position.lineNumber;
const uri = this.editor.getModel().uri;
if (e.event.rightButton || (env.isMacintosh && e.event.leftButton && e.event.ctrlKey)) {
if (!canSetBreakpoints) {
return;
}
const anchor = { x: e.event.posx + 1, y: e.event.posy };
const breakpoints = this.debugService.getModel().getBreakpoints().filter(bp => bp.lineNumber === lineNumber && bp.uri.toString() === uri.toString());
this.contextMenuService.showContextMenu({
getAnchor: () => anchor,
getActions: () => this.getContextMenuActions(breakpoints, uri, lineNumber),
getActionsContext: () => breakpoints.length ? breakpoints[0] : undefined
});
} else {
const breakpoints = this.debugService.getModel().getBreakpoints()
.filter(bp => bp.uri.toString() === uri.toString() && bp.lineNumber === lineNumber);
if (breakpoints.length) {
breakpoints.forEach(bp => this.debugService.removeBreakpoints(bp.getId()));
} else if (canSetBreakpoints) {
this.debugService.addBreakpoints(uri, [{ lineNumber }]);
}
}
}));
this.toDispose.push(this.editor.onMouseMove((e: IEditorMouseEvent) => {
let showBreakpointHintAtLineNumber = -1;
if (e.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN && this.debugService.getConfigurationManager().canSetBreakpointsIn(this.editor.getModel()) &&
this.marginFreeFromNonDebugDecorations(e.target.position.lineNumber)) {
if (!e.target.detail) {
// is not after last line
showBreakpointHintAtLineNumber = e.target.position.lineNumber;
}
}
this.ensureBreakpointHintDecoration(showBreakpointHintAtLineNumber);
}));
this.toDispose.push(this.editor.onMouseLeave((e: IEditorMouseEvent) => {
this.ensureBreakpointHintDecoration(-1);
}));
this.toDispose.push(this.debugService.getViewModel().onDidFocusStackFrame(e => this.onFocusStackFrame(e.stackFrame)));
// hover listeners & hover widget
this.toDispose.push(this.editor.onMouseDown((e: IEditorMouseEvent) => this.onEditorMouseDown(e)));
this.toDispose.push(this.editor.onMouseMove((e: IEditorMouseEvent) => this.onEditorMouseMove(e)));
this.toDispose.push(this.editor.onMouseLeave((e: IEditorMouseEvent) => {
const rect = this.hoverWidget.getDomNode().getBoundingClientRect();
// Only hide the hover widget if the editor mouse leave event is outside the hover widget #3528
if (e.event.posx < rect.left || e.event.posx > rect.right || e.event.posy < rect.top || e.event.posy > rect.bottom) {
this.hideHoverWidget();
}
}));
this.toDispose.push(this.editor.onKeyDown((e: IKeyboardEvent) => this.onKeyDown(e)));
this.toDispose.push(this.editor.onDidChangeModelContent(() => {
this.wordToLineNumbersMap = null;
}));
this.toDispose.push(this.editor.onDidChangeModel(() => {
const sf = this.debugService.getViewModel().focusedStackFrame;
const model = this.editor.getModel();
this.editor.updateOptions({ hover: !sf || !model || model.uri.toString() !== sf.source.uri.toString() });
this.closeBreakpointWidget();
this.toggleExceptionWidget();
this.hideHoverWidget();
this.updateConfigurationWidgetVisibility();
this.wordToLineNumbersMap = null;
this.updateInlineDecorations(sf);
}));
this.toDispose.push(this.editor.onDidScrollChange(() => this.hideHoverWidget));
this.toDispose.push(this.debugService.onDidChangeState((state: State) => {
if (state !== State.Stopped) {
this.toggleExceptionWidget();
}
}));
}
public getId(): string {
return EDITOR_CONTRIBUTION_ID;
}
public showHover(range: Range, focus: boolean): TPromise<void> {
const sf = this.debugService.getViewModel().focusedStackFrame;
const model = this.editor.getModel();
if (sf && model && sf.source.uri.toString() === model.uri.toString()) {
return this.hoverWidget.showAt(range, focus);
}
return undefined;
}
private marginFreeFromNonDebugDecorations(line: number): boolean {
const decorations = this.editor.getLineDecorations(line);
if (decorations) {
for (const { options } of decorations) {
if (options.glyphMarginClassName && options.glyphMarginClassName.indexOf('debug') === -1) {
return false;
}
}
}
return true;
}
private ensureBreakpointHintDecoration(showBreakpointHintAtLineNumber: number): void {
const newDecoration: IModelDeltaDecoration[] = [];
if (showBreakpointHintAtLineNumber !== -1) {
newDecoration.push({
options: DebugEditorContribution.BREAKPOINT_HELPER_DECORATION,
range: {
startLineNumber: showBreakpointHintAtLineNumber,
startColumn: 1,
endLineNumber: showBreakpointHintAtLineNumber,
endColumn: 1
}
});
}
this.breakpointHintDecoration = this.editor.deltaDecorations(this.breakpointHintDecoration, newDecoration);
}
private onFocusStackFrame(sf: IStackFrame): void {
const model = this.editor.getModel();
if (model && sf && sf.source.uri.toString() === model.uri.toString()) {
this.editor.updateOptions({ hover: false });
this.toggleExceptionWidget();
} else {
this.editor.updateOptions({ hover: true });
this.hideHoverWidget();
}
this.updateInlineDecorations(sf);
}
private hideHoverWidget(): void {
if (!this.hideHoverScheduler.isScheduled() && this.hoverWidget.isVisible()) {
this.hideHoverScheduler.schedule();
}
this.showHoverScheduler.cancel();
}
// hover business
private onEditorMouseDown(mouseEvent: IEditorMouseEvent): void {
if (mouseEvent.target.type === MouseTargetType.CONTENT_WIDGET && mouseEvent.target.detail === DebugHoverWidget.ID) {
return;
}
this.hideHoverWidget();
}
private onEditorMouseMove(mouseEvent: IEditorMouseEvent): void {
if (this.debugService.state !== State.Stopped) {
return;
}
const targetType = mouseEvent.target.type;
const stopKey = env.isMacintosh ? 'metaKey' : 'ctrlKey';
if (targetType === MouseTargetType.CONTENT_WIDGET && mouseEvent.target.detail === DebugHoverWidget.ID && !(<any>mouseEvent.event)[stopKey]) {
// mouse moved on top of debug hover widget
return;
}
if (targetType === MouseTargetType.CONTENT_TEXT) {
if (!mouseEvent.target.range.equalsRange(this.hoverRange)) {
this.hoverRange = mouseEvent.target.range;
this.showHoverScheduler.schedule();
}
} else {
this.hideHoverWidget();
}
}
private onKeyDown(e: IKeyboardEvent): void {
const stopKey = env.isMacintosh ? KeyCode.Meta : KeyCode.Ctrl;
if (e.keyCode !== stopKey) {
// do not hide hover when Ctrl/Meta is pressed
this.hideHoverWidget();
}
}
// end hover business
// breakpoint widget
public showBreakpointWidget(lineNumber: number, column: number): void {
if (this.breakpointWidget) {
this.breakpointWidget.dispose();
}
this.breakpointWidget = this.instantiationService.createInstance(BreakpointWidget, this.editor, lineNumber, column);
this.breakpointWidget.show({ lineNumber, column: 1 }, 2);
this.breakpointWidgetVisible.set(true);
}
public closeBreakpointWidget(): void {
if (this.breakpointWidget) {
this.breakpointWidget.dispose();
this.breakpointWidget = null;
this.breakpointWidgetVisible.reset();
this.editor.focus();
}
}
// exception widget
private toggleExceptionWidget(): void {
// Toggles exception widget based on the state of the current editor model and debug stack frame
const model = this.editor.getModel();
const focusedSf = this.debugService.getViewModel().focusedStackFrame;
const callStack = focusedSf ? focusedSf.thread.getCallStack() : null;
if (!model || !focusedSf || !callStack || callStack.length === 0) {
this.closeExceptionWidget();
return;
}
// First call stack frame that is available is the frame where exception has been thrown
const exceptionSf = first(callStack, sf => sf.source && sf.source.available, undefined);
if (!exceptionSf) {
this.closeExceptionWidget();
return;
}
const sameUri = exceptionSf.source.uri.toString() === model.uri.toString();
if (this.exceptionWidget && !sameUri) {
this.closeExceptionWidget();
} else if (sameUri) {
focusedSf.thread.exceptionInfo.then(exceptionInfo => {
if (exceptionInfo && exceptionSf.range.startLineNumber && exceptionSf.range.startColumn) {
this.showExceptionWidget(exceptionInfo, exceptionSf.range.startLineNumber, exceptionSf.range.startColumn);
}
});
}
}
private showExceptionWidget(exceptionInfo: IExceptionInfo, lineNumber: number, column: number): void {
if (this.exceptionWidget) {
this.exceptionWidget.dispose();
}
this.exceptionWidget = this.instantiationService.createInstance(ExceptionWidget, this.editor, exceptionInfo, lineNumber);
this.exceptionWidget.show({ lineNumber, column }, 0);
}
private closeExceptionWidget(): void {
if (this.exceptionWidget) {
this.exceptionWidget.dispose();
this.exceptionWidget = null;
}
}
// configuration widget
private updateConfigurationWidgetVisibility(): void {
const model = this.editor.getModel();
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();
}
}
public addLaunchConfiguration(): TPromise<any> {
this.telemetryService.publicLog('debug/addLaunchConfiguration');
let configurationsArrayPosition: Position;
const model = this.editor.getModel();
let depthInArray = 0;
let lastProperty: string;
visit(model.getValue(), {
onObjectProperty: (property, offset, length) => {
lastProperty = property;
},
onArrayBegin: (offset: number, length: number) => {
if (lastProperty === 'configurations' && depthInArray === 0) {
configurationsArrayPosition = model.getPositionAt(offset + 1);
}
depthInArray++;
},
onArrayEnd: () => {
depthInArray--;
}
});
this.editor.focus();
if (!configurationsArrayPosition) {
return this.commandService.executeCommand('editor.action.triggerSuggest');
}
const insertLine = (position: Position): TPromise<any> => {
// Check if there are more characters on a line after a "configurations": [, if yes enter a newline
if (this.editor.getModel().getLineLastNonWhitespaceColumn(position.lineNumber) > position.column) {
this.editor.setPosition(position);
CoreEditingCommands.LineBreakInsert.runEditorCommand(null, this.editor, null);
}
// Check if there is already an empty line to insert suggest, if yes just place the cursor
if (this.editor.getModel().getLineLastNonWhitespaceColumn(position.lineNumber + 1) === 0) {
this.editor.setPosition({ lineNumber: position.lineNumber + 1, column: Constants.MAX_SAFE_SMALL_INTEGER });
return TPromise.as(null);
}
this.editor.setPosition(position);
return this.commandService.executeCommand('editor.action.insertLineAfter');
};
return insertLine(configurationsArrayPosition).then(() => this.commandService.executeCommand('editor.action.triggerSuggest'));
}
private static BREAKPOINT_HELPER_DECORATION: IModelDecorationOptions = {
glyphMarginClassName: 'debug-breakpoint-hint-glyph',
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
};
// Inline Decorations
private updateInlineDecorations(stackFrame: IStackFrame): void {
const model = this.editor.getModel();
if (!this.configurationService.getConfiguration<IDebugConfiguration>('debug').inlineValues ||
!model || !stackFrame || model.uri.toString() !== stackFrame.source.uri.toString()) {
if (!this.removeInlineValuesScheduler.isScheduled()) {
this.removeInlineValuesScheduler.schedule();
}
return;
}
this.removeInlineValuesScheduler.cancel();
stackFrame.getMostSpecificScopes(stackFrame.range)
// Get all top level children in the scope chain
.then(scopes => TPromise.join(scopes.map(scope => scope.getChildren()
.then(children => {
let range = new Range(0, 0, stackFrame.range.startLineNumber, stackFrame.range.startColumn);
if (scope.range) {
range = range.setStartPosition(scope.range.startLineNumber, scope.range.startColumn);
}
return this.createInlineValueDecorationsInsideRange(children, range);
}))).then(decorationsPerScope => {
const allDecorations = decorationsPerScope.reduce((previous, current) => previous.concat(current), []);
this.editor.setDecorations(INLINE_VALUE_DECORATION_KEY, allDecorations);
}));
}
private createInlineValueDecorationsInsideRange(expressions: IExpression[], range: Range): IDecorationOptions[] {
const nameValueMap = new Map<string, string>();
for (let expr of expressions) {
nameValueMap.set(expr.name, expr.value);
// Limit the size of map. Too large can have a perf impact
if (nameValueMap.size >= MAX_NUM_INLINE_VALUES) {
break;
}
}
const lineToNamesMap: Map<number, string[]> = new Map<number, string[]>();
const wordToPositionsMap = this.getWordToPositionsMap();
// Compute unique set of names on each line
nameValueMap.forEach((value, name) => {
if (wordToPositionsMap.has(name)) {
for (let position of wordToPositionsMap.get(name)) {
if (range.containsPosition(position)) {
if (!lineToNamesMap.has(position.lineNumber)) {
lineToNamesMap.set(position.lineNumber, []);
}
if (lineToNamesMap.get(position.lineNumber).indexOf(name) === -1) {
lineToNamesMap.get(position.lineNumber).push(name);
}
}
}
}
});
const decorations: IDecorationOptions[] = [];
// Compute decorators for each line
lineToNamesMap.forEach((names, line) => {
const contentText = names.sort((first, second) => {
const content = this.editor.getModel().getLineContent(line);
return content.indexOf(first) - content.indexOf(second);
}).map(name => `${name} = ${nameValueMap.get(name)}`).join(', ');
decorations.push(this.createInlineValueDecoration(line, contentText));
});
return decorations;
}
private createInlineValueDecoration(lineNumber: number, contentText: string): IDecorationOptions {
// If decoratorText is too long, trim and add ellipses. This could happen for minified files with everything on a single line
if (contentText.length > MAX_INLINE_DECORATOR_LENGTH) {
contentText = contentText.substr(0, MAX_INLINE_DECORATOR_LENGTH) + '...';
}
return {
range: {
startLineNumber: lineNumber,
endLineNumber: lineNumber,
startColumn: Constants.MAX_SAFE_SMALL_INTEGER,
endColumn: Constants.MAX_SAFE_SMALL_INTEGER
},
renderOptions: {
after: {
contentText,
backgroundColor: 'rgba(255, 200, 0, 0.2)',
margin: '10px'
},
dark: {
after: {
color: 'rgba(255, 255, 255, 0.5)',
}
},
light: {
after: {
color: 'rgba(0, 0, 0, 0.5)',
}
}
}
};
}
private getWordToPositionsMap(): Map<string, Position[]> {
if (!this.wordToLineNumbersMap) {
this.wordToLineNumbersMap = new Map<string, Position[]>();
const model = this.editor.getModel();
// 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);
// If line is too long then skip the line
if (lineContent.length > MAX_TOKENIZATION_LINE_LEN) {
continue;
}
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);
// Token is a word and not a comment
if (token.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);
if (wordMatch) {
const word = wordMatch[0];
if (!this.wordToLineNumbersMap.has(word)) {
this.wordToLineNumbersMap.set(word, []);
}
this.wordToLineNumbersMap.get(word).push(new Position(lineNumber, token.startOffset));
}
}
}
}
}
return this.wordToLineNumbersMap;
}
public dispose(): void {
if (this.breakpointWidget) {
this.breakpointWidget.dispose();
}
if (this.hoverWidget) {
this.hoverWidget.dispose();
}
if (this.configurationWidget) {
this.configurationWidget.dispose();
}
this.toDispose = lifecycle.dispose(this.toDispose);
}
}

View File

@@ -0,0 +1,357 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as lifecycle from 'vs/base/common/lifecycle';
import { TPromise } from 'vs/base/common/winjs.base';
import { KeyCode } from 'vs/base/common/keyCodes';
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 { Tree } from 'vs/base/parts/tree/browser/treeImpl';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { DefaultController, ICancelableEvent, ClickBehavior } 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';
import { IContentWidget, ICodeEditor, IContentWidgetPosition, ContentWidgetPositionPreference } from 'vs/editor/browser/editorBrowser';
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 { VariablesRenderer, renderExpressionValue, VariablesDataSource } from 'vs/workbench/parts/debug/electron-browser/debugViewer';
import { IListService } from 'vs/platform/list/browser/listService';
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
import { attachListStyler, attachStylerCallback } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { editorHoverBackground, editorHoverBorder } from 'vs/platform/theme/common/colorRegistry';
const $ = dom.$;
const MAX_ELEMENTS_SHOWN = 18;
export class DebugHoverWidget implements IContentWidget {
public static ID = 'debug.hoverWidget';
// editor.IContentWidget.allowEditorOverflow
public allowEditorOverflow = true;
private _isVisible: boolean;
private domNode: HTMLElement;
private tree: ITree;
private showAtPosition: Position;
private highlightDecorations: string[];
private complexValueContainer: HTMLElement;
private treeContainer: HTMLElement;
private complexValueTitle: HTMLElement;
private valueContainer: HTMLElement;
private stoleFocus: boolean;
private toDispose: lifecycle.IDisposable[];
private scrollbar: DomScrollableElement;
constructor(
private editor: ICodeEditor,
private debugService: IDebugService,
private listService: IListService,
instantiationService: IInstantiationService,
private themeService: IThemeService
) {
this.toDispose = [];
this.create(instantiationService);
this.registerListeners();
this.valueContainer = $('.value');
this.valueContainer.tabIndex = 0;
this.valueContainer.setAttribute('role', 'tooltip');
this.scrollbar = new DomScrollableElement(this.valueContainer, { horizontal: ScrollbarVisibility.Hidden });
this.domNode.appendChild(this.scrollbar.getDomNode());
this.toDispose.push(this.scrollbar);
this._isVisible = false;
this.showAtPosition = null;
this.highlightDecorations = [];
this.editor.addContentWidget(this);
this.editor.applyFontInfo(this.domNode);
}
private create(instantiationService: IInstantiationService): void {
this.domNode = $('.debug-hover-widget');
this.complexValueContainer = dom.append(this.domNode, $('.complex-value'));
this.complexValueTitle = dom.append(this.complexValueContainer, $('.title'));
this.treeContainer = dom.append(this.complexValueContainer, $('.debug-hover-tree'));
this.treeContainer.setAttribute('role', 'tree');
this.tree = new Tree(this.treeContainer, {
dataSource: new VariablesDataSource(),
renderer: instantiationService.createInstance(VariablesHoverRenderer),
controller: new DebugHoverController(this.editor)
}, {
indentPixels: 6,
twistiePixels: 15,
ariaLabel: nls.localize('treeAriaLabel', "Debug Hover"),
keyboardSupport: false
});
this.toDispose.push(attachListStyler(this.tree, this.themeService));
this.toDispose.push(this.listService.register(this.tree));
this.toDispose.push(attachStylerCallback(this.themeService, { editorHoverBackground, editorHoverBorder }, colors => {
this.domNode.style.backgroundColor = colors.editorHoverBackground;
if (colors.editorHoverBorder) {
this.domNode.style.border = `1px solid ${colors.editorHoverBorder}`;
} else {
this.domNode.style.border = null;
}
}));
}
private registerListeners(): void {
this.toDispose.push(this.tree.addListener('item:expanded', () => {
this.layoutTree();
}));
this.toDispose.push(this.tree.addListener('item:collapsed', () => {
this.layoutTree();
}));
this.toDispose.push(dom.addStandardDisposableListener(this.domNode, 'keydown', (e: IKeyboardEvent) => {
if (e.equals(KeyCode.Escape)) {
this.hide();
}
}));
this.toDispose.push(this.editor.onDidChangeConfiguration((e: IConfigurationChangedEvent) => {
if (e.fontInfo) {
this.editor.applyFontInfo(this.domNode);
}
}));
}
public isVisible(): boolean {
return this._isVisible;
}
public getId(): string {
return DebugHoverWidget.ID;
}
public getDomNode(): HTMLElement {
return this.domNode;
}
private getExactExpressionRange(lineContent: string, range: Range): Range {
let matchingExpression: string = undefined;
let startOffset = 0;
// Some example supported expressions: myVar.prop, a.b.c.d, myVar?.prop, myVar->prop, MyClass::StaticProp, *myVar
// Match any character except a set of characters which often break interesting sub-expressions
let expression: RegExp = /([^()\[\]{}<>\s+\-/%~#^;=|,`!]|\->)+/g;
let result: RegExpExecArray = undefined;
// First find the full expression under the cursor
while (result = expression.exec(lineContent)) {
let start = result.index + 1;
let end = start + result[0].length;
if (start <= range.startColumn && end >= range.endColumn) {
matchingExpression = result[0];
startOffset = start;
break;
}
}
// If there are non-word characters after the cursor, we want to truncate the expression then.
// For example in expression 'a.b.c.d', if the focus was under 'b', 'a.b' would be evaluated.
if (matchingExpression) {
let subExpression: RegExp = /\w+/g;
let subExpressionResult: RegExpExecArray = undefined;
while (subExpressionResult = subExpression.exec(matchingExpression)) {
let subEnd = subExpressionResult.index + 1 + startOffset + subExpressionResult[0].length;
if (subEnd >= range.endColumn) {
break;
}
}
if (subExpressionResult) {
matchingExpression = matchingExpression.substring(0, subExpression.lastIndex);
}
}
return matchingExpression ?
new Range(range.startLineNumber, startOffset, range.endLineNumber, startOffset + matchingExpression.length - 1) :
new Range(range.startLineNumber, 0, range.endLineNumber, 0);
}
public showAt(range: Range, focus: boolean): TPromise<void> {
const pos = range.getStartPosition();
const process = this.debugService.getViewModel().focusedProcess;
const lineContent = this.editor.getModel().getLineContent(pos.lineNumber);
const expressionRange = this.getExactExpressionRange(lineContent, range);
// use regex to extract the sub-expression #9821
const matchingExpression = lineContent.substring(expressionRange.startColumn - 1, expressionRange.endColumn);
if (!matchingExpression) {
return TPromise.as(this.hide());
}
let promise: TPromise<IExpression>;
if (process.session.capabilities.supportsEvaluateForHovers) {
const result = new Expression(matchingExpression);
promise = result.evaluate(process, this.debugService.getViewModel().focusedStackFrame, 'hover').then(() => result);
} else {
promise = this.findExpressionInStackFrame(matchingExpression.split('.').map(word => word.trim()).filter(word => !!word), expressionRange);
}
return promise.then(expression => {
if (!expression || (expression instanceof Expression && !expression.available)) {
this.hide();
return undefined;
}
this.highlightDecorations = this.editor.deltaDecorations(this.highlightDecorations, [{
range: new Range(pos.lineNumber, expressionRange.startColumn, pos.lineNumber, expressionRange.startColumn + matchingExpression.length),
options: {
className: 'hoverHighlight'
}
}]);
return this.doShow(pos, expression, focus);
});
}
private doFindExpression(container: IExpressionContainer, namesToFind: string[]): TPromise<IExpression> {
if (!container) {
return TPromise.as(null);
}
return container.getChildren().then(children => {
// look for our variable in the list. First find the parents of the hovered variable if there are any.
const filtered = children.filter(v => namesToFind[0] === v.name);
if (filtered.length !== 1) {
return null;
}
if (namesToFind.length === 1) {
return filtered[0];
} else {
return this.doFindExpression(filtered[0], namesToFind.slice(1));
}
});
}
private findExpressionInStackFrame(namesToFind: string[], expressionRange: Range): TPromise<IExpression> {
return this.debugService.getViewModel().focusedStackFrame.getMostSpecificScopes(expressionRange)
.then(scopes => TPromise.join(scopes.map(scope => this.doFindExpression(scope, namesToFind))))
.then(expressions => expressions.filter(exp => !!exp))
// only show if all expressions found have the same value
.then(expressions => (expressions.length > 0 && expressions.every(e => e.value === expressions[0].value)) ? expressions[0] : null);
}
private doShow(position: Position, expression: IExpression, focus: boolean, forceValueHover = false): TPromise<void> {
this.showAtPosition = position;
this._isVisible = true;
this.stoleFocus = focus;
if (!expression.hasChildren || forceValueHover) {
this.complexValueContainer.hidden = true;
this.valueContainer.hidden = false;
renderExpressionValue(expression, this.valueContainer, {
showChanged: false,
preserveWhitespace: true
});
this.valueContainer.title = '';
this.editor.layoutContentWidget(this);
this.scrollbar.scanDomNode();
if (focus) {
this.editor.render();
this.valueContainer.focus();
}
return TPromise.as(null);
}
this.valueContainer.hidden = true;
this.complexValueContainer.hidden = false;
return this.tree.setInput(expression).then(() => {
this.complexValueTitle.textContent = expression.value;
this.complexValueTitle.title = expression.value;
this.layoutTree();
this.editor.layoutContentWidget(this);
this.scrollbar.scanDomNode();
if (focus) {
this.editor.render();
this.tree.DOMFocus();
}
});
}
private layoutTree(): void {
const navigator = this.tree.getNavigator();
let visibleElementsCount = 0;
while (navigator.next()) {
visibleElementsCount++;
}
if (visibleElementsCount === 0) {
this.doShow(this.showAtPosition, this.tree.getInput(), false, true);
} else {
const height = Math.min(visibleElementsCount, MAX_ELEMENTS_SHOWN) * 18;
if (this.treeContainer.clientHeight !== height) {
this.treeContainer.style.height = `${height}px`;
this.tree.layout();
}
}
}
public hide(): void {
if (!this._isVisible) {
return;
}
this._isVisible = false;
this.editor.deltaDecorations(this.highlightDecorations, []);
this.highlightDecorations = [];
this.editor.layoutContentWidget(this);
if (this.stoleFocus) {
this.editor.focus();
}
}
public getPosition(): IContentWidgetPosition {
return this._isVisible ? {
position: this.showAtPosition,
preference: [
ContentWidgetPositionPreference.ABOVE,
ContentWidgetPositionPreference.BELOW
]
} : null;
}
public dispose(): void {
this.toDispose = lifecycle.dispose(this.toDispose);
}
}
class DebugHoverController extends DefaultController {
constructor(private editor: ICodeEditor) {
super({ clickBehavior: ClickBehavior.ON_MOUSE_UP, keyboardSupport: false });
}
protected onLeftClick(tree: ITree, element: any, eventish: ICancelableEvent, origin = 'mouse'): boolean {
if (element.reference > 0) {
super.onLeftClick(tree, element, eventish, origin);
tree.clearFocus();
tree.deselect(element);
this.editor.focus();
}
return true;
}
}
class VariablesHoverRenderer extends VariablesRenderer {
public getHeight(tree: ITree, element: any): number {
return 18;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,533 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as paths from 'vs/base/common/paths';
import { RunOnceScheduler } from 'vs/base/common/async';
import * as dom from 'vs/base/browser/dom';
import * as builder from 'vs/base/browser/builder';
import { TPromise } from 'vs/base/common/winjs.base';
import * as errors from 'vs/base/common/errors';
import { EventType } from 'vs/base/common/events';
import { IAction } from 'vs/base/common/actions';
import { prepareActions } from 'vs/workbench/browser/actions';
import { IHighlightEvent, ITree } from 'vs/base/parts/tree/browser/tree';
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
import { CollapsibleState, ViewSizing } from 'vs/base/browser/ui/splitview/splitview';
import { CollapseAction } from 'vs/workbench/browser/viewlet';
import { CollapsibleView, IViewletViewOptions, IViewOptions } from 'vs/workbench/parts/views/browser/views';
import { IDebugService, State, IBreakpoint, IExpression, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_VARIABLES_FOCUSED } from 'vs/workbench/parts/debug/common/debug';
import { Expression, Variable, ExceptionBreakpoint, FunctionBreakpoint, Thread, StackFrame, Breakpoint, ThreadAndProcessIds } from 'vs/workbench/parts/debug/common/debugModel';
import * as viewer from 'vs/workbench/parts/debug/electron-browser/debugViewer';
import { AddWatchExpressionAction, RemoveAllWatchExpressionsAction, AddFunctionBreakpointAction, ToggleBreakpointsActivatedAction, RemoveAllBreakpointsAction } from 'vs/workbench/parts/debug/browser/debugActions';
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 { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IListService } from 'vs/platform/list/browser/listService';
import { attachListStyler } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
function renderViewTree(container: HTMLElement): HTMLElement {
const treeContainer = document.createElement('div');
dom.addClass(treeContainer, 'debug-view-content');
container.appendChild(treeContainer);
return treeContainer;
}
const $ = builder.$;
const twistiePixels = 20;
export class VariablesView extends CollapsibleView {
private static MEMENTO = 'variablesview.memento';
private onFocusStackFrameScheduler: RunOnceScheduler;
private variablesFocusedContext: IContextKey<boolean>;
private settings: any;
constructor(
initialSize: number,
private options: IViewletViewOptions,
@IContextMenuService contextMenuService: IContextMenuService,
@ITelemetryService private telemetryService: ITelemetryService,
@IDebugService private debugService: IDebugService,
@IKeybindingService keybindingService: IKeybindingService,
@IInstantiationService private instantiationService: IInstantiationService,
@IContextKeyService contextKeyService: IContextKeyService,
@IListService private listService: IListService,
@IThemeService private themeService: IThemeService
) {
super(initialSize, { ...(options as IViewOptions), sizing: ViewSizing.Flexible, ariaHeaderLabel: nls.localize('variablesSection', "Variables Section") }, keybindingService, contextMenuService);
this.settings = options.viewletSettings;
this.variablesFocusedContext = CONTEXT_VARIABLES_FOCUSED.bindTo(contextKeyService);
// Use scheduler to prevent unnecessary flashing
this.onFocusStackFrameScheduler = new RunOnceScheduler(() => {
// Always clear tree highlight to avoid ending up in a broken state #12203
this.tree.clearHighlight();
this.tree.refresh().then(() => {
const stackFrame = this.debugService.getViewModel().focusedStackFrame;
if (stackFrame) {
return stackFrame.getScopes().then(scopes => {
if (scopes.length > 0 && !scopes[0].expensive) {
return this.tree.expand(scopes[0]);
}
return undefined;
});
}
return undefined;
}).done(null, errors.onUnexpectedError);
}, 400);
}
public renderHeader(container: HTMLElement): void {
const titleDiv = $('div.title').appendTo(container);
$('span').text(this.options.name).appendTo(titleDiv);
super.renderHeader(container);
}
public renderBody(container: HTMLElement): void {
dom.addClass(container, 'debug-variables');
this.treeContainer = renderViewTree(container);
this.tree = new Tree(this.treeContainer, {
dataSource: new viewer.VariablesDataSource(),
renderer: this.instantiationService.createInstance(viewer.VariablesRenderer),
accessibilityProvider: new viewer.VariablesAccessibilityProvider(),
controller: this.instantiationService.createInstance(viewer.VariablesController, new viewer.VariablesActionProvider(this.instantiationService), MenuId.DebugVariablesContext)
}, {
ariaLabel: nls.localize('variablesAriaTreeLabel', "Debug Variables"),
twistiePixels,
keyboardSupport: false
});
this.toDispose.push(attachListStyler(this.tree, this.themeService));
this.toDispose.push(this.listService.register(this.tree, [this.variablesFocusedContext]));
const viewModel = this.debugService.getViewModel();
this.tree.setInput(viewModel);
const collapseAction = this.instantiationService.createInstance(CollapseAction, this.tree, false, 'explorer-action collapse-explorer');
this.toolBar.setActions(prepareActions([collapseAction]))();
this.toDispose.push(viewModel.onDidFocusStackFrame(sf => {
// Refresh the tree immediately if it is not visible.
// Otherwise postpone the refresh until user stops stepping.
if (!this.tree.getContentHeight() || sf.explicit) {
this.onFocusStackFrameScheduler.schedule(0);
} else {
this.onFocusStackFrameScheduler.schedule();
}
}));
this.toDispose.push(this.debugService.onDidChangeState(state => {
collapseAction.enabled = state === State.Running || state === State.Stopped;
}));
this.toDispose.push(this.debugService.getViewModel().onDidSelectExpression(expression => {
if (!expression || !(expression instanceof Variable)) {
return;
}
this.tree.refresh(expression, false).then(() => {
this.tree.setHighlight(expression);
this.tree.addOneTimeListener(EventType.HIGHLIGHT, (e: IHighlightEvent) => {
if (!e.highlight) {
this.debugService.getViewModel().setSelectedExpression(null);
}
});
}).done(null, errors.onUnexpectedError);
}));
}
public shutdown(): void {
this.settings[VariablesView.MEMENTO] = (this.state === CollapsibleState.COLLAPSED);
super.shutdown();
}
}
export class WatchExpressionsView extends CollapsibleView {
private static MEMENTO = 'watchexpressionsview.memento';
private onWatchExpressionsUpdatedScheduler: RunOnceScheduler;
private toReveal: IExpression;
private watchExpressionsFocusedContext: IContextKey<boolean>;
private settings: any;
constructor(
size: number,
private options: IViewletViewOptions,
@IContextMenuService contextMenuService: IContextMenuService,
@IDebugService private debugService: IDebugService,
@IKeybindingService keybindingService: IKeybindingService,
@IInstantiationService private instantiationService: IInstantiationService,
@IContextKeyService contextKeyService: IContextKeyService,
@IListService private listService: IListService,
@IThemeService private themeService: IThemeService
) {
super(size, { ...(options as IViewOptions), ariaHeaderLabel: nls.localize('expressionsSection', "Expressions Section"), sizing: ViewSizing.Flexible }, keybindingService, contextMenuService);
this.settings = options.viewletSettings;
this.toDispose.push(this.debugService.getModel().onDidChangeWatchExpressions(we => {
// only expand when a new watch expression is added.
if (we instanceof Expression) {
this.expand();
}
}));
this.watchExpressionsFocusedContext = CONTEXT_WATCH_EXPRESSIONS_FOCUSED.bindTo(contextKeyService);
this.onWatchExpressionsUpdatedScheduler = new RunOnceScheduler(() => {
this.tree.refresh().done(() => {
return this.toReveal instanceof Expression ? this.tree.reveal(this.toReveal) : TPromise.as(true);
}, errors.onUnexpectedError);
}, 50);
}
public renderHeader(container: HTMLElement): void {
const titleDiv = $('div.title').appendTo(container);
$('span').text(this.options.name).appendTo(titleDiv);
super.renderHeader(container);
}
public renderBody(container: HTMLElement): void {
dom.addClass(container, 'debug-watch');
this.treeContainer = renderViewTree(container);
const actionProvider = new viewer.WatchExpressionsActionProvider(this.instantiationService);
this.tree = new Tree(this.treeContainer, {
dataSource: new viewer.WatchExpressionsDataSource(),
renderer: this.instantiationService.createInstance(viewer.WatchExpressionsRenderer, actionProvider, this.actionRunner),
accessibilityProvider: new viewer.WatchExpressionsAccessibilityProvider(),
controller: this.instantiationService.createInstance(viewer.WatchExpressionsController, actionProvider, MenuId.DebugWatchContext),
dnd: this.instantiationService.createInstance(viewer.WatchExpressionsDragAndDrop)
}, {
ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'watchAriaTreeLabel' }, "Debug Watch Expressions"),
twistiePixels,
keyboardSupport: false
});
this.toDispose.push(attachListStyler(this.tree, this.themeService));
this.toDispose.push(this.listService.register(this.tree, [this.watchExpressionsFocusedContext]));
this.tree.setInput(this.debugService.getModel());
const addWatchExpressionAction = this.instantiationService.createInstance(AddWatchExpressionAction, AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL);
const collapseAction = this.instantiationService.createInstance(CollapseAction, this.tree, true, 'explorer-action collapse-explorer');
const removeAllWatchExpressionsAction = this.instantiationService.createInstance(RemoveAllWatchExpressionsAction, RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL);
this.toolBar.setActions(prepareActions([addWatchExpressionAction, collapseAction, removeAllWatchExpressionsAction]))();
this.toDispose.push(this.debugService.getModel().onDidChangeWatchExpressions(we => {
if (!this.onWatchExpressionsUpdatedScheduler.isScheduled()) {
this.onWatchExpressionsUpdatedScheduler.schedule();
}
this.toReveal = we;
}));
this.toDispose.push(this.debugService.getViewModel().onDidSelectExpression(expression => {
if (!expression || !(expression instanceof Expression)) {
return;
}
this.tree.refresh(expression, false).then(() => {
this.tree.setHighlight(expression);
this.tree.addOneTimeListener(EventType.HIGHLIGHT, (e: IHighlightEvent) => {
if (!e.highlight) {
this.debugService.getViewModel().setSelectedExpression(null);
}
});
}).done(null, errors.onUnexpectedError);
}));
}
public shutdown(): void {
this.settings[WatchExpressionsView.MEMENTO] = (this.state === CollapsibleState.COLLAPSED);
super.shutdown();
}
}
export class CallStackView extends CollapsibleView {
private static MEMENTO = 'callstackview.memento';
private pauseMessage: builder.Builder;
private pauseMessageLabel: builder.Builder;
private onCallStackChangeScheduler: RunOnceScheduler;
private settings: any;
constructor(
size: number,
private options: IViewletViewOptions,
@IContextMenuService contextMenuService: IContextMenuService,
@ITelemetryService private telemetryService: ITelemetryService,
@IDebugService private debugService: IDebugService,
@IKeybindingService keybindingService: IKeybindingService,
@IInstantiationService private instantiationService: IInstantiationService,
@IListService private listService: IListService,
@IThemeService private themeService: IThemeService
) {
super(size, { ...(options as IViewOptions), ariaHeaderLabel: nls.localize('callstackSection', "Call Stack Section"), sizing: ViewSizing.Flexible }, keybindingService, contextMenuService);
this.settings = options.viewletSettings;
// Create scheduler to prevent unnecessary flashing of tree when reacting to changes
this.onCallStackChangeScheduler = new RunOnceScheduler(() => {
let newTreeInput: any = this.debugService.getModel();
const processes = this.debugService.getModel().getProcesses();
if (!this.debugService.getViewModel().isMultiProcessView() && processes.length) {
const threads = processes[0].getAllThreads();
// Only show the threads in the call stack if there is more than 1 thread.
newTreeInput = threads.length === 1 ? threads[0] : processes[0];
}
// Only show the global pause message if we do not display threads.
// Otherwise there will be a pause message per thread and there is no need for a global one.
if (newTreeInput instanceof Thread && newTreeInput.stoppedDetails) {
this.pauseMessageLabel.text(newTreeInput.stoppedDetails.description || nls.localize('debugStopped', "Paused on {0}", newTreeInput.stoppedDetails.reason));
if (newTreeInput.stoppedDetails.text) {
this.pauseMessageLabel.title(newTreeInput.stoppedDetails.text);
}
newTreeInput.stoppedDetails.reason === 'exception' ? this.pauseMessageLabel.addClass('exception') : this.pauseMessageLabel.removeClass('exception');
this.pauseMessage.show();
} else {
this.pauseMessage.hide();
}
(this.tree.getInput() === newTreeInput ? this.tree.refresh() : this.tree.setInput(newTreeInput))
.done(() => this.updateTreeSelection(), errors.onUnexpectedError);
}, 50);
}
public renderHeader(container: HTMLElement): void {
const title = $('div.debug-call-stack-title').appendTo(container);
$('span.title').text(this.options.name).appendTo(title);
this.pauseMessage = $('span.pause-message').appendTo(title);
this.pauseMessage.hide();
this.pauseMessageLabel = $('span.label').appendTo(this.pauseMessage);
super.renderHeader(container);
}
public renderBody(container: HTMLElement): void {
dom.addClass(container, 'debug-call-stack');
this.treeContainer = renderViewTree(container);
const actionProvider = this.instantiationService.createInstance(viewer.CallStackActionProvider);
const controller = this.instantiationService.createInstance(viewer.CallStackController, actionProvider, MenuId.DebugCallStackContext);
this.tree = new Tree(this.treeContainer, {
dataSource: this.instantiationService.createInstance(viewer.CallStackDataSource),
renderer: this.instantiationService.createInstance(viewer.CallStackRenderer),
accessibilityProvider: this.instantiationService.createInstance(viewer.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.toDispose.push(attachListStyler(this.tree, this.themeService));
this.toDispose.push(this.listService.register(this.tree));
this.toDispose.push(this.tree.addListener('selection', 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);
}
}
}));
this.toDispose.push(this.debugService.getModel().onDidChangeCallStack(() => {
if (!this.onCallStackChangeScheduler.isScheduled()) {
this.onCallStackChangeScheduler.schedule();
}
}));
this.toDispose.push(this.debugService.getViewModel().onDidFocusStackFrame(() =>
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) {
this.onCallStackChangeScheduler.schedule();
}
}
private updateTreeSelection(): TPromise<void> {
if (!this.tree.getInput()) {
// Tree not initialized yet
return TPromise.as(null);
}
const stackFrame = this.debugService.getViewModel().focusedStackFrame;
const thread = this.debugService.getViewModel().focusedThread;
const process = this.debugService.getViewModel().focusedProcess;
if (!thread) {
if (!process) {
this.tree.clearSelection();
return TPromise.as(null);
}
this.tree.setSelection([process]);
return this.tree.reveal(process);
}
return this.tree.expandAll([thread.process, thread]).then(() => {
if (!stackFrame) {
return TPromise.as(null);
}
this.tree.setSelection([stackFrame]);
return this.tree.reveal(stackFrame);
});
}
public shutdown(): void {
this.settings[CallStackView.MEMENTO] = (this.state === CollapsibleState.COLLAPSED);
super.shutdown();
}
}
export class BreakpointsView extends CollapsibleView {
private static MAX_VISIBLE_FILES = 9;
private static MEMENTO = 'breakopintsview.memento';
private breakpointsFocusedContext: IContextKey<boolean>;
private settings: any;
constructor(
size: number,
private options: IViewletViewOptions,
@IContextMenuService contextMenuService: IContextMenuService,
@IDebugService private debugService: IDebugService,
@IKeybindingService keybindingService: IKeybindingService,
@IInstantiationService private instantiationService: IInstantiationService,
@IContextKeyService contextKeyService: IContextKeyService,
@IListService private listService: IListService,
@IThemeService private themeService: IThemeService
) {
super(size, {
...(options as IViewOptions),
ariaHeaderLabel: nls.localize('breakpointsSection', "Breakpoints Section"),
sizing: ViewSizing.Fixed,
initialBodySize: BreakpointsView.getExpandedBodySize(debugService.getModel().getBreakpoints().length + debugService.getModel().getFunctionBreakpoints().length + debugService.getModel().getExceptionBreakpoints().length)
}, keybindingService, contextMenuService);
this.settings = options.viewletSettings;
this.breakpointsFocusedContext = CONTEXT_BREAKPOINTS_FOCUSED.bindTo(contextKeyService);
this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.onBreakpointsChange()));
}
public renderHeader(container: HTMLElement): void {
const titleDiv = $('div.title').appendTo(container);
$('span').text(this.options.name).appendTo(titleDiv);
super.renderHeader(container);
}
public renderBody(container: HTMLElement): void {
dom.addClass(container, 'debug-breakpoints');
this.treeContainer = renderViewTree(container);
const actionProvider = new viewer.BreakpointsActionProvider(this.instantiationService, this.debugService);
const controller = this.instantiationService.createInstance(viewer.BreakpointsController, actionProvider, MenuId.DebugBreakpointsContext);
this.tree = new Tree(this.treeContainer, {
dataSource: new viewer.BreakpointsDataSource(),
renderer: this.instantiationService.createInstance(viewer.BreakpointsRenderer, actionProvider, this.actionRunner),
accessibilityProvider: this.instantiationService.createInstance(viewer.BreakpointsAccessibilityProvider),
controller,
sorter: {
compare(tree: ITree, element: any, otherElement: any): number {
const first = <IBreakpoint>element;
const second = <IBreakpoint>otherElement;
if (first instanceof ExceptionBreakpoint) {
return -1;
}
if (second instanceof ExceptionBreakpoint) {
return 1;
}
if (first instanceof FunctionBreakpoint) {
return -1;
}
if (second instanceof FunctionBreakpoint) {
return 1;
}
if (first.uri.toString() !== second.uri.toString()) {
return paths.basename(first.uri.fsPath).localeCompare(paths.basename(second.uri.fsPath));
}
if (first.lineNumber === second.lineNumber) {
return first.column - second.column;
}
return first.lineNumber - second.lineNumber;
}
}
}, {
ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'breakpointsAriaTreeLabel' }, "Debug Breakpoints"),
twistiePixels,
keyboardSupport: false
});
this.toDispose.push(attachListStyler(this.tree, this.themeService));
this.toDispose.push(this.listService.register(this.tree, [this.breakpointsFocusedContext]));
this.toDispose.push(this.tree.addListener('selection', event => {
if (event && event.payload && event.payload.origin === 'keyboard') {
const element = this.tree.getFocus();
if (element instanceof Breakpoint) {
controller.openBreakpointSource(element, event, false);
}
}
}));
const debugModel = this.debugService.getModel();
this.tree.setInput(debugModel);
this.toDispose.push(this.debugService.getViewModel().onDidSelectFunctionBreakpoint(fbp => {
if (!fbp || !(fbp instanceof FunctionBreakpoint)) {
return;
}
this.tree.refresh(fbp, false).then(() => {
this.tree.setHighlight(fbp);
this.tree.addOneTimeListener(EventType.HIGHLIGHT, (e: IHighlightEvent) => {
if (!e.highlight) {
this.debugService.getViewModel().setSelectedFunctionBreakpoint(null);
}
});
}).done(null, errors.onUnexpectedError);
}));
}
public getActions(): IAction[] {
return [
this.instantiationService.createInstance(AddFunctionBreakpointAction, AddFunctionBreakpointAction.ID, AddFunctionBreakpointAction.LABEL),
this.instantiationService.createInstance(ToggleBreakpointsActivatedAction, ToggleBreakpointsActivatedAction.ID, ToggleBreakpointsActivatedAction.ACTIVATE_LABEL),
this.instantiationService.createInstance(RemoveAllBreakpointsAction, RemoveAllBreakpointsAction.ID, RemoveAllBreakpointsAction.LABEL)
];
}
private onBreakpointsChange(): void {
const model = this.debugService.getModel();
this.setBodySize(BreakpointsView.getExpandedBodySize(
model.getBreakpoints().length + model.getExceptionBreakpoints().length + model.getFunctionBreakpoints().length));
if (this.tree) {
this.tree.refresh();
}
}
private static getExpandedBodySize(length: number): number {
return Math.min(BreakpointsView.MAX_VISIBLE_FILES, length) * 22;
}
public shutdown(): void {
this.settings[BreakpointsView.MEMENTO] = (this.state === CollapsibleState.COLLAPSED);
super.shutdown();
}
}

View File

@@ -0,0 +1,79 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { TPromise } from 'vs/base/common/winjs.base';
import { ITree } from 'vs/base/parts/tree/browser/tree';
import { removeAnsiEscapeCodes } from 'vs/base/common/strings';
import { Variable } from 'vs/workbench/parts/debug/common/debugModel';
import { IDebugService, IStackFrame } from 'vs/workbench/parts/debug/common/debug';
import { clipboard } from 'electron';
export class CopyValueAction extends Action {
static 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) {
super(id, label, 'debug-action copy-value');
}
public run(): TPromise<any> {
if (this.value instanceof Variable) {
const frameId = this.debugService.getViewModel().focusedStackFrame.frameId;
const process = this.debugService.getViewModel().focusedProcess;
return process.session.evaluate({ expression: this.value.evaluateName, frameId }).then(result => {
clipboard.writeText(result.body.result);
}, err => clipboard.writeText(this.value.value));
}
clipboard.writeText(this.value);
return TPromise.as(null);
}
}
export class CopyAction extends Action {
static ID = 'workbench.debug.action.copy';
static LABEL = nls.localize('copy', "Copy");
public run(): TPromise<any> {
clipboard.writeText(window.getSelection().toString());
return TPromise.as(null);
}
}
export class CopyAllAction extends Action {
static ID = 'workbench.debug.action.copyAll';
static LABEL = nls.localize('copyAll', "Copy All");
constructor(id: string, label: string, private tree: ITree) {
super(id, label);
}
public run(): TPromise<any> {
let text = '';
const navigator = this.tree.getNavigator();
// skip first navigator element - the root node
while (navigator.next()) {
if (text) {
text += `\n`;
}
text += navigator.current().toString();
}
clipboard.writeText(removeAnsiEscapeCodes(text));
return TPromise.as(null);
}
}
export class CopyStackTraceAction extends Action {
static ID = 'workbench.action.debug.copyStackTrace';
static LABEL = nls.localize('copyStackTrace', "Copy Call Stack");
public run(frame: IStackFrame): TPromise<any> {
clipboard.writeText(frame.thread.getCallStack().map(sf => sf.toString()).join('\n'));
return TPromise.as(null);
}
}

View File

@@ -0,0 +1,534 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import nls = require('vs/nls');
import cp = require('child_process');
import net = require('net');
import uri from 'vs/base/common/uri';
import Event, { Emitter } from 'vs/base/common/event';
import platform = require('vs/base/common/platform');
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';
import debug = require('vs/workbench/parts/debug/common/debug');
import { Adapter } from 'vs/workbench/parts/debug/node/debugAdapter';
import { V8Protocol } from 'vs/workbench/parts/debug/node/v8Protocol';
import { IOutputService } from 'vs/workbench/parts/output/common/output';
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';
export interface SessionExitedEvent extends debug.DebugEvent {
body: {
exitCode: number,
sessionId: string
};
}
export interface SessionTerminatedEvent extends debug.DebugEvent {
body: {
restart?: boolean,
sessionId: string
};
}
export class RawDebugSession extends V8Protocol implements debug.ISession {
public emittedStopped: boolean;
public readyForBreakpoints: boolean;
private serverProcess: cp.ChildProcess;
private socket: net.Socket = null;
private cachedInitServer: TPromise<void>;
private startTime: number;
public disconnected: boolean;
private sentPromises: TPromise<DebugProtocol.Response>[];
private _capabilities: DebugProtocol.Capabilities;
private allThreadsContinued: boolean;
private _onDidInitialize: Emitter<DebugProtocol.InitializedEvent>;
private _onDidStop: Emitter<DebugProtocol.StoppedEvent>;
private _onDidContinued: Emitter<DebugProtocol.ContinuedEvent>;
private _onDidTerminateDebugee: Emitter<SessionTerminatedEvent>;
private _onDidExitAdapter: Emitter<SessionExitedEvent>;
private _onDidThread: Emitter<DebugProtocol.ThreadEvent>;
private _onDidOutput: Emitter<DebugProtocol.OutputEvent>;
private _onDidBreakpoint: Emitter<DebugProtocol.BreakpointEvent>;
private _onDidCustomEvent: Emitter<debug.DebugEvent>;
private _onDidEvent: Emitter<DebugProtocol.Event>;
constructor(
id: string,
private debugServerPort: number,
private adapter: Adapter,
private customTelemetryService: ITelemetryService,
public root: uri,
@IMessageService private messageService: IMessageService,
@ITelemetryService private telemetryService: ITelemetryService,
@IOutputService private outputService: IOutputService,
@ITerminalService private terminalService: ITerminalService,
@IExternalTerminalService private nativeTerminalService: IExternalTerminalService,
@IConfigurationService private configurationService: IConfigurationService
) {
super(id);
this.emittedStopped = false;
this.readyForBreakpoints = false;
this.allThreadsContinued = true;
this.sentPromises = [];
this._onDidInitialize = new Emitter<DebugProtocol.InitializedEvent>();
this._onDidStop = new Emitter<DebugProtocol.StoppedEvent>();
this._onDidContinued = new Emitter<DebugProtocol.ContinuedEvent>();
this._onDidTerminateDebugee = new Emitter<SessionTerminatedEvent>();
this._onDidExitAdapter = new Emitter<SessionExitedEvent>();
this._onDidThread = new Emitter<DebugProtocol.ThreadEvent>();
this._onDidOutput = new Emitter<DebugProtocol.OutputEvent>();
this._onDidBreakpoint = new Emitter<DebugProtocol.BreakpointEvent>();
this._onDidCustomEvent = new Emitter<debug.DebugEvent>();
this._onDidEvent = new Emitter<DebugProtocol.Event>();
}
public get onDidInitialize(): Event<DebugProtocol.InitializedEvent> {
return this._onDidInitialize.event;
}
public get onDidStop(): Event<DebugProtocol.StoppedEvent> {
return this._onDidStop.event;
}
public get onDidContinued(): Event<DebugProtocol.ContinuedEvent> {
return this._onDidContinued.event;
}
public get onDidTerminateDebugee(): Event<SessionTerminatedEvent> {
return this._onDidTerminateDebugee.event;
}
public get onDidExitAdapter(): Event<SessionExitedEvent> {
return this._onDidExitAdapter.event;
}
public get onDidThread(): Event<DebugProtocol.ThreadEvent> {
return this._onDidThread.event;
}
public get onDidOutput(): Event<DebugProtocol.OutputEvent> {
return this._onDidOutput.event;
}
public get onDidBreakpoint(): Event<DebugProtocol.BreakpointEvent> {
return this._onDidBreakpoint.event;
}
public get onDidCustomEvent(): Event<debug.DebugEvent> {
return this._onDidCustomEvent.event;
}
public get onDidEvent(): Event<DebugProtocol.Event> {
return this._onDidEvent.event;
}
private initServer(): TPromise<void> {
if (this.cachedInitServer) {
return this.cachedInitServer;
}
const serverPromise = this.debugServerPort ? this.connectServer(this.debugServerPort) : this.startServer();
this.cachedInitServer = serverPromise.then(() => {
this.startTime = new Date().getTime();
}, err => {
this.cachedInitServer = null;
return TPromise.wrapError(err);
});
return this.cachedInitServer;
}
public custom(request: string, args: any): TPromise<DebugProtocol.Response> {
return this.send(request, args);
}
protected send<R extends DebugProtocol.Response>(command: string, args: any, cancelOnDisconnect = true): TPromise<R> {
return this.initServer().then(() => {
const promise = super.send<R>(command, args).then(response => response, (errorResponse: DebugProtocol.ErrorResponse) => {
const error = errorResponse && errorResponse.body ? errorResponse.body.error : null;
const errorMessage = errorResponse ? errorResponse.message : '';
const telemetryMessage = error ? debug.formatPII(error.format, true, error.variables) : errorMessage;
if (error && error.sendTelemetry) {
this.telemetryService.publicLog('debugProtocolErrorResponse', { error: telemetryMessage });
if (this.customTelemetryService) {
this.customTelemetryService.publicLog('debugProtocolErrorResponse', { error: telemetryMessage });
}
}
const userMessage = error ? debug.formatPII(error.format, false, error.variables) : errorMessage;
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, () => {
window.open(error.url);
return TPromise.as(null);
})]
}));
}
return errors.isPromiseCanceledError(errorResponse) ? undefined : TPromise.wrapError<R>(new Error(userMessage));
});
if (cancelOnDisconnect) {
this.sentPromises.push(promise);
}
return promise;
});
}
protected onEvent(event: debug.DebugEvent): void {
event.sessionId = this.getId();
if (event.event === 'initialized') {
this.readyForBreakpoints = true;
this._onDidInitialize.fire(event);
} else if (event.event === 'stopped') {
this.emittedStopped = true;
this._onDidStop.fire(<DebugProtocol.StoppedEvent>event);
} else if (event.event === 'continued') {
this.allThreadsContinued = (<DebugProtocol.ContinuedEvent>event).body.allThreadsContinued === false ? false : true;
this._onDidContinued.fire(<DebugProtocol.ContinuedEvent>event);
} else if (event.event === 'thread') {
this._onDidThread.fire(<DebugProtocol.ThreadEvent>event);
} else if (event.event === 'output') {
this._onDidOutput.fire(<DebugProtocol.OutputEvent>event);
} else if (event.event === 'breakpoint') {
this._onDidBreakpoint.fire(<DebugProtocol.BreakpointEvent>event);
} else if (event.event === 'terminated') {
this._onDidTerminateDebugee.fire(<SessionTerminatedEvent>event);
} else if (event.event === 'exit') {
this._onDidExitAdapter.fire(<SessionExitedEvent>event);
} else {
this._onDidCustomEvent.fire(event);
}
this._onDidEvent.fire(event);
}
public get capabilities(): DebugProtocol.Capabilities {
return this._capabilities || {};
}
public initialize(args: DebugProtocol.InitializeRequestArguments): TPromise<DebugProtocol.InitializeResponse> {
return this.send('initialize', args).then(response => this.readCapabilities(response));
}
private readCapabilities(response: DebugProtocol.Response): DebugProtocol.Response {
if (response) {
this._capabilities = objects.mixin(this._capabilities, response.body);
}
return response;
}
public launch(args: DebugProtocol.LaunchRequestArguments): TPromise<DebugProtocol.LaunchResponse> {
return this.send('launch', args).then(response => this.readCapabilities(response));
}
public attach(args: DebugProtocol.AttachRequestArguments): TPromise<DebugProtocol.AttachResponse> {
return this.send('attach', args).then(response => this.readCapabilities(response));
}
public next(args: DebugProtocol.NextArguments): TPromise<DebugProtocol.NextResponse> {
return this.send('next', args).then(response => {
this.fireFakeContinued(args.threadId);
return response;
});
}
public stepIn(args: DebugProtocol.StepInArguments): TPromise<DebugProtocol.StepInResponse> {
return this.send('stepIn', args).then(response => {
this.fireFakeContinued(args.threadId);
return response;
});
}
public stepOut(args: DebugProtocol.StepOutArguments): TPromise<DebugProtocol.StepOutResponse> {
return this.send('stepOut', args).then(response => {
this.fireFakeContinued(args.threadId);
return response;
});
}
public continue(args: DebugProtocol.ContinueArguments): TPromise<DebugProtocol.ContinueResponse> {
return this.send<DebugProtocol.ContinueResponse>('continue', args).then(response => {
if (response && response.body && response.body.allThreadsContinued !== undefined) {
this.allThreadsContinued = response.body.allThreadsContinued;
}
this.fireFakeContinued(args.threadId, this.allThreadsContinued);
return response;
});
}
public pause(args: DebugProtocol.PauseArguments): TPromise<DebugProtocol.PauseResponse> {
return this.send('pause', args);
}
public setVariable(args: DebugProtocol.SetVariableArguments): TPromise<DebugProtocol.SetVariableResponse> {
return this.send<DebugProtocol.SetVariableResponse>('setVariable', args);
}
public restartFrame(args: DebugProtocol.RestartFrameArguments, threadId: number): TPromise<DebugProtocol.RestartFrameResponse> {
return this.send('restartFrame', args).then(response => {
this.fireFakeContinued(threadId);
return response;
});
}
public completions(args: DebugProtocol.CompletionsArguments): TPromise<DebugProtocol.CompletionsResponse> {
return this.send<DebugProtocol.CompletionsResponse>('completions', args);
}
public disconnect(restart = false, force = false): TPromise<DebugProtocol.DisconnectResponse> {
if (this.disconnected && force) {
return this.stopServer();
}
// Cancel all sent promises on disconnect so debug trees are not left in a broken state #3666.
// Give a 1s timeout to give a chance for some promises to complete.
setTimeout(() => {
this.sentPromises.forEach(p => p && p.cancel());
this.sentPromises = [];
}, 1000);
if ((this.serverProcess || this.socket) && !this.disconnected) {
// point of no return: from now on don't report any errors
this.disconnected = true;
return this.send('disconnect', { restart: restart }, false).then(() => this.stopServer(), () => this.stopServer());
}
return TPromise.as(null);
}
public setBreakpoints(args: DebugProtocol.SetBreakpointsArguments): TPromise<DebugProtocol.SetBreakpointsResponse> {
return this.send<DebugProtocol.SetBreakpointsResponse>('setBreakpoints', args);
}
public setFunctionBreakpoints(args: DebugProtocol.SetFunctionBreakpointsArguments): TPromise<DebugProtocol.SetFunctionBreakpointsResponse> {
return this.send<DebugProtocol.SetFunctionBreakpointsResponse>('setFunctionBreakpoints', args);
}
public setExceptionBreakpoints(args: DebugProtocol.SetExceptionBreakpointsArguments): TPromise<DebugProtocol.SetExceptionBreakpointsResponse> {
return this.send<DebugProtocol.SetExceptionBreakpointsResponse>('setExceptionBreakpoints', args);
}
public configurationDone(): TPromise<DebugProtocol.ConfigurationDoneResponse> {
return this.send('configurationDone', null);
}
public stackTrace(args: DebugProtocol.StackTraceArguments): TPromise<DebugProtocol.StackTraceResponse> {
return this.send<DebugProtocol.StackTraceResponse>('stackTrace', args);
}
public exceptionInfo(args: DebugProtocol.ExceptionInfoArguments): TPromise<DebugProtocol.ExceptionInfoResponse> {
return this.send<DebugProtocol.ExceptionInfoResponse>('exceptionInfo', args);
}
public scopes(args: DebugProtocol.ScopesArguments): TPromise<DebugProtocol.ScopesResponse> {
return this.send<DebugProtocol.ScopesResponse>('scopes', args);
}
public variables(args: DebugProtocol.VariablesArguments): TPromise<DebugProtocol.VariablesResponse> {
return this.send<DebugProtocol.VariablesResponse>('variables', args);
}
public source(args: DebugProtocol.SourceArguments): TPromise<DebugProtocol.SourceResponse> {
return this.send<DebugProtocol.SourceResponse>('source', args);
}
public threads(): TPromise<DebugProtocol.ThreadsResponse> {
return this.send<DebugProtocol.ThreadsResponse>('threads', null);
}
public evaluate(args: DebugProtocol.EvaluateArguments): TPromise<DebugProtocol.EvaluateResponse> {
return this.send<DebugProtocol.EvaluateResponse>('evaluate', args);
}
public stepBack(args: DebugProtocol.StepBackArguments): TPromise<DebugProtocol.StepBackResponse> {
return this.send('stepBack', args).then(response => {
this.fireFakeContinued(args.threadId);
return response;
});
}
public reverseContinue(args: DebugProtocol.ReverseContinueArguments): TPromise<DebugProtocol.ReverseContinueResponse> {
return this.send('reverseContinue', args).then(response => {
this.fireFakeContinued(args.threadId);
return response;
});
}
public getLengthInSeconds(): number {
return (new Date().getTime() - this.startTime) / 1000;
}
protected dispatchRequest(request: DebugProtocol.Request, response: DebugProtocol.Response): void {
if (request.command === 'runInTerminal') {
TerminalSupport.runInTerminal(this.terminalService, this.nativeTerminalService, this.configurationService, <DebugProtocol.RunInTerminalRequestArguments>request.arguments, <DebugProtocol.RunInTerminalResponse>response).then(() => {
this.sendResponse(response);
}, e => {
response.success = false;
response.message = e.message;
this.sendResponse(response);
});
} else if (request.command === 'handshake') {
try {
const vsda = <any>require.__$__nodeRequire('vsda');
const obj = new vsda.signer();
const sig = obj.sign(request.arguments.value);
response.body = {
signature: sig
};
this.sendResponse(response);
} catch (e) {
response.success = false;
response.message = e.message;
this.sendResponse(response);
}
} else {
response.success = false;
response.message = `unknown request '${request.command}'`;
this.sendResponse(response);
}
}
private fireFakeContinued(threadId: number, allThreadsContinued = false): void {
this._onDidContinued.fire({
type: 'event',
event: 'continued',
body: {
threadId,
allThreadsContinued
},
seq: undefined
});
}
private connectServer(port: number): TPromise<void> {
return new TPromise<void>((c, e) => {
this.socket = net.createConnection(port, '127.0.0.1', () => {
this.connect(this.socket, <any>this.socket);
c(null);
});
this.socket.on('error', (err: any) => {
e(err);
});
this.socket.on('close', () => this.onServerExit());
});
}
private startServer(): TPromise<any> {
return this.adapter.getAdapterExecutable(this.root).then(ae => this.launchServer(ae).then(() => {
this.serverProcess.on('error', (err: Error) => this.onServerError(err));
this.serverProcess.on('exit', (code: number, signal: string) => this.onServerExit());
const sanitize = (s: string) => s.toString().replace(/\r?\n$/mg, '');
// this.serverProcess.stdout.on('data', (data: string) => {
// console.log('%c' + sanitize(data), 'background: #ddd; font-style: italic;');
// });
this.serverProcess.stderr.on('data', (data: string) => {
this.outputService.getChannel(ExtensionsChannelId).append(sanitize(data));
});
this.connect(this.serverProcess.stdout, this.serverProcess.stdin);
}));
}
private launchServer(launch: debug.IAdapterExecutable): TPromise<void> {
return new TPromise<void>((c, e) => {
if (launch.command === 'node') {
if (Array.isArray(launch.args) && launch.args.length > 0) {
stdfork.fork(launch.args[0], launch.args.slice(1), {}, (err, child) => {
if (err) {
e(new Error(nls.localize('unableToLaunchDebugAdapter', "Unable to launch debug adapter from '{0}'.", launch.args[0])));
}
this.serverProcess = child;
c(null);
});
} else {
e(new Error(nls.localize('unableToLaunchDebugAdapterNoArgs', "Unable to launch debug adapter.")));
}
} else {
this.serverProcess = cp.spawn(launch.command, launch.args, {
stdio: [
'pipe', // stdin
'pipe', // stdout
'pipe' // stderr
],
});
c(null);
}
});
}
private stopServer(): TPromise<any> {
if (this.socket !== null) {
this.socket.end();
this.cachedInitServer = null;
}
this.onEvent({ event: 'exit', type: 'event', seq: 0 });
if (!this.serverProcess) {
return TPromise.as(null);
}
this.disconnected = true;
let ret: TPromise<void>;
// when killing a process in windows its child
// processes are *not* killed but become root
// processes. Therefore we use TASKKILL.EXE
if (platform.isWindows) {
ret = new TPromise<void>((c, e) => {
const killer = cp.exec(`taskkill /F /T /PID ${this.serverProcess.pid}`, function (err, stdout, stderr) {
if (err) {
return e(err);
}
});
killer.on('exit', c);
killer.on('error', e);
});
} else {
this.serverProcess.kill('SIGTERM');
ret = TPromise.as(null);
}
return ret;
}
protected onServerError(err: Error): void {
this.messageService.show(severity.Error, nls.localize('stoppingDebugAdapter', "{0}. Stopping the debug adapter.", err.message));
this.stopServer().done(null, errors.onUnexpectedError);
}
private onServerExit(): void {
this.serverProcess = null;
this.cachedInitServer = null;
if (!this.disconnected) {
this.messageService.show(severity.Error, nls.localize('debugAdapterCrash', "Debug adapter process has terminated unexpectedly"));
}
this.onEvent({ event: 'exit', type: 'event', seq: 0 });
}
public dispose(): void {
this.disconnect().done(null, errors.onUnexpectedError);
}
}

View File

@@ -0,0 +1,414 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import 'vs/css!vs/workbench/parts/debug/browser/media/repl';
import * as nls from 'vs/nls';
import uri from 'vs/base/common/uri';
import { wireCancellationToken } from 'vs/base/common/async';
import { TPromise } from 'vs/base/common/winjs.base';
import * as errors from 'vs/base/common/errors';
import { IAction } from 'vs/base/common/actions';
import { Dimension, Builder } from 'vs/base/browser/builder';
import * as dom from 'vs/base/browser/dom';
import { isMacintosh } from 'vs/base/common/platform';
import { CancellationToken } from 'vs/base/common/cancellation';
import { KeyCode } from 'vs/base/common/keyCodes';
import { ITree, ITreeOptions } from 'vs/base/parts/tree/browser/tree';
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
import { Context as SuggestContext } from 'vs/editor/contrib/suggest/browser/suggest';
import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController';
import { IReadOnlyModel, ICommonCodeEditor } from 'vs/editor/common/editorCommon';
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
import { Position } from 'vs/editor/common/core/position';
import * as modes from 'vs/editor/common/modes';
import { editorAction, ServicesAccessor, EditorAction, EditorCommand, CommonEditorRegistry } from 'vs/editor/common/editorCommonExtensions';
import { IModelService } from 'vs/editor/common/services/modelService';
import { MenuId } from 'vs/platform/actions/common/actions';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { ReplExpressionsRenderer, ReplExpressionsController, ReplExpressionsDataSource, ReplExpressionsActionProvider, ReplExpressionsAccessibilityProvider } from 'vs/workbench/parts/debug/electron-browser/replViewer';
import { ReplInputEditor } from 'vs/workbench/parts/debug/electron-browser/replEditor';
import * as debug from 'vs/workbench/parts/debug/common/debug';
import { ClearReplAction } from 'vs/workbench/parts/debug/browser/debugActions';
import { ReplHistory } from 'vs/workbench/parts/debug/common/replHistory';
import { Panel } from 'vs/workbench/browser/panel';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IListService } from 'vs/platform/list/browser/listService';
import { attachListStyler } from 'vs/platform/theme/common/styler';
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { clipboard } from 'electron';
const $ = dom.$;
const replTreeOptions: ITreeOptions = {
twistiePixels: 20,
ariaLabel: nls.localize('replAriaLabel', "Read Eval Print Loop Panel"),
keyboardSupport: false
};
const HISTORY_STORAGE_KEY = 'debug.repl.history';
const IPrivateReplService = createDecorator<IPrivateReplService>('privateReplService');
export interface IPrivateReplService {
_serviceBrand: any;
navigateHistory(previous: boolean): void;
acceptReplInput(): void;
getVisibleContent(): string;
}
export class Repl extends Panel implements IPrivateReplService {
public _serviceBrand: any;
private static HALF_WIDTH_TYPICAL = 'n';
private static HISTORY: ReplHistory;
private static REFRESH_DELAY = 500; // delay in ms to refresh the repl for new elements to show
private static REPL_INPUT_INITIAL_HEIGHT = 19;
private static REPL_INPUT_MAX_HEIGHT = 170;
private tree: ITree;
private renderer: ReplExpressionsRenderer;
private characterWidthSurveyor: HTMLElement;
private treeContainer: HTMLElement;
private replInput: ReplInputEditor;
private replInputContainer: HTMLElement;
private refreshTimeoutHandle: number;
private actions: IAction[];
private dimension: Dimension;
private replInputHeight: number;
constructor(
@debug.IDebugService private debugService: debug.IDebugService,
@ITelemetryService telemetryService: ITelemetryService,
@IInstantiationService private instantiationService: IInstantiationService,
@IStorageService private storageService: IStorageService,
@IPanelService private panelService: IPanelService,
@IThemeService protected themeService: IThemeService,
@IModelService private modelService: IModelService,
@IContextKeyService private contextKeyService: IContextKeyService,
@IListService private listService: IListService
) {
super(debug.REPL_ID, telemetryService, themeService);
this.replInputHeight = Repl.REPL_INPUT_INITIAL_HEIGHT;
this.registerListeners();
}
private registerListeners(): void {
this.toUnbind.push(this.debugService.getModel().onDidChangeReplElements(() => {
this.refreshReplElements(this.debugService.getModel().getReplElements().length === 0);
}));
this.toUnbind.push(this.panelService.onDidPanelOpen(panel => this.refreshReplElements(true)));
}
private refreshReplElements(noDelay: boolean): void {
if (this.tree && this.isVisible()) {
if (this.refreshTimeoutHandle) {
return; // refresh already triggered
}
const delay = noDelay ? 0 : Repl.REFRESH_DELAY;
this.refreshTimeoutHandle = setTimeout(() => {
this.refreshTimeoutHandle = null;
const previousScrollPosition = this.tree.getScrollPosition();
this.tree.refresh().then(() => {
if (previousScrollPosition === 1 || previousScrollPosition === 0) {
// Only scroll if we were scrolled all the way down before tree refreshed #10486
this.tree.setScrollPosition(1);
}
}, errors.onUnexpectedError);
}, delay);
}
}
public create(parent: Builder): TPromise<void> {
super.create(parent);
const container = dom.append(parent.getHTMLElement(), $('.repl'));
this.treeContainer = dom.append(container, $('.repl-tree'));
this.createReplInput(container);
this.characterWidthSurveyor = dom.append(container, $('.surveyor'));
this.characterWidthSurveyor.textContent = Repl.HALF_WIDTH_TYPICAL;
for (let i = 0; i < 10; i++) {
this.characterWidthSurveyor.textContent += this.characterWidthSurveyor.textContent;
}
this.characterWidthSurveyor.style.fontSize = isMacintosh ? '12px' : '14px';
this.renderer = this.instantiationService.createInstance(ReplExpressionsRenderer);
const controller = this.instantiationService.createInstance(ReplExpressionsController, new ReplExpressionsActionProvider(this.instantiationService), MenuId.DebugConsoleContext);
controller.toFocusOnClick = this.replInput;
this.tree = new Tree(this.treeContainer, {
dataSource: new ReplExpressionsDataSource(),
renderer: this.renderer,
accessibilityProvider: new ReplExpressionsAccessibilityProvider(),
controller
}, replTreeOptions);
this.toUnbind.push(attachListStyler(this.tree, this.themeService));
this.toUnbind.push(this.listService.register(this.tree));
if (!Repl.HISTORY) {
Repl.HISTORY = new ReplHistory(JSON.parse(this.storageService.get(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')));
}
return this.tree.setInput(this.debugService.getModel());
}
private createReplInput(container: HTMLElement): void {
this.replInputContainer = dom.append(container, $('.repl-input-wrapper'));
const scopedContextKeyService = this.contextKeyService.createScoped(this.replInputContainer);
this.toUnbind.push(scopedContextKeyService);
debug.CONTEXT_IN_DEBUG_REPL.bindTo(scopedContextKeyService).set(true);
const onFirstReplLine = debug.CONTEXT_ON_FIRST_DEBUG_REPL_LINE.bindTo(scopedContextKeyService);
onFirstReplLine.set(true);
const onLastReplLine = debug.CONTEXT_ON_LAST_DEBUG_REPL_LINE.bindTo(scopedContextKeyService);
onLastReplLine.set(true);
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, 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);
const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame;
const frameId = focusedStackFrame ? focusedStackFrame.frameId : undefined;
const focusedProcess = this.debugService.getViewModel().focusedProcess;
const completions = focusedProcess ? focusedProcess.completions(frameId, text, position, overwriteBefore) : TPromise.as([]);
return wireCancellationToken(token, completions.then(suggestions => ({
suggestions
})));
}
});
this.toUnbind.push(this.replInput.onDidScrollChange(e => {
if (!e.scrollHeightChanged) {
return;
}
this.replInputHeight = Math.max(Repl.REPL_INPUT_INITIAL_HEIGHT, Math.min(Repl.REPL_INPUT_MAX_HEIGHT, e.scrollHeight, this.dimension.height));
this.layout(this.dimension);
}));
this.toUnbind.push(this.replInput.onDidChangeCursorPosition(e => {
onFirstReplLine.set(e.position.lineNumber === 1);
onLastReplLine.set(e.position.lineNumber === this.replInput.getModel().getLineCount());
}));
this.toUnbind.push(dom.addStandardDisposableListener(this.replInputContainer, dom.EventType.FOCUS, () => dom.addClass(this.replInputContainer, 'synthetic-focus')));
this.toUnbind.push(dom.addStandardDisposableListener(this.replInputContainer, dom.EventType.BLUR, () => dom.removeClass(this.replInputContainer, 'synthetic-focus')));
}
public navigateHistory(previous: boolean): void {
const historyInput = previous ? Repl.HISTORY.previous() : Repl.HISTORY.next();
if (historyInput) {
Repl.HISTORY.remember(this.replInput.getValue(), previous);
this.replInput.setValue(historyInput);
// always leave cursor at the end.
this.replInput.setPosition({ lineNumber: 1, column: historyInput.length + 1 });
}
}
public acceptReplInput(): void {
this.debugService.addReplExpression(this.replInput.getValue());
Repl.HISTORY.evaluated(this.replInput.getValue());
this.replInput.setValue('');
// Trigger a layout to shrink a potential multi line input
this.replInputHeight = Repl.REPL_INPUT_INITIAL_HEIGHT;
this.layout(this.dimension);
}
public getVisibleContent(): string {
let text = '';
const navigator = this.tree.getNavigator();
// skip first navigator element - the root node
while (navigator.next()) {
if (text) {
text += `\n`;
}
text += navigator.current().toString();
}
return text;
}
public layout(dimension: Dimension): void {
this.dimension = dimension;
if (this.tree) {
this.renderer.setWidth(dimension.width - 25, this.characterWidthSurveyor.clientWidth / this.characterWidthSurveyor.textContent.length);
const treeHeight = dimension.height - this.replInputHeight;
this.treeContainer.style.height = `${treeHeight}px`;
this.tree.layout(treeHeight);
}
this.replInputContainer.style.height = `${this.replInputHeight}px`;
this.replInput.layout({ width: dimension.width - 20, height: this.replInputHeight });
}
public focus(): void {
this.replInput.focus();
}
public getActions(): IAction[] {
if (!this.actions) {
this.actions = [
this.instantiationService.createInstance(ClearReplAction, ClearReplAction.ID, ClearReplAction.LABEL)
];
this.actions.forEach(a => {
this.toUnbind.push(a);
});
}
return this.actions;
}
public shutdown(): void {
const replHistory = Repl.HISTORY.save();
if (replHistory.length) {
this.storageService.store(HISTORY_STORAGE_KEY, JSON.stringify(replHistory), StorageScope.WORKSPACE);
} else {
this.storageService.remove(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE);
}
}
private getReplInputOptions(): IEditorOptions {
return {
wordWrap: 'on',
overviewRulerLanes: 0,
glyphMargin: false,
lineNumbers: 'off',
folding: false,
selectOnLineNumbers: false,
selectionHighlight: false,
scrollbar: {
horizontal: 'hidden'
},
lineDecorationsWidth: 0,
scrollBeyondLastLine: false,
renderLineHighlight: 'none',
fixedOverflowWidgets: true,
acceptSuggestionOnEnter: 'smart',
minimap: {
enabled: false
}
};
}
public dispose(): void {
this.replInput.dispose();
super.dispose();
}
}
@editorAction
class ReplHistoryPreviousAction extends EditorAction {
constructor() {
super({
id: 'repl.action.historyPrevious',
label: nls.localize('actions.repl.historyPrevious', "History Previous"),
alias: 'History Previous',
precondition: debug.CONTEXT_IN_DEBUG_REPL,
kbOpts: {
kbExpr: ContextKeyExpr.and(EditorContextKeys.textFocus, debug.CONTEXT_ON_FIRST_DEBUG_REPL_LINE),
primary: KeyCode.UpArrow,
weight: 50
},
menuOpts: {
group: 'debug'
}
});
}
public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void | TPromise<void> {
accessor.get(IPrivateReplService).navigateHistory(true);
}
}
@editorAction
class ReplHistoryNextAction extends EditorAction {
constructor() {
super({
id: 'repl.action.historyNext',
label: nls.localize('actions.repl.historyNext', "History Next"),
alias: 'History Next',
precondition: debug.CONTEXT_IN_DEBUG_REPL,
kbOpts: {
kbExpr: ContextKeyExpr.and(EditorContextKeys.textFocus, debug.CONTEXT_ON_LAST_DEBUG_REPL_LINE),
primary: KeyCode.DownArrow,
weight: 50
},
menuOpts: {
group: 'debug'
}
});
}
public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void | TPromise<void> {
accessor.get(IPrivateReplService).navigateHistory(false);
}
}
@editorAction
class AcceptReplInputAction extends EditorAction {
constructor() {
super({
id: 'repl.action.acceptInput',
label: nls.localize({ key: 'actions.repl.acceptInput', comment: ['Apply input from the debug console input box'] }, "REPL Accept Input"),
alias: 'REPL Accept Input',
precondition: debug.CONTEXT_IN_DEBUG_REPL,
kbOpts: {
kbExpr: EditorContextKeys.textFocus,
primary: KeyCode.Enter
}
});
}
public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void | TPromise<void> {
SuggestController.get(editor).acceptSelectedSuggestion();
accessor.get(IPrivateReplService).acceptReplInput();
}
}
const SuggestCommand = EditorCommand.bindToContribution<SuggestController>(SuggestController.get);
CommonEditorRegistry.registerEditorCommand(new SuggestCommand({
id: 'repl.action.acceptSuggestion',
precondition: ContextKeyExpr.and(debug.CONTEXT_IN_DEBUG_REPL, SuggestContext.Visible),
handler: x => x.acceptSelectedSuggestion(),
kbOpts: {
weight: 50,
kbExpr: EditorContextKeys.textFocus,
primary: KeyCode.RightArrow
}
}));
@editorAction
export class ReplCopyAllAction extends EditorAction {
constructor() {
super({
id: 'repl.action.copyAll',
label: nls.localize('actions.repl.copyAll', "Debug: Console Copy All"),
alias: 'Debug Console Copy All',
precondition: debug.CONTEXT_IN_DEBUG_REPL,
});
}
public run(accessor: ServicesAccessor, editor: ICommonCodeEditor): void | TPromise<void> {
clipboard.writeText(accessor.get(IPrivateReplService).getVisibleContent());
}
}

View File

@@ -0,0 +1,51 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IEditorOptions } from 'vs/editor/common/config/editorOptions';
import { EditorAction, CommonEditorRegistry } from 'vs/editor/common/editorCommonExtensions';
import { ICodeEditorService } from 'vs/editor/common/services/codeEditorService';
import { IEditorContributionCtor } from 'vs/editor/browser/editorBrowser';
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ICommandService } from 'vs/platform/commands/common/commands';
// Allowed Editor Contributions:
import { MenuPreventer } from 'vs/workbench/parts/codeEditor/electron-browser/menuPreventer';
import { SelectionClipboard } from 'vs/workbench/parts/codeEditor/electron-browser/selectionClipboard';
import { ContextMenuController } from 'vs/editor/contrib/contextmenu/browser/contextmenu';
import { SuggestController } from 'vs/editor/contrib/suggest/browser/suggestController';
import { SnippetController2 } from 'vs/editor/contrib/snippet/browser/snippetController2';
import { TabCompletionController } from 'vs/workbench/parts/snippets/electron-browser/tabCompletion';
import { IThemeService } from 'vs/platform/theme/common/themeService';
export class ReplInputEditor extends CodeEditorWidget {
constructor(
domElement: HTMLElement,
options: IEditorOptions,
@IInstantiationService instantiationService: IInstantiationService,
@ICodeEditorService codeEditorService: ICodeEditorService,
@ICommandService commandService: ICommandService,
@IContextKeyService contextKeyService: IContextKeyService,
@IThemeService themeService: IThemeService
) {
super(domElement, options, instantiationService, codeEditorService, commandService, contextKeyService, themeService);
}
protected _getContributions(): IEditorContributionCtor[] {
return [
MenuPreventer,
SelectionClipboard,
ContextMenuController,
SuggestController,
SnippetController2,
TabCompletionController,
];
}
protected _getActions(): EditorAction[] {
return CommonEditorRegistry.getEditorActions();
}
}

View File

@@ -0,0 +1,434 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { TPromise } from 'vs/base/common/winjs.base';
import { IAction } from 'vs/base/common/actions';
import { isFullWidthCharacter, removeAnsiEscapeCodes, endsWith } from 'vs/base/common/strings';
import { IActionItem } 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';
import { ITree, IAccessibilityProvider, IDataSource, IRenderer, IActionProvider } from 'vs/base/parts/tree/browser/tree';
import { ICancelableEvent } from 'vs/base/parts/tree/browser/treeDefaults';
import { IExpressionContainer, IExpression } from 'vs/workbench/parts/debug/common/debug';
import { Model, OutputNameValueElement, Expression, OutputElement, Variable } from 'vs/workbench/parts/debug/common/debugModel';
import { renderVariable, renderExpressionValue, IVariableTemplateData, BaseDebugController } from 'vs/workbench/parts/debug/electron-browser/debugViewer';
import { ClearReplAction } 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';
import { LinkDetector } from 'vs/workbench/parts/debug/browser/linkDetector';
const $ = dom.$;
export class ReplExpressionsDataSource implements IDataSource {
public getId(tree: ITree, element: any): string {
return element.getId();
}
public hasChildren(tree: ITree, element: any): boolean {
return element instanceof Model || (<IExpressionContainer>element).hasChildren;
}
public getChildren(tree: ITree, element: any): TPromise<any> {
if (element instanceof Model) {
return TPromise.as(element.getReplElements());
}
if (element instanceof OutputNameValueElement) {
return TPromise.as(element.getChildren());
}
if (element instanceof OutputElement) {
return TPromise.as(null);
}
return (<IExpression>element).getChildren();
}
public getParent(tree: ITree, element: any): TPromise<any> {
return TPromise.as(null);
}
}
interface IExpressionTemplateData {
input: HTMLElement;
output: HTMLElement;
value: HTMLElement;
annotation: HTMLElement;
}
interface IValueOutputTemplateData {
container: HTMLElement;
value: HTMLElement;
}
interface IKeyValueOutputTemplateData {
container: HTMLElement;
expression: HTMLElement;
name: HTMLElement;
value: HTMLElement;
annotation: HTMLElement;
}
export class ReplExpressionsRenderer implements IRenderer {
private static VARIABLE_TEMPLATE_ID = 'variable';
private static EXPRESSION_TEMPLATE_ID = 'inputOutputPair';
private static VALUE_OUTPUT_TEMPLATE_ID = 'outputValue';
private static NAME_VALUE_OUTPUT_TEMPLATE_ID = 'outputNameValue';
private static LINE_HEIGHT_PX = 18;
private width: number;
private characterWidth: number;
private linkDetector: LinkDetector;
constructor(
@IWorkbenchEditorService private editorService: IWorkbenchEditorService,
@IInstantiationService private instantiationService: IInstantiationService
) {
this.linkDetector = this.instantiationService.createInstance(LinkDetector);
}
public getHeight(tree: ITree, element: any): number {
if (element instanceof Variable && (element.hasChildren || (element.name !== null))) {
return ReplExpressionsRenderer.LINE_HEIGHT_PX;
}
if (element instanceof Expression && element.hasChildren) {
return 2 * ReplExpressionsRenderer.LINE_HEIGHT_PX;
}
return this.getHeightForString(element.value) + (element instanceof Expression ? this.getHeightForString(element.name) : 0);
}
private getHeightForString(s: string): number {
if (!s || !s.length || !this.width || this.width <= 0 || !this.characterWidth || this.characterWidth <= 0) {
return ReplExpressionsRenderer.LINE_HEIGHT_PX;
}
// Last new line should be ignored since the repl elements are by design split by rows
if (endsWith(s, '\n')) {
s = s.substr(0, s.length - 1);
}
const lines = removeAnsiEscapeCodes(s).split('\n');
const numLines = lines.reduce((lineCount: number, line: string) => {
let lineLength = 0;
for (let i = 0; i < line.length; i++) {
lineLength += isFullWidthCharacter(line.charCodeAt(i)) ? 2 : 1;
}
return lineCount + Math.floor(lineLength * this.characterWidth / this.width);
}, lines.length);
return ReplExpressionsRenderer.LINE_HEIGHT_PX * numLines;
}
public setWidth(fullWidth: number, characterWidth: number): void {
this.width = fullWidth;
this.characterWidth = characterWidth;
}
public getTemplateId(tree: ITree, element: any): string {
if (element instanceof Variable && element.name) {
return ReplExpressionsRenderer.VARIABLE_TEMPLATE_ID;
}
if (element instanceof Expression) {
return ReplExpressionsRenderer.EXPRESSION_TEMPLATE_ID;
}
if (element instanceof OutputElement || (element instanceof Variable && !element.name)) {
// Variable with no name is a top level variable which should be rendered like an output element #17404
return ReplExpressionsRenderer.VALUE_OUTPUT_TEMPLATE_ID;
}
if (element instanceof OutputNameValueElement) {
return ReplExpressionsRenderer.NAME_VALUE_OUTPUT_TEMPLATE_ID;
}
return null;
}
public renderTemplate(tree: ITree, templateId: string, container: HTMLElement): any {
if (templateId === ReplExpressionsRenderer.VARIABLE_TEMPLATE_ID) {
let data: IVariableTemplateData = Object.create(null);
data.expression = dom.append(container, $('.expression'));
data.name = dom.append(data.expression, $('span.name'));
data.value = dom.append(data.expression, $('span.value'));
return data;
}
if (templateId === ReplExpressionsRenderer.EXPRESSION_TEMPLATE_ID) {
let data: IExpressionTemplateData = Object.create(null);
dom.addClass(container, 'input-output-pair');
data.input = dom.append(container, $('.input.expression'));
data.output = dom.append(container, $('.output.expression'));
data.value = dom.append(data.output, $('span.value'));
data.annotation = dom.append(data.output, $('span'));
return data;
}
if (templateId === ReplExpressionsRenderer.VALUE_OUTPUT_TEMPLATE_ID) {
let data: IValueOutputTemplateData = Object.create(null);
dom.addClass(container, 'output');
let expression = dom.append(container, $('.output.expression'));
data.container = container;
data.value = dom.append(expression, $('span.value'));
return data;
}
if (templateId === ReplExpressionsRenderer.NAME_VALUE_OUTPUT_TEMPLATE_ID) {
let data: IKeyValueOutputTemplateData = Object.create(null);
dom.addClass(container, 'output');
data.container = container;
data.expression = dom.append(container, $('.output.expression'));
data.name = dom.append(data.expression, $('span.name'));
data.value = dom.append(data.expression, $('span.value'));
data.annotation = dom.append(data.expression, $('span'));
return data;
}
}
public renderElement(tree: ITree, element: any, templateId: string, templateData: any): void {
if (templateId === ReplExpressionsRenderer.VARIABLE_TEMPLATE_ID) {
renderVariable(tree, element, templateData, false);
} else if (templateId === ReplExpressionsRenderer.EXPRESSION_TEMPLATE_ID) {
this.renderExpression(tree, element, templateData);
} else if (templateId === ReplExpressionsRenderer.VALUE_OUTPUT_TEMPLATE_ID) {
this.renderOutputValue(element, templateData);
} else if (templateId === ReplExpressionsRenderer.NAME_VALUE_OUTPUT_TEMPLATE_ID) {
this.renderOutputNameValue(tree, element, templateData);
}
}
private renderExpression(tree: ITree, expression: IExpression, templateData: IExpressionTemplateData): void {
templateData.input.textContent = expression.name;
renderExpressionValue(expression, templateData.value, {
preserveWhitespace: !expression.hasChildren,
showHover: false
});
if (expression.hasChildren) {
templateData.annotation.className = 'annotation octicon octicon-info';
templateData.annotation.title = nls.localize('stateCapture', "Object state is captured from first evaluation");
}
}
private renderOutputValue(output: OutputElement, templateData: IValueOutputTemplateData): void {
// value
dom.clearNode(templateData.value);
templateData.value.className = '';
let result = this.handleANSIOutput(output.value);
if (typeof result === 'string') {
renderExpressionValue(result, templateData.value, {
preserveWhitespace: true,
showHover: false
});
} else {
templateData.value.appendChild(result);
}
dom.addClass(templateData.value, (output.severity === severity.Warning) ? 'warn' : (output.severity === severity.Error) ? 'error' : 'info');
}
private renderOutputNameValue(tree: ITree, output: OutputNameValueElement, templateData: IKeyValueOutputTemplateData): void {
// key
if (output.name) {
templateData.name.textContent = `${output.name}:`;
} else {
templateData.name.textContent = '';
}
// value
renderExpressionValue(output.value, templateData.value, {
preserveWhitespace: true,
showHover: false
});
// annotation if any
if (output.annotation) {
templateData.annotation.className = 'annotation octicon octicon-info';
templateData.annotation.title = output.annotation;
} else {
templateData.annotation.className = '';
templateData.annotation.title = '';
}
}
private handleANSIOutput(text: string): HTMLElement | string {
let tokensContainer: HTMLSpanElement;
let currentToken: HTMLSpanElement;
let buffer: string = '';
for (let i = 0, len = text.length; i < len; i++) {
// start of ANSI escape sequence (see http://ascii-table.com/ansi-escape-sequences.php)
if (text.charCodeAt(i) === 27) {
let index = i;
let chr = (++index < len ? text.charAt(index) : null);
if (chr && chr === '[') {
let code: string = null;
chr = (++index < len ? text.charAt(index) : null);
if (chr && chr >= '0' && chr <= '9') {
code = chr;
chr = (++index < len ? text.charAt(index) : null);
}
if (chr && chr >= '0' && chr <= '9') {
code += chr;
chr = (++index < len ? text.charAt(index) : null);
}
if (code === null) {
code = '0';
}
if (chr === 'm') { // set text color/mode.
// only respect text-foreground ranges and ignore the values for "black" & "white" because those
// only make sense in combination with text-background ranges which we currently not support
let parsedMode = parseInt(code, 10);
let token = document.createElement('span');
if ((parsedMode >= 30 && parsedMode <= 37) || (parsedMode >= 90 && parsedMode <= 97)) {
token.className = 'code' + parsedMode;
} else if (parsedMode === 1) {
token.className = 'code-bold';
}
// we need a tokens container now
if (!tokensContainer) {
tokensContainer = document.createElement('span');
}
// flush text buffer if we have any
if (buffer) {
this.insert(this.linkDetector.handleLinks(buffer), currentToken || tokensContainer);
buffer = '';
}
currentToken = token;
tokensContainer.appendChild(token);
i = index;
}
}
}
// normal text
else {
buffer += text[i];
}
}
// flush remaining text buffer if we have any
if (buffer) {
let res = this.linkDetector.handleLinks(buffer);
if (typeof res !== 'string' || currentToken) {
if (!tokensContainer) {
tokensContainer = document.createElement('span');
}
this.insert(res, currentToken || tokensContainer);
}
}
return tokensContainer || buffer;
}
private insert(arg: HTMLElement | string, target: HTMLElement): void {
if (typeof arg === 'string') {
target.textContent = arg;
} else {
target.appendChild(arg);
}
}
public disposeTemplate(tree: ITree, templateId: string, templateData: any): void {
// noop
}
}
export class ReplExpressionsAccessibilityProvider implements IAccessibilityProvider {
public getAriaLabel(tree: ITree, element: any): string {
if (element instanceof Variable) {
return nls.localize('replVariableAriaLabel', "Variable {0} has value {1}, read eval print loop, debug", (<Variable>element).name, (<Variable>element).value);
}
if (element instanceof Expression) {
return nls.localize('replExpressionAriaLabel', "Expression {0} has value {1}, read eval print loop, debug", (<Expression>element).name, (<Expression>element).value);
}
if (element instanceof OutputElement) {
return nls.localize('replValueOutputAriaLabel', "{0}, read eval print loop, debug", (<OutputElement>element).value);
}
if (element instanceof OutputNameValueElement) {
return nls.localize('replKeyValueOutputAriaLabel', "Output variable {0} has value {1}, read eval print loop, debug", (<OutputNameValueElement>element).name, (<OutputNameValueElement>element).value);
}
return null;
}
}
export class ReplExpressionsActionProvider implements IActionProvider {
constructor(private instantiationService: IInstantiationService) {
// noop
}
public hasActions(tree: ITree, element: any): boolean {
return false;
}
public getActions(tree: ITree, element: any): TPromise<IAction[]> {
return TPromise.as([]);
}
public hasSecondaryActions(tree: ITree, element: any): boolean {
return true;
}
public getSecondaryActions(tree: ITree, element: any): TPromise<IAction[]> {
const actions: IAction[] = [];
actions.push(new CopyAction(CopyAction.ID, CopyAction.LABEL));
actions.push(new CopyAllAction(CopyAllAction.ID, CopyAllAction.LABEL, tree));
actions.push(this.instantiationService.createInstance(ClearReplAction, ClearReplAction.ID, ClearReplAction.LABEL));
return TPromise.as(actions);
}
public getActionItem(tree: ITree, element: any, action: IAction): IActionItem {
return null;
}
}
export class ReplExpressionsController extends BaseDebugController {
private lastSelectedString: string = null;
public toFocusOnClick: { focus(): void };
protected onLeftClick(tree: ITree, element: any, eventish: ICancelableEvent, origin: string = 'mouse'): boolean {
const mouseEvent = <IMouseEvent>eventish;
// input and output are one element in the tree => we only expand if the user clicked on the output.
if ((element.reference > 0 || (element instanceof OutputNameValueElement && element.hasChildren)) && mouseEvent.target.className.indexOf('input expression') === -1) {
super.onLeftClick(tree, element, eventish, origin);
tree.clearFocus();
tree.deselect(element);
}
const selection = window.getSelection();
if (selection.type !== 'Range' || this.lastSelectedString === selection.toString()) {
// only focus the input if the user is not currently selecting.
this.toFocusOnClick.focus();
}
this.lastSelectedString = selection.toString();
return true;
}
}

View File

@@ -0,0 +1,117 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
import { localize } from 'vs/nls';
import { registerColor, contrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IPartService, Parts } from 'vs/workbench/services/part/common/partService';
import { IDebugService, State } from 'vs/workbench/parts/debug/common/debug';
import { IWorkspaceContextService } 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';
// colors for theming
export const STATUS_BAR_DEBUGGING_BACKGROUND = registerColor('statusBar.debuggingBackground', {
dark: '#CC6633',
light: '#CC6633',
hc: '#CC6633'
}, localize('statusBarDebuggingBackground', "Status bar background color when a program is being debugged. The status bar is shown in the bottom of the window"));
export const STATUS_BAR_DEBUGGING_FOREGROUND = registerColor('statusBar.debuggingForeground', {
dark: STATUS_BAR_FOREGROUND,
light: STATUS_BAR_FOREGROUND,
hc: STATUS_BAR_FOREGROUND
}, localize('statusBarDebuggingForeground', "Status bar foreground color when a program is being debugged. The status bar is shown in the bottom of the window"));
export const STATUS_BAR_DEBUGGING_BORDER = registerColor('statusBar.debuggingBorder', {
dark: STATUS_BAR_BORDER,
light: STATUS_BAR_BORDER,
hc: STATUS_BAR_BORDER
}, 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 static ID = 'debug.statusbarColorProvider';
constructor(
@IThemeService themeService: IThemeService,
@IDebugService private debugService: IDebugService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IPartService private partService: IPartService
) {
super(themeService);
this.registerListeners();
}
private registerListeners(): void {
this.toUnbind.push(this.debugService.onDidChangeState(state => this.updateStyles()));
this.toUnbind.push(this.contextService.onDidChangeWorkspaceRoots(state => this.updateStyles()));
}
protected updateStyles(): void {
super.updateStyles();
const container = this.partService.getContainer(Parts.STATUSBAR_PART);
if (this.isDebugging()) {
addClass(container, 'debugging');
} else {
removeClass(container, 'debugging');
}
container.style.backgroundColor = this.getColor(this.getColorKey(STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_DEBUGGING_BACKGROUND, STATUS_BAR_BACKGROUND));
container.style.color = this.getColor(this.getColorKey(STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_DEBUGGING_FOREGROUND, STATUS_BAR_FOREGROUND));
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;
}
private getColorKey(noFolderColor: string, debuggingColor: string, normalColor: string): string {
// Not debugging
if (!this.isDebugging()) {
if (this.contextService.hasWorkspace()) {
return normalColor;
}
return noFolderColor;
}
// Debugging
return debuggingColor;
}
private isDebugging(): boolean {
if (this.debugService.state === State.Inactive) {
return false;
}
if (this.isRunningWithoutDebug()) {
return false;
}
return true;
}
private isRunningWithoutDebug(): boolean {
const process = this.debugService.getViewModel().focusedProcess;
return process && process.configuration && process.configuration.noDebug;
}
public getId(): string {
return StatusBarColorProvider.ID;
}
}
registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
const statusBarItemDebuggingForeground = theme.getColor(STATUS_BAR_DEBUGGING_FOREGROUND);
if (statusBarItemDebuggingForeground) {
collector.addRule(`.monaco-workbench > .part.statusbar.debugging > .statusbar-item .mask-icon { background-color: ${statusBarItemDebuggingForeground} !important; }`);
}
});

View File

@@ -0,0 +1,166 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import * as platform from 'vs/base/common/platform';
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';
import { ITerminalService as IExternalTerminalService } from 'vs/workbench/parts/execution/common/execution';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
const enum ShellType { cmd, powershell, bash };
export class TerminalSupport {
private static integratedTerminalInstance: ITerminalInstance;
private static terminalDisposedListener: IDisposable;
public static runInTerminal(terminalService: ITerminalService, nativeTerminalService: IExternalTerminalService, configurationService: IConfigurationService, args: DebugProtocol.RunInTerminalRequestArguments, response: DebugProtocol.RunInTerminalResponse): TPromise<void> {
if (args.kind === 'external') {
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 => {
if (TerminalSupport.integratedTerminalInstance && TerminalSupport.integratedTerminalInstance.id === terminal.id) {
TerminalSupport.integratedTerminalInstance = null;
}
});
}
terminalService.setActiveInstance(TerminalSupport.integratedTerminalInstance);
terminalService.showPanel(true);
return new TPromise<void>((c, e) => {
setTimeout(() => {
if (TerminalSupport.integratedTerminalInstance) {
const command = this.prepareCommand(args, configurationService);
TerminalSupport.integratedTerminalInstance.sendText(command, true);
c(void 0);
} else {
e(new Error(nls.localize('debug.terminal.not.available.error', "Integrated terminal not available")));
}
}, delay);
});
}
private static prepareCommand(args: DebugProtocol.RunInTerminalRequestArguments, configurationService: IConfigurationService): string {
let shellType: ShellType;
// get the shell configuration for the current platform
let shell: string;
const shell_config = (<ITerminalConfiguration>configurationService.getConfiguration<any>().terminal.integrated).shell;
if (platform.isWindows) {
shell = shell_config.windows;
shellType = ShellType.cmd;
} else if (platform.isLinux) {
shell = shell_config.linux;
shellType = ShellType.bash;
} else if (platform.isMacintosh) {
shell = shell_config.osx;
shellType = ShellType.bash;
}
// try to determine the shell type
shell = shell.trim().toLowerCase();
if (shell.indexOf('powershell') >= 0) {
shellType = ShellType.powershell;
} else if (shell.indexOf('cmd.exe') >= 0) {
shellType = ShellType.cmd;
} else if (shell.indexOf('bash') >= 0) {
shellType = ShellType.bash;
} else if (shell.indexOf('git\\bin\\bash.exe') >= 0) {
shellType = ShellType.bash;
}
let quote: (s: string) => string;
let command = '';
switch (shellType) {
case ShellType.powershell:
quote = (s: string) => {
s = s.replace(/\'/g, '\'\'');
return s.indexOf(' ') >= 0 || s.indexOf('\'') >= 0 || s.indexOf('"') >= 0 ? `'${s}'` : s;
};
if (args.cwd) {
command += `cd '${args.cwd}'; `;
}
if (args.env) {
for (let key in args.env) {
command += `$env:${key}='${args.env[key]}'; `;
}
}
if (args.args && args.args.length > 0) {
const cmd = quote(args.args.shift());
command += (cmd[0] === '\'') ? `& ${cmd} ` : `${cmd} `;
for (let a of args.args) {
command += `${quote(a)} `;
}
}
break;
case ShellType.cmd:
quote = (s: string) => {
s = s.replace(/\"/g, '""');
return (s.indexOf(' ') >= 0 || s.indexOf('"') >= 0) ? `"${s}"` : s;
};
if (args.cwd) {
command += `cd ${quote(args.cwd)} && `;
}
if (args.env) {
command += 'cmd /C "';
for (let key in args.env) {
command += `set "${key}=${args.env[key]}" && `;
}
}
for (let a of args.args) {
command += `${quote(a)} `;
}
if (args.env) {
command += '"';
}
break;
case ShellType.bash:
quote = (s: string) => {
s = s.replace(/\"/g, '\\"');
return (s.indexOf(' ') >= 0 || s.indexOf('\\') >= 0) ? `"${s}"` : s;
};
if (args.cwd) {
command += `cd ${quote(args.cwd)} ; `;
}
if (args.env) {
command += 'env';
for (let key in args.env) {
command += ` "${key}=${args.env[key]}"`;
}
command += ' ';
}
for (let a of args.args) {
command += `${quote(a)} `;
}
break;
}
return command;
}
}