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

import errors = require('vs/base/common/errors');
import dom = require('vs/base/browser/dom');
I
isidor 已提交
8 9 10
import { ITree } from 'vs/base/parts/tree/common/tree';
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
import { DefaultController, ICancelableEvent } from 'vs/base/parts/tree/browser/treeDefaults';
E
Erich Gamma 已提交
11 12
import editorbrowser = require('vs/editor/browser/editorBrowser');
import editorcommon = require('vs/editor/common/editorCommon');
I
isidor 已提交
13 14 15
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import debug = require('vs/workbench/parts/debug/common/debug');
import viewer = require('vs/workbench/parts/debug/browser/debugViewer');
E
Erich Gamma 已提交
16

I
isidor 已提交
17
const $ = dom.emmet;
I
isidor 已提交
18
const debugTreeOptions = {
19 20
	indentPixels: 6,
	twistiePixels: 12
I
isidor 已提交
21
};
22
const MAX_ELEMENTS_SHOWN = 18;
E
Erich Gamma 已提交
23 24 25 26

export class DebugHoverWidget implements editorbrowser.IContentWidget {

	public static ID = 'debug.hoverWidget';
I
isidor 已提交
27
	// editor.IContentWidget.allowEditorOverflow
E
Erich Gamma 已提交
28 29 30 31
	public allowEditorOverflow = true;

	private domNode: HTMLElement;
	private isVisible: boolean;
I
isidor 已提交
32
	private tree: ITree;
E
Erich Gamma 已提交
33 34 35
	private showAtPosition: editorcommon.IPosition;
	private lastHoveringOver: string;
	private highlightDecorations: string[];
36 37
	private treeContainer: HTMLElement;
	private valueContainer: HTMLElement;
E
Erich Gamma 已提交
38

I
isidor 已提交
39
	constructor(private editor: editorbrowser.ICodeEditor, private debugService: debug.IDebugService, private instantiationService: IInstantiationService) {
E
Erich Gamma 已提交
40
		this.domNode = $('.debug-hover-widget monaco-editor-background');
41 42
		this.treeContainer = dom.append(this.domNode, $('.debug-hover-tree'));
		this.tree = new Tree(this.treeContainer, {
I
isidor 已提交
43
			dataSource: new viewer.VariablesDataSource(this.debugService),
44
			renderer: this.instantiationService.createInstance(VariablesHoverRenderer),
I
isidor 已提交
45 46
			controller: new DebugHoverController()
		}, debugTreeOptions);
47 48 49 50 51 52 53
		this.tree.addListener2('item:expanded', () => {
			this.layoutTree();
		});
		this.tree.addListener2('item:collapsed', () => {
			this.layoutTree();
		});

54
		this.valueContainer = dom.append(this.domNode, $('.debug-hover-value'));
I
isidor 已提交
55

E
Erich Gamma 已提交
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
		this.isVisible = false;
		this.showAtPosition = null;
		this.lastHoveringOver = null;
		this.highlightDecorations = [];

		this.editor.addContentWidget(this);
	}

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

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

	public showAt(range: editorcommon.IEditorRange): void {
		const pos = range.getStartPosition();
74 75
		const model = this.editor.getModel();
		const wordAtPosition = model.getWordAtPosition(pos);
E
Erich Gamma 已提交
76
		const hoveringOver = wordAtPosition ? wordAtPosition.word : null;
77
		const focusedStackFrame = this.debugService.getViewModel().getFocusedStackFrame();
78 79
		if (!hoveringOver || !focusedStackFrame || (this.isVisible && hoveringOver === this.lastHoveringOver) ||
			(focusedStackFrame.source.uri.toString() !== model.getAssociatedResource().toString())) {
E
Erich Gamma 已提交
80 81 82
			return;
		}

I
isidor 已提交
83
		// string magic to get the parents of the variable (a and b for a.b.foo)
84
		const lineContent = model.getLineContent(pos.lineNumber);
E
Erich Gamma 已提交
85 86 87 88 89 90
		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);
		const variables: debug.IExpression[] = [];

91
		focusedStackFrame.getScopes(this.debugService).done(scopes => {
E
Erich Gamma 已提交
92 93 94 95 96 97 98 99 100 101

			// 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 已提交
102
				// look for our variable in the list. First find the parents of the hovered variable if there are any.
E
Erich Gamma 已提交
103
				for (var i = 0; i < namesToFind.length && children; i++) {
I
isidor 已提交
104
					// 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 已提交
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
					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));
		}, errors.onUnexpectedError);

		// don't show if there are duplicates across scopes
		if (variables.length !== 1) {
			this.hide();
			return;
		}

		// show it
		this.highlightDecorations = this.editor.deltaDecorations(this.highlightDecorations, [{
			range: {
				startLineNumber: pos.lineNumber,
				endLineNumber: pos.lineNumber,
				startColumn: wordAtPosition.startColumn,
				endColumn: wordAtPosition.endColumn
			},
			options: {
				className: 'hoverHighlight'
			}
		}]);
		this.lastHoveringOver = hoveringOver;
138 139
		this.doShow(pos, variables[0]);
	}
E
Erich Gamma 已提交
140

141
	private doShow(position: editorcommon.IEditorPosition, expression: debug.IExpression): void {
142 143 144
		if (expression.reference > 0) {
			this.valueContainer.hidden = true;
			this.treeContainer.hidden = false;
145 146 147
			this.tree.setInput(expression).then(() => {
				this.layoutTree();
			}).done(null, errors.onUnexpectedError);
148 149 150
		} else {
			this.treeContainer.hidden = true;
			this.valueContainer.hidden = false;
151
			viewer.renderExpressionValue(expression, false, this.valueContainer);
152
		}
153

E
Erich Gamma 已提交
154 155 156 157 158
		this.showAtPosition = position;
		this.isVisible = true;
		this.editor.layoutContentWidget(this);
	}

159 160 161 162 163 164 165 166 167 168 169 170
	private layoutTree(): void {
		const navigator = this.tree.getNavigator();
		let visibleElementsCount = 0;
		while (navigator.next()) {
			visibleElementsCount++;
		}
		const height = Math.min(visibleElementsCount, MAX_ELEMENTS_SHOWN) * 18;

		if (this.treeContainer.clientHeight !== height) {
			this.treeContainer.style.height = `${ height }px`;
			this.tree.layout();
		}
171 172
	}

E
Erich Gamma 已提交
173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
	public hide(): void {
		if (!this.isVisible) {
			// already not visible
			return;
		}
		this.isVisible = false;
		this.editor.deltaDecorations(this.highlightDecorations, []);
		this.highlightDecorations = [];
		this.editor.layoutContentWidget(this);
	}

	public getPosition(): editorbrowser.IContentWidgetPosition {
		return this.isVisible ? {
			position: this.showAtPosition,
			preference: [
				editorbrowser.ContentWidgetPositionPreference.ABOVE,
				editorbrowser.ContentWidgetPositionPreference.BELOW
			]
		} : null;
	}
}
I
isidor 已提交
194 195 196 197 198 199 200 201 202 203 204 205 206

class DebugHoverController extends DefaultController {

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

		return true;
	}
}
207 208 209 210 211 212 213

class VariablesHoverRenderer extends viewer.VariablesRenderer {

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