mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-01-28 01:25:39 -05:00
* Merge from vscode 64980ea1f3f532c82bb6c28d27bba9ef2c5b4463 * fix config changes * fix strictnull checks
312 lines
13 KiB
TypeScript
312 lines
13 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* 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, 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, Action } 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';
|
|
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
|
|
|
const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024;
|
|
|
|
export class WatchExpressionsView extends ViewletPanel {
|
|
|
|
private onWatchExpressionsUpdatedScheduler: RunOnceScheduler;
|
|
private needsRefresh = false;
|
|
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,
|
|
@IContextKeyService contextKeyService: IContextKeyService,
|
|
) {
|
|
super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('watchExpressionsSection', "Watch Expressions Section") }, keybindingService, contextMenuService, configurationService, contextKeyService);
|
|
|
|
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, 'WatchExpressions', 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: IExpression) => e },
|
|
dnd: new WatchExpressionsDragAndDrop(this.debugService),
|
|
});
|
|
|
|
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._register(this.tree.onContextMenu(e => this.onContextMenu(e)));
|
|
this._register(this.tree.onMouseDblClick(e => this.onMouseDblClick(e)));
|
|
this._register(this.debugService.getModel().onDidChangeWatchExpressions(we => {
|
|
if (!this.isBodyVisible()) {
|
|
this.needsRefresh = true;
|
|
} else {
|
|
this.tree.updateChildren();
|
|
}
|
|
}));
|
|
this._register(this.debugService.getViewModel().onDidFocusStackFrame(() => {
|
|
if (!this.isBodyVisible()) {
|
|
this.needsRefresh = true;
|
|
return;
|
|
}
|
|
|
|
if (!this.onWatchExpressionsUpdatedScheduler.isScheduled()) {
|
|
this.onWatchExpressionsUpdatedScheduler.schedule();
|
|
}
|
|
}));
|
|
this._register(variableSetEmitter.event(() => this.tree.updateChildren()));
|
|
|
|
this._register(this.onDidChangeBodyVisibility(visible => {
|
|
if (visible && this.needsRefresh) {
|
|
this.onWatchExpressionsUpdatedScheduler.schedule();
|
|
}
|
|
}));
|
|
this._register(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 Action('debug.editWatchExpression', nls.localize('editWatchExpression', "Edit Expression"), undefined, true, () => {
|
|
this.debugService.getViewModel().setSelectedExpression(expression);
|
|
return Promise.resolve();
|
|
}));
|
|
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 Action('debug.removeWatchExpression', nls.localize('removeWatchExpression', "Remove Expression"), undefined, true, () => {
|
|
this.debugService.removeWatchExpressions(expression.getId());
|
|
return Promise.resolve();
|
|
}));
|
|
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);
|
|
variableSetEmitter.fire();
|
|
} 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);
|
|
}
|
|
}
|