mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-03-26 14:50:31 -04:00
SQL Operations Studio Public Preview 1 (0.23) release source code
This commit is contained in:
@@ -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();
|
||||
225
src/vs/workbench/parts/debug/electron-browser/debugCommands.ts
Normal file
225
src/vs/workbench/parts/debug/electron-browser/debugCommands.ts
Normal 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;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -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));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
357
src/vs/workbench/parts/debug/electron-browser/debugHover.ts
Normal file
357
src/vs/workbench/parts/debug/electron-browser/debugHover.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
1192
src/vs/workbench/parts/debug/electron-browser/debugService.ts
Normal file
1192
src/vs/workbench/parts/debug/electron-browser/debugService.ts
Normal file
File diff suppressed because it is too large
Load Diff
1307
src/vs/workbench/parts/debug/electron-browser/debugViewer.ts
Normal file
1307
src/vs/workbench/parts/debug/electron-browser/debugViewer.ts
Normal file
File diff suppressed because it is too large
Load Diff
533
src/vs/workbench/parts/debug/electron-browser/debugViews.ts
Normal file
533
src/vs/workbench/parts/debug/electron-browser/debugViews.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
534
src/vs/workbench/parts/debug/electron-browser/rawDebugSession.ts
Normal file
534
src/vs/workbench/parts/debug/electron-browser/rawDebugSession.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
414
src/vs/workbench/parts/debug/electron-browser/repl.ts
Normal file
414
src/vs/workbench/parts/debug/electron-browser/repl.ts
Normal 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());
|
||||
}
|
||||
}
|
||||
51
src/vs/workbench/parts/debug/electron-browser/replEditor.ts
Normal file
51
src/vs/workbench/parts/debug/electron-browser/replEditor.ts
Normal 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();
|
||||
}
|
||||
}
|
||||
434
src/vs/workbench/parts/debug/electron-browser/replViewer.ts
Normal file
434
src/vs/workbench/parts/debug/electron-browser/replViewer.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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; }`);
|
||||
}
|
||||
});
|
||||
166
src/vs/workbench/parts/debug/electron-browser/terminalSupport.ts
Normal file
166
src/vs/workbench/parts/debug/electron-browser/terminalSupport.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user