debugHover.ts 9.9 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

I
isidor 已提交
6
import lifecycle = require('vs/base/common/lifecycle');
7
import { TPromise } from 'vs/base/common/winjs.base';
E
Erich Gamma 已提交
8
import errors = require('vs/base/common/errors');
I
isidor 已提交
9
import { CommonKeybindings } from 'vs/base/common/keyCodes';
E
Erich Gamma 已提交
10
import dom = require('vs/base/browser/dom');
B
Benjamin Pasero 已提交
11
import * as nls from 'vs/nls';
J
Joao Moreno 已提交
12
import { ITree } from 'vs/base/parts/tree/browser/tree';
I
isidor 已提交
13 14
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
import { DefaultController, ICancelableEvent } from 'vs/base/parts/tree/browser/treeDefaults';
15
import { IConfigurationChangedEvent } from 'vs/editor/common/editorCommon';
E
Erich Gamma 已提交
16
import editorbrowser = require('vs/editor/browser/editorBrowser');
I
isidor 已提交
17 18
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import debug = require('vs/workbench/parts/debug/common/debug');
19
import {evaluateExpression, Expression} from 'vs/workbench/parts/debug/common/debugModel';
20
import viewer = require('vs/workbench/parts/debug/electron-browser/debugViewer');
A
Cleanup  
Alex Dima 已提交
21
import {IKeyboardEvent} from 'vs/base/browser/keyboardEvent';
A
Alex Dima 已提交
22
import {Position} from 'vs/editor/common/core/position';
23
import {Range} from 'vs/editor/common/core/range';
E
Erich Gamma 已提交
24

I
isidor 已提交
25
const $ = dom.emmet;
I
isidor 已提交
26
const debugTreeOptions = {
27
	indentPixels: 6,
B
Benjamin Pasero 已提交
28 29
	twistiePixels: 15,
	ariaLabel: nls.localize('treeAriaLabel', "Debug Hover")
I
isidor 已提交
30
};
31
const MAX_ELEMENTS_SHOWN = 18;
32
const MAX_VALUE_RENDER_LENGTH_IN_HOVER = 4096;
E
Erich Gamma 已提交
33 34 35 36

export class DebugHoverWidget implements editorbrowser.IContentWidget {

	public static ID = 'debug.hoverWidget';
I
isidor 已提交
37
	// editor.IContentWidget.allowEditorOverflow
E
Erich Gamma 已提交
38 39 40
	public allowEditorOverflow = true;

	private domNode: HTMLElement;
I
isidor 已提交
41
	public isVisible: boolean;
I
isidor 已提交
42
	private tree: ITree;
A
Alex Dima 已提交
43
	private showAtPosition: Position;
I
isidor 已提交
44
	private highlightDecorations: string[];
45
	private complexValueContainer: HTMLElement;
46
	private treeContainer: HTMLElement;
47
	private complexValueTitle: HTMLElement;
48
	private valueContainer: HTMLElement;
I
isidor 已提交
49 50
	private stoleFocus: boolean;
	private toDispose: lifecycle.IDisposable[];
E
Erich Gamma 已提交
51

I
isidor 已提交
52
	constructor(private editor: editorbrowser.ICodeEditor, private debugService: debug.IDebugService, private instantiationService: IInstantiationService) {
E
Erich Gamma 已提交
53
		this.domNode = $('.debug-hover-widget monaco-editor-background');
54 55 56
		this.complexValueContainer = dom.append(this.domNode, $('.complex-value'));
		this.complexValueTitle = dom.append(this.complexValueContainer, $('.title'));
		this.treeContainer = dom.append(this.complexValueContainer, $('.debug-hover-tree'));
I
isidor 已提交
57
		this.treeContainer.setAttribute('role', 'tree');
58
		this.tree = new Tree(this.treeContainer, {
I
isidor 已提交
59
			dataSource: new viewer.VariablesDataSource(this.debugService),
60
			renderer: this.instantiationService.createInstance(VariablesHoverRenderer),
B
Benjamin Pasero 已提交
61
			controller: new DebugHoverController(editor)
I
isidor 已提交
62
		}, debugTreeOptions);
I
isidor 已提交
63

I
isidor 已提交
64
		this.toDispose = [];
I
isidor 已提交
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
		this.registerListeners();

		this.valueContainer = dom.append(this.domNode, $('.value'));
		this.valueContainer.tabIndex = 0;
		this.valueContainer.setAttribute('role', 'tooltip');

		this.isVisible = false;
		this.showAtPosition = null;
		this.highlightDecorations = [];

		this.editor.addContentWidget(this);
		this.editor.applyFontInfo(this.domNode);
	}

	private registerListeners(): void {
I
isidor 已提交
80
		this.toDispose.push(this.tree.addListener2('item:expanded', () => {
81
			this.layoutTree();
I
isidor 已提交
82 83
		}));
		this.toDispose.push(this.tree.addListener2('item:collapsed', () => {
84
			this.layoutTree();
I
isidor 已提交
85
		}));
86

A
Cleanup  
Alex Dima 已提交
87
		this.toDispose.push(dom.addStandardDisposableListener(this.domNode, 'keydown', (e: IKeyboardEvent) => {
I
isidor 已提交
88 89 90 91
			if (e.equals(CommonKeybindings.ESCAPE)) {
				this.hide();
			}
		}));
A
Alex Dima 已提交
92
		this.toDispose.push(this.editor.onDidChangeConfiguration((e: IConfigurationChangedEvent) => {
I
isidor 已提交
93 94 95 96
			if (e.fontInfo) {
				this.editor.applyFontInfo(this.domNode);
			}
		}));
E
Erich Gamma 已提交
97 98 99 100 101 102 103 104 105 106
	}

	public getId(): string {
		return DebugHoverWidget.ID;
	}

	public getDomNode(): HTMLElement {
		return this.domNode;
	}

107
	public showAt(range: Range, hoveringOver: string, focus: boolean): TPromise<void> {
E
Erich Gamma 已提交
108
		const pos = range.getStartPosition();
109
		const model = this.editor.getModel();
110
		const focusedStackFrame = this.debugService.getViewModel().getFocusedStackFrame();
111
		if (!hoveringOver || !focusedStackFrame || (focusedStackFrame.source.uri.toString() !== model.uri.toString())) {
E
Erich Gamma 已提交
112 113 114
			return;
		}

I
isidor 已提交
115
		// string magic to get the parents of the variable (a and b for a.b.foo)
116
		const lineContent = model.getLineContent(pos.lineNumber);
E
Erich Gamma 已提交
117 118 119 120 121
		const namesToFind = lineContent.substring(0, lineContent.indexOf('.' + hoveringOver))
			.split('.').map(word => word.trim()).filter(word => !!word);
		namesToFind.push(hoveringOver);
		namesToFind[0] = namesToFind[0].substring(namesToFind[0].lastIndexOf(' ') + 1);

I
isidor 已提交
122
		return this.getExpression(namesToFind).then(expression => {
123
			if (!expression || !expression.available) {
124 125 126 127 128
				this.hide();
				return;
			}

			// show it
I
isidor 已提交
129 130 131 132 133 134 135 136 137 138 139
			this.highlightDecorations = this.editor.deltaDecorations(this.highlightDecorations, [{
				range: {
					startLineNumber: pos.lineNumber,
					endLineNumber: pos.lineNumber,
					startColumn: lineContent.indexOf(hoveringOver) + 1,
					endColumn: lineContent.indexOf(hoveringOver) + 1 + hoveringOver.length
				},
				options: {
					className: 'hoverHighlight'
				}
			}]);
I
isidor 已提交
140 141 142

			return this.doShow(pos, expression, focus);
		});
143 144
	}

145
	private getExpression(namesToFind: string[]): TPromise<Expression> {
146 147
		const session = this.debugService.getActiveSession();
		const focusedStackFrame = this.debugService.getViewModel().getFocusedStackFrame();
I
isidor 已提交
148
		if (session.configuration.capabilities.supportsEvaluateForHovers) {
149 150 151 152 153
			return evaluateExpression(session, focusedStackFrame, new Expression(namesToFind.join('.'), true), 'hover');
		}

		const variables: debug.IExpression[] = [];
		return focusedStackFrame.getScopes(this.debugService).then(scopes => {
E
Erich Gamma 已提交
154 155 156 157 158 159 160 161 162 163

			// flatten out scopes lists
			return scopes.reduce((accum, scopes) => { return accum.concat(scopes); }, [])

			// no expensive scopes
			.filter((scope: debug.IScope) => !scope.expensive)

			// get the scopes variables
			.map((scope: debug.IScope) => scope.getChildren(this.debugService).done((children: debug.IExpression[]) => {

I
isidor 已提交
164
				// look for our variable in the list. First find the parents of the hovered variable if there are any.
E
Erich Gamma 已提交
165
				for (var i = 0; i < namesToFind.length && children; i++) {
I
isidor 已提交
166
					// some languages pass the type as part of the name, so need to check if the last word of the name matches.
E
Erich Gamma 已提交
167 168 169 170 171 172 173 174 175 176 177 178 179
					const filtered = children.filter(v => typeof v.name === 'string' && (namesToFind[i] === v.name || namesToFind[i] === v.name.substr(v.name.lastIndexOf(' ') + 1)));
					if (filtered.length !== 1) {
						break;
					}

					if (i === namesToFind.length - 1) {
						variables.push(filtered[0]);
					} else {
						filtered[0].getChildren(this.debugService).done(c => children = c, children = null);
					}
				}
			}, errors.onUnexpectedError));

180 181
		// only show if there are no duplicates across scopes
		}).then(() => variables.length === 1 ? TPromise.as(variables[0]) : TPromise.as(null));
182
	}
E
Erich Gamma 已提交
183

A
Alex Dima 已提交
184
	private doShow(position: Position, expression: debug.IExpression, focus: boolean, forceValueHover = false): TPromise<void> {
185 186
		this.showAtPosition = position;
		this.isVisible = true;
I
isidor 已提交
187
		this.stoleFocus = focus;
188

I
isidor 已提交
189
		if (expression.reference === 0 || forceValueHover) {
190
			this.complexValueContainer.hidden = true;
191
			this.valueContainer.hidden = false;
192
			viewer.renderExpressionValue(expression, this.valueContainer, false, MAX_VALUE_RENDER_LENGTH_IN_HOVER);
I
isidor 已提交
193
			this.valueContainer.title = '';
194
			this.editor.layoutContentWidget(this);
I
isidor 已提交
195 196 197 198
			if (focus) {
				this.editor.render();
				this.valueContainer.focus();
			}
199

I
isidor 已提交
200
			return TPromise.as(null);
201
		}
I
isidor 已提交
202 203

		this.valueContainer.hidden = true;
204
		this.complexValueContainer.hidden = false;
I
isidor 已提交
205 206

		return this.tree.setInput(expression).then(() => {
207
			this.complexValueTitle.textContent = expression.value;
I
isidor 已提交
208
			this.complexValueTitle.title = expression.value;
I
isidor 已提交
209 210 211 212 213 214 215
			this.layoutTree();
			this.editor.layoutContentWidget(this);
			if (focus) {
				this.editor.render();
				this.tree.DOMFocus();
			}
		});
E
Erich Gamma 已提交
216 217
	}

218 219 220 221 222 223 224
	private layoutTree(): void {
		const navigator = this.tree.getNavigator();
		let visibleElementsCount = 0;
		while (navigator.next()) {
			visibleElementsCount++;
		}

225
		if (visibleElementsCount === 0) {
I
isidor 已提交
226
			this.doShow(this.showAtPosition, this.tree.getInput(), false, true);
227 228 229 230 231 232 233
		} else {
			const height = Math.min(visibleElementsCount, MAX_ELEMENTS_SHOWN) * 18;

			if (this.treeContainer.clientHeight !== height) {
				this.treeContainer.style.height = `${ height }px`;
				this.tree.layout();
			}
234
		}
235 236
	}

E
Erich Gamma 已提交
237 238 239 240
	public hide(): void {
		if (!this.isVisible) {
			return;
		}
241

E
Erich Gamma 已提交
242
		this.isVisible = false;
I
isidor 已提交
243 244
		this.editor.deltaDecorations(this.highlightDecorations, []);
		this.highlightDecorations = [];
E
Erich Gamma 已提交
245
		this.editor.layoutContentWidget(this);
I
isidor 已提交
246 247 248
		if (this.stoleFocus) {
			this.editor.focus();
		}
E
Erich Gamma 已提交
249 250 251 252 253 254 255 256 257 258 259
	}

	public getPosition(): editorbrowser.IContentWidgetPosition {
		return this.isVisible ? {
			position: this.showAtPosition,
			preference: [
				editorbrowser.ContentWidgetPositionPreference.ABOVE,
				editorbrowser.ContentWidgetPositionPreference.BELOW
			]
		} : null;
	}
I
isidor 已提交
260 261

	public dispose(): void {
J
Joao Moreno 已提交
262
		this.toDispose = lifecycle.dispose(this.toDispose);
I
isidor 已提交
263
	}
E
Erich Gamma 已提交
264
}
I
isidor 已提交
265 266 267

class DebugHoverController extends DefaultController {

B
Benjamin Pasero 已提交
268 269 270 271
	constructor(private editor: editorbrowser.ICodeEditor) {
		super();
	}

I
isidor 已提交
272 273 274 275 276
	/* protected */ public onLeftClick(tree: ITree, element: any, eventish: ICancelableEvent, origin: string = 'mouse'): boolean {
		if (element.reference > 0) {
			super.onLeftClick(tree, element, eventish, origin);
			tree.clearFocus();
			tree.deselect(element);
B
Benjamin Pasero 已提交
277
			this.editor.focus();
I
isidor 已提交
278 279 280 281 282
		}

		return true;
	}
}
283 284 285 286 287 288 289

class VariablesHoverRenderer extends viewer.VariablesRenderer {

	public getHeight(tree: ITree, element: any): number {
		return 18;
	}
}