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);
|
||||
}
|
||||
}
|
||||