Vscode merge (#4582)
* Merge from vscode 37cb23d3dd4f9433d56d4ba5ea3203580719a0bd * fix issues with merges * bump node version in azpipe * replace license headers * remove duplicate launch task * fix build errors * fix build errors * fix tslint issues * working through package and linux build issues * more work * wip * fix packaged builds * working through linux build errors * wip * wip * wip * fix mac and linux file limits * iterate linux pipeline * disable editor typing * revert series to parallel * remove optimize vscode from linux * fix linting issues * revert testing change * add work round for new node * readd packaging for extensions * fix issue with angular not resolving decorator dependencies
224
src/vs/workbench/contrib/debug/browser/baseDebugView.ts
Normal file
@@ -0,0 +1,224 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { IExpression, IDebugService } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { Expression, Variable, ExpressionContainer } from 'vs/workbench/contrib/debug/common/debugModel';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IInputValidationOptions, InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { ITreeRenderer, ITreeNode } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
|
||||
import { FuzzyScore, createMatches } from 'vs/base/common/filters';
|
||||
|
||||
export const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024;
|
||||
export const twistiePixels = 20;
|
||||
const booleanRegex = /^true|false$/i;
|
||||
const stringRegex = /^(['"]).*\1$/;
|
||||
const $ = dom.$;
|
||||
|
||||
export interface IRenderValueOptions {
|
||||
preserveWhitespace?: boolean;
|
||||
showChanged?: boolean;
|
||||
maxValueLength?: number;
|
||||
showHover?: boolean;
|
||||
colorize?: boolean;
|
||||
}
|
||||
|
||||
export interface IVariableTemplateData {
|
||||
expression: HTMLElement;
|
||||
name: HTMLElement;
|
||||
value: HTMLElement;
|
||||
label: HighlightedLabel;
|
||||
}
|
||||
|
||||
export function renderViewTree(container: HTMLElement): HTMLElement {
|
||||
const treeContainer = document.createElement('div');
|
||||
dom.addClass(treeContainer, 'debug-view-content');
|
||||
container.appendChild(treeContainer);
|
||||
return treeContainer;
|
||||
}
|
||||
|
||||
export function replaceWhitespace(value: string): string {
|
||||
const map: { [x: string]: string } = { '\n': '\\n', '\r': '\\r', '\t': '\\t' };
|
||||
return value.replace(/[\n\r\t]/g, char => map[char]);
|
||||
}
|
||||
|
||||
export function renderExpressionValue(expressionOrValue: IExpression | string, container: HTMLElement, options: IRenderValueOptions): void {
|
||||
let value = typeof expressionOrValue === 'string' ? expressionOrValue : expressionOrValue.value;
|
||||
|
||||
// remove stale classes
|
||||
container.className = 'value';
|
||||
// when resolving expressions we represent errors from the server as a variable with name === null.
|
||||
if (value === null || ((expressionOrValue instanceof Expression || expressionOrValue instanceof Variable) && !expressionOrValue.available)) {
|
||||
dom.addClass(container, 'unavailable');
|
||||
if (value !== Expression.DEFAULT_VALUE) {
|
||||
dom.addClass(container, 'error');
|
||||
}
|
||||
} else if ((expressionOrValue instanceof ExpressionContainer) && options.showChanged && expressionOrValue.valueChanged && value !== Expression.DEFAULT_VALUE) {
|
||||
// value changed color has priority over other colors.
|
||||
container.className = 'value changed';
|
||||
expressionOrValue.valueChanged = false;
|
||||
}
|
||||
|
||||
if (options.colorize && typeof expressionOrValue !== 'string') {
|
||||
if (expressionOrValue.type === 'number' || expressionOrValue.type === 'boolean' || expressionOrValue.type === 'string') {
|
||||
dom.addClass(container, expressionOrValue.type);
|
||||
} else if (!isNaN(+value)) {
|
||||
dom.addClass(container, 'number');
|
||||
} else if (booleanRegex.test(value)) {
|
||||
dom.addClass(container, 'boolean');
|
||||
} else if (stringRegex.test(value)) {
|
||||
dom.addClass(container, 'string');
|
||||
}
|
||||
}
|
||||
|
||||
if (options.maxValueLength && value && value.length > options.maxValueLength) {
|
||||
value = value.substr(0, options.maxValueLength) + '...';
|
||||
}
|
||||
if (value && !options.preserveWhitespace) {
|
||||
container.textContent = replaceWhitespace(value);
|
||||
} else {
|
||||
container.textContent = value || '';
|
||||
}
|
||||
if (options.showHover) {
|
||||
container.title = value || '';
|
||||
}
|
||||
}
|
||||
|
||||
export function renderVariable(variable: Variable, data: IVariableTemplateData, showChanged: boolean, highlights: IHighlight[]): void {
|
||||
if (variable.available) {
|
||||
let text = replaceWhitespace(variable.name);
|
||||
if (variable.value && typeof variable.name === 'string') {
|
||||
text += ':';
|
||||
}
|
||||
data.label.set(text, highlights, variable.type ? variable.type : variable.name);
|
||||
dom.toggleClass(data.name, 'virtual', !!variable.presentationHint && variable.presentationHint.kind === 'virtual');
|
||||
} else if (variable.value && typeof variable.name === 'string') {
|
||||
data.label.set(':');
|
||||
}
|
||||
|
||||
renderExpressionValue(variable, data.value, {
|
||||
showChanged,
|
||||
maxValueLength: MAX_VALUE_RENDER_LENGTH_IN_VIEWLET,
|
||||
preserveWhitespace: false,
|
||||
showHover: true,
|
||||
colorize: true
|
||||
});
|
||||
}
|
||||
|
||||
export interface IInputBoxOptions {
|
||||
initialValue: string;
|
||||
ariaLabel: string;
|
||||
placeholder?: string;
|
||||
validationOptions?: IInputValidationOptions;
|
||||
onFinish: (value: string, success: boolean) => void;
|
||||
}
|
||||
|
||||
export interface IExpressionTemplateData {
|
||||
expression: HTMLElement;
|
||||
name: HTMLSpanElement;
|
||||
value: HTMLSpanElement;
|
||||
inputBoxContainer: HTMLElement;
|
||||
enableInputBox(expression: IExpression, options: IInputBoxOptions);
|
||||
toDispose: IDisposable[];
|
||||
label: HighlightedLabel;
|
||||
}
|
||||
|
||||
export abstract class AbstractExpressionsRenderer implements ITreeRenderer<IExpression, FuzzyScore, IExpressionTemplateData> {
|
||||
|
||||
constructor(
|
||||
@IDebugService protected debugService: IDebugService,
|
||||
@IContextViewService private readonly contextViewService: IContextViewService,
|
||||
@IThemeService private readonly themeService: IThemeService
|
||||
) { }
|
||||
|
||||
abstract get templateId(): string;
|
||||
|
||||
renderTemplate(container: HTMLElement): IExpressionTemplateData {
|
||||
const expression = dom.append(container, $('.expression'));
|
||||
const name = dom.append(expression, $('span.name'));
|
||||
const value = dom.append(expression, $('span.value'));
|
||||
const label = new HighlightedLabel(name, false);
|
||||
|
||||
const inputBoxContainer = dom.append(expression, $('.inputBoxContainer'));
|
||||
const toDispose: IDisposable[] = [];
|
||||
|
||||
const enableInputBox = (expression: IExpression, options: IInputBoxOptions) => {
|
||||
name.style.display = 'none';
|
||||
value.style.display = 'none';
|
||||
inputBoxContainer.style.display = 'initial';
|
||||
|
||||
const inputBox = new InputBox(inputBoxContainer, this.contextViewService, {
|
||||
placeholder: options.placeholder,
|
||||
ariaLabel: options.ariaLabel
|
||||
});
|
||||
const styler = attachInputBoxStyler(inputBox, this.themeService);
|
||||
|
||||
inputBox.value = options.initialValue;
|
||||
inputBox.focus();
|
||||
inputBox.select();
|
||||
|
||||
let disposed = false;
|
||||
toDispose.push(inputBox);
|
||||
toDispose.push(styler);
|
||||
|
||||
const wrapUp = (renamed: boolean) => {
|
||||
if (!disposed) {
|
||||
disposed = true;
|
||||
this.debugService.getViewModel().setSelectedExpression(undefined);
|
||||
options.onFinish(inputBox.value, renamed);
|
||||
|
||||
// need to remove the input box since this template will be reused.
|
||||
inputBoxContainer.removeChild(inputBox.element);
|
||||
name.style.display = 'initial';
|
||||
value.style.display = 'initial';
|
||||
inputBoxContainer.style.display = 'none';
|
||||
dispose(toDispose);
|
||||
}
|
||||
};
|
||||
|
||||
toDispose.push(dom.addStandardDisposableListener(inputBox.inputElement, 'keydown', (e: IKeyboardEvent) => {
|
||||
const isEscape = e.equals(KeyCode.Escape);
|
||||
const isEnter = e.equals(KeyCode.Enter);
|
||||
if (isEscape || isEnter) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
wrapUp(isEnter);
|
||||
}
|
||||
}));
|
||||
toDispose.push(dom.addDisposableListener(inputBox.inputElement, 'blur', () => {
|
||||
wrapUp(true);
|
||||
}));
|
||||
toDispose.push(dom.addDisposableListener(inputBox.inputElement, 'click', e => {
|
||||
// Do not expand / collapse selected elements
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}));
|
||||
};
|
||||
|
||||
return { expression, name, value, label, enableInputBox, inputBoxContainer, toDispose };
|
||||
}
|
||||
|
||||
renderElement(node: ITreeNode<IExpression, FuzzyScore>, index: number, data: IExpressionTemplateData): void {
|
||||
const { element } = node;
|
||||
if (element === this.debugService.getViewModel().getSelectedExpression()) {
|
||||
data.enableInputBox(element, this.getInputBoxOptions(element));
|
||||
} else {
|
||||
this.renderExpression(element, data, createMatches(node.filterData));
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract renderExpression(expression: IExpression, data: IExpressionTemplateData, highlights: IHighlight[]): void;
|
||||
protected abstract getInputBoxOptions(expression: IExpression): IInputBoxOptions;
|
||||
|
||||
disposeTemplate(templateData: IExpressionTemplateData): void {
|
||||
dispose(templateData.toDispose);
|
||||
}
|
||||
}
|
||||
361
src/vs/workbench/contrib/debug/browser/breakpointWidget.ts
Normal file
@@ -0,0 +1,361 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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!./media/breakpointWidget';
|
||||
import * as nls from 'vs/nls';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { SelectBox, ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox';
|
||||
import * as lifecycle from 'vs/base/common/lifecycle';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { Position, IPosition } from 'vs/editor/common/core/position';
|
||||
import { ICodeEditor, IActiveCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IDebugService, IBreakpoint, BreakpointWidgetContext as Context, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, DEBUG_SCHEME, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID, CONTEXT_IN_BREAKPOINT_WIDGET } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { attachSelectBoxStyler } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ServicesAccessor, EditorCommand, registerEditorCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { URI as uri } from 'vs/base/common/uri';
|
||||
import { CompletionProviderRegistry, CompletionList, CompletionContext, CompletionItemKind } from 'vs/editor/common/modes';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { provideSuggestionItems, CompletionOptions } from 'vs/editor/contrib/suggest/suggest';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { transparent, editorForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { IDecorationOptions } from 'vs/editor/common/editorCommon';
|
||||
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { getSimpleEditorOptions, getSimpleCodeEditorWidgetOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
|
||||
const $ = dom.$;
|
||||
const IPrivateBreakpointWidgetService = createDecorator<IPrivateBreakpointWidgetService>('privateBreakopintWidgetService');
|
||||
export interface IPrivateBreakpointWidgetService {
|
||||
_serviceBrand: any;
|
||||
close(success: boolean): void;
|
||||
}
|
||||
const DECORATION_KEY = 'breakpointwidgetdecoration';
|
||||
|
||||
export class BreakpointWidget extends ZoneWidget implements IPrivateBreakpointWidgetService {
|
||||
public _serviceBrand: any;
|
||||
|
||||
private selectContainer: HTMLElement;
|
||||
private input: IActiveCodeEditor;
|
||||
private toDispose: lifecycle.IDisposable[];
|
||||
private conditionInput = '';
|
||||
private hitCountInput = '';
|
||||
private logMessageInput = '';
|
||||
private breakpoint: IBreakpoint | undefined;
|
||||
|
||||
constructor(editor: ICodeEditor, private lineNumber: number, private context: Context,
|
||||
@IContextViewService private readonly contextViewService: IContextViewService,
|
||||
@IDebugService private readonly debugService: IDebugService,
|
||||
@IThemeService private readonly themeService: IThemeService,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IModelService private readonly modelService: IModelService,
|
||||
@ICodeEditorService private readonly codeEditorService: ICodeEditorService,
|
||||
) {
|
||||
super(editor, { showFrame: true, showArrow: false, frameWidth: 1 });
|
||||
|
||||
this.toDispose = [];
|
||||
const model = this.editor.getModel();
|
||||
if (model) {
|
||||
const uri = model.uri;
|
||||
const breakpoints = this.debugService.getModel().getBreakpoints({ lineNumber: this.lineNumber, uri });
|
||||
this.breakpoint = breakpoints.length ? breakpoints[0] : undefined;
|
||||
}
|
||||
|
||||
if (this.context === undefined) {
|
||||
if (this.breakpoint && !this.breakpoint.condition && !this.breakpoint.hitCondition && this.breakpoint.logMessage) {
|
||||
this.context = Context.LOG_MESSAGE;
|
||||
} else if (this.breakpoint && !this.breakpoint.condition && this.breakpoint.hitCondition) {
|
||||
this.context = Context.HIT_COUNT;
|
||||
} else {
|
||||
this.context = Context.CONDITION;
|
||||
}
|
||||
}
|
||||
|
||||
this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(e => {
|
||||
if (this.breakpoint && e && e.removed && e.removed.indexOf(this.breakpoint) >= 0) {
|
||||
this.dispose();
|
||||
}
|
||||
}));
|
||||
this.codeEditorService.registerDecorationType(DECORATION_KEY, {});
|
||||
|
||||
this.create();
|
||||
}
|
||||
|
||||
private get placeholder(): string {
|
||||
switch (this.context) {
|
||||
case Context.LOG_MESSAGE:
|
||||
return nls.localize('breakpointWidgetLogMessagePlaceholder', "Message to log when breakpoint is hit. Expressions within {} are interpolated. 'Enter' to accept, 'esc' to cancel.");
|
||||
case Context.HIT_COUNT:
|
||||
return nls.localize('breakpointWidgetHitCountPlaceholder', "Break when hit count condition is met. 'Enter' to accept, 'esc' to cancel.");
|
||||
default:
|
||||
return nls.localize('breakpointWidgetExpressionPlaceholder', "Break when expression evaluates to true. 'Enter' to accept, 'esc' to cancel.");
|
||||
}
|
||||
}
|
||||
|
||||
private getInputValue(breakpoint: IBreakpoint | undefined): string {
|
||||
switch (this.context) {
|
||||
case Context.LOG_MESSAGE:
|
||||
return breakpoint && breakpoint.logMessage ? breakpoint.logMessage : this.logMessageInput;
|
||||
case Context.HIT_COUNT:
|
||||
return breakpoint && breakpoint.hitCondition ? breakpoint.hitCondition : this.hitCountInput;
|
||||
default:
|
||||
return breakpoint && breakpoint.condition ? breakpoint.condition : this.conditionInput;
|
||||
}
|
||||
}
|
||||
|
||||
private rememberInput(): void {
|
||||
const value = this.input.getModel().getValue();
|
||||
switch (this.context) {
|
||||
case Context.LOG_MESSAGE:
|
||||
this.logMessageInput = value;
|
||||
break;
|
||||
case Context.HIT_COUNT:
|
||||
this.hitCountInput = value;
|
||||
break;
|
||||
default:
|
||||
this.conditionInput = value;
|
||||
}
|
||||
}
|
||||
|
||||
public show(rangeOrPos: IRange | IPosition, heightInLines: number) {
|
||||
const lineNum = this.input.getModel().getLineCount();
|
||||
super.show(rangeOrPos, lineNum + 1);
|
||||
}
|
||||
|
||||
public fitHeightToContent() {
|
||||
const lineNum = this.input.getModel().getLineCount();
|
||||
this._relayout(lineNum + 1);
|
||||
}
|
||||
|
||||
protected _fillContainer(container: HTMLElement): void {
|
||||
this.setCssClass('breakpoint-widget');
|
||||
const selectBox = new SelectBox(<ISelectOptionItem[]>[{ text: nls.localize('expression', "Expression") }, { text: nls.localize('hitCount', "Hit Count") }, { text: nls.localize('logMessage', "Log Message") }], this.context, this.contextViewService, undefined, { ariaLabel: nls.localize('breakpointType', 'Breakpoint Type') });
|
||||
this.toDispose.push(attachSelectBoxStyler(selectBox, this.themeService));
|
||||
this.selectContainer = $('.breakpoint-select-container');
|
||||
selectBox.render(dom.append(container, this.selectContainer));
|
||||
selectBox.onDidSelect(e => {
|
||||
this.rememberInput();
|
||||
this.context = e.index;
|
||||
|
||||
const value = this.getInputValue(this.breakpoint);
|
||||
this.input.getModel().setValue(value);
|
||||
});
|
||||
|
||||
this.createBreakpointInput(dom.append(container, $('.inputContainer')));
|
||||
|
||||
this.input.getModel().setValue(this.getInputValue(this.breakpoint));
|
||||
this.toDispose.push(this.input.getModel().onDidChangeContent(() => {
|
||||
this.fitHeightToContent();
|
||||
}));
|
||||
this.input.setPosition({ lineNumber: 1, column: this.input.getModel().getLineMaxColumn(1) });
|
||||
// Due to an electron bug we have to do the timeout, otherwise we do not get focus
|
||||
setTimeout(() => this.input.focus(), 150);
|
||||
}
|
||||
|
||||
public close(success: boolean): void {
|
||||
if (success) {
|
||||
// if there is already a breakpoint on this location - remove it.
|
||||
|
||||
let condition = this.breakpoint && this.breakpoint.condition;
|
||||
let hitCondition = this.breakpoint && this.breakpoint.hitCondition;
|
||||
let logMessage = this.breakpoint && this.breakpoint.logMessage;
|
||||
this.rememberInput();
|
||||
|
||||
if (this.conditionInput || this.context === Context.CONDITION) {
|
||||
condition = this.conditionInput;
|
||||
}
|
||||
if (this.hitCountInput || this.context === Context.HIT_COUNT) {
|
||||
hitCondition = this.hitCountInput;
|
||||
}
|
||||
if (this.logMessageInput || this.context === Context.LOG_MESSAGE) {
|
||||
logMessage = this.logMessageInput;
|
||||
}
|
||||
|
||||
if (this.breakpoint) {
|
||||
this.debugService.updateBreakpoints(this.breakpoint.uri, {
|
||||
[this.breakpoint.getId()]: {
|
||||
condition,
|
||||
hitCondition,
|
||||
logMessage
|
||||
}
|
||||
}, false);
|
||||
} else {
|
||||
const model = this.editor.getModel();
|
||||
if (model) {
|
||||
this.debugService.addBreakpoints(model.uri, [{
|
||||
lineNumber: this.lineNumber,
|
||||
enabled: true,
|
||||
condition,
|
||||
hitCondition,
|
||||
logMessage
|
||||
}], `breakpointWidget`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.dispose();
|
||||
}
|
||||
|
||||
protected _doLayout(heightInPixel: number, widthInPixel: number): void {
|
||||
this.input.layout({ height: heightInPixel, width: widthInPixel - 113 });
|
||||
}
|
||||
|
||||
private createBreakpointInput(container: HTMLElement): void {
|
||||
const scopedContextKeyService = this.contextKeyService.createScoped(container);
|
||||
this.toDispose.push(scopedContextKeyService);
|
||||
|
||||
const scopedInstatiationService = this.instantiationService.createChild(new ServiceCollection(
|
||||
[IContextKeyService, scopedContextKeyService], [IPrivateBreakpointWidgetService, this]));
|
||||
|
||||
const options = getSimpleEditorOptions();
|
||||
const codeEditorWidgetOptions = getSimpleCodeEditorWidgetOptions();
|
||||
this.input = <IActiveCodeEditor>scopedInstatiationService.createInstance(CodeEditorWidget, container, options, codeEditorWidgetOptions);
|
||||
CONTEXT_IN_BREAKPOINT_WIDGET.bindTo(scopedContextKeyService).set(true);
|
||||
const model = this.modelService.createModel('', null, uri.parse(`${DEBUG_SCHEME}:${this.editor.getId()}:breakpointinput`), true);
|
||||
this.input.setModel(model);
|
||||
this.toDispose.push(model);
|
||||
const setDecorations = () => {
|
||||
const value = this.input.getModel().getValue();
|
||||
const decorations = !!value ? [] : this.createDecorations();
|
||||
this.input.setDecorations(DECORATION_KEY, decorations);
|
||||
};
|
||||
this.input.getModel().onDidChangeContent(() => setDecorations());
|
||||
this.themeService.onThemeChange(() => setDecorations());
|
||||
|
||||
this.toDispose.push(CompletionProviderRegistry.register({ scheme: DEBUG_SCHEME, hasAccessToAllModels: true }, {
|
||||
provideCompletionItems: (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken): Promise<CompletionList> => {
|
||||
let suggestionsPromise: Promise<CompletionList>;
|
||||
const underlyingModel = this.editor.getModel();
|
||||
if (underlyingModel && (this.context === Context.CONDITION || this.context === Context.LOG_MESSAGE && this.isCurlyBracketOpen())) {
|
||||
suggestionsPromise = provideSuggestionItems(underlyingModel, new Position(this.lineNumber, 1), new CompletionOptions(undefined, new Set<CompletionItemKind>().add(CompletionItemKind.Snippet)), _context, token).then(suggestions => {
|
||||
|
||||
let overwriteBefore = 0;
|
||||
if (this.context === Context.CONDITION) {
|
||||
overwriteBefore = position.column - 1;
|
||||
} else {
|
||||
// Inside the currly brackets, need to count how many useful characters are behind the position so they would all be taken into account
|
||||
const value = this.input.getModel().getValue();
|
||||
while ((position.column - 2 - overwriteBefore >= 0) && value[position.column - 2 - overwriteBefore] !== '{' && value[position.column - 2 - overwriteBefore] !== ' ') {
|
||||
overwriteBefore++;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
suggestions: suggestions.map(s => {
|
||||
s.completion.range = Range.fromPositions(position.delta(0, -overwriteBefore), position);
|
||||
return s.completion;
|
||||
})
|
||||
};
|
||||
});
|
||||
} else {
|
||||
suggestionsPromise = Promise.resolve({ suggestions: [] });
|
||||
}
|
||||
|
||||
return suggestionsPromise;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private createDecorations(): IDecorationOptions[] {
|
||||
const transparentForeground = transparent(editorForeground, 0.4)(this.themeService.getTheme());
|
||||
return [{
|
||||
range: {
|
||||
startLineNumber: 0,
|
||||
endLineNumber: 0,
|
||||
startColumn: 0,
|
||||
endColumn: 1
|
||||
},
|
||||
renderOptions: {
|
||||
after: {
|
||||
contentText: this.placeholder,
|
||||
color: transparentForeground ? transparentForeground.toString() : undefined
|
||||
}
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
private isCurlyBracketOpen(): boolean {
|
||||
const value = this.input.getModel().getValue();
|
||||
const position = this.input.getPosition();
|
||||
if (position) {
|
||||
for (let i = position.column - 2; i >= 0; i--) {
|
||||
if (value[i] === '{') {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value[i] === '}') {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
this.input.dispose();
|
||||
lifecycle.dispose(this.toDispose);
|
||||
setTimeout(() => this.editor.focus(), 0);
|
||||
}
|
||||
}
|
||||
|
||||
class AcceptBreakpointWidgetInputAction extends EditorCommand {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'breakpointWidget.action.acceptInput',
|
||||
precondition: CONTEXT_BREAKPOINT_WIDGET_VISIBLE,
|
||||
kbOpts: {
|
||||
kbExpr: CONTEXT_IN_BREAKPOINT_WIDGET,
|
||||
primary: KeyCode.Enter,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
accessor.get(IPrivateBreakpointWidgetService).close(true);
|
||||
}
|
||||
}
|
||||
|
||||
class CloseBreakpointWidgetCommand extends EditorCommand {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'closeBreakpointWidget',
|
||||
precondition: CONTEXT_BREAKPOINT_WIDGET_VISIBLE,
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.textInputFocus,
|
||||
primary: KeyCode.Escape,
|
||||
secondary: [KeyMod.Shift | KeyCode.Escape],
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void {
|
||||
const debugContribution = editor.getContribution<IDebugEditorContribution>(EDITOR_CONTRIBUTION_ID);
|
||||
if (debugContribution) {
|
||||
// if focus is in outer editor we need to use the debug contribution to close
|
||||
return debugContribution.closeBreakpointWidget();
|
||||
}
|
||||
|
||||
accessor.get(IPrivateBreakpointWidgetService).close(false);
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorCommand(new AcceptBreakpointWidgetInputAction());
|
||||
registerEditorCommand(new CloseBreakpointWidgetCommand());
|
||||
645
src/vs/workbench/contrib/debug/browser/breakpointsView.ts
Normal file
@@ -0,0 +1,645 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 resources from 'vs/base/common/resources';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { IAction, Action } from 'vs/base/common/actions';
|
||||
import { IDebugService, IBreakpoint, CONTEXT_BREAKPOINTS_FOCUSED, EDITOR_CONTRIBUTION_ID, State, DEBUG_SCHEME, IFunctionBreakpoint, IExceptionBreakpoint, IEnablement, IDebugEditorContribution } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { ExceptionBreakpoint, FunctionBreakpoint, Breakpoint } from 'vs/workbench/contrib/debug/common/debugModel';
|
||||
import { AddFunctionBreakpointAction, ToggleBreakpointsActivatedAction, RemoveAllBreakpointsAction, RemoveBreakpointAction, EnableAllBreakpointsAction, DisableAllBreakpointsAction, ReapplyBreakpointsAction } from 'vs/workbench/contrib/debug/browser/debugActions';
|
||||
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { Constants } from 'vs/editor/common/core/uint';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IListVirtualDelegate, IListContextMenuEvent, IListRenderer } from 'vs/base/browser/ui/list/list';
|
||||
import { IEditor } from 'vs/workbench/common/editor';
|
||||
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { WorkbenchList } from 'vs/platform/list/browser/listService';
|
||||
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { attachInputBoxStyler } from 'vs/platform/theme/common/styler';
|
||||
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { ViewletPanel, IViewletPanelOptions } from 'vs/workbench/browser/parts/views/panelViewlet';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
function createCheckbox(): HTMLInputElement {
|
||||
const checkbox = <HTMLInputElement>$('input');
|
||||
checkbox.type = 'checkbox';
|
||||
checkbox.tabIndex = -1;
|
||||
|
||||
return checkbox;
|
||||
}
|
||||
|
||||
export class BreakpointsView extends ViewletPanel {
|
||||
|
||||
private static readonly MAX_VISIBLE_FILES = 9;
|
||||
private list: WorkbenchList<IEnablement>;
|
||||
private needsRefresh: boolean;
|
||||
|
||||
constructor(
|
||||
options: IViewletViewOptions,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IDebugService private readonly debugService: IDebugService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IThemeService private readonly themeService: IThemeService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IContextViewService private readonly contextViewService: IContextViewService,
|
||||
@IConfigurationService configurationService: IConfigurationService
|
||||
) {
|
||||
super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('breakpointsSection', "Breakpoints Section") }, keybindingService, contextMenuService, configurationService);
|
||||
|
||||
this.minimumBodySize = this.maximumBodySize = this.getExpandedBodySize();
|
||||
this.disposables.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.onBreakpointsChange()));
|
||||
}
|
||||
|
||||
public renderBody(container: HTMLElement): void {
|
||||
dom.addClass(container, 'debug-breakpoints');
|
||||
const delegate = new BreakpointsDelegate(this.debugService);
|
||||
|
||||
this.list = this.instantiationService.createInstance(WorkbenchList, container, delegate, [
|
||||
this.instantiationService.createInstance(BreakpointsRenderer),
|
||||
new ExceptionBreakpointsRenderer(this.debugService),
|
||||
this.instantiationService.createInstance(FunctionBreakpointsRenderer),
|
||||
new FunctionBreakpointInputRenderer(this.debugService, this.contextViewService, this.themeService)
|
||||
], {
|
||||
identityProvider: { getId: (element: IEnablement) => element.getId() },
|
||||
multipleSelectionSupport: false,
|
||||
keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: e => e }
|
||||
}) as WorkbenchList<IEnablement>;
|
||||
|
||||
CONTEXT_BREAKPOINTS_FOCUSED.bindTo(this.list.contextKeyService);
|
||||
|
||||
this.list.onContextMenu(this.onListContextMenu, this, this.disposables);
|
||||
|
||||
this.disposables.push(this.list.onDidOpen(e => {
|
||||
let isSingleClick = false;
|
||||
let isDoubleClick = false;
|
||||
let isMiddleClick = false;
|
||||
let openToSide = false;
|
||||
|
||||
const browserEvent = e.browserEvent;
|
||||
if (browserEvent instanceof MouseEvent) {
|
||||
isSingleClick = browserEvent.detail === 1;
|
||||
isDoubleClick = browserEvent.detail === 2;
|
||||
isMiddleClick = browserEvent.button === 1;
|
||||
openToSide = (browserEvent.ctrlKey || browserEvent.metaKey || browserEvent.altKey);
|
||||
}
|
||||
|
||||
const focused = this.list.getFocusedElements();
|
||||
const element = focused.length ? focused[0] : undefined;
|
||||
|
||||
if (isMiddleClick) {
|
||||
if (element instanceof Breakpoint) {
|
||||
this.debugService.removeBreakpoints(element.getId());
|
||||
} else if (element instanceof FunctionBreakpoint) {
|
||||
this.debugService.removeFunctionBreakpoints(element.getId());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (element instanceof Breakpoint) {
|
||||
openBreakpointSource(element, openToSide, isSingleClick, this.debugService, this.editorService);
|
||||
}
|
||||
if (isDoubleClick && element instanceof FunctionBreakpoint && element !== this.debugService.getViewModel().getSelectedFunctionBreakpoint()) {
|
||||
this.debugService.getViewModel().setSelectedFunctionBreakpoint(element);
|
||||
this.onBreakpointsChange();
|
||||
}
|
||||
}));
|
||||
|
||||
this.list.splice(0, this.list.length, this.elements);
|
||||
|
||||
this.disposables.push(this.onDidChangeBodyVisibility(visible => {
|
||||
if (visible && this.needsRefresh) {
|
||||
this.onBreakpointsChange();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public focus(): void {
|
||||
super.focus();
|
||||
if (this.list) {
|
||||
this.list.domFocus();
|
||||
}
|
||||
}
|
||||
|
||||
protected layoutBody(height: number, width: number): void {
|
||||
if (this.list) {
|
||||
this.list.layout(height, width);
|
||||
}
|
||||
}
|
||||
|
||||
private onListContextMenu(e: IListContextMenuEvent<IEnablement>): void {
|
||||
if (!e.element) {
|
||||
return;
|
||||
}
|
||||
|
||||
const actions: IAction[] = [];
|
||||
const element = e.element;
|
||||
|
||||
const breakpointType = element instanceof Breakpoint && element.logMessage ? nls.localize('Logpoint', "Logpoint") : nls.localize('Breakpoint', "Breakpoint");
|
||||
if (element instanceof Breakpoint || element instanceof FunctionBreakpoint) {
|
||||
actions.push(new Action('workbench.action.debug.openEditorAndEditBreakpoint', nls.localize('editBreakpoint', "Edit {0}...", breakpointType), '', true, () => {
|
||||
if (element instanceof Breakpoint) {
|
||||
return openBreakpointSource(element, false, false, this.debugService, this.editorService).then(editor => {
|
||||
if (editor) {
|
||||
const codeEditor = editor.getControl();
|
||||
if (isCodeEditor(codeEditor)) {
|
||||
codeEditor.getContribution<IDebugEditorContribution>(EDITOR_CONTRIBUTION_ID).showBreakpointWidget(element.lineNumber, element.column);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.debugService.getViewModel().setSelectedFunctionBreakpoint(element);
|
||||
this.onBreakpointsChange();
|
||||
return Promise.resolve(undefined);
|
||||
}));
|
||||
actions.push(new Separator());
|
||||
}
|
||||
|
||||
actions.push(new RemoveBreakpointAction(RemoveBreakpointAction.ID, nls.localize('removeBreakpoint', "Remove {0}", breakpointType), this.debugService, this.keybindingService));
|
||||
|
||||
if (this.debugService.getModel().getBreakpoints().length + this.debugService.getModel().getFunctionBreakpoints().length > 1) {
|
||||
actions.push(new RemoveAllBreakpointsAction(RemoveAllBreakpointsAction.ID, RemoveAllBreakpointsAction.LABEL, this.debugService, this.keybindingService));
|
||||
actions.push(new Separator());
|
||||
|
||||
actions.push(new EnableAllBreakpointsAction(EnableAllBreakpointsAction.ID, EnableAllBreakpointsAction.LABEL, this.debugService, this.keybindingService));
|
||||
actions.push(new DisableAllBreakpointsAction(DisableAllBreakpointsAction.ID, DisableAllBreakpointsAction.LABEL, this.debugService, this.keybindingService));
|
||||
}
|
||||
|
||||
actions.push(new Separator());
|
||||
actions.push(new ReapplyBreakpointsAction(ReapplyBreakpointsAction.ID, ReapplyBreakpointsAction.LABEL, this.debugService, this.keybindingService));
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => e.anchor,
|
||||
getActions: () => actions,
|
||||
getActionsContext: () => element
|
||||
});
|
||||
}
|
||||
|
||||
public getActions(): IAction[] {
|
||||
return [
|
||||
new AddFunctionBreakpointAction(AddFunctionBreakpointAction.ID, AddFunctionBreakpointAction.LABEL, this.debugService, this.keybindingService),
|
||||
new ToggleBreakpointsActivatedAction(ToggleBreakpointsActivatedAction.ID, ToggleBreakpointsActivatedAction.ACTIVATE_LABEL, this.debugService, this.keybindingService),
|
||||
new RemoveAllBreakpointsAction(RemoveAllBreakpointsAction.ID, RemoveAllBreakpointsAction.LABEL, this.debugService, this.keybindingService)
|
||||
];
|
||||
}
|
||||
|
||||
private onBreakpointsChange(): void {
|
||||
if (this.isBodyVisible()) {
|
||||
this.minimumBodySize = this.getExpandedBodySize();
|
||||
if (this.maximumBodySize < Number.POSITIVE_INFINITY) {
|
||||
this.maximumBodySize = this.minimumBodySize;
|
||||
}
|
||||
if (this.list) {
|
||||
this.list.splice(0, this.list.length, this.elements);
|
||||
this.needsRefresh = false;
|
||||
}
|
||||
} else {
|
||||
this.needsRefresh = true;
|
||||
}
|
||||
}
|
||||
|
||||
private get elements(): IEnablement[] {
|
||||
const model = this.debugService.getModel();
|
||||
const elements = (<ReadonlyArray<IEnablement>>model.getExceptionBreakpoints()).concat(model.getFunctionBreakpoints()).concat(model.getBreakpoints());
|
||||
|
||||
return elements;
|
||||
}
|
||||
|
||||
private getExpandedBodySize(): number {
|
||||
const model = this.debugService.getModel();
|
||||
const length = model.getBreakpoints().length + model.getExceptionBreakpoints().length + model.getFunctionBreakpoints().length;
|
||||
return Math.min(BreakpointsView.MAX_VISIBLE_FILES, length) * 22;
|
||||
}
|
||||
}
|
||||
|
||||
class BreakpointsDelegate implements IListVirtualDelegate<IEnablement> {
|
||||
|
||||
constructor(private debugService: IDebugService) {
|
||||
// noop
|
||||
}
|
||||
|
||||
getHeight(element: IEnablement): number {
|
||||
return 22;
|
||||
}
|
||||
|
||||
getTemplateId(element: IEnablement): string {
|
||||
if (element instanceof Breakpoint) {
|
||||
return BreakpointsRenderer.ID;
|
||||
}
|
||||
if (element instanceof FunctionBreakpoint) {
|
||||
const selected = this.debugService.getViewModel().getSelectedFunctionBreakpoint();
|
||||
if (!element.name || (selected && selected.getId() === element.getId())) {
|
||||
return FunctionBreakpointInputRenderer.ID;
|
||||
}
|
||||
|
||||
return FunctionBreakpointsRenderer.ID;
|
||||
}
|
||||
if (element instanceof ExceptionBreakpoint) {
|
||||
return ExceptionBreakpointsRenderer.ID;
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
interface IBaseBreakpointTemplateData {
|
||||
breakpoint: HTMLElement;
|
||||
name: HTMLElement;
|
||||
checkbox: HTMLInputElement;
|
||||
context: IEnablement;
|
||||
toDispose: IDisposable[];
|
||||
}
|
||||
|
||||
interface IBaseBreakpointWithIconTemplateData extends IBaseBreakpointTemplateData {
|
||||
icon: HTMLElement;
|
||||
}
|
||||
|
||||
interface IBreakpointTemplateData extends IBaseBreakpointWithIconTemplateData {
|
||||
lineNumber: HTMLElement;
|
||||
filePath: HTMLElement;
|
||||
}
|
||||
|
||||
interface IInputTemplateData {
|
||||
inputBox: InputBox;
|
||||
checkbox: HTMLInputElement;
|
||||
icon: HTMLElement;
|
||||
breakpoint: IFunctionBreakpoint;
|
||||
reactedOnEvent: boolean;
|
||||
toDispose: IDisposable[];
|
||||
}
|
||||
|
||||
class BreakpointsRenderer implements IListRenderer<IBreakpoint, IBreakpointTemplateData> {
|
||||
|
||||
constructor(
|
||||
@IDebugService private readonly debugService: IDebugService,
|
||||
@ILabelService private readonly labelService: ILabelService
|
||||
) {
|
||||
// noop
|
||||
}
|
||||
|
||||
static readonly ID = 'breakpoints';
|
||||
|
||||
get templateId() {
|
||||
return BreakpointsRenderer.ID;
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): IBreakpointTemplateData {
|
||||
const data: IBreakpointTemplateData = Object.create(null);
|
||||
data.breakpoint = dom.append(container, $('.breakpoint'));
|
||||
|
||||
data.icon = $('.icon');
|
||||
data.checkbox = createCheckbox();
|
||||
data.toDispose = [];
|
||||
data.toDispose.push(dom.addStandardDisposableListener(data.checkbox, 'change', (e) => {
|
||||
this.debugService.enableOrDisableBreakpoints(!data.context.enabled, data.context);
|
||||
}));
|
||||
|
||||
dom.append(data.breakpoint, data.icon);
|
||||
dom.append(data.breakpoint, data.checkbox);
|
||||
|
||||
data.name = dom.append(data.breakpoint, $('span.name'));
|
||||
|
||||
data.filePath = dom.append(data.breakpoint, $('span.file-path'));
|
||||
const lineNumberContainer = dom.append(data.breakpoint, $('.line-number-container'));
|
||||
data.lineNumber = dom.append(lineNumberContainer, $('span.line-number'));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
renderElement(breakpoint: IBreakpoint, index: number, data: IBreakpointTemplateData): void {
|
||||
data.context = breakpoint;
|
||||
dom.toggleClass(data.breakpoint, 'disabled', !this.debugService.getModel().areBreakpointsActivated());
|
||||
|
||||
data.name.textContent = resources.basenameOrAuthority(breakpoint.uri);
|
||||
data.lineNumber.textContent = breakpoint.lineNumber.toString();
|
||||
if (breakpoint.column) {
|
||||
data.lineNumber.textContent += `:${breakpoint.column}`;
|
||||
}
|
||||
data.filePath.textContent = this.labelService.getUriLabel(resources.dirname(breakpoint.uri), { relative: true });
|
||||
data.checkbox.checked = breakpoint.enabled;
|
||||
|
||||
const { message, className } = getBreakpointMessageAndClassName(this.debugService, breakpoint);
|
||||
data.icon.className = className + ' icon';
|
||||
data.breakpoint.title = breakpoint.message || message || '';
|
||||
|
||||
const debugActive = this.debugService.state === State.Running || this.debugService.state === State.Stopped;
|
||||
if (debugActive && !breakpoint.verified) {
|
||||
dom.addClass(data.breakpoint, 'disabled');
|
||||
}
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: IBreakpointTemplateData): void {
|
||||
dispose(templateData.toDispose);
|
||||
}
|
||||
}
|
||||
|
||||
class ExceptionBreakpointsRenderer implements IListRenderer<IExceptionBreakpoint, IBaseBreakpointTemplateData> {
|
||||
|
||||
constructor(
|
||||
private debugService: IDebugService
|
||||
) {
|
||||
// noop
|
||||
}
|
||||
|
||||
static readonly ID = 'exceptionbreakpoints';
|
||||
|
||||
get templateId() {
|
||||
return ExceptionBreakpointsRenderer.ID;
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): IBaseBreakpointTemplateData {
|
||||
const data: IBreakpointTemplateData = Object.create(null);
|
||||
data.breakpoint = dom.append(container, $('.breakpoint'));
|
||||
|
||||
data.checkbox = createCheckbox();
|
||||
data.toDispose = [];
|
||||
data.toDispose.push(dom.addStandardDisposableListener(data.checkbox, 'change', (e) => {
|
||||
this.debugService.enableOrDisableBreakpoints(!data.context.enabled, data.context);
|
||||
}));
|
||||
|
||||
dom.append(data.breakpoint, data.checkbox);
|
||||
|
||||
data.name = dom.append(data.breakpoint, $('span.name'));
|
||||
dom.addClass(data.breakpoint, 'exception');
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
renderElement(exceptionBreakpoint: IExceptionBreakpoint, index: number, data: IBaseBreakpointTemplateData): void {
|
||||
data.context = exceptionBreakpoint;
|
||||
data.name.textContent = exceptionBreakpoint.label || `${exceptionBreakpoint.filter} exceptions`;
|
||||
data.breakpoint.title = data.name.textContent;
|
||||
data.checkbox.checked = exceptionBreakpoint.enabled;
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: IBaseBreakpointTemplateData): void {
|
||||
dispose(templateData.toDispose);
|
||||
}
|
||||
}
|
||||
|
||||
class FunctionBreakpointsRenderer implements IListRenderer<FunctionBreakpoint, IBaseBreakpointWithIconTemplateData> {
|
||||
|
||||
constructor(
|
||||
@IDebugService private readonly debugService: IDebugService
|
||||
) {
|
||||
// noop
|
||||
}
|
||||
|
||||
static readonly ID = 'functionbreakpoints';
|
||||
|
||||
get templateId() {
|
||||
return FunctionBreakpointsRenderer.ID;
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): IBaseBreakpointWithIconTemplateData {
|
||||
const data: IBreakpointTemplateData = Object.create(null);
|
||||
data.breakpoint = dom.append(container, $('.breakpoint'));
|
||||
|
||||
data.icon = $('.icon');
|
||||
data.checkbox = createCheckbox();
|
||||
data.toDispose = [];
|
||||
data.toDispose.push(dom.addStandardDisposableListener(data.checkbox, 'change', (e) => {
|
||||
this.debugService.enableOrDisableBreakpoints(!data.context.enabled, data.context);
|
||||
}));
|
||||
|
||||
dom.append(data.breakpoint, data.icon);
|
||||
dom.append(data.breakpoint, data.checkbox);
|
||||
|
||||
data.name = dom.append(data.breakpoint, $('span.name'));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
renderElement(functionBreakpoint: FunctionBreakpoint, index: number, data: IBaseBreakpointWithIconTemplateData): void {
|
||||
data.context = functionBreakpoint;
|
||||
data.name.textContent = functionBreakpoint.name;
|
||||
const { className, message } = getBreakpointMessageAndClassName(this.debugService, functionBreakpoint);
|
||||
data.icon.className = className + ' icon';
|
||||
data.icon.title = message ? message : '';
|
||||
data.checkbox.checked = functionBreakpoint.enabled;
|
||||
data.breakpoint.title = functionBreakpoint.name;
|
||||
|
||||
// Mark function breakpoints as disabled if deactivated or if debug type does not support them #9099
|
||||
const session = this.debugService.getViewModel().focusedSession;
|
||||
dom.toggleClass(data.breakpoint, 'disabled', (session && !session.capabilities.supportsFunctionBreakpoints) || !this.debugService.getModel().areBreakpointsActivated());
|
||||
if (session && !session.capabilities.supportsFunctionBreakpoints) {
|
||||
data.breakpoint.title = nls.localize('functionBreakpointsNotSupported', "Function breakpoints are not supported by this debug type");
|
||||
}
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: IBaseBreakpointWithIconTemplateData): void {
|
||||
dispose(templateData.toDispose);
|
||||
}
|
||||
}
|
||||
|
||||
class FunctionBreakpointInputRenderer implements IListRenderer<IFunctionBreakpoint, IInputTemplateData> {
|
||||
|
||||
constructor(
|
||||
private debugService: IDebugService,
|
||||
private contextViewService: IContextViewService,
|
||||
private themeService: IThemeService
|
||||
) {
|
||||
// noop
|
||||
}
|
||||
|
||||
static readonly ID = 'functionbreakpointinput';
|
||||
|
||||
get templateId() {
|
||||
return FunctionBreakpointInputRenderer.ID;
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): IInputTemplateData {
|
||||
const template: IInputTemplateData = Object.create(null);
|
||||
|
||||
const breakpoint = dom.append(container, $('.breakpoint'));
|
||||
template.icon = $('.icon');
|
||||
template.checkbox = createCheckbox();
|
||||
|
||||
dom.append(breakpoint, template.icon);
|
||||
dom.append(breakpoint, template.checkbox);
|
||||
const inputBoxContainer = dom.append(breakpoint, $('.inputBoxContainer'));
|
||||
const inputBox = new InputBox(inputBoxContainer, this.contextViewService, {
|
||||
placeholder: nls.localize('functionBreakpointPlaceholder', "Function to break on"),
|
||||
ariaLabel: nls.localize('functionBreakPointInputAriaLabel', "Type function breakpoint")
|
||||
});
|
||||
const styler = attachInputBoxStyler(inputBox, this.themeService);
|
||||
const toDispose: IDisposable[] = [inputBox, styler];
|
||||
|
||||
const wrapUp = (renamed: boolean) => {
|
||||
if (!template.reactedOnEvent) {
|
||||
template.reactedOnEvent = true;
|
||||
this.debugService.getViewModel().setSelectedFunctionBreakpoint(undefined);
|
||||
if (inputBox.value && (renamed || template.breakpoint.name)) {
|
||||
this.debugService.renameFunctionBreakpoint(template.breakpoint.getId(), renamed ? inputBox.value : template.breakpoint.name);
|
||||
} else {
|
||||
this.debugService.removeFunctionBreakpoints(template.breakpoint.getId());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
toDispose.push(dom.addStandardDisposableListener(inputBox.inputElement, 'keydown', (e: IKeyboardEvent) => {
|
||||
const isEscape = e.equals(KeyCode.Escape);
|
||||
const isEnter = e.equals(KeyCode.Enter);
|
||||
if (isEscape || isEnter) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
wrapUp(isEnter);
|
||||
}
|
||||
}));
|
||||
toDispose.push(dom.addDisposableListener(inputBox.inputElement, 'blur', () => {
|
||||
// Need to react with a timeout on the blur event due to possible concurent splices #56443
|
||||
setTimeout(() => {
|
||||
if (!template.breakpoint.name) {
|
||||
wrapUp(true);
|
||||
}
|
||||
});
|
||||
}));
|
||||
|
||||
template.inputBox = inputBox;
|
||||
template.toDispose = toDispose;
|
||||
return template;
|
||||
}
|
||||
|
||||
renderElement(functionBreakpoint: FunctionBreakpoint, index: number, data: IInputTemplateData): void {
|
||||
data.breakpoint = functionBreakpoint;
|
||||
data.reactedOnEvent = false;
|
||||
const { className, message } = getBreakpointMessageAndClassName(this.debugService, functionBreakpoint);
|
||||
|
||||
data.icon.className = className + ' icon';
|
||||
data.icon.title = message ? message : '';
|
||||
data.checkbox.checked = functionBreakpoint.enabled;
|
||||
data.checkbox.disabled = true;
|
||||
data.inputBox.value = functionBreakpoint.name || '';
|
||||
setTimeout(() => {
|
||||
data.inputBox.focus();
|
||||
data.inputBox.select();
|
||||
}, 0);
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: IInputTemplateData): void {
|
||||
dispose(templateData.toDispose);
|
||||
}
|
||||
}
|
||||
|
||||
export function openBreakpointSource(breakpoint: IBreakpoint, sideBySide: boolean, preserveFocus: boolean, debugService: IDebugService, editorService: IEditorService): Promise<IEditor | null> {
|
||||
if (breakpoint.uri.scheme === DEBUG_SCHEME && debugService.state === State.Inactive) {
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
|
||||
const selection = breakpoint.endLineNumber ? {
|
||||
startLineNumber: breakpoint.lineNumber,
|
||||
endLineNumber: breakpoint.endLineNumber,
|
||||
startColumn: breakpoint.column || 1,
|
||||
endColumn: breakpoint.endColumn || Constants.MAX_SAFE_SMALL_INTEGER
|
||||
} : {
|
||||
startLineNumber: breakpoint.lineNumber,
|
||||
startColumn: breakpoint.column || 1,
|
||||
endLineNumber: breakpoint.lineNumber,
|
||||
endColumn: breakpoint.column || Constants.MAX_SAFE_SMALL_INTEGER
|
||||
};
|
||||
|
||||
return editorService.openEditor({
|
||||
resource: breakpoint.uri,
|
||||
options: {
|
||||
preserveFocus,
|
||||
selection,
|
||||
revealIfVisible: true,
|
||||
revealInCenterIfOutsideViewport: true,
|
||||
pinned: !preserveFocus
|
||||
}
|
||||
}, sideBySide ? SIDE_GROUP : ACTIVE_GROUP);
|
||||
}
|
||||
|
||||
export function getBreakpointMessageAndClassName(debugService: IDebugService, breakpoint: IBreakpoint | FunctionBreakpoint): { message?: string, className: string } {
|
||||
const state = debugService.state;
|
||||
const debugActive = state === State.Running || state === State.Stopped;
|
||||
|
||||
if (!breakpoint.enabled || !debugService.getModel().areBreakpointsActivated()) {
|
||||
return {
|
||||
className: breakpoint instanceof FunctionBreakpoint ? 'debug-function-breakpoint-disabled' : breakpoint.logMessage ? 'debug-breakpoint-log-disabled' : 'debug-breakpoint-disabled',
|
||||
message: breakpoint.logMessage ? nls.localize('disabledLogpoint', "Disabled logpoint") : nls.localize('disabledBreakpoint', "Disabled breakpoint"),
|
||||
};
|
||||
}
|
||||
|
||||
const appendMessage = (text: string): string => {
|
||||
return !(breakpoint instanceof FunctionBreakpoint) && breakpoint.message ? text.concat(', ' + breakpoint.message) : text;
|
||||
};
|
||||
if (debugActive && !breakpoint.verified) {
|
||||
return {
|
||||
className: breakpoint instanceof FunctionBreakpoint ? 'debug-function-breakpoint-unverified' : breakpoint.logMessage ? 'debug-breakpoint-log-unverified' : 'debug-breakpoint-unverified',
|
||||
message: breakpoint.logMessage ? nls.localize('unverifiedLogpoint', "Unverified logpoint") : nls.localize('unverifiedBreakopint', "Unverified breakpoint"),
|
||||
};
|
||||
}
|
||||
|
||||
const session = debugService.getViewModel().focusedSession;
|
||||
if (breakpoint instanceof FunctionBreakpoint) {
|
||||
if (session && !session.capabilities.supportsFunctionBreakpoints) {
|
||||
return {
|
||||
className: 'debug-function-breakpoint-unverified',
|
||||
message: nls.localize('functionBreakpointUnsupported', "Function breakpoints not supported by this debug type"),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
className: 'debug-function-breakpoint',
|
||||
};
|
||||
}
|
||||
|
||||
if (breakpoint.logMessage || breakpoint.condition || breakpoint.hitCondition) {
|
||||
const messages: string[] = [];
|
||||
if (breakpoint.logMessage) {
|
||||
if (session && !session.capabilities.supportsLogPoints) {
|
||||
return {
|
||||
className: 'debug-breakpoint-unsupported',
|
||||
message: nls.localize('logBreakpointUnsupported', "Logpoints not supported by this debug type"),
|
||||
};
|
||||
}
|
||||
|
||||
messages.push(nls.localize('logMessage', "Log Message: {0}", breakpoint.logMessage));
|
||||
}
|
||||
|
||||
if (session && breakpoint.condition && !session.capabilities.supportsConditionalBreakpoints) {
|
||||
return {
|
||||
className: 'debug-breakpoint-unsupported',
|
||||
message: nls.localize('conditionalBreakpointUnsupported', "Conditional breakpoints not supported by this debug type"),
|
||||
};
|
||||
}
|
||||
if (session && breakpoint.hitCondition && !session.capabilities.supportsHitConditionalBreakpoints) {
|
||||
return {
|
||||
className: 'debug-breakpoint-unsupported',
|
||||
message: nls.localize('hitBreakpointUnsupported', "Hit conditional breakpoints not supported by this debug type"),
|
||||
};
|
||||
}
|
||||
|
||||
if (breakpoint.condition) {
|
||||
messages.push(nls.localize('expression', "Expression: {0}", breakpoint.condition));
|
||||
}
|
||||
if (breakpoint.hitCondition) {
|
||||
messages.push(nls.localize('hitCount', "Hit Count: {0}", breakpoint.hitCondition));
|
||||
}
|
||||
|
||||
return {
|
||||
className: breakpoint.logMessage ? 'debug-breakpoint-log' : 'debug-breakpoint-conditional',
|
||||
message: appendMessage(messages.join('\n'))
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
className: 'debug-breakpoint',
|
||||
message: breakpoint.message
|
||||
};
|
||||
}
|
||||
686
src/vs/workbench/contrib/debug/browser/callStackView.ts
Normal file
@@ -0,0 +1,686 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { RunOnceScheduler, ignoreErrors } from 'vs/base/common/async';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { IDebugService, State, IStackFrame, IDebugSession, IThread, CONTEXT_CALLSTACK_ITEM_TYPE, IDebugModel } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { Thread, StackFrame, ThreadAndSessionIds } from 'vs/workbench/contrib/debug/common/debugModel';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { MenuId, IMenu, IMenuService } from 'vs/platform/actions/common/actions';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { RestartAction, StopAction, ContinueAction, StepOverAction, StepIntoAction, StepOutAction, PauseAction, RestartFrameAction, TerminateThreadAction, CopyStackTraceAction } from 'vs/workbench/contrib/debug/browser/debugActions';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IViewletPanelOptions, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { fillInContextMenuActions } from 'vs/platform/actions/browser/menuItemActionItem';
|
||||
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
||||
import { ITreeRenderer, ITreeNode, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
|
||||
import { TreeResourceNavigator2, WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
|
||||
import { createMatches, FuzzyScore } from 'vs/base/common/filters';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
type CallStackItem = IStackFrame | IThread | IDebugSession | string | ThreadAndSessionIds | IStackFrame[];
|
||||
|
||||
export class CallStackView extends ViewletPanel {
|
||||
|
||||
private pauseMessage: HTMLSpanElement;
|
||||
private pauseMessageLabel: HTMLSpanElement;
|
||||
private onCallStackChangeScheduler: RunOnceScheduler;
|
||||
private needsRefresh: boolean;
|
||||
private ignoreSelectionChangedEvent: boolean;
|
||||
private ignoreFocusStackFrameEvent: boolean;
|
||||
private callStackItemType: IContextKey<string>;
|
||||
private dataSource: CallStackDataSource;
|
||||
private tree: WorkbenchAsyncDataTree<CallStackItem | IDebugModel, CallStackItem, FuzzyScore>;
|
||||
private contributedContextMenu: IMenu;
|
||||
|
||||
constructor(
|
||||
private options: IViewletViewOptions,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IDebugService private readonly debugService: IDebugService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IMenuService menuService: IMenuService,
|
||||
@IContextKeyService readonly contextKeyService: IContextKeyService,
|
||||
) {
|
||||
super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('callstackSection', "Call Stack Section") }, keybindingService, contextMenuService, configurationService);
|
||||
this.callStackItemType = CONTEXT_CALLSTACK_ITEM_TYPE.bindTo(contextKeyService);
|
||||
|
||||
this.contributedContextMenu = menuService.createMenu(MenuId.DebugCallStackContext, contextKeyService);
|
||||
this.disposables.push(this.contributedContextMenu);
|
||||
|
||||
// Create scheduler to prevent unnecessary flashing of tree when reacting to changes
|
||||
this.onCallStackChangeScheduler = new RunOnceScheduler(() => {
|
||||
// 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.
|
||||
const sessions = this.debugService.getModel().getSessions();
|
||||
const thread = sessions.length === 1 && sessions[0].getAllThreads().length === 1 ? sessions[0].getAllThreads()[0] : undefined;
|
||||
if (thread && thread.stoppedDetails) {
|
||||
this.pauseMessageLabel.textContent = thread.stoppedDetails.description || nls.localize('debugStopped', "Paused on {0}", thread.stoppedDetails.reason || '');
|
||||
this.pauseMessageLabel.title = thread.stoppedDetails.text || '';
|
||||
dom.toggleClass(this.pauseMessageLabel, 'exception', thread.stoppedDetails.reason === 'exception');
|
||||
this.pauseMessage.hidden = false;
|
||||
} else {
|
||||
this.pauseMessage.hidden = true;
|
||||
}
|
||||
|
||||
this.needsRefresh = false;
|
||||
this.dataSource.deemphasizedStackFramesToShow = [];
|
||||
this.tree.updateChildren().then(() => this.updateTreeSelection());
|
||||
}, 50);
|
||||
}
|
||||
|
||||
protected renderHeaderTitle(container: HTMLElement): void {
|
||||
const titleContainer = dom.append(container, $('.debug-call-stack-title'));
|
||||
super.renderHeaderTitle(titleContainer, this.options.title);
|
||||
|
||||
this.pauseMessage = dom.append(titleContainer, $('span.pause-message'));
|
||||
this.pauseMessage.hidden = true;
|
||||
this.pauseMessageLabel = dom.append(this.pauseMessage, $('span.label'));
|
||||
}
|
||||
|
||||
renderBody(container: HTMLElement): void {
|
||||
dom.addClass(container, 'debug-call-stack');
|
||||
const treeContainer = renderViewTree(container);
|
||||
|
||||
this.dataSource = new CallStackDataSource();
|
||||
this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, treeContainer, new CallStackDelegate(), [
|
||||
new SessionsRenderer(),
|
||||
new ThreadsRenderer(),
|
||||
this.instantiationService.createInstance(StackFramesRenderer),
|
||||
new ErrorsRenderer(),
|
||||
new LoadMoreRenderer(),
|
||||
new ShowMoreRenderer()
|
||||
], this.dataSource, {
|
||||
accessibilityProvider: new CallStackAccessibilityProvider(),
|
||||
ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'callStackAriaLabel' }, "Debug Call Stack"),
|
||||
identityProvider: {
|
||||
getId: element => {
|
||||
if (typeof element === 'string') {
|
||||
return element;
|
||||
}
|
||||
if (element instanceof Array) {
|
||||
return `showMore ${element[0].getId()}`;
|
||||
}
|
||||
|
||||
return (<IStackFrame | IThread | IDebugSession | ThreadAndSessionIds>element).getId();
|
||||
}
|
||||
},
|
||||
keyboardNavigationLabelProvider: {
|
||||
getKeyboardNavigationLabel: e => {
|
||||
if (isDebugSession(e)) {
|
||||
return e.getLabel();
|
||||
}
|
||||
if (e instanceof Thread) {
|
||||
return `${e.name} ${e.stateLabel}`;
|
||||
}
|
||||
if (e instanceof StackFrame || typeof e === 'string') {
|
||||
return e;
|
||||
}
|
||||
if (e instanceof ThreadAndSessionIds) {
|
||||
return LoadMoreRenderer.LABEL;
|
||||
}
|
||||
|
||||
return nls.localize('showMoreStackFrames2', "Show More Stack Frames");
|
||||
}
|
||||
}
|
||||
}) as WorkbenchAsyncDataTree<CallStackItem | IDebugModel, CallStackItem, FuzzyScore>;
|
||||
|
||||
this.tree.setInput(this.debugService.getModel()).then(undefined, onUnexpectedError);
|
||||
|
||||
const callstackNavigator = new TreeResourceNavigator2(this.tree);
|
||||
this.disposables.push(callstackNavigator);
|
||||
this.disposables.push(callstackNavigator.onDidOpenResource(e => {
|
||||
if (this.ignoreSelectionChangedEvent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const focusStackFrame = (stackFrame: IStackFrame | undefined, thread: IThread | undefined, session: IDebugSession) => {
|
||||
this.ignoreFocusStackFrameEvent = true;
|
||||
try {
|
||||
this.debugService.focusStackFrame(stackFrame, thread, session, true);
|
||||
} finally {
|
||||
this.ignoreFocusStackFrameEvent = false;
|
||||
}
|
||||
};
|
||||
|
||||
const element = e.element;
|
||||
if (element instanceof StackFrame) {
|
||||
focusStackFrame(element, element.thread, element.thread.session);
|
||||
element.openInEditor(this.editorService, e.editorOptions.preserveFocus, e.sideBySide, e.editorOptions.pinned);
|
||||
}
|
||||
if (element instanceof Thread) {
|
||||
focusStackFrame(undefined, element, element.session);
|
||||
}
|
||||
if (isDebugSession(element)) {
|
||||
focusStackFrame(undefined, undefined, element);
|
||||
}
|
||||
if (element instanceof ThreadAndSessionIds) {
|
||||
const session = this.debugService.getModel().getSessions().filter(p => p.getId() === element.sessionId).pop();
|
||||
const thread = session && session.getThread(element.threadId);
|
||||
if (thread) {
|
||||
(<Thread>thread).fetchCallStack()
|
||||
.then(() => this.tree.updateChildren());
|
||||
}
|
||||
}
|
||||
if (element instanceof Array) {
|
||||
this.dataSource.deemphasizedStackFramesToShow.push(...element);
|
||||
this.tree.updateChildren();
|
||||
}
|
||||
}));
|
||||
|
||||
this.disposables.push(this.debugService.getModel().onDidChangeCallStack(() => {
|
||||
if (!this.isBodyVisible()) {
|
||||
this.needsRefresh = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.onCallStackChangeScheduler.isScheduled()) {
|
||||
this.onCallStackChangeScheduler.schedule();
|
||||
}
|
||||
}));
|
||||
this.disposables.push(this.debugService.getViewModel().onDidFocusStackFrame(() => {
|
||||
if (this.ignoreFocusStackFrameEvent) {
|
||||
return;
|
||||
}
|
||||
if (!this.isBodyVisible()) {
|
||||
this.needsRefresh = true;
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateTreeSelection();
|
||||
}));
|
||||
this.disposables.push(this.tree.onContextMenu(e => this.onContextMenu(e)));
|
||||
|
||||
// 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(0);
|
||||
}
|
||||
|
||||
this.disposables.push(this.onDidChangeBodyVisibility(visible => {
|
||||
if (visible && this.needsRefresh) {
|
||||
this.onCallStackChangeScheduler.schedule();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
layoutBody(height: number, width: number): void {
|
||||
this.tree.layout(height, width);
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
this.tree.domFocus();
|
||||
}
|
||||
|
||||
private updateTreeSelection(): void {
|
||||
if (!this.tree || !this.tree.getInput()) {
|
||||
// Tree not initialized yet
|
||||
return;
|
||||
}
|
||||
|
||||
const updateSelectionAndReveal = (element: IStackFrame | IDebugSession) => {
|
||||
this.ignoreSelectionChangedEvent = true;
|
||||
try {
|
||||
this.tree.setSelection([element]);
|
||||
this.tree.reveal(element);
|
||||
} catch (e) { }
|
||||
finally {
|
||||
this.ignoreSelectionChangedEvent = false;
|
||||
}
|
||||
};
|
||||
|
||||
const thread = this.debugService.getViewModel().focusedThread;
|
||||
const session = this.debugService.getViewModel().focusedSession;
|
||||
const stackFrame = this.debugService.getViewModel().focusedStackFrame;
|
||||
if (!thread) {
|
||||
if (!session) {
|
||||
this.tree.setSelection([]);
|
||||
} else {
|
||||
updateSelectionAndReveal(session);
|
||||
}
|
||||
} else {
|
||||
const expansionsPromise = ignoreErrors(this.tree.expand(thread.session))
|
||||
.then(() => ignoreErrors(this.tree.expand(thread)));
|
||||
if (stackFrame) {
|
||||
expansionsPromise.then(() => updateSelectionAndReveal(stackFrame));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private onContextMenu(e: ITreeContextMenuEvent<CallStackItem>): void {
|
||||
const actions: IAction[] = [];
|
||||
const element = e.element;
|
||||
if (isDebugSession(element)) {
|
||||
this.callStackItemType.set('session');
|
||||
actions.push(this.instantiationService.createInstance(RestartAction, RestartAction.ID, RestartAction.LABEL));
|
||||
actions.push(new StopAction(StopAction.ID, StopAction.LABEL, this.debugService, this.keybindingService));
|
||||
} else if (element instanceof Thread) {
|
||||
this.callStackItemType.set('thread');
|
||||
const thread = <Thread>element;
|
||||
if (thread.stopped) {
|
||||
actions.push(new ContinueAction(ContinueAction.ID, ContinueAction.LABEL, this.debugService, this.keybindingService));
|
||||
actions.push(new StepOverAction(StepOverAction.ID, StepOverAction.LABEL, this.debugService, this.keybindingService));
|
||||
actions.push(new StepIntoAction(StepIntoAction.ID, StepIntoAction.LABEL, this.debugService, this.keybindingService));
|
||||
actions.push(new StepOutAction(StepOutAction.ID, StepOutAction.LABEL, this.debugService, this.keybindingService));
|
||||
} else {
|
||||
actions.push(new PauseAction(PauseAction.ID, PauseAction.LABEL, this.debugService, this.keybindingService));
|
||||
}
|
||||
|
||||
actions.push(new Separator());
|
||||
actions.push(new TerminateThreadAction(TerminateThreadAction.ID, TerminateThreadAction.LABEL, this.debugService, this.keybindingService));
|
||||
} else if (element instanceof StackFrame) {
|
||||
this.callStackItemType.set('stackFrame');
|
||||
if (element.thread.session.capabilities.supportsRestartFrame) {
|
||||
actions.push(new RestartFrameAction(RestartFrameAction.ID, RestartFrameAction.LABEL, this.debugService, this.keybindingService));
|
||||
}
|
||||
actions.push(this.instantiationService.createInstance(CopyStackTraceAction, CopyStackTraceAction.ID, CopyStackTraceAction.LABEL));
|
||||
} else {
|
||||
this.callStackItemType.reset();
|
||||
}
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => e.anchor,
|
||||
getActions: () => {
|
||||
fillInContextMenuActions(this.contributedContextMenu, { arg: this.getContextForContributedActions(element) }, actions, this.contextMenuService);
|
||||
return actions;
|
||||
},
|
||||
getActionsContext: () => element
|
||||
});
|
||||
}
|
||||
|
||||
private getContextForContributedActions(element: CallStackItem | null): string | number | undefined {
|
||||
if (element instanceof StackFrame) {
|
||||
if (element.source.inMemory) {
|
||||
return element.source.raw.path || element.source.reference;
|
||||
}
|
||||
|
||||
return element.source.uri.toString();
|
||||
}
|
||||
if (element instanceof Thread) {
|
||||
return element.threadId;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
interface IThreadTemplateData {
|
||||
thread: HTMLElement;
|
||||
name: HTMLElement;
|
||||
state: HTMLElement;
|
||||
stateLabel: HTMLSpanElement;
|
||||
label: HighlightedLabel;
|
||||
}
|
||||
|
||||
interface ISessionTemplateData {
|
||||
session: HTMLElement;
|
||||
name: HTMLElement;
|
||||
state: HTMLElement;
|
||||
stateLabel: HTMLSpanElement;
|
||||
label: HighlightedLabel;
|
||||
}
|
||||
|
||||
interface IErrorTemplateData {
|
||||
label: HTMLElement;
|
||||
}
|
||||
|
||||
interface ILabelTemplateData {
|
||||
label: HTMLElement;
|
||||
}
|
||||
|
||||
interface IStackFrameTemplateData {
|
||||
stackFrame: HTMLElement;
|
||||
file: HTMLElement;
|
||||
fileName: HTMLElement;
|
||||
lineNumber: HTMLElement;
|
||||
label: HighlightedLabel;
|
||||
}
|
||||
|
||||
class SessionsRenderer implements ITreeRenderer<IDebugSession, FuzzyScore, ISessionTemplateData> {
|
||||
static readonly ID = 'session';
|
||||
|
||||
get templateId(): string {
|
||||
return SessionsRenderer.ID;
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): ISessionTemplateData {
|
||||
const session = dom.append(container, $('.session'));
|
||||
const name = dom.append(session, $('.name'));
|
||||
const state = dom.append(session, $('.state'));
|
||||
const stateLabel = dom.append(state, $('span.label'));
|
||||
const label = new HighlightedLabel(name, false);
|
||||
|
||||
return { session, name, state, stateLabel, label };
|
||||
}
|
||||
|
||||
renderElement(element: ITreeNode<IDebugSession, FuzzyScore>, index: number, data: ISessionTemplateData): void {
|
||||
const session = element.element;
|
||||
data.session.title = nls.localize({ key: 'session', comment: ['Session is a noun'] }, "Session");
|
||||
data.label.set(session.getLabel(), createMatches(element.filterData));
|
||||
const stoppedThread = session.getAllThreads().filter(t => t.stopped).pop();
|
||||
|
||||
data.stateLabel.textContent = stoppedThread ? nls.localize('paused', "Paused")
|
||||
: nls.localize({ key: 'running', comment: ['indicates state'] }, "Running");
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: ISessionTemplateData): void {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
class ThreadsRenderer implements ITreeRenderer<IThread, FuzzyScore, IThreadTemplateData> {
|
||||
static readonly ID = 'thread';
|
||||
|
||||
get templateId(): string {
|
||||
return ThreadsRenderer.ID;
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): IThreadTemplateData {
|
||||
const thread = dom.append(container, $('.thread'));
|
||||
const name = dom.append(thread, $('.name'));
|
||||
const state = dom.append(thread, $('.state'));
|
||||
const stateLabel = dom.append(state, $('span.label'));
|
||||
const label = new HighlightedLabel(name, false);
|
||||
|
||||
return { thread, name, state, stateLabel, label };
|
||||
}
|
||||
|
||||
renderElement(element: ITreeNode<IThread, FuzzyScore>, index: number, data: IThreadTemplateData): void {
|
||||
const thread = element.element;
|
||||
data.thread.title = nls.localize('thread', "Thread");
|
||||
data.label.set(thread.name, createMatches(element.filterData));
|
||||
data.stateLabel.textContent = thread.stateLabel;
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: IThreadTemplateData): void {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
class StackFramesRenderer implements ITreeRenderer<IStackFrame, FuzzyScore, IStackFrameTemplateData> {
|
||||
static readonly ID = 'stackFrame';
|
||||
|
||||
constructor(@ILabelService private readonly labelService: ILabelService) { }
|
||||
|
||||
get templateId(): string {
|
||||
return StackFramesRenderer.ID;
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): IStackFrameTemplateData {
|
||||
const stackFrame = dom.append(container, $('.stack-frame'));
|
||||
const labelDiv = dom.append(stackFrame, $('span.label.expression'));
|
||||
const file = dom.append(stackFrame, $('.file'));
|
||||
const fileName = dom.append(file, $('span.file-name'));
|
||||
const wrapper = dom.append(file, $('span.line-number-wrapper'));
|
||||
const lineNumber = dom.append(wrapper, $('span.line-number'));
|
||||
const label = new HighlightedLabel(labelDiv, false);
|
||||
|
||||
return { file, fileName, label, lineNumber, stackFrame };
|
||||
}
|
||||
|
||||
renderElement(element: ITreeNode<IStackFrame, FuzzyScore>, index: number, data: IStackFrameTemplateData): void {
|
||||
const stackFrame = element.element;
|
||||
dom.toggleClass(data.stackFrame, 'disabled', !stackFrame.source || !stackFrame.source.available || isDeemphasized(stackFrame));
|
||||
dom.toggleClass(data.stackFrame, 'label', stackFrame.presentationHint === 'label');
|
||||
dom.toggleClass(data.stackFrame, 'subtle', stackFrame.presentationHint === 'subtle');
|
||||
|
||||
data.file.title = stackFrame.source.inMemory ? stackFrame.source.uri.path : this.labelService.getUriLabel(stackFrame.source.uri);
|
||||
if (stackFrame.source.raw.origin) {
|
||||
data.file.title += `\n${stackFrame.source.raw.origin}`;
|
||||
}
|
||||
data.label.set(stackFrame.name, createMatches(element.filterData), stackFrame.name);
|
||||
data.fileName.textContent = stackFrame.getSpecificSourceName();
|
||||
if (stackFrame.range.startLineNumber !== undefined) {
|
||||
data.lineNumber.textContent = `${stackFrame.range.startLineNumber}`;
|
||||
if (stackFrame.range.startColumn) {
|
||||
data.lineNumber.textContent += `:${stackFrame.range.startColumn}`;
|
||||
}
|
||||
dom.removeClass(data.lineNumber, 'unavailable');
|
||||
} else {
|
||||
dom.addClass(data.lineNumber, 'unavailable');
|
||||
}
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: IStackFrameTemplateData): void {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
class ErrorsRenderer implements ITreeRenderer<string, FuzzyScore, IErrorTemplateData> {
|
||||
static readonly ID = 'error';
|
||||
|
||||
get templateId(): string {
|
||||
return ErrorsRenderer.ID;
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): IErrorTemplateData {
|
||||
const label = dom.append(container, $('.error'));
|
||||
|
||||
return { label };
|
||||
}
|
||||
|
||||
renderElement(element: ITreeNode<string, FuzzyScore>, index: number, data: IErrorTemplateData): void {
|
||||
const error = element.element;
|
||||
data.label.textContent = error;
|
||||
data.label.title = error;
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: IErrorTemplateData): void {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
class LoadMoreRenderer implements ITreeRenderer<ThreadAndSessionIds, FuzzyScore, ILabelTemplateData> {
|
||||
static readonly ID = 'loadMore';
|
||||
static readonly LABEL = nls.localize('loadMoreStackFrames', "Load More Stack Frames");
|
||||
|
||||
get templateId(): string {
|
||||
return LoadMoreRenderer.ID;
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): IErrorTemplateData {
|
||||
const label = dom.append(container, $('.load-more'));
|
||||
|
||||
return { label };
|
||||
}
|
||||
|
||||
renderElement(element: ITreeNode<ThreadAndSessionIds, FuzzyScore>, index: number, data: ILabelTemplateData): void {
|
||||
data.label.textContent = LoadMoreRenderer.LABEL;
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: ILabelTemplateData): void {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
class ShowMoreRenderer implements ITreeRenderer<IStackFrame[], FuzzyScore, ILabelTemplateData> {
|
||||
static readonly ID = 'showMore';
|
||||
|
||||
get templateId(): string {
|
||||
return ShowMoreRenderer.ID;
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): IErrorTemplateData {
|
||||
const label = dom.append(container, $('.show-more'));
|
||||
|
||||
return { label };
|
||||
}
|
||||
|
||||
renderElement(element: ITreeNode<IStackFrame[], FuzzyScore>, index: number, data: ILabelTemplateData): void {
|
||||
const stackFrames = element.element;
|
||||
if (stackFrames.every(sf => !!(sf.source && sf.source.origin && sf.source.origin === stackFrames[0].source.origin))) {
|
||||
data.label.textContent = nls.localize('showMoreAndOrigin', "Show {0} More: {1}", stackFrames.length, stackFrames[0].source.origin);
|
||||
} else {
|
||||
data.label.textContent = nls.localize('showMoreStackFrames', "Show {0} More Stack Frames", stackFrames.length);
|
||||
}
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: ILabelTemplateData): void {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
class CallStackDelegate implements IListVirtualDelegate<CallStackItem> {
|
||||
|
||||
getHeight(element: CallStackItem): number {
|
||||
return 22;
|
||||
}
|
||||
|
||||
getTemplateId(element: CallStackItem): string {
|
||||
if (isDebugSession(element)) {
|
||||
return SessionsRenderer.ID;
|
||||
}
|
||||
if (element instanceof Thread) {
|
||||
return ThreadsRenderer.ID;
|
||||
}
|
||||
if (element instanceof StackFrame) {
|
||||
return StackFramesRenderer.ID;
|
||||
}
|
||||
if (typeof element === 'string') {
|
||||
return ErrorsRenderer.ID;
|
||||
}
|
||||
if (element instanceof ThreadAndSessionIds) {
|
||||
return LoadMoreRenderer.ID;
|
||||
}
|
||||
|
||||
// element instanceof Array
|
||||
return ShowMoreRenderer.ID;
|
||||
}
|
||||
}
|
||||
|
||||
function isDebugModel(obj: any): obj is IDebugModel {
|
||||
return typeof obj.getSessions === 'function';
|
||||
}
|
||||
|
||||
function isDebugSession(obj: any): obj is IDebugSession {
|
||||
return typeof obj.getAllThreads === 'function';
|
||||
}
|
||||
|
||||
function isDeemphasized(frame: IStackFrame): boolean {
|
||||
return frame.source.presentationHint === 'deemphasize' || frame.presentationHint === 'deemphasize';
|
||||
}
|
||||
|
||||
class CallStackDataSource implements IAsyncDataSource<IDebugModel, CallStackItem> {
|
||||
deemphasizedStackFramesToShow: IStackFrame[];
|
||||
|
||||
hasChildren(element: IDebugModel | CallStackItem): boolean {
|
||||
return isDebugModel(element) || isDebugSession(element) || (element instanceof Thread && element.stopped);
|
||||
}
|
||||
|
||||
getChildren(element: IDebugModel | CallStackItem): Promise<CallStackItem[]> {
|
||||
if (isDebugModel(element)) {
|
||||
const sessions = element.getSessions();
|
||||
if (sessions.length === 0) {
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
if (sessions.length > 1) {
|
||||
return Promise.resolve(sessions);
|
||||
}
|
||||
|
||||
const threads = sessions[0].getAllThreads();
|
||||
// Only show the threads in the call stack if there is more than 1 thread.
|
||||
return threads.length === 1 ? this.getThreadChildren(<Thread>threads[0]) : Promise.resolve(threads);
|
||||
} else if (isDebugSession(element)) {
|
||||
return Promise.resolve(element.getAllThreads());
|
||||
} else {
|
||||
return this.getThreadChildren(<Thread>element);
|
||||
}
|
||||
}
|
||||
|
||||
private getThreadChildren(thread: Thread): Promise<CallStackItem[]> {
|
||||
return this.getThreadCallstack(thread).then(children => {
|
||||
// Check if some stack frames should be hidden under a parent element since they are deemphasized
|
||||
const result: CallStackItem[] = [];
|
||||
children.forEach((child, index) => {
|
||||
if (child instanceof StackFrame && child.source && isDeemphasized(child)) {
|
||||
// Check if the user clicked to show the deemphasized source
|
||||
if (this.deemphasizedStackFramesToShow.indexOf(child) === -1) {
|
||||
if (result.length) {
|
||||
const last = result[result.length - 1];
|
||||
if (last instanceof Array) {
|
||||
// Collect all the stackframes that will be "collapsed"
|
||||
last.push(child);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const nextChild = index < children.length - 1 ? children[index + 1] : undefined;
|
||||
if (nextChild instanceof StackFrame && nextChild.source && isDeemphasized(nextChild)) {
|
||||
// Start collecting stackframes that will be "collapsed"
|
||||
result.push([child]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result.push(child);
|
||||
});
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
private getThreadCallstack(thread: Thread): Promise<Array<IStackFrame | string | ThreadAndSessionIds>> {
|
||||
let callStack: any[] = thread.getCallStack();
|
||||
let callStackPromise: Promise<any> = Promise.resolve(null);
|
||||
if (!callStack || !callStack.length) {
|
||||
callStackPromise = thread.fetchCallStack().then(() => callStack = thread.getCallStack());
|
||||
}
|
||||
|
||||
return callStackPromise.then(() => {
|
||||
if (callStack.length === 1 && thread.session.capabilities.supportsDelayedStackTraceLoading && thread.stoppedDetails && thread.stoppedDetails.totalFrames && thread.stoppedDetails.totalFrames > 1) {
|
||||
// To reduce flashing of the call stack view simply append the stale call stack
|
||||
// once we have the correct data the tree will refresh and we will no longer display it.
|
||||
callStack = callStack.concat(thread.getStaleCallStack().slice(1));
|
||||
}
|
||||
|
||||
if (thread.stoppedDetails && thread.stoppedDetails.framesErrorMessage) {
|
||||
callStack = callStack.concat([thread.stoppedDetails.framesErrorMessage]);
|
||||
}
|
||||
if (thread.stoppedDetails && thread.stoppedDetails.totalFrames && thread.stoppedDetails.totalFrames > callStack.length && callStack.length > 1) {
|
||||
callStack = callStack.concat([new ThreadAndSessionIds(thread.session.getId(), thread.threadId)]);
|
||||
}
|
||||
|
||||
return callStack;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class CallStackAccessibilityProvider implements IAccessibilityProvider<CallStackItem> {
|
||||
getAriaLabel(element: CallStackItem): string {
|
||||
if (element instanceof Thread) {
|
||||
return nls.localize('threadAriaLabel', "Thread {0}, callstack, debug", (<Thread>element).name);
|
||||
}
|
||||
if (element instanceof StackFrame) {
|
||||
return nls.localize('stackFrameAriaLabel', "Stack Frame {0} line {1} {2}, callstack, debug", element.name, element.range.startLineNumber, element.getSpecificSourceName());
|
||||
}
|
||||
if (isDebugSession(element)) {
|
||||
return nls.localize('sessionLabel', "Debug Session {0}", element.getLabel());
|
||||
}
|
||||
if (typeof element === 'string') {
|
||||
return element;
|
||||
}
|
||||
if (element instanceof Array) {
|
||||
return nls.localize('showMoreStackFrames', "Show {0} More Stack Frames", element.length);
|
||||
}
|
||||
|
||||
// element instanceof ThreadAndSessionIds
|
||||
return nls.localize('loadMoreStackFrames', "Load More Stack Frames");
|
||||
}
|
||||
}
|
||||
133
src/vs/workbench/contrib/debug/browser/debugANSIHandling.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector';
|
||||
|
||||
/**
|
||||
* @param text The content to stylize.
|
||||
* @returns An {@link HTMLSpanElement} that contains the potentially stylized text.
|
||||
*/
|
||||
export function handleANSIOutput(text: string, linkDetector: LinkDetector): HTMLSpanElement {
|
||||
|
||||
const root: HTMLSpanElement = document.createElement('span');
|
||||
const textLength: number = text.length;
|
||||
|
||||
let styleNames: string[] = [];
|
||||
let currentPos: number = 0;
|
||||
let buffer: string = '';
|
||||
|
||||
while (currentPos < textLength) {
|
||||
|
||||
let sequenceFound: boolean = false;
|
||||
|
||||
// Potentially an ANSI escape sequence.
|
||||
// See http://ascii-table.com/ansi-escape-sequences.php & https://en.wikipedia.org/wiki/ANSI_escape_code
|
||||
if (text.charCodeAt(currentPos) === 27 && text.charAt(currentPos + 1) === '[') {
|
||||
|
||||
const startPos: number = currentPos;
|
||||
currentPos += 2; // Ignore 'Esc[' as it's in every sequence.
|
||||
|
||||
let ansiSequence: string = '';
|
||||
|
||||
while (currentPos < textLength) {
|
||||
const char: string = text.charAt(currentPos);
|
||||
ansiSequence += char;
|
||||
|
||||
currentPos++;
|
||||
|
||||
// Look for a known sequence terminating character.
|
||||
if (char.match(/^[ABCDHIJKfhmpsu]$/)) {
|
||||
sequenceFound = true;
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (sequenceFound) {
|
||||
|
||||
// Flush buffer with previous styles.
|
||||
appendStylizedStringToContainer(root, buffer, styleNames, linkDetector);
|
||||
|
||||
buffer = '';
|
||||
|
||||
/*
|
||||
* Certain ranges that are matched here do not contain real graphics rendition sequences. For
|
||||
* the sake of having a simpler expression, they have been included anyway.
|
||||
*/
|
||||
if (ansiSequence.match(/^(?:[349][0-7]|10[0-7]|[013]|4|[34]9)(?:;(?:[349][0-7]|10[0-7]|[013]|4|[34]9))*;?m$/)) {
|
||||
|
||||
const styleCodes: number[] = ansiSequence.slice(0, -1) // Remove final 'm' character.
|
||||
.split(';') // Separate style codes.
|
||||
.filter(elem => elem !== '') // Filter empty elems as '34;m' -> ['34', ''].
|
||||
.map(elem => parseInt(elem, 10)); // Convert to numbers.
|
||||
|
||||
for (let code of styleCodes) {
|
||||
if (code === 0) {
|
||||
styleNames = [];
|
||||
} else if (code === 1) {
|
||||
styleNames.push('code-bold');
|
||||
} else if (code === 3) {
|
||||
styleNames.push('code-italic');
|
||||
} else if (code === 4) {
|
||||
styleNames.push('code-underline');
|
||||
} else if (code === 39 || (code >= 30 && code <= 37) || (code >= 90 && code <= 97)) {
|
||||
// Remove all previous foreground colour codes
|
||||
styleNames = styleNames.filter(style => !style.match(/^code-foreground-\d+$/));
|
||||
|
||||
if (code !== 39) {
|
||||
styleNames.push('code-foreground-' + code);
|
||||
}
|
||||
} else if (code === 49 || (code >= 40 && code <= 47) || (code >= 100 && code <= 107)) {
|
||||
// Remove all previous background colour codes
|
||||
styleNames = styleNames.filter(style => !style.match(/^code-background-\d+$/));
|
||||
|
||||
if (code !== 49) {
|
||||
styleNames.push('code-background-' + code);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
// Unsupported sequence so simply hide it.
|
||||
}
|
||||
|
||||
} else {
|
||||
currentPos = startPos;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (sequenceFound === false) {
|
||||
buffer += text.charAt(currentPos);
|
||||
currentPos++;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Flush remaining text buffer if not empty.
|
||||
if (buffer) {
|
||||
appendStylizedStringToContainer(root, buffer, styleNames, linkDetector);
|
||||
}
|
||||
|
||||
return root;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* @param root The {@link HTMLElement} to append the content to.
|
||||
* @param stringContent The text content to be appended.
|
||||
* @param cssClasses The list of CSS styles to apply to the text content.
|
||||
* @param linkDetector The {@link LinkDetector} responsible for generating links from {@param stringContent}.
|
||||
*/
|
||||
export function appendStylizedStringToContainer(root: HTMLElement, stringContent: string, cssClasses: string[], linkDetector: LinkDetector): void {
|
||||
if (!root || !stringContent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const container = linkDetector.handleLinks(stringContent);
|
||||
|
||||
container.className = cssClasses.join(' ');
|
||||
root.appendChild(container);
|
||||
}
|
||||
227
src/vs/workbench/contrib/debug/browser/debugActionItems.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { IAction, IActionRunner } from 'vs/base/common/actions';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
import { SelectBox, ISelectOptionItem } from 'vs/base/browser/ui/selectBox/selectBox';
|
||||
import { SelectActionItem, IActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IDebugService, IDebugSession } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { attachSelectBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler';
|
||||
import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { selectBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
export class StartDebugActionItem implements IActionItem {
|
||||
|
||||
private static readonly SEPARATOR = '─────────';
|
||||
|
||||
public actionRunner: IActionRunner;
|
||||
private container: HTMLElement;
|
||||
private start: HTMLElement;
|
||||
private selectBox: SelectBox;
|
||||
private options: { label: string, handler?: (() => boolean) }[];
|
||||
private toDispose: IDisposable[];
|
||||
private selected: number;
|
||||
|
||||
constructor(
|
||||
private context: any,
|
||||
private action: IAction,
|
||||
@IDebugService private readonly debugService: IDebugService,
|
||||
@IThemeService private readonly themeService: IThemeService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
|
||||
@IContextViewService contextViewService: IContextViewService,
|
||||
) {
|
||||
this.toDispose = [];
|
||||
this.selectBox = new SelectBox([], -1, contextViewService, undefined, { ariaLabel: nls.localize('debugLaunchConfigurations', 'Debug Launch Configurations') });
|
||||
this.toDispose.push(this.selectBox);
|
||||
this.toDispose.push(attachSelectBoxStyler(this.selectBox, themeService, {
|
||||
selectBackground: SIDE_BAR_BACKGROUND
|
||||
}));
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('launch')) {
|
||||
this.updateOptions();
|
||||
}
|
||||
}));
|
||||
this.toDispose.push(this.debugService.getConfigurationManager().onDidSelectConfiguration(() => {
|
||||
this.updateOptions();
|
||||
}));
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): void {
|
||||
this.container = container;
|
||||
dom.addClass(container, 'start-debug-action-item');
|
||||
this.start = dom.append(container, $('.icon'));
|
||||
this.start.title = this.action.label;
|
||||
this.start.setAttribute('role', 'button');
|
||||
this.start.tabIndex = 0;
|
||||
|
||||
this.toDispose.push(dom.addDisposableListener(this.start, dom.EventType.CLICK, () => {
|
||||
this.start.blur();
|
||||
this.actionRunner.run(this.action, this.context);
|
||||
}));
|
||||
|
||||
this.toDispose.push(dom.addDisposableListener(this.start, dom.EventType.MOUSE_DOWN, (e: MouseEvent) => {
|
||||
if (this.action.enabled && e.button === 0) {
|
||||
dom.addClass(this.start, 'active');
|
||||
}
|
||||
}));
|
||||
this.toDispose.push(dom.addDisposableListener(this.start, dom.EventType.MOUSE_UP, () => {
|
||||
dom.removeClass(this.start, 'active');
|
||||
}));
|
||||
this.toDispose.push(dom.addDisposableListener(this.start, dom.EventType.MOUSE_OUT, () => {
|
||||
dom.removeClass(this.start, 'active');
|
||||
}));
|
||||
|
||||
this.toDispose.push(dom.addDisposableListener(this.start, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
if (event.equals(KeyCode.Enter)) {
|
||||
this.actionRunner.run(this.action, this.context);
|
||||
}
|
||||
if (event.equals(KeyCode.RightArrow)) {
|
||||
this.selectBox.focus();
|
||||
event.stopPropagation();
|
||||
}
|
||||
}));
|
||||
this.toDispose.push(this.selectBox.onDidSelect(e => {
|
||||
const target = this.options[e.index];
|
||||
const shouldBeSelected = target.handler ? target.handler() : false;
|
||||
if (shouldBeSelected) {
|
||||
this.selected = e.index;
|
||||
} else {
|
||||
// Some select options should not remain selected https://github.com/Microsoft/vscode/issues/31526
|
||||
this.selectBox.select(this.selected);
|
||||
}
|
||||
}));
|
||||
|
||||
const selectBoxContainer = $('.configuration');
|
||||
this.selectBox.render(dom.append(container, selectBoxContainer));
|
||||
this.toDispose.push(dom.addDisposableListener(selectBoxContainer, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
|
||||
const event = new StandardKeyboardEvent(e);
|
||||
if (event.equals(KeyCode.LeftArrow)) {
|
||||
this.start.focus();
|
||||
event.stopPropagation();
|
||||
}
|
||||
}));
|
||||
this.toDispose.push(attachStylerCallback(this.themeService, { selectBorder }, colors => {
|
||||
this.container.style.border = colors.selectBorder ? `1px solid ${colors.selectBorder}` : null;
|
||||
selectBoxContainer.style.borderLeft = colors.selectBorder ? `1px solid ${colors.selectBorder}` : null;
|
||||
}));
|
||||
|
||||
this.updateOptions();
|
||||
}
|
||||
|
||||
public setActionContext(context: any): void {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
public isEnabled(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public focus(fromRight?: boolean): void {
|
||||
if (fromRight) {
|
||||
this.selectBox.focus();
|
||||
} else {
|
||||
this.start.focus();
|
||||
}
|
||||
}
|
||||
|
||||
public blur(): void {
|
||||
this.container.blur();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
}
|
||||
|
||||
private updateOptions(): void {
|
||||
this.selected = 0;
|
||||
this.options = [];
|
||||
const manager = this.debugService.getConfigurationManager();
|
||||
const launches = manager.getLaunches();
|
||||
const inWorkspace = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE;
|
||||
launches.forEach(launch =>
|
||||
launch.getConfigurationNames().forEach(name => {
|
||||
if (name === manager.selectedConfiguration.name && launch === manager.selectedConfiguration.launch) {
|
||||
this.selected = this.options.length;
|
||||
}
|
||||
const label = inWorkspace ? `${name} (${launch.name})` : name;
|
||||
this.options.push({ label, handler: () => { manager.selectConfiguration(launch, name); return true; } });
|
||||
}));
|
||||
|
||||
if (this.options.length === 0) {
|
||||
this.options.push({ label: nls.localize('noConfigurations', "No Configurations"), handler: () => false });
|
||||
} else {
|
||||
this.options.push({ label: StartDebugActionItem.SEPARATOR, handler: undefined });
|
||||
}
|
||||
|
||||
const disabledIdx = this.options.length - 1;
|
||||
launches.filter(l => !l.hidden).forEach(l => {
|
||||
const label = inWorkspace ? nls.localize("addConfigTo", "Add Config ({0})...", l.name) : nls.localize('addConfiguration', "Add Configuration...");
|
||||
this.options.push({
|
||||
label, handler: () => {
|
||||
this.commandService.executeCommand('debug.addConfiguration', l.uri.toString());
|
||||
return false;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
this.selectBox.setOptions(this.options.map((data, index) => <ISelectOptionItem>{ text: data.label, isDisabled: (index === disabledIdx ? true : undefined) }), this.selected);
|
||||
}
|
||||
}
|
||||
|
||||
export class FocusSessionActionItem extends SelectActionItem {
|
||||
constructor(
|
||||
action: IAction,
|
||||
@IDebugService protected debugService: IDebugService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextViewService contextViewService: IContextViewService,
|
||||
) {
|
||||
super(null, action, [], -1, contextViewService, { ariaLabel: nls.localize('debugSession', 'Debug Session') });
|
||||
|
||||
this.toDispose.push(attachSelectBoxStyler(this.selectBox, themeService));
|
||||
|
||||
this.toDispose.push(this.debugService.getViewModel().onDidFocusSession(() => {
|
||||
const session = this.debugService.getViewModel().focusedSession;
|
||||
if (session) {
|
||||
const index = this.getSessions().indexOf(session);
|
||||
this.select(index);
|
||||
}
|
||||
}));
|
||||
|
||||
this.toDispose.push(this.debugService.onDidNewSession(() => this.update()));
|
||||
this.toDispose.push(this.debugService.onDidEndSession(() => this.update()));
|
||||
|
||||
this.update();
|
||||
}
|
||||
|
||||
private update() {
|
||||
const session = this.debugService.getViewModel().focusedSession;
|
||||
const sessions = this.getSessions();
|
||||
const names = sessions.map(s => s.getLabel());
|
||||
this.setOptions(names.map(data => <ISelectOptionItem>{ text: data }), session ? sessions.indexOf(session) : undefined);
|
||||
}
|
||||
|
||||
protected getSessions(): ReadonlyArray<IDebugSession> {
|
||||
return this.debugService.getModel().getSessions();
|
||||
}
|
||||
}
|
||||
896
src/vs/workbench/contrib/debug/browser/debugActions.ts
Normal file
@@ -0,0 +1,896 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as lifecycle from 'vs/base/common/lifecycle';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { IDebugService, State, IDebugSession, IThread, IEnablement, IBreakpoint, IStackFrame, REPL_ID }
|
||||
from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { Variable, Expression, Thread, Breakpoint } from 'vs/workbench/contrib/debug/common/debugModel';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { TogglePanelAction } from 'vs/workbench/browser/panel';
|
||||
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { CollapseAction } from 'vs/workbench/browser/viewlet';
|
||||
import { first } from 'vs/base/common/arrays';
|
||||
import { IHistoryService } from 'vs/workbench/services/history/common/history';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
|
||||
export abstract class AbstractDebugAction extends Action {
|
||||
|
||||
protected toDispose: lifecycle.IDisposable[];
|
||||
|
||||
constructor(
|
||||
id: string, label: string, cssClass: string,
|
||||
@IDebugService protected debugService: IDebugService,
|
||||
@IKeybindingService protected keybindingService: IKeybindingService,
|
||||
public weight?: number
|
||||
) {
|
||||
super(id, label, cssClass, false);
|
||||
this.toDispose = [];
|
||||
this.toDispose.push(this.debugService.onDidChangeState(state => this.updateEnablement(state)));
|
||||
|
||||
this.updateLabel(label);
|
||||
this.updateEnablement();
|
||||
}
|
||||
|
||||
public run(e?: any): Promise<any> {
|
||||
throw new Error('implement me');
|
||||
}
|
||||
|
||||
public get tooltip(): string {
|
||||
const keybinding = this.keybindingService.lookupKeybinding(this.id);
|
||||
const keybindingLabel = keybinding && keybinding.getLabel();
|
||||
|
||||
return keybindingLabel ? `${this.label} (${keybindingLabel})` : this.label;
|
||||
}
|
||||
|
||||
protected updateLabel(newLabel: string): void {
|
||||
this.label = newLabel;
|
||||
}
|
||||
|
||||
protected updateEnablement(state = this.debugService.state): void {
|
||||
this.enabled = this.isEnabled(state);
|
||||
}
|
||||
|
||||
protected isEnabled(state: State): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
this.toDispose = lifecycle.dispose(this.toDispose);
|
||||
}
|
||||
}
|
||||
|
||||
export class ConfigureAction extends AbstractDebugAction {
|
||||
static readonly ID = 'workbench.action.debug.configure';
|
||||
static LABEL = nls.localize('openLaunchJson', "Open {0}", 'launch.json');
|
||||
|
||||
constructor(id: string, label: string,
|
||||
@IDebugService debugService: IDebugService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService
|
||||
) {
|
||||
super(id, label, 'debug-action configure', debugService, keybindingService);
|
||||
this.toDispose.push(debugService.getConfigurationManager().onDidSelectConfiguration(() => this.updateClass()));
|
||||
this.updateClass();
|
||||
}
|
||||
|
||||
public get tooltip(): string {
|
||||
if (this.debugService.getConfigurationManager().selectedConfiguration.name) {
|
||||
return ConfigureAction.LABEL;
|
||||
}
|
||||
|
||||
return nls.localize('launchJsonNeedsConfigurtion', "Configure or Fix 'launch.json'");
|
||||
}
|
||||
|
||||
private updateClass(): void {
|
||||
const configurationManager = this.debugService.getConfigurationManager();
|
||||
const configurationCount = configurationManager.getLaunches().map(l => l.getConfigurationNames().length).reduce((sum, current) => sum + current);
|
||||
this.class = configurationCount > 0 ? 'debug-action configure' : 'debug-action configure notification';
|
||||
}
|
||||
|
||||
public run(event?: any): Promise<any> {
|
||||
if (this.contextService.getWorkbenchState() === WorkbenchState.EMPTY) {
|
||||
this.notificationService.info(nls.localize('noFolderDebugConfig', "Please first open a folder in order to do advanced debug configuration."));
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const sideBySide = !!(event && (event.ctrlKey || event.metaKey));
|
||||
const configurationManager = this.debugService.getConfigurationManager();
|
||||
if (!configurationManager.selectedConfiguration.launch) {
|
||||
configurationManager.selectConfiguration(configurationManager.getLaunches()[0]);
|
||||
}
|
||||
|
||||
return configurationManager.selectedConfiguration.launch!.openConfigFile(sideBySide, false);
|
||||
}
|
||||
}
|
||||
|
||||
export class StartAction extends AbstractDebugAction {
|
||||
static ID = 'workbench.action.debug.start';
|
||||
static LABEL = nls.localize('startDebug', "Start Debugging");
|
||||
|
||||
constructor(id: string, label: string,
|
||||
@IDebugService debugService: IDebugService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
|
||||
@IHistoryService private readonly historyService: IHistoryService
|
||||
) {
|
||||
super(id, label, 'debug-action start', debugService, keybindingService);
|
||||
|
||||
this.toDispose.push(this.debugService.getConfigurationManager().onDidSelectConfiguration(() => this.updateEnablement()));
|
||||
this.toDispose.push(this.debugService.onDidNewSession(() => this.updateEnablement()));
|
||||
this.toDispose.push(this.debugService.onDidEndSession(() => this.updateEnablement()));
|
||||
this.toDispose.push(this.contextService.onDidChangeWorkbenchState(() => this.updateEnablement()));
|
||||
}
|
||||
|
||||
// Note: When this action is executed from the process explorer, a config is passed. For all
|
||||
// other cases it is run with no arguments.
|
||||
public run(): Promise<any> {
|
||||
const configurationManager = this.debugService.getConfigurationManager();
|
||||
let launch = configurationManager.selectedConfiguration.launch;
|
||||
if (!launch || launch.getConfigurationNames().length === 0) {
|
||||
const rootUri = this.historyService.getLastActiveWorkspaceRoot();
|
||||
launch = configurationManager.getLaunch(rootUri);
|
||||
if (!launch || launch.getConfigurationNames().length === 0) {
|
||||
const launches = configurationManager.getLaunches();
|
||||
launch = first(launches, l => !!(l && l.getConfigurationNames().length), launch);
|
||||
}
|
||||
|
||||
configurationManager.selectConfiguration(launch);
|
||||
}
|
||||
|
||||
return this.debugService.startDebugging(launch, undefined, this.isNoDebug());
|
||||
}
|
||||
|
||||
protected isNoDebug(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
public static isEnabled(debugService: IDebugService) {
|
||||
const sessions = debugService.getModel().getSessions();
|
||||
|
||||
if (debugService.state === State.Initializing) {
|
||||
return false;
|
||||
}
|
||||
if ((sessions.length > 0) && debugService.getConfigurationManager().getLaunches().every(l => l.getConfigurationNames().length === 0)) {
|
||||
// There is already a debug session running and we do not have any launch configuration selected
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Disabled if the launch drop down shows the launch config that is already running.
|
||||
protected isEnabled(): boolean {
|
||||
return StartAction.isEnabled(this.debugService);
|
||||
}
|
||||
}
|
||||
|
||||
export class RunAction extends StartAction {
|
||||
static readonly ID = 'workbench.action.debug.run';
|
||||
static LABEL = nls.localize('startWithoutDebugging', "Start Without Debugging");
|
||||
|
||||
protected isNoDebug(): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class SelectAndStartAction extends AbstractDebugAction {
|
||||
static readonly ID = 'workbench.action.debug.selectandstart';
|
||||
static LABEL = nls.localize('selectAndStartDebugging', "Select and Start Debugging");
|
||||
|
||||
constructor(id: string, label: string,
|
||||
@IDebugService debugService: IDebugService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IQuickOpenService private readonly quickOpenService: IQuickOpenService
|
||||
) {
|
||||
super(id, label, '', debugService, keybindingService);
|
||||
}
|
||||
|
||||
public run(): Promise<any> {
|
||||
return this.quickOpenService.show('debug ');
|
||||
}
|
||||
}
|
||||
|
||||
export class RestartAction extends AbstractDebugAction {
|
||||
static readonly ID = 'workbench.action.debug.restart';
|
||||
static LABEL = nls.localize('restartDebug', "Restart");
|
||||
static RECONNECT_LABEL = nls.localize('reconnectDebug', "Reconnect");
|
||||
|
||||
constructor(id: string, label: string,
|
||||
@IDebugService debugService: IDebugService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
|
||||
@IHistoryService private readonly historyService: IHistoryService
|
||||
) {
|
||||
super(id, label, 'debug-action restart', debugService, keybindingService, 70);
|
||||
this.setLabel(this.debugService.getViewModel().focusedSession);
|
||||
this.toDispose.push(this.debugService.getViewModel().onDidFocusSession(() => this.setLabel(this.debugService.getViewModel().focusedSession)));
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get startAction(): StartAction {
|
||||
return new StartAction(StartAction.ID, StartAction.LABEL, this.debugService, this.keybindingService, this.contextService, this.historyService);
|
||||
}
|
||||
|
||||
private setLabel(session: IDebugSession | undefined): void {
|
||||
if (session) {
|
||||
this.updateLabel(session && session.configuration.request === 'attach' ? RestartAction.RECONNECT_LABEL : RestartAction.LABEL);
|
||||
}
|
||||
}
|
||||
|
||||
public run(session: IDebugSession | undefined): Promise<any> {
|
||||
if (!session || !session.getId) {
|
||||
session = this.debugService.getViewModel().focusedSession;
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
return this.startAction.run();
|
||||
}
|
||||
|
||||
session.removeReplExpressions();
|
||||
return this.debugService.restartSession(session);
|
||||
}
|
||||
|
||||
protected isEnabled(state: State): boolean {
|
||||
return super.isEnabled(state) && (
|
||||
state === State.Running ||
|
||||
state === State.Stopped ||
|
||||
StartAction.isEnabled(this.debugService)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export class StepOverAction extends AbstractDebugAction {
|
||||
static readonly ID = 'workbench.action.debug.stepOver';
|
||||
static LABEL = nls.localize('stepOverDebug', "Step Over");
|
||||
|
||||
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
|
||||
super(id, label, 'debug-action step-over', debugService, keybindingService, 20);
|
||||
}
|
||||
|
||||
public run(thread: IThread | undefined): Promise<any> {
|
||||
if (!(thread instanceof Thread)) {
|
||||
thread = this.debugService.getViewModel().focusedThread;
|
||||
}
|
||||
|
||||
return thread ? thread.next() : Promise.resolve();
|
||||
}
|
||||
|
||||
protected isEnabled(state: State): boolean {
|
||||
return super.isEnabled(state) && state === State.Stopped;
|
||||
}
|
||||
}
|
||||
|
||||
export class StepIntoAction extends AbstractDebugAction {
|
||||
static readonly ID = 'workbench.action.debug.stepInto';
|
||||
static LABEL = nls.localize('stepIntoDebug', "Step Into");
|
||||
|
||||
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
|
||||
super(id, label, 'debug-action step-into', debugService, keybindingService, 30);
|
||||
}
|
||||
|
||||
public run(thread: IThread | undefined): Promise<any> {
|
||||
if (!(thread instanceof Thread)) {
|
||||
thread = this.debugService.getViewModel().focusedThread;
|
||||
}
|
||||
|
||||
return thread ? thread.stepIn() : Promise.resolve();
|
||||
}
|
||||
|
||||
protected isEnabled(state: State): boolean {
|
||||
return super.isEnabled(state) && state === State.Stopped;
|
||||
}
|
||||
}
|
||||
|
||||
export class StepOutAction extends AbstractDebugAction {
|
||||
static readonly ID = 'workbench.action.debug.stepOut';
|
||||
static LABEL = nls.localize('stepOutDebug', "Step Out");
|
||||
|
||||
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
|
||||
super(id, label, 'debug-action step-out', debugService, keybindingService, 40);
|
||||
}
|
||||
|
||||
public run(thread: IThread | undefined): Promise<any> {
|
||||
if (!(thread instanceof Thread)) {
|
||||
thread = this.debugService.getViewModel().focusedThread;
|
||||
}
|
||||
|
||||
return thread ? thread.stepOut() : Promise.resolve();
|
||||
}
|
||||
|
||||
protected isEnabled(state: State): boolean {
|
||||
return super.isEnabled(state) && state === State.Stopped;
|
||||
}
|
||||
}
|
||||
|
||||
export class StopAction extends AbstractDebugAction {
|
||||
static readonly ID = 'workbench.action.debug.stop';
|
||||
static LABEL = nls.localize('stopDebug', "Stop");
|
||||
|
||||
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
|
||||
super(id, label, 'debug-action stop', debugService, keybindingService, 80);
|
||||
}
|
||||
|
||||
public run(session: IDebugSession | undefined): Promise<any> {
|
||||
if (!session || !session.getId) {
|
||||
session = this.debugService.getViewModel().focusedSession;
|
||||
}
|
||||
|
||||
return this.debugService.stopSession(session);
|
||||
}
|
||||
|
||||
protected isEnabled(state: State): boolean {
|
||||
return super.isEnabled(state) && (state !== State.Inactive);
|
||||
}
|
||||
}
|
||||
|
||||
export class DisconnectAction extends AbstractDebugAction {
|
||||
static readonly ID = 'workbench.action.debug.disconnect';
|
||||
static LABEL = nls.localize('disconnectDebug', "Disconnect");
|
||||
|
||||
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
|
||||
super(id, label, 'debug-action disconnect', debugService, keybindingService, 80);
|
||||
}
|
||||
|
||||
public run(): Promise<any> {
|
||||
const session = this.debugService.getViewModel().focusedSession;
|
||||
return this.debugService.stopSession(session);
|
||||
}
|
||||
|
||||
protected isEnabled(state: State): boolean {
|
||||
return super.isEnabled(state) && (state === State.Running || state === State.Stopped);
|
||||
}
|
||||
}
|
||||
|
||||
export class ContinueAction extends AbstractDebugAction {
|
||||
static readonly ID = 'workbench.action.debug.continue';
|
||||
static LABEL = nls.localize('continueDebug', "Continue");
|
||||
|
||||
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
|
||||
super(id, label, 'debug-action continue', debugService, keybindingService, 10);
|
||||
}
|
||||
|
||||
public run(thread: IThread | undefined): Promise<any> {
|
||||
if (!(thread instanceof Thread)) {
|
||||
thread = this.debugService.getViewModel().focusedThread;
|
||||
}
|
||||
|
||||
return thread ? thread.continue() : Promise.resolve();
|
||||
}
|
||||
|
||||
protected isEnabled(state: State): boolean {
|
||||
return super.isEnabled(state) && state === State.Stopped;
|
||||
}
|
||||
}
|
||||
|
||||
export class PauseAction extends AbstractDebugAction {
|
||||
static readonly ID = 'workbench.action.debug.pause';
|
||||
static LABEL = nls.localize('pauseDebug', "Pause");
|
||||
|
||||
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
|
||||
super(id, label, 'debug-action pause', debugService, keybindingService, 10);
|
||||
}
|
||||
|
||||
public run(thread: IThread | undefined): Promise<any> {
|
||||
if (!(thread instanceof Thread)) {
|
||||
thread = this.debugService.getViewModel().focusedThread;
|
||||
if (!thread) {
|
||||
const session = this.debugService.getViewModel().focusedSession;
|
||||
const threads = session && session.getAllThreads();
|
||||
thread = threads && threads.length ? threads[0] : undefined;
|
||||
}
|
||||
}
|
||||
|
||||
return thread ? thread.pause() : Promise.resolve();
|
||||
}
|
||||
|
||||
protected isEnabled(state: State): boolean {
|
||||
return super.isEnabled(state) && state === State.Running;
|
||||
}
|
||||
}
|
||||
|
||||
export class TerminateThreadAction extends AbstractDebugAction {
|
||||
static readonly ID = 'workbench.action.debug.terminateThread';
|
||||
static LABEL = nls.localize('terminateThread', "Terminate Thread");
|
||||
|
||||
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
|
||||
super(id, label, '', debugService, keybindingService);
|
||||
}
|
||||
|
||||
public run(thread: IThread | undefined): Promise<any> {
|
||||
if (!(thread instanceof Thread)) {
|
||||
thread = this.debugService.getViewModel().focusedThread;
|
||||
}
|
||||
|
||||
return thread ? thread.terminate() : Promise.resolve();
|
||||
}
|
||||
|
||||
protected isEnabled(state: State): boolean {
|
||||
return super.isEnabled(state) && (state === State.Running || state === State.Stopped);
|
||||
}
|
||||
}
|
||||
|
||||
export class RestartFrameAction extends AbstractDebugAction {
|
||||
static readonly ID = 'workbench.action.debug.restartFrame';
|
||||
static LABEL = nls.localize('restartFrame', "Restart Frame");
|
||||
|
||||
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
|
||||
super(id, label, '', debugService, keybindingService);
|
||||
}
|
||||
|
||||
public run(frame: IStackFrame | undefined): Promise<any> {
|
||||
if (!frame) {
|
||||
frame = this.debugService.getViewModel().focusedStackFrame;
|
||||
}
|
||||
|
||||
return frame!.restart();
|
||||
}
|
||||
}
|
||||
|
||||
export class RemoveBreakpointAction extends AbstractDebugAction {
|
||||
static readonly ID = 'workbench.debug.viewlet.action.removeBreakpoint';
|
||||
static LABEL = nls.localize('removeBreakpoint', "Remove Breakpoint");
|
||||
|
||||
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
|
||||
super(id, label, 'debug-action remove', debugService, keybindingService);
|
||||
}
|
||||
|
||||
public run(breakpoint: IBreakpoint): Promise<any> {
|
||||
return breakpoint instanceof Breakpoint ? this.debugService.removeBreakpoints(breakpoint.getId())
|
||||
: this.debugService.removeFunctionBreakpoints(breakpoint.getId());
|
||||
}
|
||||
}
|
||||
|
||||
export class RemoveAllBreakpointsAction extends AbstractDebugAction {
|
||||
static readonly ID = 'workbench.debug.viewlet.action.removeAllBreakpoints';
|
||||
static LABEL = nls.localize('removeAllBreakpoints', "Remove All Breakpoints");
|
||||
|
||||
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
|
||||
super(id, label, 'debug-action remove-all', debugService, keybindingService);
|
||||
this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement()));
|
||||
}
|
||||
|
||||
public run(): Promise<any> {
|
||||
return Promise.all([this.debugService.removeBreakpoints(), this.debugService.removeFunctionBreakpoints()]);
|
||||
}
|
||||
|
||||
protected isEnabled(state: State): boolean {
|
||||
const model = this.debugService.getModel();
|
||||
return super.isEnabled(state) && (model.getBreakpoints().length > 0 || model.getFunctionBreakpoints().length > 0);
|
||||
}
|
||||
}
|
||||
|
||||
export class EnableAllBreakpointsAction extends AbstractDebugAction {
|
||||
static readonly ID = 'workbench.debug.viewlet.action.enableAllBreakpoints';
|
||||
static LABEL = nls.localize('enableAllBreakpoints', "Enable All Breakpoints");
|
||||
|
||||
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
|
||||
super(id, label, 'debug-action enable-all-breakpoints', debugService, keybindingService);
|
||||
this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement()));
|
||||
}
|
||||
|
||||
public run(): Promise<any> {
|
||||
return this.debugService.enableOrDisableBreakpoints(true);
|
||||
}
|
||||
|
||||
protected isEnabled(state: State): boolean {
|
||||
const model = this.debugService.getModel();
|
||||
return super.isEnabled(state) && (<ReadonlyArray<IEnablement>>model.getBreakpoints()).concat(model.getFunctionBreakpoints()).concat(model.getExceptionBreakpoints()).some(bp => !bp.enabled);
|
||||
}
|
||||
}
|
||||
|
||||
export class DisableAllBreakpointsAction extends AbstractDebugAction {
|
||||
static readonly ID = 'workbench.debug.viewlet.action.disableAllBreakpoints';
|
||||
static LABEL = nls.localize('disableAllBreakpoints', "Disable All Breakpoints");
|
||||
|
||||
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
|
||||
super(id, label, 'debug-action disable-all-breakpoints', debugService, keybindingService);
|
||||
this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement()));
|
||||
}
|
||||
|
||||
public run(): Promise<any> {
|
||||
return this.debugService.enableOrDisableBreakpoints(false);
|
||||
}
|
||||
|
||||
protected isEnabled(state: State): boolean {
|
||||
const model = this.debugService.getModel();
|
||||
return super.isEnabled(state) && (<ReadonlyArray<IEnablement>>model.getBreakpoints()).concat(model.getFunctionBreakpoints()).concat(model.getExceptionBreakpoints()).some(bp => bp.enabled);
|
||||
}
|
||||
}
|
||||
|
||||
export class ToggleBreakpointsActivatedAction extends AbstractDebugAction {
|
||||
static readonly ID = 'workbench.debug.viewlet.action.toggleBreakpointsActivatedAction';
|
||||
static ACTIVATE_LABEL = nls.localize('activateBreakpoints', "Activate Breakpoints");
|
||||
static DEACTIVATE_LABEL = nls.localize('deactivateBreakpoints', "Deactivate Breakpoints");
|
||||
|
||||
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
|
||||
super(id, label, 'debug-action breakpoints-activate', debugService, keybindingService);
|
||||
this.updateLabel(this.debugService.getModel().areBreakpointsActivated() ? ToggleBreakpointsActivatedAction.DEACTIVATE_LABEL : ToggleBreakpointsActivatedAction.ACTIVATE_LABEL);
|
||||
|
||||
this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => {
|
||||
this.updateLabel(this.debugService.getModel().areBreakpointsActivated() ? ToggleBreakpointsActivatedAction.DEACTIVATE_LABEL : ToggleBreakpointsActivatedAction.ACTIVATE_LABEL);
|
||||
this.updateEnablement();
|
||||
}));
|
||||
}
|
||||
|
||||
public run(): Promise<any> {
|
||||
return this.debugService.setBreakpointsActivated(!this.debugService.getModel().areBreakpointsActivated());
|
||||
}
|
||||
|
||||
protected isEnabled(state: State): boolean {
|
||||
return (this.debugService.getModel().getFunctionBreakpoints().length + this.debugService.getModel().getBreakpoints().length) > 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class ReapplyBreakpointsAction extends AbstractDebugAction {
|
||||
static readonly ID = 'workbench.debug.viewlet.action.reapplyBreakpointsAction';
|
||||
static LABEL = nls.localize('reapplyAllBreakpoints', "Reapply All Breakpoints");
|
||||
|
||||
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
|
||||
super(id, label, '', debugService, keybindingService);
|
||||
this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement()));
|
||||
}
|
||||
|
||||
public run(): Promise<any> {
|
||||
return this.debugService.setBreakpointsActivated(true);
|
||||
}
|
||||
|
||||
protected isEnabled(state: State): boolean {
|
||||
const model = this.debugService.getModel();
|
||||
return super.isEnabled(state) && (state === State.Running || state === State.Stopped) &&
|
||||
(model.getFunctionBreakpoints().length + model.getBreakpoints().length + model.getExceptionBreakpoints().length > 0);
|
||||
}
|
||||
}
|
||||
|
||||
export class AddFunctionBreakpointAction extends AbstractDebugAction {
|
||||
static readonly ID = 'workbench.debug.viewlet.action.addFunctionBreakpointAction';
|
||||
static LABEL = nls.localize('addFunctionBreakpoint', "Add Function Breakpoint");
|
||||
|
||||
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
|
||||
super(id, label, 'debug-action add-function-breakpoint', debugService, keybindingService);
|
||||
this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.updateEnablement()));
|
||||
}
|
||||
|
||||
public run(): Promise<any> {
|
||||
this.debugService.addFunctionBreakpoint();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
protected isEnabled(state: State): boolean {
|
||||
return !this.debugService.getViewModel().getSelectedFunctionBreakpoint()
|
||||
&& this.debugService.getModel().getFunctionBreakpoints().every(fbp => !!fbp.name);
|
||||
}
|
||||
}
|
||||
|
||||
export class SetValueAction extends AbstractDebugAction {
|
||||
static readonly ID = 'workbench.debug.viewlet.action.setValue';
|
||||
static LABEL = nls.localize('setValue', "Set Value");
|
||||
|
||||
constructor(id: string, label: string, private variable: Variable, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
|
||||
super(id, label, '', debugService, keybindingService);
|
||||
}
|
||||
|
||||
public run(): Promise<any> {
|
||||
if (this.variable instanceof Variable) {
|
||||
this.debugService.getViewModel().setSelectedExpression(this.variable);
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
protected isEnabled(state: State): boolean {
|
||||
const session = this.debugService.getViewModel().focusedSession;
|
||||
return !!(super.isEnabled(state) && state === State.Stopped && session && session.capabilities.supportsSetVariable);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class AddWatchExpressionAction extends AbstractDebugAction {
|
||||
static readonly ID = 'workbench.debug.viewlet.action.addWatchExpression';
|
||||
static LABEL = nls.localize('addWatchExpression', "Add Expression");
|
||||
|
||||
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
|
||||
super(id, label, 'debug-action add-watch-expression', debugService, keybindingService);
|
||||
this.toDispose.push(this.debugService.getModel().onDidChangeWatchExpressions(() => this.updateEnablement()));
|
||||
}
|
||||
|
||||
public run(): Promise<any> {
|
||||
this.debugService.addWatchExpression();
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
protected isEnabled(state: State): boolean {
|
||||
return super.isEnabled(state) && this.debugService.getModel().getWatchExpressions().every(we => !!we.name);
|
||||
}
|
||||
}
|
||||
|
||||
export class EditWatchExpressionAction extends AbstractDebugAction {
|
||||
static readonly ID = 'workbench.debug.viewlet.action.editWatchExpression';
|
||||
static LABEL = nls.localize('editWatchExpression', "Edit Expression");
|
||||
|
||||
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
|
||||
super(id, label, '', debugService, keybindingService);
|
||||
}
|
||||
|
||||
public run(expression: Expression): Promise<any> {
|
||||
this.debugService.getViewModel().setSelectedExpression(expression);
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
export class AddToWatchExpressionsAction extends AbstractDebugAction {
|
||||
static readonly ID = 'workbench.debug.viewlet.action.addToWatchExpressions';
|
||||
static LABEL = nls.localize('addToWatchExpressions', "Add to Watch");
|
||||
|
||||
constructor(id: string, label: string, private variable: Variable, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
|
||||
super(id, label, 'debug-action add-to-watch', debugService, keybindingService);
|
||||
this.updateEnablement();
|
||||
}
|
||||
|
||||
public run(): Promise<any> {
|
||||
this.debugService.addWatchExpression(this.variable.evaluateName);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
protected isEnabled(state: State): boolean {
|
||||
return super.isEnabled(state) && this.variable && !!this.variable.evaluateName;
|
||||
}
|
||||
}
|
||||
|
||||
export class RemoveWatchExpressionAction extends AbstractDebugAction {
|
||||
static readonly ID = 'workbench.debug.viewlet.action.removeWatchExpression';
|
||||
static LABEL = nls.localize('removeWatchExpression', "Remove Expression");
|
||||
|
||||
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
|
||||
super(id, label, '', debugService, keybindingService);
|
||||
}
|
||||
|
||||
public run(expression: Expression): Promise<any> {
|
||||
this.debugService.removeWatchExpressions(expression.getId());
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
export class RemoveAllWatchExpressionsAction extends AbstractDebugAction {
|
||||
static readonly ID = 'workbench.debug.viewlet.action.removeAllWatchExpressions';
|
||||
static LABEL = nls.localize('removeAllWatchExpressions', "Remove All Expressions");
|
||||
|
||||
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
|
||||
super(id, label, 'debug-action remove-all', debugService, keybindingService);
|
||||
this.toDispose.push(this.debugService.getModel().onDidChangeWatchExpressions(() => this.updateEnablement()));
|
||||
}
|
||||
|
||||
public run(): Promise<any> {
|
||||
this.debugService.removeWatchExpressions();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
protected isEnabled(state: State): boolean {
|
||||
return super.isEnabled(state) && this.debugService.getModel().getWatchExpressions().length > 0;
|
||||
}
|
||||
}
|
||||
|
||||
export class ToggleReplAction extends TogglePanelAction {
|
||||
static readonly ID = 'workbench.debug.action.toggleRepl';
|
||||
static LABEL = nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugConsoleAction' }, 'Debug Console');
|
||||
private toDispose: lifecycle.IDisposable[];
|
||||
|
||||
constructor(id: string, label: string,
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
||||
@IPanelService panelService: IPanelService
|
||||
) {
|
||||
super(id, label, REPL_ID, panelService, layoutService, 'debug-action toggle-repl');
|
||||
this.toDispose = [];
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.toDispose.push(this.panelService.onDidPanelOpen(({ panel }) => {
|
||||
if (panel.getId() === REPL_ID) {
|
||||
this.class = 'debug-action toggle-repl';
|
||||
this.tooltip = ToggleReplAction.LABEL;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
this.toDispose = lifecycle.dispose(this.toDispose);
|
||||
}
|
||||
}
|
||||
|
||||
export class FocusReplAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.debug.action.focusRepl';
|
||||
static LABEL = nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'debugFocusConsole' }, 'Focus on Debug Console View');
|
||||
|
||||
|
||||
constructor(id: string, label: string,
|
||||
@IPanelService private readonly panelService: IPanelService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(): Promise<any> {
|
||||
this.panelService.openPanel(REPL_ID, true);
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
export class FocusSessionAction extends AbstractDebugAction {
|
||||
static readonly ID = 'workbench.action.debug.focusProcess';
|
||||
static LABEL = nls.localize('focusSession', "Focus Session");
|
||||
|
||||
constructor(id: string, label: string,
|
||||
@IDebugService debugService: IDebugService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IEditorService private readonly editorService: IEditorService
|
||||
) {
|
||||
super(id, label, '', debugService, keybindingService, 100);
|
||||
}
|
||||
|
||||
public run(sessionName: string): Promise<any> {
|
||||
const session = this.debugService.getModel().getSessions().filter(p => p.getLabel() === sessionName).pop();
|
||||
this.debugService.focusStackFrame(undefined, undefined, session, true);
|
||||
const stackFrame = this.debugService.getViewModel().focusedStackFrame;
|
||||
if (stackFrame) {
|
||||
return stackFrame.openInEditor(this.editorService, true);
|
||||
}
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
// Actions used by the chakra debugger
|
||||
export class StepBackAction extends AbstractDebugAction {
|
||||
static readonly ID = 'workbench.action.debug.stepBack';
|
||||
static LABEL = nls.localize('stepBackDebug', "Step Back");
|
||||
|
||||
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
|
||||
super(id, label, 'debug-action step-back', debugService, keybindingService, 50);
|
||||
}
|
||||
|
||||
public run(thread: IThread | undefined): Promise<any> {
|
||||
if (!(thread instanceof Thread)) {
|
||||
thread = this.debugService.getViewModel().focusedThread;
|
||||
}
|
||||
|
||||
return thread ? thread.stepBack() : Promise.resolve();
|
||||
}
|
||||
|
||||
protected isEnabled(state: State): boolean {
|
||||
const session = this.debugService.getViewModel().focusedSession;
|
||||
return !!(super.isEnabled(state) && state === State.Stopped &&
|
||||
session && session.capabilities.supportsStepBack);
|
||||
}
|
||||
}
|
||||
|
||||
export class ReverseContinueAction extends AbstractDebugAction {
|
||||
static readonly ID = 'workbench.action.debug.reverseContinue';
|
||||
static LABEL = nls.localize('reverseContinue', "Reverse");
|
||||
|
||||
constructor(id: string, label: string, @IDebugService debugService: IDebugService, @IKeybindingService keybindingService: IKeybindingService) {
|
||||
super(id, label, 'debug-action reverse-continue', debugService, keybindingService, 60);
|
||||
}
|
||||
|
||||
public run(thread: IThread | undefined): Promise<any> {
|
||||
if (!(thread instanceof Thread)) {
|
||||
thread = this.debugService.getViewModel().focusedThread;
|
||||
}
|
||||
|
||||
return thread ? thread.reverseContinue() : Promise.resolve();
|
||||
}
|
||||
|
||||
protected isEnabled(state: State): boolean {
|
||||
const session = this.debugService.getViewModel().focusedSession;
|
||||
return !!(super.isEnabled(state) && state === State.Stopped &&
|
||||
session && session.capabilities.supportsStepBack);
|
||||
}
|
||||
}
|
||||
|
||||
export class ReplCollapseAllAction extends CollapseAction {
|
||||
constructor(tree: AsyncDataTree<any, any, any>, private toFocus: { focus(): void; }) {
|
||||
super(tree, true, undefined);
|
||||
}
|
||||
|
||||
public run(event?: any): Promise<any> {
|
||||
return super.run(event).then(() => {
|
||||
this.toFocus.focus();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class CopyValueAction extends Action {
|
||||
static readonly ID = 'workbench.debug.viewlet.action.copyValue';
|
||||
static LABEL = nls.localize('copyValue', "Copy Value");
|
||||
|
||||
constructor(
|
||||
id: string, label: string, private value: any, private context: string,
|
||||
@IDebugService private readonly debugService: IDebugService,
|
||||
@IClipboardService private readonly clipboardService: IClipboardService
|
||||
) {
|
||||
super(id, label, 'debug-action copy-value');
|
||||
this._enabled = typeof this.value === 'string' || (this.value instanceof Variable && !!this.value.evaluateName);
|
||||
}
|
||||
|
||||
public run(): Promise<any> {
|
||||
const stackFrame = this.debugService.getViewModel().focusedStackFrame;
|
||||
const session = this.debugService.getViewModel().focusedSession;
|
||||
|
||||
if (this.value instanceof Variable && stackFrame && session && this.value.evaluateName) {
|
||||
return session.evaluate(this.value.evaluateName, stackFrame.frameId, this.context).then(result => {
|
||||
this.clipboardService.writeText(result.body.result);
|
||||
}, err => this.clipboardService.writeText(this.value.value));
|
||||
}
|
||||
|
||||
this.clipboardService.writeText(this.value);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
export class CopyEvaluatePathAction extends Action {
|
||||
static readonly ID = 'workbench.debug.viewlet.action.copyEvaluatePath';
|
||||
static LABEL = nls.localize('copyAsExpression', "Copy as Expression");
|
||||
|
||||
constructor(
|
||||
id: string, label: string, private value: Variable,
|
||||
@IClipboardService private readonly clipboardService: IClipboardService
|
||||
) {
|
||||
super(id, label);
|
||||
this._enabled = this.value && !!this.value.evaluateName;
|
||||
}
|
||||
|
||||
public run(): Promise<any> {
|
||||
this.clipboardService.writeText(this.value.evaluateName!);
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
export class CopyAction extends Action {
|
||||
static readonly ID = 'workbench.debug.action.copy';
|
||||
static LABEL = nls.localize('copy', "Copy");
|
||||
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@IClipboardService private readonly clipboardService: IClipboardService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(): Promise<any> {
|
||||
this.clipboardService.writeText(window.getSelection().toString());
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
export class CopyStackTraceAction extends Action {
|
||||
static readonly ID = 'workbench.action.debug.copyStackTrace';
|
||||
static LABEL = nls.localize('copyStackTrace', "Copy Call Stack");
|
||||
|
||||
constructor(
|
||||
id: string, label: string,
|
||||
@IClipboardService private readonly clipboardService: IClipboardService,
|
||||
@ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
public run(frame: IStackFrame): Promise<any> {
|
||||
const eol = this.textResourcePropertiesService.getEOL(frame.source.uri);
|
||||
this.clipboardService.writeText(frame.thread.getCallStack().map(sf => sf.toString()).join(eol));
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
277
src/vs/workbench/contrib/debug/browser/debugCommands.ts
Normal file
@@ -0,0 +1,277 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { List } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { IListService } from 'vs/platform/list/browser/listService';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { IDebugService, IEnablement, CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_VARIABLES_FOCUSED, EDITOR_CONTRIBUTION_ID, IDebugEditorContribution, CONTEXT_IN_DEBUG_MODE, CONTEXT_EXPRESSION_SELECTED, CONTEXT_BREAKPOINT_SELECTED, IConfig } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { Expression, Variable, Breakpoint, FunctionBreakpoint } from 'vs/workbench/contrib/debug/common/debugModel';
|
||||
import { IExtensionsViewlet, VIEWLET_ID as EXTENSIONS_VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { openBreakpointSource } from 'vs/workbench/contrib/debug/browser/breakpointsView';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { InputFocusedContext } from 'vs/platform/contextkey/common/contextkeys';
|
||||
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
|
||||
import { PanelFocusContext } from 'vs/workbench/common/panel';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
|
||||
export const ADD_CONFIGURATION_ID = 'debug.addConfiguration';
|
||||
export const TOGGLE_INLINE_BREAKPOINT_ID = 'editor.debug.action.toggleInlineBreakpoint';
|
||||
|
||||
export function registerCommands(): void {
|
||||
|
||||
CommandsRegistry.registerCommand({
|
||||
id: 'debug.startFromConfig',
|
||||
handler: (accessor, config: IConfig) => {
|
||||
const debugService = accessor.get(IDebugService);
|
||||
debugService.startDebugging(undefined, config).then(undefined, onUnexpectedError);
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'debug.toggleBreakpoint',
|
||||
weight: KeybindingWeight.WorkbenchContrib + 5,
|
||||
when: ContextKeyExpr.and(CONTEXT_BREAKPOINTS_FOCUSED, InputFocusedContext.toNegated()),
|
||||
primary: KeyCode.Space,
|
||||
handler: (accessor) => {
|
||||
const listService = accessor.get(IListService);
|
||||
const debugService = accessor.get(IDebugService);
|
||||
const list = listService.lastFocusedList;
|
||||
if (list instanceof List) {
|
||||
const focused = <IEnablement[]>list.getFocusedElements();
|
||||
if (focused && focused.length) {
|
||||
debugService.enableOrDisableBreakpoints(!focused[0].enabled, focused[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'debug.enableOrDisableBreakpoint',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: undefined,
|
||||
when: EditorContextKeys.editorTextFocus,
|
||||
handler: (accessor) => {
|
||||
const debugService = accessor.get(IDebugService);
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const widget = editorService.activeTextEditorWidget;
|
||||
if (isCodeEditor(widget)) {
|
||||
const model = widget.getModel();
|
||||
if (model) {
|
||||
const position = widget.getPosition();
|
||||
if (position) {
|
||||
const bps = debugService.getModel().getBreakpoints({ uri: model.uri, lineNumber: position.lineNumber });
|
||||
if (bps.length) {
|
||||
debugService.enableOrDisableBreakpoints(!bps[0].enabled, bps[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'debug.renameWatchExpression',
|
||||
weight: KeybindingWeight.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.lastFocusedList;
|
||||
|
||||
if (focused) {
|
||||
const elements = focused.getFocus();
|
||||
if (Array.isArray(elements) && elements[0] instanceof Expression) {
|
||||
debugService.getViewModel().setSelectedExpression(elements[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'debug.setVariable',
|
||||
weight: KeybindingWeight.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.lastFocusedList;
|
||||
|
||||
if (focused) {
|
||||
const elements = focused.getFocus();
|
||||
if (Array.isArray(elements) && elements[0] instanceof Variable) {
|
||||
debugService.getViewModel().setSelectedExpression(elements[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'debug.removeWatchExpression',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.and(CONTEXT_WATCH_EXPRESSIONS_FOCUSED, CONTEXT_EXPRESSION_SELECTED.toNegated()),
|
||||
primary: KeyCode.Delete,
|
||||
mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace },
|
||||
handler: (accessor) => {
|
||||
const listService = accessor.get(IListService);
|
||||
const debugService = accessor.get(IDebugService);
|
||||
const focused = listService.lastFocusedList;
|
||||
|
||||
if (focused) {
|
||||
const elements = focused.getFocus();
|
||||
if (Array.isArray(elements) && elements[0] instanceof Expression) {
|
||||
debugService.removeWatchExpressions(elements[0].getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'debug.removeBreakpoint',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: ContextKeyExpr.and(CONTEXT_BREAKPOINTS_FOCUSED, CONTEXT_BREAKPOINT_SELECTED.toNegated()),
|
||||
primary: KeyCode.Delete,
|
||||
mac: { primary: KeyMod.CtrlCmd | KeyCode.Backspace },
|
||||
handler: (accessor) => {
|
||||
const listService = accessor.get(IListService);
|
||||
const debugService = accessor.get(IDebugService);
|
||||
const list = listService.lastFocusedList;
|
||||
|
||||
if (list instanceof List) {
|
||||
const focused = list.getFocusedElements();
|
||||
const element = focused.length ? focused[0] : undefined;
|
||||
if (element instanceof Breakpoint) {
|
||||
debugService.removeBreakpoints(element.getId());
|
||||
} else if (element instanceof FunctionBreakpoint) {
|
||||
debugService.removeFunctionBreakpoints(element.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'debug.installAdditionalDebuggers',
|
||||
weight: KeybindingWeight.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: ADD_CONFIGURATION_ID,
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: undefined,
|
||||
primary: undefined,
|
||||
handler: (accessor, launchUri: string) => {
|
||||
const manager = accessor.get(IDebugService).getConfigurationManager();
|
||||
if (accessor.get(IWorkspaceContextService).getWorkbenchState() === WorkbenchState.EMPTY) {
|
||||
accessor.get(INotificationService).info(nls.localize('noFolderDebugConfig', "Please first open a folder in order to do advanced debug configuration."));
|
||||
return undefined;
|
||||
}
|
||||
const launch = manager.getLaunches().filter(l => l.uri.toString() === launchUri).pop() || manager.selectedConfiguration.launch;
|
||||
|
||||
return launch!.openConfigFile(false, false).then(({ editor, created }) => {
|
||||
if (editor && !created) {
|
||||
const codeEditor = <ICodeEditor>editor.getControl();
|
||||
if (codeEditor) {
|
||||
return codeEditor.getContribution<IDebugEditorContribution>(EDITOR_CONTRIBUTION_ID).addLaunchConfiguration();
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const inlineBreakpointHandler = (accessor: ServicesAccessor) => {
|
||||
const debugService = accessor.get(IDebugService);
|
||||
const editorService = accessor.get(IEditorService);
|
||||
const widget = editorService.activeTextEditorWidget;
|
||||
if (isCodeEditor(widget)) {
|
||||
const position = widget.getPosition();
|
||||
if (!position || !widget.hasModel()) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const modelUri = widget.getModel().uri;
|
||||
const bp = debugService.getModel().getBreakpoints({ lineNumber: position.lineNumber, uri: modelUri })
|
||||
.filter(bp => (bp.column === position.column || !bp.column && position.column <= 1)).pop();
|
||||
|
||||
if (bp) {
|
||||
return undefined;
|
||||
}
|
||||
if (debugService.getConfigurationManager().canSetBreakpointsIn(widget.getModel())) {
|
||||
return debugService.addBreakpoints(modelUri, [{ lineNumber: position.lineNumber, column: position.column > 1 ? position.column : undefined }], 'debugCommands.inlineBreakpointCommand');
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
primary: KeyMod.Shift | KeyCode.F9,
|
||||
when: EditorContextKeys.editorTextFocus,
|
||||
id: TOGGLE_INLINE_BREAKPOINT_ID,
|
||||
handler: inlineBreakpointHandler
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
|
||||
command: {
|
||||
id: TOGGLE_INLINE_BREAKPOINT_ID,
|
||||
title: { value: nls.localize('inlineBreakpoint', "Inline Breakpoint"), original: 'Debug: Inline Breakpoint' },
|
||||
category: nls.localize('debug', "Debug")
|
||||
}
|
||||
});
|
||||
MenuRegistry.appendMenuItem(MenuId.EditorContext, {
|
||||
command: {
|
||||
id: TOGGLE_INLINE_BREAKPOINT_ID,
|
||||
title: nls.localize('addInlineBreakpoint', "Add Inline Breakpoint")
|
||||
},
|
||||
when: ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, PanelFocusContext.toNegated(), EditorContextKeys.editorTextFocus),
|
||||
group: 'debug',
|
||||
order: 1
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'debug.openBreakpointToSide',
|
||||
weight: KeybindingWeight.WorkbenchContrib,
|
||||
when: CONTEXT_BREAKPOINTS_FOCUSED,
|
||||
primary: KeyMod.CtrlCmd | KeyCode.Enter,
|
||||
secondary: [KeyMod.Alt | KeyCode.Enter],
|
||||
handler: (accessor) => {
|
||||
const listService = accessor.get(IListService);
|
||||
const list = listService.lastFocusedList;
|
||||
if (list instanceof List) {
|
||||
const focus = list.getFocusedElements();
|
||||
if (focus.length && focus[0] instanceof Breakpoint) {
|
||||
return openBreakpointSource(focus[0], true, false, accessor.get(IDebugService), accessor.get(IEditorService));
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
}
|
||||
324
src/vs/workbench/contrib/debug/browser/debugEditorActions.ts
Normal file
@@ -0,0 +1,324 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { ServicesAccessor, registerEditorAction, EditorAction, IActionOptions } from 'vs/editor/browser/editorExtensions';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IDebugService, CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE, State, REPL_ID, VIEWLET_ID, IDebugEditorContribution, EDITOR_CONTRIBUTION_ID, BreakpointWidgetContext, IBreakpoint } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { openBreakpointSource } from 'vs/workbench/contrib/debug/browser/breakpointsView';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { PanelFocusContext } from 'vs/workbench/common/panel';
|
||||
import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
|
||||
export const TOGGLE_BREAKPOINT_ID = 'editor.debug.action.toggleBreakpoint';
|
||||
class ToggleBreakpointAction extends EditorAction {
|
||||
constructor() {
|
||||
super({
|
||||
id: TOGGLE_BREAKPOINT_ID,
|
||||
label: nls.localize('toggleBreakpointAction', "Debug: Toggle Breakpoint"),
|
||||
alias: 'Debug: Toggle Breakpoint',
|
||||
precondition: null,
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
primary: KeyCode.F9,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<any> {
|
||||
const debugService = accessor.get(IDebugService);
|
||||
|
||||
const position = editor.getPosition();
|
||||
if (editor.hasModel() && position) {
|
||||
const modelUri = editor.getModel().uri;
|
||||
const bps = debugService.getModel().getBreakpoints({ lineNumber: position.lineNumber, uri: modelUri });
|
||||
|
||||
if (bps.length) {
|
||||
return Promise.all(bps.map(bp => debugService.removeBreakpoints(bp.getId())));
|
||||
}
|
||||
if (debugService.getConfigurationManager().canSetBreakpointsIn(editor.getModel())) {
|
||||
return debugService.addBreakpoints(modelUri, [{ lineNumber: position.lineNumber }], 'debugEditorActions.toggleBreakpointAction');
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
export const TOGGLE_CONDITIONAL_BREAKPOINT_ID = 'editor.debug.action.conditionalBreakpoint';
|
||||
class ConditionalBreakpointAction extends EditorAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: TOGGLE_CONDITIONAL_BREAKPOINT_ID,
|
||||
label: nls.localize('conditionalBreakpointEditorAction', "Debug: Add Conditional Breakpoint..."),
|
||||
alias: 'Debug: Add Conditional Breakpoint...',
|
||||
precondition: null
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
const debugService = accessor.get(IDebugService);
|
||||
|
||||
const position = editor.getPosition();
|
||||
if (position && editor.hasModel() && debugService.getConfigurationManager().canSetBreakpointsIn(editor.getModel())) {
|
||||
editor.getContribution<IDebugEditorContribution>(EDITOR_CONTRIBUTION_ID).showBreakpointWidget(position.lineNumber, position.column);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const TOGGLE_LOG_POINT_ID = 'editor.debug.action.toggleLogPoint';
|
||||
class LogPointAction extends EditorAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: TOGGLE_LOG_POINT_ID,
|
||||
label: nls.localize('logPointEditorAction', "Debug: Add Logpoint..."),
|
||||
alias: 'Debug: Add Logpoint...',
|
||||
precondition: null
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
|
||||
const debugService = accessor.get(IDebugService);
|
||||
|
||||
const position = editor.getPosition();
|
||||
if (position && editor.hasModel() && debugService.getConfigurationManager().canSetBreakpointsIn(editor.getModel())) {
|
||||
editor.getContribution<IDebugEditorContribution>(EDITOR_CONTRIBUTION_ID).showBreakpointWidget(position.lineNumber, position.column, BreakpointWidgetContext.LOG_MESSAGE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RunToCursorAction extends EditorAction {
|
||||
|
||||
public static ID = 'editor.debug.action.runToCursor';
|
||||
public static LABEL = nls.localize('runToCursor', "Run to Cursor");
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: RunToCursorAction.ID,
|
||||
label: RunToCursorAction.LABEL,
|
||||
alias: 'Debug: Run to Cursor',
|
||||
precondition: ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, PanelFocusContext.toNegated(), CONTEXT_DEBUG_STATE.isEqualTo('stopped'), EditorContextKeys.editorTextFocus),
|
||||
menuOpts: {
|
||||
group: 'debug',
|
||||
order: 2
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
|
||||
const debugService = accessor.get(IDebugService);
|
||||
const focusedSession = debugService.getViewModel().focusedSession;
|
||||
if (debugService.state !== State.Stopped || !focusedSession) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
let breakpointToRemove: IBreakpoint;
|
||||
const oneTimeListener = focusedSession.onDidChangeState(() => {
|
||||
const state = focusedSession.state;
|
||||
if (state === State.Stopped || state === State.Inactive) {
|
||||
if (breakpointToRemove) {
|
||||
debugService.removeBreakpoints(breakpointToRemove.getId());
|
||||
}
|
||||
oneTimeListener.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
const position = editor.getPosition();
|
||||
if (!editor.hasModel() || !position) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const uri = editor.getModel().uri;
|
||||
const bpExists = !!(debugService.getModel().getBreakpoints({ column: position.column, lineNumber: position.lineNumber, uri }).length);
|
||||
return (bpExists ? Promise.resolve(null) : <Promise<any>>debugService.addBreakpoints(uri, [{ lineNumber: position.lineNumber, column: position.column }], 'debugEditorActions.runToCursorAction')).then((breakpoints) => {
|
||||
if (breakpoints && breakpoints.length) {
|
||||
breakpointToRemove = breakpoints[0];
|
||||
}
|
||||
debugService.getViewModel().focusedThread!.continue();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class SelectionToReplAction extends EditorAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.debug.action.selectionToRepl',
|
||||
label: nls.localize('debugEvaluate', "Debug: Evaluate"),
|
||||
alias: 'Debug: Evaluate',
|
||||
precondition: ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection, CONTEXT_IN_DEBUG_MODE, EditorContextKeys.editorTextFocus),
|
||||
menuOpts: {
|
||||
group: 'debug',
|
||||
order: 0
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
|
||||
const debugService = accessor.get(IDebugService);
|
||||
const panelService = accessor.get(IPanelService);
|
||||
const viewModel = debugService.getViewModel();
|
||||
const session = viewModel.focusedSession;
|
||||
if (!editor.hasModel() || !session) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const text = editor.getModel().getValueInRange(editor.getSelection());
|
||||
return session.addReplExpression(viewModel.focusedStackFrame!, text)
|
||||
.then(() => panelService.openPanel(REPL_ID, true))
|
||||
.then(_ => undefined);
|
||||
}
|
||||
}
|
||||
|
||||
class SelectionToWatchExpressionsAction extends EditorAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.debug.action.selectionToWatch',
|
||||
label: nls.localize('debugAddToWatch', "Debug: Add to Watch"),
|
||||
alias: 'Debug: Add to Watch',
|
||||
precondition: ContextKeyExpr.and(EditorContextKeys.hasNonEmptySelection, CONTEXT_IN_DEBUG_MODE, EditorContextKeys.editorTextFocus),
|
||||
menuOpts: {
|
||||
group: 'debug',
|
||||
order: 1
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
|
||||
const debugService = accessor.get(IDebugService);
|
||||
const viewletService = accessor.get(IViewletService);
|
||||
if (!editor.hasModel()) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const text = editor.getModel().getValueInRange(editor.getSelection());
|
||||
return viewletService.openViewlet(VIEWLET_ID).then(() => debugService.addWatchExpression(text));
|
||||
}
|
||||
}
|
||||
|
||||
class ShowDebugHoverAction extends EditorAction {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: 'editor.debug.action.showDebugHover',
|
||||
label: nls.localize('showDebugHover', "Debug: Show Hover"),
|
||||
alias: 'Debug: Show Hover',
|
||||
precondition: CONTEXT_IN_DEBUG_MODE,
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.editorTextFocus,
|
||||
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_I),
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor): Promise<void> {
|
||||
const position = editor.getPosition();
|
||||
if (!position || !editor.hasModel()) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
const word = editor.getModel().getWordAtPosition(position);
|
||||
if (!word) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
const range = new Range(position.lineNumber, position.column, position.lineNumber, word.endColumn);
|
||||
return editor.getContribution<IDebugEditorContribution>(EDITOR_CONTRIBUTION_ID).showHover(range, true);
|
||||
}
|
||||
}
|
||||
|
||||
class GoToBreakpointAction extends EditorAction {
|
||||
constructor(private isNext: boolean, opts: IActionOptions) {
|
||||
super(opts);
|
||||
}
|
||||
|
||||
public run(accessor: ServicesAccessor, editor: ICodeEditor, args: any): Promise<any> {
|
||||
const debugService = accessor.get(IDebugService);
|
||||
const editorService = accessor.get(IEditorService);
|
||||
if (editor.hasModel()) {
|
||||
const currentUri = editor.getModel().uri;
|
||||
const currentLine = editor.getPosition().lineNumber;
|
||||
//Breakpoints returned from `getBreakpoints` are already sorted.
|
||||
const allEnabledBreakpoints = debugService.getModel().getBreakpoints({ enabledOnly: true });
|
||||
|
||||
//Try to find breakpoint in current file
|
||||
let moveBreakpoint =
|
||||
this.isNext
|
||||
? allEnabledBreakpoints.filter(bp => bp.uri.toString() === currentUri.toString() && bp.lineNumber > currentLine).shift()
|
||||
: allEnabledBreakpoints.filter(bp => bp.uri.toString() === currentUri.toString() && bp.lineNumber < currentLine).pop();
|
||||
|
||||
//Try to find breakpoints in following files
|
||||
if (!moveBreakpoint) {
|
||||
moveBreakpoint =
|
||||
this.isNext
|
||||
? allEnabledBreakpoints.filter(bp => bp.uri.toString() > currentUri.toString()).shift()
|
||||
: allEnabledBreakpoints.filter(bp => bp.uri.toString() < currentUri.toString()).pop();
|
||||
}
|
||||
|
||||
//Move to first or last possible breakpoint
|
||||
if (!moveBreakpoint && allEnabledBreakpoints.length) {
|
||||
moveBreakpoint = this.isNext ? allEnabledBreakpoints[0] : allEnabledBreakpoints[allEnabledBreakpoints.length - 1];
|
||||
}
|
||||
|
||||
if (moveBreakpoint) {
|
||||
return openBreakpointSource(moveBreakpoint, false, true, debugService, editorService);
|
||||
}
|
||||
}
|
||||
|
||||
return Promise.resolve(null);
|
||||
}
|
||||
}
|
||||
|
||||
class GoToNextBreakpointAction extends GoToBreakpointAction {
|
||||
constructor() {
|
||||
super(true, {
|
||||
id: 'editor.debug.action.goToNextBreakpoint',
|
||||
label: nls.localize('goToNextBreakpoint', "Debug: Go To Next Breakpoint"),
|
||||
alias: 'Debug: Go To Next Breakpoint',
|
||||
precondition: null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class GoToPreviousBreakpointAction extends GoToBreakpointAction {
|
||||
constructor() {
|
||||
super(false, {
|
||||
id: 'editor.debug.action.goToPreviousBreakpoint',
|
||||
label: nls.localize('goToPreviousBreakpoint', "Debug: Go To Previous Breakpoint"),
|
||||
alias: 'Debug: Go To Previous Breakpoint',
|
||||
precondition: null
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorAction(ToggleBreakpointAction);
|
||||
registerEditorAction(ConditionalBreakpointAction);
|
||||
registerEditorAction(LogPointAction);
|
||||
registerEditorAction(RunToCursorAction);
|
||||
registerEditorAction(SelectionToReplAction);
|
||||
registerEditorAction(SelectionToWatchExpressionsAction);
|
||||
registerEditorAction(ShowDebugHoverAction);
|
||||
registerEditorAction(GoToNextBreakpointAction);
|
||||
registerEditorAction(GoToPreviousBreakpointAction);
|
||||
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
|
||||
command: {
|
||||
id: RunToCursorAction.ID,
|
||||
title: { value: RunToCursorAction.LABEL, original: 'Debug: Run to Cursor' },
|
||||
category: nls.localize('debug', "Debug")
|
||||
},
|
||||
group: 'debug',
|
||||
when: ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, CONTEXT_DEBUG_STATE.isEqualTo('stopped')),
|
||||
});
|
||||
@@ -0,0 +1,776 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import * as lifecycle from 'vs/base/common/lifecycle';
|
||||
import * as env from 'vs/base/common/platform';
|
||||
import { URI as uri } from 'vs/base/common/uri';
|
||||
import { visit } from 'vs/base/common/json';
|
||||
import severity from 'vs/base/common/severity';
|
||||
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 { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
import { IDecorationOptions } from 'vs/editor/common/editorCommon';
|
||||
import { IModelDecorationOptions, IModelDeltaDecoration, TrackedRangeStickiness, ITextModel } from 'vs/editor/common/model';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
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 } from 'vs/platform/contextview/browser/contextView';
|
||||
import { RemoveBreakpointAction } from 'vs/workbench/contrib/debug/browser/debugActions';
|
||||
import { IDebugEditorContribution, IDebugService, State, IBreakpoint, EDITOR_CONTRIBUTION_ID, CONTEXT_BREAKPOINT_WIDGET_VISIBLE, IStackFrame, IDebugConfiguration, IExpression, IExceptionInfo, BreakpointWidgetContext } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { ExceptionWidget } from 'vs/workbench/contrib/debug/browser/exceptionWidget';
|
||||
import { FloatingClickWidget } from 'vs/workbench/browser/parts/editor/editorWidgets';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands';
|
||||
import { first } from 'vs/base/common/arrays';
|
||||
import { IMarginData } from 'vs/editor/browser/controller/mouseTarget';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { ContextSubMenu } from 'vs/base/browser/contextmenu';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { getHover } from 'vs/editor/contrib/hover/getHover';
|
||||
import { IEditorHoverOptions } from 'vs/editor/common/config/editorOptions';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { BreakpointWidget } from 'vs/workbench/contrib/debug/browser/breakpointWidget';
|
||||
import { DebugHoverWidget } from 'vs/workbench/contrib/debug/browser/debugHover';
|
||||
|
||||
const HOVER_DELAY = 300;
|
||||
const LAUNCH_JSON_REGEX = /launch\.json$/;
|
||||
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
|
||||
|
||||
export class DebugEditorContribution implements IDebugEditorContribution {
|
||||
|
||||
private toDispose: lifecycle.IDisposable[];
|
||||
private hoverWidget: DebugHoverWidget;
|
||||
private nonDebugHoverPosition: Position;
|
||||
private hoverRange: Range;
|
||||
private mouseDown = false;
|
||||
|
||||
private breakpointHintDecoration: string[];
|
||||
private breakpointWidget: BreakpointWidget | undefined;
|
||||
private breakpointWidgetVisible: IContextKey<boolean>;
|
||||
private wordToLineNumbersMap: Map<string, Position[]> | undefined;
|
||||
|
||||
private exceptionWidget: ExceptionWidget | undefined;
|
||||
|
||||
private configurationWidget: FloatingClickWidget;
|
||||
|
||||
constructor(
|
||||
private editor: ICodeEditor,
|
||||
@IDebugService private readonly debugService: IDebugService,
|
||||
@IContextMenuService private readonly contextMenuService: IContextMenuService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@ICodeEditorService private readonly codeEditorService: ICodeEditorService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IKeybindingService private readonly keybindingService: IKeybindingService,
|
||||
@IDialogService private readonly dialogService: IDialogService,
|
||||
) {
|
||||
this.breakpointHintDecoration = [];
|
||||
this.hoverWidget = this.instantiationService.createInstance(DebugHoverWidget, this.editor);
|
||||
this.toDispose = [];
|
||||
this.registerListeners();
|
||||
this.breakpointWidgetVisible = CONTEXT_BREAKPOINT_WIDGET_VISIBLE.bindTo(contextKeyService);
|
||||
this.updateConfigurationWidgetVisibility();
|
||||
this.codeEditorService.registerDecorationType(INLINE_VALUE_DECORATION_KEY, {});
|
||||
this.toggleExceptionWidget();
|
||||
}
|
||||
|
||||
private getContextMenuActions(breakpoints: ReadonlyArray<IBreakpoint>, uri: uri, lineNumber: number): Array<IAction | ContextSubMenu> {
|
||||
const actions: Array<IAction | ContextSubMenu> = [];
|
||||
if (breakpoints.length === 1) {
|
||||
const breakpointType = breakpoints[0].logMessage ? nls.localize('logPoint', "Logpoint") : nls.localize('breakpoint', "Breakpoint");
|
||||
actions.push(new RemoveBreakpointAction(RemoveBreakpointAction.ID, nls.localize('removeBreakpoint', "Remove {0}", breakpointType), this.debugService, this.keybindingService));
|
||||
actions.push(new Action(
|
||||
'workbench.debug.action.editBreakpointAction',
|
||||
nls.localize('editBreakpoint', "Edit {0}...", breakpointType),
|
||||
undefined,
|
||||
true,
|
||||
() => Promise.resolve(this.editor.getContribution<IDebugEditorContribution>(EDITOR_CONTRIBUTION_ID).showBreakpointWidget(breakpoints[0].lineNumber, breakpoints[0].column))
|
||||
));
|
||||
|
||||
actions.push(new Action(
|
||||
`workbench.debug.viewlet.action.toggleBreakpoint`,
|
||||
breakpoints[0].enabled ? nls.localize('disableBreakpoint', "Disable {0}", breakpointType) : nls.localize('enableBreakpoint', "Enable {0}", breakpointType),
|
||||
undefined,
|
||||
true,
|
||||
() => this.debugService.enableOrDisableBreakpoints(!breakpoints[0].enabled, breakpoints[0])
|
||||
));
|
||||
} else if (breakpoints.length > 1) {
|
||||
const sorted = breakpoints.slice().sort((first, second) => (first.column && second.column) ? first.column - second.column : 1);
|
||||
actions.push(new ContextSubMenu(nls.localize('removeBreakpoints', "Remove Breakpoints"), sorted.map(bp => new Action(
|
||||
'removeInlineBreakpoint',
|
||||
bp.column ? nls.localize('removeInlineBreakpointOnColumn', "Remove Inline Breakpoint on Column {0}", bp.column) : nls.localize('removeLineBreakpoint', "Remove Line Breakpoint"),
|
||||
undefined,
|
||||
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('editInlineBreakpointOnColumn', "Edit Inline Breakpoint on Column {0}", bp.column) : nls.localize('editLineBrekapoint', "Edit Line Breakpoint"),
|
||||
undefined,
|
||||
true,
|
||||
() => Promise.resolve(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('disableInlineColumnBreakpoint', "Disable Inline Breakpoint on Column {0}", bp.column) : nls.localize('disableBreakpointOnLine', "Disable Line Breakpoint"))
|
||||
: (bp.column ? nls.localize('enableBreakpoints', "Enable Inline Breakpoint on Column {0}", bp.column) : nls.localize('enableBreakpointOnLine', "Enable Line Breakpoint")),
|
||||
undefined,
|
||||
true,
|
||||
() => this.debugService.enableOrDisableBreakpoints(!bp.enabled, bp)
|
||||
))));
|
||||
} else {
|
||||
actions.push(new Action(
|
||||
'addBreakpoint',
|
||||
nls.localize('addBreakpoint', "Add Breakpoint"),
|
||||
undefined,
|
||||
true,
|
||||
() => this.debugService.addBreakpoints(uri, [{ lineNumber }], `debugEditorContextMenu`)
|
||||
));
|
||||
actions.push(new Action(
|
||||
'addConditionalBreakpoint',
|
||||
nls.localize('addConditionalBreakpoint', "Add Conditional Breakpoint..."),
|
||||
undefined,
|
||||
true,
|
||||
() => Promise.resolve(this.editor.getContribution<IDebugEditorContribution>(EDITOR_CONTRIBUTION_ID).showBreakpointWidget(lineNumber, undefined))
|
||||
));
|
||||
actions.push(new Action(
|
||||
'addLogPoint',
|
||||
nls.localize('addLogPoint', "Add Logpoint..."),
|
||||
undefined,
|
||||
true,
|
||||
() => Promise.resolve(this.editor.getContribution<IDebugEditorContribution>(EDITOR_CONTRIBUTION_ID).showBreakpointWidget(lineNumber, undefined, BreakpointWidgetContext.LOG_MESSAGE))
|
||||
));
|
||||
}
|
||||
|
||||
return actions;
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.toDispose.push(this.editor.onMouseDown((e: IEditorMouseEvent) => {
|
||||
const data = e.target.detail as IMarginData;
|
||||
const model = this.editor.getModel();
|
||||
if (!e.target.position || !model || e.target.type !== MouseTargetType.GUTTER_GLYPH_MARGIN || data.isAfterLines || !this.marginFreeFromNonDebugDecorations(e.target.position.lineNumber)) {
|
||||
return;
|
||||
}
|
||||
const canSetBreakpoints = this.debugService.getConfigurationManager().canSetBreakpointsIn(model);
|
||||
const lineNumber = e.target.position.lineNumber;
|
||||
const uri = model.uri;
|
||||
|
||||
if (e.event.rightButton || (env.isMacintosh && e.event.leftButton && e.event.ctrlKey)) {
|
||||
if (!canSetBreakpoints) {
|
||||
return;
|
||||
}
|
||||
|
||||
const anchor = { x: e.event.posx, y: e.event.posy };
|
||||
const breakpoints = this.debugService.getModel().getBreakpoints({ lineNumber, uri });
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
getActions: () => this.getContextMenuActions(breakpoints, uri, lineNumber),
|
||||
getActionsContext: () => breakpoints.length ? breakpoints[0] : undefined
|
||||
});
|
||||
} else {
|
||||
const breakpoints = this.debugService.getModel().getBreakpoints({ uri, lineNumber });
|
||||
|
||||
if (breakpoints.length) {
|
||||
// Show the dialog if there is a potential condition to be accidently lost.
|
||||
// Do not show dialog on linux due to electron issue freezing the mouse #50026
|
||||
if (!env.isLinux && breakpoints.some(bp => !!bp.condition || !!bp.logMessage || !!bp.hitCondition)) {
|
||||
const logPoint = breakpoints.every(bp => !!bp.logMessage);
|
||||
const breakpointType = logPoint ? nls.localize('logPoint', "Logpoint") : nls.localize('breakpoint', "Breakpoint");
|
||||
const disable = breakpoints.some(bp => bp.enabled);
|
||||
|
||||
const enabling = nls.localize('breakpointHasConditionDisabled',
|
||||
"This {0} has a {1} that will get lost on remove. Consider enabling the {0} instead.",
|
||||
breakpointType.toLowerCase(),
|
||||
logPoint ? nls.localize('message', "message") : nls.localize('condition', "condition")
|
||||
);
|
||||
const disabling = nls.localize('breakpointHasConditionEnabled',
|
||||
"This {0} has a {1} that will get lost on remove. Consider disabling the {0} instead.",
|
||||
breakpointType.toLowerCase(),
|
||||
logPoint ? nls.localize('message', "message") : nls.localize('condition', "condition")
|
||||
);
|
||||
|
||||
this.dialogService.show(severity.Info, disable ? disabling : enabling, [
|
||||
nls.localize('removeLogPoint', "Remove {0}", breakpointType),
|
||||
nls.localize('disableLogPoint', "{0} {1}", disable ? nls.localize('disable', "Disable") : nls.localize('enable', "Enable"), breakpointType),
|
||||
nls.localize('cancel', "Cancel")
|
||||
], { cancelId: 2 }).then(choice => {
|
||||
if (choice === 0) {
|
||||
breakpoints.forEach(bp => this.debugService.removeBreakpoints(bp.getId()));
|
||||
}
|
||||
if (choice === 1) {
|
||||
breakpoints.forEach(bp => this.debugService.enableOrDisableBreakpoints(!disable, bp));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
breakpoints.forEach(bp => this.debugService.removeBreakpoints(bp.getId()));
|
||||
}
|
||||
} else if (canSetBreakpoints) {
|
||||
this.debugService.addBreakpoints(uri, [{ lineNumber }], `debugEditorGutter`);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this.toDispose.push(this.editor.onMouseMove((e: IEditorMouseEvent) => {
|
||||
let showBreakpointHintAtLineNumber = -1;
|
||||
const model = this.editor.getModel();
|
||||
if (model && e.target.position && e.target.type === MouseTargetType.GUTTER_GLYPH_MARGIN && this.debugService.getConfigurationManager().canSetBreakpointsIn(model) &&
|
||||
this.marginFreeFromNonDebugDecorations(e.target.position.lineNumber)) {
|
||||
const data = e.target.detail as IMarginData;
|
||||
if (!data.isAfterLines) {
|
||||
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.onMouseUp(() => this.mouseDown = false));
|
||||
this.toDispose.push(this.editor.onMouseMove((e: IEditorMouseEvent) => this.onEditorMouseMove(e)));
|
||||
this.toDispose.push(this.editor.onMouseLeave((e: IEditorMouseEvent) => {
|
||||
this.provideNonDebugHoverScheduler.cancel();
|
||||
const hoverDomNode = this.hoverWidget.getDomNode();
|
||||
if (!hoverDomNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const rect = hoverDomNode.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 = undefined;
|
||||
this.updateInlineValuesScheduler.schedule();
|
||||
}));
|
||||
this.toDispose.push(this.editor.onDidChangeModel(() => {
|
||||
const stackFrame = this.debugService.getViewModel().focusedStackFrame;
|
||||
const model = this.editor.getModel();
|
||||
if (model) {
|
||||
this._applyHoverConfiguration(model, stackFrame);
|
||||
}
|
||||
this.closeBreakpointWidget();
|
||||
this.toggleExceptionWidget();
|
||||
this.hideHoverWidget();
|
||||
this.updateConfigurationWidgetVisibility();
|
||||
this.wordToLineNumbersMap = undefined;
|
||||
this.updateInlineValueDecorations(stackFrame);
|
||||
}));
|
||||
this.toDispose.push(this.editor.onDidScrollChange(() => this.hideHoverWidget));
|
||||
this.toDispose.push(this.debugService.onDidChangeState((state: State) => {
|
||||
if (state !== State.Stopped) {
|
||||
this.toggleExceptionWidget();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private _applyHoverConfiguration(model: ITextModel, stackFrame: IStackFrame | undefined): void {
|
||||
if (stackFrame && model.uri.toString() === stackFrame.source.uri.toString()) {
|
||||
this.editor.updateOptions({
|
||||
hover: {
|
||||
enabled: false
|
||||
}
|
||||
});
|
||||
} else {
|
||||
let overrides = {
|
||||
resource: model.uri,
|
||||
overrideIdentifier: model.getLanguageIdentifier().language
|
||||
};
|
||||
const defaultConfiguration = this.configurationService.getValue<IEditorHoverOptions>('editor.hover', overrides);
|
||||
this.editor.updateOptions({
|
||||
hover: {
|
||||
enabled: defaultConfiguration.enabled,
|
||||
delay: defaultConfiguration.delay,
|
||||
sticky: defaultConfiguration.sticky
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public getId(): string {
|
||||
return EDITOR_CONTRIBUTION_ID;
|
||||
}
|
||||
|
||||
public showHover(range: Range, focus: boolean): Promise<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 Promise.resolve();
|
||||
}
|
||||
|
||||
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 | undefined): void {
|
||||
const model = this.editor.getModel();
|
||||
if (model) {
|
||||
this._applyHoverConfiguration(model, sf);
|
||||
if (sf && sf.source.uri.toString() === model.uri.toString()) {
|
||||
this.toggleExceptionWidget();
|
||||
} else {
|
||||
this.hideHoverWidget();
|
||||
}
|
||||
}
|
||||
|
||||
this.updateInlineValueDecorations(sf);
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get showHoverScheduler(): RunOnceScheduler {
|
||||
const scheduler = new RunOnceScheduler(() => this.showHover(this.hoverRange, false), HOVER_DELAY);
|
||||
this.toDispose.push(scheduler);
|
||||
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get hideHoverScheduler(): RunOnceScheduler {
|
||||
const scheduler = new RunOnceScheduler(() => this.hoverWidget.hide(), 2 * HOVER_DELAY);
|
||||
this.toDispose.push(scheduler);
|
||||
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get provideNonDebugHoverScheduler(): RunOnceScheduler {
|
||||
const scheduler = new RunOnceScheduler(() => {
|
||||
if (this.editor.hasModel()) {
|
||||
getHover(this.editor.getModel(), this.nonDebugHoverPosition, CancellationToken.None);
|
||||
}
|
||||
}, HOVER_DELAY);
|
||||
this.toDispose.push(scheduler);
|
||||
|
||||
return scheduler;
|
||||
}
|
||||
|
||||
private hideHoverWidget(): void {
|
||||
if (!this.hideHoverScheduler.isScheduled() && this.hoverWidget.isVisible()) {
|
||||
this.hideHoverScheduler.schedule();
|
||||
}
|
||||
this.showHoverScheduler.cancel();
|
||||
this.provideNonDebugHoverScheduler.cancel();
|
||||
}
|
||||
|
||||
// hover business
|
||||
|
||||
private onEditorMouseDown(mouseEvent: IEditorMouseEvent): void {
|
||||
this.mouseDown = true;
|
||||
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;
|
||||
}
|
||||
|
||||
if (this.configurationService.getValue<IDebugConfiguration>('debug').enableAllHovers && mouseEvent.target.position) {
|
||||
this.nonDebugHoverPosition = mouseEvent.target.position;
|
||||
this.provideNonDebugHoverScheduler.schedule();
|
||||
}
|
||||
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 && !mouseEvent.target.range.equalsRange(this.hoverRange)) {
|
||||
this.hoverRange = mouseEvent.target.range;
|
||||
this.showHoverScheduler.schedule();
|
||||
}
|
||||
} else if (!this.mouseDown) {
|
||||
// Do not hide debug hover when the mouse is pressed because it usually leads to accidental closing #64620
|
||||
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, context?: BreakpointWidgetContext): void {
|
||||
if (this.breakpointWidget) {
|
||||
this.breakpointWidget.dispose();
|
||||
}
|
||||
|
||||
this.breakpointWidget = this.instantiationService.createInstance(BreakpointWidget, this.editor, lineNumber, context);
|
||||
this.breakpointWidget.show({ lineNumber, column: 1 }, 2);
|
||||
this.breakpointWidgetVisible.set(true);
|
||||
}
|
||||
|
||||
public closeBreakpointWidget(): void {
|
||||
if (this.breakpointWidget) {
|
||||
this.breakpointWidget.dispose();
|
||||
this.breakpointWidget = undefined;
|
||||
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 && sf.source && sf.source.available && sf.source.presentationHint !== 'deemphasize'), undefined);
|
||||
if (!exceptionSf || exceptionSf !== focusedSf) {
|
||||
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);
|
||||
this.exceptionWidget.show({ lineNumber, column }, 0);
|
||||
}
|
||||
|
||||
private closeExceptionWidget(): void {
|
||||
if (this.exceptionWidget) {
|
||||
this.exceptionWidget.dispose();
|
||||
this.exceptionWidget = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// configuration widget
|
||||
private updateConfigurationWidgetVisibility(): void {
|
||||
const model = this.editor.getModel();
|
||||
if (this.configurationWidget) {
|
||||
this.configurationWidget.dispose();
|
||||
}
|
||||
if (model && LAUNCH_JSON_REGEX.test(model.uri.toString()) && !this.editor.getConfiguration().readOnly) {
|
||||
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()));
|
||||
}
|
||||
}
|
||||
|
||||
public addLaunchConfiguration(): Promise<any> {
|
||||
/* __GDPR__
|
||||
"debug/addLaunchConfiguration" : {}
|
||||
*/
|
||||
this.telemetryService.publicLog('debug/addLaunchConfiguration');
|
||||
let configurationsArrayPosition: Position | undefined;
|
||||
const model = this.editor.getModel();
|
||||
if (!model) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
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 Promise.resolve();
|
||||
}
|
||||
|
||||
const insertLine = (position: Position): Promise<any> => {
|
||||
// Check if there are more characters on a line after a "configurations": [, if yes enter a newline
|
||||
if (model.getLineLastNonWhitespaceColumn(position.lineNumber) > position.column) {
|
||||
this.editor.setPosition(position);
|
||||
CoreEditingCommands.LineBreakInsert.runEditorCommand(null, this.editor, 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',
|
||||
stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges
|
||||
};
|
||||
|
||||
// Inline Decorations
|
||||
|
||||
@memoize
|
||||
private get removeInlineValuesScheduler(): RunOnceScheduler {
|
||||
return new RunOnceScheduler(
|
||||
() => this.editor.removeDecorations(INLINE_VALUE_DECORATION_KEY),
|
||||
100
|
||||
);
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get updateInlineValuesScheduler(): RunOnceScheduler {
|
||||
return new RunOnceScheduler(
|
||||
() => this.updateInlineValueDecorations(this.debugService.getViewModel().focusedStackFrame),
|
||||
200
|
||||
);
|
||||
}
|
||||
|
||||
private updateInlineValueDecorations(stackFrame: IStackFrame | undefined): void {
|
||||
const model = this.editor.getModel();
|
||||
if (!this.configurationService.getValue<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 => Promise.all(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, model);
|
||||
}))).then(decorationsPerScope => {
|
||||
const allDecorations = decorationsPerScope.reduce((previous, current) => previous.concat(current), []);
|
||||
this.editor.setDecorations(INLINE_VALUE_DECORATION_KEY, allDecorations);
|
||||
}));
|
||||
}
|
||||
|
||||
private createInlineValueDecorationsInsideRange(expressions: ReadonlyArray<IExpression>, range: Range, model: ITextModel): 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) => {
|
||||
const positions = wordToPositionsMap.get(name);
|
||||
if (positions) {
|
||||
for (let position of positions) {
|
||||
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 = model.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();
|
||||
if (!model) {
|
||||
return this.wordToLineNumbersMap;
|
||||
}
|
||||
|
||||
// For every word in every line, map its ranges for fast lookup
|
||||
for (let lineNumber = 1, len = model.getLineCount(); lineNumber <= len; ++lineNumber) {
|
||||
const lineContent = model.getLineContent(lineNumber);
|
||||
|
||||
// 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 tokenIndex = 0, tokenCount = lineTokens.getCount(); tokenIndex < tokenCount; tokenIndex++) {
|
||||
const tokenStartOffset = lineTokens.getStartOffset(tokenIndex);
|
||||
const tokenEndOffset = lineTokens.getEndOffset(tokenIndex);
|
||||
const tokenType = lineTokens.getStandardTokenType(tokenIndex);
|
||||
const tokenStr = lineContent.substring(tokenStartOffset, tokenEndOffset);
|
||||
|
||||
// Token is a word and not a comment
|
||||
if (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, tokenStartOffset));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorContribution(DebugEditorContribution);
|
||||
@@ -0,0 +1,333 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as lifecycle from 'vs/base/common/lifecycle';
|
||||
import { Constants } from 'vs/editor/common/core/uint';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { ITextModel, TrackedRangeStickiness, IModelDeltaDecoration, IModelDecorationOptions } from 'vs/editor/common/model';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { IDebugService, IBreakpoint, State, IBreakpointUpdateData } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { MarkdownString } from 'vs/base/common/htmlContent';
|
||||
import { getBreakpointMessageAndClassName } from 'vs/workbench/contrib/debug/browser/breakpointsView';
|
||||
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { registerColor } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
interface IBreakpointDecoration {
|
||||
decorationId: string;
|
||||
modelId: string;
|
||||
range: Range;
|
||||
}
|
||||
|
||||
interface IDebugEditorModelData {
|
||||
model: ITextModel;
|
||||
toDispose: lifecycle.IDisposable[];
|
||||
breakpointDecorations: IBreakpointDecoration[];
|
||||
currentStackDecorations: string[];
|
||||
topStackFrameRange: Range | undefined;
|
||||
}
|
||||
|
||||
export class DebugEditorModelManager implements IWorkbenchContribution {
|
||||
static readonly ID = 'breakpointManager';
|
||||
static readonly STICKINESS = TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges;
|
||||
private modelDataMap: Map<string, IDebugEditorModelData>;
|
||||
private toDispose: lifecycle.IDisposable[];
|
||||
private ignoreDecorationsChangedEvent: boolean;
|
||||
|
||||
constructor(
|
||||
@IModelService private readonly modelService: IModelService,
|
||||
@IDebugService private readonly debugService: IDebugService,
|
||||
) {
|
||||
this.modelDataMap = new Map<string, IDebugEditorModelData>();
|
||||
this.toDispose = [];
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
this.modelDataMap.forEach(modelData => {
|
||||
lifecycle.dispose(modelData.toDispose);
|
||||
modelData.model.deltaDecorations(modelData.breakpointDecorations.map(bpd => bpd.decorationId), []);
|
||||
modelData.model.deltaDecorations(modelData.currentStackDecorations, []);
|
||||
});
|
||||
this.toDispose = lifecycle.dispose(this.toDispose);
|
||||
|
||||
this.modelDataMap.clear();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.toDispose.push(this.modelService.onModelAdded(this.onModelAdded, this));
|
||||
this.modelService.getModels().forEach(model => this.onModelAdded(model));
|
||||
this.toDispose.push(this.modelService.onModelRemoved(this.onModelRemoved, this));
|
||||
|
||||
this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => this.onBreakpointsChange()));
|
||||
this.toDispose.push(this.debugService.getViewModel().onDidFocusStackFrame(() => this.onFocusStackFrame()));
|
||||
this.toDispose.push(this.debugService.onDidChangeState(state => {
|
||||
if (state === State.Inactive) {
|
||||
this.modelDataMap.forEach(modelData => {
|
||||
modelData.topStackFrameRange = undefined;
|
||||
});
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private onModelAdded(model: ITextModel): void {
|
||||
const modelUriStr = model.uri.toString();
|
||||
const breakpoints = this.debugService.getModel().getBreakpoints({ uri: model.uri });
|
||||
|
||||
const currentStackDecorations = model.deltaDecorations([], this.createCallStackDecorations(modelUriStr));
|
||||
const desiredDecorations = this.createBreakpointDecorations(model, breakpoints);
|
||||
const breakpointDecorationIds = model.deltaDecorations([], desiredDecorations);
|
||||
const toDispose: lifecycle.IDisposable[] = [model.onDidChangeDecorations((e) => this.onModelDecorationsChanged(modelUriStr))];
|
||||
|
||||
this.modelDataMap.set(modelUriStr, {
|
||||
model: model,
|
||||
toDispose: toDispose,
|
||||
breakpointDecorations: breakpointDecorationIds.map((decorationId, index) => ({ decorationId, modelId: breakpoints[index].getId(), range: desiredDecorations[index].range })),
|
||||
currentStackDecorations: currentStackDecorations,
|
||||
topStackFrameRange: undefined
|
||||
});
|
||||
}
|
||||
|
||||
private onModelRemoved(model: ITextModel): void {
|
||||
const modelUriStr = model.uri.toString();
|
||||
const data = this.modelDataMap.get(modelUriStr);
|
||||
if (data) {
|
||||
lifecycle.dispose(data.toDispose);
|
||||
this.modelDataMap.delete(modelUriStr);
|
||||
}
|
||||
}
|
||||
|
||||
// call stack management. Represent data coming from the debug service.
|
||||
|
||||
private onFocusStackFrame(): void {
|
||||
this.modelDataMap.forEach((modelData, uri) => {
|
||||
modelData.currentStackDecorations = modelData.model.deltaDecorations(modelData.currentStackDecorations, this.createCallStackDecorations(uri));
|
||||
});
|
||||
}
|
||||
|
||||
private createCallStackDecorations(modelUriStr: string): IModelDeltaDecoration[] {
|
||||
const result: IModelDeltaDecoration[] = [];
|
||||
const stackFrame = this.debugService.getViewModel().focusedStackFrame;
|
||||
if (!stackFrame || stackFrame.source.uri.toString() !== modelUriStr) {
|
||||
return result;
|
||||
}
|
||||
|
||||
// only show decorations for the currently focused thread.
|
||||
const columnUntilEOLRange = new Range(stackFrame.range.startLineNumber, stackFrame.range.startColumn, stackFrame.range.startLineNumber, Constants.MAX_SAFE_SMALL_INTEGER);
|
||||
const range = new Range(stackFrame.range.startLineNumber, stackFrame.range.startColumn, stackFrame.range.startLineNumber, stackFrame.range.startColumn + 1);
|
||||
|
||||
// compute how to decorate the editor. Different decorations are used if this is a top stack frame, focused stack frame,
|
||||
// an exception or a stack frame that did not change the line number (we only decorate the columns, not the whole line).
|
||||
const callStack = stackFrame.thread.getCallStack();
|
||||
if (callStack && callStack.length && stackFrame === callStack[0]) {
|
||||
result.push({
|
||||
options: DebugEditorModelManager.TOP_STACK_FRAME_MARGIN,
|
||||
range
|
||||
});
|
||||
|
||||
result.push({
|
||||
options: DebugEditorModelManager.TOP_STACK_FRAME_DECORATION,
|
||||
range: columnUntilEOLRange
|
||||
});
|
||||
|
||||
const modelData = this.modelDataMap.get(modelUriStr);
|
||||
if (modelData) {
|
||||
if (modelData.topStackFrameRange && modelData.topStackFrameRange.startLineNumber === stackFrame.range.startLineNumber && modelData.topStackFrameRange.startColumn !== stackFrame.range.startColumn) {
|
||||
result.push({
|
||||
options: DebugEditorModelManager.TOP_STACK_FRAME_INLINE_DECORATION,
|
||||
range: columnUntilEOLRange
|
||||
});
|
||||
}
|
||||
modelData.topStackFrameRange = columnUntilEOLRange;
|
||||
}
|
||||
} else {
|
||||
result.push({
|
||||
options: DebugEditorModelManager.FOCUSED_STACK_FRAME_MARGIN,
|
||||
range
|
||||
});
|
||||
|
||||
result.push({
|
||||
options: DebugEditorModelManager.FOCUSED_STACK_FRAME_DECORATION,
|
||||
range: columnUntilEOLRange
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// breakpoints management. Represent data coming from the debug service and also send data back.
|
||||
private onModelDecorationsChanged(modelUrlStr: string): void {
|
||||
const modelData = this.modelDataMap.get(modelUrlStr);
|
||||
if (!modelData || modelData.breakpointDecorations.length === 0 || this.ignoreDecorationsChangedEvent) {
|
||||
// I have no decorations
|
||||
return;
|
||||
}
|
||||
let somethingChanged = false;
|
||||
modelData.breakpointDecorations.forEach(breakpointDecoration => {
|
||||
if (somethingChanged) {
|
||||
return;
|
||||
}
|
||||
const newBreakpointRange = modelData.model.getDecorationRange(breakpointDecoration.decorationId);
|
||||
if (newBreakpointRange && (!breakpointDecoration.range.equalsRange(newBreakpointRange))) {
|
||||
somethingChanged = true;
|
||||
}
|
||||
});
|
||||
if (!somethingChanged) {
|
||||
// nothing to do, my decorations did not change.
|
||||
return;
|
||||
}
|
||||
|
||||
const data: { [id: string]: IBreakpointUpdateData } = Object.create(null);
|
||||
const breakpoints = this.debugService.getModel().getBreakpoints();
|
||||
const modelUri = modelData.model.uri;
|
||||
for (let i = 0, len = modelData.breakpointDecorations.length; i < len; i++) {
|
||||
const breakpointDecoration = modelData.breakpointDecorations[i];
|
||||
const decorationRange = modelData.model.getDecorationRange(breakpointDecoration.decorationId);
|
||||
// check if the line got deleted.
|
||||
if (decorationRange) {
|
||||
const breakpoint = breakpoints.filter(bp => bp.getId() === breakpointDecoration.modelId).pop();
|
||||
// since we know it is collapsed, it cannot grow to multiple lines
|
||||
if (breakpoint) {
|
||||
data[breakpoint.getId()] = {
|
||||
lineNumber: decorationRange.startLineNumber,
|
||||
column: breakpoint.column ? decorationRange.startColumn : undefined,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.debugService.updateBreakpoints(modelUri, data, true);
|
||||
}
|
||||
|
||||
private onBreakpointsChange(): void {
|
||||
const breakpointsMap = new Map<string, IBreakpoint[]>();
|
||||
this.debugService.getModel().getBreakpoints().forEach(bp => {
|
||||
const uriStr = bp.uri.toString();
|
||||
const breakpoints = breakpointsMap.get(uriStr);
|
||||
if (breakpoints) {
|
||||
breakpoints.push(bp);
|
||||
} else {
|
||||
breakpointsMap.set(uriStr, [bp]);
|
||||
}
|
||||
});
|
||||
|
||||
breakpointsMap.forEach((bps, uri) => {
|
||||
const data = this.modelDataMap.get(uri);
|
||||
if (data) {
|
||||
this.updateBreakpoints(data, breakpointsMap.get(uri)!);
|
||||
}
|
||||
});
|
||||
this.modelDataMap.forEach((modelData, uri) => {
|
||||
if (!breakpointsMap.has(uri)) {
|
||||
this.updateBreakpoints(modelData, []);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private updateBreakpoints(modelData: IDebugEditorModelData, newBreakpoints: IBreakpoint[]): void {
|
||||
const desiredDecorations = this.createBreakpointDecorations(modelData.model, newBreakpoints);
|
||||
try {
|
||||
this.ignoreDecorationsChangedEvent = true;
|
||||
const breakpointDecorationIds = modelData.model.deltaDecorations(modelData.breakpointDecorations.map(bpd => bpd.decorationId), desiredDecorations);
|
||||
modelData.breakpointDecorations = breakpointDecorationIds.map((decorationId, index) => ({
|
||||
decorationId,
|
||||
modelId: newBreakpoints[index].getId(),
|
||||
range: desiredDecorations[index].range
|
||||
}));
|
||||
} finally {
|
||||
this.ignoreDecorationsChangedEvent = false;
|
||||
}
|
||||
}
|
||||
|
||||
private createBreakpointDecorations(model: ITextModel, breakpoints: ReadonlyArray<IBreakpoint>): { range: Range; options: IModelDecorationOptions; }[] {
|
||||
const result: { range: Range; options: IModelDecorationOptions; }[] = [];
|
||||
breakpoints.forEach((breakpoint) => {
|
||||
if (breakpoint.lineNumber <= model.getLineCount()) {
|
||||
const column = model.getLineFirstNonWhitespaceColumn(breakpoint.lineNumber);
|
||||
const range = model.validateRange(
|
||||
breakpoint.column ? new Range(breakpoint.lineNumber, breakpoint.column, breakpoint.lineNumber, breakpoint.column + 1)
|
||||
: new Range(breakpoint.lineNumber, column, breakpoint.lineNumber, column + 1) // Decoration has to have a width #20688
|
||||
);
|
||||
|
||||
result.push({
|
||||
options: this.getBreakpointDecorationOptions(breakpoint),
|
||||
range
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private getBreakpointDecorationOptions(breakpoint: IBreakpoint): IModelDecorationOptions {
|
||||
const { className, message } = getBreakpointMessageAndClassName(this.debugService, breakpoint);
|
||||
let glyphMarginHoverMessage: MarkdownString | undefined;
|
||||
|
||||
if (message) {
|
||||
if (breakpoint.condition || breakpoint.hitCondition) {
|
||||
const modelData = this.modelDataMap.get(breakpoint.uri.toString());
|
||||
const modeId = modelData ? modelData.model.getLanguageIdentifier().language : '';
|
||||
glyphMarginHoverMessage = new MarkdownString().appendCodeblock(modeId, message);
|
||||
} else {
|
||||
glyphMarginHoverMessage = new MarkdownString().appendText(message);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
glyphMarginClassName: className,
|
||||
glyphMarginHoverMessage,
|
||||
stickiness: DebugEditorModelManager.STICKINESS,
|
||||
beforeContentClassName: breakpoint.column ? `debug-breakpoint-column ${className}-column` : undefined
|
||||
};
|
||||
}
|
||||
|
||||
// editor decorations
|
||||
|
||||
// we need a separate decoration for glyph margin, since we do not want it on each line of a multi line statement.
|
||||
private static TOP_STACK_FRAME_MARGIN: IModelDecorationOptions = {
|
||||
glyphMarginClassName: 'debug-top-stack-frame',
|
||||
stickiness: DebugEditorModelManager.STICKINESS
|
||||
};
|
||||
|
||||
private static FOCUSED_STACK_FRAME_MARGIN: IModelDecorationOptions = {
|
||||
glyphMarginClassName: 'debug-focused-stack-frame',
|
||||
stickiness: DebugEditorModelManager.STICKINESS
|
||||
};
|
||||
|
||||
private static TOP_STACK_FRAME_DECORATION: IModelDecorationOptions = {
|
||||
isWholeLine: true,
|
||||
inlineClassName: 'debug-remove-token-colors',
|
||||
className: 'debug-top-stack-frame-line',
|
||||
stickiness: DebugEditorModelManager.STICKINESS
|
||||
};
|
||||
|
||||
private static TOP_STACK_FRAME_INLINE_DECORATION: IModelDecorationOptions = {
|
||||
beforeContentClassName: 'debug-top-stack-frame-column'
|
||||
};
|
||||
|
||||
private static FOCUSED_STACK_FRAME_DECORATION: IModelDecorationOptions = {
|
||||
isWholeLine: true,
|
||||
inlineClassName: 'debug-remove-token-colors',
|
||||
className: 'debug-focused-stack-frame-line',
|
||||
stickiness: DebugEditorModelManager.STICKINESS
|
||||
};
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const topStackFrame = theme.getColor(topStackFrameColor);
|
||||
if (topStackFrame) {
|
||||
collector.addRule(`.monaco-editor .view-overlays .debug-top-stack-frame-line { background: ${topStackFrame}; }`);
|
||||
collector.addRule(`.monaco-editor .view-overlays .debug-top-stack-frame-line { background: ${topStackFrame}; }`);
|
||||
}
|
||||
|
||||
const focusedStackFrame = theme.getColor(focusedStackFrameColor);
|
||||
if (focusedStackFrame) {
|
||||
collector.addRule(`.monaco-editor .view-overlays .debug-focused-stack-frame-line { background: ${focusedStackFrame}; }`);
|
||||
}
|
||||
});
|
||||
|
||||
const topStackFrameColor = registerColor('editor.stackFrameHighlightBackground', { dark: '#ffff0033', light: '#ffff6673', hc: '#fff600' }, localize('topStackFrameLineHighlight', 'Background color for the highlight of line at the top stack frame position.'));
|
||||
const focusedStackFrameColor = registerColor('editor.focusedStackFrameHighlightBackground', { dark: '#7abd7a4d', light: '#cee7ce73', hc: '#cee7ce' }, localize('focusedStackFrameLineHighlight', 'Background color for the highlight of line at focused stack frame position.'));
|
||||
310
src/vs/workbench/contrib/debug/browser/debugHover.ts
Normal file
@@ -0,0 +1,310 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
|
||||
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/contrib/debug/common/debug';
|
||||
import { Expression } from 'vs/workbench/contrib/debug/common/debugModel';
|
||||
import { renderExpressionValue } from 'vs/workbench/contrib/debug/browser/baseDebugView';
|
||||
import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement';
|
||||
import { attachStylerCallback } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { editorHoverBackground, editorHoverBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
||||
import { getExactExpressionStartAndEnd } from 'vs/workbench/contrib/debug/common/debugUtils';
|
||||
import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree';
|
||||
import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
||||
import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
import { IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
|
||||
import { VariablesRenderer } from 'vs/workbench/contrib/debug/browser/variablesView';
|
||||
|
||||
const $ = dom.$;
|
||||
const MAX_TREE_HEIGHT = 324;
|
||||
|
||||
export class DebugHoverWidget implements IContentWidget {
|
||||
|
||||
static readonly ID = 'debug.hoverWidget';
|
||||
// editor.IContentWidget.allowEditorOverflow
|
||||
allowEditorOverflow = true;
|
||||
|
||||
private _isVisible: boolean;
|
||||
private domNode: HTMLElement;
|
||||
private tree: AsyncDataTree<IExpression, IExpression, any>;
|
||||
private showAtPosition: Position | null;
|
||||
private highlightDecorations: string[];
|
||||
private complexValueContainer: HTMLElement;
|
||||
private complexValueTitle: HTMLElement;
|
||||
private valueContainer: HTMLElement;
|
||||
private treeContainer: HTMLElement;
|
||||
private toDispose: lifecycle.IDisposable[];
|
||||
private scrollbar: DomScrollableElement;
|
||||
private dataSource: DebugHoverDataSource;
|
||||
|
||||
constructor(
|
||||
private editor: ICodeEditor,
|
||||
@IDebugService private readonly debugService: IDebugService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IThemeService private readonly themeService: IThemeService,
|
||||
) {
|
||||
this.toDispose = [];
|
||||
|
||||
this._isVisible = false;
|
||||
this.showAtPosition = null;
|
||||
this.highlightDecorations = [];
|
||||
}
|
||||
|
||||
private create(): 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.dataSource = new DebugHoverDataSource();
|
||||
|
||||
this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, this.treeContainer, new DebugHoverDelegate(), [this.instantiationService.createInstance(VariablesRenderer)],
|
||||
this.dataSource, {
|
||||
ariaLabel: nls.localize('treeAriaLabel', "Debug Hover"),
|
||||
accessibilityProvider: new DebugHoverAccessibilityProvider(),
|
||||
mouseSupport: false,
|
||||
horizontalScrolling: true
|
||||
}) as any as AsyncDataTree<IExpression, IExpression, any>;
|
||||
|
||||
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.editor.applyFontInfo(this.domNode);
|
||||
|
||||
this.toDispose.push(attachStylerCallback(this.themeService, { editorHoverBackground, editorHoverBorder }, colors => {
|
||||
if (colors.editorHoverBackground) {
|
||||
this.domNode.style.backgroundColor = colors.editorHoverBackground.toString();
|
||||
} else {
|
||||
this.domNode.style.backgroundColor = null;
|
||||
}
|
||||
if (colors.editorHoverBorder) {
|
||||
this.domNode.style.border = `1px solid ${colors.editorHoverBorder}`;
|
||||
} else {
|
||||
this.domNode.style.border = null;
|
||||
}
|
||||
}));
|
||||
this.toDispose.push(this.tree.onDidChangeContentHeight(() => this.layoutTreeAndContainer()));
|
||||
|
||||
this.registerListeners();
|
||||
this.editor.addContentWidget(this);
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
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);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
isVisible(): boolean {
|
||||
return this._isVisible;
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return DebugHoverWidget.ID;
|
||||
}
|
||||
|
||||
getDomNode(): HTMLElement {
|
||||
return this.domNode;
|
||||
}
|
||||
|
||||
showAt(range: Range, focus: boolean): Promise<void> {
|
||||
const pos = range.getStartPosition();
|
||||
|
||||
const session = this.debugService.getViewModel().focusedSession;
|
||||
if (!this.editor.hasModel()) {
|
||||
return Promise.resolve(this.hide());
|
||||
}
|
||||
|
||||
const lineContent = this.editor.getModel().getLineContent(pos.lineNumber);
|
||||
const { start, end } = getExactExpressionStartAndEnd(lineContent, range.startColumn, range.endColumn);
|
||||
// use regex to extract the sub-expression #9821
|
||||
const matchingExpression = lineContent.substring(start - 1, end);
|
||||
if (!matchingExpression || !session) {
|
||||
return Promise.resolve(this.hide());
|
||||
}
|
||||
|
||||
let promise: Promise<IExpression | undefined>;
|
||||
if (session.capabilities.supportsEvaluateForHovers) {
|
||||
const result = new Expression(matchingExpression);
|
||||
promise = result.evaluate(session, this.debugService.getViewModel().focusedStackFrame, 'hover').then(() => result);
|
||||
} else {
|
||||
promise = this.findExpressionInStackFrame(coalesce(matchingExpression.split('.').map(word => word.trim())));
|
||||
}
|
||||
|
||||
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, start, pos.lineNumber, start + matchingExpression.length),
|
||||
options: DebugHoverWidget._HOVER_HIGHLIGHT_DECORATION_OPTIONS
|
||||
}]);
|
||||
|
||||
return this.doShow(pos, expression, focus);
|
||||
});
|
||||
}
|
||||
|
||||
private static _HOVER_HIGHLIGHT_DECORATION_OPTIONS = ModelDecorationOptions.register({
|
||||
className: 'hoverHighlight'
|
||||
});
|
||||
|
||||
private doFindExpression(container: IExpressionContainer, namesToFind: string[]): Promise<IExpression | null> {
|
||||
if (!container) {
|
||||
return Promise.resolve(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[]): Promise<IExpression | undefined> {
|
||||
return this.debugService.getViewModel().focusedStackFrame!.getScopes()
|
||||
.then(scopes => scopes.filter(s => !s.expensive))
|
||||
.then(scopes => Promise.all(scopes.map(scope => this.doFindExpression(scope, namesToFind))))
|
||||
.then(coalesce)
|
||||
// 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] : undefined);
|
||||
}
|
||||
|
||||
private doShow(position: Position, expression: IExpression, focus: boolean, forceValueHover = false): Promise<void> {
|
||||
if (!this.domNode) {
|
||||
this.create();
|
||||
}
|
||||
|
||||
this.showAtPosition = position;
|
||||
this._isVisible = true;
|
||||
|
||||
if (!expression.hasChildren || forceValueHover) {
|
||||
this.complexValueContainer.hidden = true;
|
||||
this.valueContainer.hidden = false;
|
||||
renderExpressionValue(expression, this.valueContainer, {
|
||||
showChanged: false,
|
||||
preserveWhitespace: true,
|
||||
colorize: true
|
||||
});
|
||||
this.valueContainer.title = '';
|
||||
this.editor.layoutContentWidget(this);
|
||||
this.scrollbar.scanDomNode();
|
||||
if (focus) {
|
||||
this.editor.render();
|
||||
this.valueContainer.focus();
|
||||
}
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
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.layoutTreeAndContainer();
|
||||
this.editor.layoutContentWidget(this);
|
||||
this.scrollbar.scanDomNode();
|
||||
if (focus) {
|
||||
this.editor.render();
|
||||
this.tree.domFocus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private layoutTreeAndContainer(): void {
|
||||
const treeHeight = Math.min(MAX_TREE_HEIGHT, this.tree.contentHeight);
|
||||
this.treeContainer.style.height = `${treeHeight}px`;
|
||||
this.tree.layout(treeHeight, 324);
|
||||
}
|
||||
|
||||
hide(): void {
|
||||
if (!this._isVisible) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isVisible = false;
|
||||
this.editor.deltaDecorations(this.highlightDecorations, []);
|
||||
this.highlightDecorations = [];
|
||||
this.editor.layoutContentWidget(this);
|
||||
this.editor.focus();
|
||||
}
|
||||
|
||||
getPosition(): IContentWidgetPosition | null {
|
||||
return this._isVisible ? {
|
||||
position: this.showAtPosition,
|
||||
preference: [
|
||||
ContentWidgetPositionPreference.ABOVE,
|
||||
ContentWidgetPositionPreference.BELOW
|
||||
]
|
||||
} : null;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.toDispose = lifecycle.dispose(this.toDispose);
|
||||
}
|
||||
}
|
||||
|
||||
class DebugHoverAccessibilityProvider implements IAccessibilityProvider<IExpression> {
|
||||
getAriaLabel(element: IExpression): string {
|
||||
return nls.localize('variableAriaLabel', "{0} value {1}, variables, debug", element.name, element.value);
|
||||
}
|
||||
}
|
||||
|
||||
class DebugHoverDataSource implements IAsyncDataSource<IExpression, IExpression> {
|
||||
|
||||
hasChildren(element: IExpression): boolean {
|
||||
return element.hasChildren;
|
||||
}
|
||||
|
||||
getChildren(element: IExpression): Promise<IExpression[]> {
|
||||
return element.getChildren();
|
||||
}
|
||||
}
|
||||
|
||||
class DebugHoverDelegate implements IListVirtualDelegate<IExpression> {
|
||||
getHeight(element: IExpression): number {
|
||||
return 18;
|
||||
}
|
||||
|
||||
getTemplateId(element: IExpression): string {
|
||||
return VariablesRenderer.ID;
|
||||
}
|
||||
}
|
||||
139
src/vs/workbench/contrib/debug/browser/debugQuickOpen.ts
Normal file
@@ -0,0 +1,139 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { IDebugService, ILaunch } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { StartAction } from 'vs/workbench/contrib/debug/browser/debugActions';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { QuickOpenEntry, QuickOpenModel, QuickOpenEntryGroup, IHighlight } from 'vs/base/parts/quickopen/browser/quickOpenModel';
|
||||
import { Mode, IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen';
|
||||
import { QuickOpenHandler } from 'vs/workbench/browser/quickopen';
|
||||
import { matchesFuzzy } from 'vs/base/common/filters';
|
||||
|
||||
class AddConfigEntry extends QuickOpenEntry {
|
||||
|
||||
constructor(private label: string, private launch: ILaunch, private commandService: ICommandService, private contextService: IWorkspaceContextService, highlights: IHighlight[] = []) {
|
||||
super(highlights);
|
||||
}
|
||||
|
||||
public getLabel(): string {
|
||||
return this.label;
|
||||
}
|
||||
|
||||
public getDescription(): string {
|
||||
return this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? this.launch.name : '';
|
||||
}
|
||||
|
||||
public getAriaLabel(): string {
|
||||
return nls.localize('entryAriaLabel', "{0}, debug", this.getLabel());
|
||||
}
|
||||
|
||||
public run(mode: Mode): boolean {
|
||||
if (mode === Mode.PREVIEW) {
|
||||
return false;
|
||||
}
|
||||
this.commandService.executeCommand('debug.addConfiguration', this.launch.uri.toString());
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class StartDebugEntry extends QuickOpenEntry {
|
||||
|
||||
constructor(private debugService: IDebugService, private contextService: IWorkspaceContextService, private notificationService: INotificationService, private launch: ILaunch, private configurationName: string, highlights: IHighlight[] = []) {
|
||||
super(highlights);
|
||||
}
|
||||
|
||||
public getLabel(): string {
|
||||
return this.configurationName;
|
||||
}
|
||||
|
||||
public getDescription(): string {
|
||||
return this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? this.launch.name : '';
|
||||
}
|
||||
|
||||
public getAriaLabel(): string {
|
||||
return nls.localize('entryAriaLabel', "{0}, debug", this.getLabel());
|
||||
}
|
||||
|
||||
public run(mode: Mode): boolean {
|
||||
if (mode === Mode.PREVIEW || !StartAction.isEnabled(this.debugService)) {
|
||||
return false;
|
||||
}
|
||||
// Run selected debug configuration
|
||||
this.debugService.getConfigurationManager().selectConfiguration(this.launch, this.configurationName);
|
||||
this.debugService.startDebugging(this.launch).then(undefined, e => this.notificationService.error(e));
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export class DebugQuickOpenHandler extends QuickOpenHandler {
|
||||
|
||||
public static readonly ID = 'workbench.picker.launch';
|
||||
|
||||
private autoFocusIndex: number;
|
||||
|
||||
constructor(
|
||||
@IDebugService private readonly debugService: IDebugService,
|
||||
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@INotificationService private readonly notificationService: INotificationService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
public getAriaLabel(): string {
|
||||
return nls.localize('debugAriaLabel', "Type a name of a launch configuration to run.");
|
||||
}
|
||||
|
||||
public getResults(input: string, token: CancellationToken): Promise<QuickOpenModel> {
|
||||
const configurations: QuickOpenEntry[] = [];
|
||||
|
||||
const configManager = this.debugService.getConfigurationManager();
|
||||
const launches = configManager.getLaunches();
|
||||
for (let launch of launches) {
|
||||
launch.getConfigurationNames().map(config => ({ config: config, highlights: matchesFuzzy(input, config, true) || undefined }))
|
||||
.filter(({ highlights }) => !!highlights)
|
||||
.forEach(({ config, highlights }) => {
|
||||
if (launch === configManager.selectedConfiguration.launch && config === configManager.selectedConfiguration.name) {
|
||||
this.autoFocusIndex = configurations.length;
|
||||
}
|
||||
configurations.push(new StartDebugEntry(this.debugService, this.contextService, this.notificationService, launch, config, highlights));
|
||||
});
|
||||
}
|
||||
launches.filter(l => !l.hidden).forEach((l, index) => {
|
||||
|
||||
const label = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE ? nls.localize("addConfigTo", "Add Config ({0})...", l.name) : nls.localize('addConfiguration', "Add Configuration...");
|
||||
const entry = new AddConfigEntry(label, l, this.commandService, this.contextService, matchesFuzzy(input, label, true) || undefined);
|
||||
if (index === 0) {
|
||||
configurations.push(new QuickOpenEntryGroup(entry, undefined, true));
|
||||
} else {
|
||||
configurations.push(entry);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return Promise.resolve(new QuickOpenModel(configurations));
|
||||
}
|
||||
|
||||
public getAutoFocus(input: string): IAutoFocus {
|
||||
return {
|
||||
autoFocusFirstEntry: !!input,
|
||||
autoFocusIndex: this.autoFocusIndex
|
||||
};
|
||||
}
|
||||
|
||||
public getEmptyLabel(searchString: string): string {
|
||||
if (searchString.length > 0) {
|
||||
return nls.localize('noConfigurationsMatching', "No debug configurations matching");
|
||||
}
|
||||
|
||||
return nls.localize('noConfigurationsFound', "No debug configurations found. Please create a 'launch.json' file.");
|
||||
}
|
||||
}
|
||||
100
src/vs/workbench/contrib/debug/browser/debugStatus.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 dom from 'vs/base/browser/dom';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IQuickOpenService } from 'vs/platform/quickOpen/common/quickOpen';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IStatusbarItem } from 'vs/workbench/browser/parts/statusbar/statusbar';
|
||||
import { IDebugService, State, IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { Themable, STATUS_BAR_FOREGROUND } from 'vs/workbench/common/theme';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { STATUS_BAR_DEBUGGING_FOREGROUND, isStatusbarInDebugMode } from 'vs/workbench/contrib/debug/browser/statusbarColorProvider';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
export class DebugStatus extends Themable implements IStatusbarItem {
|
||||
private container: HTMLElement;
|
||||
private statusBarItem: HTMLElement;
|
||||
private label: HTMLElement;
|
||||
private icon: HTMLElement;
|
||||
private showInStatusBar: string;
|
||||
|
||||
constructor(
|
||||
@IQuickOpenService private readonly quickOpenService: IQuickOpenService,
|
||||
@IDebugService private readonly debugService: IDebugService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IConfigurationService configurationService: IConfigurationService
|
||||
) {
|
||||
super(themeService);
|
||||
this._register(this.debugService.getConfigurationManager().onDidSelectConfiguration(e => {
|
||||
this.setLabel();
|
||||
}));
|
||||
this._register(this.debugService.onDidChangeState(state => {
|
||||
if (state !== State.Inactive && this.showInStatusBar === 'onFirstSessionStart') {
|
||||
this.doRender();
|
||||
}
|
||||
}));
|
||||
this.showInStatusBar = configurationService.getValue<IDebugConfiguration>('debug').showInStatusBar;
|
||||
this._register(configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('debug.showInStatusBar')) {
|
||||
this.showInStatusBar = configurationService.getValue<IDebugConfiguration>('debug').showInStatusBar;
|
||||
if (this.showInStatusBar === 'always') {
|
||||
this.doRender();
|
||||
}
|
||||
if (this.statusBarItem) {
|
||||
dom.toggleClass(this.statusBarItem, 'hidden', this.showInStatusBar === 'never');
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
protected updateStyles(): void {
|
||||
super.updateStyles();
|
||||
if (this.icon) {
|
||||
if (isStatusbarInDebugMode(this.debugService)) {
|
||||
this.icon.style.backgroundColor = this.getColor(STATUS_BAR_DEBUGGING_FOREGROUND);
|
||||
} else {
|
||||
this.icon.style.backgroundColor = this.getColor(STATUS_BAR_FOREGROUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public render(container: HTMLElement): IDisposable {
|
||||
this.container = container;
|
||||
if (this.showInStatusBar === 'always') {
|
||||
this.doRender();
|
||||
}
|
||||
// noop, we render when we decide is best
|
||||
return this;
|
||||
}
|
||||
|
||||
private doRender(): void {
|
||||
if (!this.statusBarItem && this.container) {
|
||||
this.statusBarItem = dom.append(this.container, $('.debug-statusbar-item'));
|
||||
this._register(dom.addDisposableListener(this.statusBarItem, 'click', () => this.quickOpenService.show('debug ')));
|
||||
this.statusBarItem.title = nls.localize('selectAndStartDebug', "Select and start debug configuration");
|
||||
const a = dom.append(this.statusBarItem, $('a'));
|
||||
this.icon = dom.append(a, $('.icon'));
|
||||
this.label = dom.append(a, $('span.label'));
|
||||
this.setLabel();
|
||||
}
|
||||
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
private setLabel(): void {
|
||||
if (this.label && this.statusBarItem) {
|
||||
const manager = this.debugService.getConfigurationManager();
|
||||
const name = manager.selectedConfiguration.name || '';
|
||||
const nameAndLaunchPresent = name && manager.selectedConfiguration.launch;
|
||||
dom.toggleClass(this.statusBarItem, 'hidden', this.showInStatusBar === 'never' || !nameAndLaunchPresent);
|
||||
if (nameAndLaunchPresent) {
|
||||
this.label.textContent = manager.getLaunches().length > 1 ? `${name} (${manager.selectedConfiguration.launch!.name})` : name;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
285
src/vs/workbench/contrib/debug/browser/debugToolbar.ts
Normal file
@@ -0,0 +1,285 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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!./media/debugToolbar';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import * as browser from 'vs/base/browser/browser';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import { IAction, IRunEvent } from 'vs/base/common/actions';
|
||||
import { ActionBar, ActionsOrientation, Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { IDebugConfiguration, IDebugService, State } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { FocusSessionActionItem } from 'vs/workbench/contrib/debug/browser/debugActionItems';
|
||||
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { Themable } from 'vs/workbench/common/theme';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { registerColor, contrastBorder, widgetShadow } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { localize } from 'vs/nls';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { fillInActionBarActions, MenuItemActionItem } from 'vs/platform/actions/browser/menuItemActionItem';
|
||||
import { IMenu, IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { FocusSessionAction } from 'vs/workbench/contrib/debug/browser/debugActions';
|
||||
|
||||
const DEBUG_TOOLBAR_POSITION_KEY = 'debug.actionswidgetposition';
|
||||
const DEBUG_TOOLBAR_Y_KEY = 'debug.actionswidgety';
|
||||
|
||||
export const debugToolBarBackground = registerColor('debugToolBar.background', {
|
||||
dark: '#333333',
|
||||
light: '#F3F3F3',
|
||||
hc: '#000000'
|
||||
}, localize('debugToolBarBackground', "Debug toolbar background color."));
|
||||
export const debugToolBarBorder = registerColor('debugToolBar.border', {
|
||||
dark: null,
|
||||
light: null,
|
||||
hc: null
|
||||
}, localize('debugToolBarBorder', "Debug toolbar border color."));
|
||||
|
||||
export class DebugToolbar extends Themable implements IWorkbenchContribution {
|
||||
|
||||
private $el: HTMLElement;
|
||||
private dragArea: HTMLElement;
|
||||
private actionBar: ActionBar;
|
||||
private activeActions: IAction[];
|
||||
private updateScheduler: RunOnceScheduler;
|
||||
private debugToolbarMenu: IMenu;
|
||||
|
||||
private isVisible: boolean;
|
||||
private isBuilt: boolean;
|
||||
|
||||
constructor(
|
||||
@INotificationService private readonly notificationService: INotificationService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IDebugService private readonly debugService: IDebugService,
|
||||
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IKeybindingService private readonly keybindingService: IKeybindingService,
|
||||
@IContextViewService contextViewService: IContextViewService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IMenuService menuService: IMenuService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService
|
||||
) {
|
||||
super(themeService);
|
||||
|
||||
this.$el = dom.$('div.debug-toolbar');
|
||||
this.$el.style.top = `${layoutService.getTitleBarOffset()}px`;
|
||||
|
||||
this.dragArea = dom.append(this.$el, dom.$('div.drag-area'));
|
||||
|
||||
const actionBarContainer = dom.append(this.$el, dom.$('div.action-bar-container'));
|
||||
this.debugToolbarMenu = menuService.createMenu(MenuId.DebugToolbar, contextKeyService);
|
||||
this.toDispose.push(this.debugToolbarMenu);
|
||||
|
||||
this.activeActions = [];
|
||||
this.actionBar = this._register(new ActionBar(actionBarContainer, {
|
||||
orientation: ActionsOrientation.HORIZONTAL,
|
||||
actionItemProvider: (action: IAction) => {
|
||||
if (action.id === FocusSessionAction.ID) {
|
||||
return new FocusSessionActionItem(action, this.debugService, this.themeService, contextViewService);
|
||||
}
|
||||
if (action instanceof MenuItemAction) {
|
||||
return new MenuItemActionItem(action, this.keybindingService, this.notificationService, contextMenuService);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}));
|
||||
|
||||
this.updateScheduler = this._register(new RunOnceScheduler(() => {
|
||||
const state = this.debugService.state;
|
||||
const toolBarLocation = this.configurationService.getValue<IDebugConfiguration>('debug').toolBarLocation;
|
||||
if (state === State.Inactive || toolBarLocation === 'docked' || toolBarLocation === 'hidden') {
|
||||
return this.hide();
|
||||
}
|
||||
|
||||
const actions = DebugToolbar.getActions(this.debugToolbarMenu, this.debugService, this.instantiationService);
|
||||
if (!arrays.equals(actions, this.activeActions, (first, second) => first.id === second.id)) {
|
||||
this.actionBar.clear();
|
||||
this.actionBar.push(actions, { icon: true, label: false });
|
||||
this.activeActions = actions;
|
||||
}
|
||||
this.show();
|
||||
}, 20));
|
||||
|
||||
this.updateStyles();
|
||||
|
||||
this.registerListeners();
|
||||
|
||||
this.hide();
|
||||
this.isBuilt = false;
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this._register(this.debugService.onDidChangeState(() => this.updateScheduler.schedule()));
|
||||
this._register(this.debugService.getViewModel().onDidFocusSession(() => this.updateScheduler.schedule()));
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => this.onDidConfigurationChange(e)));
|
||||
this._register(this.actionBar.actionRunner.onDidRun((e: IRunEvent) => {
|
||||
// check for error
|
||||
if (e.error && !errors.isPromiseCanceledError(e.error)) {
|
||||
this.notificationService.error(e.error);
|
||||
}
|
||||
|
||||
// log in telemetry
|
||||
if (this.telemetryService) {
|
||||
/* __GDPR__
|
||||
"workbenchActionExecuted" : {
|
||||
"id" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"from": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
this.telemetryService.publicLog('workbenchActionExecuted', { id: e.action.id, from: 'debugActionsWidget' });
|
||||
}
|
||||
}));
|
||||
this._register(dom.addDisposableListener(window, dom.EventType.RESIZE, () => this.setCoordinates()));
|
||||
|
||||
this._register(dom.addDisposableListener(this.dragArea, dom.EventType.MOUSE_UP, (event: MouseEvent) => {
|
||||
const mouseClickEvent = new StandardMouseEvent(event);
|
||||
if (mouseClickEvent.detail === 2) {
|
||||
// double click on debug bar centers it again #8250
|
||||
const widgetWidth = this.$el.clientWidth;
|
||||
this.setCoordinates(0.5 * window.innerWidth - 0.5 * widgetWidth, 0);
|
||||
this.storePosition();
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(dom.addDisposableListener(this.dragArea, dom.EventType.MOUSE_DOWN, (event: MouseEvent) => {
|
||||
dom.addClass(this.dragArea, 'dragged');
|
||||
|
||||
const mouseMoveListener = dom.addDisposableListener(window, 'mousemove', (e: MouseEvent) => {
|
||||
const mouseMoveEvent = new StandardMouseEvent(e);
|
||||
// Prevent default to stop editor selecting text #8524
|
||||
mouseMoveEvent.preventDefault();
|
||||
// Reduce x by width of drag handle to reduce jarring #16604
|
||||
this.setCoordinates(mouseMoveEvent.posx - 14, mouseMoveEvent.posy - this.layoutService.getTitleBarOffset());
|
||||
});
|
||||
|
||||
const mouseUpListener = dom.addDisposableListener(window, 'mouseup', (e: MouseEvent) => {
|
||||
this.storePosition();
|
||||
dom.removeClass(this.dragArea, 'dragged');
|
||||
|
||||
mouseMoveListener.dispose();
|
||||
mouseUpListener.dispose();
|
||||
});
|
||||
}));
|
||||
|
||||
this._register(this.layoutService.onTitleBarVisibilityChange(() => this.setYCoordinate()));
|
||||
this._register(browser.onDidChangeZoomLevel(() => this.setYCoordinate()));
|
||||
}
|
||||
|
||||
private storePosition(): void {
|
||||
const left = dom.getComputedStyle(this.$el).left;
|
||||
if (left) {
|
||||
const position = parseFloat(left) / window.innerWidth;
|
||||
this.storageService.store(DEBUG_TOOLBAR_POSITION_KEY, position, StorageScope.GLOBAL);
|
||||
}
|
||||
}
|
||||
|
||||
protected updateStyles(): void {
|
||||
super.updateStyles();
|
||||
|
||||
if (this.$el) {
|
||||
this.$el.style.backgroundColor = this.getColor(debugToolBarBackground);
|
||||
|
||||
const widgetShadowColor = this.getColor(widgetShadow);
|
||||
this.$el.style.boxShadow = widgetShadowColor ? `0 5px 8px ${widgetShadowColor}` : null;
|
||||
|
||||
const contrastBorderColor = this.getColor(contrastBorder);
|
||||
const borderColor = this.getColor(debugToolBarBorder);
|
||||
|
||||
if (contrastBorderColor) {
|
||||
this.$el.style.border = `1px solid ${contrastBorderColor}`;
|
||||
} else {
|
||||
this.$el.style.border = borderColor ? `solid ${borderColor}` : 'none';
|
||||
this.$el.style.border = '1px 0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private setYCoordinate(y = 0): void {
|
||||
const titlebarOffset = this.layoutService.getTitleBarOffset();
|
||||
this.$el.style.top = `${titlebarOffset + y}px`;
|
||||
}
|
||||
|
||||
private setCoordinates(x?: number, y?: number): void {
|
||||
if (!this.isVisible) {
|
||||
return;
|
||||
}
|
||||
const widgetWidth = this.$el.clientWidth;
|
||||
if (x === undefined) {
|
||||
const positionPercentage = this.storageService.get(DEBUG_TOOLBAR_POSITION_KEY, StorageScope.GLOBAL);
|
||||
x = positionPercentage !== undefined ? parseFloat(positionPercentage) * window.innerWidth : (0.5 * window.innerWidth - 0.5 * widgetWidth);
|
||||
}
|
||||
|
||||
x = Math.max(0, Math.min(x, window.innerWidth - widgetWidth)); // do not allow the widget to overflow on the right
|
||||
this.$el.style.left = `${x}px`;
|
||||
|
||||
if (y === undefined) {
|
||||
y = this.storageService.getNumber(DEBUG_TOOLBAR_Y_KEY, StorageScope.GLOBAL, 0);
|
||||
}
|
||||
const titleAreaHeight = 35;
|
||||
if ((y < titleAreaHeight / 2) || (y > titleAreaHeight + titleAreaHeight / 2)) {
|
||||
const moveToTop = y < titleAreaHeight;
|
||||
this.setYCoordinate(moveToTop ? 0 : titleAreaHeight);
|
||||
this.storageService.store(DEBUG_TOOLBAR_Y_KEY, moveToTop ? 0 : 2 * titleAreaHeight, StorageScope.GLOBAL);
|
||||
}
|
||||
}
|
||||
|
||||
private onDidConfigurationChange(event: IConfigurationChangeEvent): void {
|
||||
if (event.affectsConfiguration('debug.hideActionBar') || event.affectsConfiguration('debug.toolBarLocation')) {
|
||||
this.updateScheduler.schedule();
|
||||
}
|
||||
}
|
||||
|
||||
private show(): void {
|
||||
if (this.isVisible) {
|
||||
this.setCoordinates();
|
||||
return;
|
||||
}
|
||||
if (!this.isBuilt) {
|
||||
this.isBuilt = true;
|
||||
this.layoutService.getWorkbenchElement().appendChild(this.$el);
|
||||
}
|
||||
|
||||
this.isVisible = true;
|
||||
dom.show(this.$el);
|
||||
this.setCoordinates();
|
||||
}
|
||||
|
||||
private hide(): void {
|
||||
this.isVisible = false;
|
||||
dom.hide(this.$el);
|
||||
}
|
||||
|
||||
public static getActions(menu: IMenu, debugService: IDebugService, instantiationService: IInstantiationService): IAction[] {
|
||||
const actions: IAction[] = [];
|
||||
fillInActionBarActions(menu, undefined, actions, () => false);
|
||||
if (debugService.getViewModel().isMultiSessionView()) {
|
||||
actions.push(instantiationService.createInstance(FocusSessionAction, FocusSessionAction.ID, FocusSessionAction.LABEL));
|
||||
}
|
||||
|
||||
return actions.filter(a => !(a instanceof Separator)); // do not render separators for now
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
super.dispose();
|
||||
|
||||
if (this.$el) {
|
||||
this.$el.remove();
|
||||
delete this.$el;
|
||||
}
|
||||
}
|
||||
}
|
||||
195
src/vs/workbench/contrib/debug/browser/debugViewlet.ts
Normal file
@@ -0,0 +1,195 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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!./media/debugViewlet';
|
||||
import * as nls from 'vs/nls';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import * as DOM from 'vs/base/browser/dom';
|
||||
import { IActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { ViewContainerViewlet } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { IDebugService, VIEWLET_ID, State, BREAKPOINTS_VIEW_ID, IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { StartAction, ToggleReplAction, ConfigureAction, SelectAndStartAction, FocusSessionAction } from 'vs/workbench/contrib/debug/browser/debugActions';
|
||||
import { StartDebugActionItem, FocusSessionActionItem } from 'vs/workbench/contrib/debug/browser/debugActionItems';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IProgressService, IProgressRunner } from 'vs/platform/progress/common/progress';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { DebugToolbar } from 'vs/workbench/contrib/debug/browser/debugToolbar';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet';
|
||||
import { IMenu, MenuId, IMenuService, MenuItemAction } from 'vs/platform/actions/common/actions';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { MenuItemActionItem } from 'vs/platform/actions/browser/menuItemActionItem';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
|
||||
export class DebugViewlet extends ViewContainerViewlet {
|
||||
|
||||
private startDebugActionItem: StartDebugActionItem;
|
||||
private progressRunner: IProgressRunner;
|
||||
private breakpointView: ViewletPanel;
|
||||
private panelListeners = new Map<string, IDisposable>();
|
||||
private debugToolbarMenu: IMenu;
|
||||
|
||||
constructor(
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IProgressService private readonly progressService: IProgressService,
|
||||
@IDebugService private readonly debugService: IDebugService,
|
||||
@IInstantiationService instantiationService: IInstantiationService,
|
||||
@IWorkspaceContextService contextService: IWorkspaceContextService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IKeybindingService private readonly keybindingService: IKeybindingService,
|
||||
@IContextViewService private readonly contextViewService: IContextViewService,
|
||||
@IMenuService private readonly menuService: IMenuService,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@INotificationService private readonly notificationService: INotificationService
|
||||
) {
|
||||
super(VIEWLET_ID, `${VIEWLET_ID}.state`, false, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService);
|
||||
|
||||
this._register(this.debugService.onDidChangeState(state => this.onDebugServiceStateChange(state)));
|
||||
this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateTitleArea()));
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('debug.toolBarLocation')) {
|
||||
this.updateTitleArea();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
create(parent: HTMLElement): void {
|
||||
super.create(parent);
|
||||
DOM.addClass(parent, 'debug-viewlet');
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
super.focus();
|
||||
|
||||
if (this.startDebugActionItem) {
|
||||
this.startDebugActionItem.focus();
|
||||
}
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get startAction(): StartAction {
|
||||
return this._register(this.instantiationService.createInstance(StartAction, StartAction.ID, StartAction.LABEL));
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get configureAction(): ConfigureAction {
|
||||
return this._register(this.instantiationService.createInstance(ConfigureAction, ConfigureAction.ID, ConfigureAction.LABEL));
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get toggleReplAction(): ToggleReplAction {
|
||||
return this._register(this.instantiationService.createInstance(ToggleReplAction, ToggleReplAction.ID, ToggleReplAction.LABEL));
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get selectAndStartAction(): SelectAndStartAction {
|
||||
return this._register(this.instantiationService.createInstance(SelectAndStartAction, SelectAndStartAction.ID, nls.localize('startAdditionalSession', "Start Additional Session")));
|
||||
}
|
||||
|
||||
getActions(): IAction[] {
|
||||
if (this.showInitialDebugActions) {
|
||||
return [this.startAction, this.configureAction, this.toggleReplAction];
|
||||
}
|
||||
|
||||
if (!this.debugToolbarMenu) {
|
||||
this.debugToolbarMenu = this.menuService.createMenu(MenuId.DebugToolbar, this.contextKeyService);
|
||||
this.toDispose.push(this.debugToolbarMenu);
|
||||
}
|
||||
return DebugToolbar.getActions(this.debugToolbarMenu, this.debugService, this.instantiationService);
|
||||
}
|
||||
|
||||
get showInitialDebugActions(): boolean {
|
||||
const state = this.debugService.state;
|
||||
return state === State.Inactive || this.configurationService.getValue<IDebugConfiguration>('debug').toolBarLocation !== 'docked';
|
||||
}
|
||||
|
||||
getSecondaryActions(): IAction[] {
|
||||
if (this.showInitialDebugActions) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return [this.selectAndStartAction, this.configureAction, this.toggleReplAction];
|
||||
}
|
||||
|
||||
getActionItem(action: IAction): IActionItem | null {
|
||||
if (action.id === StartAction.ID) {
|
||||
this.startDebugActionItem = this.instantiationService.createInstance(StartDebugActionItem, null, action);
|
||||
return this.startDebugActionItem;
|
||||
}
|
||||
if (action.id === FocusSessionAction.ID) {
|
||||
return new FocusSessionActionItem(action, this.debugService, this.themeService, this.contextViewService);
|
||||
}
|
||||
if (action instanceof MenuItemAction) {
|
||||
return new MenuItemActionItem(action, this.keybindingService, this.notificationService, this.contextMenuService);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
focusView(id: string): void {
|
||||
const view = this.getView(id);
|
||||
if (view) {
|
||||
view.focus();
|
||||
}
|
||||
}
|
||||
|
||||
private onDebugServiceStateChange(state: State): void {
|
||||
if (this.progressRunner) {
|
||||
this.progressRunner.done();
|
||||
}
|
||||
|
||||
if (state === State.Initializing) {
|
||||
this.progressRunner = this.progressService.show(true);
|
||||
}
|
||||
|
||||
if (this.configurationService.getValue<IDebugConfiguration>('debug').toolBarLocation === 'docked') {
|
||||
this.updateTitleArea();
|
||||
}
|
||||
}
|
||||
|
||||
addPanels(panels: { panel: ViewletPanel, size: number, index?: number }[]): void {
|
||||
super.addPanels(panels);
|
||||
|
||||
for (const { panel } of panels) {
|
||||
// attach event listener to
|
||||
if (panel.id === BREAKPOINTS_VIEW_ID) {
|
||||
this.breakpointView = panel;
|
||||
this.updateBreakpointsMaxSize();
|
||||
} else {
|
||||
this.panelListeners.set(panel.id, panel.onDidChange(() => this.updateBreakpointsMaxSize()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
removePanels(panels: ViewletPanel[]): void {
|
||||
super.removePanels(panels);
|
||||
for (const panel of panels) {
|
||||
dispose(this.panelListeners.get(panel.id));
|
||||
this.panelListeners.delete(panel.id);
|
||||
}
|
||||
}
|
||||
|
||||
private updateBreakpointsMaxSize(): void {
|
||||
if (this.breakpointView) {
|
||||
// We need to update the breakpoints view since all other views are collapsed #25384
|
||||
const allOtherCollapsed = this.panels.every(view => !view.isExpanded() || view === this.breakpointView);
|
||||
this.breakpointView.maximumBodySize = allOtherCollapsed ? Number.POSITIVE_INFINITY : this.breakpointView.minimumBodySize;
|
||||
}
|
||||
}
|
||||
}
|
||||
99
src/vs/workbench/contrib/debug/browser/exceptionWidget.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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!./media/exceptionWidget';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/zoneWidget';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { IExceptionInfo } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
|
||||
import { Color } from 'vs/base/common/color';
|
||||
import { registerColor } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector';
|
||||
const $ = dom.$;
|
||||
|
||||
// theming
|
||||
|
||||
export const debugExceptionWidgetBorder = registerColor('debugExceptionWidget.border', { dark: '#a31515', light: '#a31515', hc: '#a31515' }, nls.localize('debugExceptionWidgetBorder', 'Exception widget border color.'));
|
||||
export const debugExceptionWidgetBackground = registerColor('debugExceptionWidget.background', { dark: '#420b0d', light: '#f1dfde', hc: '#420b0d' }, nls.localize('debugExceptionWidgetBackground', 'Exception widget background color.'));
|
||||
|
||||
export class ExceptionWidget extends ZoneWidget {
|
||||
|
||||
private _backgroundColor?: Color;
|
||||
|
||||
constructor(editor: ICodeEditor, private exceptionInfo: IExceptionInfo,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
) {
|
||||
super(editor, { showFrame: true, showArrow: true, frameWidth: 1, className: 'exception-widget-container' });
|
||||
|
||||
this._backgroundColor = Color.white;
|
||||
|
||||
this._applyTheme(themeService.getTheme());
|
||||
this._disposables.push(themeService.onThemeChange(this._applyTheme.bind(this)));
|
||||
|
||||
|
||||
this.create();
|
||||
const onDidLayoutChangeScheduler = new RunOnceScheduler(() => this._doLayout(undefined, undefined), 50);
|
||||
this._disposables.push(this.editor.onDidLayoutChange(() => onDidLayoutChangeScheduler.schedule()));
|
||||
this._disposables.push(onDidLayoutChangeScheduler);
|
||||
}
|
||||
|
||||
private _applyTheme(theme: ITheme): void {
|
||||
this._backgroundColor = theme.getColor(debugExceptionWidgetBackground);
|
||||
const frameColor = theme.getColor(debugExceptionWidgetBorder);
|
||||
this.style({
|
||||
arrowColor: frameColor,
|
||||
frameColor: frameColor
|
||||
}); // style() will trigger _applyStyles
|
||||
}
|
||||
|
||||
protected _applyStyles(): void {
|
||||
if (this.container) {
|
||||
this.container.style.backgroundColor = this._backgroundColor ? this._backgroundColor.toString() : '';
|
||||
}
|
||||
super._applyStyles();
|
||||
}
|
||||
|
||||
protected _fillContainer(container: HTMLElement): void {
|
||||
this.setCssClass('exception-widget');
|
||||
// Set the font size and line height to the one from the editor configuration.
|
||||
const fontInfo = this.editor.getConfiguration().fontInfo;
|
||||
this.container.style.fontSize = `${fontInfo.fontSize}px`;
|
||||
this.container.style.lineHeight = `${fontInfo.lineHeight}px`;
|
||||
|
||||
let title = $('.title');
|
||||
title.textContent = this.exceptionInfo.id ? nls.localize('exceptionThrownWithId', 'Exception has occurred: {0}', this.exceptionInfo.id) : nls.localize('exceptionThrown', 'Exception has occurred.');
|
||||
dom.append(container, title);
|
||||
|
||||
if (this.exceptionInfo.description) {
|
||||
let description = $('.description');
|
||||
description.textContent = this.exceptionInfo.description;
|
||||
dom.append(container, description);
|
||||
}
|
||||
|
||||
if (this.exceptionInfo.details && this.exceptionInfo.details.stackTrace) {
|
||||
let stackTrace = $('.stack-trace');
|
||||
const linkDetector = this.instantiationService.createInstance(LinkDetector);
|
||||
const linkedStackTrace = linkDetector.handleLinks(this.exceptionInfo.details.stackTrace);
|
||||
stackTrace.appendChild(linkedStackTrace);
|
||||
dom.append(container, stackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
protected _doLayout(_heightInPixel: number | undefined, _widthInPixel: number | undefined): void {
|
||||
// Reload the height with respect to the exception text content and relayout it to match the line count.
|
||||
this.container.style.height = 'initial';
|
||||
|
||||
const lineHeight = this.editor.getConfiguration().lineHeight;
|
||||
const arrowHeight = Math.round(lineHeight / 3);
|
||||
const computedLinesNumber = Math.ceil((this.container.offsetHeight + arrowHeight) / lineHeight);
|
||||
|
||||
this._relayout(computedLinesNumber);
|
||||
}
|
||||
}
|
||||
158
src/vs/workbench/contrib/debug/browser/linkDetector.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import strings = require('vs/base/common/strings');
|
||||
import { isAbsolute } from 'vs/base/common/path';
|
||||
import { URI as uri } from 'vs/base/common/uri';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { IMouseEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||
import * as nls from 'vs/nls';
|
||||
import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
|
||||
export class LinkDetector {
|
||||
private static readonly MAX_LENGTH = 500;
|
||||
private static FILE_LOCATION_PATTERNS: RegExp[] = [
|
||||
// group 0: full path with line and column
|
||||
// group 1: full path without line and column, matched by `*.*` in the end to work only on paths with extensions in the end (s.t. node:10352 would not match)
|
||||
// group 2: drive letter on windows with trailing backslash or leading slash on mac/linux
|
||||
// group 3: line number, matched by (:(\d+))
|
||||
// group 4: column number, matched by ((?::(\d+))?)
|
||||
// eg: at Context.<anonymous> (c:\Users\someone\Desktop\mocha-runner\test\test.js:26:11)
|
||||
/(?![\(])(?:file:\/\/)?((?:([a-zA-Z]+:)|[^\(\)<>\'\"\[\]:\s]+)(?:[\\/][^\(\)<>\'\"\[\]:]*)?\.[a-zA-Z]+[0-9]*):(\d+)(?::(\d+))?/g
|
||||
];
|
||||
|
||||
constructor(
|
||||
@IEditorService private readonly editorService: IEditorService
|
||||
) {
|
||||
// noop
|
||||
}
|
||||
|
||||
/**
|
||||
* Matches and handles absolute file links in the string provided.
|
||||
* Returns <span/> element that wraps the processed string, where matched links are replaced by <a/> and unmatched parts are surrounded by <span/> elements.
|
||||
* 'onclick' event is attached to all anchored links that opens them in the editor.
|
||||
* Each line of the text, even if it contains no links, is wrapped in a <span> and added as a child of the returned <span>.
|
||||
*/
|
||||
handleLinks(text: string): HTMLElement {
|
||||
const container = document.createElement('span');
|
||||
|
||||
// Handle the text one line at a time
|
||||
const lines = text.split('\n');
|
||||
|
||||
if (strings.endsWith(text, '\n')) {
|
||||
// Remove the last element ('') that split added
|
||||
lines.pop();
|
||||
}
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
let line = lines[i];
|
||||
|
||||
// Re-introduce the newline for every line except the last (unless the last line originally ended with a newline)
|
||||
if (i < lines.length - 1 || strings.endsWith(text, '\n')) {
|
||||
line += '\n';
|
||||
}
|
||||
|
||||
// Don't handle links for lines that are too long
|
||||
if (line.length > LinkDetector.MAX_LENGTH) {
|
||||
let span = document.createElement('span');
|
||||
span.textContent = line;
|
||||
container.appendChild(span);
|
||||
continue;
|
||||
}
|
||||
|
||||
const lineContainer = document.createElement('span');
|
||||
|
||||
for (let pattern of LinkDetector.FILE_LOCATION_PATTERNS) {
|
||||
// Reset the state of the pattern
|
||||
pattern = new RegExp(pattern);
|
||||
let lastMatchIndex = 0;
|
||||
|
||||
let match = pattern.exec(line);
|
||||
|
||||
while (match !== null) {
|
||||
let resource: uri | null = isAbsolute(match[1]) ? uri.file(match[1]) : null;
|
||||
|
||||
if (!resource) {
|
||||
match = pattern.exec(line);
|
||||
continue;
|
||||
}
|
||||
|
||||
const textBeforeLink = line.substring(lastMatchIndex, match.index);
|
||||
if (textBeforeLink) {
|
||||
// textBeforeLink may have matches for other patterns, so we run handleLinks on it before adding it.
|
||||
lineContainer.appendChild(this.handleLinks(textBeforeLink));
|
||||
}
|
||||
|
||||
const link = document.createElement('a');
|
||||
link.textContent = line.substr(match.index, match[0].length);
|
||||
link.title = isMacintosh ? nls.localize('fileLinkMac', "Click to follow (Cmd + click opens to the side)") : nls.localize('fileLink', "Click to follow (Ctrl + click opens to the side)");
|
||||
lineContainer.appendChild(link);
|
||||
const lineNumber = Number(match[3]);
|
||||
const columnNumber = match[4] ? Number(match[4]) : undefined;
|
||||
link.onclick = (e) => this.onLinkClick(new StandardMouseEvent(e), resource!, lineNumber, columnNumber);
|
||||
|
||||
lastMatchIndex = pattern.lastIndex;
|
||||
const currentMatch = match;
|
||||
match = pattern.exec(line);
|
||||
|
||||
// Append last string part if no more link matches
|
||||
if (!match) {
|
||||
const textAfterLink = line.substr(currentMatch.index + currentMatch[0].length);
|
||||
if (textAfterLink) {
|
||||
// textAfterLink may have matches for other patterns, so we run handleLinks on it before adding it.
|
||||
lineContainer.appendChild(this.handleLinks(textAfterLink));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we found any matches for this pattern, don't check any more patterns. Other parts of the line will be checked for the other patterns due to the recursion.
|
||||
if (lineContainer.hasChildNodes()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (lines.length === 1) {
|
||||
if (lineContainer.hasChildNodes()) {
|
||||
// Adding lineContainer to container would introduce an unnecessary surrounding span since there is only one line, so instead we just return lineContainer
|
||||
return lineContainer;
|
||||
} else {
|
||||
container.textContent = line;
|
||||
}
|
||||
} else {
|
||||
if (lineContainer.hasChildNodes()) {
|
||||
// Add this line to the container
|
||||
container.appendChild(lineContainer);
|
||||
} else {
|
||||
// No links were added, but we still need to surround the unmodified line with a span before adding it
|
||||
let span = document.createElement('span');
|
||||
span.textContent = line;
|
||||
container.appendChild(span);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
|
||||
private onLinkClick(event: IMouseEvent, resource: uri, line: number, column: number = 0): void {
|
||||
const selection = window.getSelection();
|
||||
if (selection.type === 'Range') {
|
||||
return; // do not navigate when user is selecting
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
const group = event.ctrlKey || event.metaKey ? SIDE_GROUP : ACTIVE_GROUP;
|
||||
|
||||
this.editorService.openEditor({
|
||||
resource,
|
||||
options: {
|
||||
selection: {
|
||||
startLineNumber: line,
|
||||
startColumn: column
|
||||
}
|
||||
}
|
||||
}, group);
|
||||
}
|
||||
}
|
||||
648
src/vs/workbench/contrib/debug/browser/loadedScriptsView.ts
Normal file
@@ -0,0 +1,648 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 dom from 'vs/base/browser/dom';
|
||||
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { normalize, isAbsolute, posix } from 'vs/base/common/path';
|
||||
import { IViewletPanelOptions, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { renderViewTree } from 'vs/workbench/contrib/debug/browser/baseDebugView';
|
||||
import { IDebugSession, IDebugService, IDebugModel, CONTEXT_LOADED_SCRIPTS_ITEM_TYPE } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
|
||||
import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { tildify } from 'vs/base/common/labels';
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ltrim } from 'vs/base/common/strings';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { ResourceLabels, IResourceLabelProps, IResourceLabelOptions, IResourceLabel, IResourceLabelsContainer } from 'vs/workbench/browser/labels';
|
||||
import { FileKind } from 'vs/platform/files/common/files';
|
||||
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
||||
import { ITreeRenderer, ITreeNode, ITreeFilter, TreeVisibility, TreeFilterResult, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { WorkbenchAsyncDataTree, TreeResourceNavigator2 } from 'vs/platform/list/browser/listService';
|
||||
import { dispose } from 'vs/base/common/lifecycle';
|
||||
import { createMatches, FuzzyScore } from 'vs/base/common/filters';
|
||||
import { DebugContentProvider } from 'vs/workbench/contrib/debug/common/debugContentProvider';
|
||||
|
||||
const SMART = true;
|
||||
|
||||
type LoadedScriptsItem = BaseTreeItem;
|
||||
|
||||
class BaseTreeItem {
|
||||
|
||||
private _showedMoreThanOne: boolean;
|
||||
private _children: { [key: string]: BaseTreeItem; };
|
||||
private _source: Source;
|
||||
|
||||
constructor(private _parent: BaseTreeItem | undefined, private _label: string) {
|
||||
this._children = {};
|
||||
this._showedMoreThanOne = false;
|
||||
}
|
||||
|
||||
isLeaf(): boolean {
|
||||
return Object.keys(this._children).length === 0;
|
||||
}
|
||||
|
||||
getSession(): IDebugSession | undefined {
|
||||
if (this._parent) {
|
||||
return this._parent.getSession();
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
setSource(session: IDebugSession, source: Source): void {
|
||||
this._source = source;
|
||||
this._children = {};
|
||||
if (source.raw && source.raw.sources) {
|
||||
for (const src of source.raw.sources) {
|
||||
if (src.name && src.path) {
|
||||
const s = new BaseTreeItem(this, src.name);
|
||||
this._children[src.path] = s;
|
||||
const ss = session.getSource(src);
|
||||
s.setSource(session, ss);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
createIfNeeded<T extends BaseTreeItem>(key: string, factory: (parent: BaseTreeItem, label: string) => T): T {
|
||||
let child = <T>this._children[key];
|
||||
if (!child) {
|
||||
child = factory(this, key);
|
||||
this._children[key] = child;
|
||||
}
|
||||
return child;
|
||||
}
|
||||
|
||||
getChild(key: string): BaseTreeItem {
|
||||
return this._children[key];
|
||||
}
|
||||
|
||||
remove(key: string): void {
|
||||
delete this._children[key];
|
||||
}
|
||||
|
||||
removeFromParent(): void {
|
||||
if (this._parent) {
|
||||
this._parent.remove(this._label);
|
||||
if (Object.keys(this._parent._children).length === 0) {
|
||||
this._parent.removeFromParent();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getTemplateId(): string {
|
||||
return 'id';
|
||||
}
|
||||
|
||||
// a dynamic ID based on the parent chain; required for reparenting (see #55448)
|
||||
getId(): string {
|
||||
const parent = this.getParent();
|
||||
return parent ? `${parent.getId()}/${this._label}` : this._label;
|
||||
}
|
||||
|
||||
// skips intermediate single-child nodes
|
||||
getParent(): BaseTreeItem | undefined {
|
||||
if (this._parent) {
|
||||
if (this._parent.isSkipped()) {
|
||||
return this._parent.getParent();
|
||||
}
|
||||
return this._parent;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
isSkipped(): boolean {
|
||||
if (this._parent) {
|
||||
if (this._parent.oneChild()) {
|
||||
return true; // skipped if I'm the only child of my parents
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return true; // roots are never skipped
|
||||
}
|
||||
|
||||
// skips intermediate single-child nodes
|
||||
hasChildren(): boolean {
|
||||
const child = this.oneChild();
|
||||
if (child) {
|
||||
return child.hasChildren();
|
||||
}
|
||||
return Object.keys(this._children).length > 0;
|
||||
}
|
||||
|
||||
// skips intermediate single-child nodes
|
||||
getChildren(): Promise<BaseTreeItem[]> {
|
||||
const child = this.oneChild();
|
||||
if (child) {
|
||||
return child.getChildren();
|
||||
}
|
||||
const array = Object.keys(this._children).map(key => this._children[key]);
|
||||
return Promise.resolve(array.sort((a, b) => this.compare(a, b)));
|
||||
}
|
||||
|
||||
// skips intermediate single-child nodes
|
||||
getLabel(separateRootFolder = true): string {
|
||||
const child = this.oneChild();
|
||||
if (child) {
|
||||
const sep = (this instanceof RootFolderTreeItem && separateRootFolder) ? ' • ' : posix.sep;
|
||||
return `${this._label}${sep}${child.getLabel()}`;
|
||||
}
|
||||
return this._label;
|
||||
}
|
||||
|
||||
// skips intermediate single-child nodes
|
||||
getHoverLabel(): string | undefined {
|
||||
if (this._source && this._parent && this._parent._source) {
|
||||
return this._source.raw.path || this._source.raw.name;
|
||||
}
|
||||
let label = this.getLabel(false);
|
||||
const parent = this.getParent();
|
||||
if (parent) {
|
||||
const hover = parent.getHoverLabel();
|
||||
if (hover) {
|
||||
return `${hover}/${label}`;
|
||||
}
|
||||
}
|
||||
return label;
|
||||
}
|
||||
|
||||
// skips intermediate single-child nodes
|
||||
getSource(): Source {
|
||||
const child = this.oneChild();
|
||||
if (child) {
|
||||
return child.getSource();
|
||||
}
|
||||
return this._source;
|
||||
}
|
||||
|
||||
protected compare(a: BaseTreeItem, b: BaseTreeItem): number {
|
||||
if (a._label && b._label) {
|
||||
return a._label.localeCompare(b._label);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private oneChild(): BaseTreeItem | undefined {
|
||||
if (SMART && !this._source && !this._showedMoreThanOne && !(this instanceof RootFolderTreeItem) && !(this instanceof SessionTreeItem)) {
|
||||
const keys = Object.keys(this._children);
|
||||
if (keys.length === 1) {
|
||||
return this._children[keys[0]];
|
||||
}
|
||||
// if a node had more than one child once, it will never be skipped again
|
||||
if (keys.length > 1) {
|
||||
this._showedMoreThanOne = true;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
class RootFolderTreeItem extends BaseTreeItem {
|
||||
|
||||
constructor(parent: BaseTreeItem, public folder: IWorkspaceFolder) {
|
||||
super(parent, folder.name);
|
||||
}
|
||||
}
|
||||
|
||||
class RootTreeItem extends BaseTreeItem {
|
||||
|
||||
constructor(private _debugModel: IDebugModel, private _environmentService: IEnvironmentService, private _contextService: IWorkspaceContextService) {
|
||||
super(undefined, 'Root');
|
||||
this._debugModel.getSessions().forEach(session => {
|
||||
this.add(session);
|
||||
});
|
||||
}
|
||||
|
||||
add(session: IDebugSession): SessionTreeItem {
|
||||
return this.createIfNeeded(session.getId(), () => new SessionTreeItem(this, session, this._environmentService, this._contextService));
|
||||
}
|
||||
|
||||
find(session: IDebugSession): SessionTreeItem {
|
||||
return <SessionTreeItem>this.getChild(session.getId());
|
||||
}
|
||||
}
|
||||
|
||||
class SessionTreeItem extends BaseTreeItem {
|
||||
|
||||
private static URL_REGEXP = /^(https?:\/\/[^/]+)(\/.*)$/;
|
||||
|
||||
private _session: IDebugSession;
|
||||
private _initialized: boolean;
|
||||
private _map: Map<string, BaseTreeItem>;
|
||||
|
||||
constructor(parent: BaseTreeItem, session: IDebugSession, private _environmentService: IEnvironmentService, private rootProvider: IWorkspaceContextService) {
|
||||
super(parent, session.getLabel());
|
||||
this._initialized = false;
|
||||
this._session = session;
|
||||
this._map = new Map();
|
||||
}
|
||||
|
||||
getSession(): IDebugSession {
|
||||
return this._session;
|
||||
}
|
||||
|
||||
getHoverLabel(): string | undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
hasChildren(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
getChildren(): Promise<BaseTreeItem[]> {
|
||||
|
||||
if (!this._initialized) {
|
||||
this._initialized = true;
|
||||
return this._session.getLoadedSources().then(paths => {
|
||||
paths.forEach(path => this.addPath(path));
|
||||
return super.getChildren();
|
||||
});
|
||||
}
|
||||
|
||||
return super.getChildren();
|
||||
}
|
||||
|
||||
protected compare(a: BaseTreeItem, b: BaseTreeItem): number {
|
||||
const acat = this.category(a);
|
||||
const bcat = this.category(b);
|
||||
if (acat !== bcat) {
|
||||
return acat - bcat;
|
||||
}
|
||||
return super.compare(a, b);
|
||||
}
|
||||
|
||||
private category(item: BaseTreeItem): number {
|
||||
|
||||
// workspace scripts come at the beginning in "folder" order
|
||||
if (item instanceof RootFolderTreeItem) {
|
||||
return item.folder.index;
|
||||
}
|
||||
|
||||
// <...> come at the very end
|
||||
const l = item.getLabel();
|
||||
if (l && /^<.+>$/.test(l)) {
|
||||
return 1000;
|
||||
}
|
||||
|
||||
// everything else in between
|
||||
return 999;
|
||||
}
|
||||
|
||||
addPath(source: Source): void {
|
||||
|
||||
let folder: IWorkspaceFolder | null;
|
||||
let url: string;
|
||||
|
||||
let path = source.raw.path;
|
||||
if (!path) {
|
||||
return;
|
||||
}
|
||||
|
||||
const match = SessionTreeItem.URL_REGEXP.exec(path);
|
||||
if (match && match.length === 3) {
|
||||
url = match[1];
|
||||
path = decodeURI(match[2]);
|
||||
} else {
|
||||
if (isAbsolute(path)) {
|
||||
const resource = URI.file(path);
|
||||
|
||||
// return early if we can resolve a relative path label from the root folder
|
||||
folder = this.rootProvider ? this.rootProvider.getWorkspaceFolder(resource) : null;
|
||||
if (folder) {
|
||||
// strip off the root folder path
|
||||
path = normalize(ltrim(resource.path.substr(folder.uri.path.length), posix.sep));
|
||||
const hasMultipleRoots = this.rootProvider.getWorkspace().folders.length > 1;
|
||||
if (hasMultipleRoots) {
|
||||
path = posix.sep + path;
|
||||
} else {
|
||||
// don't show root folder
|
||||
folder = null;
|
||||
}
|
||||
} else {
|
||||
// on unix try to tildify absolute paths
|
||||
path = normalize(path);
|
||||
if (!isWindows) {
|
||||
path = tildify(path, this._environmentService.userHome);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let leaf: BaseTreeItem = this;
|
||||
path.split(/[\/\\]/).forEach((segment, i) => {
|
||||
if (i === 0 && folder) {
|
||||
const f = folder;
|
||||
leaf = leaf.createIfNeeded(folder.name, parent => new RootFolderTreeItem(parent, f));
|
||||
} else if (i === 0 && url) {
|
||||
leaf = leaf.createIfNeeded(url, parent => new BaseTreeItem(parent, url));
|
||||
} else {
|
||||
leaf = leaf.createIfNeeded(segment, parent => new BaseTreeItem(parent, segment));
|
||||
}
|
||||
});
|
||||
|
||||
leaf.setSource(this._session, source);
|
||||
if (source.raw.path) {
|
||||
this._map.set(source.raw.path, leaf);
|
||||
}
|
||||
}
|
||||
|
||||
removePath(source: Source): boolean {
|
||||
if (source.raw.path) {
|
||||
const leaf = this._map.get(source.raw.path);
|
||||
if (leaf) {
|
||||
leaf.removeFromParent();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export class LoadedScriptsView extends ViewletPanel {
|
||||
|
||||
private treeContainer: HTMLElement;
|
||||
private loadedScriptsItemType: IContextKey<string>;
|
||||
private tree: WorkbenchAsyncDataTree<LoadedScriptsItem, LoadedScriptsItem, FuzzyScore>;
|
||||
private treeLabels: ResourceLabels;
|
||||
private changeScheduler: RunOnceScheduler;
|
||||
private treeNeedsRefreshOnVisible: boolean;
|
||||
private filter: LoadedScriptsFilter;
|
||||
|
||||
constructor(
|
||||
options: IViewletViewOptions,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IContextKeyService readonly contextKeyService: IContextKeyService,
|
||||
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@IDebugService private readonly debugService: IDebugService,
|
||||
) {
|
||||
super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('loadedScriptsSection', "Loaded Scripts Section") }, keybindingService, contextMenuService, configurationService);
|
||||
this.loadedScriptsItemType = CONTEXT_LOADED_SCRIPTS_ITEM_TYPE.bindTo(contextKeyService);
|
||||
}
|
||||
|
||||
renderBody(container: HTMLElement): void {
|
||||
dom.addClass(container, 'debug-loaded-scripts');
|
||||
dom.addClass(container, 'show-file-icons');
|
||||
|
||||
this.treeContainer = renderViewTree(container);
|
||||
|
||||
this.filter = new LoadedScriptsFilter();
|
||||
|
||||
const root = new RootTreeItem(this.debugService.getModel(), this.environmentService, this.contextService);
|
||||
|
||||
this.treeLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility } as IResourceLabelsContainer);
|
||||
this.disposables.push(this.treeLabels);
|
||||
|
||||
this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, this.treeContainer, new LoadedScriptsDelegate(),
|
||||
[new LoadedScriptsRenderer(this.treeLabels)],
|
||||
new LoadedScriptsDataSource(),
|
||||
{
|
||||
identityProvider: {
|
||||
getId: element => (<LoadedScriptsItem>element).getId()
|
||||
},
|
||||
keyboardNavigationLabelProvider: {
|
||||
getKeyboardNavigationLabel: element => (<LoadedScriptsItem>element).getLabel()
|
||||
},
|
||||
filter: this.filter,
|
||||
accessibilityProvider: new LoadedSciptsAccessibilityProvider(),
|
||||
ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'loadedScriptsAriaLabel' }, "Debug Loaded Scripts"),
|
||||
}
|
||||
) as WorkbenchAsyncDataTree<LoadedScriptsItem, LoadedScriptsItem, FuzzyScore>;
|
||||
|
||||
this.tree.setInput(root);
|
||||
|
||||
this.changeScheduler = new RunOnceScheduler(() => {
|
||||
this.treeNeedsRefreshOnVisible = false;
|
||||
if (this.tree) {
|
||||
this.tree.updateChildren();
|
||||
}
|
||||
}, 300);
|
||||
this.disposables.push(this.changeScheduler);
|
||||
|
||||
const loadedScriptsNavigator = new TreeResourceNavigator2(this.tree);
|
||||
this.disposables.push(loadedScriptsNavigator);
|
||||
this.disposables.push(loadedScriptsNavigator.onDidOpenResource(e => {
|
||||
if (e.element instanceof BaseTreeItem) {
|
||||
const source = e.element.getSource();
|
||||
if (source && source.available) {
|
||||
const nullRange = { startLineNumber: 0, startColumn: 0, endLineNumber: 0, endColumn: 0 };
|
||||
source.openInEditor(this.editorService, nullRange, e.editorOptions.preserveFocus, e.sideBySide, e.editorOptions.pinned);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this.disposables.push(this.tree.onDidChangeFocus(() => {
|
||||
const focus = this.tree.getFocus();
|
||||
if (focus instanceof SessionTreeItem) {
|
||||
this.loadedScriptsItemType.set('session');
|
||||
} else {
|
||||
this.loadedScriptsItemType.reset();
|
||||
}
|
||||
}));
|
||||
|
||||
const registerLoadedSourceListener = (session: IDebugSession) => {
|
||||
this.disposables.push(session.onDidLoadedSource(event => {
|
||||
let sessionRoot: SessionTreeItem;
|
||||
switch (event.reason) {
|
||||
case 'new':
|
||||
case 'changed':
|
||||
sessionRoot = root.add(session);
|
||||
sessionRoot.addPath(event.source);
|
||||
if (this.isBodyVisible()) {
|
||||
this.changeScheduler.schedule();
|
||||
} else {
|
||||
this.treeNeedsRefreshOnVisible = true;
|
||||
}
|
||||
if (event.reason === 'changed') {
|
||||
DebugContentProvider.refreshDebugContent(event.source.uri);
|
||||
}
|
||||
break;
|
||||
case 'removed':
|
||||
sessionRoot = root.find(session);
|
||||
if (sessionRoot && sessionRoot.removePath(event.source)) {
|
||||
if (this.isBodyVisible()) {
|
||||
this.changeScheduler.schedule();
|
||||
} else {
|
||||
this.treeNeedsRefreshOnVisible = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
this.filter.setFilter(event.source.name);
|
||||
this.tree.refilter();
|
||||
break;
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
this.disposables.push(this.debugService.onDidNewSession(registerLoadedSourceListener));
|
||||
this.debugService.getModel().getSessions().forEach(registerLoadedSourceListener);
|
||||
|
||||
this.disposables.push(this.debugService.onDidEndSession(session => {
|
||||
root.remove(session.getId());
|
||||
this.changeScheduler.schedule();
|
||||
}));
|
||||
|
||||
this.changeScheduler.schedule(0);
|
||||
|
||||
this.disposables.push(this.onDidChangeBodyVisibility(visible => {
|
||||
if (visible && this.treeNeedsRefreshOnVisible) {
|
||||
this.changeScheduler.schedule();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
layoutBody(height: number, width: number): void {
|
||||
this.tree.layout(height, width);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.tree = dispose(this.tree);
|
||||
this.treeLabels = dispose(this.treeLabels);
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class LoadedScriptsDelegate implements IListVirtualDelegate<LoadedScriptsItem> {
|
||||
|
||||
getHeight(element: LoadedScriptsItem): number {
|
||||
return 22;
|
||||
}
|
||||
|
||||
getTemplateId(element: LoadedScriptsItem): string {
|
||||
return LoadedScriptsRenderer.ID;
|
||||
}
|
||||
}
|
||||
|
||||
class LoadedScriptsDataSource implements IAsyncDataSource<LoadedScriptsItem, LoadedScriptsItem> {
|
||||
|
||||
hasChildren(element: LoadedScriptsItem): boolean {
|
||||
return element.hasChildren();
|
||||
}
|
||||
|
||||
getChildren(element: LoadedScriptsItem): Promise<LoadedScriptsItem[]> {
|
||||
return element.getChildren();
|
||||
}
|
||||
}
|
||||
|
||||
interface ILoadedScriptsItemTemplateData {
|
||||
label: IResourceLabel;
|
||||
}
|
||||
|
||||
class LoadedScriptsRenderer implements ITreeRenderer<BaseTreeItem, FuzzyScore, ILoadedScriptsItemTemplateData> {
|
||||
|
||||
static readonly ID = 'lsrenderer';
|
||||
|
||||
constructor(
|
||||
private labels: ResourceLabels
|
||||
) {
|
||||
}
|
||||
|
||||
get templateId(): string {
|
||||
return LoadedScriptsRenderer.ID;
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): ILoadedScriptsItemTemplateData {
|
||||
const label = this.labels.create(container, { supportHighlights: true });
|
||||
return { label };
|
||||
}
|
||||
|
||||
renderElement(node: ITreeNode<BaseTreeItem, FuzzyScore>, index: number, data: ILoadedScriptsItemTemplateData): void {
|
||||
|
||||
const element = node.element;
|
||||
|
||||
const label: IResourceLabelProps = {
|
||||
name: element.getLabel()
|
||||
};
|
||||
const options: IResourceLabelOptions = {
|
||||
title: element.getHoverLabel()
|
||||
};
|
||||
|
||||
if (element instanceof RootFolderTreeItem) {
|
||||
|
||||
options.fileKind = FileKind.ROOT_FOLDER;
|
||||
|
||||
} else if (element instanceof SessionTreeItem) {
|
||||
|
||||
options.title = nls.localize('loadedScriptsSession', "Debug Session");
|
||||
options.hideIcon = true;
|
||||
|
||||
} else if (element instanceof BaseTreeItem) {
|
||||
|
||||
const src = element.getSource();
|
||||
if (src && src.uri) {
|
||||
label.resource = src.uri;
|
||||
options.fileKind = FileKind.FILE;
|
||||
} else {
|
||||
options.fileKind = FileKind.FOLDER;
|
||||
}
|
||||
}
|
||||
options.matches = createMatches(node.filterData);
|
||||
|
||||
data.label.setResource(label, options);
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: ILoadedScriptsItemTemplateData): void {
|
||||
templateData.label.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class LoadedSciptsAccessibilityProvider implements IAccessibilityProvider<LoadedScriptsItem> {
|
||||
|
||||
getAriaLabel(element: LoadedScriptsItem): string {
|
||||
|
||||
if (element instanceof RootFolderTreeItem) {
|
||||
return nls.localize('loadedScriptsRootFolderAriaLabel', "Workspace folder {0}, loaded script, debug", element.getLabel());
|
||||
}
|
||||
|
||||
if (element instanceof SessionTreeItem) {
|
||||
return nls.localize('loadedScriptsSessionAriaLabel', "Session {0}, loaded script, debug", element.getLabel());
|
||||
}
|
||||
|
||||
if (element.hasChildren()) {
|
||||
return nls.localize('loadedScriptsFolderAriaLabel', "Folder {0}, loaded script, debug", element.getLabel());
|
||||
} else {
|
||||
return nls.localize('loadedScriptsSourceAriaLabel', "{0}, loaded script, debug", element.getLabel());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class LoadedScriptsFilter implements ITreeFilter<BaseTreeItem, FuzzyScore> {
|
||||
|
||||
private filterText: string;
|
||||
|
||||
setFilter(filterText: string) {
|
||||
this.filterText = filterText;
|
||||
}
|
||||
|
||||
filter(element: BaseTreeItem, parentVisibility: TreeVisibility): TreeFilterResult<FuzzyScore> {
|
||||
|
||||
if (!this.filterText) {
|
||||
return TreeVisibility.Visible;
|
||||
}
|
||||
|
||||
if (element.isLeaf()) {
|
||||
const name = element.getLabel();
|
||||
if (name.indexOf(this.filterText) >= 0) {
|
||||
return TreeVisibility.Visible;
|
||||
}
|
||||
return TreeVisibility.Hidden;
|
||||
}
|
||||
return TreeVisibility.Recurse;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent{fill:#f6f6f6;opacity:0;}.icon-white{fill:#fff;}</style></defs><title>add-focus</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,16H0V0H16Z"/></g><g id="iconBg"><path class="icon-white" d="M13,7V9H9v4H7V9H3V7H7V3H9V7Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 345 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;}</style></defs><title>add</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,16H0V0H16Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14,6v4H10v4H6V10H2V6H6V2h4V6Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M13,7V9H9v4H7V9H3V7H7V3H9V7Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 486 B |
1
src/vs/workbench/contrib/debug/browser/media/add.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>add</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,16H0V0H16Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14,6v4H10v4H6V10H2V6H6V2h4V6Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M13,7V9H9v4H7V9H3V7H7V3H9V7Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 486 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1,.icon-vs-out{fill:#f6f6f6;}.cls-1{fill-opacity:0;}.icon-disabled-grey{fill:#848484;}.icon-white{fill:#fff;}</style></defs><title>breakpoint-conditional-disabled</title><g id="canvas"><path class="cls-1" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M12.8,8A4.8,4.8,0,1,1,8,3.2,4.806,4.806,0,0,1,12.8,8Z"/></g><g id="iconBg"><path class="icon-disabled-grey" d="M11.8,8A3.8,3.8,0,1,1,8,4.2,3.8,3.8,0,0,1,11.8,8Z"/><path class="icon-white" d="M10.1,6.7v.8H5.9V6.7ZM5.9,9.2h4.2V8.4H5.9Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 620 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-disabled-grey{fill:#848484;}</style></defs><title>breakpoint-conditional-unverified</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M12.632,8A4.632,4.632,0,1,1,8,3.368,4.638,4.638,0,0,1,12.632,8Z"/></g><g id="iconBg"><path class="icon-disabled-grey" d="M6.526,8.421H9.474v.842H6.526ZM11.789,8A3.789,3.789,0,1,1,8,4.211,3.788,3.788,0,0,1,11.789,8Zm-1,0A2.789,2.789,0,1,0,8,10.79,2.793,2.793,0,0,0,10.789,8ZM6.526,7.579H9.474V6.737H6.526Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 719 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.cls-1,.icon-vs-out{fill:#f6f6f6;}.cls-1{fill-opacity:0;}.icon-vs-red{fill:#e51400;}.icon-white{fill:#fff;}</style></defs><title>breakpoint-conditional</title><g id="canvas"><path class="cls-1" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M12.8,8A4.8,4.8,0,1,1,8,3.2,4.806,4.806,0,0,1,12.8,8Z"/></g><g id="iconBg"><path class="icon-vs-red" d="M11.8,8A3.8,3.8,0,1,1,8,4.2,3.8,3.8,0,0,1,11.8,8Z"/><path class="icon-white" d="M10.1,6.7v.8H5.9V6.7ZM5.9,9.2h4.2V8.4H5.9Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 597 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-disabled-grey{fill:#848484;}</style></defs><title>breakpoint-disabled</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M12.632,8A4.632,4.632,0,1,1,8,3.368,4.638,4.638,0,0,1,12.632,8Z"/></g><g id="iconBg"><path class="icon-disabled-grey" d="M11.789,8A3.789,3.789,0,1,1,8,4.211,3.788,3.788,0,0,1,11.789,8Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 585 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-disabled-grey{fill:#848484;}</style></defs><title>breakpoint-function-disabled</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M13.7,13H2.3L8,2.741Z"/></g><g id="iconBg"><path class="icon-disabled-grey" d="M12,12H4L8,4.8Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 504 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-disabled-grey{fill:#848484;}</style></defs><title>breakpoint-function-unverified</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M13.95,13H2.05L8,2.291Z"/></g><g id="iconBg"><path class="icon-disabled-grey" d="M8,4.35,3.75,12h8.5ZM8,6.924l2.125,3.826H5.875Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 540 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-red{fill:#e51400;}</style></defs><title>breakpoint-function</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M13.7,13H2.3L8,2.741Z"/></g><g id="iconBg"><path class="icon-vs-red" d="M12,12H4L8,4.8Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 481 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.cls-1{fill:#e51400;opacity:0.4;}</style></defs><title>breakpoint-hint</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M12.632,8A4.632,4.632,0,1,1,8,3.368,4.638,4.638,0,0,1,12.632,8Z"/></g><g id="iconBg"><path class="cls-1" d="M11.789,8A3.789,3.789,0,1,1,8,4.211,3.788,3.788,0,0,1,11.789,8Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 567 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-disabled-grey{fill:#848484;}</style></defs><title>breakpoint-log-disabled</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M13.414,8,8,13.414,2.586,8,8,2.586Z"/></g><g id="iconBg"><path class="icon-disabled-grey" d="M12,8,8,12,4,8,8,4Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 517 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-disabled-grey{fill:#848484;}</style></defs><title>breakpoint-log-unverified</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M13.664,8,8,13.664,2.336,8,8,2.336Z"/></g><g id="iconBg"><path class="icon-disabled-grey" d="M8,3.75,3.75,8,8,12.25,12.25,8ZM5.518,8,8,5.518,10.482,8,8,10.482Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 566 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-red{fill:#e51400;}</style></defs><title>breakpoint-log</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M13.414,8,8,13.414,2.586,8,8,2.586Z"/></g><g id="iconBg"><path class="icon-vs-red" d="M12,8,8,12,4,8,8,4Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 494 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-red{fill:#e51400;}.icon-white{fill:#fff;}</style></defs><title>breakpoint-unsupported</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M12.632,8A4.632,4.632,0,1,1,8,3.368,4.638,4.638,0,0,1,12.632,8Z"/></g><g id="iconBg"><path class="icon-vs-red" d="M11.789,8A3.789,3.789,0,1,1,8,4.211,3.788,3.788,0,0,1,11.789,8Z"/><path class="icon-white" d="M7.5,9.5h1v1h-1Zm0-4v3h1v-3Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 656 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-disabled-grey{fill:#848484;}</style></defs><title>breakpoint-unverified</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M12.632,8A4.632,4.632,0,1,1,8,3.368,4.638,4.638,0,0,1,12.632,8Z"/></g><g id="iconBg"><path class="icon-disabled-grey" d="M8,4.211A3.789,3.789,0,1,0,11.79,8,3.788,3.788,0,0,0,8,4.211ZM8,10.29A2.29,2.29,0,1,1,10.29,8,2.292,2.292,0,0,1,8,10.29Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 644 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-red{fill:#e51400;}</style></defs><title>breakpoint</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M12.632,8A4.632,4.632,0,1,1,8,3.368,4.638,4.638,0,0,1,12.632,8Z"/></g><g id="iconBg"><path class="icon-vs-red" d="M11.789,8A3.789,3.789,0,1,1,8,4.211,3.788,3.788,0,0,1,11.789,8Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 562 B |
@@ -0,0 +1,23 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-editor .zone-widget .zone-widget-container.breakpoint-widget {
|
||||
display: flex;
|
||||
border-color: #007ACC;
|
||||
}
|
||||
|
||||
.monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .breakpoint-select-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
padding: 0 10px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.monaco-editor .zone-widget .zone-widget-container.breakpoint-widget .inputContainer {
|
||||
flex: 1;
|
||||
margin-top: 6px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;}</style></defs><title>breakpoints-activate</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M16,5.5A5.536,5.536,0,0,1,11,11a5.536,5.536,0,0,1-5.5,5A5.549,5.549,0,0,1,0,10.5,5.465,5.465,0,0,1,5,5a5.512,5.512,0,0,1,11,.5Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M15,5.5A4.395,4.395,0,0,1,11,10a2.957,2.957,0,0,0-.2-1A3.565,3.565,0,0,0,14,5.5a3.507,3.507,0,0,0-7-.3A3.552,3.552,0,0,0,6,5a4.622,4.622,0,0,1,4.5-4A4.481,4.481,0,0,1,15,5.5ZM5.5,6A4.5,4.5,0,1,0,10,10.5,4.5,4.5,0,0,0,5.5,6Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 795 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>breakpoints-activate</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M16,5.5A5.536,5.536,0,0,1,11,11a5.536,5.536,0,0,1-5.5,5A5.549,5.549,0,0,1,0,10.5,5.465,5.465,0,0,1,5,5a5.512,5.512,0,0,1,11,.5Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M15,5.5A4.395,4.395,0,0,1,11,10a2.957,2.957,0,0,0-.2-1A3.565,3.565,0,0,0,14,5.5a3.507,3.507,0,0,0-7-.3A3.552,3.552,0,0,0,6,5a4.622,4.622,0,0,1,4.5-4A4.481,4.481,0,0,1,15,5.5ZM5.5,6A4.5,4.5,0,1,0,10,10.5,4.5,4.5,0,0,0,5.5,6Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 794 B |
@@ -0,0 +1 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7.065 13H15v2H2.056v-2h5.009zm3.661-12H7.385L8.44 2.061 7.505 3H15V1h-4.274zM3.237 9H2.056v2H15V9H3.237zm4.208-4l.995 1-.995 1H15V5H7.445z" fill="#C5C5C5"/><path d="M5.072 4.03L7.032 6 5.978 7.061l-1.96-1.97-1.961 1.97L1 6l1.96-1.97L1 2.061 2.056 1l1.96 1.97L5.977 1l1.057 1.061L5.072 4.03z" fill="#F48771"/></svg>
|
||||
|
After Width: | Height: | Size: 419 B |
@@ -0,0 +1 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M7.065 13H15v2H2.056v-2h5.009zm3.661-12H7.385L8.44 2.061 7.505 3H15V1h-4.274zM3.237 9H2.056v2H15V9H3.237zm4.208-4l.995 1-.995 1H15V5H7.445z" fill="#424242"/><path d="M5.072 4.03L7.032 6 5.978 7.061l-1.96-1.97-1.961 1.97L1 6l1.96-1.97L1 2.061 2.056 1l1.96 1.97L5.977 1l1.057 1.061L5.072 4.03z" fill="#A1260D"/></svg>
|
||||
|
After Width: | Height: | Size: 419 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;}</style></defs><title>configure</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M16,10.015l-2.238.372,1.318,1.847L12.233,15.08l-1.847-1.318L10.013,16H5.986l-.373-2.237L3.767,15.08.919,12.233l1.319-1.847L0,10.013V5.986l2.238-.373L.919,3.767,3.768.919,5.613,2.238,5.986,0h4.028l.372,2.238L12.233.919,15.08,3.768,13.762,5.613,16,5.986Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M12.876,9.521,15,9.167V6.834L12.879,6.48a5.12,5.12,0,0,0-.354-.854l1.25-1.75-1.65-1.65L10.373,3.477c-.137-.072-.262-.159-.408-.219s-.3-.087-.444-.133L9.167,1H6.834L6.48,3.121a5.118,5.118,0,0,0-.854.354l-1.75-1.25-1.65,1.65L3.477,5.627c-.072.137-.159.262-.219.408s-.087.3-.133.444L1,6.833V9.166l2.121.354a5.122,5.122,0,0,0,.354.854l-1.25,1.75,1.65,1.65,1.752-1.252c.137.072.262.159.408.22s.3.087.444.133L6.833,15H9.166l.354-2.121a5.121,5.121,0,0,0,.854-.354l1.75,1.25,1.65-1.65-1.252-1.752c.072-.137.159-.263.219-.409S12.83,9.669,12.876,9.521ZM8,10.212A2.212,2.212,0,1,1,10.212,8,2.212,2.212,0,0,1,8,10.212Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>configure</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M16,10.015l-2.238.372,1.318,1.847L12.233,15.08l-1.847-1.318L10.013,16H5.986l-.373-2.237L3.767,15.08.919,12.233l1.319-1.847L0,10.013V5.986l2.238-.373L.919,3.767,3.768.919,5.613,2.238,5.986,0h4.028l.372,2.238L12.233.919,15.08,3.768,13.762,5.613,16,5.986Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M12.876,9.521,15,9.167V6.834L12.879,6.48a5.12,5.12,0,0,0-.354-.854l1.25-1.75-1.65-1.65L10.373,3.477c-.137-.072-.262-.159-.408-.219s-.3-.087-.444-.133L9.167,1H6.834L6.48,3.121a5.118,5.118,0,0,0-.854.354l-1.75-1.25-1.65,1.65L3.477,5.627c-.072.137-.159.262-.219.408s-.087.3-.133.444L1,6.833V9.166l2.121.354a5.122,5.122,0,0,0,.354.854l-1.25,1.75,1.65,1.65,1.752-1.252c.137.072.262.159.408.22s.3.087.444.133L6.833,15H9.166l.354-2.121a5.121,5.121,0,0,0,.854-.354l1.75,1.25,1.65-1.65-1.252-1.752c.072-.137.159-.263.219-.409S12.83,9.669,12.876,9.521ZM8,10.212A2.212,2.212,0,1,1,10.212,8,2.212,2.212,0,0,1,8,10.212Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
1
src/vs/workbench/contrib/debug/browser/media/continue-inverse.svg
Executable file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-action-blue{fill:#75beff;}</style></defs><title>continue</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M15.64,8l-7.8,6H2V2H7.84Z"/></g><g id="iconBg"><path class="icon-vs-action-blue" d="M3,3H5V13H3ZM7.5,3V13L14,8Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 502 B |
1
src/vs/workbench/contrib/debug/browser/media/continue.svg
Executable file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-action-blue{fill:#00539c;}</style></defs><title>continue</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M15.64,8l-7.8,6H2V2H7.84Z"/></g><g id="iconBg"><path class="icon-vs-action-blue" d="M3,3H5V13H3ZM7.5,3V13L14,8Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 502 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-yellow{fill:#fc0;}.icon-vs-red{fill:#e51400;}</style></defs><title>current-and-breakpoint</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M13.829,8,9.454,13H4V3H9.454Z"/></g><g id="iconBg"><path class="icon-vs-yellow" d="M12.5,8,9,12H5V4H9Z"/><path class="icon-vs-red" d="M10.5,8A2.5,2.5,0,1,1,8,5.5,2.5,2.5,0,0,1,10.5,8Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 607 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-yellow{fill:#fc0;}</style></defs><title>current-arrow</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M13.829,8,9.454,13H4V3H9.454Z"/></g><g id="iconBg"><path class="icon-vs-yellow" d="M12.5,8,9,12H5V4H9Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 490 B |
@@ -0,0 +1 @@
|
||||
<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#clip0)"><path d="M15.1673 18.0687V23.0247C15.1673 23.5637 15.2723 24.5 14.7315 24.5H12.8328V23.3327H14V19.6122L13.7988 19.4022C13.0604 20.0803 12.1008 20.4669 11.0986 20.49C10.0964 20.5132 9.11994 20.1714 8.351 19.5282C7 18.1737 7.13826 16.3327 8.60475 14H4.66726V15.1672H3.50001V13.2685C3.50001 12.7277 4.43626 12.8327 4.97526 12.8327H9.76326L15.1673 18.0687ZM11.6673 5.83275H10.5V4.66725H12.775C13.3123 4.66725 14 4.9245 14 5.4635V9.366L14.8593 10.3862C14.927 9.83979 15.1906 9.33644 15.6013 8.96958C16.0119 8.60271 16.5416 8.39723 17.0923 8.39125C17.2298 8.37945 17.3684 8.39492 17.5 8.43675V5.83275H18.6673V8.88825C18.703 8.99154 18.7618 9.08536 18.8391 9.16265C18.9164 9.23995 19.0102 9.29871 19.1135 9.3345H22.1673V10.5H19.5615C19.593 10.5 19.6105 10.675 19.6105 10.85C19.6058 11.4034 19.4011 11.9365 19.0341 12.3508C18.6671 12.7651 18.1626 13.0326 17.6138 13.104L18.634 14H22.5383C23.0773 14 23.3345 14.6807 23.3345 15.225V17.5H22.1673V16.3327H19.2273L11.6673 8.98275V5.83275ZM14 0C11.2311 0 8.52431 0.821086 6.22202 2.35943C3.91973 3.89776 2.12532 6.08426 1.06569 8.64243C0.00606593 11.2006 -0.271181 14.0155 0.269012 16.7313C0.809205 19.447 2.14258 21.9416 4.10051 23.8995C6.05845 25.8574 8.55301 27.1908 11.2687 27.731C13.9845 28.2712 16.7994 27.9939 19.3576 26.9343C21.9157 25.8747 24.1022 24.0803 25.6406 21.778C27.1789 19.4757 28 16.7689 28 14C28 10.287 26.525 6.72601 23.8995 4.1005C21.274 1.475 17.713 0 14 0V0ZM25.6673 14C25.6692 16.6908 24.7364 19.2988 23.0283 21.378L6.622 4.97175C8.33036 3.57269 10.4009 2.68755 12.5927 2.41935C14.7845 2.15115 17.0074 2.51091 19.0027 3.45676C20.998 4.40262 22.6836 5.89567 23.8635 7.76217C25.0433 9.62867 25.6689 11.7919 25.6673 14ZM2.33276 14C2.33066 11.3091 3.26351 8.70111 4.97176 6.622L21.378 23.03C19.6693 24.4284 17.5987 25.313 15.407 25.5807C13.2153 25.8485 10.9926 25.4884 8.99754 24.5425C7.00244 23.5965 5.31693 22.1036 4.13708 20.2373C2.95722 18.3709 2.33152 16.208 2.33276 14Z" fill="white"/></g><defs><clipPath id="clip0"><rect width="28" height="28" fill="white"/></clipPath></defs></svg>
|
||||
|
After Width: | Height: | Size: 2.1 KiB |
@@ -0,0 +1,244 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* Activity Bar */
|
||||
.monaco-workbench .activitybar .monaco-action-bar .action-label.debug {
|
||||
-webkit-mask: url('debug-dark.svg') no-repeat 50% 50%;
|
||||
}
|
||||
|
||||
.monaco-editor .debug-top-stack-frame-column::before {
|
||||
background: url('current-arrow.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.debug-breakpoint-hint {
|
||||
background: url('breakpoint-hint.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.debug-breakpoint-disabled,
|
||||
.monaco-editor .debug-breakpoint-column.debug-breakpoint-disabled-column::before {
|
||||
background: url('breakpoint-disabled.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.debug-breakpoint-unverified,
|
||||
.monaco-editor .debug-breakpoint-column.debug-breakpoint-unverified-column::before {
|
||||
background: url('breakpoint-unverified.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.monaco-editor .debug-top-stack-frame {
|
||||
background: url('current-arrow.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.monaco-editor .debug-focused-stack-frame {
|
||||
background: url('stackframe-arrow.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.debug-breakpoint,
|
||||
.monaco-editor .debug-breakpoint-column::before {
|
||||
background: url('breakpoint.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.monaco-editor .debug-breakpoint-column::before,
|
||||
.monaco-editor .debug-top-stack-frame-column::before {
|
||||
content: " ";
|
||||
width: 1.3em;
|
||||
height: 1.3em;
|
||||
display: inline-block;
|
||||
vertical-align: text-bottom;
|
||||
margin-right: 2px;
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.debug-function-breakpoint {
|
||||
background: url('breakpoint-function.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.debug-function-breakpoint-unverified {
|
||||
background: url('breakpoint-function-unverified.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.debug-function-breakpoint-disabled {
|
||||
background: url('breakpoint-function-disabled.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.debug-breakpoint-conditional,
|
||||
.monaco-editor .debug-breakpoint-column.debug-breakpoint-conditional-column::before {
|
||||
background: url('breakpoint-conditional.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.debug-breakpoint-log,
|
||||
.monaco-editor .debug-breakpoint-column.debug-breakpoint-log-column::before {
|
||||
background: url('breakpoint-log.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.debug-breakpoint-log-disabled,
|
||||
.monaco-editor .debug-breakpoint-log-disabled-column::before {
|
||||
background: url('breakpoint-log-disabled.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.debug-breakpoint-log-unverified,
|
||||
.monaco-editor .debug-breakpoint-log-unverified-column::before {
|
||||
background: url('breakpoint-log-unverified.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.debug-breakpoint-unsupported,
|
||||
.monaco-editor .debug-breakpoint-column.debug-breakpoint-unsupported-column::before {
|
||||
background: url('breakpoint-unsupported.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.monaco-editor .debug-top-stack-frame.debug-breakpoint,
|
||||
.monaco-editor .debug-top-stack-frame.debug-breakpoint-conditional,
|
||||
.monaco-editor .debug-top-stack-frame.debug-breakpoint-log,
|
||||
.monaco-editor .debug-breakpoint-column.debug-breakpoint-column.debug-top-stack-frame-column::before {
|
||||
background: url('current-and-breakpoint.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.monaco-editor .debug-focused-stack-frame.debug-breakpoint,
|
||||
.monaco-editor .debug-focused-stack-frame.debug-breakpoint-conditional,
|
||||
.monaco-editor .debug-focused-stack-frame.debug-breakpoint-log {
|
||||
background: url('stackframe-and-breakpoint.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
/* Error editor */
|
||||
.debug-error-editor:focus {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.debug-error-editor {
|
||||
padding: 5px 0 0 10px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
/* Debug status */
|
||||
/* A very precise css rule to overwrite the display set in statusbar.css */
|
||||
.monaco-workbench .part.statusbar > .statusbar-item > .debug-statusbar-item > a {
|
||||
display: flex;
|
||||
padding: 0 5px 0 5px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.statusbar .debug-statusbar-item.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.monaco-workbench .part.statusbar .debug-statusbar-item .icon {
|
||||
-webkit-mask: url('start.svg') no-repeat 50% 50%;
|
||||
-webkit-mask-size: 16px;
|
||||
display: inline-block;
|
||||
padding-right: 2px;
|
||||
width: 16px;
|
||||
}
|
||||
|
||||
.monaco-workbench .debug-view-content .monaco-list-row .monaco-tl-contents {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* Expressions */
|
||||
|
||||
.monaco-workbench .debug-viewlet .monaco-list-row .expression,
|
||||
.monaco-workbench .debug-hover-widget .monaco-list-row .expression {
|
||||
font-size: 13px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
font-family: var(--monaco-monospace-font);
|
||||
}
|
||||
|
||||
.monaco-workbench.mac .debug-viewlet .monaco-list-row .expression,
|
||||
.monaco-workbench.mac .debug-hover-widget .monaco-list-row .expression {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.monaco-workbench .monaco-list-row .expression .value {
|
||||
margin-left: 6px;
|
||||
}
|
||||
|
||||
/* White color when element is selected and list is focused. White looks better on blue selection background. */
|
||||
.monaco-workbench .monaco-list:focus .monaco-list-row.selected .expression .name,
|
||||
.monaco-workbench .monaco-list:focus .monaco-list-row.selected .expression .value {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.monaco-workbench .monaco-list-row .expression .name {
|
||||
color: #9B46B0;
|
||||
}
|
||||
|
||||
.monaco-workbench .monaco-list-row .expression .name.virtual {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.monaco-workbench > .monaco-list-row .expression .value {
|
||||
color: rgba(108, 108, 108, 0.8);
|
||||
}
|
||||
|
||||
.monaco-workbench .monaco-list-row .expression .unavailable {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.monaco-workbench .monaco-list-row .expression .error {
|
||||
color: #E51400;
|
||||
}
|
||||
|
||||
.monaco-workbench .monaco-list-row .expression .value.number {
|
||||
color: #09885A;
|
||||
}
|
||||
|
||||
.monaco-workbench .monaco-list-row .expression .value.boolean {
|
||||
color: #0000FF;
|
||||
}
|
||||
|
||||
.monaco-workbench .monaco-list-row .expression .value.string {
|
||||
color: #A31515;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench > .monaco-list-row .expression .value {
|
||||
color: rgba(204, 204, 204, 0.6);
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .monaco-list-row .expression .error {
|
||||
color: #F48771;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .monaco-list-row .expression .value.number {
|
||||
color: #B5CEA8;
|
||||
}
|
||||
|
||||
.hc-black .monaco-workbench .monaco-list-row .expression .value.number {
|
||||
color: #89d185;
|
||||
}
|
||||
|
||||
.hc-black .monaco-workbench .monaco-list-row .expression .value.boolean {
|
||||
color: #75bdfe;
|
||||
}
|
||||
|
||||
.hc-black .monaco-workbench .monaco-list-row .expression .value.string {
|
||||
color: #f48771;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .monaco-list-row .expression .value.boolean {
|
||||
color: #4E94CE;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .monaco-list-row .expression .value.string {
|
||||
color: #CE9178;
|
||||
}
|
||||
|
||||
.hc-black .monaco-workbench .monaco-list-row .expression .error {
|
||||
color: #F48771;
|
||||
}
|
||||
|
||||
/* Dark theme */
|
||||
|
||||
.vs-dark .monaco-workbench .monaco-list-row .expression .name {
|
||||
color: #C586C0;
|
||||
}
|
||||
|
||||
/* High Contrast Theming */
|
||||
|
||||
.hc-black .monaco-workbench .monaco-list-row .expression .name {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.hc-black .monaco-editor .debug-remove-token-colors {
|
||||
color:black;
|
||||
}
|
||||
113
src/vs/workbench/contrib/debug/browser/media/debugHover.css
Normal file
@@ -0,0 +1,113 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-editor .debug-hover-widget {
|
||||
position: absolute;
|
||||
margin-top: -1px;
|
||||
z-index: 50;
|
||||
animation-duration: 0.15s;
|
||||
animation-name: fadeIn;
|
||||
user-select: text;
|
||||
word-break: break-all;
|
||||
padding: 4px 5px;
|
||||
}
|
||||
|
||||
.monaco-editor .debug-hover-widget .complex-value {
|
||||
width: 324px;
|
||||
}
|
||||
|
||||
.monaco-editor .debug-hover-widget .complex-value .title {
|
||||
padding-left: 15px;
|
||||
padding-right: 2px;
|
||||
font-size: 11px;
|
||||
word-break: normal;
|
||||
text-overflow: ellipsis;
|
||||
height: 18px;
|
||||
overflow: hidden;
|
||||
border-bottom: 1px solid rgba(128, 128, 128, 0.35);
|
||||
}
|
||||
|
||||
.monaco-editor .debug-hover-widget .debug-hover-tree {
|
||||
line-height: 18px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.monaco-editor .debug-hover-widget .debug-hover-tree .monaco-list-row .monaco-tl-contents {
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
/* Disable tree highlight in debug hover tree. */
|
||||
.monaco-editor .debug-hover-widget .debug-hover-tree .monaco-list-rows .monaco-list-row:hover:not(.highlighted):not(.selected):not(.focused) {
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.monaco-editor .debug-hover-widget pre {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.monaco-editor .debugHoverHighlight {
|
||||
background-color: rgba(173, 214, 255, 0.15);
|
||||
}
|
||||
|
||||
.monaco-editor .debug-hover-widget .value {
|
||||
white-space: pre-wrap;
|
||||
color: rgba(108, 108, 108, 0.8);
|
||||
overflow: auto;
|
||||
font-family: var(--monaco-monospace-font);
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
.monaco-editor .debug-hover-widget .monaco-tl-contents .value {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.monaco-editor .debug-hover-widget .error {
|
||||
color: #E51400;
|
||||
}
|
||||
|
||||
.monaco-editor .debug-hover-widget .value.number {
|
||||
color: #09885A;
|
||||
}
|
||||
|
||||
.monaco-editor .debug-hover-widget .value.boolean {
|
||||
color: #0000FF;
|
||||
}
|
||||
|
||||
.monaco-editor .debug-hover-widget .value.string {
|
||||
color: #A31515;
|
||||
}
|
||||
|
||||
/* Dark theme */
|
||||
|
||||
.monaco-editor.vs-dark .debug-hover-widget .value,
|
||||
.monaco-editor.hc-black .debug-hover-widget .value {
|
||||
color: rgba(204, 204, 204, 0.6);
|
||||
}
|
||||
|
||||
.monaco-editor.vs-dark .debug-hover-widget .error,
|
||||
.monaco-editor.hc-black .debug-hover-widget .error {
|
||||
color: #F48771;
|
||||
}
|
||||
|
||||
.monaco-editor.vs-dark .debug-hover-widget .value.number,
|
||||
.monaco-editor.hc-black .debug-hover-widget .value.number {
|
||||
color: #B5CEA8;
|
||||
}
|
||||
|
||||
.monaco-editor.vs-dark .debug-hover-widget .value.boolean,
|
||||
.monaco-editor.hc-black .debug-hover-widget .value.boolean {
|
||||
color: #4E94CE;
|
||||
}
|
||||
|
||||
.monaco-editor.vs-dark .debug-hover-widget .value.string,
|
||||
.monaco-editor.hc-black .debug-hover-widget .value.string {
|
||||
color: #CE9178;
|
||||
}
|
||||
|
||||
.monaco-editor.vs-dark .debugHoverHighlight,
|
||||
.monaco-editor.hc-theme .debugHoverHighlight {
|
||||
background-color: rgba(38, 79, 120, 0.25);
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-workbench .debug-toolbar {
|
||||
position: absolute;
|
||||
z-index: 200;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
padding-left: 7px;
|
||||
}
|
||||
|
||||
.monaco-workbench .debug-toolbar .monaco-action-bar .action-item {
|
||||
height: 32px;
|
||||
}
|
||||
|
||||
.monaco-workbench .debug-toolbar .monaco-action-bar .action-item.select-container {
|
||||
margin-right: 7px;
|
||||
}
|
||||
|
||||
.monaco-workbench .debug-toolbar .drag-area {
|
||||
cursor: -webkit-grab;
|
||||
height: 32px;
|
||||
width: 16px;
|
||||
background: url('drag.svg') center center no-repeat;
|
||||
background-size: 16px 16px;
|
||||
}
|
||||
|
||||
.monaco-workbench .debug-toolbar .drag-area.dragged {
|
||||
cursor: -webkit-grabbing;
|
||||
}
|
||||
|
||||
.monaco-workbench .debug-toolbar .monaco-action-bar .action-item > .action-label {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-right: 0;
|
||||
background-size: 16px;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
405
src/vs/workbench/contrib/debug/browser/media/debugViewlet.css
Normal file
@@ -0,0 +1,405 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* Debug viewlet */
|
||||
|
||||
.debug-viewlet {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.debug-view-content {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* Actionbar actions */
|
||||
|
||||
.monaco-workbench .debug-action.configure {
|
||||
background: url('configure.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.monaco-workbench .debug-action.start {
|
||||
background: url('start.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.monaco-workbench .debug-action.toggle-repl {
|
||||
background: url('repl.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.monaco-workbench .debug-action.notification:before {
|
||||
content: '';
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background-color: #CC6633;
|
||||
position: absolute;
|
||||
top: 11px;
|
||||
right: 5px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid white;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .debug-action.start,
|
||||
.hc-black .monaco-workbench .debug-action.start {
|
||||
background: url('continue-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .debug-action.configure,
|
||||
.hc-black .monaco-workbench .debug-action.configure {
|
||||
background: url('configure-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .debug-action.toggle-repl,
|
||||
.hc-black .monaco-workbench .debug-action.toggle-repl {
|
||||
background: url('repl-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.monaco-workbench .part > .title > .title-actions .start-debug-action-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 11px;
|
||||
margin-right: 0.3em;
|
||||
height: 20px;
|
||||
flex-shrink: 1;
|
||||
margin-top: 7px;
|
||||
}
|
||||
|
||||
.monaco-workbench.mac .part > .title > .title-actions .start-debug-action-item {
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.monaco-workbench .part > .title > .title-actions .start-debug-action-item .icon {
|
||||
height: 20px;
|
||||
width: 20px;
|
||||
background: url('start.svg') no-repeat;
|
||||
background-size: 16px 16px;
|
||||
background-position: center center;
|
||||
flex-shrink: 0;
|
||||
transition: transform 50ms ease;
|
||||
}
|
||||
|
||||
.vs-dark .monaco-workbench .part > .title > .title-actions .start-debug-action-item .icon,
|
||||
.hc-black .monaco-workbench .part > .title > .title-actions .start-debug-action-item .icon {
|
||||
background-image: url('start-inverse.svg');
|
||||
}
|
||||
|
||||
.monaco-workbench .monaco-action-bar .start-debug-action-item .configuration .monaco-select-box {
|
||||
border: none;
|
||||
margin-top: 0px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.monaco-workbench .monaco-action-bar .start-debug-action-item .configuration.disabled .monaco-select-box {
|
||||
opacity: 0.7;
|
||||
font-style: italic;
|
||||
cursor: initial;
|
||||
}
|
||||
|
||||
.monaco-workbench .part > .title > .title-actions .start-debug-action-item .icon.active {
|
||||
transform: scale(1.272019649, 1.272019649);
|
||||
}
|
||||
|
||||
/* Debug viewlet trees */
|
||||
|
||||
.debug-viewlet .line-number {
|
||||
background: rgba(136, 136, 136, 0.3);
|
||||
border-radius: 2px;
|
||||
font-size: 0.9em;
|
||||
padding: 0 3px;
|
||||
margin-left: 0.8em;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.debug-viewlet .monaco-list-row.selected .line-number,
|
||||
.debug-viewlet .monaco-list-row.selected .thread > .state > .label,
|
||||
.debug-viewlet .monaco-list-row.selected .session > .state > .label {
|
||||
background-color: #ffffff;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.debug-viewlet .disabled {
|
||||
opacity: 0.35;
|
||||
}
|
||||
|
||||
/* Call stack */
|
||||
|
||||
.debug-viewlet .debug-call-stack-title {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.debug-viewlet .debug-call-stack-title > .pause-message {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
margin: 0px 10px;
|
||||
}
|
||||
|
||||
.debug-viewlet .debug-call-stack-title > .pause-message > .label {
|
||||
border-radius: 3px;
|
||||
padding: 1px 2px;
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
.debug-viewlet .debug-call-stack-title > .pause-message > .label.exception {
|
||||
background-color: #A31515;
|
||||
color: rgb(255, 255, 255);
|
||||
}
|
||||
|
||||
.vs-dark .debug-viewlet .debug-call-stack-title > .pause-message > .label.exception {
|
||||
background-color: #6C2022;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.hc-black .debug-viewlet .debug-call-stack-title > .pause-message > .label.exception {
|
||||
background-color: #6C2022;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.debug-viewlet .debug-call-stack .thread,
|
||||
.debug-viewlet .debug-call-stack .session {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.debug-viewlet .debug-call-stack .thread > .state,
|
||||
.debug-viewlet .debug-call-stack .session > .state {
|
||||
flex: 1;
|
||||
text-align: right;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding: 0 10px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.debug-viewlet .debug-call-stack .thread > .state > .label,
|
||||
.debug-viewlet .debug-call-stack .session > .state > .label {
|
||||
background: rgba(136, 136, 136, 0.3);
|
||||
border-radius: 2px;
|
||||
font-size: 0.8em;
|
||||
padding: 0 3px;
|
||||
}
|
||||
|
||||
.debug-viewlet .debug-call-stack .stack-frame {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
padding-right: 0.8em;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.debug-viewlet .debug-call-stack .stack-frame.label {
|
||||
text-align: center;
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.debug-viewlet .debug-call-stack .stack-frame .label {
|
||||
flex: 1;
|
||||
flex-shrink: 0;
|
||||
min-width: fit-content;
|
||||
}
|
||||
|
||||
.debug-viewlet .debug-call-stack .stack-frame.subtle {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.debug-viewlet .debug-call-stack .stack-frame.label > .file {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.debug-viewlet .debug-call-stack .stack-frame > .file {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.debug-viewlet .debug-call-stack .stack-frame > .file > .line-number.unavailable {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.debug-viewlet .debug-call-stack .monaco-list-row:not(.selected) .stack-frame > .file {
|
||||
color: rgba(108, 108, 108, 0.8);
|
||||
}
|
||||
|
||||
.debug-viewlet .debug-call-stack .stack-frame > .file > .file-name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.vs-dark .debug-viewlet .debug-call-stack .monaco-list-row:not(.selected) .stack-frame > .file {
|
||||
color: rgba(204, 204, 204, 0.6);
|
||||
}
|
||||
|
||||
.debug-viewlet .debug-call-stack .stack-frame > .file:not(:first-child) {
|
||||
margin-left: 0.8em;
|
||||
}
|
||||
|
||||
.debug-viewlet .debug-call-stack .load-more {
|
||||
font-style: italic;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.debug-viewlet .debug-call-stack .show-more {
|
||||
font-style: italic;
|
||||
opacity: 0.35;
|
||||
}
|
||||
|
||||
.monaco-workbench .debug-viewlet .monaco-list-row .expression {
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
.debug-viewlet .debug-call-stack .error {
|
||||
font-style: italic;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Variables & Expression view */
|
||||
|
||||
.debug-viewlet .scope {
|
||||
font-weight: bold;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
/* Animation of changed values in Debug viewlet */
|
||||
@keyframes debugViewletValueChanged {
|
||||
0% { background-color: rgba(86, 156, 214, 0) }
|
||||
5% { background-color: rgba(86, 156, 214, .75) }
|
||||
100% { background-color: rgba(86, 156, 214, .3) }
|
||||
}
|
||||
|
||||
@keyframes debugViewletValueChangedDark {
|
||||
0% { background-color: rgba(86, 156, 214, 0) }
|
||||
5% { background-color: rgba(86, 156, 214, .5) }
|
||||
100% { background-color: rgba(86, 156, 214, .2) }
|
||||
}
|
||||
|
||||
.debug-viewlet .monaco-list-row .expression .value.changed {
|
||||
padding: 2px;
|
||||
margin: 4px;
|
||||
border-radius: 4px;
|
||||
background-color: rgba(86, 156, 214, .5);
|
||||
animation-name: debugViewletValueChanged;
|
||||
animation-duration: .75s;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
.debug-viewlet .monaco-inputbox {
|
||||
width: 100%;
|
||||
line-height: normal;
|
||||
}
|
||||
|
||||
.debug-viewlet .inputBoxContainer {
|
||||
box-sizing: border-box;
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.debug-viewlet .debug-watch .monaco-inputbox {
|
||||
font-family: var(--monaco-monospace-font);
|
||||
}
|
||||
|
||||
.debug-viewlet .monaco-inputbox > .wrapper {
|
||||
height: 19px;
|
||||
}
|
||||
|
||||
.debug-viewlet .monaco-inputbox > .wrapper > .input {
|
||||
padding: 0px;
|
||||
color: initial;
|
||||
}
|
||||
|
||||
.debug-viewlet .watch-expression {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.debug-viewlet .watch-expression .expression {
|
||||
flex : 1;
|
||||
}
|
||||
|
||||
.debug-viewlet .debug-action.add-watch-expression,
|
||||
.debug-viewlet .debug-action.add-function-breakpoint {
|
||||
background: url('add.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .debug-viewlet .debug-action.add-watch-expression,
|
||||
.vs-dark .debug-viewlet .debug-action.add-function-breakpoint,
|
||||
.hc-black .debug-viewlet .debug-action.add-watch-expression {
|
||||
background: url('add-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .debug-viewlet .monaco-list-row .expression .value.changed {
|
||||
animation-name: debugViewletValueChanged;
|
||||
}
|
||||
|
||||
/* Breakpoints */
|
||||
|
||||
.debug-viewlet .monaco-list-row {
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
.debug-viewlet .debug-breakpoints .monaco-list-row .breakpoint {
|
||||
padding-left: 2px;
|
||||
}
|
||||
|
||||
.debug-viewlet .debug-breakpoints .breakpoint.exception {
|
||||
padding-left: 20px;
|
||||
}
|
||||
|
||||
.debug-viewlet .debug-breakpoints .breakpoint {
|
||||
display: flex;
|
||||
padding-right: 0.8em;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.debug-viewlet .debug-breakpoints .breakpoint input {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.debug-viewlet .debug-breakpoints .breakpoint > .icon {
|
||||
width: 19px;
|
||||
height: 19px;
|
||||
min-width: 19px;
|
||||
}
|
||||
|
||||
.debug-viewlet .debug-breakpoints .breakpoint > .file-path {
|
||||
opacity: 0.7;
|
||||
font-size: 0.9em;
|
||||
margin-left: 0.8em;
|
||||
flex: 1;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.debug-viewlet .debug-breakpoints .breakpoint .name {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis
|
||||
}
|
||||
|
||||
.debug-viewlet .debug-action.remove-all {
|
||||
background: url('remove-all.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.debug-viewlet .debug-action.breakpoints-activate {
|
||||
background: url('breakpoints-activate.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .debug-viewlet .debug-action.remove-all,
|
||||
.hc-black .debug-viewlet .debug-action.remove-all {
|
||||
background: url('remove-all-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .debug-viewlet .debug-action.breakpoints-activate,
|
||||
.hc-black .debug-viewlet .debug-action.breakpoints-activate {
|
||||
background: url('breakpoints-activate-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
/* No workspace view */
|
||||
|
||||
.debug-viewlet > .noworkspace-view {
|
||||
padding: 0 20px 0 20px;
|
||||
}
|
||||
|
||||
.debug-viewlet > .noworkspace-view > p {
|
||||
line-height: 1.5em;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-action-red{fill:#f48771;}</style></defs><title>disconnect</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M1,15V1H15V15Z"/></g><g id="iconBg"><path class="icon-vs-action-red" d="M2,2V14H14V2ZM9,3.5a.5.5,0,0,1,1,0V5H9Zm-3,0a.5.5,0,0,1,1,0V5H6ZM11,7H10V8.5a2,2,0,0,1-1.5,1.929V13h-1V10.429A2,2,0,0,1,6,8.5V7H5V6h6Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 598 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-action-red{fill:#a1260d;}</style></defs><title>disconnect</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M1,15V1H15V15Z"/></g><g id="iconBg"><path class="icon-vs-action-red" d="M2,2V14H14V2ZM9,3.5a.5.5,0,0,1,1,0V5H9Zm-3,0a.5.5,0,0,1,1,0V5H6ZM11,7H10V8.5a2,2,0,0,1-1.5,1.929V13h-1V10.429A2,2,0,0,1,6,8.5V7H5V6h6Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 598 B |
1
src/vs/workbench/contrib/debug/browser/media/drag.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent{fill:#f6f6f6;opacity:0;}.icon-disabled-grey{fill:#848484;}</style></defs><title>drag</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="iconBg"><path class="icon-disabled-grey" d="M7,4A1,1,0,1,1,6,3,1,1,0,0,1,7,4Zm3,1A1,1,0,1,0,9,4,1,1,0,0,0,10,5ZM6,7A1,1,0,1,0,7,8,1,1,0,0,0,6,7Zm4,0a1,1,0,1,0,1,1A1,1,0,0,0,10,7ZM6,11a1,1,0,1,0,1,1A1,1,0,0,0,6,11Zm4,0a1,1,0,1,0,1,1A1,1,0,0,0,10,11Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 534 B |
@@ -0,0 +1,42 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
.monaco-editor .zone-widget.exception-widget-container {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.monaco-editor .zone-widget .zone-widget-container.exception-widget {
|
||||
padding: 6px 10px;
|
||||
white-space: pre-wrap;
|
||||
user-select: text;
|
||||
}
|
||||
|
||||
.monaco-editor .zone-widget .zone-widget-container.exception-widget .title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.monaco-editor .zone-widget .zone-widget-container.exception-widget .description,
|
||||
.monaco-editor .zone-widget .zone-widget-container.exception-widget .stack-trace {
|
||||
font-family: var(--monaco-monospace-font);
|
||||
}
|
||||
|
||||
.monaco-editor .zone-widget .zone-widget-container.exception-widget .stack-trace {
|
||||
margin-top: 0.5em;
|
||||
max-height: 500px;
|
||||
}
|
||||
|
||||
.monaco-editor .zone-widget .zone-widget-container.exception-widget a {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.monaco-workbench.mac .zone-widget .zone-widget-container.exception-widget {
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.monaco-workbench.windows .zone-widget .zone-widget-container.exception-widget,
|
||||
.monaco-workbench.linux .zone-widget .zone-widget-container.exception-widget {
|
||||
font-size: 13px;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-action-blue{fill:#75beff;}</style></defs><title>pause</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M13,2V14H8.5V2ZM3,14H7.5V2H3Z"/></g><g id="iconBg"><path class="icon-vs-action-blue" d="M4,3H6.5V13H4ZM9.5,3V13H12V3Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 505 B |
1
src/vs/workbench/contrib/debug/browser/media/pause.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-action-blue{fill:#00539c;}</style></defs><title>pause</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M13,2V14H8.5V2ZM3,14H7.5V2H3Z"/></g><g id="iconBg"><path class="icon-vs-action-blue" d="M4,3H6.5V13H4ZM9.5,3V13H12V3Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 505 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;}</style></defs><title>remove-all</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M16,1V12H14v2H12v2H0V5H2V3H4V1Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M15,2v9H14V3H5V2ZM3,5h9v8h1V4H3ZM1,6H11v9H1Zm4.116,4.5L3.058,12.558l.884.884L6,11.384l2.058,2.058.884-.884L6.884,10.5,8.942,8.442l-.884-.884L6,9.616,3.942,7.558l-.884.884Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 636 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}.icon-vs-fg{fill:#f0eff1;}</style></defs><title>remove-all_16x</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline"><path class="icon-vs-out" d="M16,1V12H14v2H12v2H0V5H2V3H4V1Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M3,4H13v9H12V5H3ZM1,15H11V6H1ZM5,2V3h9v8h1V2Z"/></g><g id="iconFg"><path class="icon-vs-fg" d="M6.75,10.75,9,13H7.5L6,11.5,4.5,13H3l2.25-2.25L3,8.5H4.5L6,10,7.5,8.5H9Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 639 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;}</style></defs><title>repl</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M16,2V14H0V2Z"/></g><g id="iconBg"><path id="iconBg-2" data-name="iconBg" class="icon-vs-bg" d="M1,3V13H15V3Zm13,9H2V4H14ZM7.5,8,4.875,10.75,4,9.833,5.75,8,4,6.167l.875-.917Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 552 B |
189
src/vs/workbench/contrib/debug/browser/media/repl.css
Normal file
@@ -0,0 +1,189 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
/* Debug repl */
|
||||
|
||||
.repl {
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.repl .repl-tree .monaco-tl-contents {
|
||||
user-select: text;
|
||||
/* Wrap words but also do not trim whitespace #6275 */
|
||||
word-wrap: break-word;
|
||||
white-space: pre-wrap;
|
||||
/* Break on all #7533 */
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.monaco-workbench.mac .repl .repl-tree .monaco-tl-twistie.collapsible + .monaco-tl-contents,
|
||||
.monaco-workbench.mac .repl .repl-tree .monaco-tl-twistie {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
.repl .repl-tree .output.expression.value-and-source {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.repl .repl-tree .output.expression.value-and-source .value {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.repl .repl-tree .output.expression.value-and-source .source {
|
||||
margin-left: 4px;
|
||||
margin-right: 8px;
|
||||
cursor: pointer;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.repl .repl-tree .monaco-list-row {
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.repl .repl-tree .output.expression > .value {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
.repl .repl-tree .output.expression > .annotation {
|
||||
font-size: inherit;
|
||||
padding-left: 6px;
|
||||
}
|
||||
|
||||
.repl .repl-tree .output.expression .name:not(:empty) {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.repl .repl-input-wrapper {
|
||||
padding-left: 20px;
|
||||
border-top: 1px solid rgba(128, 128, 128, 0.35);
|
||||
}
|
||||
|
||||
/* Only show 'stale expansion' info when the element gets expanded. */
|
||||
.repl .repl-tree .input-output-pair > .output > .annotation::before {
|
||||
content: '';
|
||||
}
|
||||
|
||||
.hc-black .repl .repl-input-wrapper {
|
||||
border-top-color: #6FC3DF;
|
||||
}
|
||||
|
||||
.repl .repl-input-wrapper:before {
|
||||
left: 8px;
|
||||
position: absolute;
|
||||
content: '\276f';
|
||||
line-height: 18px;
|
||||
}
|
||||
|
||||
.monaco-workbench.linux .repl .repl-input-wrapper:before {
|
||||
font-size: 9px;
|
||||
}
|
||||
|
||||
/* Actions */
|
||||
.debug-action.clear-repl {
|
||||
background: url('clear-repl.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.vs-dark .debug-action.clear-repl,
|
||||
.hc-black .debug-action.clear-repl {
|
||||
background: url('clear-repl-inverse.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
/* Output coloring and styling */
|
||||
.repl .repl-tree .output.expression > .ignore {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.vs .repl .repl-tree .output.expression > .warn {
|
||||
color: #cd9731;
|
||||
}
|
||||
|
||||
.vs-dark .repl .repl-tree .output.expression > .warn {
|
||||
color: #cd9731;
|
||||
}
|
||||
|
||||
.hc-black .repl .repl-tree .output.expression > .warn {
|
||||
color: #008000;
|
||||
}
|
||||
|
||||
.vs .repl .repl-tree .output.expression > .annotation {
|
||||
color: #007ACC;
|
||||
}
|
||||
|
||||
.vs-dark .repl .repl-tree .output.expression > .annotation {
|
||||
color: #1B80B2;
|
||||
}
|
||||
|
||||
.hc-black .repl .repl-tree .output.expression > .annotation {
|
||||
color: #0000FF;
|
||||
}
|
||||
|
||||
/* ANSI Codes */
|
||||
.monaco-workbench .repl .repl-tree .output.expression .code-bold { font-weight: bold; }
|
||||
.monaco-workbench .repl .repl-tree .output.expression .code-italic { font-style: italic; }
|
||||
.monaco-workbench .repl .repl-tree .output.expression .code-underline { text-decoration: underline; }
|
||||
|
||||
/* Regular and bright color codes are currently treated the same. */
|
||||
.monaco-workbench .repl .repl-tree .output.expression .code-foreground-30, .monaco-workbench .repl .repl-tree .output.expression .code-foreground-90 { color: gray; }
|
||||
.monaco-workbench .repl .repl-tree .output.expression .code-foreground-31, .monaco-workbench .repl .repl-tree .output.expression .code-foreground-91 { color: #BE1717; }
|
||||
.monaco-workbench .repl .repl-tree .output.expression .code-foreground-32, .monaco-workbench .repl .repl-tree .output.expression .code-foreground-92 { color: #338A2F; }
|
||||
.monaco-workbench .repl .repl-tree .output.expression .code-foreground-33, .monaco-workbench .repl .repl-tree .output.expression .code-foreground-93 { color: #BEB817; }
|
||||
.monaco-workbench .repl .repl-tree .output.expression .code-foreground-34, .monaco-workbench .repl .repl-tree .output.expression .code-foreground-94 { color: darkblue; }
|
||||
.monaco-workbench .repl .repl-tree .output.expression .code-foreground-35, .monaco-workbench .repl .repl-tree .output.expression .code-foreground-95 { color: darkmagenta; }
|
||||
.monaco-workbench .repl .repl-tree .output.expression .code-foreground-36, .monaco-workbench .repl .repl-tree .output.expression .code-foreground-96 { color: darkcyan; }
|
||||
.monaco-workbench .repl .repl-tree .output.expression .code-foreground-37, .monaco-workbench .repl .repl-tree .output.expression .code-foreground-97 { color: #BDBDBD; }
|
||||
|
||||
.monaco-workbench .repl .repl-tree .output.expression .code-background-40, .monaco-workbench .repl .repl-tree .output.expression .code-background-100 { background-color: gray; }
|
||||
.monaco-workbench .repl .repl-tree .output.expression .code-background-41, .monaco-workbench .repl .repl-tree .output.expression .code-background-101 { background-color: #BE1717; }
|
||||
.monaco-workbench .repl .repl-tree .output.expression .code-background-42, .monaco-workbench .repl .repl-tree .output.expression .code-background-102 { background-color: #338A2F; }
|
||||
.monaco-workbench .repl .repl-tree .output.expression .code-background-43, .monaco-workbench .repl .repl-tree .output.expression .code-background-103 { background-color: #BEB817; }
|
||||
.monaco-workbench .repl .repl-tree .output.expression .code-background-44, .monaco-workbench .repl .repl-tree .output.expression .code-background-104 { background-color: darkblue; }
|
||||
.monaco-workbench .repl .repl-tree .output.expression .code-background-45, .monaco-workbench .repl .repl-tree .output.expression .code-background-105 { background-color: darkmagenta; }
|
||||
.monaco-workbench .repl .repl-tree .output.expression .code-background-46, .monaco-workbench .repl .repl-tree .output.expression .code-background-106 { background-color: darkcyan; }
|
||||
.monaco-workbench .repl .repl-tree .output.expression .code-background-47, .monaco-workbench .repl .repl-tree .output.expression .code-background-107 { background-color: #BDBDBD; }
|
||||
|
||||
.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-30, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-90 { color: #A0A0A0; }
|
||||
.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-31, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-91 { color: #A74747; }
|
||||
.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-32, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-92 { color: #348F34; }
|
||||
.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-33, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-93 { color: #5F4C29; }
|
||||
.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-34, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-94 { color: #6286BB; }
|
||||
.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-35, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-95 { color: #914191; }
|
||||
.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-36, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-96 { color: #218D8D; }
|
||||
.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-37, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-foreground-97 { color: #707070; }
|
||||
|
||||
.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-40, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-100 { background-color: #A0A0A0; }
|
||||
.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-41, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-101 { background-color: #A74747; }
|
||||
.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-42, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-102 { background-color: #348F34; }
|
||||
.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-43, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-103 { background-color: #5F4C29; }
|
||||
.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-44, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-104 { background-color: #6286BB; }
|
||||
.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-45, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-105 { background-color: #914191; }
|
||||
.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-46, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-106 { background-color: #218D8D; }
|
||||
.vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-47, .vs-dark .monaco-workbench .repl .repl-tree .output.expression .code-background-107 { background-color: #707070; }
|
||||
|
||||
.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-30, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-90 { color: gray; }
|
||||
.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-31, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-91 { color: #A74747; }
|
||||
.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-32, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-92 { color: #348F34; }
|
||||
.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-33, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-93 { color: #5F4C29; }
|
||||
.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-34, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-94 { color: #6286BB; }
|
||||
.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-35, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-95 { color: #914191; }
|
||||
.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-36, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-96 { color: #218D8D; }
|
||||
.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-37, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-foreground-97 { color: #707070; }
|
||||
|
||||
.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-40, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-100 { background-color: gray; }
|
||||
.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-41, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-101 { background-color: #A74747; }
|
||||
.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-42, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-102 { background-color: #348F34; }
|
||||
.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-43, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-103 { background-color: #5F4C29; }
|
||||
.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-44, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-104 { background-color: #6286BB; }
|
||||
.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-45, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-105 { background-color: #914191; }
|
||||
.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-46, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-106 { background-color: #218D8D; }
|
||||
.hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-47, .hc-black .monaco-workbench .repl .repl-tree .output.expression .code-background-107 { background-color: #707070; }
|
||||
|
||||
/* Links */
|
||||
.monaco-workbench .repl .repl-tree .output.expression a {
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
}
|
||||
1
src/vs/workbench/contrib/debug/browser/media/repl.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}</style></defs><title>repl</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M16,2V14H0V2Z"/></g><g id="iconBg"><path id="iconBg-2" data-name="iconBg" class="icon-vs-bg" d="M1,3V13H15V3Zm13,9H2V4H14ZM7.5,8,4.875,10.75,4,9.833,5.75,8,4,6.167l.875-.917Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 552 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.st0{fill:#252526;fill-opacity:0}.st1{display:none}.st2{display:inline;fill:#252526}.st3{fill:#89d185}</style><title>restart</title><path class="st0" d="M16 0v16H0V0h16z" id="canvas"/><g id="outline" class="st1" style="display: none;"><path class="st2" d="M8.5 1C6.9 1 5.3 1.5 4 2.5V1H0v8h3.1L1 9.6l.3 1c1.1 4 5.3 6.3 9.3 5.2s6.3-5.3 5.2-9.3C14.8 3.2 11.9 1 8.5 1zm0 11c-1.6 0-2.9-1-3.4-2.5L5 9h3V5h.5C10.4 5 12 6.6 12 8.5S10.4 12 8.5 12z"/></g><path class="st3" d="M15 8c0 3.6-2.9 6.5-6.5 6.5-2.9 0-5.5-1.9-6.3-4.7l1.9-.5c.7 2.4 3.2 3.8 5.6 3.1 2.4-.7 3.8-3.2 3.1-5.6S9.7 3 7.3 3.7c-1 .3-1.9.9-2.5 1.8H7v2H1v-6h2v3.1c1.9-3 5.9-4 8.9-2.1C13.8 3.7 15 5.7 15 8z" id="iconBg"/></svg>
|
||||
|
After Width: | Height: | Size: 747 B |
1
src/vs/workbench/contrib/debug/browser/media/restart.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.st0{fill:#f6f6f6;fill-opacity:0}.st1{display:none}.st2{display:inline;fill:#f6f6f6}.st3{fill:#388a34}</style><title>restart</title><path class="st0" d="M16 0v16H0V0h16z" id="canvas"/><g id="outline" class="st1" style="display: none;"><path class="st2" d="M8.5 1C6.9 1 5.3 1.5 4 2.5V1H0v8h3.1L1 9.6l.3 1c1.1 4 5.3 6.3 9.3 5.2s6.3-5.3 5.2-9.3C14.8 3.2 11.9 1 8.5 1zm0 11c-1.6 0-2.9-1-3.4-2.5L5 9h3V5h.5C10.4 5 12 6.6 12 8.5S10.4 12 8.5 12z"/></g><path class="st3" d="M15 8c0 3.6-2.9 6.5-6.5 6.5-2.9 0-5.5-1.9-6.3-4.7l1.9-.5c.7 2.4 3.2 3.8 5.6 3.1 2.4-.7 3.8-3.2 3.1-5.6S9.7 3 7.3 3.7c-1 .3-1.9.9-2.5 1.8H7v2H1v-6h2v3.1c1.9-3 5.9-4 8.9-2.1C13.8 3.7 15 5.7 15 8z" id="iconBg"/></svg>
|
||||
|
After Width: | Height: | Size: 747 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13 3H11V13H13V3ZM8.5 3V13L2 8L8.5 3Z" fill="#75BEFF"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 168 B |
@@ -0,0 +1,3 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M13 3H11V13H13V3ZM8.5 3V13L2 8L8.5 3Z" fill="#00539C"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 168 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.cls-1{fill:#9cce9c;}.icon-vs-red{fill:#e51400;}</style></defs><title>stackframe-and-breakpoint</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M13.829,8,9.454,13H4V3H9.454Z"/></g><g id="iconBg"><path class="cls-1" d="M12.5,8,9,12H5V4H9Z"/><path class="icon-vs-red" d="M10.5,8A2.5,2.5,0,1,1,8,5.5,2.5,2.5,0,0,1,10.5,8Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 595 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.cls-1{fill:#9cce9c;}</style></defs><title>stackframe-arrow</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M13.829,8,9.454,13H4V3H9.454Z"/></g><g id="iconBg"><path class="cls-1" d="M12.5,8,9,12H5V4H9Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 478 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-action-green{fill:#89d185;}</style></defs><title>continue</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14.334,8,3.667,16H3V0h.667Z"/></g><g id="iconBg"><path class="icon-vs-action-green" d="M4,1.5v13L12.667,8,4,1.5Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 505 B |
1
src/vs/workbench/contrib/debug/browser/media/start.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-action-green{fill:#388a34;}</style></defs><title>continue</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14.334,8,3.667,16H3V0h.667Z"/></g><g id="iconBg"><path class="icon-vs-action-green" d="M4,1.5v13L12.667,8,4,1.5Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 505 B |
@@ -0,0 +1,11 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0)">
|
||||
<path d="M6 13C6 13.3956 6.1173 13.7822 6.33706 14.1111C6.55682 14.44 6.86918 14.6964 7.23463 14.8478C7.60008 14.9991 8.00222 15.0387 8.39018 14.9616C8.77814 14.8844 9.13451 14.6939 9.41421 14.4142C9.69392 14.1345 9.8844 13.7781 9.96157 13.3902C10.0387 13.0022 9.99913 12.6001 9.84776 12.2346C9.69638 11.8692 9.44004 11.5568 9.11114 11.3371C8.78224 11.1173 8.39556 11 8 11C7.46957 11 6.96086 11.2107 6.58579 11.5858C6.21071 11.9609 6 12.4696 6 13Z" fill="#C5C5C5"/>
|
||||
<path d="M0.999998 2V8H7V6H4.763C5.27303 5.23323 6.00693 4.64224 6.86481 4.30742C7.72269 3.97261 8.6629 3.91024 9.55749 4.12881C10.4521 4.34737 11.2576 4.83626 11.8644 5.52893C12.4713 6.22161 12.85 7.08443 12.949 8H14.975C14.8733 6.6352 14.3422 5.3376 13.4579 4.29312C12.5736 3.24863 11.3813 2.51086 10.052 2.18549C8.72262 1.86012 7.3244 1.96385 6.05763 2.48183C4.79085 2.99981 3.72053 3.90545 3 5.069V2H0.999998Z" fill="#75BEFF"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0">
|
||||
<rect width="16" height="16" fill="white" transform="matrix(-1 0 0 1 16 0)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
11
src/vs/workbench/contrib/debug/browser/media/step-back.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0)">
|
||||
<path d="M6 13C6 13.3956 6.1173 13.7822 6.33706 14.1111C6.55682 14.44 6.86918 14.6964 7.23463 14.8478C7.60008 14.9991 8.00222 15.0387 8.39018 14.9616C8.77814 14.8844 9.13451 14.6939 9.41421 14.4142C9.69392 14.1345 9.8844 13.7781 9.96157 13.3902C10.0387 13.0022 9.99913 12.6001 9.84776 12.2346C9.69638 11.8692 9.44004 11.5568 9.11114 11.3371C8.78224 11.1173 8.39556 11 8 11C7.46957 11 6.96086 11.2107 6.58579 11.5858C6.21071 11.9609 6 12.4696 6 13Z" fill="#424242"/>
|
||||
<path d="M0.999998 2V8H7V6H4.763C5.27303 5.23323 6.00693 4.64224 6.86481 4.30742C7.72269 3.97261 8.6629 3.91024 9.55749 4.12881C10.4521 4.34737 11.2576 4.83626 11.8644 5.52893C12.4713 6.22161 12.85 7.08443 12.949 8H14.975C14.8733 6.6352 14.3422 5.3376 13.4579 4.29312C12.5736 3.24863 11.3813 2.51086 10.052 2.18549C8.72262 1.86012 7.3244 1.96385 6.05763 2.48183C4.79085 2.99981 3.72053 3.90545 3 5.069V2H0.999998Z" fill="#00539C"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0">
|
||||
<rect width="16" height="16" fill="white" transform="matrix(-1 0 0 1 16 0)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;}.icon-vs-action-blue{fill:#75beff;}</style></defs><title>step-into</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M11,13a3,3,0,1,1-4.375-2.651L2.556,6.28,5.03,3.806l.97.97V0h4V4.775l.97-.97L13.444,6.28,9.375,10.349A2.991,2.991,0,0,1,11,13Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M8,11a2,2,0,1,1-2,2A2,2,0,0,1,8,11Z"/></g><g id="colorAction"><path class="icon-vs-action-blue" d="M12.03,6.28,8,10.311,3.97,6.28,5.03,5.22,7,7.189V1H9V7.189l1.97-1.97Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 761 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}.icon-vs-action-blue{fill:#00539c;}</style></defs><title>step-into</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M11,13a3,3,0,1,1-4.375-2.651L2.556,6.28,5.03,3.806l.97.97V0h4V4.775l.97-.97L13.444,6.28,9.375,10.349A2.991,2.991,0,0,1,11,13Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M8,11a2,2,0,1,1-2,2A2,2,0,0,1,8,11Z"/></g><g id="colorAction"><path class="icon-vs-action-blue" d="M12.03,6.28,8,10.311,3.97,6.28,5.03,5.22,7,7.189V1H9V7.189l1.97-1.97Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 761 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;}.icon-vs-action-blue{fill:#75beff;}</style></defs><title>step-out</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,16H0V0H16Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M10,10.78a3,3,0,1,1-4,0V6.225l-.97.97L2.556,4.72,7.275,0H8.725l4.72,4.72L10.97,7.194,10,6.225Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M8,11a2,2,0,1,1-2,2A2,2,0,0,1,8,11Z"/></g><g id="colorAction"><path class="icon-vs-action-blue" d="M3.97,4.72,8,.689l4.03,4.03L10.97,5.78,9,3.811V10H7V3.811L5.03,5.78Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 729 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}.icon-vs-action-blue{fill:#00539c;}</style></defs><title>step-out</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,16H0V0H16Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M10,10.78a3,3,0,1,1-4,0V6.225l-.97.97L2.556,4.72,7.275,0H8.725l4.72,4.72L10.97,7.194,10,6.225Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M8,11a2,2,0,1,1-2,2A2,2,0,0,1,8,11Z"/></g><g id="colorAction"><path class="icon-vs-action-blue" d="M3.97,4.72,8,.689l4.03,4.03L10.97,5.78,9,3.811V10H7V3.811L5.03,5.78Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 729 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#c5c5c5;}.icon-vs-action-blue{fill:#75beff;}</style></defs><title>step-over</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M11,13a3,3,0,1,1-3-3A3,3,0,0,1,11,13ZM12,1V2.527A7.466,7.466,0,0,0,.028,7.923L0,8.293V9H3.945l.1-.888A3.475,3.475,0,0,1,8,5.036V9h8V1Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M10,13a2,2,0,1,1-2-2A2,2,0,0,1,10,13Z"/></g><g id="colorAction"><path class="icon-vs-action-blue" d="M15,2V8H9V6h2.237A4.481,4.481,0,0,0,3.051,8H1.025A6.482,6.482,0,0,1,13,5.069V2Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 782 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-bg{fill:#424242;}.icon-vs-action-blue{fill:#00539c;}</style></defs><title>step-over</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M11,13a3,3,0,1,1-3-3A3,3,0,0,1,11,13ZM12,1V2.527A7.466,7.466,0,0,0,.028,7.923L0,8.293V9H3.945l.1-.888A3.475,3.475,0,0,1,8,5.036V9h8V1Z"/></g><g id="iconBg"><path class="icon-vs-bg" d="M10,13a2,2,0,1,1-2-2A2,2,0,0,1,10,13Z"/></g><g id="colorAction"><path class="icon-vs-action-blue" d="M15,2V8H9V6h2.237A4.481,4.481,0,0,0,3.051,8H1.025A6.482,6.482,0,0,1,13,5.069V2Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 782 B |
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#252526;}.icon-canvas-transparent{opacity:0;}.icon-vs-action-red{fill:#f48771;}</style></defs><title>stop</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14,2V14H2V2Z"/></g><g id="iconBg"><path class="icon-vs-action-red" d="M13,3V13H3V3Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 470 B |
1
src/vs/workbench/contrib/debug/browser/media/stop.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><defs><style>.icon-canvas-transparent,.icon-vs-out{fill:#f6f6f6;}.icon-canvas-transparent{opacity:0;}.icon-vs-action-red{fill:#a1260d;}</style></defs><title>stop</title><g id="canvas"><path class="icon-canvas-transparent" d="M16,0V16H0V0Z"/></g><g id="outline" style="display: none;"><path class="icon-vs-out" d="M14,2V14H2V2Z"/></g><g id="iconBg"><path class="icon-vs-action-red" d="M13,3V13H3V3Z"/></g></svg>
|
||||
|
After Width: | Height: | Size: 470 B |
879
src/vs/workbench/contrib/debug/browser/repl.ts
Normal file
@@ -0,0 +1,879 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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/contrib/debug/browser/media/repl';
|
||||
import * as nls from 'vs/nls';
|
||||
import { URI as uri } from 'vs/base/common/uri';
|
||||
import * as errors from 'vs/base/common/errors';
|
||||
import { IAction, IActionItem, Action } from 'vs/base/common/actions';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import * as aria from 'vs/base/browser/ui/aria/aria';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||
import severity from 'vs/base/common/severity';
|
||||
import { SuggestController } from 'vs/editor/contrib/suggest/suggestController';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { registerEditorAction, ServicesAccessor, EditorAction } from 'vs/editor/browser/editorExtensions';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { IContextKeyService, IContextKey } 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 { Panel } from 'vs/workbench/browser/panel';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { memoize } from 'vs/base/common/decorators';
|
||||
import { dispose, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { EditorContextKeys } from 'vs/editor/common/editorContextKeys';
|
||||
import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget';
|
||||
import { IDebugService, REPL_ID, DEBUG_SCHEME, CONTEXT_IN_DEBUG_REPL, IDebugSession, State, IReplElement, IExpressionContainer, IExpression, IReplElementSource, IDebugConfiguration } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { HistoryNavigator } from 'vs/base/common/history';
|
||||
import { IHistoryNavigationWidget } from 'vs/base/browser/history';
|
||||
import { createAndBindHistoryNavigationWidgetScopedContextKeyService } from 'vs/platform/browser/contextScopedHistoryWidget';
|
||||
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { getSimpleEditorOptions, getSimpleCodeEditorWidgetOptions } from 'vs/workbench/contrib/codeEditor/browser/simpleEditorOptions';
|
||||
import { IDecorationOptions } from 'vs/editor/common/editorCommon';
|
||||
import { transparent, editorForeground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
|
||||
import { FocusSessionActionItem } from 'vs/workbench/contrib/debug/browser/debugActionItems';
|
||||
import { CompletionContext, CompletionList, CompletionProviderRegistry } from 'vs/editor/common/modes';
|
||||
import { first } from 'vs/base/common/arrays';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { Variable, Expression, SimpleReplElement, RawObjectReplElement } from 'vs/workbench/contrib/debug/common/debugModel';
|
||||
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
||||
import { ITreeRenderer, ITreeNode, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { renderExpressionValue } from 'vs/workbench/contrib/debug/browser/baseDebugView';
|
||||
import { handleANSIOutput } from 'vs/workbench/contrib/debug/browser/debugANSIHandling';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { LinkDetector } from 'vs/workbench/contrib/debug/browser/linkDetector';
|
||||
import { ReplCollapseAllAction, CopyAction } from 'vs/workbench/contrib/debug/browser/debugActions';
|
||||
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { removeAnsiEscapeCodes } from 'vs/base/common/strings';
|
||||
import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { FuzzyScore, createMatches } from 'vs/base/common/filters';
|
||||
import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
|
||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||
import { VariablesRenderer } from 'vs/workbench/contrib/debug/browser/variablesView';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
const HISTORY_STORAGE_KEY = 'debug.repl.history';
|
||||
const IPrivateReplService = createDecorator<IPrivateReplService>('privateReplService');
|
||||
const DECORATION_KEY = 'replinputdecoration';
|
||||
|
||||
interface IPrivateReplService {
|
||||
_serviceBrand: any;
|
||||
acceptReplInput(): void;
|
||||
getVisibleContent(): string;
|
||||
selectSession(session?: IDebugSession): void;
|
||||
clearRepl(): void;
|
||||
}
|
||||
|
||||
function revealLastElement(tree: WorkbenchAsyncDataTree<any, any, any>) {
|
||||
tree.scrollTop = tree.scrollHeight - tree.renderHeight;
|
||||
}
|
||||
|
||||
const sessionsToIgnore = new Set<IDebugSession>();
|
||||
export class Repl extends Panel implements IPrivateReplService, IHistoryNavigationWidget {
|
||||
_serviceBrand: any;
|
||||
|
||||
private static readonly REFRESH_DELAY = 100; // delay in ms to refresh the repl for new elements to show
|
||||
private static readonly REPL_INPUT_INITIAL_HEIGHT = 19;
|
||||
private static readonly REPL_INPUT_MAX_HEIGHT = 170;
|
||||
|
||||
private history: HistoryNavigator<string>;
|
||||
private tree: WorkbenchAsyncDataTree<IDebugSession, IReplElement, FuzzyScore>;
|
||||
private replDelegate: ReplDelegate;
|
||||
private container: HTMLElement;
|
||||
private replInput: CodeEditorWidget;
|
||||
private replInputContainer: HTMLElement;
|
||||
private dimension: dom.Dimension;
|
||||
private replInputHeight: number;
|
||||
private model: ITextModel;
|
||||
private historyNavigationEnablement: IContextKey<boolean>;
|
||||
private scopedInstantiationService: IInstantiationService;
|
||||
private replElementsChangeListener: IDisposable;
|
||||
private styleElement: HTMLStyleElement;
|
||||
|
||||
constructor(
|
||||
@IDebugService private readonly debugService: IDebugService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IThemeService protected themeService: IThemeService,
|
||||
@IModelService private readonly modelService: IModelService,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@ICodeEditorService codeEditorService: ICodeEditorService,
|
||||
@IContextMenuService private readonly contextMenuService: IContextMenuService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@ITextResourcePropertiesService private readonly textResourcePropertiesService: ITextResourcePropertiesService,
|
||||
@IClipboardService private readonly clipboardService: IClipboardService
|
||||
) {
|
||||
super(REPL_ID, telemetryService, themeService, storageService);
|
||||
|
||||
this.replInputHeight = Repl.REPL_INPUT_INITIAL_HEIGHT;
|
||||
this.history = new HistoryNavigator(JSON.parse(this.storageService.get(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')), 50);
|
||||
codeEditorService.registerDecorationType(DECORATION_KEY, {});
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this._register(this.debugService.getViewModel().onDidFocusSession(session => {
|
||||
if (session) {
|
||||
sessionsToIgnore.delete(session);
|
||||
}
|
||||
this.selectSession();
|
||||
}));
|
||||
this._register(this.debugService.onWillNewSession(() => {
|
||||
// Need to listen to output events for sessions which are not yet fully initialised
|
||||
const input = this.tree.getInput();
|
||||
if (!input || input.state === State.Inactive) {
|
||||
this.selectSession();
|
||||
}
|
||||
this.updateTitleArea();
|
||||
}));
|
||||
this._register(this.themeService.onThemeChange(() => {
|
||||
if (this.isVisible()) {
|
||||
this.updateInputDecoration();
|
||||
}
|
||||
}));
|
||||
this._register(this.onDidChangeVisibility(visible => {
|
||||
if (!visible) {
|
||||
dispose(this.model);
|
||||
} else {
|
||||
this.model = this.modelService.createModel('', null, uri.parse(`${DEBUG_SCHEME}:replinput`), true);
|
||||
this.replInput.setModel(this.model);
|
||||
this.updateInputDecoration();
|
||||
this.refreshReplElements(true);
|
||||
}
|
||||
}));
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('debug.console.lineHeight') || e.affectsConfiguration('debug.console.fontSize') || e.affectsConfiguration('debug.console.fontFamily')) {
|
||||
this.onDidFontChange();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
get isReadonly(): boolean {
|
||||
// Do not allow to edit inactive sessions
|
||||
const session = this.tree.getInput();
|
||||
if (session && session.state !== State.Inactive) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
showPreviousValue(): void {
|
||||
this.navigateHistory(true);
|
||||
}
|
||||
|
||||
showNextValue(): void {
|
||||
this.navigateHistory(false);
|
||||
}
|
||||
|
||||
private onDidFontChange(): void {
|
||||
if (this.styleElement) {
|
||||
const debugConsole = this.configurationService.getValue<IDebugConfiguration>('debug').console;
|
||||
const fontSize = debugConsole.fontSize;
|
||||
const fontFamily = debugConsole.fontFamily === 'default' ? 'var(--monaco-monospace-font)' : debugConsole.fontFamily;
|
||||
const lineHeight = debugConsole.lineHeight ? `${debugConsole.lineHeight}px` : '1.4em';
|
||||
|
||||
// Set the font size, font family, line height and align the twistie to be centered
|
||||
this.styleElement.innerHTML = `
|
||||
.repl .repl-tree .expression {
|
||||
font-size: ${fontSize}px;
|
||||
font-family: ${fontFamily};
|
||||
}
|
||||
|
||||
.repl .repl-tree .expression {
|
||||
line-height: ${lineHeight};
|
||||
}
|
||||
|
||||
.repl .repl-tree .monaco-tl-twistie {
|
||||
background-position-y: calc(100% - ${fontSize * 1.4 / 2 - 8}px);
|
||||
}
|
||||
`;
|
||||
|
||||
this.tree.rerender();
|
||||
}
|
||||
}
|
||||
|
||||
private navigateHistory(previous: boolean): void {
|
||||
const historyInput = previous ? this.history.previous() : this.history.next();
|
||||
if (historyInput) {
|
||||
this.replInput.setValue(historyInput);
|
||||
aria.status(historyInput);
|
||||
// always leave cursor at the end.
|
||||
this.replInput.setPosition({ lineNumber: 1, column: historyInput.length + 1 });
|
||||
this.historyNavigationEnablement.set(true);
|
||||
}
|
||||
}
|
||||
|
||||
selectSession(session?: IDebugSession): void {
|
||||
const treeInput = this.tree.getInput();
|
||||
if (!session) {
|
||||
const focusedSession = this.debugService.getViewModel().focusedSession;
|
||||
// If there is a focusedSession focus on that one, otherwise just show any other not ignored session
|
||||
if (focusedSession) {
|
||||
session = focusedSession;
|
||||
} else if (!treeInput || sessionsToIgnore.has(treeInput)) {
|
||||
session = first(this.debugService.getModel().getSessions(true), s => !sessionsToIgnore.has(s)) || undefined;
|
||||
}
|
||||
}
|
||||
if (session) {
|
||||
if (this.replElementsChangeListener) {
|
||||
this.replElementsChangeListener.dispose();
|
||||
}
|
||||
this.replElementsChangeListener = session.onDidChangeReplElements(() => {
|
||||
this.refreshReplElements(session!.getReplElements().length === 0);
|
||||
});
|
||||
|
||||
if (this.tree && treeInput !== session) {
|
||||
this.tree.setInput(session).then(() => revealLastElement(this.tree)).then(undefined, errors.onUnexpectedError);
|
||||
}
|
||||
}
|
||||
|
||||
this.replInput.updateOptions({ readOnly: this.isReadonly });
|
||||
this.updateInputDecoration();
|
||||
}
|
||||
|
||||
clearRepl(): void {
|
||||
const session = this.tree.getInput();
|
||||
if (session) {
|
||||
session.removeReplExpressions();
|
||||
if (session.state === State.Inactive) {
|
||||
// Ignore inactive sessions which got cleared - so they are not shown any more
|
||||
sessionsToIgnore.add(session);
|
||||
this.selectSession();
|
||||
this.updateTitleArea();
|
||||
}
|
||||
}
|
||||
this.replInput.focus();
|
||||
}
|
||||
|
||||
acceptReplInput(): void {
|
||||
const session = this.tree.getInput();
|
||||
if (session) {
|
||||
session.addReplExpression(this.debugService.getViewModel().focusedStackFrame, this.replInput.getValue());
|
||||
revealLastElement(this.tree);
|
||||
this.history.add(this.replInput.getValue());
|
||||
this.replInput.setValue('');
|
||||
const shouldRelayout = this.replInputHeight > Repl.REPL_INPUT_INITIAL_HEIGHT;
|
||||
this.replInputHeight = Repl.REPL_INPUT_INITIAL_HEIGHT;
|
||||
if (shouldRelayout) {
|
||||
// Trigger a layout to shrink a potential multi line input
|
||||
this.layout(this.dimension);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getVisibleContent(): string {
|
||||
let text = '';
|
||||
const lineDelimiter = this.textResourcePropertiesService.getEOL(this.model.uri);
|
||||
const traverseAndAppend = (node: ITreeNode<IReplElement, FuzzyScore>) => {
|
||||
node.children.forEach(child => {
|
||||
text += child.element.toString() + lineDelimiter;
|
||||
if (!child.collapsed && child.children.length) {
|
||||
traverseAndAppend(child);
|
||||
}
|
||||
});
|
||||
};
|
||||
traverseAndAppend(this.tree.getNode());
|
||||
|
||||
return removeAnsiEscapeCodes(text);
|
||||
}
|
||||
|
||||
layout(dimension: dom.Dimension): void {
|
||||
this.dimension = dimension;
|
||||
if (this.tree) {
|
||||
const treeHeight = dimension.height - this.replInputHeight;
|
||||
this.tree.getHTMLElement().style.height = `${treeHeight}px`;
|
||||
this.tree.layout(treeHeight, dimension.width);
|
||||
}
|
||||
this.replInputContainer.style.height = `${this.replInputHeight}px`;
|
||||
|
||||
this.replInput.layout({ width: dimension.width - 20, height: this.replInputHeight });
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
this.replInput.focus();
|
||||
}
|
||||
|
||||
getActionItem(action: IAction): IActionItem | null {
|
||||
if (action.id === SelectReplAction.ID) {
|
||||
return this.instantiationService.createInstance(SelectReplActionItem, this.selectReplAction);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
getActions(): IAction[] {
|
||||
const result: IAction[] = [];
|
||||
if (this.debugService.getModel().getSessions(true).filter(s => !sessionsToIgnore.has(s)).length > 1) {
|
||||
result.push(this.selectReplAction);
|
||||
}
|
||||
result.push(this.clearReplAction);
|
||||
|
||||
result.forEach(a => this._register(a));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// --- Cached locals
|
||||
@memoize
|
||||
private get selectReplAction(): SelectReplAction {
|
||||
return this.scopedInstantiationService.createInstance(SelectReplAction, SelectReplAction.ID, SelectReplAction.LABEL);
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get clearReplAction(): ClearReplAction {
|
||||
return this.scopedInstantiationService.createInstance(ClearReplAction, ClearReplAction.ID, ClearReplAction.LABEL);
|
||||
}
|
||||
|
||||
@memoize
|
||||
private get refreshScheduler(): RunOnceScheduler {
|
||||
return new RunOnceScheduler(() => {
|
||||
if (!this.tree.getInput()) {
|
||||
return;
|
||||
}
|
||||
const lastElementVisible = this.tree.scrollTop + this.tree.renderHeight >= this.tree.scrollHeight;
|
||||
this.tree.updateChildren().then(() => {
|
||||
if (lastElementVisible) {
|
||||
// Only scroll if we were scrolled all the way down before tree refreshed #10486
|
||||
revealLastElement(this.tree);
|
||||
}
|
||||
}, errors.onUnexpectedError);
|
||||
}, Repl.REFRESH_DELAY);
|
||||
}
|
||||
|
||||
// --- Creation
|
||||
|
||||
create(parent: HTMLElement): void {
|
||||
super.create(parent);
|
||||
this.container = dom.append(parent, $('.repl'));
|
||||
const treeContainer = dom.append(this.container, $('.repl-tree'));
|
||||
this.createReplInput(this.container);
|
||||
|
||||
this.replDelegate = new ReplDelegate(this.configurationService);
|
||||
this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, treeContainer, this.replDelegate, [
|
||||
this.instantiationService.createInstance(VariablesRenderer),
|
||||
this.instantiationService.createInstance(ReplSimpleElementsRenderer),
|
||||
new ReplExpressionsRenderer(),
|
||||
new ReplRawObjectsRenderer()
|
||||
], new ReplDataSource(), {
|
||||
ariaLabel: nls.localize('replAriaLabel', "Read Eval Print Loop Panel"),
|
||||
accessibilityProvider: new ReplAccessibilityProvider(),
|
||||
identityProvider: { getId: element => (<IReplElement>element).getId() },
|
||||
mouseSupport: false,
|
||||
keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: e => e },
|
||||
horizontalScrolling: false,
|
||||
setRowLineHeight: false,
|
||||
supportDynamicHeights: true
|
||||
}) as WorkbenchAsyncDataTree<IDebugSession, IReplElement, FuzzyScore>;
|
||||
|
||||
this.toDispose.push(this.tree.onContextMenu(e => this.onContextMenu(e)));
|
||||
// Make sure to select the session if debugging is already active
|
||||
this.selectSession();
|
||||
this.styleElement = dom.createStyleSheet(this.container);
|
||||
this.onDidFontChange();
|
||||
}
|
||||
|
||||
private createReplInput(container: HTMLElement): void {
|
||||
this.replInputContainer = dom.append(container, $('.repl-input-wrapper'));
|
||||
|
||||
const { scopedContextKeyService, historyNavigationEnablement } = createAndBindHistoryNavigationWidgetScopedContextKeyService(this.contextKeyService, { target: this.replInputContainer, historyNavigator: this });
|
||||
this.historyNavigationEnablement = historyNavigationEnablement;
|
||||
this._register(scopedContextKeyService);
|
||||
CONTEXT_IN_DEBUG_REPL.bindTo(scopedContextKeyService).set(true);
|
||||
|
||||
this.scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection(
|
||||
[IContextKeyService, scopedContextKeyService], [IPrivateReplService, this]));
|
||||
const options = getSimpleEditorOptions();
|
||||
options.readOnly = true;
|
||||
this.replInput = this.scopedInstantiationService.createInstance(CodeEditorWidget, this.replInputContainer, options, getSimpleCodeEditorWidgetOptions());
|
||||
|
||||
CompletionProviderRegistry.register({ scheme: DEBUG_SCHEME, pattern: '**/replinput', hasAccessToAllModels: true }, {
|
||||
triggerCharacters: ['.'],
|
||||
provideCompletionItems: (model: ITextModel, position: Position, _context: CompletionContext, token: CancellationToken): Promise<CompletionList> => {
|
||||
// Disable history navigation because up and down are used to navigate through the suggest widget
|
||||
this.historyNavigationEnablement.set(false);
|
||||
|
||||
const focusedSession = this.debugService.getViewModel().focusedSession;
|
||||
if (focusedSession && focusedSession.capabilities.supportsCompletionsRequest) {
|
||||
|
||||
const model = this.replInput.getModel();
|
||||
if (model) {
|
||||
const word = model.getWordAtPosition(position);
|
||||
const overwriteBefore = word ? word.word.length : 0;
|
||||
const text = model.getLineContent(position.lineNumber);
|
||||
const focusedStackFrame = this.debugService.getViewModel().focusedStackFrame;
|
||||
const frameId = focusedStackFrame ? focusedStackFrame.frameId : undefined;
|
||||
|
||||
return focusedSession.completions(frameId, text, position, overwriteBefore).then(suggestions => {
|
||||
return { suggestions };
|
||||
}, err => {
|
||||
return { suggestions: [] };
|
||||
});
|
||||
}
|
||||
}
|
||||
return Promise.resolve({ suggestions: [] });
|
||||
}
|
||||
});
|
||||
|
||||
this._register(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._register(this.replInput.onDidChangeModelContent(() => {
|
||||
const model = this.replInput.getModel();
|
||||
this.historyNavigationEnablement.set(!!model && model.getValue() === '');
|
||||
}));
|
||||
// We add the input decoration only when the focus is in the input #61126
|
||||
this._register(this.replInput.onDidFocusEditorText(() => this.updateInputDecoration()));
|
||||
this._register(this.replInput.onDidBlurEditorText(() => this.updateInputDecoration()));
|
||||
|
||||
this._register(dom.addStandardDisposableListener(this.replInputContainer, dom.EventType.FOCUS, () => dom.addClass(this.replInputContainer, 'synthetic-focus')));
|
||||
this._register(dom.addStandardDisposableListener(this.replInputContainer, dom.EventType.BLUR, () => dom.removeClass(this.replInputContainer, 'synthetic-focus')));
|
||||
}
|
||||
|
||||
private onContextMenu(e: ITreeContextMenuEvent<IReplElement>): void {
|
||||
const actions: IAction[] = [];
|
||||
actions.push(new CopyAction(CopyAction.ID, CopyAction.LABEL, this.clipboardService));
|
||||
actions.push(new Action('workbench.debug.action.copyAll', nls.localize('copyAll', "Copy All"), undefined, true, () => {
|
||||
this.clipboardService.writeText(this.getVisibleContent());
|
||||
return Promise.resolve(undefined);
|
||||
}));
|
||||
actions.push(new ReplCollapseAllAction(this.tree, this.replInput));
|
||||
actions.push(new Separator());
|
||||
actions.push(this.clearReplAction);
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => e.anchor,
|
||||
getActions: () => actions,
|
||||
getActionsContext: () => e.element
|
||||
});
|
||||
}
|
||||
|
||||
// --- Update
|
||||
|
||||
private refreshReplElements(noDelay: boolean): void {
|
||||
if (this.tree && this.isVisible()) {
|
||||
if (this.refreshScheduler.isScheduled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.refreshScheduler.schedule(noDelay ? 0 : undefined);
|
||||
}
|
||||
}
|
||||
|
||||
private updateInputDecoration(): void {
|
||||
if (!this.replInput) {
|
||||
return;
|
||||
}
|
||||
|
||||
const decorations: IDecorationOptions[] = [];
|
||||
if (this.isReadonly && this.replInput.hasTextFocus() && !this.replInput.getValue()) {
|
||||
const transparentForeground = transparent(editorForeground, 0.4)(this.themeService.getTheme());
|
||||
decorations.push({
|
||||
range: {
|
||||
startLineNumber: 0,
|
||||
endLineNumber: 0,
|
||||
startColumn: 0,
|
||||
endColumn: 1
|
||||
},
|
||||
renderOptions: {
|
||||
after: {
|
||||
contentText: nls.localize('startDebugFirst', "Please start a debug session to evaluate expressions"),
|
||||
color: transparentForeground ? transparentForeground.toString() : undefined
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.replInput.setDecorations(DECORATION_KEY, decorations);
|
||||
}
|
||||
|
||||
protected saveState(): void {
|
||||
const replHistory = this.history.getHistory();
|
||||
if (replHistory.length) {
|
||||
this.storageService.store(HISTORY_STORAGE_KEY, JSON.stringify(replHistory), StorageScope.WORKSPACE);
|
||||
} else {
|
||||
this.storageService.remove(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE);
|
||||
}
|
||||
|
||||
super.saveState();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.replInput.dispose();
|
||||
if (this.replElementsChangeListener) {
|
||||
this.replElementsChangeListener.dispose();
|
||||
}
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
// Repl tree
|
||||
|
||||
interface IExpressionTemplateData {
|
||||
input: HTMLElement;
|
||||
output: HTMLElement;
|
||||
value: HTMLElement;
|
||||
annotation: HTMLElement;
|
||||
label: HighlightedLabel;
|
||||
}
|
||||
|
||||
interface ISimpleReplElementTemplateData {
|
||||
container: HTMLElement;
|
||||
value: HTMLElement;
|
||||
source: HTMLElement;
|
||||
getReplElementSource(): IReplElementSource | undefined;
|
||||
toDispose: IDisposable[];
|
||||
}
|
||||
|
||||
interface IRawObjectReplTemplateData {
|
||||
container: HTMLElement;
|
||||
expression: HTMLElement;
|
||||
name: HTMLElement;
|
||||
value: HTMLElement;
|
||||
annotation: HTMLElement;
|
||||
label: HighlightedLabel;
|
||||
}
|
||||
|
||||
class ReplExpressionsRenderer implements ITreeRenderer<Expression, FuzzyScore, IExpressionTemplateData> {
|
||||
static readonly ID = 'expressionRepl';
|
||||
|
||||
get templateId(): string {
|
||||
return ReplExpressionsRenderer.ID;
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): IExpressionTemplateData {
|
||||
dom.addClass(container, 'input-output-pair');
|
||||
const input = dom.append(container, $('.input.expression'));
|
||||
const label = new HighlightedLabel(input, false);
|
||||
const output = dom.append(container, $('.output.expression'));
|
||||
const value = dom.append(output, $('span.value'));
|
||||
const annotation = dom.append(output, $('span'));
|
||||
|
||||
return { input, label, output, value, annotation };
|
||||
}
|
||||
|
||||
renderElement(element: ITreeNode<Expression, FuzzyScore>, index: number, templateData: IExpressionTemplateData): void {
|
||||
const expression = element.element;
|
||||
templateData.label.set(expression.name, createMatches(element.filterData));
|
||||
renderExpressionValue(expression, templateData.value, {
|
||||
preserveWhitespace: !expression.hasChildren,
|
||||
showHover: false,
|
||||
colorize: true
|
||||
});
|
||||
if (expression.hasChildren) {
|
||||
templateData.annotation.className = 'annotation octicon octicon-info';
|
||||
templateData.annotation.title = nls.localize('stateCapture', "Object state is captured from first evaluation");
|
||||
}
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: IExpressionTemplateData): void {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
class ReplSimpleElementsRenderer implements ITreeRenderer<SimpleReplElement, FuzzyScore, ISimpleReplElementTemplateData> {
|
||||
static readonly ID = 'simpleReplElement';
|
||||
|
||||
constructor(
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@ILabelService private readonly labelService: ILabelService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService
|
||||
) { }
|
||||
|
||||
get templateId(): string {
|
||||
return ReplSimpleElementsRenderer.ID;
|
||||
}
|
||||
|
||||
@memoize
|
||||
get linkDetector(): LinkDetector {
|
||||
return this.instantiationService.createInstance(LinkDetector);
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): ISimpleReplElementTemplateData {
|
||||
const data: ISimpleReplElementTemplateData = Object.create(null);
|
||||
dom.addClass(container, 'output');
|
||||
const expression = dom.append(container, $('.output.expression.value-and-source'));
|
||||
|
||||
data.container = container;
|
||||
data.value = dom.append(expression, $('span.value'));
|
||||
data.source = dom.append(expression, $('.source'));
|
||||
data.toDispose = [];
|
||||
data.toDispose.push(dom.addDisposableListener(data.source, 'click', e => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
const source = data.getReplElementSource();
|
||||
if (source) {
|
||||
source.source.openInEditor(this.editorService, {
|
||||
startLineNumber: source.lineNumber,
|
||||
startColumn: source.column,
|
||||
endLineNumber: source.lineNumber,
|
||||
endColumn: source.column
|
||||
});
|
||||
}
|
||||
}));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
renderElement({ element }: ITreeNode<SimpleReplElement, FuzzyScore>, index: number, templateData: ISimpleReplElementTemplateData): void {
|
||||
// value
|
||||
dom.clearNode(templateData.value);
|
||||
// Reset classes to clear ansi decorations since templates are reused
|
||||
templateData.value.className = 'value';
|
||||
const result = handleANSIOutput(element.value, this.linkDetector);
|
||||
templateData.value.appendChild(result);
|
||||
|
||||
dom.addClass(templateData.value, (element.severity === severity.Warning) ? 'warn' : (element.severity === severity.Error) ? 'error' : (element.severity === severity.Ignore) ? 'ignore' : 'info');
|
||||
templateData.source.textContent = element.sourceData ? `${element.sourceData.source.name}:${element.sourceData.lineNumber}` : '';
|
||||
templateData.source.title = element.sourceData ? this.labelService.getUriLabel(element.sourceData.source.uri) : '';
|
||||
templateData.getReplElementSource = () => element.sourceData;
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: ISimpleReplElementTemplateData): void {
|
||||
dispose(templateData.toDispose);
|
||||
}
|
||||
}
|
||||
|
||||
class ReplRawObjectsRenderer implements ITreeRenderer<RawObjectReplElement, FuzzyScore, IRawObjectReplTemplateData> {
|
||||
static readonly ID = 'rawObject';
|
||||
|
||||
get templateId(): string {
|
||||
return ReplRawObjectsRenderer.ID;
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): IRawObjectReplTemplateData {
|
||||
dom.addClass(container, 'output');
|
||||
|
||||
const expression = dom.append(container, $('.output.expression'));
|
||||
const name = dom.append(expression, $('span.name'));
|
||||
const label = new HighlightedLabel(name, false);
|
||||
const value = dom.append(expression, $('span.value'));
|
||||
const annotation = dom.append(expression, $('span'));
|
||||
|
||||
return { container, expression, name, label, value, annotation };
|
||||
}
|
||||
|
||||
renderElement(node: ITreeNode<RawObjectReplElement, FuzzyScore>, index: number, templateData: IRawObjectReplTemplateData): void {
|
||||
// key
|
||||
const element = node.element;
|
||||
templateData.label.set(element.name ? `${element.name}:` : '', createMatches(node.filterData));
|
||||
if (element.name) {
|
||||
templateData.name.textContent = `${element.name}:`;
|
||||
} else {
|
||||
templateData.name.textContent = '';
|
||||
}
|
||||
|
||||
// value
|
||||
renderExpressionValue(element.value, templateData.value, {
|
||||
preserveWhitespace: true,
|
||||
showHover: false
|
||||
});
|
||||
|
||||
// annotation if any
|
||||
if (element.annotation) {
|
||||
templateData.annotation.className = 'annotation octicon octicon-info';
|
||||
templateData.annotation.title = element.annotation;
|
||||
} else {
|
||||
templateData.annotation.className = '';
|
||||
templateData.annotation.title = '';
|
||||
}
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: IRawObjectReplTemplateData): void {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
class ReplDelegate implements IListVirtualDelegate<IReplElement> {
|
||||
|
||||
constructor(private configurationService: IConfigurationService) { }
|
||||
|
||||
getHeight(element: IReplElement): number {
|
||||
// Give approximate heights. Repl has dynamic height so the tree will measure the actual height on its own.
|
||||
const fontSize = this.configurationService.getValue<IDebugConfiguration>('debug').console.fontSize;
|
||||
if (element instanceof Expression && element.hasChildren) {
|
||||
return Math.ceil(2 * 1.4 * fontSize);
|
||||
}
|
||||
|
||||
return Math.ceil(1.4 * fontSize);
|
||||
}
|
||||
|
||||
getTemplateId(element: IReplElement): string {
|
||||
if (element instanceof Variable && element.name) {
|
||||
return VariablesRenderer.ID;
|
||||
}
|
||||
if (element instanceof Expression) {
|
||||
return ReplExpressionsRenderer.ID;
|
||||
}
|
||||
if (element instanceof SimpleReplElement || (element instanceof Variable && !element.name)) {
|
||||
// Variable with no name is a top level variable which should be rendered like a repl element #17404
|
||||
return ReplSimpleElementsRenderer.ID;
|
||||
}
|
||||
|
||||
return ReplRawObjectsRenderer.ID;
|
||||
}
|
||||
|
||||
hasDynamicHeight?(element: IReplElement): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function isDebugSession(obj: any): obj is IDebugSession {
|
||||
return typeof obj.getReplElements === 'function';
|
||||
}
|
||||
|
||||
class ReplDataSource implements IAsyncDataSource<IDebugSession, IReplElement> {
|
||||
|
||||
hasChildren(element: IReplElement | IDebugSession): boolean {
|
||||
if (isDebugSession(element)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !!(<IExpressionContainer>element).hasChildren;
|
||||
}
|
||||
|
||||
getChildren(element: IReplElement | IDebugSession): Promise<IReplElement[]> {
|
||||
if (isDebugSession(element)) {
|
||||
return Promise.resolve(element.getReplElements());
|
||||
}
|
||||
if (element instanceof RawObjectReplElement) {
|
||||
return element.getChildren();
|
||||
}
|
||||
|
||||
return (<IExpression>element).getChildren();
|
||||
}
|
||||
}
|
||||
|
||||
class ReplAccessibilityProvider implements IAccessibilityProvider<IReplElement> {
|
||||
getAriaLabel(element: IReplElement): string {
|
||||
if (element instanceof Variable) {
|
||||
return nls.localize('replVariableAriaLabel', "Variable {0} has value {1}, read eval print loop, debug", element.name, element.value);
|
||||
}
|
||||
if (element instanceof Expression) {
|
||||
return nls.localize('replExpressionAriaLabel', "Expression {0} has value {1}, read eval print loop, debug", element.name, element.value);
|
||||
}
|
||||
if (element instanceof SimpleReplElement) {
|
||||
return nls.localize('replValueOutputAriaLabel', "{0}, read eval print loop, debug", element.value);
|
||||
}
|
||||
if (element instanceof RawObjectReplElement) {
|
||||
return nls.localize('replRawObjectAriaLabel', "Repl variable {0} has value {1}, read eval print loop, debug", element.name, element.value);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Repl actions and commands
|
||||
|
||||
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: CONTEXT_IN_DEBUG_REPL,
|
||||
kbOpts: {
|
||||
kbExpr: EditorContextKeys.textInputFocus,
|
||||
primary: KeyCode.Enter,
|
||||
weight: KeybindingWeight.EditorContrib
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
run(accessor: ServicesAccessor, editor: ICodeEditor): void | Promise<void> {
|
||||
SuggestController.get(editor).acceptSelectedSuggestion();
|
||||
accessor.get(IPrivateReplService).acceptReplInput();
|
||||
}
|
||||
}
|
||||
|
||||
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: CONTEXT_IN_DEBUG_REPL,
|
||||
});
|
||||
}
|
||||
|
||||
run(accessor: ServicesAccessor, editor: ICodeEditor): void | Promise<void> {
|
||||
const clipboardService = accessor.get(IClipboardService);
|
||||
clipboardService.writeText(accessor.get(IPrivateReplService).getVisibleContent());
|
||||
}
|
||||
}
|
||||
|
||||
registerEditorAction(AcceptReplInputAction);
|
||||
registerEditorAction(ReplCopyAllAction);
|
||||
|
||||
class SelectReplActionItem extends FocusSessionActionItem {
|
||||
protected getSessions(): ReadonlyArray<IDebugSession> {
|
||||
return this.debugService.getModel().getSessions(true).filter(s => !sessionsToIgnore.has(s));
|
||||
}
|
||||
}
|
||||
|
||||
class SelectReplAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.action.debug.selectRepl';
|
||||
static LABEL = nls.localize('selectRepl', "Select Debug Console");
|
||||
|
||||
constructor(id: string, label: string,
|
||||
@IDebugService private readonly debugService: IDebugService,
|
||||
@IPrivateReplService private readonly replService: IPrivateReplService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
run(sessionName: string): Promise<any> {
|
||||
const session = this.debugService.getModel().getSessions(true).filter(p => p.getLabel() === sessionName).pop();
|
||||
// If session is already the focused session we need to manualy update the tree since view model will not send a focused change event
|
||||
if (session && session.state !== State.Inactive && session !== this.debugService.getViewModel().focusedSession) {
|
||||
this.debugService.focusStackFrame(undefined, undefined, session, true);
|
||||
} else {
|
||||
this.replService.selectSession(session);
|
||||
}
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
|
||||
export class ClearReplAction extends Action {
|
||||
static readonly ID = 'workbench.debug.panel.action.clearReplAction';
|
||||
static LABEL = nls.localize('clearRepl', "Clear Console");
|
||||
|
||||
constructor(id: string, label: string,
|
||||
@IPanelService private readonly panelService: IPanelService
|
||||
) {
|
||||
super(id, label, 'debug-action clear-repl');
|
||||
}
|
||||
|
||||
run(): Promise<any> {
|
||||
const repl = <Repl>this.panelService.openPanel(REPL_ID);
|
||||
repl.clearRepl();
|
||||
aria.status(nls.localize('debugConsoleCleared', "Debug console was cleared"));
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
}
|
||||
120
src/vs/workbench/contrib/debug/browser/statusbarColorProvider.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { IDebugService, State } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_BACKGROUND, Themable, STATUS_BAR_FOREGROUND, STATUS_BAR_NO_FOLDER_BORDER, STATUS_BAR_BORDER } from 'vs/workbench/common/theme';
|
||||
import { addClass, removeClass, createStyleSheet } 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 styleElement: HTMLStyleElement;
|
||||
|
||||
constructor(
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IDebugService private readonly debugService: IDebugService,
|
||||
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
|
||||
@IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService
|
||||
) {
|
||||
super(themeService);
|
||||
|
||||
this.registerListeners();
|
||||
this.updateStyles();
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this._register(this.debugService.onDidChangeState(state => this.updateStyles()));
|
||||
this._register(this.contextService.onDidChangeWorkbenchState(state => this.updateStyles()));
|
||||
}
|
||||
|
||||
protected updateStyles(): void {
|
||||
super.updateStyles();
|
||||
|
||||
const container = this.layoutService.getContainer(Parts.STATUSBAR_PART);
|
||||
if (isStatusbarInDebugMode(this.debugService)) {
|
||||
addClass(container, 'debugging');
|
||||
} else {
|
||||
removeClass(container, 'debugging');
|
||||
}
|
||||
|
||||
// Container Colors
|
||||
const backgroundColor = this.getColor(this.getColorKey(STATUS_BAR_NO_FOLDER_BACKGROUND, STATUS_BAR_DEBUGGING_BACKGROUND, STATUS_BAR_BACKGROUND));
|
||||
container.style.backgroundColor = backgroundColor;
|
||||
container.style.color = this.getColor(this.getColorKey(STATUS_BAR_NO_FOLDER_FOREGROUND, STATUS_BAR_DEBUGGING_FOREGROUND, STATUS_BAR_FOREGROUND));
|
||||
|
||||
// Border Color
|
||||
const borderColor = this.getColor(this.getColorKey(STATUS_BAR_NO_FOLDER_BORDER, STATUS_BAR_DEBUGGING_BORDER, STATUS_BAR_BORDER)) || this.getColor(contrastBorder);
|
||||
container.style.borderTopWidth = borderColor ? '1px' : null;
|
||||
container.style.borderTopStyle = borderColor ? 'solid' : null;
|
||||
container.style.borderTopColor = borderColor;
|
||||
|
||||
// Notification Beak
|
||||
if (!this.styleElement) {
|
||||
this.styleElement = createStyleSheet(container);
|
||||
}
|
||||
|
||||
this.styleElement.innerHTML = `.monaco-workbench .part.statusbar > .statusbar-item.has-beak:before { border-bottom-color: ${backgroundColor} !important; }`;
|
||||
}
|
||||
|
||||
private getColorKey(noFolderColor: string, debuggingColor: string, normalColor: string): string {
|
||||
|
||||
// Not debugging
|
||||
if (!isStatusbarInDebugMode(this.debugService)) {
|
||||
if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY) {
|
||||
return normalColor;
|
||||
}
|
||||
|
||||
return noFolderColor;
|
||||
}
|
||||
|
||||
// Debugging
|
||||
return debuggingColor;
|
||||
}
|
||||
}
|
||||
|
||||
export function isStatusbarInDebugMode(debugService: IDebugService): boolean {
|
||||
if (debugService.state === State.Inactive || debugService.state === State.Initializing) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const session = debugService.getViewModel().focusedSession;
|
||||
const isRunningWithoutDebug = session && session.configuration && session.configuration.noDebug;
|
||||
if (isRunningWithoutDebug) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
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; }`);
|
||||
}
|
||||
});
|
||||
262
src/vs/workbench/contrib/debug/browser/variablesView.ts
Normal file
@@ -0,0 +1,262 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { CollapseAction } from 'vs/workbench/browser/viewlet';
|
||||
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { IDebugService, IExpression, IScope, CONTEXT_VARIABLES_FOCUSED, IViewModel } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { Variable, Scope } from 'vs/workbench/contrib/debug/common/debugModel';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { renderViewTree, renderVariable, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/contrib/debug/browser/baseDebugView';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { SetValueAction, AddToWatchExpressionsAction, CopyValueAction, CopyEvaluatePathAction } from 'vs/workbench/contrib/debug/browser/debugActions';
|
||||
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IViewletPanelOptions, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet';
|
||||
import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
||||
import { ITreeRenderer, ITreeNode, ITreeMouseEvent, ITreeContextMenuEvent, IAsyncDataSource } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Emitter } from 'vs/base/common/event';
|
||||
import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { FuzzyScore, createMatches } from 'vs/base/common/filters';
|
||||
import { HighlightedLabel, IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
|
||||
|
||||
const $ = dom.$;
|
||||
|
||||
export const variableSetEmitter = new Emitter<void>();
|
||||
|
||||
export class VariablesView extends ViewletPanel {
|
||||
|
||||
private onFocusStackFrameScheduler: RunOnceScheduler;
|
||||
private needsRefresh: boolean;
|
||||
private tree: WorkbenchAsyncDataTree<IViewModel | IExpression | IScope, IExpression | IScope, FuzzyScore>;
|
||||
|
||||
constructor(
|
||||
options: IViewletViewOptions,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IDebugService private readonly debugService: IDebugService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
) {
|
||||
super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('variablesSection', "Variables Section") }, keybindingService, contextMenuService, configurationService);
|
||||
|
||||
// Use scheduler to prevent unnecessary flashing
|
||||
this.onFocusStackFrameScheduler = new RunOnceScheduler(() => {
|
||||
this.needsRefresh = false;
|
||||
this.tree.updateChildren().then(() => {
|
||||
const stackFrame = this.debugService.getViewModel().focusedStackFrame;
|
||||
if (stackFrame) {
|
||||
stackFrame.getScopes().then(scopes => {
|
||||
// Expand the first scope if it is not expensive and if there is no expansion state (all are collapsed)
|
||||
if (scopes.every(s => this.tree.getNode(s).collapsed) && scopes.length > 0 && !scopes[0].expensive) {
|
||||
this.tree.expand(scopes[0]).then(undefined, onUnexpectedError);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, onUnexpectedError);
|
||||
}, 400);
|
||||
}
|
||||
|
||||
renderBody(container: HTMLElement): void {
|
||||
dom.addClass(container, 'debug-variables');
|
||||
const treeContainer = renderViewTree(container);
|
||||
|
||||
this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, treeContainer, new VariablesDelegate(),
|
||||
[this.instantiationService.createInstance(VariablesRenderer), new ScopesRenderer()],
|
||||
new VariablesDataSource(), {
|
||||
ariaLabel: nls.localize('variablesAriaTreeLabel', "Debug Variables"),
|
||||
accessibilityProvider: new VariablesAccessibilityProvider(),
|
||||
identityProvider: { getId: element => (<IExpression | IScope>element).getId() },
|
||||
keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: e => e }
|
||||
}) as WorkbenchAsyncDataTree<IViewModel | IExpression | IScope, IExpression | IScope, FuzzyScore>;
|
||||
|
||||
this.tree.setInput(this.debugService.getViewModel()).then(null, onUnexpectedError);
|
||||
|
||||
CONTEXT_VARIABLES_FOCUSED.bindTo(this.tree.contextKeyService);
|
||||
|
||||
const collapseAction = new CollapseAction(this.tree, true, 'explorer-action collapse-explorer');
|
||||
this.toolbar.setActions([collapseAction])();
|
||||
this.tree.updateChildren();
|
||||
|
||||
this.disposables.push(this.debugService.getViewModel().onDidFocusStackFrame(sf => {
|
||||
if (!this.isBodyVisible()) {
|
||||
this.needsRefresh = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Refresh the tree immediately if the user explictly changed stack frames.
|
||||
// Otherwise postpone the refresh until user stops stepping.
|
||||
const timeout = sf.explicit ? 0 : undefined;
|
||||
this.onFocusStackFrameScheduler.schedule(timeout);
|
||||
}));
|
||||
this.disposables.push(variableSetEmitter.event(() => this.tree.updateChildren()));
|
||||
this.disposables.push(this.tree.onMouseDblClick(e => this.onMouseDblClick(e)));
|
||||
this.disposables.push(this.tree.onContextMenu(e => this.onContextMenu(e)));
|
||||
|
||||
this.disposables.push(this.onDidChangeBodyVisibility(visible => {
|
||||
if (visible && this.needsRefresh) {
|
||||
this.onFocusStackFrameScheduler.schedule();
|
||||
}
|
||||
}));
|
||||
this.disposables.push(this.debugService.getViewModel().onDidSelectExpression(e => {
|
||||
if (e instanceof Variable) {
|
||||
this.tree.rerender(e);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
layoutBody(width: number, height: number): void {
|
||||
this.tree.layout(width, height);
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
this.tree.domFocus();
|
||||
}
|
||||
|
||||
private onMouseDblClick(e: ITreeMouseEvent<IExpression | IScope>): void {
|
||||
const session = this.debugService.getViewModel().focusedSession;
|
||||
if (session && e.element instanceof Variable && session.capabilities.supportsSetVariable) {
|
||||
this.debugService.getViewModel().setSelectedExpression(e.element);
|
||||
}
|
||||
}
|
||||
|
||||
private onContextMenu(e: ITreeContextMenuEvent<IExpression | IScope>): void {
|
||||
const element = e.element;
|
||||
if (element instanceof Variable && !!element.value) {
|
||||
const actions: IAction[] = [];
|
||||
const variable = element as Variable;
|
||||
actions.push(new SetValueAction(SetValueAction.ID, SetValueAction.LABEL, variable, this.debugService, this.keybindingService));
|
||||
actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, variable, 'variables'));
|
||||
actions.push(this.instantiationService.createInstance(CopyEvaluatePathAction, CopyEvaluatePathAction.ID, CopyEvaluatePathAction.LABEL, variable));
|
||||
actions.push(new Separator());
|
||||
actions.push(new AddToWatchExpressionsAction(AddToWatchExpressionsAction.ID, AddToWatchExpressionsAction.LABEL, variable, this.debugService, this.keybindingService));
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => e.anchor,
|
||||
getActions: () => actions,
|
||||
getActionsContext: () => element
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function isViewModel(obj: any): obj is IViewModel {
|
||||
return typeof obj.getSelectedExpression === 'function';
|
||||
}
|
||||
|
||||
export class VariablesDataSource implements IAsyncDataSource<IViewModel, IExpression | IScope> {
|
||||
|
||||
hasChildren(element: IViewModel | IExpression | IScope): boolean {
|
||||
if (isViewModel(element) || element instanceof Scope) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return element.hasChildren;
|
||||
}
|
||||
|
||||
getChildren(element: IViewModel | IExpression | IScope): Promise<(IExpression | IScope)[]> {
|
||||
if (isViewModel(element)) {
|
||||
const stackFrame = element.focusedStackFrame;
|
||||
return stackFrame ? stackFrame.getScopes() : Promise.resolve([]);
|
||||
}
|
||||
|
||||
return element.getChildren();
|
||||
}
|
||||
}
|
||||
|
||||
interface IScopeTemplateData {
|
||||
name: HTMLElement;
|
||||
label: HighlightedLabel;
|
||||
}
|
||||
|
||||
class VariablesDelegate implements IListVirtualDelegate<IExpression | IScope> {
|
||||
|
||||
getHeight(element: IExpression | IScope): number {
|
||||
return 22;
|
||||
}
|
||||
|
||||
getTemplateId(element: IExpression | IScope): string {
|
||||
if (element instanceof Scope) {
|
||||
return ScopesRenderer.ID;
|
||||
}
|
||||
|
||||
return VariablesRenderer.ID;
|
||||
}
|
||||
}
|
||||
|
||||
class ScopesRenderer implements ITreeRenderer<IScope, FuzzyScore, IScopeTemplateData> {
|
||||
|
||||
static readonly ID = 'scope';
|
||||
|
||||
get templateId(): string {
|
||||
return ScopesRenderer.ID;
|
||||
}
|
||||
|
||||
renderTemplate(container: HTMLElement): IScopeTemplateData {
|
||||
const name = dom.append(container, $('.scope'));
|
||||
const label = new HighlightedLabel(name, false);
|
||||
|
||||
return { name, label };
|
||||
}
|
||||
|
||||
renderElement(element: ITreeNode<IScope, FuzzyScore>, index: number, templateData: IScopeTemplateData): void {
|
||||
templateData.label.set(element.element.name, createMatches(element.filterData));
|
||||
}
|
||||
|
||||
disposeTemplate(templateData: IScopeTemplateData): void {
|
||||
// noop
|
||||
}
|
||||
}
|
||||
|
||||
export class VariablesRenderer extends AbstractExpressionsRenderer {
|
||||
|
||||
static readonly ID = 'variable';
|
||||
|
||||
get templateId(): string {
|
||||
return VariablesRenderer.ID;
|
||||
}
|
||||
|
||||
protected renderExpression(expression: IExpression, data: IExpressionTemplateData, highlights: IHighlight[]): void {
|
||||
renderVariable(expression as Variable, data, true, highlights);
|
||||
}
|
||||
|
||||
protected getInputBoxOptions(expression: IExpression): IInputBoxOptions {
|
||||
const variable = <Variable>expression;
|
||||
return {
|
||||
initialValue: expression.value,
|
||||
ariaLabel: nls.localize('variableValueAriaLabel', "Type new variable value"),
|
||||
validationOptions: {
|
||||
validation: () => variable.errorMessage ? ({ content: variable.errorMessage }) : null
|
||||
},
|
||||
onFinish: (value: string, success: boolean) => {
|
||||
variable.errorMessage = undefined;
|
||||
if (success && variable.value !== value) {
|
||||
variable.setVariable(value)
|
||||
// Need to force watch expressions and variables to update since a variable change can have an effect on both
|
||||
.then(() => variableSetEmitter.fire());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class VariablesAccessibilityProvider implements IAccessibilityProvider<IExpression | IScope> {
|
||||
getAriaLabel(element: IExpression | IScope): string | null {
|
||||
if (element instanceof Scope) {
|
||||
return nls.localize('variableScopeAriaLabel', "Scope {0}, variables, debug", element.name);
|
||||
}
|
||||
if (element instanceof Variable) {
|
||||
return nls.localize('variableAriaLabel', "{0} value {1}, variables, debug", element.name, element.value);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
302
src/vs/workbench/contrib/debug/browser/watchExpressionsView.ts
Normal file
@@ -0,0 +1,302 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { CollapseAction } from 'vs/workbench/browser/viewlet';
|
||||
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { IDebugService, IExpression, CONTEXT_WATCH_EXPRESSIONS_FOCUSED } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { Expression, Variable } from 'vs/workbench/contrib/debug/common/debugModel';
|
||||
import { AddWatchExpressionAction, RemoveAllWatchExpressionsAction, EditWatchExpressionAction, RemoveWatchExpressionAction, CopyValueAction } from 'vs/workbench/contrib/debug/browser/debugActions';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IAction } from 'vs/base/common/actions';
|
||||
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { renderExpressionValue, renderViewTree, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/contrib/debug/browser/baseDebugView';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IViewletPanelOptions, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet';
|
||||
import { IListVirtualDelegate } from 'vs/base/browser/ui/list/list';
|
||||
import { IAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { WorkbenchAsyncDataTree } from 'vs/platform/list/browser/listService';
|
||||
import { IAsyncDataSource, ITreeMouseEvent, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeDragOverReaction } from 'vs/base/browser/ui/tree/tree';
|
||||
import { IDragAndDropData } from 'vs/base/browser/dnd';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView';
|
||||
import { FuzzyScore } from 'vs/base/common/filters';
|
||||
import { IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
|
||||
import { variableSetEmitter, VariablesRenderer } from 'vs/workbench/contrib/debug/browser/variablesView';
|
||||
|
||||
const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024;
|
||||
|
||||
export class WatchExpressionsView extends ViewletPanel {
|
||||
|
||||
private onWatchExpressionsUpdatedScheduler: RunOnceScheduler;
|
||||
private needsRefresh: boolean;
|
||||
private tree: WorkbenchAsyncDataTree<IDebugService | IExpression, IExpression, FuzzyScore>;
|
||||
|
||||
constructor(
|
||||
options: IViewletViewOptions,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IDebugService private readonly debugService: IDebugService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
) {
|
||||
super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('watchExpressionsSection', "Watch Expressions Section") }, keybindingService, contextMenuService, configurationService);
|
||||
|
||||
this.onWatchExpressionsUpdatedScheduler = new RunOnceScheduler(() => {
|
||||
this.needsRefresh = false;
|
||||
this.tree.updateChildren();
|
||||
}, 50);
|
||||
}
|
||||
|
||||
renderBody(container: HTMLElement): void {
|
||||
dom.addClass(container, 'debug-watch');
|
||||
const treeContainer = renderViewTree(container);
|
||||
|
||||
const expressionsRenderer = this.instantiationService.createInstance(WatchExpressionsRenderer);
|
||||
this.tree = this.instantiationService.createInstance(WorkbenchAsyncDataTree, treeContainer, new WatchExpressionsDelegate(), [expressionsRenderer, this.instantiationService.createInstance(VariablesRenderer)],
|
||||
new WatchExpressionsDataSource(), {
|
||||
ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'watchAriaTreeLabel' }, "Debug Watch Expressions"),
|
||||
accessibilityProvider: new WatchExpressionsAccessibilityProvider(),
|
||||
identityProvider: { getId: element => (<IExpression>element).getId() },
|
||||
keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: e => e },
|
||||
dnd: new WatchExpressionsDragAndDrop(this.debugService),
|
||||
}) as WorkbenchAsyncDataTree<IDebugService | IExpression, IExpression, FuzzyScore>;
|
||||
|
||||
this.tree.setInput(this.debugService).then(undefined, onUnexpectedError);
|
||||
CONTEXT_WATCH_EXPRESSIONS_FOCUSED.bindTo(this.tree.contextKeyService);
|
||||
|
||||
const addWatchExpressionAction = new AddWatchExpressionAction(AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL, this.debugService, this.keybindingService);
|
||||
const collapseAction = new CollapseAction(this.tree, true, 'explorer-action collapse-explorer');
|
||||
const removeAllWatchExpressionsAction = new RemoveAllWatchExpressionsAction(RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL, this.debugService, this.keybindingService);
|
||||
this.toolbar.setActions([addWatchExpressionAction, collapseAction, removeAllWatchExpressionsAction])();
|
||||
|
||||
this.disposables.push(this.tree.onContextMenu(e => this.onContextMenu(e)));
|
||||
this.disposables.push(this.tree.onMouseDblClick(e => this.onMouseDblClick(e)));
|
||||
this.disposables.push(this.debugService.getModel().onDidChangeWatchExpressions(we => {
|
||||
if (!this.isBodyVisible()) {
|
||||
this.needsRefresh = true;
|
||||
} else {
|
||||
this.tree.updateChildren();
|
||||
}
|
||||
}));
|
||||
this.disposables.push(this.debugService.getViewModel().onDidFocusStackFrame(() => {
|
||||
if (!this.isBodyVisible()) {
|
||||
this.needsRefresh = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.onWatchExpressionsUpdatedScheduler.isScheduled()) {
|
||||
this.onWatchExpressionsUpdatedScheduler.schedule();
|
||||
}
|
||||
}));
|
||||
this.disposables.push(variableSetEmitter.event(() => this.tree.updateChildren()));
|
||||
|
||||
this.disposables.push(this.onDidChangeBodyVisibility(visible => {
|
||||
if (visible && this.needsRefresh) {
|
||||
this.onWatchExpressionsUpdatedScheduler.schedule();
|
||||
}
|
||||
}));
|
||||
this.disposables.push(this.debugService.getViewModel().onDidSelectExpression(e => {
|
||||
if (e instanceof Expression && e.name) {
|
||||
this.tree.rerender(e);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
layoutBody(height: number, width: number): void {
|
||||
this.tree.layout(height, width);
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
this.tree.domFocus();
|
||||
}
|
||||
|
||||
private onMouseDblClick(e: ITreeMouseEvent<IExpression>): void {
|
||||
if ((e.browserEvent.target as HTMLElement).className.indexOf('twistie') >= 0) {
|
||||
// Ignore double click events on twistie
|
||||
return;
|
||||
}
|
||||
|
||||
const element = e.element;
|
||||
// double click on primitive value: open input box to be able to select and copy value.
|
||||
if (element instanceof Expression && element !== this.debugService.getViewModel().getSelectedExpression()) {
|
||||
this.debugService.getViewModel().setSelectedExpression(element);
|
||||
} else if (!element) {
|
||||
// Double click in watch panel triggers to add a new watch expression
|
||||
this.debugService.addWatchExpression();
|
||||
}
|
||||
}
|
||||
|
||||
private onContextMenu(e: ITreeContextMenuEvent<IExpression>): void {
|
||||
const element = e.element;
|
||||
const anchor = e.anchor;
|
||||
if (!anchor) {
|
||||
return;
|
||||
}
|
||||
const actions: IAction[] = [];
|
||||
|
||||
if (element instanceof Expression) {
|
||||
const expression = <Expression>element;
|
||||
actions.push(new AddWatchExpressionAction(AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL, this.debugService, this.keybindingService));
|
||||
actions.push(new EditWatchExpressionAction(EditWatchExpressionAction.ID, EditWatchExpressionAction.LABEL, this.debugService, this.keybindingService));
|
||||
if (!expression.hasChildren) {
|
||||
actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, expression.value, 'watch', this.debugService));
|
||||
}
|
||||
actions.push(new Separator());
|
||||
|
||||
actions.push(new RemoveWatchExpressionAction(RemoveWatchExpressionAction.ID, RemoveWatchExpressionAction.LABEL, this.debugService, this.keybindingService));
|
||||
actions.push(new RemoveAllWatchExpressionsAction(RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL, this.debugService, this.keybindingService));
|
||||
} else {
|
||||
actions.push(new AddWatchExpressionAction(AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL, this.debugService, this.keybindingService));
|
||||
if (element instanceof Variable) {
|
||||
const variable = element as Variable;
|
||||
if (!variable.hasChildren) {
|
||||
actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, variable, 'watch', this.debugService));
|
||||
}
|
||||
actions.push(new Separator());
|
||||
}
|
||||
actions.push(new RemoveAllWatchExpressionsAction(RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL, this.debugService, this.keybindingService));
|
||||
}
|
||||
|
||||
this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => anchor,
|
||||
getActions: () => actions,
|
||||
getActionsContext: () => element
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class WatchExpressionsDelegate implements IListVirtualDelegate<IExpression> {
|
||||
|
||||
getHeight(element: IExpression): number {
|
||||
return 22;
|
||||
}
|
||||
|
||||
getTemplateId(element: IExpression): string {
|
||||
if (element instanceof Expression) {
|
||||
return WatchExpressionsRenderer.ID;
|
||||
}
|
||||
|
||||
// Variable
|
||||
return VariablesRenderer.ID;
|
||||
}
|
||||
}
|
||||
|
||||
function isDebugService(element: any): element is IDebugService {
|
||||
return typeof element.getConfigurationManager === 'function';
|
||||
}
|
||||
|
||||
class WatchExpressionsDataSource implements IAsyncDataSource<IDebugService, IExpression> {
|
||||
|
||||
hasChildren(element: IExpression | IDebugService): boolean {
|
||||
return isDebugService(element) || element.hasChildren;
|
||||
}
|
||||
|
||||
getChildren(element: IDebugService | IExpression): Promise<Array<IExpression>> {
|
||||
if (isDebugService(element)) {
|
||||
const debugService = element as IDebugService;
|
||||
const watchExpressions = debugService.getModel().getWatchExpressions();
|
||||
const viewModel = debugService.getViewModel();
|
||||
return Promise.all(watchExpressions.map(we => !!we.name
|
||||
? we.evaluate(viewModel.focusedSession!, viewModel.focusedStackFrame!, 'watch').then(() => we)
|
||||
: Promise.resolve(we)));
|
||||
}
|
||||
|
||||
return element.getChildren();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class WatchExpressionsRenderer extends AbstractExpressionsRenderer {
|
||||
|
||||
static readonly ID = 'watchexpression';
|
||||
|
||||
get templateId() {
|
||||
return WatchExpressionsRenderer.ID;
|
||||
}
|
||||
|
||||
protected renderExpression(expression: IExpression, data: IExpressionTemplateData, highlights: IHighlight[]): void {
|
||||
const text = typeof expression.value === 'string' ? `${expression.name}:` : expression.name;
|
||||
data.label.set(text, highlights, expression.type ? expression.type : expression.value);
|
||||
renderExpressionValue(expression, data.value, {
|
||||
showChanged: true,
|
||||
maxValueLength: MAX_VALUE_RENDER_LENGTH_IN_VIEWLET,
|
||||
preserveWhitespace: false,
|
||||
showHover: true,
|
||||
colorize: true
|
||||
});
|
||||
}
|
||||
|
||||
protected getInputBoxOptions(expression: IExpression): IInputBoxOptions {
|
||||
return {
|
||||
initialValue: expression.name ? expression.name : '',
|
||||
ariaLabel: nls.localize('watchExpressionInputAriaLabel', "Type watch expression"),
|
||||
placeholder: nls.localize('watchExpressionPlaceholder', "Expression to watch"),
|
||||
onFinish: (value: string, success: boolean) => {
|
||||
if (success && value) {
|
||||
this.debugService.renameWatchExpression(expression.getId(), value);
|
||||
} else if (!expression.name) {
|
||||
this.debugService.removeWatchExpressions(expression.getId());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class WatchExpressionsAccessibilityProvider implements IAccessibilityProvider<IExpression> {
|
||||
getAriaLabel(element: IExpression): string {
|
||||
if (element instanceof Expression) {
|
||||
return nls.localize('watchExpressionAriaLabel', "{0} value {1}, watch, debug", (<Expression>element).name, (<Expression>element).value);
|
||||
}
|
||||
|
||||
// Variable
|
||||
return nls.localize('watchVariableAriaLabel', "{0} value {1}, watch, debug", (<Variable>element).name, (<Variable>element).value);
|
||||
}
|
||||
}
|
||||
|
||||
class WatchExpressionsDragAndDrop implements ITreeDragAndDrop<IExpression> {
|
||||
|
||||
constructor(private debugService: IDebugService) { }
|
||||
|
||||
onDragOver(data: IDragAndDropData): boolean | ITreeDragOverReaction {
|
||||
if (!(data instanceof ElementsDragAndDropData)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const expressions = (data as ElementsDragAndDropData<IExpression>).elements;
|
||||
return expressions.length > 0 && expressions[0] instanceof Expression;
|
||||
}
|
||||
|
||||
getDragURI(element: IExpression): string | null {
|
||||
if (!(element instanceof Expression) || element === this.debugService.getViewModel().getSelectedExpression()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return element.getId();
|
||||
}
|
||||
|
||||
getDragLabel(elements: IExpression[]): string | undefined {
|
||||
if (elements.length === 1) {
|
||||
return elements[0].name;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
drop(data: IDragAndDropData, targetElement: IExpression): void {
|
||||
if (!(data instanceof ElementsDragAndDropData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const draggedElement = (data as ElementsDragAndDropData<IExpression>).elements[0];
|
||||
const watches = this.debugService.getModel().getWatchExpressions();
|
||||
const position = targetElement instanceof Expression ? watches.indexOf(targetElement) : watches.length - 1;
|
||||
this.debugService.moveWatchExpression(draggedElement.getId(), position);
|
||||
}
|
||||
}
|
||||
834
src/vs/workbench/contrib/debug/common/debug.ts
Normal file
@@ -0,0 +1,834 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 as uri } from 'vs/base/common/uri';
|
||||
import severity from 'vs/base/common/severity';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IJSONSchemaSnippet } from 'vs/base/common/jsonSchema';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IEditorContribution } from 'vs/editor/common/editorCommon';
|
||||
import { ITextModel as EditorIModel } from 'vs/editor/common/model';
|
||||
import { IEditor } from 'vs/workbench/common/editor';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { CompletionItem } from 'vs/editor/common/modes';
|
||||
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
|
||||
import { Range, IRange } from 'vs/editor/common/core/range';
|
||||
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IViewContainersRegistry, ViewContainer, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { TaskIdentifier } from 'vs/workbench/contrib/tasks/common/tasks';
|
||||
import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
|
||||
import { IOutputService } from 'vs/workbench/contrib/output/common/output';
|
||||
|
||||
export const VIEWLET_ID = 'workbench.view.debug';
|
||||
export const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer(VIEWLET_ID);
|
||||
|
||||
export const VARIABLES_VIEW_ID = 'workbench.debug.variablesView';
|
||||
export const WATCH_VIEW_ID = 'workbench.debug.watchExpressionsView';
|
||||
export const CALLSTACK_VIEW_ID = 'workbench.debug.callStackView';
|
||||
export const LOADED_SCRIPTS_VIEW_ID = 'workbench.debug.loadedScriptsView';
|
||||
export const BREAKPOINTS_VIEW_ID = 'workbench.debug.breakPointsView';
|
||||
export const REPL_ID = 'workbench.panel.repl';
|
||||
export const DEBUG_SERVICE_ID = 'debugService';
|
||||
export const CONTEXT_DEBUG_TYPE = new RawContextKey<string>('debugType', undefined);
|
||||
export const CONTEXT_DEBUG_CONFIGURATION_TYPE = new RawContextKey<string>('debugConfigurationType', undefined);
|
||||
export const CONTEXT_DEBUG_STATE = new RawContextKey<string>('debugState', 'inactive');
|
||||
export const CONTEXT_IN_DEBUG_MODE = new RawContextKey<boolean>('inDebugMode', false);
|
||||
export const CONTEXT_IN_DEBUG_REPL = new RawContextKey<boolean>('inDebugRepl', false);
|
||||
export const CONTEXT_BREAKPOINT_WIDGET_VISIBLE = new RawContextKey<boolean>('breakpointWidgetVisible', false);
|
||||
export const CONTEXT_IN_BREAKPOINT_WIDGET = new RawContextKey<boolean>('inBreakpointWidget', false);
|
||||
export const CONTEXT_BREAKPOINTS_FOCUSED = new RawContextKey<boolean>('breakpointsFocused', true);
|
||||
export const CONTEXT_WATCH_EXPRESSIONS_FOCUSED = new RawContextKey<boolean>('watchExpressionsFocused', true);
|
||||
export const CONTEXT_VARIABLES_FOCUSED = new RawContextKey<boolean>('variablesFocused', true);
|
||||
export const CONTEXT_EXPRESSION_SELECTED = new RawContextKey<boolean>('expressionSelected', false);
|
||||
export const CONTEXT_BREAKPOINT_SELECTED = new RawContextKey<boolean>('breakpointSelected', false);
|
||||
export const CONTEXT_CALLSTACK_ITEM_TYPE = new RawContextKey<string>('callStackItemType', undefined);
|
||||
export const CONTEXT_LOADED_SCRIPTS_SUPPORTED = new RawContextKey<boolean>('loadedScriptsSupported', false);
|
||||
export const CONTEXT_LOADED_SCRIPTS_ITEM_TYPE = new RawContextKey<string>('loadedScriptsItemType', undefined);
|
||||
export const CONTEXT_FOCUSED_SESSION_IS_ATTACH = new RawContextKey<boolean>('focusedSessionIsAttach', false);
|
||||
export const CONTEXT_STEP_BACK_SUPPORTED = new RawContextKey<boolean>('stepBackSupported', false);
|
||||
|
||||
export const EDITOR_CONTRIBUTION_ID = 'editor.contrib.debug';
|
||||
export const DEBUG_SCHEME = 'debug';
|
||||
export const INTERNAL_CONSOLE_OPTIONS_SCHEMA = {
|
||||
enum: ['neverOpen', 'openOnSessionStart', 'openOnFirstSessionStart'],
|
||||
default: 'openOnFirstSessionStart',
|
||||
description: nls.localize('internalConsoleOptions', "Controls when the internal debug console should open.")
|
||||
};
|
||||
|
||||
// raw
|
||||
|
||||
export interface IRawModelUpdate {
|
||||
sessionId: string;
|
||||
threads: DebugProtocol.Thread[];
|
||||
stoppedDetails?: IRawStoppedDetails;
|
||||
}
|
||||
|
||||
export interface IRawStoppedDetails {
|
||||
reason?: string;
|
||||
description?: string;
|
||||
threadId?: number;
|
||||
text?: string;
|
||||
totalFrames?: number;
|
||||
allThreadsStopped?: boolean;
|
||||
framesErrorMessage?: string;
|
||||
}
|
||||
|
||||
// model
|
||||
|
||||
export interface ITreeElement {
|
||||
getId(): string;
|
||||
}
|
||||
|
||||
export interface IReplElement extends ITreeElement {
|
||||
toString(): string;
|
||||
readonly sourceData?: IReplElementSource;
|
||||
}
|
||||
|
||||
export interface IReplElementSource {
|
||||
readonly source: Source;
|
||||
readonly lineNumber: number;
|
||||
readonly column: number;
|
||||
}
|
||||
|
||||
export interface IExpressionContainer extends ITreeElement {
|
||||
readonly hasChildren: boolean;
|
||||
getChildren(): Promise<IExpression[]>;
|
||||
}
|
||||
|
||||
export interface IExpression extends IReplElement, IExpressionContainer {
|
||||
name: string;
|
||||
readonly value: string;
|
||||
valueChanged?: boolean;
|
||||
readonly type?: string;
|
||||
}
|
||||
|
||||
export interface IDebugger {
|
||||
createDebugAdapter(session: IDebugSession, outputService: IOutputService): Promise<IDebugAdapter>;
|
||||
runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments): Promise<number | undefined>;
|
||||
getCustomTelemetryService(): Promise<TelemetryService | undefined>;
|
||||
}
|
||||
|
||||
export const enum State {
|
||||
Inactive,
|
||||
Initializing,
|
||||
Stopped,
|
||||
Running
|
||||
}
|
||||
|
||||
export function getStateLabel(state: State): string {
|
||||
switch (state) {
|
||||
case State.Initializing: return 'initializing';
|
||||
case State.Stopped: return 'stopped';
|
||||
case State.Running: return 'running';
|
||||
default: return 'inactive';
|
||||
}
|
||||
}
|
||||
|
||||
export class AdapterEndEvent {
|
||||
error?: Error;
|
||||
sessionLengthInSeconds: number;
|
||||
emittedStopped: boolean;
|
||||
}
|
||||
|
||||
export interface LoadedSourceEvent {
|
||||
reason: 'new' | 'changed' | 'removed';
|
||||
source: Source;
|
||||
}
|
||||
|
||||
export interface IDebugSession extends ITreeElement {
|
||||
|
||||
readonly configuration: IConfig;
|
||||
readonly unresolvedConfiguration: IConfig | undefined;
|
||||
readonly state: State;
|
||||
readonly root: IWorkspaceFolder;
|
||||
|
||||
getLabel(): string;
|
||||
|
||||
getSourceForUri(modelUri: uri): Source | undefined;
|
||||
getSource(raw?: DebugProtocol.Source): Source;
|
||||
|
||||
setConfiguration(configuration: { resolved: IConfig, unresolved: IConfig | undefined }): void;
|
||||
rawUpdate(data: IRawModelUpdate): void;
|
||||
|
||||
getThread(threadId: number): IThread | undefined;
|
||||
getAllThreads(): IThread[];
|
||||
clearThreads(removeThreads: boolean, reference?: number): void;
|
||||
|
||||
getReplElements(): IReplElement[];
|
||||
|
||||
removeReplExpressions(): void;
|
||||
addReplExpression(stackFrame: IStackFrame | undefined, name: string): Promise<void>;
|
||||
appendToRepl(data: string | IExpression, severity: severity, source?: IReplElementSource): void;
|
||||
logToRepl(sev: severity, args: any[], frame?: { uri: uri, line: number, column: number });
|
||||
|
||||
// session events
|
||||
readonly onDidEndAdapter: Event<AdapterEndEvent>;
|
||||
readonly onDidChangeState: Event<void>;
|
||||
readonly onDidChangeReplElements: Event<void>;
|
||||
|
||||
// DA capabilities
|
||||
readonly capabilities: DebugProtocol.Capabilities;
|
||||
|
||||
// DAP events
|
||||
|
||||
readonly onDidLoadedSource: Event<LoadedSourceEvent>;
|
||||
readonly onDidCustomEvent: Event<DebugProtocol.Event>;
|
||||
|
||||
// Disconnects and clears state. Session can be initialized again for a new connection.
|
||||
shutdown(): void;
|
||||
|
||||
// DAP request
|
||||
|
||||
initialize(dbgr: IDebugger): Promise<void>;
|
||||
launchOrAttach(config: IConfig): Promise<void>;
|
||||
restart(): Promise<void>;
|
||||
terminate(restart?: boolean /* false */): Promise<void>;
|
||||
disconnect(restart?: boolean /* false */): Promise<void>;
|
||||
|
||||
sendBreakpoints(modelUri: uri, bpts: IBreakpoint[], sourceModified: boolean): Promise<void>;
|
||||
sendFunctionBreakpoints(fbps: IFunctionBreakpoint[]): Promise<void>;
|
||||
sendExceptionBreakpoints(exbpts: IExceptionBreakpoint[]): Promise<void>;
|
||||
|
||||
stackTrace(threadId: number, startFrame: number, levels: number): Promise<DebugProtocol.StackTraceResponse>;
|
||||
exceptionInfo(threadId: number): Promise<IExceptionInfo | undefined>;
|
||||
scopes(frameId: number): Promise<DebugProtocol.ScopesResponse>;
|
||||
variables(variablesReference: number, filter: 'indexed' | 'named' | undefined, start: number | undefined, count: number | undefined): Promise<DebugProtocol.VariablesResponse>;
|
||||
evaluate(expression: string, frameId?: number, context?: string): Promise<DebugProtocol.EvaluateResponse>;
|
||||
customRequest(request: string, args: any): Promise<DebugProtocol.Response>;
|
||||
|
||||
restartFrame(frameId: number, threadId: number): Promise<void>;
|
||||
next(threadId: number): Promise<void>;
|
||||
stepIn(threadId: number): Promise<void>;
|
||||
stepOut(threadId: number): Promise<void>;
|
||||
stepBack(threadId: number): Promise<void>;
|
||||
continue(threadId: number): Promise<void>;
|
||||
reverseContinue(threadId: number): Promise<void>;
|
||||
pause(threadId: number): Promise<void>;
|
||||
terminateThreads(threadIds: number[]): Promise<void>;
|
||||
|
||||
completions(frameId: number | undefined, text: string, position: Position, overwriteBefore: number): Promise<CompletionItem[]>;
|
||||
setVariable(variablesReference: number | undefined, name: string, value: string): Promise<DebugProtocol.SetVariableResponse>;
|
||||
loadSource(resource: uri): Promise<DebugProtocol.SourceResponse>;
|
||||
getLoadedSources(): Promise<Source[]>;
|
||||
}
|
||||
|
||||
export interface IThread extends ITreeElement {
|
||||
|
||||
/**
|
||||
* Process the thread belongs to
|
||||
*/
|
||||
readonly session: IDebugSession;
|
||||
|
||||
/**
|
||||
* Id of the thread generated by the debug adapter backend.
|
||||
*/
|
||||
readonly threadId: number;
|
||||
|
||||
/**
|
||||
* Name of the thread.
|
||||
*/
|
||||
readonly name: string;
|
||||
|
||||
/**
|
||||
* Information about the current thread stop event. Undefined if thread is not stopped.
|
||||
*/
|
||||
readonly stoppedDetails: IRawStoppedDetails | undefined;
|
||||
|
||||
/**
|
||||
* Information about the exception if an 'exception' stopped event raised and DA supports the 'exceptionInfo' request, otherwise undefined.
|
||||
*/
|
||||
readonly exceptionInfo: Promise<IExceptionInfo | undefined>;
|
||||
|
||||
readonly stateLabel: string;
|
||||
|
||||
/**
|
||||
* Gets the callstack if it has already been received from the debug
|
||||
* adapter.
|
||||
*/
|
||||
getCallStack(): ReadonlyArray<IStackFrame>;
|
||||
|
||||
/**
|
||||
* Invalidates the callstack cache
|
||||
*/
|
||||
clearCallStack(): void;
|
||||
|
||||
/**
|
||||
* Indicates whether this thread is stopped. The callstack for stopped
|
||||
* threads can be retrieved from the debug adapter.
|
||||
*/
|
||||
readonly stopped: boolean;
|
||||
|
||||
next(): Promise<any>;
|
||||
stepIn(): Promise<any>;
|
||||
stepOut(): Promise<any>;
|
||||
stepBack(): Promise<any>;
|
||||
continue(): Promise<any>;
|
||||
pause(): Promise<any>;
|
||||
terminate(): Promise<any>;
|
||||
reverseContinue(): Promise<any>;
|
||||
}
|
||||
|
||||
export interface IScope extends IExpressionContainer {
|
||||
readonly name: string;
|
||||
readonly expensive: boolean;
|
||||
readonly range?: IRange;
|
||||
}
|
||||
|
||||
export interface IStackFrame extends ITreeElement {
|
||||
readonly thread: IThread;
|
||||
readonly name: string;
|
||||
readonly presentationHint: string | undefined;
|
||||
readonly frameId: number;
|
||||
readonly range: IRange;
|
||||
readonly source: Source;
|
||||
getScopes(): Promise<IScope[]>;
|
||||
getMostSpecificScopes(range: IRange): Promise<ReadonlyArray<IScope>>;
|
||||
getSpecificSourceName(): string;
|
||||
restart(): Promise<any>;
|
||||
toString(): string;
|
||||
openInEditor(editorService: IEditorService, preserveFocus?: boolean, sideBySide?: boolean): Promise<any>;
|
||||
}
|
||||
|
||||
export interface IEnablement extends ITreeElement {
|
||||
readonly enabled: boolean;
|
||||
}
|
||||
|
||||
export interface IBreakpointData {
|
||||
readonly id?: string;
|
||||
readonly lineNumber: number;
|
||||
readonly column?: number;
|
||||
readonly enabled?: boolean;
|
||||
readonly condition?: string;
|
||||
readonly logMessage?: string;
|
||||
readonly hitCondition?: string;
|
||||
}
|
||||
|
||||
export interface IBreakpointUpdateData {
|
||||
readonly condition?: string;
|
||||
readonly hitCondition?: string;
|
||||
readonly logMessage?: string;
|
||||
readonly lineNumber?: number;
|
||||
readonly column?: number;
|
||||
}
|
||||
|
||||
export interface IBaseBreakpoint extends IEnablement {
|
||||
readonly condition?: string;
|
||||
readonly hitCondition?: string;
|
||||
readonly logMessage?: string;
|
||||
readonly verified: boolean;
|
||||
readonly idFromAdapter: number | undefined;
|
||||
}
|
||||
|
||||
export interface IBreakpoint extends IBaseBreakpoint {
|
||||
readonly uri: uri;
|
||||
readonly lineNumber: number;
|
||||
readonly endLineNumber?: number;
|
||||
readonly column?: number;
|
||||
readonly endColumn?: number;
|
||||
readonly message?: string;
|
||||
readonly adapterData: any;
|
||||
}
|
||||
|
||||
export interface IFunctionBreakpoint extends IBaseBreakpoint {
|
||||
readonly name: string;
|
||||
}
|
||||
|
||||
export interface IExceptionBreakpoint extends IEnablement {
|
||||
readonly filter: string;
|
||||
readonly label: string;
|
||||
}
|
||||
|
||||
export interface IExceptionInfo {
|
||||
readonly id?: string;
|
||||
readonly description?: string;
|
||||
readonly breakMode: string | null;
|
||||
readonly details?: DebugProtocol.ExceptionDetails;
|
||||
}
|
||||
|
||||
// model interfaces
|
||||
|
||||
export interface IViewModel extends ITreeElement {
|
||||
/**
|
||||
* Returns the focused debug session or undefined if no session is stopped.
|
||||
*/
|
||||
readonly focusedSession: IDebugSession | undefined;
|
||||
|
||||
/**
|
||||
* Returns the focused thread or undefined if no thread is stopped.
|
||||
*/
|
||||
readonly focusedThread: IThread | undefined;
|
||||
|
||||
/**
|
||||
* Returns the focused stack frame or undefined if there are no stack frames.
|
||||
*/
|
||||
readonly focusedStackFrame: IStackFrame | undefined;
|
||||
|
||||
getSelectedExpression(): IExpression | undefined;
|
||||
getSelectedFunctionBreakpoint(): IFunctionBreakpoint | undefined;
|
||||
setSelectedExpression(expression: IExpression | undefined): void;
|
||||
setSelectedFunctionBreakpoint(functionBreakpoint: IFunctionBreakpoint | undefined): void;
|
||||
|
||||
isMultiSessionView(): boolean;
|
||||
|
||||
onDidFocusSession: Event<IDebugSession | undefined>;
|
||||
onDidFocusStackFrame: Event<{ stackFrame: IStackFrame | undefined, explicit: boolean }>;
|
||||
onDidSelectExpression: Event<IExpression | undefined>;
|
||||
}
|
||||
|
||||
export interface IEvaluate {
|
||||
evaluate(session: IDebugSession, stackFrame: IStackFrame, context: string): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IDebugModel extends ITreeElement {
|
||||
getSessions(includeInactive?: boolean): IDebugSession[];
|
||||
getBreakpoints(filter?: { uri?: uri, lineNumber?: number, column?: number, enabledOnly?: boolean }): ReadonlyArray<IBreakpoint>;
|
||||
areBreakpointsActivated(): boolean;
|
||||
getFunctionBreakpoints(): ReadonlyArray<IFunctionBreakpoint>;
|
||||
getExceptionBreakpoints(): ReadonlyArray<IExceptionBreakpoint>;
|
||||
getWatchExpressions(): ReadonlyArray<IExpression & IEvaluate>;
|
||||
|
||||
onDidChangeBreakpoints: Event<IBreakpointsChangeEvent | undefined>;
|
||||
onDidChangeCallStack: Event<void>;
|
||||
onDidChangeWatchExpressions: Event<IExpression | undefined>;
|
||||
}
|
||||
|
||||
/**
|
||||
* An event describing a change to the set of [breakpoints](#debug.Breakpoint).
|
||||
*/
|
||||
export interface IBreakpointsChangeEvent {
|
||||
added?: Array<IBreakpoint | IFunctionBreakpoint>;
|
||||
removed?: Array<IBreakpoint | IFunctionBreakpoint>;
|
||||
changed?: Array<IBreakpoint | IFunctionBreakpoint>;
|
||||
sessionOnly?: boolean;
|
||||
}
|
||||
|
||||
// Debug configuration interfaces
|
||||
|
||||
export interface IDebugConfiguration {
|
||||
allowBreakpointsEverywhere: boolean;
|
||||
openDebug: 'neverOpen' | 'openOnSessionStart' | 'openOnFirstSessionStart' | 'openOnDebugBreak';
|
||||
openExplorerOnEnd: boolean;
|
||||
inlineValues: boolean;
|
||||
toolBarLocation: 'floating' | 'docked' | 'hidden';
|
||||
showInStatusBar: 'never' | 'always' | 'onFirstSessionStart';
|
||||
internalConsoleOptions: 'neverOpen' | 'openOnSessionStart' | 'openOnFirstSessionStart';
|
||||
extensionHostDebugAdapter: boolean;
|
||||
enableAllHovers: boolean;
|
||||
console: {
|
||||
fontSize: number;
|
||||
fontFamily: string;
|
||||
lineHeight: number;
|
||||
};
|
||||
}
|
||||
|
||||
export interface IGlobalConfig {
|
||||
version: string;
|
||||
compounds: ICompound[];
|
||||
configurations: IConfig[];
|
||||
}
|
||||
|
||||
export interface IEnvConfig {
|
||||
internalConsoleOptions?: 'neverOpen' | 'openOnSessionStart' | 'openOnFirstSessionStart';
|
||||
preLaunchTask?: string | TaskIdentifier;
|
||||
postDebugTask?: string | TaskIdentifier;
|
||||
debugServer?: number;
|
||||
noDebug?: boolean;
|
||||
}
|
||||
|
||||
export interface IConfig extends IEnvConfig {
|
||||
|
||||
// fundamental attributes
|
||||
type: string;
|
||||
request: string;
|
||||
name: string;
|
||||
|
||||
// platform specifics
|
||||
windows?: IEnvConfig;
|
||||
osx?: IEnvConfig;
|
||||
linux?: IEnvConfig;
|
||||
|
||||
// internals
|
||||
__sessionId?: string;
|
||||
__restart?: any;
|
||||
__autoAttach?: boolean;
|
||||
port?: number; // TODO
|
||||
}
|
||||
|
||||
export interface ICompound {
|
||||
name: string;
|
||||
configurations: (string | { name: string, folder: string })[];
|
||||
}
|
||||
|
||||
export interface IDebugAdapter extends IDisposable {
|
||||
readonly onError: Event<Error>;
|
||||
readonly onExit: Event<number | null>;
|
||||
onRequest(callback: (request: DebugProtocol.Request) => void);
|
||||
onEvent(callback: (event: DebugProtocol.Event) => void);
|
||||
startSession(): Promise<void>;
|
||||
sendMessage(message: DebugProtocol.ProtocolMessage): void;
|
||||
sendResponse(response: DebugProtocol.Response): void;
|
||||
sendRequest(command: string, args: any, clb: (result: DebugProtocol.Response) => void, timemout?: number): void;
|
||||
stopSession(): Promise<void>;
|
||||
}
|
||||
|
||||
export interface IDebugAdapterFactory extends ITerminalLauncher {
|
||||
createDebugAdapter(session: IDebugSession): IDebugAdapter;
|
||||
substituteVariables(folder: IWorkspaceFolder | undefined, config: IConfig): Promise<IConfig>;
|
||||
}
|
||||
|
||||
export interface IDebugAdapterExecutableOptions {
|
||||
cwd?: string;
|
||||
env?: { [key: string]: string };
|
||||
}
|
||||
|
||||
export interface IDebugAdapterExecutable {
|
||||
readonly type: 'executable';
|
||||
readonly command: string;
|
||||
readonly args: string[];
|
||||
readonly options?: IDebugAdapterExecutableOptions;
|
||||
}
|
||||
|
||||
export interface IDebugAdapterServer {
|
||||
readonly type: 'server';
|
||||
readonly port: number;
|
||||
readonly host?: string;
|
||||
}
|
||||
|
||||
export interface IDebugAdapterImplementation {
|
||||
readonly type: 'implementation';
|
||||
readonly implementation: any;
|
||||
}
|
||||
|
||||
export type IAdapterDescriptor = IDebugAdapterExecutable | IDebugAdapterServer | IDebugAdapterImplementation;
|
||||
|
||||
export interface IPlatformSpecificAdapterContribution {
|
||||
program?: string;
|
||||
args?: string[];
|
||||
runtime?: string;
|
||||
runtimeArgs?: string[];
|
||||
}
|
||||
|
||||
export interface IDebuggerContribution extends IPlatformSpecificAdapterContribution {
|
||||
type: string;
|
||||
label?: string;
|
||||
// debug adapter executable
|
||||
adapterExecutableCommand?: string;
|
||||
win?: IPlatformSpecificAdapterContribution;
|
||||
winx86?: IPlatformSpecificAdapterContribution;
|
||||
windows?: IPlatformSpecificAdapterContribution;
|
||||
osx?: IPlatformSpecificAdapterContribution;
|
||||
linux?: IPlatformSpecificAdapterContribution;
|
||||
|
||||
// internal
|
||||
aiKey?: string;
|
||||
|
||||
// supported languages
|
||||
languages?: string[];
|
||||
enableBreakpointsFor?: { languageIds: string[] };
|
||||
|
||||
// debug configuration support
|
||||
configurationAttributes?: any;
|
||||
initialConfigurations?: any[];
|
||||
configurationSnippets?: IJSONSchemaSnippet[];
|
||||
variables?: { [key: string]: string };
|
||||
}
|
||||
|
||||
export interface IDebugConfigurationProvider {
|
||||
readonly type: string;
|
||||
resolveDebugConfiguration?(folderUri: uri | undefined, debugConfiguration: IConfig): Promise<IConfig | null | undefined>;
|
||||
provideDebugConfigurations?(folderUri: uri | undefined): Promise<IConfig[]>;
|
||||
debugAdapterExecutable?(folderUri: uri | undefined): Promise<IAdapterDescriptor>; // TODO@AW legacy
|
||||
}
|
||||
|
||||
export interface IDebugAdapterDescriptorFactory {
|
||||
readonly type: string;
|
||||
createDebugAdapterDescriptor(session: IDebugSession): Promise<IAdapterDescriptor>;
|
||||
}
|
||||
|
||||
export interface IDebugAdapterTrackerFactory {
|
||||
readonly type: string;
|
||||
}
|
||||
|
||||
export interface ITerminalLauncher {
|
||||
runInTerminal(args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): Promise<number | undefined>;
|
||||
}
|
||||
|
||||
export interface ITerminalSettings {
|
||||
external: {
|
||||
windowsExec: string,
|
||||
osxExec: string,
|
||||
linuxExec: string
|
||||
};
|
||||
integrated: {
|
||||
shell: {
|
||||
osx: string,
|
||||
windows: string,
|
||||
linux: string
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export interface IConfigurationManager {
|
||||
/**
|
||||
* Returns true if breakpoints can be set for a given editor model. Depends on mode.
|
||||
*/
|
||||
canSetBreakpointsIn(model: EditorIModel): boolean;
|
||||
|
||||
/**
|
||||
* Returns an object containing the selected launch configuration and the selected configuration name. Both these fields can be null (no folder workspace).
|
||||
*/
|
||||
readonly selectedConfiguration: {
|
||||
launch: ILaunch | undefined;
|
||||
name: string | undefined;
|
||||
};
|
||||
|
||||
selectConfiguration(launch: ILaunch | undefined, name?: string, debugStarted?: boolean): void;
|
||||
|
||||
getLaunches(): ReadonlyArray<ILaunch>;
|
||||
|
||||
getLaunch(workspaceUri: uri | undefined): ILaunch | undefined;
|
||||
|
||||
/**
|
||||
* Allows to register on change of selected debug configuration.
|
||||
*/
|
||||
onDidSelectConfiguration: Event<void>;
|
||||
|
||||
activateDebuggers(activationEvent: string, debugType?: string): Promise<void>;
|
||||
|
||||
needsToRunInExtHost(debugType: string): boolean;
|
||||
hasDebugConfigurationProvider(debugType: string): boolean;
|
||||
|
||||
registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable;
|
||||
unregisterDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): void;
|
||||
|
||||
registerDebugAdapterDescriptorFactory(debugAdapterDescriptorFactory: IDebugAdapterDescriptorFactory): IDisposable;
|
||||
unregisterDebugAdapterDescriptorFactory(debugAdapterDescriptorFactory: IDebugAdapterDescriptorFactory): void;
|
||||
|
||||
registerDebugAdapterTrackerFactory(debugAdapterTrackerFactory: IDebugAdapterTrackerFactory): IDisposable;
|
||||
unregisterDebugAdapterTrackerFactory(debugAdapterTrackerFactory: IDebugAdapterTrackerFactory): void;
|
||||
|
||||
resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: any): Promise<any>;
|
||||
getDebugAdapterDescriptor(session: IDebugSession): Promise<IAdapterDescriptor | undefined>;
|
||||
|
||||
registerDebugAdapterFactory(debugTypes: string[], debugAdapterFactory: IDebugAdapterFactory): IDisposable;
|
||||
createDebugAdapter(session: IDebugSession): IDebugAdapter | undefined;
|
||||
|
||||
substituteVariables(debugType: string, folder: IWorkspaceFolder | undefined, config: IConfig): Promise<IConfig>;
|
||||
runInTerminal(debugType: string, args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): Promise<number | undefined>;
|
||||
}
|
||||
|
||||
export interface ILaunch {
|
||||
|
||||
/**
|
||||
* Resource pointing to the launch.json this object is wrapping.
|
||||
*/
|
||||
readonly uri: uri;
|
||||
|
||||
/**
|
||||
* Name of the launch.
|
||||
*/
|
||||
readonly name: string;
|
||||
|
||||
/**
|
||||
* Workspace of the launch. Can be undefined.
|
||||
*/
|
||||
readonly workspace: IWorkspaceFolder | undefined;
|
||||
|
||||
/**
|
||||
* Should this launch be shown in the debug dropdown.
|
||||
*/
|
||||
readonly hidden: boolean;
|
||||
|
||||
/**
|
||||
* Returns a configuration with the specified name.
|
||||
* Returns undefined if there is no configuration with the specified name.
|
||||
*/
|
||||
getConfiguration(name: string): IConfig | undefined;
|
||||
|
||||
/**
|
||||
* Returns a compound with the specified name.
|
||||
* Returns undefined if there is no compound with the specified name.
|
||||
*/
|
||||
getCompound(name: string): ICompound | undefined;
|
||||
|
||||
/**
|
||||
* Returns the names of all configurations and compounds.
|
||||
* Ignores configurations which are invalid.
|
||||
*/
|
||||
getConfigurationNames(includeCompounds?: boolean): string[];
|
||||
|
||||
/**
|
||||
* Opens the launch.json file. Creates if it does not exist.
|
||||
*/
|
||||
openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string): Promise<{ editor: IEditor | null, created: boolean }>;
|
||||
}
|
||||
|
||||
// Debug service interfaces
|
||||
|
||||
export const IDebugService = createDecorator<IDebugService>(DEBUG_SERVICE_ID);
|
||||
|
||||
export interface IDebugService {
|
||||
_serviceBrand: any;
|
||||
|
||||
/**
|
||||
* Gets the current debug state.
|
||||
*/
|
||||
readonly state: State;
|
||||
|
||||
/**
|
||||
* Allows to register on debug state changes.
|
||||
*/
|
||||
onDidChangeState: Event<State>;
|
||||
|
||||
/**
|
||||
* Allows to register on new session events.
|
||||
*/
|
||||
onDidNewSession: Event<IDebugSession>;
|
||||
|
||||
/**
|
||||
* Allows to register on sessions about to be created (not yet fully initialised)
|
||||
*/
|
||||
onWillNewSession: Event<IDebugSession>;
|
||||
|
||||
/**
|
||||
* Allows to register on end session events.
|
||||
*/
|
||||
onDidEndSession: Event<IDebugSession>;
|
||||
|
||||
/**
|
||||
* Gets the current configuration manager.
|
||||
*/
|
||||
getConfigurationManager(): IConfigurationManager;
|
||||
|
||||
/**
|
||||
* Sets the focused stack frame and evaluates all expressions against the newly focused stack frame,
|
||||
*/
|
||||
focusStackFrame(focusedStackFrame: IStackFrame | undefined, thread?: IThread, session?: IDebugSession, explicit?: boolean): void;
|
||||
|
||||
/**
|
||||
* Adds new breakpoints to the model for the file specified with the uri. Notifies debug adapter of breakpoint changes.
|
||||
*/
|
||||
addBreakpoints(uri: uri, rawBreakpoints: IBreakpointData[], context: string): Promise<IBreakpoint[]>;
|
||||
|
||||
/**
|
||||
* Updates the breakpoints.
|
||||
*/
|
||||
updateBreakpoints(uri: uri, data: { [id: string]: IBreakpointUpdateData }, sendOnResourceSaved: boolean): void;
|
||||
|
||||
/**
|
||||
* Enables or disables all breakpoints. If breakpoint is passed only enables or disables the passed breakpoint.
|
||||
* Notifies debug adapter of breakpoint changes.
|
||||
*/
|
||||
enableOrDisableBreakpoints(enable: boolean, breakpoint?: IEnablement): Promise<void>;
|
||||
|
||||
/**
|
||||
* Sets the global activated property for all breakpoints.
|
||||
* Notifies debug adapter of breakpoint changes.
|
||||
*/
|
||||
setBreakpointsActivated(activated: boolean): Promise<void>;
|
||||
|
||||
/**
|
||||
* Removes all breakpoints. If id is passed only removes the breakpoint associated with that id.
|
||||
* Notifies debug adapter of breakpoint changes.
|
||||
*/
|
||||
removeBreakpoints(id?: string): Promise<any>;
|
||||
|
||||
/**
|
||||
* Adds a new function breakpoint for the given name.
|
||||
*/
|
||||
addFunctionBreakpoint(name?: string, id?: string): void;
|
||||
|
||||
/**
|
||||
* Renames an already existing function breakpoint.
|
||||
* Notifies debug adapter of breakpoint changes.
|
||||
*/
|
||||
renameFunctionBreakpoint(id: string, newFunctionName: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Removes all function breakpoints. If id is passed only removes the function breakpoint with the passed id.
|
||||
* Notifies debug adapter of breakpoint changes.
|
||||
*/
|
||||
removeFunctionBreakpoints(id?: string): Promise<void>;
|
||||
|
||||
/**
|
||||
* Sends all breakpoints to the passed session.
|
||||
* If session is not passed, sends all breakpoints to each session.
|
||||
*/
|
||||
sendAllBreakpoints(session?: IDebugSession): Promise<any>;
|
||||
|
||||
/**
|
||||
* Adds a new watch expression and evaluates it against the debug adapter.
|
||||
*/
|
||||
addWatchExpression(name?: string): void;
|
||||
|
||||
/**
|
||||
* Renames a watch expression and evaluates it against the debug adapter.
|
||||
*/
|
||||
renameWatchExpression(id: string, newName: string): void;
|
||||
|
||||
/**
|
||||
* Moves a watch expression to a new possition. Used for reordering watch expressions.
|
||||
*/
|
||||
moveWatchExpression(id: string, position: number): void;
|
||||
|
||||
/**
|
||||
* Removes all watch expressions. If id is passed only removes the watch expression with the passed id.
|
||||
*/
|
||||
removeWatchExpressions(id?: string): void;
|
||||
|
||||
/**
|
||||
* Starts debugging. If the configOrName is not passed uses the selected configuration in the debug dropdown.
|
||||
* Also saves all files, manages if compounds are present in the configuration
|
||||
* and resolveds configurations via DebugConfigurationProviders.
|
||||
*
|
||||
* Returns true if the start debugging was successfull. For compound launches, all configurations have to start successfuly for it to return success.
|
||||
* On errors the startDebugging will throw an error, however some error and cancelations are handled and in that case will simply return false.
|
||||
*/
|
||||
startDebugging(launch: ILaunch | undefined, configOrName?: IConfig | string, noDebug?: boolean, parentSession?: IDebugSession): Promise<boolean>;
|
||||
|
||||
/**
|
||||
* Restarts a session or creates a new one if there is no active session.
|
||||
*/
|
||||
restartSession(session: IDebugSession, restartData?: any): Promise<any>;
|
||||
|
||||
/**
|
||||
* Stops the session. If the session does not exist then stops all sessions.
|
||||
*/
|
||||
stopSession(session: IDebugSession | undefined): Promise<any>;
|
||||
|
||||
/**
|
||||
* Makes unavailable all sources with the passed uri. Source will appear as grayed out in callstack view.
|
||||
*/
|
||||
sourceIsNotAvailable(uri: uri): void;
|
||||
|
||||
/**
|
||||
* Gets the current debug model.
|
||||
*/
|
||||
getModel(): IDebugModel;
|
||||
|
||||
/**
|
||||
* Gets the current view model.
|
||||
*/
|
||||
getViewModel(): IViewModel;
|
||||
}
|
||||
|
||||
// Editor interfaces
|
||||
export const enum BreakpointWidgetContext {
|
||||
CONDITION = 0,
|
||||
HIT_COUNT = 1,
|
||||
LOG_MESSAGE = 2
|
||||
}
|
||||
|
||||
export interface IDebugEditorContribution extends IEditorContribution {
|
||||
showHover(range: Range, focus: boolean): Promise<void>;
|
||||
showBreakpointWidget(lineNumber: number, column: number | undefined, context?: BreakpointWidgetContext): void;
|
||||
closeBreakpointWidget(): void;
|
||||
addLaunchConfiguration(): Promise<any>;
|
||||
}
|
||||
146
src/vs/workbench/contrib/debug/common/debugContentProvider.ts
Normal file
@@ -0,0 +1,146 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { URI as uri } from 'vs/base/common/uri';
|
||||
import { localize } from 'vs/nls';
|
||||
import { guessMimeTypes, MIME_TEXT } from 'vs/base/common/mime';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
import { ITextModelService, ITextModelContentProvider } from 'vs/editor/common/services/resolverService';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { DEBUG_SCHEME, IDebugService, IDebugSession } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
|
||||
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
|
||||
import { EditOperation } from 'vs/editor/common/core/editOperation';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
|
||||
/**
|
||||
* Debug URI format
|
||||
*
|
||||
* a debug URI represents a Source object and the debug session where the Source comes from.
|
||||
*
|
||||
* debug:arbitrary_path?session=123e4567-e89b-12d3-a456-426655440000&ref=1016
|
||||
* \___/ \____________/ \__________________________________________/ \______/
|
||||
* | | | |
|
||||
* scheme source.path session id source.reference
|
||||
*
|
||||
* the arbitrary_path and the session id are encoded with 'encodeURIComponent'
|
||||
*
|
||||
*/
|
||||
export class DebugContentProvider implements IWorkbenchContribution, ITextModelContentProvider {
|
||||
|
||||
private static INSTANCE: DebugContentProvider;
|
||||
|
||||
private readonly pendingUpdates = new Map<string, CancellationTokenSource>();
|
||||
|
||||
constructor(
|
||||
@ITextModelService textModelResolverService: ITextModelService,
|
||||
@IDebugService private readonly debugService: IDebugService,
|
||||
@IModelService private readonly modelService: IModelService,
|
||||
@IModeService private readonly modeService: IModeService,
|
||||
@IEditorWorkerService private readonly editorWorkerService: IEditorWorkerService
|
||||
) {
|
||||
textModelResolverService.registerTextModelContentProvider(DEBUG_SCHEME, this);
|
||||
DebugContentProvider.INSTANCE = this;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.pendingUpdates.forEach(cancellationSource => cancellationSource.dispose());
|
||||
}
|
||||
|
||||
provideTextContent(resource: uri): Promise<ITextModel> | null {
|
||||
return this.createOrUpdateContentModel(resource, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload the model content of the given resource.
|
||||
* If there is no model for the given resource, this method does nothing.
|
||||
*/
|
||||
static refreshDebugContent(resource: uri): void {
|
||||
if (DebugContentProvider.INSTANCE) {
|
||||
DebugContentProvider.INSTANCE.createOrUpdateContentModel(resource, false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create or reload the model content of the given resource.
|
||||
*/
|
||||
private createOrUpdateContentModel(resource: uri, createIfNotExists: boolean): Promise<ITextModel> | null {
|
||||
|
||||
const model = this.modelService.getModel(resource);
|
||||
if (!model && !createIfNotExists) {
|
||||
// nothing to do
|
||||
return null;
|
||||
}
|
||||
|
||||
let session: IDebugSession | undefined;
|
||||
|
||||
if (resource.query) {
|
||||
const data = Source.getEncodedDebugData(resource);
|
||||
session = this.debugService.getModel().getSessions().filter(p => p.getId() === data.sessionId).pop();
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
// fallback: use focused session
|
||||
session = this.debugService.getViewModel().focusedSession;
|
||||
}
|
||||
|
||||
if (!session) {
|
||||
return Promise.reject(new Error(localize('unable', "Unable to resolve the resource without a debug session")));
|
||||
}
|
||||
const createErrModel = (errMsg?: string) => {
|
||||
this.debugService.sourceIsNotAvailable(resource);
|
||||
const languageSelection = this.modeService.create(MIME_TEXT);
|
||||
const message = errMsg
|
||||
? localize('canNotResolveSourceWithError', "Could not load source '{0}': {1}.", resource.path, errMsg)
|
||||
: localize('canNotResolveSource', "Could not load source '{0}'.", resource.path);
|
||||
return this.modelService.createModel(message, languageSelection, resource);
|
||||
};
|
||||
|
||||
return session.loadSource(resource).then(response => {
|
||||
|
||||
if (response && response.body) {
|
||||
|
||||
if (model) {
|
||||
|
||||
const newContent = response.body.content;
|
||||
|
||||
// cancel and dispose an existing update
|
||||
const cancellationSource = this.pendingUpdates.get(model.id);
|
||||
if (cancellationSource) {
|
||||
cancellationSource.cancel();
|
||||
}
|
||||
|
||||
// create and keep update token
|
||||
const myToken = new CancellationTokenSource();
|
||||
this.pendingUpdates.set(model.id, myToken);
|
||||
|
||||
// update text model
|
||||
return this.editorWorkerService.computeMoreMinimalEdits(model.uri, [{ text: newContent, range: model.getFullModelRange() }]).then(edits => {
|
||||
|
||||
// remove token
|
||||
this.pendingUpdates.delete(model.id);
|
||||
|
||||
if (!myToken.token.isCancellationRequested && edits && edits.length > 0) {
|
||||
// use the evil-edit as these models show in readonly-editor only
|
||||
model.applyEdits(edits.map(edit => EditOperation.replace(Range.lift(edit.range), edit.text)));
|
||||
}
|
||||
return model;
|
||||
});
|
||||
} else {
|
||||
// create text model
|
||||
const mime = response.body.mimeType || guessMimeTypes(resource.path)[0];
|
||||
const languageSelection = this.modeService.create(mime);
|
||||
return this.modelService.createModel(response.body.content, languageSelection, resource);
|
||||
}
|
||||
}
|
||||
|
||||
return createErrModel();
|
||||
|
||||
}, (err: DebugProtocol.ErrorResponse) => createErrModel(err.message));
|
||||
}
|
||||
}
|
||||
1106
src/vs/workbench/contrib/debug/common/debugModel.ts
Normal file
1677
src/vs/workbench/contrib/debug/common/debugProtocol.d.ts
vendored
Normal file
199
src/vs/workbench/contrib/debug/common/debugSchemas.ts
Normal file
@@ -0,0 +1,199 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as extensionsRegistry from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import * as nls from 'vs/nls';
|
||||
import { IDebuggerContribution, ICompound } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { inputsSchema } from 'vs/workbench/services/configurationResolver/common/configurationResolverSchema';
|
||||
|
||||
// debuggers extension point
|
||||
export const debuggersExtPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint<IDebuggerContribution[]>({
|
||||
extensionPoint: 'debuggers',
|
||||
defaultExtensionKind: 'workspace',
|
||||
jsonSchema: {
|
||||
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'
|
||||
},
|
||||
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', "macOS specific settings."),
|
||||
type: 'object',
|
||||
properties: {
|
||||
runtime: {
|
||||
description: nls.localize('vscode.extension.contributes.debuggers.osx.runtime', "Runtime used for macOS."),
|
||||
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'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export interface IRawBreakpointContribution {
|
||||
language: string;
|
||||
}
|
||||
|
||||
// breakpoints extension point #9037
|
||||
export const breakpointsExtPoint = extensionsRegistry.ExtensionsRegistry.registerExtensionPoint<IRawBreakpointContribution[]>({
|
||||
extensionPoint: 'breakpoints',
|
||||
jsonSchema: {
|
||||
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
|
||||
const defaultCompound: ICompound = { name: 'Compound', configurations: [] };
|
||||
export const launchSchema: IJSONSchema = {
|
||||
id: launchSchemaId,
|
||||
type: 'object',
|
||||
title: nls.localize('app.launch.json.title', "Launch"),
|
||||
required: [],
|
||||
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: {
|
||||
oneOf: [{
|
||||
enum: [],
|
||||
description: nls.localize('useUniqueNames', "Please use unique configuration names.")
|
||||
}, {
|
||||
type: 'object',
|
||||
required: ['name'],
|
||||
properties: {
|
||||
name: {
|
||||
enum: [],
|
||||
description: nls.localize('app.launch.json.compound.name', "Name of compound. Appears in the launch configuration drop down menu.")
|
||||
},
|
||||
folder: {
|
||||
enum: [],
|
||||
description: nls.localize('app.launch.json.compound.folder', "Name of folder in which the compound is located.")
|
||||
}
|
||||
}
|
||||
}]
|
||||
},
|
||||
description: nls.localize('app.launch.json.compounds.configurations', "Names of configurations that will be started as part of this compound.")
|
||||
}
|
||||
},
|
||||
default: defaultCompound
|
||||
},
|
||||
default: [
|
||||
defaultCompound
|
||||
]
|
||||
},
|
||||
inputs: inputsSchema.definitions!.inputs
|
||||
}
|
||||
};
|
||||
142
src/vs/workbench/contrib/debug/common/debugSource.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 as uri } from 'vs/base/common/uri';
|
||||
import { normalize, isAbsolute } from 'vs/base/common/path';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import { DEBUG_SCHEME } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { IRange } from 'vs/editor/common/core/range';
|
||||
import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { isUri } from 'vs/workbench/contrib/debug/common/debugUtils';
|
||||
|
||||
const UNKNOWN_SOURCE_LABEL = nls.localize('unknownSource', "Unknown Source");
|
||||
|
||||
/**
|
||||
* Debug URI format
|
||||
*
|
||||
* a debug URI represents a Source object and the debug session where the Source comes from.
|
||||
*
|
||||
* debug:arbitrary_path?session=123e4567-e89b-12d3-a456-426655440000&ref=1016
|
||||
* \___/ \____________/ \__________________________________________/ \______/
|
||||
* | | | |
|
||||
* scheme source.path session id source.reference
|
||||
*
|
||||
* the arbitrary_path and the session id are encoded with 'encodeURIComponent'
|
||||
*
|
||||
*/
|
||||
|
||||
export class Source {
|
||||
|
||||
public readonly uri: uri;
|
||||
public available: boolean;
|
||||
public raw: DebugProtocol.Source;
|
||||
|
||||
constructor(raw_: DebugProtocol.Source | undefined, sessionId: string) {
|
||||
let path: string;
|
||||
if (raw_) {
|
||||
this.raw = raw_;
|
||||
path = this.raw.path || this.raw.name || '';
|
||||
this.available = true;
|
||||
} else {
|
||||
this.raw = { name: UNKNOWN_SOURCE_LABEL };
|
||||
this.available = false;
|
||||
path = `${DEBUG_SCHEME}:${UNKNOWN_SOURCE_LABEL}`;
|
||||
}
|
||||
|
||||
if (typeof this.raw.sourceReference === 'number' && this.raw.sourceReference > 0) {
|
||||
this.uri = uri.parse(`${DEBUG_SCHEME}:${encodeURIComponent(path)}?session=${encodeURIComponent(sessionId)}&ref=${this.raw.sourceReference}`);
|
||||
} else {
|
||||
if (isUri(path)) { // path looks like a uri
|
||||
this.uri = uri.parse(path);
|
||||
} else {
|
||||
// assume a filesystem path
|
||||
if (isAbsolute(path)) {
|
||||
this.uri = uri.file(path);
|
||||
} else {
|
||||
// path is relative: since VS Code cannot deal with this by itself
|
||||
// create a debug url that will result in a DAP 'source' request when the url is resolved.
|
||||
this.uri = uri.parse(`${DEBUG_SCHEME}:${encodeURIComponent(path)}?session=${encodeURIComponent(sessionId)}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get name() {
|
||||
return this.raw.name || resources.basenameOrAuthority(this.uri);
|
||||
}
|
||||
|
||||
get origin() {
|
||||
return this.raw.origin;
|
||||
}
|
||||
|
||||
get presentationHint() {
|
||||
return this.raw.presentationHint;
|
||||
}
|
||||
|
||||
get reference() {
|
||||
return this.raw.sourceReference;
|
||||
}
|
||||
|
||||
get inMemory() {
|
||||
return this.uri.scheme === DEBUG_SCHEME;
|
||||
}
|
||||
|
||||
openInEditor(editorService: IEditorService, selection: IRange, preserveFocus?: boolean, sideBySide?: boolean, pinned?: boolean): Promise<any> {
|
||||
return !this.available ? Promise.resolve(null) : editorService.openEditor({
|
||||
resource: this.uri,
|
||||
description: this.origin,
|
||||
options: {
|
||||
preserveFocus,
|
||||
selection,
|
||||
revealIfVisible: true,
|
||||
revealInCenterIfOutsideViewport: true,
|
||||
pinned: pinned || (!preserveFocus && !this.inMemory)
|
||||
}
|
||||
}, sideBySide ? SIDE_GROUP : ACTIVE_GROUP);
|
||||
}
|
||||
|
||||
static getEncodedDebugData(modelUri: uri): { name: string, path: string, sessionId?: string, sourceReference?: number } {
|
||||
let path: string;
|
||||
let sourceReference: number | undefined;
|
||||
let sessionId: string | undefined;
|
||||
|
||||
switch (modelUri.scheme) {
|
||||
case Schemas.file:
|
||||
path = normalize(modelUri.fsPath);
|
||||
break;
|
||||
case DEBUG_SCHEME:
|
||||
path = modelUri.path;
|
||||
if (modelUri.query) {
|
||||
const keyvalues = modelUri.query.split('&');
|
||||
for (let keyvalue of keyvalues) {
|
||||
const pair = keyvalue.split('=');
|
||||
if (pair.length === 2) {
|
||||
switch (pair[0]) {
|
||||
case 'session':
|
||||
sessionId = decodeURIComponent(pair[1]);
|
||||
break;
|
||||
case 'ref':
|
||||
sourceReference = parseInt(pair[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
path = modelUri.toString();
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
name: resources.basenameOrAuthority(modelUri),
|
||||
path,
|
||||
sourceReference,
|
||||
sessionId
|
||||
};
|
||||
}
|
||||
}
|
||||
214
src/vs/workbench/contrib/debug/common/debugUtils.ts
Normal file
@@ -0,0 +1,214 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { equalsIgnoreCase } from 'vs/base/common/strings';
|
||||
import { IConfig, IDebuggerContribution } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { URI as uri } from 'vs/base/common/uri';
|
||||
import { isAbsolute } from 'vs/base/common/path';
|
||||
import { deepClone } from 'vs/base/common/objects';
|
||||
|
||||
const _formatPIIRegexp = /{([^}]+)}/g;
|
||||
|
||||
export function formatPII(value: string, excludePII: boolean, args: { [key: string]: string }): string {
|
||||
return value.replace(_formatPIIRegexp, function (match, group) {
|
||||
if (excludePII && group.length > 0 && group[0] !== '_') {
|
||||
return match;
|
||||
}
|
||||
|
||||
return args && args.hasOwnProperty(group) ?
|
||||
args[group] :
|
||||
match;
|
||||
});
|
||||
}
|
||||
|
||||
export function isExtensionHostDebugging(config: IConfig) {
|
||||
return config.type && equalsIgnoreCase(config.type === 'vslsShare' ? (<any>config).adapterProxy.configuration.type : config.type, 'extensionhost');
|
||||
}
|
||||
|
||||
// only a debugger contributions with a label, program, or runtime attribute is considered a "defining" or "main" debugger contribution
|
||||
export function isDebuggerMainContribution(dbg: IDebuggerContribution) {
|
||||
return dbg.type && (dbg.label || dbg.program || dbg.runtime);
|
||||
}
|
||||
|
||||
export function getExactExpressionStartAndEnd(lineContent: string, looseStart: number, looseEnd: number): { start: number, end: number } {
|
||||
let matchingExpression: string | undefined = 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 | null = null;
|
||||
|
||||
// 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 <= looseStart && end >= looseEnd) {
|
||||
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 | null = null;
|
||||
while (subExpressionResult = subExpression.exec(matchingExpression)) {
|
||||
let subEnd = subExpressionResult.index + 1 + startOffset + subExpressionResult[0].length;
|
||||
if (subEnd >= looseEnd) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (subExpressionResult) {
|
||||
matchingExpression = matchingExpression.substring(0, subExpression.lastIndex);
|
||||
}
|
||||
}
|
||||
|
||||
return matchingExpression ?
|
||||
{ start: startOffset, end: startOffset + matchingExpression.length - 1 } :
|
||||
{ start: 0, end: 0 };
|
||||
}
|
||||
|
||||
// RFC 2396, Appendix A: https://www.ietf.org/rfc/rfc2396.txt
|
||||
const _schemePattern = /^[a-zA-Z][a-zA-Z0-9\+\-\.]+:/;
|
||||
|
||||
export function isUri(s: string | undefined): boolean {
|
||||
// heuristics: a valid uri starts with a scheme and
|
||||
// the scheme has at least 2 characters so that it doesn't look like a drive letter.
|
||||
return !!(s && s.match(_schemePattern));
|
||||
}
|
||||
|
||||
function stringToUri(path: string): string {
|
||||
if (typeof path === 'string') {
|
||||
if (isUri(path)) {
|
||||
return <string><unknown>uri.parse(path);
|
||||
} else {
|
||||
// assume path
|
||||
if (isAbsolute(path)) {
|
||||
return <string><unknown>uri.file(path);
|
||||
} else {
|
||||
// leave relative path as is
|
||||
}
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
function uriToString(path: string): string {
|
||||
if (typeof path === 'object') {
|
||||
const u = uri.revive(path);
|
||||
if (u.scheme === 'file') {
|
||||
return u.fsPath;
|
||||
} else {
|
||||
return u.toString();
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
// path hooks helpers
|
||||
|
||||
interface PathContainer {
|
||||
path?: string;
|
||||
}
|
||||
|
||||
export function convertToDAPaths(message: DebugProtocol.ProtocolMessage, toUri: boolean): DebugProtocol.ProtocolMessage {
|
||||
|
||||
const fixPath = toUri ? stringToUri : uriToString;
|
||||
|
||||
// since we modify Source.paths in the message in place, we need to make a copy of it (see #61129)
|
||||
const msg = deepClone(message);
|
||||
|
||||
convertPaths(msg, (toDA: boolean, source: PathContainer | undefined) => {
|
||||
if (toDA && source) {
|
||||
source.path = source.path ? fixPath(source.path) : undefined;
|
||||
}
|
||||
});
|
||||
return msg;
|
||||
}
|
||||
|
||||
export function convertToVSCPaths(message: DebugProtocol.ProtocolMessage, toUri: boolean): DebugProtocol.ProtocolMessage {
|
||||
|
||||
const fixPath = toUri ? stringToUri : uriToString;
|
||||
|
||||
// since we modify Source.paths in the message in place, we need to make a copy of it (see #61129)
|
||||
const msg = deepClone(message);
|
||||
|
||||
convertPaths(msg, (toDA: boolean, source: PathContainer | undefined) => {
|
||||
if (!toDA && source) {
|
||||
source.path = source.path ? fixPath(source.path) : undefined;
|
||||
}
|
||||
});
|
||||
return msg;
|
||||
}
|
||||
|
||||
function convertPaths(msg: DebugProtocol.ProtocolMessage, fixSourcePath: (toDA: boolean, source: PathContainer | undefined) => void): void {
|
||||
|
||||
switch (msg.type) {
|
||||
case 'event':
|
||||
const event = <DebugProtocol.Event>msg;
|
||||
switch (event.event) {
|
||||
case 'output':
|
||||
fixSourcePath(false, (<DebugProtocol.OutputEvent>event).body.source);
|
||||
break;
|
||||
case 'loadedSource':
|
||||
fixSourcePath(false, (<DebugProtocol.LoadedSourceEvent>event).body.source);
|
||||
break;
|
||||
case 'breakpoint':
|
||||
fixSourcePath(false, (<DebugProtocol.BreakpointEvent>event).body.breakpoint.source);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'request':
|
||||
const request = <DebugProtocol.Request>msg;
|
||||
switch (request.command) {
|
||||
case 'setBreakpoints':
|
||||
fixSourcePath(true, (<DebugProtocol.SetBreakpointsArguments>request.arguments).source);
|
||||
break;
|
||||
case 'source':
|
||||
fixSourcePath(true, (<DebugProtocol.SourceArguments>request.arguments).source);
|
||||
break;
|
||||
case 'gotoTargets':
|
||||
fixSourcePath(true, (<DebugProtocol.GotoTargetsArguments>request.arguments).source);
|
||||
break;
|
||||
case 'launchVSCode':
|
||||
request.arguments.args.forEach(arg => fixSourcePath(false, arg));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case 'response':
|
||||
const response = <DebugProtocol.Response>msg;
|
||||
if (response.success) {
|
||||
switch (response.command) {
|
||||
case 'stackTrace':
|
||||
(<DebugProtocol.StackTraceResponse>response).body.stackFrames.forEach(frame => fixSourcePath(false, frame.source));
|
||||
break;
|
||||
case 'loadedSources':
|
||||
(<DebugProtocol.LoadedSourcesResponse>response).body.sources.forEach(source => fixSourcePath(false, source));
|
||||
break;
|
||||
case 'scopes':
|
||||
(<DebugProtocol.ScopesResponse>response).body.scopes.forEach(scope => fixSourcePath(false, scope.source));
|
||||
break;
|
||||
case 'setFunctionBreakpoints':
|
||||
(<DebugProtocol.SetFunctionBreakpointsResponse>response).body.breakpoints.forEach(bp => fixSourcePath(false, bp.source));
|
||||
break;
|
||||
case 'setBreakpoints':
|
||||
(<DebugProtocol.SetBreakpointsResponse>response).body.breakpoints.forEach(bp => fixSourcePath(false, bp.source));
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
117
src/vs/workbench/contrib/debug/common/debugViewModel.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { CONTEXT_EXPRESSION_SELECTED, IViewModel, IStackFrame, IDebugSession, IThread, IExpression, IFunctionBreakpoint, CONTEXT_BREAKPOINT_SELECTED, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { isExtensionHostDebugging } from 'vs/workbench/contrib/debug/common/debugUtils';
|
||||
|
||||
export class ViewModel implements IViewModel {
|
||||
|
||||
firstSessionStart = true;
|
||||
|
||||
private _focusedStackFrame: IStackFrame | undefined;
|
||||
private _focusedSession: IDebugSession | undefined;
|
||||
private _focusedThread: IThread | undefined;
|
||||
private selectedExpression: IExpression | undefined;
|
||||
private selectedFunctionBreakpoint: IFunctionBreakpoint | undefined;
|
||||
private readonly _onDidFocusSession: Emitter<IDebugSession | undefined>;
|
||||
private readonly _onDidFocusStackFrame: Emitter<{ stackFrame: IStackFrame | undefined, explicit: boolean }>;
|
||||
private readonly _onDidSelectExpression: Emitter<IExpression | undefined>;
|
||||
private multiSessionView: boolean;
|
||||
private expressionSelectedContextKey: IContextKey<boolean>;
|
||||
private breakpointSelectedContextKey: IContextKey<boolean>;
|
||||
private loadedScriptsSupportedContextKey: IContextKey<boolean>;
|
||||
private stepBackSupportedContextKey: IContextKey<boolean>;
|
||||
private focusedSessionIsAttach: IContextKey<boolean>;
|
||||
|
||||
constructor(contextKeyService: IContextKeyService) {
|
||||
this._onDidFocusSession = new Emitter<IDebugSession | undefined>();
|
||||
this._onDidFocusStackFrame = new Emitter<{ stackFrame: IStackFrame, explicit: boolean }>();
|
||||
this._onDidSelectExpression = new Emitter<IExpression>();
|
||||
this.multiSessionView = false;
|
||||
this.expressionSelectedContextKey = CONTEXT_EXPRESSION_SELECTED.bindTo(contextKeyService);
|
||||
this.breakpointSelectedContextKey = CONTEXT_BREAKPOINT_SELECTED.bindTo(contextKeyService);
|
||||
this.loadedScriptsSupportedContextKey = CONTEXT_LOADED_SCRIPTS_SUPPORTED.bindTo(contextKeyService);
|
||||
this.stepBackSupportedContextKey = CONTEXT_STEP_BACK_SUPPORTED.bindTo(contextKeyService);
|
||||
this.focusedSessionIsAttach = CONTEXT_FOCUSED_SESSION_IS_ATTACH.bindTo(contextKeyService);
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return 'root';
|
||||
}
|
||||
|
||||
get focusedSession(): IDebugSession | undefined {
|
||||
return this._focusedSession;
|
||||
}
|
||||
|
||||
get focusedThread(): IThread | undefined {
|
||||
return this._focusedThread;
|
||||
}
|
||||
|
||||
get focusedStackFrame(): IStackFrame | undefined {
|
||||
return this._focusedStackFrame;
|
||||
}
|
||||
|
||||
setFocus(stackFrame: IStackFrame | undefined, thread: IThread | undefined, session: IDebugSession | undefined, explicit: boolean): void {
|
||||
const shouldEmitForStackFrame = this._focusedStackFrame !== stackFrame;
|
||||
const shouldEmitForSession = this._focusedSession !== session;
|
||||
|
||||
this._focusedStackFrame = stackFrame;
|
||||
this._focusedThread = thread;
|
||||
this._focusedSession = session;
|
||||
|
||||
this.loadedScriptsSupportedContextKey.set(session ? !!session.capabilities.supportsLoadedSourcesRequest : false);
|
||||
this.stepBackSupportedContextKey.set(session ? !!session.capabilities.supportsStepBack : false);
|
||||
const attach = !!session && session.configuration.request === 'attach' && !isExtensionHostDebugging(session.configuration);
|
||||
this.focusedSessionIsAttach.set(attach);
|
||||
|
||||
if (shouldEmitForSession) {
|
||||
this._onDidFocusSession.fire(session);
|
||||
}
|
||||
if (shouldEmitForStackFrame) {
|
||||
this._onDidFocusStackFrame.fire({ stackFrame, explicit });
|
||||
}
|
||||
}
|
||||
|
||||
get onDidFocusSession(): Event<IDebugSession | undefined> {
|
||||
return this._onDidFocusSession.event;
|
||||
}
|
||||
|
||||
get onDidFocusStackFrame(): Event<{ stackFrame: IStackFrame | undefined, explicit: boolean }> {
|
||||
return this._onDidFocusStackFrame.event;
|
||||
}
|
||||
|
||||
getSelectedExpression(): IExpression | undefined {
|
||||
return this.selectedExpression;
|
||||
}
|
||||
|
||||
setSelectedExpression(expression: IExpression | undefined) {
|
||||
this.selectedExpression = expression;
|
||||
this.expressionSelectedContextKey.set(!!expression);
|
||||
this._onDidSelectExpression.fire(expression);
|
||||
}
|
||||
|
||||
get onDidSelectExpression(): Event<IExpression | undefined> {
|
||||
return this._onDidSelectExpression.event;
|
||||
}
|
||||
|
||||
getSelectedFunctionBreakpoint(): IFunctionBreakpoint | undefined {
|
||||
return this.selectedFunctionBreakpoint;
|
||||
}
|
||||
|
||||
setSelectedFunctionBreakpoint(functionBreakpoint: IFunctionBreakpoint | undefined): void {
|
||||
this.selectedFunctionBreakpoint = functionBreakpoint;
|
||||
this.breakpointSelectedContextKey.set(!!functionBreakpoint);
|
||||
}
|
||||
|
||||
isMultiSessionView(): boolean {
|
||||
return this.multiSessionView;
|
||||
}
|
||||
|
||||
setMultiSessionView(isMultiSessionView: boolean): void {
|
||||
this.multiSessionView = isMultiSessionView;
|
||||
}
|
||||
}
|
||||
145
src/vs/workbench/contrib/debug/common/replModel.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 severity from 'vs/base/common/severity';
|
||||
import { IReplElement, IStackFrame, IExpression, IReplElementSource, IDebugSession } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { Expression, SimpleReplElement, RawObjectReplElement } from 'vs/workbench/contrib/debug/common/debugModel';
|
||||
import { isUndefinedOrNull, isObject } from 'vs/base/common/types';
|
||||
import { basenameOrAuthority } from 'vs/base/common/resources';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
const MAX_REPL_LENGTH = 10000;
|
||||
let topReplElementCounter = 0;
|
||||
|
||||
export class ReplModel {
|
||||
private replElements: IReplElement[] = [];
|
||||
|
||||
constructor(private session: IDebugSession) { }
|
||||
|
||||
getReplElements(): IReplElement[] {
|
||||
return this.replElements;
|
||||
}
|
||||
|
||||
addReplExpression(stackFrame: IStackFrame | undefined, name: string): Promise<void> {
|
||||
const expression = new Expression(name);
|
||||
this.addReplElements([expression]);
|
||||
return expression.evaluate(this.session, stackFrame, 'repl');
|
||||
}
|
||||
|
||||
appendToRepl(data: string | IExpression, sev: severity, source?: IReplElementSource): void {
|
||||
const clearAnsiSequence = '\u001b[2J';
|
||||
if (typeof data === 'string' && data.indexOf(clearAnsiSequence) >= 0) {
|
||||
// [2J is the ansi escape sequence for clearing the display http://ascii-table.com/ansi-escape-sequences.php
|
||||
this.removeReplExpressions();
|
||||
this.appendToRepl(nls.localize('consoleCleared', "Console was cleared"), severity.Ignore);
|
||||
data = data.substr(data.lastIndexOf(clearAnsiSequence) + clearAnsiSequence.length);
|
||||
}
|
||||
|
||||
if (typeof data === 'string') {
|
||||
const previousElement = this.replElements.length && (this.replElements[this.replElements.length - 1] as SimpleReplElement);
|
||||
|
||||
const toAdd = data.split('\n').map((line, index) => new SimpleReplElement(`topReplElement:${topReplElementCounter++}`, line, sev, index === 0 ? source : undefined));
|
||||
if (previousElement && previousElement.value === '') {
|
||||
// remove potential empty lines between different repl types
|
||||
this.replElements.pop();
|
||||
} else if (previousElement instanceof SimpleReplElement && sev === previousElement.severity && toAdd.length && toAdd[0].sourceData === previousElement.sourceData) {
|
||||
previousElement.value += toAdd.shift()!.value;
|
||||
}
|
||||
this.addReplElements(toAdd);
|
||||
} else {
|
||||
// TODO@Isidor hack, we should introduce a new type which is an output that can fetch children like an expression
|
||||
(<any>data).severity = sev;
|
||||
(<any>data).sourceData = source;
|
||||
this.addReplElements([data]);
|
||||
}
|
||||
}
|
||||
|
||||
private addReplElements(newElements: IReplElement[]): void {
|
||||
this.replElements.push(...newElements);
|
||||
if (this.replElements.length > MAX_REPL_LENGTH) {
|
||||
this.replElements.splice(0, this.replElements.length - MAX_REPL_LENGTH);
|
||||
}
|
||||
}
|
||||
|
||||
logToRepl(sev: severity, args: any[], frame?: { uri: URI, line: number, column: number }) {
|
||||
|
||||
let source: IReplElementSource | undefined;
|
||||
if (frame) {
|
||||
source = {
|
||||
column: frame.column,
|
||||
lineNumber: frame.line,
|
||||
source: this.session.getSource({
|
||||
name: basenameOrAuthority(frame.uri),
|
||||
path: frame.uri.fsPath
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
// add output for each argument logged
|
||||
let simpleVals: any[] = [];
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
let a = args[i];
|
||||
|
||||
// undefined gets printed as 'undefined'
|
||||
if (typeof a === 'undefined') {
|
||||
simpleVals.push('undefined');
|
||||
}
|
||||
|
||||
// null gets printed as 'null'
|
||||
else if (a === null) {
|
||||
simpleVals.push('null');
|
||||
}
|
||||
|
||||
// objects & arrays are special because we want to inspect them in the REPL
|
||||
else if (isObject(a) || Array.isArray(a)) {
|
||||
|
||||
// flush any existing simple values logged
|
||||
if (simpleVals.length) {
|
||||
this.appendToRepl(simpleVals.join(' '), sev, source);
|
||||
simpleVals = [];
|
||||
}
|
||||
|
||||
// show object
|
||||
this.appendToRepl(new RawObjectReplElement(`topReplElement:${topReplElementCounter++}`, (<any>a).prototype, a, undefined, nls.localize('snapshotObj', "Only primitive values are shown for this object.")), sev, source);
|
||||
}
|
||||
|
||||
// string: watch out for % replacement directive
|
||||
// string substitution and formatting @ https://developer.chrome.com/devtools/docs/console
|
||||
else if (typeof a === 'string') {
|
||||
let buf = '';
|
||||
|
||||
for (let j = 0, len = a.length; j < len; j++) {
|
||||
if (a[j] === '%' && (a[j + 1] === 's' || a[j + 1] === 'i' || a[j + 1] === 'd' || a[j + 1] === 'O')) {
|
||||
i++; // read over substitution
|
||||
buf += !isUndefinedOrNull(args[i]) ? args[i] : ''; // replace
|
||||
j++; // read over directive
|
||||
} else {
|
||||
buf += a[j];
|
||||
}
|
||||
}
|
||||
|
||||
simpleVals.push(buf);
|
||||
}
|
||||
|
||||
// number or boolean is joined together
|
||||
else {
|
||||
simpleVals.push(a);
|
||||
}
|
||||
}
|
||||
|
||||
// flush simple values
|
||||
// always append a new line for output coming from an extension such that separate logs go to separate lines #23695
|
||||
if (simpleVals.length) {
|
||||
this.appendToRepl(simpleVals.join(' ') + '\n', sev, source);
|
||||
}
|
||||
}
|
||||
|
||||
removeReplExpressions(): void {
|
||||
if (this.replElements.length > 0) {
|
||||
this.replElements = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,512 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { KeybindingWeight, 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/actions';
|
||||
import { ShowViewletAction, Extensions as ViewletExtensions, ViewletRegistry, ViewletDescriptor } from 'vs/workbench/browser/viewlet';
|
||||
import { TogglePanelAction, Extensions as PanelExtensions, PanelRegistry, PanelDescriptor } from 'vs/workbench/browser/panel';
|
||||
import { StatusbarItemDescriptor, IStatusbarRegistry, Extensions as StatusExtensions } from 'vs/workbench/browser/parts/statusbar/statusbar';
|
||||
import { StatusbarAlignment } from 'vs/platform/statusbar/common/statusbar';
|
||||
import { BreakpointsView } from 'vs/workbench/contrib/debug/browser/breakpointsView';
|
||||
import { CallStackView } from 'vs/workbench/contrib/debug/browser/callStackView';
|
||||
import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
|
||||
import {
|
||||
IDebugService, VIEWLET_ID, REPL_ID, CONTEXT_IN_DEBUG_MODE, INTERNAL_CONSOLE_OPTIONS_SCHEMA,
|
||||
CONTEXT_DEBUG_STATE, VARIABLES_VIEW_ID, CALLSTACK_VIEW_ID, WATCH_VIEW_ID, BREAKPOINTS_VIEW_ID, VIEW_CONTAINER, LOADED_SCRIPTS_VIEW_ID, CONTEXT_LOADED_SCRIPTS_SUPPORTED, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_STEP_BACK_SUPPORTED,
|
||||
} from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { DebugEditorModelManager } from 'vs/workbench/contrib/debug/browser/debugEditorModelManager';
|
||||
import {
|
||||
StepOverAction, FocusReplAction, StepIntoAction, StepOutAction, StartAction, RestartAction, ContinueAction, StopAction, DisconnectAction, PauseAction, AddFunctionBreakpointAction,
|
||||
ConfigureAction, DisableAllBreakpointsAction, EnableAllBreakpointsAction, RemoveAllBreakpointsAction, RunAction, ReapplyBreakpointsAction, SelectAndStartAction, TerminateThreadAction, StepBackAction, ReverseContinueAction,
|
||||
} from 'vs/workbench/contrib/debug/browser/debugActions';
|
||||
import { DebugToolbar } from 'vs/workbench/contrib/debug/browser/debugToolbar';
|
||||
import * as service from 'vs/workbench/contrib/debug/electron-browser/debugService';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { registerCommands, ADD_CONFIGURATION_ID, TOGGLE_INLINE_BREAKPOINT_ID } from 'vs/workbench/contrib/debug/browser/debugCommands';
|
||||
import { IQuickOpenRegistry, Extensions as QuickOpenExtensions, QuickOpenHandlerDescriptor } from 'vs/workbench/browser/quickopen';
|
||||
import { StatusBarColorProvider } from 'vs/workbench/contrib/debug/browser/statusbarColorProvider';
|
||||
import { IViewsRegistry, Extensions as ViewExtensions } from 'vs/workbench/common/views';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { DebugViewlet } from 'vs/workbench/contrib/debug/browser/debugViewlet';
|
||||
import { DebugQuickOpenHandler } from 'vs/workbench/contrib/debug/browser/debugQuickOpen';
|
||||
import { DebugStatus } from 'vs/workbench/contrib/debug/browser/debugStatus';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { LoadedScriptsView } from 'vs/workbench/contrib/debug/browser/loadedScriptsView';
|
||||
import { TOGGLE_LOG_POINT_ID, TOGGLE_CONDITIONAL_BREAKPOINT_ID, TOGGLE_BREAKPOINT_ID } from 'vs/workbench/contrib/debug/browser/debugEditorActions';
|
||||
import { WatchExpressionsView } from 'vs/workbench/contrib/debug/browser/watchExpressionsView';
|
||||
import { VariablesView } from 'vs/workbench/contrib/debug/browser/variablesView';
|
||||
import { ClearReplAction, Repl } from 'vs/workbench/contrib/debug/browser/repl';
|
||||
import { DebugContentProvider } from 'vs/workbench/contrib/debug/common/debugContentProvider';
|
||||
|
||||
class OpenDebugViewletAction extends ShowViewletAction {
|
||||
public static readonly ID = VIEWLET_ID;
|
||||
public static readonly LABEL = nls.localize('toggleDebugViewlet', "Show Debug");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IViewletService viewletService: IViewletService,
|
||||
@IEditorGroupsService editorGroupService: IEditorGroupsService,
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService
|
||||
) {
|
||||
super(id, label, VIEWLET_ID, viewletService, editorGroupService, layoutService);
|
||||
}
|
||||
}
|
||||
|
||||
class OpenDebugPanelAction extends TogglePanelAction {
|
||||
public static readonly ID = 'workbench.debug.action.toggleRepl';
|
||||
public static readonly LABEL = nls.localize('toggleDebugPanel', "Debug Console");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IPanelService panelService: IPanelService,
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService
|
||||
) {
|
||||
super(id, label, REPL_ID, panelService, layoutService);
|
||||
}
|
||||
}
|
||||
|
||||
// register viewlet
|
||||
Registry.as<ViewletRegistry>(ViewletExtensions.Viewlets).registerViewlet(new ViewletDescriptor(
|
||||
DebugViewlet,
|
||||
VIEWLET_ID,
|
||||
nls.localize('debug', "Debug"),
|
||||
'debug',
|
||||
// {{SQL CARBON EDIT}}
|
||||
13
|
||||
));
|
||||
|
||||
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(
|
||||
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
|
||||
const viewsRegistry = Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry);
|
||||
viewsRegistry.registerViews([{ id: VARIABLES_VIEW_ID, name: nls.localize('variables', "Variables"), ctorDescriptor: { ctor: VariablesView }, order: 10, weight: 40, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusVariablesView' } }], VIEW_CONTAINER);
|
||||
viewsRegistry.registerViews([{ id: WATCH_VIEW_ID, name: nls.localize('watch', "Watch"), ctorDescriptor: { ctor: WatchExpressionsView }, order: 20, weight: 10, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusWatchView' } }], VIEW_CONTAINER);
|
||||
viewsRegistry.registerViews([{ id: CALLSTACK_VIEW_ID, name: nls.localize('callStack', "Call Stack"), ctorDescriptor: { ctor: CallStackView }, order: 30, weight: 30, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusCallStackView' } }], VIEW_CONTAINER);
|
||||
viewsRegistry.registerViews([{ id: BREAKPOINTS_VIEW_ID, name: nls.localize('breakpoints', "Breakpoints"), ctorDescriptor: { ctor: BreakpointsView }, order: 40, weight: 20, canToggleVisibility: true, focusCommand: { id: 'workbench.debug.action.focusBreakpointsView' } }], VIEW_CONTAINER);
|
||||
viewsRegistry.registerViews([{ id: LOADED_SCRIPTS_VIEW_ID, name: nls.localize('loadedScripts', "Loaded Scripts"), ctorDescriptor: { ctor: LoadedScriptsView }, order: 35, weight: 5, canToggleVisibility: true, collapsed: true, when: CONTEXT_LOADED_SCRIPTS_SUPPORTED }], VIEW_CONTAINER);
|
||||
|
||||
// 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, LifecyclePhase.Restored);
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugToolbar, LifecyclePhase.Restored);
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(DebugContentProvider, LifecyclePhase.Eventually);
|
||||
Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench).registerWorkbenchContribution(StatusBarColorProvider, LifecyclePhase.Eventually);
|
||||
|
||||
const debugCategory = nls.localize('debugCategory', "Debug");
|
||||
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(StartAction, StartAction.ID, StartAction.LABEL, { primary: KeyCode.F5 }, CONTEXT_IN_DEBUG_MODE.toNegated()), '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, KeybindingWeight.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(TerminateThreadAction, TerminateThreadAction.ID, TerminateThreadAction.LABEL, undefined, CONTEXT_IN_DEBUG_MODE), 'Debug: Terminate Thread', 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, mac: { primary: KeyMod.WinCtrl | KeyCode.F5 } }, CONTEXT_IN_DEBUG_MODE.toNegated()), '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(FocusReplAction, FocusReplAction.ID, FocusReplAction.LABEL), 'Debug: Focus on Debug Console View', debugCategory);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(SelectAndStartAction, SelectAndStartAction.ID, SelectAndStartAction.LABEL), 'Debug: Select and Start Debugging', debugCategory);
|
||||
registry.registerWorkbenchAction(new SyncActionDescriptor(ClearReplAction, ClearReplAction.ID, ClearReplAction.LABEL), 'Debug: Clear Console', debugCategory);
|
||||
|
||||
// Register Quick Open
|
||||
(Registry.as<IQuickOpenRegistry>(QuickOpenExtensions.Quickopen)).registerQuickOpenHandler(
|
||||
new QuickOpenHandlerDescriptor(
|
||||
DebugQuickOpenHandler,
|
||||
DebugQuickOpenHandler.ID,
|
||||
'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' }, "Allow setting breakpoints in any file."),
|
||||
default: false
|
||||
},
|
||||
'debug.openExplorerOnEnd': {
|
||||
type: 'boolean',
|
||||
description: nls.localize({ comment: ['This is the description for a setting'], key: 'openExplorerOnEnd' }, "Automatically open the explorer view at 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.toolBarLocation': {
|
||||
enum: ['floating', 'docked', 'hidden'],
|
||||
markdownDescription: nls.localize({ comment: ['This is the description for a setting'], key: 'toolBarLocation' }, "Controls the location of the debug toolbar. Either `floating` in all views, `docked` in the debug view, or `hidden`."),
|
||||
default: 'floating'
|
||||
},
|
||||
'debug.showInStatusBar': {
|
||||
enum: ['never', 'always', 'onFirstSessionStart'],
|
||||
enumDescriptions: [nls.localize('never', "Never show debug in status bar"), nls.localize('always', "Always show debug in status bar"), nls.localize('onFirstSessionStart', "Show debug in status bar only after debug was started for the first time")],
|
||||
description: nls.localize({ comment: ['This is the description for a setting'], key: 'showInStatusBar' }, "Controls when the debug status bar should be visible."),
|
||||
default: 'onFirstSessionStart'
|
||||
},
|
||||
'debug.internalConsoleOptions': INTERNAL_CONSOLE_OPTIONS_SCHEMA,
|
||||
'debug.openDebug': {
|
||||
enum: ['neverOpen', 'openOnSessionStart', 'openOnFirstSessionStart', 'openOnDebugBreak'],
|
||||
default: 'openOnSessionStart',
|
||||
description: nls.localize('openDebug', "Controls when the debug view should open.")
|
||||
},
|
||||
'debug.enableAllHovers': {
|
||||
type: 'boolean',
|
||||
description: nls.localize({ comment: ['This is the description for a setting'], key: 'enableAllHovers' }, "Controls whether the non-debug hovers should be enabled while debugging. When enabled the hover providers will be called to provide a hover. Regular hovers will not be shown even if this setting is enabled."),
|
||||
default: false
|
||||
},
|
||||
'debug.console.fontSize': {
|
||||
type: 'number',
|
||||
description: nls.localize('debug.console.fontSize', "Controls the font size in pixels in the debug console."),
|
||||
default: isMacintosh ? 12 : 14,
|
||||
},
|
||||
'debug.console.fontFamily': {
|
||||
type: 'string',
|
||||
description: nls.localize('debug.console.fontFamily', "Controls the font family in the debug console."),
|
||||
default: 'default'
|
||||
},
|
||||
'debug.console.lineHeight': {
|
||||
type: 'number',
|
||||
description: nls.localize('debug.console.lineHeight', "Controls the line height in pixels in the debug console. Use 0 to compute the line height from the font size."),
|
||||
default: 0
|
||||
},
|
||||
'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: { configurations: [], compounds: [] },
|
||||
$ref: launchSchemaId
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
registerCommands();
|
||||
|
||||
// Register Debug Status
|
||||
const statusBar = Registry.as<IStatusbarRegistry>(StatusExtensions.Statusbar);
|
||||
statusBar.registerStatusbarItem(new StatusbarItemDescriptor(DebugStatus, StatusbarAlignment.LEFT, 30 /* Low Priority */));
|
||||
|
||||
// Debug toolbar
|
||||
|
||||
const registerDebugToolbarItem = (id: string, title: string, icon: string, order: number, when?: ContextKeyExpr, precondition?: ContextKeyExpr) => {
|
||||
MenuRegistry.appendMenuItem(MenuId.DebugToolbar, {
|
||||
group: 'navigation',
|
||||
when,
|
||||
order,
|
||||
command: {
|
||||
id,
|
||||
title,
|
||||
iconLocation: {
|
||||
light: URI.parse(require.toUrl(`vs/workbench/contrib/debug/browser/media/${icon}.svg`)),
|
||||
dark: URI.parse(require.toUrl(`vs/workbench/contrib/debug/browser/media/${icon}-inverse.svg`))
|
||||
},
|
||||
precondition
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
registerDebugToolbarItem(ContinueAction.ID, ContinueAction.LABEL, 'continue', 10, CONTEXT_DEBUG_STATE.notEqualsTo('running'));
|
||||
registerDebugToolbarItem(PauseAction.ID, PauseAction.LABEL, 'pause', 10, CONTEXT_DEBUG_STATE.isEqualTo('running'));
|
||||
registerDebugToolbarItem(StopAction.ID, StopAction.LABEL, 'stop', 70, CONTEXT_FOCUSED_SESSION_IS_ATTACH.toNegated());
|
||||
registerDebugToolbarItem(DisconnectAction.ID, DisconnectAction.LABEL, 'disconnect', 70, CONTEXT_FOCUSED_SESSION_IS_ATTACH);
|
||||
registerDebugToolbarItem(StepOverAction.ID, StepOverAction.LABEL, 'step-over', 20, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
|
||||
registerDebugToolbarItem(StepIntoAction.ID, StepIntoAction.LABEL, 'step-into', 30, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
|
||||
registerDebugToolbarItem(StepOutAction.ID, StepOutAction.LABEL, 'step-out', 40, undefined, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
|
||||
registerDebugToolbarItem(RestartAction.ID, RestartAction.LABEL, 'restart', 60);
|
||||
registerDebugToolbarItem(StepBackAction.ID, StepBackAction.LABEL, 'step-back', 50, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
|
||||
registerDebugToolbarItem(ReverseContinueAction.ID, ReverseContinueAction.LABEL, 'reverse-continue', 60, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_DEBUG_STATE.isEqualTo('stopped'));
|
||||
|
||||
// View menu
|
||||
|
||||
// {{SQL CARBON EDIT}} - Disable unused menu item
|
||||
// MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {
|
||||
// group: '3_views',
|
||||
// command: {
|
||||
// id: VIEWLET_ID,
|
||||
// title: nls.localize({ key: 'miViewDebug', comment: ['&& denotes a mnemonic'] }, "&&Debug")
|
||||
// },
|
||||
// order: 4
|
||||
// });
|
||||
// {{SQL CARBON EDIT}} - End
|
||||
|
||||
// {{SQL CARBON EDIT}} - Disable unused menu item
|
||||
// MenuRegistry.appendMenuItem(MenuId.MenubarViewMenu, {
|
||||
// group: '4_panels',
|
||||
// command: {
|
||||
// id: OpenDebugPanelAction.ID,
|
||||
// title: nls.localize({ key: 'miToggleDebugConsole', comment: ['&& denotes a mnemonic'] }, "De&&bug Console")
|
||||
// },
|
||||
// order: 2
|
||||
// });
|
||||
// {{SQL CARBON EDIT}} - End
|
||||
|
||||
// Debug menu
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, {
|
||||
group: '1_debug',
|
||||
command: {
|
||||
id: StartAction.ID,
|
||||
title: nls.localize({ key: 'miStartDebugging', comment: ['&& denotes a mnemonic'] }, "&&Start Debugging")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, {
|
||||
group: '1_debug',
|
||||
command: {
|
||||
id: RunAction.ID,
|
||||
title: nls.localize({ key: 'miStartWithoutDebugging', comment: ['&& denotes a mnemonic'] }, "Start &&Without Debugging")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, {
|
||||
group: '1_debug',
|
||||
command: {
|
||||
id: StopAction.ID,
|
||||
title: nls.localize({ key: 'miStopDebugging', comment: ['&& denotes a mnemonic'] }, "&&Stop Debugging"),
|
||||
precondition: CONTEXT_IN_DEBUG_MODE
|
||||
},
|
||||
order: 3
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, {
|
||||
group: '1_debug',
|
||||
command: {
|
||||
id: RestartAction.ID,
|
||||
title: nls.localize({ key: 'miRestart Debugging', comment: ['&& denotes a mnemonic'] }, "&&Restart Debugging"),
|
||||
precondition: CONTEXT_IN_DEBUG_MODE
|
||||
},
|
||||
order: 4
|
||||
});
|
||||
|
||||
// Configuration
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, {
|
||||
group: '2_configuration',
|
||||
command: {
|
||||
id: ConfigureAction.ID,
|
||||
title: nls.localize({ key: 'miOpenConfigurations', comment: ['&& denotes a mnemonic'] }, "Open &&Configurations")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, {
|
||||
group: '2_configuration',
|
||||
command: {
|
||||
id: ADD_CONFIGURATION_ID,
|
||||
title: nls.localize({ key: 'miAddConfiguration', comment: ['&& denotes a mnemonic'] }, "A&&dd Configuration...")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
|
||||
// Step Commands
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, {
|
||||
group: '3_step',
|
||||
command: {
|
||||
id: StepOverAction.ID,
|
||||
title: nls.localize({ key: 'miStepOver', comment: ['&& denotes a mnemonic'] }, "Step &&Over"),
|
||||
precondition: CONTEXT_DEBUG_STATE.isEqualTo('stopped')
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, {
|
||||
group: '3_step',
|
||||
command: {
|
||||
id: StepIntoAction.ID,
|
||||
title: nls.localize({ key: 'miStepInto', comment: ['&& denotes a mnemonic'] }, "Step &&Into"),
|
||||
precondition: CONTEXT_DEBUG_STATE.isEqualTo('stopped')
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, {
|
||||
group: '3_step',
|
||||
command: {
|
||||
id: StepOutAction.ID,
|
||||
title: nls.localize({ key: 'miStepOut', comment: ['&& denotes a mnemonic'] }, "Step O&&ut"),
|
||||
precondition: CONTEXT_DEBUG_STATE.isEqualTo('stopped')
|
||||
},
|
||||
order: 3
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, {
|
||||
group: '3_step',
|
||||
command: {
|
||||
id: ContinueAction.ID,
|
||||
title: nls.localize({ key: 'miContinue', comment: ['&& denotes a mnemonic'] }, "&&Continue"),
|
||||
precondition: CONTEXT_DEBUG_STATE.isEqualTo('stopped')
|
||||
},
|
||||
order: 4
|
||||
});
|
||||
|
||||
// New Breakpoints
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, {
|
||||
group: '4_new_breakpoint',
|
||||
command: {
|
||||
id: TOGGLE_BREAKPOINT_ID,
|
||||
title: nls.localize({ key: 'miToggleBreakpoint', comment: ['&& denotes a mnemonic'] }, "Toggle &&Breakpoint")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarNewBreakpointMenu, {
|
||||
group: '1_breakpoints',
|
||||
command: {
|
||||
id: TOGGLE_CONDITIONAL_BREAKPOINT_ID,
|
||||
title: nls.localize({ key: 'miConditionalBreakpoint', comment: ['&& denotes a mnemonic'] }, "&&Conditional Breakpoint...")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarNewBreakpointMenu, {
|
||||
group: '1_breakpoints',
|
||||
command: {
|
||||
id: TOGGLE_INLINE_BREAKPOINT_ID,
|
||||
title: nls.localize({ key: 'miInlineBreakpoint', comment: ['&& denotes a mnemonic'] }, "Inline Breakp&&oint")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarNewBreakpointMenu, {
|
||||
group: '1_breakpoints',
|
||||
command: {
|
||||
id: AddFunctionBreakpointAction.ID,
|
||||
title: nls.localize({ key: 'miFunctionBreakpoint', comment: ['&& denotes a mnemonic'] }, "&&Function Breakpoint...")
|
||||
},
|
||||
order: 3
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarNewBreakpointMenu, {
|
||||
group: '1_breakpoints',
|
||||
command: {
|
||||
id: TOGGLE_LOG_POINT_ID,
|
||||
title: nls.localize({ key: 'miLogPoint', comment: ['&& denotes a mnemonic'] }, "&&Logpoint...")
|
||||
},
|
||||
order: 4
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, {
|
||||
group: '4_new_breakpoint',
|
||||
title: nls.localize({ key: 'miNewBreakpoint', comment: ['&& denotes a mnemonic'] }, "&&New Breakpoint"),
|
||||
submenu: MenuId.MenubarNewBreakpointMenu,
|
||||
order: 2
|
||||
});
|
||||
|
||||
// Modify Breakpoints
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, {
|
||||
group: '5_breakpoints',
|
||||
command: {
|
||||
id: EnableAllBreakpointsAction.ID,
|
||||
title: nls.localize({ key: 'miEnableAllBreakpoints', comment: ['&& denotes a mnemonic'] }, "&&Enable All Breakpoints")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, {
|
||||
group: '5_breakpoints',
|
||||
command: {
|
||||
id: DisableAllBreakpointsAction.ID,
|
||||
title: nls.localize({ key: 'miDisableAllBreakpoints', comment: ['&& denotes a mnemonic'] }, "Disable A&&ll Breakpoints")
|
||||
},
|
||||
order: 2
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, {
|
||||
group: '5_breakpoints',
|
||||
command: {
|
||||
id: RemoveAllBreakpointsAction.ID,
|
||||
title: nls.localize({ key: 'miRemoveAllBreakpoints', comment: ['&& denotes a mnemonic'] }, "Remove &&All Breakpoints")
|
||||
},
|
||||
order: 3
|
||||
});
|
||||
|
||||
// Install Debuggers
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, {
|
||||
group: 'z_install',
|
||||
command: {
|
||||
id: 'debug.installAdditionalDebuggers',
|
||||
title: nls.localize({ key: 'miInstallAdditionalDebuggers', comment: ['&& denotes a mnemonic'] }, "&&Install Additional Debuggers...")
|
||||
},
|
||||
order: 1
|
||||
});
|
||||
|
||||
// Touch Bar
|
||||
if (isMacintosh) {
|
||||
|
||||
const registerTouchBarEntry = (id: string, title: string, order, when: ContextKeyExpr, icon: string) => {
|
||||
MenuRegistry.appendMenuItem(MenuId.TouchBarContext, {
|
||||
command: {
|
||||
id, title, iconLocation: { dark: URI.parse(require.toUrl(`vs/workbench/contrib/debug/electron-browser/media/${icon}`)) }
|
||||
},
|
||||
when,
|
||||
group: '9_debug',
|
||||
order
|
||||
});
|
||||
};
|
||||
|
||||
registerTouchBarEntry(StartAction.ID, StartAction.LABEL, 0, CONTEXT_IN_DEBUG_MODE.toNegated(), 'continue-tb.png');
|
||||
registerTouchBarEntry(RunAction.ID, RunAction.LABEL, 1, CONTEXT_IN_DEBUG_MODE.toNegated(), 'continue-without-debugging-tb.png');
|
||||
registerTouchBarEntry(ContinueAction.ID, ContinueAction.LABEL, 0, CONTEXT_DEBUG_STATE.isEqualTo('stopped'), 'continue-tb.png');
|
||||
registerTouchBarEntry(PauseAction.ID, PauseAction.LABEL, 1, ContextKeyExpr.and(CONTEXT_IN_DEBUG_MODE, ContextKeyExpr.notEquals('debugState', 'stopped')), 'pause-tb.png');
|
||||
registerTouchBarEntry(StepOverAction.ID, StepOverAction.LABEL, 2, CONTEXT_DEBUG_STATE.isEqualTo('stopped'), 'stepover-tb.png');
|
||||
registerTouchBarEntry(StepIntoAction.ID, StepIntoAction.LABEL, 3, CONTEXT_DEBUG_STATE.isEqualTo('stopped'), 'stepinto-tb.png');
|
||||
registerTouchBarEntry(StepOutAction.ID, StepOutAction.LABEL, 4, CONTEXT_DEBUG_STATE.isEqualTo('stopped'), 'stepout-tb.png');
|
||||
registerTouchBarEntry(RestartAction.ID, RestartAction.LABEL, 5, CONTEXT_IN_DEBUG_MODE, 'restart-tb.png');
|
||||
registerTouchBarEntry(StopAction.ID, StopAction.LABEL, 6, CONTEXT_IN_DEBUG_MODE, 'stop-tb.png');
|
||||
}
|
||||
@@ -0,0 +1,675 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 * as strings from 'vs/base/common/strings';
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { URI as uri } from 'vs/base/common/uri';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import { IJSONSchema } from 'vs/base/common/jsonSchema';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { IEditor } from 'vs/workbench/common/editor';
|
||||
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IDebugConfigurationProvider, ICompound, IDebugConfiguration, IConfig, IGlobalConfig, IConfigurationManager, ILaunch, IDebugAdapterDescriptorFactory, IDebugAdapter, ITerminalSettings, ITerminalLauncher, IDebugSession, IAdapterDescriptor, CONTEXT_DEBUG_CONFIGURATION_TYPE, IDebugAdapterFactory, IDebugAdapterTrackerFactory, IDebugService } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { Debugger } from 'vs/workbench/contrib/debug/node/debugger';
|
||||
import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { isCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { launchSchemaId } from 'vs/workbench/services/configuration/common/configuration';
|
||||
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
|
||||
import { TerminalLauncher } from 'vs/workbench/contrib/debug/electron-browser/terminalSupport';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IJSONContributionRegistry, Extensions as JSONExtensions } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
|
||||
import { launchSchema, debuggersExtPoint, breakpointsExtPoint } from 'vs/workbench/contrib/debug/common/debugSchemas';
|
||||
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
|
||||
const jsonRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
|
||||
jsonRegistry.registerSchema(launchSchemaId, launchSchema);
|
||||
|
||||
const DEBUG_SELECTED_CONFIG_NAME_KEY = 'debug.selectedconfigname';
|
||||
const DEBUG_SELECTED_ROOT = 'debug.selectedroot';
|
||||
|
||||
export class ConfigurationManager implements IConfigurationManager {
|
||||
private debuggers: Debugger[];
|
||||
private breakpointModeIdsSet = new Set<string>();
|
||||
private launches: ILaunch[];
|
||||
private selectedName: string | undefined;
|
||||
private selectedLaunch: ILaunch | undefined;
|
||||
private toDispose: IDisposable[];
|
||||
private _onDidSelectConfigurationName = new Emitter<void>();
|
||||
private configProviders: IDebugConfigurationProvider[];
|
||||
private adapterDescriptorFactories: IDebugAdapterDescriptorFactory[];
|
||||
private adapterTrackerFactories: IDebugAdapterTrackerFactory[];
|
||||
private debugAdapterFactories: Map<string, IDebugAdapterFactory>;
|
||||
private terminalLauncher: ITerminalLauncher;
|
||||
private debugConfigurationTypeContext: IContextKey<string>;
|
||||
|
||||
constructor(
|
||||
private debugService: IDebugService,
|
||||
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService,
|
||||
@IInstantiationService private readonly instantiationService: IInstantiationService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@ILifecycleService lifecycleService: ILifecycleService,
|
||||
@IExtensionService private readonly extensionService: IExtensionService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService
|
||||
) {
|
||||
this.configProviders = [];
|
||||
this.adapterDescriptorFactories = [];
|
||||
this.adapterTrackerFactories = [];
|
||||
this.debuggers = [];
|
||||
this.toDispose = [];
|
||||
this.registerListeners(lifecycleService);
|
||||
this.initLaunches();
|
||||
const previousSelectedRoot = this.storageService.get(DEBUG_SELECTED_ROOT, StorageScope.WORKSPACE);
|
||||
const previousSelectedLaunch = this.launches.filter(l => l.uri.toString() === previousSelectedRoot).pop();
|
||||
this.debugConfigurationTypeContext = CONTEXT_DEBUG_CONFIGURATION_TYPE.bindTo(contextKeyService);
|
||||
this.debugAdapterFactories = new Map();
|
||||
if (previousSelectedLaunch) {
|
||||
this.selectConfiguration(previousSelectedLaunch, this.storageService.get(DEBUG_SELECTED_CONFIG_NAME_KEY, StorageScope.WORKSPACE));
|
||||
}
|
||||
}
|
||||
|
||||
// debuggers
|
||||
|
||||
registerDebugAdapterFactory(debugTypes: string[], debugAdapterLauncher: IDebugAdapterFactory): IDisposable {
|
||||
debugTypes.forEach(debugType => this.debugAdapterFactories.set(debugType, debugAdapterLauncher));
|
||||
return {
|
||||
dispose: () => {
|
||||
debugTypes.forEach(debugType => this.debugAdapterFactories.delete(debugType));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
createDebugAdapter(session: IDebugSession): IDebugAdapter | undefined {
|
||||
let dap = this.debugAdapterFactories.get(session.configuration.type);
|
||||
if (dap) {
|
||||
return dap.createDebugAdapter(session);
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
substituteVariables(debugType: string, folder: IWorkspaceFolder | undefined, config: IConfig): Promise<IConfig> {
|
||||
let dap = this.debugAdapterFactories.get(debugType);
|
||||
if (dap) {
|
||||
return dap.substituteVariables(folder, config);
|
||||
}
|
||||
return Promise.resolve(config);
|
||||
}
|
||||
|
||||
runInTerminal(debugType: string, args: DebugProtocol.RunInTerminalRequestArguments, config: ITerminalSettings): Promise<number | undefined> {
|
||||
let tl: ITerminalLauncher | undefined = this.debugAdapterFactories.get(debugType);
|
||||
if (!tl) {
|
||||
if (!this.terminalLauncher) {
|
||||
this.terminalLauncher = this.instantiationService.createInstance(TerminalLauncher);
|
||||
}
|
||||
tl = this.terminalLauncher;
|
||||
}
|
||||
return tl.runInTerminal(args, config);
|
||||
}
|
||||
|
||||
// debug adapter
|
||||
|
||||
registerDebugAdapterDescriptorFactory(debugAdapterProvider: IDebugAdapterDescriptorFactory): IDisposable {
|
||||
this.adapterDescriptorFactories.push(debugAdapterProvider);
|
||||
return {
|
||||
dispose: () => {
|
||||
this.unregisterDebugAdapterDescriptorFactory(debugAdapterProvider);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
unregisterDebugAdapterDescriptorFactory(debugAdapterProvider: IDebugAdapterDescriptorFactory): void {
|
||||
const ix = this.adapterDescriptorFactories.indexOf(debugAdapterProvider);
|
||||
if (ix >= 0) {
|
||||
this.adapterDescriptorFactories.splice(ix, 1);
|
||||
}
|
||||
}
|
||||
|
||||
getDebugAdapterDescriptor(session: IDebugSession): Promise<IAdapterDescriptor | undefined> {
|
||||
|
||||
const config = session.configuration;
|
||||
|
||||
// first try legacy proposed API: DebugConfigurationProvider.debugAdapterExecutable
|
||||
const providers0 = this.configProviders.filter(p => p.type === config.type && p.debugAdapterExecutable);
|
||||
if (providers0.length === 1 && providers0[0].debugAdapterExecutable) {
|
||||
return providers0[0].debugAdapterExecutable(session.root ? session.root.uri : undefined);
|
||||
} else {
|
||||
// TODO@AW handle n > 1 case
|
||||
}
|
||||
|
||||
// new API
|
||||
const providers = this.adapterDescriptorFactories.filter(p => p.type === config.type && p.createDebugAdapterDescriptor);
|
||||
if (providers.length === 1) {
|
||||
return providers[0].createDebugAdapterDescriptor(session);
|
||||
} else {
|
||||
// TODO@AW handle n > 1 case
|
||||
}
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
// debug adapter trackers
|
||||
|
||||
registerDebugAdapterTrackerFactory(debugAdapterTrackerFactory: IDebugAdapterTrackerFactory): IDisposable {
|
||||
this.adapterTrackerFactories.push(debugAdapterTrackerFactory);
|
||||
return {
|
||||
dispose: () => {
|
||||
this.unregisterDebugAdapterTrackerFactory(debugAdapterTrackerFactory);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
unregisterDebugAdapterTrackerFactory(debugAdapterTrackerFactory: IDebugAdapterTrackerFactory): void {
|
||||
const ix = this.adapterTrackerFactories.indexOf(debugAdapterTrackerFactory);
|
||||
if (ix >= 0) {
|
||||
this.adapterTrackerFactories.splice(ix, 1);
|
||||
}
|
||||
}
|
||||
|
||||
// debug configurations
|
||||
|
||||
registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable {
|
||||
this.configProviders.push(debugConfigurationProvider);
|
||||
return {
|
||||
dispose: () => {
|
||||
this.unregisterDebugConfigurationProvider(debugConfigurationProvider);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
unregisterDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): void {
|
||||
const ix = this.configProviders.indexOf(debugConfigurationProvider);
|
||||
if (ix >= 0) {
|
||||
this.configProviders.splice(ix, 1);
|
||||
}
|
||||
}
|
||||
|
||||
hasDebugConfigurationProvider(debugType: string): boolean {
|
||||
// check if there are providers for the given type that contribute a provideDebugConfigurations method
|
||||
const providers = this.configProviders.filter(p => p.provideDebugConfigurations && (p.type === debugType));
|
||||
return providers.length > 0;
|
||||
}
|
||||
|
||||
needsToRunInExtHost(debugType: string): boolean {
|
||||
|
||||
// if the given debugType matches any registered tracker factory we need to run the DA in the EH
|
||||
const providers = this.adapterTrackerFactories.filter(p => p.type === debugType || p.type === '*');
|
||||
return providers.length > 0;
|
||||
}
|
||||
|
||||
resolveConfigurationByProviders(folderUri: uri | undefined, type: string | undefined, debugConfiguration: IConfig): Promise<IConfig | null | undefined> {
|
||||
return this.activateDebuggers('onDebugResolve', type).then(() => {
|
||||
// pipe the config through the promises sequentially. Append at the end the '*' types
|
||||
const providers = this.configProviders.filter(p => p.type === type && p.resolveDebugConfiguration)
|
||||
.concat(this.configProviders.filter(p => p.type === '*' && p.resolveDebugConfiguration));
|
||||
|
||||
return providers.reduce((promise, provider) => {
|
||||
return promise.then(config => {
|
||||
if (config) {
|
||||
return provider.resolveDebugConfiguration!(folderUri, config);
|
||||
} else {
|
||||
return Promise.resolve(config);
|
||||
}
|
||||
});
|
||||
}, Promise.resolve(debugConfiguration));
|
||||
});
|
||||
}
|
||||
|
||||
provideDebugConfigurations(folderUri: uri | undefined, type: string): Promise<any[]> {
|
||||
return this.activateDebuggers('onDebugInitialConfigurations')
|
||||
.then(() => Promise.all(this.configProviders.filter(p => p.type === type && p.provideDebugConfigurations).map(p => p.provideDebugConfigurations!(folderUri)))
|
||||
.then(results => results.reduce((first, second) => first.concat(second), [])));
|
||||
}
|
||||
|
||||
private registerListeners(lifecycleService: ILifecycleService): void {
|
||||
debuggersExtPoint.setHandler((extensions, delta) => {
|
||||
delta.added.forEach(added => {
|
||||
added.value.forEach(rawAdapter => {
|
||||
if (!rawAdapter.type || (typeof rawAdapter.type !== 'string')) {
|
||||
added.collector.error(nls.localize('debugNoType', "Debugger 'type' can not be omitted and must be of type 'string'."));
|
||||
}
|
||||
if (rawAdapter.enableBreakpointsFor) {
|
||||
rawAdapter.enableBreakpointsFor.languageIds.forEach(modeId => {
|
||||
this.breakpointModeIdsSet.add(modeId);
|
||||
});
|
||||
}
|
||||
|
||||
if (rawAdapter.type !== '*') {
|
||||
const existing = this.getDebugger(rawAdapter.type);
|
||||
if (existing) {
|
||||
existing.merge(rawAdapter, added.description);
|
||||
} else {
|
||||
this.debuggers.push(this.instantiationService.createInstance(Debugger, this, rawAdapter, added.description));
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// take care of all wildcard contributions
|
||||
extensions.forEach(extension => {
|
||||
extension.value.forEach(rawAdapter => {
|
||||
if (rawAdapter.type === '*') {
|
||||
this.debuggers.forEach(dbg => dbg.merge(rawAdapter, extension.description));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
delta.removed.forEach(removed => {
|
||||
const removedTypes = removed.value.map(rawAdapter => rawAdapter.type);
|
||||
this.debuggers = this.debuggers.filter(d => removedTypes.indexOf(d.type) === -1);
|
||||
this.debugService.getModel().getSessions().forEach(s => {
|
||||
// Stop sessions if their debugger has been removed
|
||||
if (removedTypes.indexOf(s.configuration.type) >= 0) {
|
||||
this.debugService.stopSession(s).then(undefined, onUnexpectedError);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// update the schema to include all attributes, snippets and types from extensions.
|
||||
this.debuggers.forEach(adapter => {
|
||||
const items = (<IJSONSchema>launchSchema.properties!['configurations'].items);
|
||||
const schemaAttributes = adapter.getSchemaAttributes();
|
||||
if (schemaAttributes && items.oneOf) {
|
||||
items.oneOf.push(...schemaAttributes);
|
||||
}
|
||||
const configurationSnippets = adapter.configurationSnippets;
|
||||
if (configurationSnippets && items.defaultSnippets) {
|
||||
items.defaultSnippets.push(...configurationSnippets);
|
||||
}
|
||||
});
|
||||
|
||||
this.setCompoundSchemaValues();
|
||||
});
|
||||
|
||||
breakpointsExtPoint.setHandler((extensions, delta) => {
|
||||
delta.removed.forEach(removed => {
|
||||
removed.value.forEach(breakpoints => this.breakpointModeIdsSet.delete(breakpoints.language));
|
||||
});
|
||||
delta.added.forEach(added => {
|
||||
added.value.forEach(breakpoints => this.breakpointModeIdsSet.add(breakpoints.language));
|
||||
});
|
||||
});
|
||||
|
||||
this.toDispose.push(this.contextService.onDidChangeWorkspaceFolders(() => {
|
||||
this.initLaunches();
|
||||
this.selectConfiguration(this.selectedLaunch);
|
||||
this.setCompoundSchemaValues();
|
||||
}));
|
||||
this.toDispose.push(this.configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('launch')) {
|
||||
this.selectConfiguration(this.selectedLaunch);
|
||||
this.setCompoundSchemaValues();
|
||||
}
|
||||
}));
|
||||
|
||||
this.toDispose.push(this.storageService.onWillSaveState(this.saveState, this));
|
||||
}
|
||||
|
||||
private initLaunches(): void {
|
||||
this.launches = this.contextService.getWorkspace().folders.map(folder => this.instantiationService.createInstance(Launch, this, folder));
|
||||
if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
|
||||
this.launches.push(this.instantiationService.createInstance(WorkspaceLaunch));
|
||||
}
|
||||
this.launches.push(this.instantiationService.createInstance(UserLaunch));
|
||||
|
||||
if (this.selectedLaunch && this.launches.indexOf(this.selectedLaunch) === -1) {
|
||||
this.selectedLaunch = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
private setCompoundSchemaValues(): void {
|
||||
const compoundConfigurationsSchema = (<IJSONSchema>launchSchema.properties!['compounds'].items).properties!['configurations'];
|
||||
const launchNames = this.launches.map(l =>
|
||||
l.getConfigurationNames(false)).reduce((first, second) => first.concat(second), []);
|
||||
(<IJSONSchema>compoundConfigurationsSchema.items).oneOf![0].enum = launchNames;
|
||||
(<IJSONSchema>compoundConfigurationsSchema.items).oneOf![1].properties!.name.enum = launchNames;
|
||||
|
||||
const folderNames = this.contextService.getWorkspace().folders.map(f => f.name);
|
||||
(<IJSONSchema>compoundConfigurationsSchema.items).oneOf![1].properties!.folder.enum = folderNames;
|
||||
|
||||
jsonRegistry.registerSchema(launchSchemaId, launchSchema);
|
||||
}
|
||||
|
||||
getLaunches(): ILaunch[] {
|
||||
return this.launches;
|
||||
}
|
||||
|
||||
getLaunch(workspaceUri: uri | undefined): ILaunch | undefined {
|
||||
if (!uri.isUri(workspaceUri)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.launches.filter(l => l.workspace && l.workspace.uri.toString() === workspaceUri.toString()).pop();
|
||||
}
|
||||
|
||||
get selectedConfiguration(): { launch: ILaunch | undefined, name: string | undefined } {
|
||||
return {
|
||||
launch: this.selectedLaunch,
|
||||
name: this.selectedName
|
||||
};
|
||||
}
|
||||
|
||||
get onDidSelectConfiguration(): Event<void> {
|
||||
return this._onDidSelectConfigurationName.event;
|
||||
}
|
||||
|
||||
getWorkspaceLaunch(): ILaunch | undefined {
|
||||
if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
|
||||
return this.launches[this.launches.length - 1];
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
selectConfiguration(launch: ILaunch | undefined, name?: string): 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 (!this.selectedName || names.indexOf(this.selectedName) === -1) {
|
||||
this.selectedName = names.length ? names[0] : undefined;
|
||||
}
|
||||
|
||||
const configuration = this.selectedLaunch && this.selectedName ? this.selectedLaunch.getConfiguration(this.selectedName) : undefined;
|
||||
if (configuration) {
|
||||
this.debugConfigurationTypeContext.set(configuration.type);
|
||||
} else {
|
||||
this.debugConfigurationTypeContext.reset();
|
||||
}
|
||||
|
||||
if (this.selectedLaunch !== previousLaunch || this.selectedName !== previousName) {
|
||||
this._onDidSelectConfigurationName.fire();
|
||||
}
|
||||
}
|
||||
|
||||
canSetBreakpointsIn(model: ITextModel): boolean {
|
||||
const modeId = model.getLanguageIdentifier().language;
|
||||
if (!modeId || modeId === 'jsonc' || modeId === 'log') {
|
||||
// do not allow breakpoints in our settings files and output
|
||||
return false;
|
||||
}
|
||||
if (this.configurationService.getValue<IDebugConfiguration>('debug').allowBreakpointsEverywhere) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this.breakpointModeIdsSet.has(modeId);
|
||||
}
|
||||
|
||||
getDebugger(type: string): Debugger | undefined {
|
||||
return this.debuggers.filter(dbg => strings.equalsIgnoreCase(dbg.type, type)).pop();
|
||||
}
|
||||
|
||||
guessDebugger(type?: string): Promise<Debugger | undefined> {
|
||||
if (type) {
|
||||
const adapter = this.getDebugger(type);
|
||||
return Promise.resolve(adapter);
|
||||
}
|
||||
|
||||
const activeTextEditorWidget = this.editorService.activeTextEditorWidget;
|
||||
let candidates: Promise<Debugger[]> | undefined;
|
||||
if (isCodeEditor(activeTextEditorWidget)) {
|
||||
const model = activeTextEditorWidget.getModel();
|
||||
const language = model ? model.getLanguageIdentifier().language : undefined;
|
||||
const adapters = this.debuggers.filter(a => language && a.languages && a.languages.indexOf(language) >= 0);
|
||||
if (adapters.length === 1) {
|
||||
return Promise.resolve(adapters[0]);
|
||||
}
|
||||
if (adapters.length > 1) {
|
||||
candidates = Promise.resolve(adapters);
|
||||
}
|
||||
}
|
||||
|
||||
if (!candidates) {
|
||||
candidates = this.activateDebuggers('onDebugInitialConfigurations').then(() => this.debuggers.filter(dbg => dbg.hasInitialConfiguration() || dbg.hasConfigurationProvider()));
|
||||
}
|
||||
|
||||
return candidates.then(debuggers => {
|
||||
debuggers.sort((first, second) => first.label.localeCompare(second.label));
|
||||
const picks = debuggers.map(c => ({ label: c.label, debugger: c }));
|
||||
return this.quickInputService.pick<{ label: string, debugger: Debugger | undefined }>([...picks, { type: 'separator' }, { label: 'More...', debugger: undefined }], { placeHolder: nls.localize('selectDebug', "Select Environment") })
|
||||
.then(picked => {
|
||||
if (picked && picked.debugger) {
|
||||
return picked.debugger;
|
||||
}
|
||||
if (picked) {
|
||||
this.commandService.executeCommand('debug.installAdditionalDebuggers');
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
activateDebuggers(activationEvent: string, debugType?: string): Promise<void> {
|
||||
const thenables: Promise<any>[] = [
|
||||
this.extensionService.activateByEvent(activationEvent),
|
||||
this.extensionService.activateByEvent('onDebug')
|
||||
];
|
||||
if (debugType) {
|
||||
thenables.push(this.extensionService.activateByEvent(`${activationEvent}:${debugType}`));
|
||||
}
|
||||
return Promise.all(thenables).then(_ => {
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
|
||||
private saveState(): void {
|
||||
if (this.selectedName) {
|
||||
this.storageService.store(DEBUG_SELECTED_CONFIG_NAME_KEY, this.selectedName, StorageScope.WORKSPACE);
|
||||
}
|
||||
if (this.selectedLaunch) {
|
||||
this.storageService.store(DEBUG_SELECTED_ROOT, this.selectedLaunch.uri.toString(), StorageScope.WORKSPACE);
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.toDispose = dispose(this.toDispose);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class AbstractLaunch {
|
||||
protected abstract getConfig(): IGlobalConfig | undefined;
|
||||
|
||||
getCompound(name: string): ICompound | undefined {
|
||||
const config = this.getConfig();
|
||||
if (!config || !config.compounds) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return config.compounds.filter(compound => compound.name === name).pop();
|
||||
}
|
||||
|
||||
getConfigurationNames(includeCompounds = true): string[] {
|
||||
const config = this.getConfig();
|
||||
if (!config || !config.configurations || !Array.isArray(config.configurations)) {
|
||||
return [];
|
||||
} else {
|
||||
const names = config.configurations.filter(cfg => cfg && typeof cfg.name === 'string').map(cfg => cfg.name);
|
||||
if (includeCompounds && config.compounds) {
|
||||
if (config.compounds) {
|
||||
names.push(...config.compounds.filter(compound => typeof compound.name === 'string' && compound.configurations && compound.configurations.length)
|
||||
.map(compound => compound.name));
|
||||
}
|
||||
}
|
||||
|
||||
return names;
|
||||
}
|
||||
}
|
||||
|
||||
getConfiguration(name: string): IConfig | undefined {
|
||||
// We need to clone the configuration in order to be able to make changes to it #42198
|
||||
const config = objects.deepClone(this.getConfig());
|
||||
if (!config || !config.configurations) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return config.configurations.filter(config => config && config.name === name).shift();
|
||||
}
|
||||
|
||||
get hidden(): boolean {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
class Launch extends AbstractLaunch implements ILaunch {
|
||||
|
||||
constructor(
|
||||
private configurationManager: ConfigurationManager,
|
||||
public workspace: IWorkspaceFolder,
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
get uri(): uri {
|
||||
return resources.joinPath(this.workspace.uri, '/.vscode/launch.json');
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return this.workspace.name;
|
||||
}
|
||||
|
||||
protected getConfig(): IGlobalConfig | undefined {
|
||||
return this.configurationService.inspect<IGlobalConfig>('launch', { resource: this.workspace.uri }).workspaceFolder;
|
||||
}
|
||||
|
||||
openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string): Promise<{ editor: IEditor | null, created: boolean }> {
|
||||
const resource = this.uri;
|
||||
let created = false;
|
||||
|
||||
return this.fileService.resolveContent(resource).then(content => content.value, err => {
|
||||
// launch.json not found: create one by collecting launch configs from debugConfigProviders
|
||||
return this.configurationManager.guessDebugger(type).then(adapter => {
|
||||
if (adapter) {
|
||||
return this.configurationManager.provideDebugConfigurations(this.workspace.uri, adapter.type).then(initialConfigs => {
|
||||
return adapter.getInitialConfigurationContent(initialConfigs);
|
||||
});
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}).then(content => {
|
||||
|
||||
if (!content) {
|
||||
return '';
|
||||
}
|
||||
|
||||
created = true; // pin only if config file is created #8727
|
||||
return this.fileService.updateContent(resource, content).then(() => {
|
||||
// convert string into IContent; see #32135
|
||||
return content;
|
||||
});
|
||||
});
|
||||
}).then(content => {
|
||||
if (!content) {
|
||||
return { editor: null, created: false };
|
||||
}
|
||||
const index = content.indexOf(`"${this.configurationManager.selectedConfiguration.name}"`);
|
||||
let startLineNumber = 1;
|
||||
for (let i = 0; i < index; i++) {
|
||||
if (content.charAt(i) === '\n') {
|
||||
startLineNumber++;
|
||||
}
|
||||
}
|
||||
const selection = startLineNumber > 1 ? { startLineNumber, startColumn: 4 } : undefined;
|
||||
|
||||
return Promise.resolve(this.editorService.openEditor({
|
||||
resource,
|
||||
options: {
|
||||
selection,
|
||||
preserveFocus,
|
||||
pinned: created,
|
||||
revealIfVisible: true
|
||||
},
|
||||
}, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => ({ editor, created })));
|
||||
}, (error) => {
|
||||
throw new Error(nls.localize('DebugConfig.failed', "Unable to create 'launch.json' file inside the '.vscode' folder ({0}).", error));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class WorkspaceLaunch extends AbstractLaunch implements ILaunch {
|
||||
constructor(
|
||||
@IEditorService private readonly editorService: IEditorService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
get workspace(): undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
get uri(): uri {
|
||||
return this.contextService.getWorkspace().configuration!;
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return nls.localize('workspace', "workspace");
|
||||
}
|
||||
|
||||
protected getConfig(): IGlobalConfig | undefined {
|
||||
return this.configurationService.inspect<IGlobalConfig>('launch').workspace;
|
||||
}
|
||||
|
||||
openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string): Promise<{ editor: IEditor | null, created: boolean }> {
|
||||
return this.editorService.openEditor({
|
||||
resource: this.contextService.getWorkspace().configuration!,
|
||||
options: { preserveFocus }
|
||||
}, sideBySide ? SIDE_GROUP : ACTIVE_GROUP).then(editor => ({ editor, created: false }));
|
||||
}
|
||||
}
|
||||
|
||||
class UserLaunch extends AbstractLaunch implements ILaunch {
|
||||
|
||||
constructor(
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IPreferencesService private readonly preferencesService: IPreferencesService
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
get workspace(): undefined {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
get uri(): uri {
|
||||
return this.preferencesService.userSettingsResource;
|
||||
}
|
||||
|
||||
get name(): string {
|
||||
return nls.localize('user settings', "user settings");
|
||||
}
|
||||
|
||||
get hidden(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected getConfig(): IGlobalConfig | undefined {
|
||||
return this.configurationService.inspect<IGlobalConfig>('launch').user;
|
||||
}
|
||||
|
||||
openConfigFile(sideBySide: boolean, preserveFocus: boolean, type?: string): Promise<{ editor: IEditor | null, created: boolean }> {
|
||||
return this.preferencesService.openGlobalSettings(false, { preserveFocus }).then(editor => ({ editor, created: false }));
|
||||
}
|
||||
}
|
||||
1110
src/vs/workbench/contrib/debug/electron-browser/debugService.ts
Normal file
849
src/vs/workbench/contrib/debug/electron-browser/debugSession.ts
Normal file
@@ -0,0 +1,849 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import severity from 'vs/base/common/severity';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { CompletionItem, completionKindFromString } from 'vs/editor/common/modes';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import * as aria from 'vs/base/browser/ui/aria/aria';
|
||||
import { IDebugSession, IConfig, IThread, IRawModelUpdate, IDebugService, IRawStoppedDetails, State, LoadedSourceEvent, IFunctionBreakpoint, IExceptionBreakpoint, IBreakpoint, IExceptionInfo, AdapterEndEvent, IDebugger, VIEWLET_ID, IDebugConfiguration, IReplElement, IStackFrame, IExpression, IReplElementSource } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { Source } from 'vs/workbench/contrib/debug/common/debugSource';
|
||||
import { mixin } from 'vs/base/common/objects';
|
||||
import { Thread, ExpressionContainer, DebugModel } from 'vs/workbench/contrib/debug/common/debugModel';
|
||||
import { RawDebugSession } from 'vs/workbench/contrib/debug/electron-browser/rawDebugSession';
|
||||
import product from 'vs/platform/product/node/product';
|
||||
import { IWorkspaceFolder, IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import { generateUuid } from 'vs/base/common/uuid';
|
||||
import { IWindowService } from 'vs/platform/windows/common/windows';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { normalizeDriveLetter } from 'vs/base/common/labels';
|
||||
import { IOutputService } from 'vs/workbench/contrib/output/common/output';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { ReplModel } from 'vs/workbench/contrib/debug/common/replModel';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
|
||||
export class DebugSession implements IDebugSession {
|
||||
private id: string;
|
||||
private raw: RawDebugSession | undefined;
|
||||
private initialized = false;
|
||||
|
||||
private sources = new Map<string, Source>();
|
||||
private threads = new Map<number, Thread>();
|
||||
private rawListeners: IDisposable[] = [];
|
||||
private fetchThreadsScheduler: RunOnceScheduler;
|
||||
private repl: ReplModel;
|
||||
|
||||
private readonly _onDidChangeState = new Emitter<void>();
|
||||
private readonly _onDidEndAdapter = new Emitter<AdapterEndEvent>();
|
||||
|
||||
private readonly _onDidLoadedSource = new Emitter<LoadedSourceEvent>();
|
||||
private readonly _onDidCustomEvent = new Emitter<DebugProtocol.Event>();
|
||||
|
||||
private readonly _onDidChangeREPLElements = new Emitter<void>();
|
||||
|
||||
constructor(
|
||||
private _configuration: { resolved: IConfig, unresolved: IConfig | undefined },
|
||||
public root: IWorkspaceFolder,
|
||||
private model: DebugModel,
|
||||
@IDebugService private readonly debugService: IDebugService,
|
||||
@ITelemetryService private readonly telemetryService: ITelemetryService,
|
||||
@IOutputService private readonly outputService: IOutputService,
|
||||
@IWindowService private readonly windowService: IWindowService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IViewletService private readonly viewletService: IViewletService,
|
||||
@IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@INotificationService private readonly notificationService: INotificationService
|
||||
) {
|
||||
this.id = generateUuid();
|
||||
this.repl = new ReplModel(this);
|
||||
}
|
||||
|
||||
getId(): string {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
get configuration(): IConfig {
|
||||
return this._configuration.resolved;
|
||||
}
|
||||
|
||||
get unresolvedConfiguration(): IConfig | undefined {
|
||||
return this._configuration.unresolved;
|
||||
}
|
||||
|
||||
setConfiguration(configuration: { resolved: IConfig, unresolved: IConfig | undefined }) {
|
||||
this._configuration = configuration;
|
||||
}
|
||||
|
||||
getLabel(): string {
|
||||
const includeRoot = this.workspaceContextService.getWorkspace().folders.length > 1;
|
||||
return includeRoot && this.root ? `${this.configuration.name} (${resources.basenameOrAuthority(this.root.uri)})` : this.configuration.name;
|
||||
}
|
||||
|
||||
get state(): State {
|
||||
if (!this.initialized) {
|
||||
return State.Initializing;
|
||||
}
|
||||
if (!this.raw) {
|
||||
return State.Inactive;
|
||||
}
|
||||
|
||||
const focusedThread = this.debugService.getViewModel().focusedThread;
|
||||
if (focusedThread && focusedThread.session === this) {
|
||||
return focusedThread.stopped ? State.Stopped : State.Running;
|
||||
}
|
||||
if (this.getAllThreads().some(t => t.stopped)) {
|
||||
return State.Stopped;
|
||||
}
|
||||
|
||||
return State.Running;
|
||||
}
|
||||
|
||||
get capabilities(): DebugProtocol.Capabilities {
|
||||
return this.raw ? this.raw.capabilities : Object.create(null);
|
||||
}
|
||||
|
||||
//---- events
|
||||
get onDidChangeState(): Event<void> {
|
||||
return this._onDidChangeState.event;
|
||||
}
|
||||
|
||||
get onDidEndAdapter(): Event<AdapterEndEvent> {
|
||||
return this._onDidEndAdapter.event;
|
||||
}
|
||||
|
||||
get onDidChangeReplElements(): Event<void> {
|
||||
return this._onDidChangeREPLElements.event;
|
||||
}
|
||||
|
||||
//---- DAP events
|
||||
|
||||
get onDidCustomEvent(): Event<DebugProtocol.Event> {
|
||||
return this._onDidCustomEvent.event;
|
||||
}
|
||||
|
||||
get onDidLoadedSource(): Event<LoadedSourceEvent> {
|
||||
return this._onDidLoadedSource.event;
|
||||
}
|
||||
|
||||
//---- DAP requests
|
||||
|
||||
/**
|
||||
* create and initialize a new debug adapter for this session
|
||||
*/
|
||||
initialize(dbgr: IDebugger): Promise<void> {
|
||||
|
||||
if (this.raw) {
|
||||
// if there was already a connection make sure to remove old listeners
|
||||
this.shutdown();
|
||||
}
|
||||
|
||||
return dbgr.getCustomTelemetryService().then(customTelemetryService => {
|
||||
|
||||
return dbgr.createDebugAdapter(this, this.outputService).then(debugAdapter => {
|
||||
|
||||
this.raw = new RawDebugSession(debugAdapter, dbgr, this.telemetryService, customTelemetryService, this.environmentService);
|
||||
|
||||
return this.raw!.start().then(() => {
|
||||
|
||||
this.registerListeners();
|
||||
|
||||
return this.raw!.initialize({
|
||||
clientID: 'vscode',
|
||||
clientName: product.nameLong,
|
||||
adapterID: this.configuration.type,
|
||||
pathFormat: 'path',
|
||||
linesStartAt1: true,
|
||||
columnsStartAt1: true,
|
||||
supportsVariableType: true, // #8858
|
||||
supportsVariablePaging: true, // #9537
|
||||
supportsRunInTerminalRequest: true, // #10574
|
||||
locale: platform.locale
|
||||
}).then(() => {
|
||||
this.initialized = true;
|
||||
this._onDidChangeState.fire();
|
||||
this.model.setExceptionBreakpoints(this.raw!.capabilities.exceptionBreakpointFilters || []);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* launch or attach to the debuggee
|
||||
*/
|
||||
launchOrAttach(config: IConfig): Promise<void> {
|
||||
if (this.raw) {
|
||||
|
||||
// __sessionID only used for EH debugging (but we add it always for now...)
|
||||
config.__sessionId = this.getId();
|
||||
|
||||
return this.raw.launchOrAttach(config).then(result => {
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
return Promise.reject(new Error('no debug adapter'));
|
||||
}
|
||||
|
||||
/**
|
||||
* end the current debug adapter session
|
||||
*/
|
||||
terminate(restart = false): Promise<void> {
|
||||
if (this.raw) {
|
||||
if (this.raw.capabilities.supportsTerminateRequest && this._configuration.resolved.request === 'launch') {
|
||||
return this.raw.terminate(restart).then(response => {
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
return this.raw.disconnect(restart).then(response => {
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
return Promise.reject(new Error('no debug adapter'));
|
||||
}
|
||||
|
||||
/**
|
||||
* end the current debug adapter session
|
||||
*/
|
||||
disconnect(restart = false): Promise<void> {
|
||||
if (this.raw) {
|
||||
return this.raw.disconnect(restart).then(response => {
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
return Promise.reject(new Error('no debug adapter'));
|
||||
}
|
||||
|
||||
/**
|
||||
* restart debug adapter session
|
||||
*/
|
||||
restart(): Promise<void> {
|
||||
if (this.raw) {
|
||||
return this.raw.restart().then(() => undefined);
|
||||
}
|
||||
return Promise.reject(new Error('no debug adapter'));
|
||||
}
|
||||
|
||||
sendBreakpoints(modelUri: URI, breakpointsToSend: IBreakpoint[], sourceModified: boolean): Promise<void> {
|
||||
|
||||
if (!this.raw) {
|
||||
return Promise.reject(new Error('no debug adapter'));
|
||||
}
|
||||
|
||||
if (!this.raw.readyForBreakpoints) {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
const source = this.getSourceForUri(modelUri);
|
||||
let rawSource: DebugProtocol.Source;
|
||||
if (source) {
|
||||
rawSource = source.raw;
|
||||
} else {
|
||||
const data = Source.getEncodedDebugData(modelUri);
|
||||
rawSource = { name: data.name, path: data.path, sourceReference: data.sourceReference };
|
||||
}
|
||||
|
||||
if (breakpointsToSend.length && !rawSource.adapterData) {
|
||||
rawSource.adapterData = breakpointsToSend[0].adapterData;
|
||||
}
|
||||
// Normalize all drive letters going out from vscode to debug adapters so we are consistent with our resolving #43959
|
||||
if (rawSource.path) {
|
||||
rawSource.path = normalizeDriveLetter(rawSource.path);
|
||||
}
|
||||
|
||||
return this.raw.setBreakpoints({
|
||||
source: rawSource,
|
||||
lines: breakpointsToSend.map(bp => bp.lineNumber),
|
||||
breakpoints: breakpointsToSend.map(bp => ({ line: bp.lineNumber, column: bp.column, condition: bp.condition, hitCondition: bp.hitCondition, logMessage: bp.logMessage })),
|
||||
sourceModified
|
||||
}).then(response => {
|
||||
if (response && response.body) {
|
||||
const data: { [id: string]: DebugProtocol.Breakpoint } = Object.create(null);
|
||||
for (let i = 0; i < breakpointsToSend.length; i++) {
|
||||
data[breakpointsToSend[i].getId()] = response.body.breakpoints[i];
|
||||
}
|
||||
|
||||
this.model.setBreakpointSessionData(this.getId(), data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
sendFunctionBreakpoints(fbpts: IFunctionBreakpoint[]): Promise<void> {
|
||||
if (this.raw) {
|
||||
if (this.raw.readyForBreakpoints) {
|
||||
return this.raw.setFunctionBreakpoints({ breakpoints: fbpts }).then(response => {
|
||||
if (response && response.body) {
|
||||
const data: { [id: string]: DebugProtocol.Breakpoint } = Object.create(null);
|
||||
for (let i = 0; i < fbpts.length; i++) {
|
||||
data[fbpts[i].getId()] = response.body.breakpoints[i];
|
||||
}
|
||||
this.model.setBreakpointSessionData(this.getId(), data);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
return Promise.reject(new Error('no debug adapter'));
|
||||
}
|
||||
|
||||
sendExceptionBreakpoints(exbpts: IExceptionBreakpoint[]): Promise<void> {
|
||||
if (this.raw) {
|
||||
if (this.raw.readyForBreakpoints) {
|
||||
return this.raw.setExceptionBreakpoints({ filters: exbpts.map(exb => exb.filter) }).then(() => undefined);
|
||||
}
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
return Promise.reject(new Error('no debug adapter'));
|
||||
}
|
||||
|
||||
customRequest(request: string, args: any): Promise<DebugProtocol.Response> {
|
||||
if (this.raw) {
|
||||
return this.raw.custom(request, args);
|
||||
}
|
||||
return Promise.reject(new Error('no debug adapter'));
|
||||
}
|
||||
|
||||
stackTrace(threadId: number, startFrame: number, levels: number): Promise<DebugProtocol.StackTraceResponse> {
|
||||
if (this.raw) {
|
||||
return this.raw.stackTrace({ threadId, startFrame, levels });
|
||||
}
|
||||
return Promise.reject(new Error('no debug adapter'));
|
||||
}
|
||||
|
||||
exceptionInfo(threadId: number): Promise<IExceptionInfo | undefined> {
|
||||
if (this.raw) {
|
||||
return this.raw.exceptionInfo({ threadId }).then(response => {
|
||||
if (response) {
|
||||
return {
|
||||
id: response.body.exceptionId,
|
||||
description: response.body.description,
|
||||
breakMode: response.body.breakMode,
|
||||
details: response.body.details
|
||||
};
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
}
|
||||
return Promise.reject(new Error('no debug adapter'));
|
||||
}
|
||||
|
||||
scopes(frameId: number): Promise<DebugProtocol.ScopesResponse> {
|
||||
if (this.raw) {
|
||||
return this.raw.scopes({ frameId });
|
||||
}
|
||||
return Promise.reject(new Error('no debug adapter'));
|
||||
}
|
||||
|
||||
variables(variablesReference: number, filter: 'indexed' | 'named' | undefined, start: number | undefined, count: number | undefined): Promise<DebugProtocol.VariablesResponse> {
|
||||
if (this.raw) {
|
||||
return this.raw.variables({ variablesReference, filter, start, count });
|
||||
}
|
||||
return Promise.reject(new Error('no debug adapter'));
|
||||
}
|
||||
|
||||
evaluate(expression: string, frameId: number, context?: string): Promise<DebugProtocol.EvaluateResponse> {
|
||||
if (this.raw) {
|
||||
return this.raw.evaluate({ expression, frameId, context });
|
||||
}
|
||||
return Promise.reject(new Error('no debug adapter'));
|
||||
}
|
||||
|
||||
restartFrame(frameId: number, threadId: number): Promise<void> {
|
||||
if (this.raw) {
|
||||
return this.raw.restartFrame({ frameId }, threadId).then(() => undefined);
|
||||
}
|
||||
return Promise.reject(new Error('no debug adapter'));
|
||||
}
|
||||
|
||||
next(threadId: number): Promise<void> {
|
||||
if (this.raw) {
|
||||
return this.raw.next({ threadId }).then(() => undefined);
|
||||
}
|
||||
return Promise.reject(new Error('no debug adapter'));
|
||||
}
|
||||
|
||||
stepIn(threadId: number): Promise<void> {
|
||||
if (this.raw) {
|
||||
return this.raw.stepIn({ threadId }).then(() => undefined);
|
||||
}
|
||||
return Promise.reject(new Error('no debug adapter'));
|
||||
}
|
||||
|
||||
stepOut(threadId: number): Promise<void> {
|
||||
if (this.raw) {
|
||||
return this.raw.stepOut({ threadId }).then(() => undefined);
|
||||
}
|
||||
return Promise.reject(new Error('no debug adapter'));
|
||||
}
|
||||
|
||||
stepBack(threadId: number): Promise<void> {
|
||||
if (this.raw) {
|
||||
return this.raw.stepBack({ threadId }).then(() => undefined);
|
||||
}
|
||||
return Promise.reject(new Error('no debug adapter'));
|
||||
}
|
||||
|
||||
continue(threadId: number): Promise<void> {
|
||||
if (this.raw) {
|
||||
return this.raw.continue({ threadId }).then(() => undefined);
|
||||
}
|
||||
return Promise.reject(new Error('no debug adapter'));
|
||||
}
|
||||
|
||||
reverseContinue(threadId: number): Promise<void> {
|
||||
if (this.raw) {
|
||||
return this.raw.reverseContinue({ threadId }).then(() => undefined);
|
||||
}
|
||||
return Promise.reject(new Error('no debug adapter'));
|
||||
}
|
||||
|
||||
pause(threadId: number): Promise<void> {
|
||||
if (this.raw) {
|
||||
return this.raw.pause({ threadId }).then(() => undefined);
|
||||
}
|
||||
return Promise.reject(new Error('no debug adapter'));
|
||||
}
|
||||
|
||||
terminateThreads(threadIds?: number[]): Promise<void> {
|
||||
if (this.raw) {
|
||||
return this.raw.terminateThreads({ threadIds }).then(() => undefined);
|
||||
}
|
||||
return Promise.reject(new Error('no debug adapter'));
|
||||
}
|
||||
|
||||
setVariable(variablesReference: number, name: string, value: string): Promise<DebugProtocol.SetVariableResponse> {
|
||||
if (this.raw) {
|
||||
return this.raw.setVariable({ variablesReference, name, value });
|
||||
}
|
||||
return Promise.reject(new Error('no debug adapter'));
|
||||
}
|
||||
|
||||
loadSource(resource: URI): Promise<DebugProtocol.SourceResponse> {
|
||||
|
||||
if (!this.raw) {
|
||||
return Promise.reject(new Error('no debug adapter'));
|
||||
}
|
||||
|
||||
const source = this.getSourceForUri(resource);
|
||||
let rawSource: DebugProtocol.Source;
|
||||
if (source) {
|
||||
rawSource = source.raw;
|
||||
} else {
|
||||
// create a Source
|
||||
|
||||
let sourceRef: number | undefined;
|
||||
if (resource.query) {
|
||||
const data = Source.getEncodedDebugData(resource);
|
||||
sourceRef = data.sourceReference;
|
||||
}
|
||||
|
||||
rawSource = {
|
||||
path: resource.with({ scheme: '', query: '' }).toString(true), // Remove debug: scheme
|
||||
sourceReference: sourceRef
|
||||
};
|
||||
}
|
||||
|
||||
return this.raw.source({ sourceReference: rawSource.sourceReference || 0, source: rawSource });
|
||||
}
|
||||
|
||||
getLoadedSources(): Promise<Source[]> {
|
||||
if (this.raw) {
|
||||
return this.raw.loadedSources({}).then(response => {
|
||||
if (response.body && response.body.sources) {
|
||||
return response.body.sources.map(src => this.getSource(src));
|
||||
} else {
|
||||
return [];
|
||||
}
|
||||
}, () => {
|
||||
return [];
|
||||
});
|
||||
}
|
||||
return Promise.reject(new Error('no debug adapter'));
|
||||
}
|
||||
|
||||
completions(frameId: number | undefined, text: string, position: Position, overwriteBefore: number): Promise<CompletionItem[]> {
|
||||
if (this.raw) {
|
||||
return this.raw.completions({
|
||||
frameId,
|
||||
text,
|
||||
column: position.column,
|
||||
line: position.lineNumber
|
||||
}).then(response => {
|
||||
|
||||
const result: CompletionItem[] = [];
|
||||
if (response && response.body && response.body.targets) {
|
||||
response.body.targets.forEach(item => {
|
||||
if (item && item.label) {
|
||||
result.push({
|
||||
label: item.label,
|
||||
insertText: item.text || item.label,
|
||||
kind: completionKindFromString(item.type || 'property'),
|
||||
filterText: (item.start && item.length) ? text.substr(item.start, item.length).concat(item.label) : undefined,
|
||||
range: Range.fromPositions(position.delta(0, -(item.length || overwriteBefore)), position)
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
});
|
||||
}
|
||||
return Promise.reject(new Error('no debug adapter'));
|
||||
}
|
||||
|
||||
//---- threads
|
||||
|
||||
getThread(threadId: number): Thread | undefined {
|
||||
return this.threads.get(threadId);
|
||||
}
|
||||
|
||||
getAllThreads(): IThread[] {
|
||||
const result: IThread[] = [];
|
||||
this.threads.forEach(t => result.push(t));
|
||||
return result;
|
||||
}
|
||||
|
||||
clearThreads(removeThreads: boolean, reference: number | undefined = undefined): void {
|
||||
if (reference !== undefined && reference !== null) {
|
||||
const thread = this.threads.get(reference);
|
||||
if (thread) {
|
||||
thread.clearCallStack();
|
||||
thread.stoppedDetails = undefined;
|
||||
thread.stopped = false;
|
||||
|
||||
if (removeThreads) {
|
||||
this.threads.delete(reference);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.threads.forEach(thread => {
|
||||
thread.clearCallStack();
|
||||
thread.stoppedDetails = undefined;
|
||||
thread.stopped = false;
|
||||
});
|
||||
|
||||
if (removeThreads) {
|
||||
this.threads.clear();
|
||||
ExpressionContainer.allValues.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rawUpdate(data: IRawModelUpdate): void {
|
||||
data.threads.forEach(thread => {
|
||||
if (!this.threads.has(thread.id)) {
|
||||
// A new thread came in, initialize it.
|
||||
this.threads.set(thread.id, new Thread(this, thread.name, thread.id));
|
||||
} else if (thread.name) {
|
||||
// Just the thread name got updated #18244
|
||||
const oldThread = this.threads.get(thread.id);
|
||||
if (oldThread) {
|
||||
oldThread.name = thread.name;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const stoppedDetails = data.stoppedDetails;
|
||||
if (stoppedDetails) {
|
||||
// Set the availability of the threads' callstacks depending on
|
||||
// whether the thread is stopped or not
|
||||
if (stoppedDetails.allThreadsStopped) {
|
||||
this.threads.forEach(thread => {
|
||||
thread.stoppedDetails = thread.threadId === stoppedDetails.threadId ? stoppedDetails : { reason: undefined };
|
||||
thread.stopped = true;
|
||||
thread.clearCallStack();
|
||||
});
|
||||
} else {
|
||||
const thread = typeof stoppedDetails.threadId === 'number' ? this.threads.get(stoppedDetails.threadId) : undefined;
|
||||
if (thread) {
|
||||
// One thread is stopped, only update that thread.
|
||||
thread.stoppedDetails = stoppedDetails;
|
||||
thread.clearCallStack();
|
||||
thread.stopped = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fetchThreads(stoppedDetails?: IRawStoppedDetails): Promise<void> {
|
||||
return this.raw ? this.raw.threads().then(response => {
|
||||
if (response && response.body && response.body.threads) {
|
||||
this.model.rawUpdate({
|
||||
sessionId: this.getId(),
|
||||
threads: response.body.threads,
|
||||
stoppedDetails
|
||||
});
|
||||
}
|
||||
}) : Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
//---- private
|
||||
|
||||
private registerListeners(): void {
|
||||
if (!this.raw) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.rawListeners.push(this.raw.onDidInitialize(() => {
|
||||
aria.status(nls.localize('debuggingStarted', "Debugging started."));
|
||||
const sendConfigurationDone = () => {
|
||||
if (this.raw && this.raw.capabilities.supportsConfigurationDoneRequest) {
|
||||
return this.raw.configurationDone().then(undefined, e => {
|
||||
// Disconnect the debug session on configuration done error #10596
|
||||
if (this.raw) {
|
||||
this.raw.disconnect();
|
||||
}
|
||||
if (e.command !== 'canceled' && e.message !== 'canceled') {
|
||||
this.notificationService.error(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
// Send all breakpoints
|
||||
this.debugService.sendAllBreakpoints(this).then(sendConfigurationDone, sendConfigurationDone)
|
||||
.then(() => this.fetchThreads());
|
||||
}));
|
||||
|
||||
this.rawListeners.push(this.raw.onDidStop(event => {
|
||||
this.fetchThreads(event.body).then(() => {
|
||||
const thread = typeof event.body.threadId === 'number' ? this.getThread(event.body.threadId) : undefined;
|
||||
if (thread) {
|
||||
// Call fetch call stack twice, the first only return the top stack frame.
|
||||
// Second retrieves the rest of the call stack. For performance reasons #25605
|
||||
const promises = this.model.fetchCallStack(<Thread>thread);
|
||||
const focus = () => {
|
||||
if (!event.body.preserveFocusHint && thread.getCallStack().length) {
|
||||
this.debugService.focusStackFrame(undefined, thread);
|
||||
if (thread.stoppedDetails) {
|
||||
if (this.configurationService.getValue<IDebugConfiguration>('debug').openDebug === 'openOnDebugBreak') {
|
||||
this.viewletService.openViewlet(VIEWLET_ID);
|
||||
}
|
||||
this.windowService.focusWindow();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
promises.topCallStack.then(focus);
|
||||
promises.wholeCallStack.then(() => {
|
||||
if (!this.debugService.getViewModel().focusedStackFrame) {
|
||||
// The top stack frame can be deemphesized so try to focus again #68616
|
||||
focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
}).then(() => this._onDidChangeState.fire());
|
||||
}));
|
||||
|
||||
this.rawListeners.push(this.raw.onDidThread(event => {
|
||||
if (event.body.reason === 'started') {
|
||||
// debounce to reduce threadsRequest frequency and improve performance
|
||||
if (!this.fetchThreadsScheduler) {
|
||||
this.fetchThreadsScheduler = new RunOnceScheduler(() => {
|
||||
this.fetchThreads();
|
||||
}, 100);
|
||||
this.rawListeners.push(this.fetchThreadsScheduler);
|
||||
}
|
||||
if (!this.fetchThreadsScheduler.isScheduled()) {
|
||||
this.fetchThreadsScheduler.schedule();
|
||||
}
|
||||
} else if (event.body.reason === 'exited') {
|
||||
this.model.clearThreads(this.getId(), true, event.body.threadId);
|
||||
}
|
||||
}));
|
||||
|
||||
this.rawListeners.push(this.raw.onDidTerminateDebugee(event => {
|
||||
aria.status(nls.localize('debuggingStopped', "Debugging stopped."));
|
||||
if (event.body && event.body.restart) {
|
||||
this.debugService.restartSession(this, event.body.restart).then(undefined, onUnexpectedError);
|
||||
} else if (this.raw) {
|
||||
this.raw.disconnect();
|
||||
}
|
||||
}));
|
||||
|
||||
this.rawListeners.push(this.raw.onDidContinued(event => {
|
||||
const threadId = event.body.allThreadsContinued !== false ? undefined : event.body.threadId;
|
||||
this.model.clearThreads(this.getId(), false, threadId);
|
||||
this._onDidChangeState.fire();
|
||||
}));
|
||||
|
||||
let outpuPromises: Promise<void>[] = [];
|
||||
this.rawListeners.push(this.raw.onDidOutput(event => {
|
||||
if (!event.body || !this.raw) {
|
||||
return;
|
||||
}
|
||||
|
||||
const outputSeverity = event.body.category === 'stderr' ? severity.Error : event.body.category === 'console' ? severity.Warning : severity.Info;
|
||||
if (event.body.category === 'telemetry') {
|
||||
// only log telemetry events from debug adapter if the debug extension provided the telemetry key
|
||||
// and the user opted in telemetry
|
||||
if (this.raw.customTelemetryService && this.telemetryService.isOptedIn) {
|
||||
// __GDPR__TODO__ We're sending events in the name of the debug extension and we can not ensure that those are declared correctly.
|
||||
this.raw.customTelemetryService.publicLog(event.body.output, event.body.data);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Make sure to append output in the correct order by properly waiting on preivous promises #33822
|
||||
const waitFor = outpuPromises.slice();
|
||||
const source = event.body.source && event.body.line ? {
|
||||
lineNumber: event.body.line,
|
||||
column: event.body.column ? event.body.column : 1,
|
||||
source: this.getSource(event.body.source)
|
||||
} : undefined;
|
||||
if (event.body.variablesReference) {
|
||||
const container = new ExpressionContainer(this, event.body.variablesReference, generateUuid());
|
||||
outpuPromises.push(container.getChildren().then(children => {
|
||||
return Promise.all(waitFor).then(() => children.forEach(child => {
|
||||
// Since we can not display multiple trees in a row, we are displaying these variables one after the other (ignoring their names)
|
||||
(<any>child).name = null;
|
||||
this.appendToRepl(child, outputSeverity, source);
|
||||
}));
|
||||
}));
|
||||
} else if (typeof event.body.output === 'string') {
|
||||
Promise.all(waitFor).then(() => this.appendToRepl(event.body.output, outputSeverity, source));
|
||||
}
|
||||
Promise.all(outpuPromises).then(() => outpuPromises = []);
|
||||
}));
|
||||
|
||||
this.rawListeners.push(this.raw.onDidBreakpoint(event => {
|
||||
const id = event.body && event.body.breakpoint ? event.body.breakpoint.id : undefined;
|
||||
const breakpoint = this.model.getBreakpoints().filter(bp => bp.idFromAdapter === id).pop();
|
||||
const functionBreakpoint = this.model.getFunctionBreakpoints().filter(bp => bp.idFromAdapter === id).pop();
|
||||
|
||||
if (event.body.reason === 'new' && event.body.breakpoint.source && event.body.breakpoint.line) {
|
||||
const source = this.getSource(event.body.breakpoint.source);
|
||||
const bps = this.model.addBreakpoints(source.uri, [{
|
||||
column: event.body.breakpoint.column,
|
||||
enabled: true,
|
||||
lineNumber: event.body.breakpoint.line,
|
||||
}], false);
|
||||
if (bps.length === 1) {
|
||||
this.model.setBreakpointSessionData(this.getId(), { [bps[0].getId()]: event.body.breakpoint });
|
||||
}
|
||||
}
|
||||
|
||||
if (event.body.reason === 'removed') {
|
||||
if (breakpoint) {
|
||||
this.model.removeBreakpoints([breakpoint]);
|
||||
}
|
||||
if (functionBreakpoint) {
|
||||
this.model.removeFunctionBreakpoints(functionBreakpoint.getId());
|
||||
}
|
||||
}
|
||||
|
||||
if (event.body.reason === 'changed') {
|
||||
if (breakpoint) {
|
||||
if (!breakpoint.column) {
|
||||
event.body.breakpoint.column = undefined;
|
||||
}
|
||||
this.model.setBreakpointSessionData(this.getId(), { [breakpoint.getId()]: event.body.breakpoint });
|
||||
}
|
||||
if (functionBreakpoint) {
|
||||
this.model.setBreakpointSessionData(this.getId(), { [functionBreakpoint.getId()]: event.body.breakpoint });
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
this.rawListeners.push(this.raw.onDidLoadedSource(event => {
|
||||
this._onDidLoadedSource.fire({
|
||||
reason: event.body.reason,
|
||||
source: this.getSource(event.body.source)
|
||||
});
|
||||
}));
|
||||
|
||||
this.rawListeners.push(this.raw.onDidCustomEvent(event => {
|
||||
this._onDidCustomEvent.fire(event);
|
||||
}));
|
||||
|
||||
this.rawListeners.push(this.raw.onDidExitAdapter(event => {
|
||||
this._onDidEndAdapter.fire(event);
|
||||
}));
|
||||
}
|
||||
|
||||
shutdown(): void {
|
||||
dispose(this.rawListeners);
|
||||
if (this.raw) {
|
||||
this.raw.disconnect();
|
||||
}
|
||||
this.raw = undefined;
|
||||
this.model.clearThreads(this.getId(), true);
|
||||
this._onDidChangeState.fire();
|
||||
}
|
||||
|
||||
//---- sources
|
||||
|
||||
getSourceForUri(uri: URI): Source | undefined {
|
||||
return this.sources.get(this.getUriKey(uri));
|
||||
}
|
||||
|
||||
getSource(raw?: DebugProtocol.Source): Source {
|
||||
let source = new Source(raw, this.getId());
|
||||
const uriKey = this.getUriKey(source.uri);
|
||||
const found = this.sources.get(uriKey);
|
||||
if (found) {
|
||||
source = found;
|
||||
// merge attributes of new into existing
|
||||
source.raw = mixin(source.raw, raw);
|
||||
if (source.raw && raw) {
|
||||
// Always take the latest presentation hint from adapter #42139
|
||||
source.raw.presentationHint = raw.presentationHint;
|
||||
}
|
||||
} else {
|
||||
this.sources.set(uriKey, source);
|
||||
}
|
||||
|
||||
return source;
|
||||
}
|
||||
|
||||
private getUriKey(uri: URI): string {
|
||||
// TODO: the following code does not make sense if uri originates from a different platform
|
||||
return platform.isLinux ? uri.toString() : uri.toString().toLowerCase();
|
||||
}
|
||||
|
||||
// REPL
|
||||
|
||||
getReplElements(): IReplElement[] {
|
||||
return this.repl.getReplElements();
|
||||
}
|
||||
|
||||
removeReplExpressions(): void {
|
||||
this.repl.removeReplExpressions();
|
||||
this._onDidChangeREPLElements.fire();
|
||||
}
|
||||
|
||||
addReplExpression(stackFrame: IStackFrame | undefined, name: string): Promise<void> {
|
||||
const viewModel = this.debugService.getViewModel();
|
||||
return this.repl.addReplExpression(stackFrame, name)
|
||||
.then(() => this._onDidChangeREPLElements.fire())
|
||||
// Evaluate all watch expressions and fetch variables again since repl evaluation might have changed some.
|
||||
.then(() => this.debugService.focusStackFrame(viewModel.focusedStackFrame, viewModel.focusedThread, viewModel.focusedSession));
|
||||
}
|
||||
|
||||
appendToRepl(data: string | IExpression, severity: severity, source?: IReplElementSource): void {
|
||||
this.repl.appendToRepl(data, severity, source);
|
||||
this._onDidChangeREPLElements.fire();
|
||||
}
|
||||
|
||||
logToRepl(sev: severity, args: any[], frame?: { uri: URI, line: number, column: number }) {
|
||||
this.repl.logToRepl(sev, args, frame);
|
||||
this._onDidChangeREPLElements.fire();
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 339 B |
|
After Width: | Height: | Size: 363 B |