debugHover.ts 7.6 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');
B
Benjamin Pasero 已提交
8
import * as nls from 'vs/nls';
J
Joao Moreno 已提交
9
import { ITree } from 'vs/base/parts/tree/browser/tree';
I
isidor 已提交
10 11
import { Tree } from 'vs/base/parts/tree/browser/treeImpl';
import { DefaultController, ICancelableEvent } from 'vs/base/parts/tree/browser/treeDefaults';
E
Erich Gamma 已提交
12 13
import editorbrowser = require('vs/editor/browser/editorBrowser');
import editorcommon = require('vs/editor/common/editorCommon');
I
isidor 已提交
14 15 16
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 已提交
17

I
isidor 已提交
18
const $ = dom.emmet;
I
isidor 已提交
19
const debugTreeOptions = {
20
	indentPixels: 6,
B
Benjamin Pasero 已提交
21 22
	twistiePixels: 15,
	ariaLabel: nls.localize('treeAriaLabel', "Debug Hover")
I
isidor 已提交
23
};
24
const MAX_ELEMENTS_SHOWN = 18;
E
Erich Gamma 已提交
25 26 27 28

export class DebugHoverWidget implements editorbrowser.IContentWidget {

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

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

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

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

E
Erich Gamma 已提交
58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
		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();
76 77
		const model = this.editor.getModel();
		const wordAtPosition = model.getWordAtPosition(pos);
E
Erich Gamma 已提交
78
		const hoveringOver = wordAtPosition ? wordAtPosition.word : null;
79
		const focusedStackFrame = this.debugService.getViewModel().getFocusedStackFrame();
80 81
		if (!hoveringOver || !focusedStackFrame || (this.isVisible && hoveringOver === this.lastHoveringOver) ||
			(focusedStackFrame.source.uri.toString() !== model.getAssociatedResource().toString())) {
E
Erich Gamma 已提交
82 83 84
			return;
		}

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

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

			// 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 已提交
104
				// look for our variable in the list. First find the parents of the hovered variable if there are any.
E
Erich Gamma 已提交
105
				for (var i = 0; i < namesToFind.length && children; i++) {
I
isidor 已提交
106
					// 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 已提交
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 138 139
					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;
140 141
		this.doShow(pos, variables[0]);
	}
E
Erich Gamma 已提交
142

143 144
	private doShow(position: editorcommon.IEditorPosition, expression: debug.IExpression, forceValueHover = false): void {
		if (expression.reference > 0 && !forceValueHover) {
145 146
			this.valueContainer.hidden = true;
			this.treeContainer.hidden = false;
147 148 149
			this.tree.setInput(expression).then(() => {
				this.layoutTree();
			}).done(null, errors.onUnexpectedError);
150 151 152
		} else {
			this.treeContainer.hidden = true;
			this.valueContainer.hidden = false;
153
			viewer.renderExpressionValue(expression, false, this.valueContainer, false);
I
isidor 已提交
154
			this.valueContainer.title = '';
155
		}
156

E
Erich Gamma 已提交
157 158 159 160 161
		this.showAtPosition = position;
		this.isVisible = true;
		this.editor.layoutContentWidget(this);
	}

162 163 164 165 166 167 168
	private layoutTree(): void {
		const navigator = this.tree.getNavigator();
		let visibleElementsCount = 0;
		while (navigator.next()) {
			visibleElementsCount++;
		}

169 170 171 172 173 174 175 176 177
		if (visibleElementsCount === 0) {
			this.doShow(this.showAtPosition, this.tree.getInput(), true);
		} else {
			const height = Math.min(visibleElementsCount, MAX_ELEMENTS_SHOWN) * 18;

			if (this.treeContainer.clientHeight !== height) {
				this.treeContainer.style.height = `${ height }px`;
				this.tree.layout();
			}
178
		}
179 180
	}

E
Erich Gamma 已提交
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
	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 已提交
202 203 204

class DebugHoverController extends DefaultController {

B
Benjamin Pasero 已提交
205 206 207 208
	constructor(private editor: editorbrowser.ICodeEditor) {
		super();
	}

I
isidor 已提交
209 210 211 212 213
	/* 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 已提交
214
			this.editor.focus();
I
isidor 已提交
215 216 217 218 219
		}

		return true;
	}
}
220 221 222 223 224 225 226

class VariablesHoverRenderer extends viewer.VariablesRenderer {

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