watchExpressionsView.ts 13.4 KB
Newer Older
I
isidor 已提交
1 2 3 4 5 6 7 8
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. 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';
B
Benjamin Pasero 已提交
9
import { CollapseAction } from 'vs/workbench/browser/viewlet';
10
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
11 12
import { IDebugService, IExpression, CONTEXT_WATCH_EXPRESSIONS_FOCUSED } from 'vs/workbench/contrib/debug/common/debug';
import { Expression, Variable } from 'vs/workbench/contrib/debug/common/debugModel';
I
isidor 已提交
13
import { AddWatchExpressionAction, RemoveAllWatchExpressionsAction, EditWatchExpressionAction, RemoveWatchExpressionAction, CopyValueAction } from 'vs/workbench/contrib/debug/browser/debugActions';
I
isidor 已提交
14
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
I
isidor 已提交
15 16
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
17
import { IAction } from 'vs/base/common/actions';
18
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
19
import { renderExpressionValue, renderViewTree, IInputBoxOptions, AbstractExpressionsRenderer, IExpressionTemplateData } from 'vs/workbench/contrib/debug/browser/baseDebugView';
20
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
21 22 23
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';
I
isidor 已提交
24
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
A
Andre Weinand 已提交
25
import { WorkbenchAsyncDataTree, IListService } from 'vs/platform/list/browser/listService';
I
isidor 已提交
26
import { IThemeService } from 'vs/platform/theme/common/themeService';
B
Benjamin Pasero 已提交
27
import { IAsyncDataSource, ITreeMouseEvent, ITreeContextMenuEvent, ITreeDragAndDrop, ITreeDragOverReaction } from 'vs/base/browser/ui/tree/tree';
I
isidor 已提交
28 29
import { IDragAndDropData } from 'vs/base/browser/dnd';
import { onUnexpectedError } from 'vs/base/common/errors';
J
Joao Moreno 已提交
30
import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView';
I
isidor 已提交
31 32
import { FuzzyScore } from 'vs/base/common/filters';
import { IHighlight } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
I
isidor 已提交
33
import { variableSetEmitter, VariablesRenderer } from 'vs/workbench/contrib/debug/browser/variablesView';
I
isidor 已提交
34

35
const MAX_VALUE_RENDER_LENGTH_IN_VIEWLET = 1024;
I
isidor 已提交
36

37
export class WatchExpressionsView extends ViewletPanel {
I
isidor 已提交
38 39

	private onWatchExpressionsUpdatedScheduler: RunOnceScheduler;
40
	private needsRefresh: boolean;
M
Matt Bierner 已提交
41
	private tree: WorkbenchAsyncDataTree<IDebugService | IExpression, IExpression, FuzzyScore>;
I
isidor 已提交
42 43 44 45

	constructor(
		options: IViewletViewOptions,
		@IContextMenuService contextMenuService: IContextMenuService,
46
		@IDebugService private readonly debugService: IDebugService,
I
isidor 已提交
47
		@IKeybindingService keybindingService: IKeybindingService,
48
		@IInstantiationService private readonly instantiationService: IInstantiationService,
I
isidor 已提交
49
		@IConfigurationService configurationService: IConfigurationService,
50 51 52
		@IContextKeyService private readonly contextKeyService: IContextKeyService,
		@IListService private readonly listService: IListService,
		@IThemeService private readonly themeService: IThemeService
I
isidor 已提交
53
	) {
I
isidor 已提交
54
		super({ ...(options as IViewletPanelOptions), ariaHeaderLabel: nls.localize('watchExpressionsSection', "Watch Expressions Section") }, keybindingService, contextMenuService, configurationService);
I
isidor 已提交
55 56

		this.onWatchExpressionsUpdatedScheduler = new RunOnceScheduler(() => {
57
			this.needsRefresh = false;
58
			this.tree.updateChildren();
I
isidor 已提交
59 60 61
		}, 50);
	}

I
isidor 已提交
62
	renderBody(container: HTMLElement): void {
I
isidor 已提交
63
		dom.addClass(container, 'debug-watch');
64 65
		const treeContainer = renderViewTree(container);

I
isidor 已提交
66
		const expressionsRenderer = this.instantiationService.createInstance(WatchExpressionsRenderer);
A
Andre Weinand 已提交
67
		this.tree = new WorkbenchAsyncDataTree(treeContainer, new WatchExpressionsDelegate(), [expressionsRenderer, this.instantiationService.createInstance(VariablesRenderer)],
J
Joao Moreno 已提交
68
			new WatchExpressionsDataSource(), {
I
isidor 已提交
69
				ariaLabel: nls.localize({ comment: ['Debug is a noun in this context, not a verb.'], key: 'watchAriaTreeLabel' }, "Debug Watch Expressions"),
70
				accessibilityProvider: new WatchExpressionsAccessibilityProvider(),
I
isidor 已提交
71
				identityProvider: { getId: element => element.getId() },
I
isidor 已提交
72 73
				keyboardNavigationLabelProvider: { getKeyboardNavigationLabel: e => e },
				dnd: new WatchExpressionsDragAndDrop(this.debugService),
74
			}, this.contextKeyService, this.listService, this.themeService, this.configurationService, this.keybindingService);
I
isidor 已提交
75

I
isidor 已提交
76
		this.tree.setInput(this.debugService).then(undefined, onUnexpectedError);
I
isidor 已提交
77
		CONTEXT_WATCH_EXPRESSIONS_FOCUSED.bindTo(this.tree.contextKeyService);
I
isidor 已提交
78 79

		const addWatchExpressionAction = new AddWatchExpressionAction(AddWatchExpressionAction.ID, AddWatchExpressionAction.LABEL, this.debugService, this.keybindingService);
B
Benjamin Pasero 已提交
80
		const collapseAction = new CollapseAction(this.tree, true, 'explorer-action collapse-explorer');
I
isidor 已提交
81
		const removeAllWatchExpressionsAction = new RemoveAllWatchExpressionsAction(RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL, this.debugService, this.keybindingService);
82
		this.toolbar.setActions([addWatchExpressionAction, collapseAction, removeAllWatchExpressionsAction])();
I
isidor 已提交
83

84 85
		this.disposables.push(this.tree.onContextMenu(e => this.onContextMenu(e)));
		this.disposables.push(this.tree.onMouseDblClick(e => this.onMouseDblClick(e)));
I
isidor 已提交
86
		this.disposables.push(this.debugService.getModel().onDidChangeWatchExpressions(we => {
87
			if (!this.isBodyVisible()) {
88
				this.needsRefresh = true;
I
isidor 已提交
89
			} else {
90
				this.tree.updateChildren();
91
			}
92 93
		}));
		this.disposables.push(this.debugService.getViewModel().onDidFocusStackFrame(() => {
94
			if (!this.isBodyVisible()) {
95 96 97 98
				this.needsRefresh = true;
				return;
			}

I
isidor 已提交
99 100 101 102
			if (!this.onWatchExpressionsUpdatedScheduler.isScheduled()) {
				this.onWatchExpressionsUpdatedScheduler.schedule();
			}
		}));
103
		this.disposables.push(variableSetEmitter.event(() => this.tree.updateChildren()));
104 105 106 107 108 109

		this.disposables.push(this.onDidChangeBodyVisibility(visible => {
			if (visible && this.needsRefresh) {
				this.onWatchExpressionsUpdatedScheduler.schedule();
			}
		}));
110 111
		this.disposables.push(this.debugService.getViewModel().onDidSelectExpression(e => {
			if (e instanceof Expression && e.name) {
I
isidor 已提交
112
				this.tree.rerender(e);
113 114
			}
		}));
I
isidor 已提交
115 116
	}

117 118
	layoutBody(height: number, width: number): void {
		this.tree.layout(height, width);
S
Sandeep Somavarapu 已提交
119 120
	}

I
isidor 已提交
121 122 123 124
	focus(): void {
		this.tree.domFocus();
	}

125
	private onMouseDblClick(e: ITreeMouseEvent<IExpression>): void {
126 127 128 129 130
		if ((e.browserEvent.target as HTMLElement).className.indexOf('twistie') >= 0) {
			// Ignore double click events on twistie
			return;
		}

131 132
		const element = e.element;
		// double click on primitive value: open input box to be able to select and copy value.
I
isidor 已提交
133
		if (element instanceof Expression && element !== this.debugService.getViewModel().getSelectedExpression()) {
134
			this.debugService.getViewModel().setSelectedExpression(element);
I
isidor 已提交
135
		} else if (!element) {
136 137 138
			// Double click in watch panel triggers to add a new watch expression
			this.debugService.addWatchExpression();
		}
139 140
	}

141 142
	private onContextMenu(e: ITreeContextMenuEvent<IExpression>): void {
		const element = e.element;
143 144 145 146
		const anchor = e.anchor;
		if (!anchor) {
			return;
		}
147
		const actions: IAction[] = [];
148

149 150 151 152 153
		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) {
I
isidor 已提交
154
				actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, expression.value, 'watch', this.debugService));
155 156 157 158 159 160 161 162
			}
			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) {
163
				const variable = element as Variable;
164
				if (!variable.hasChildren) {
I
isidor 已提交
165
					actions.push(this.instantiationService.createInstance(CopyValueAction, CopyValueAction.ID, CopyValueAction.LABEL, variable, 'watch', this.debugService));
166 167 168 169 170 171
				}
				actions.push(new Separator());
			}
			actions.push(new RemoveAllWatchExpressionsAction(RemoveAllWatchExpressionsAction.ID, RemoveAllWatchExpressionsAction.LABEL, this.debugService, this.keybindingService));
		}

172
		this.contextMenuService.showContextMenu({
173
			getAnchor: () => anchor,
174 175 176
			getActions: () => actions,
			getActionsContext: () => element
		});
177 178 179
	}
}

180
class WatchExpressionsDelegate implements IListVirtualDelegate<IExpression> {
181

182 183
	getHeight(element: IExpression): number {
		return 22;
184 185
	}

186 187
	getTemplateId(element: IExpression): string {
		if (element instanceof Expression) {
I
isidor 已提交
188
			return WatchExpressionsRenderer.ID;
189 190
		}

191 192
		// Variable
		return VariablesRenderer.ID;
193
	}
194 195
}

J
Joao Moreno 已提交
196 197 198
function isDebugService(element: any): element is IDebugService {
	return typeof element.getConfigurationManager === 'function';
}
199

J
Joao Moreno 已提交
200
class WatchExpressionsDataSource implements IAsyncDataSource<IDebugService, IExpression> {
201

202
	hasChildren(element: IExpression | IDebugService): boolean {
J
Joao Moreno 已提交
203
		return isDebugService(element) || element.hasChildren;
204 205
	}

J
Joao Moreno 已提交
206 207 208 209 210
	getChildren(element: IDebugService | IExpression): Promise<Array<IExpression>> {
		if (isDebugService(element)) {
			const debugService = element as IDebugService;
			const watchExpressions = debugService.getModel().getWatchExpressions();
			const viewModel = debugService.getViewModel();
211
			return Promise.all(watchExpressions.map(we => !!we.name
212
				? we.evaluate(viewModel.focusedSession!, viewModel.focusedStackFrame!, 'watch').then(() => we)
213
				: Promise.resolve(we)));
214 215
		}

216
		return element.getChildren();
217 218 219 220
	}
}


I
isidor 已提交
221
export class WatchExpressionsRenderer extends AbstractExpressionsRenderer {
222

I
isidor 已提交
223
	static readonly ID = 'watchexpression';
224

225
	get templateId() {
I
isidor 已提交
226
		return WatchExpressionsRenderer.ID;
227
	}
228

I
isidor 已提交
229 230 231
	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);
I
isidor 已提交
232 233 234 235 236 237 238
		renderExpressionValue(expression, data.value, {
			showChanged: true,
			maxValueLength: MAX_VALUE_RENDER_LENGTH_IN_VIEWLET,
			preserveWhitespace: false,
			showHover: true,
			colorize: true
		});
239 240
	}

I
isidor 已提交
241 242 243 244 245 246 247 248 249 250 251 252 253
	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());
				}
			}
		};
254
	}
255
}
256

257 258 259 260
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);
261
		}
262

263 264
		// Variable
		return nls.localize('watchVariableAriaLabel', "{0} value {1}, watch, debug", (<Variable>element).name, (<Variable>element).value);
265 266
	}
}
267

I
isidor 已提交
268 269 270 271
class WatchExpressionsDragAndDrop implements ITreeDragAndDrop<IExpression> {

	constructor(private debugService: IDebugService) { }

J
Joao Moreno 已提交
272
	onDragOver(data: IDragAndDropData): boolean | ITreeDragOverReaction {
J
Joao Moreno 已提交
273 274 275
		if (!(data instanceof ElementsDragAndDropData)) {
			return false;
		}
I
isidor 已提交
276

J
Joao Moreno 已提交
277 278
		const expressions = (data as ElementsDragAndDropData<IExpression>).elements;
		return expressions.length > 0 && expressions[0] instanceof Expression;
I
isidor 已提交
279 280
	}

J
Joao Moreno 已提交
281
	getDragURI(element: IExpression): string | null {
I
isidor 已提交
282 283 284 285 286 287 288
		if (!(element instanceof Expression) || element === this.debugService.getViewModel().getSelectedExpression()) {
			return null;
		}

		return element.getId();
	}

J
Joao Moreno 已提交
289 290 291
	getDragLabel(elements: IExpression[]): string | undefined {
		if (elements.length === 1) {
			return elements[0].name;
I
isidor 已提交
292 293
		}

J
Joao Moreno 已提交
294
		return undefined;
I
isidor 已提交
295 296
	}

J
Joao Moreno 已提交
297
	drop(data: IDragAndDropData, targetElement: IExpression): void {
J
Joao Moreno 已提交
298 299
		if (!(data instanceof ElementsDragAndDropData)) {
			return;
I
isidor 已提交
300
		}
J
Joao Moreno 已提交
301 302 303 304 305

		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);
I
isidor 已提交
306 307
	}
}