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

import 'vs/css!./media/repl';
7
import nls = require('vs/nls');
I
isidor 已提交
8
import { TPromise } from 'vs/base/common/winjs.base';
E
Erich Gamma 已提交
9 10
import errors = require('vs/base/common/errors');
import lifecycle = require('vs/base/common/lifecycle');
I
isidor 已提交
11
import actions = require('vs/base/common/actions');
E
Erich Gamma 已提交
12 13 14
import builder = require('vs/base/browser/builder');
import dom = require('vs/base/browser/dom');
import platform = require('vs/base/common/platform');
J
Joao Moreno 已提交
15
import tree = require('vs/base/parts/tree/browser/tree');
E
Erich Gamma 已提交
16
import treeimpl = require('vs/base/parts/tree/browser/treeImpl');
17 18
import { IEventService } from 'vs/platform/event/common/event';
import { EventType, CompositeEvent } from 'vs/workbench/common/events';
E
Erich Gamma 已提交
19 20
import viewer = require('vs/workbench/parts/debug/browser/replViewer');
import debug = require('vs/workbench/parts/debug/common/debug');
21
import debugactions = require('vs/workbench/parts/debug/electron-browser/debugActions');
I
isidor 已提交
22
import replhistory = require('vs/workbench/parts/debug/common/replHistory');
I
isidor 已提交
23
import { Panel } from 'vs/workbench/browser/panel';
E
Erich Gamma 已提交
24 25
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView';
I
isidor 已提交
26
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
E
Erich Gamma 已提交
27 28 29
import { IWorkspaceContextService } from 'vs/workbench/services/workspace/common/contextService';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { CommonKeybindings } from 'vs/base/common/keyCodes';
A
Cleanup  
Alex Dima 已提交
30
import {IKeyboardEvent} from 'vs/base/browser/keyboardEvent';
E
Erich Gamma 已提交
31

I
isidor 已提交
32
const $ = dom.emmet;
E
Erich Gamma 已提交
33

34
const replTreeOptions: tree.ITreeOptions = {
E
Erich Gamma 已提交
35 36
	indentPixels: 8,
	twistiePixels: 20,
37 38
	paddingOnRow: false,
	ariaLabel: nls.localize('replAriaLabel', "Read Eval Print Loop Panel")
E
Erich Gamma 已提交
39 40
};

I
isidor 已提交
41
const HISTORY_STORAGE_KEY = 'debug.repl.history';
E
Erich Gamma 已提交
42

I
isidor 已提交
43
export class Repl extends Panel {
E
Erich Gamma 已提交
44

45
	private static HALF_WIDTH_TYPICAL = 'n';
E
Erich Gamma 已提交
46 47 48 49 50 51

	private static HISTORY: replhistory.ReplHistory;
	private static REFRESH_DELAY = 500; // delay in ms to refresh the repl for new elements to show

	private toDispose: lifecycle.IDisposable[];
	private tree: tree.ITree;
I
isidor 已提交
52
	private renderer: viewer.ReplExpressionsRenderer;
53
	private characterWidthSurveyor: HTMLElement;
E
Erich Gamma 已提交
54 55 56
	private treeContainer: HTMLElement;
	private replInput: HTMLInputElement;
	private refreshTimeoutHandle: number;
I
isidor 已提交
57
	private actions: actions.IAction[];
E
Erich Gamma 已提交
58 59 60 61 62 63 64 65

	constructor(
		@debug.IDebugService private debugService: debug.IDebugService,
		@IContextMenuService private contextMenuService: IContextMenuService,
		@IWorkspaceContextService private contextService: IWorkspaceContextService,
		@ITelemetryService telemetryService: ITelemetryService,
		@IInstantiationService private instantiationService: IInstantiationService,
		@IContextViewService private contextViewService: IContextViewService,
66 67
		@IStorageService private storageService: IStorageService,
		@IEventService private eventService: IEventService
E
Erich Gamma 已提交
68
	) {
I
isidor 已提交
69
		super(debug.REPL_ID, telemetryService);
E
Erich Gamma 已提交
70 71

		this.toDispose = [];
I
isidor 已提交
72
		this.registerListeners();
E
Erich Gamma 已提交
73 74
	}

I
isidor 已提交
75
	private registerListeners(): void {
76
		this.toDispose.push(this.debugService.getModel().onDidChangeReplElements(() => {
77
			this.onReplElementsUpdated();
E
Erich Gamma 已提交
78
		}));
79 80 81 82 83 84 85 86
		this.toDispose.push(this.eventService.addListener2(EventType.COMPOSITE_OPENED, (e: CompositeEvent) => {
			if (e.compositeId === debug.REPL_ID) {
				const elements = this.debugService.getModel().getReplElements();
				if (elements.length > 0) {
					return this.reveal(elements[elements.length - 1]);
				}
			}
		}));
E
Erich Gamma 已提交
87 88
	}

89
	private onReplElementsUpdated(): void {
E
Erich Gamma 已提交
90 91 92 93 94 95
		if (this.tree) {
			if (this.refreshTimeoutHandle) {
				return; // refresh already triggered
			}

			this.refreshTimeoutHandle = setTimeout(() => {
I
isidor 已提交
96
				this.refreshTimeoutHandle = null;
E
Erich Gamma 已提交
97 98 99 100 101 102 103 104 105 106 107

				const scrollPosition = this.tree.getScrollPosition();
				this.tree.refresh().then(() => {
					if (scrollPosition === 0 || scrollPosition === 1) {
						return this.tree.setScrollPosition(1); // keep scrolling to the end unless user scrolled up
					}
				}, errors.onUnexpectedError);
			}, Repl.REFRESH_DELAY);
		}
	}

I
isidor 已提交
108 109
	public create(parent: builder.Builder): TPromise<void> {
		super.create(parent);
I
isidor 已提交
110
		const container = dom.append(parent.getHTMLElement(), $('.repl'));
E
Erich Gamma 已提交
111
		this.treeContainer = dom.append(container, $('.repl-tree'));
I
isidor 已提交
112
		const replInputContainer = dom.append(container, $(platform.isWindows ? '.repl-input-wrapper.windows' : platform.isMacintosh ? '.repl-input-wrapper.mac' : '.repl-input-wrapper.linux'));
E
Erich Gamma 已提交
113
		this.replInput = <HTMLInputElement>dom.append(replInputContainer, $('input.repl-input'));
B
Benjamin Pasero 已提交
114
		this.replInput.type = 'text';
E
Erich Gamma 已提交
115

A
Cleanup  
Alex Dima 已提交
116
		this.toDispose.push(dom.addStandardDisposableListener(this.replInput, 'keydown', (e: IKeyboardEvent) => {
E
Erich Gamma 已提交
117 118 119 120 121 122
			let trimmedValue = this.replInput.value.trim();

			if (e.equals(CommonKeybindings.ENTER) && trimmedValue) {
				this.debugService.addReplExpression(trimmedValue);
				Repl.HISTORY.evaluated(trimmedValue);
				this.replInput.value = '';
I
isidor 已提交
123
				e.preventDefault();
E
Erich Gamma 已提交
124
			} else if (e.equals(CommonKeybindings.UP_ARROW) || e.equals(CommonKeybindings.DOWN_ARROW)) {
I
isidor 已提交
125
				const historyInput = e.equals(CommonKeybindings.UP_ARROW) ? Repl.HISTORY.previous() : Repl.HISTORY.next();
E
Erich Gamma 已提交
126 127 128
				if (historyInput) {
					Repl.HISTORY.remember(this.replInput.value, e.equals(CommonKeybindings.UP_ARROW));
					this.replInput.value = historyInput;
I
isidor 已提交
129
					// always leave cursor at the end.
E
Erich Gamma 已提交
130 131 132
					e.preventDefault();
				}
			}
I
isidor 已提交
133
		}));
134
		this.toDispose.push(dom.addStandardDisposableListener(this.replInput, dom.EventType.FOCUS, () => dom.addClass(replInputContainer, 'synthetic-focus')));
I
isidor 已提交
135 136
		this.toDispose.push(dom.addStandardDisposableListener(this.replInput, dom.EventType.BLUR, () => dom.removeClass(replInputContainer, 'synthetic-focus')));

137
		this.characterWidthSurveyor = dom.append(container, $('.surveyor'));
138
		this.characterWidthSurveyor.textContent = Repl.HALF_WIDTH_TYPICAL;
139 140 141 142 143
		for (let i = 0; i < 10; i++) {
			this.characterWidthSurveyor.textContent += this.characterWidthSurveyor.textContent;
		}
		this.characterWidthSurveyor.style.fontSize = platform.isMacintosh ? '12px' : '14px';

I
isidor 已提交
144
		this.renderer = this.instantiationService.createInstance(viewer.ReplExpressionsRenderer);
E
Erich Gamma 已提交
145 146
		this.tree = new treeimpl.Tree(this.treeContainer, {
			dataSource: new viewer.ReplExpressionsDataSource(this.debugService),
I
isidor 已提交
147
			renderer: this.renderer,
148
			accessibilityProvider: new viewer.ReplExpressionsAccessibilityProvider(),
E
Erich Gamma 已提交
149 150 151 152 153 154 155
			controller: new viewer.ReplExpressionsController(this.debugService, this.contextMenuService, new viewer.ReplExpressionsActionProvider(this.instantiationService), this.replInput, false)
		}, replTreeOptions);

		if (!Repl.HISTORY) {
			Repl.HISTORY = new replhistory.ReplHistory(JSON.parse(this.storageService.get(HISTORY_STORAGE_KEY, StorageScope.WORKSPACE, '[]')));
		}

I
isidor 已提交
156
		return this.tree.setInput(this.debugService.getModel());
E
Erich Gamma 已提交
157 158 159 160
	}

	public layout(dimension: builder.Dimension): void {
		if (this.tree) {
I
isidor 已提交
161
			this.renderer.setWidth(dimension.width - 25, this.characterWidthSurveyor.clientWidth / this.characterWidthSurveyor.textContent.length);
162
			this.tree.layout(dimension.height - 22);
I
isidor 已提交
163 164
			// refresh the tree because layout might require some elements be word wrapped differently
			this.tree.refresh().done(undefined, errors.onUnexpectedError);
E
Erich Gamma 已提交
165 166 167 168 169 170 171
		}
	}

	public focus(): void {
		this.replInput.focus();
	}

I
isidor 已提交
172
	public reveal(element: debug.ITreeElement): TPromise<void> {
E
Erich Gamma 已提交
173 174 175
		return this.tree.reveal(element);
	}

I
isidor 已提交
176
	public getActions(): actions.IAction[] {
I
isidor 已提交
177 178
		if (!this.actions) {
			this.actions = [
I
isidor 已提交
179
				this.instantiationService.createInstance(debugactions.ClearReplAction, debugactions.ClearReplAction.ID, debugactions.ClearReplAction.LABEL)
I
isidor 已提交
180 181 182 183 184 185 186 187
			];

			this.actions.forEach(a => {
				this.toDispose.push(a);
			});
		}

		return this.actions;
I
isidor 已提交
188 189 190
	}

	public shutdown(): void {
E
Erich Gamma 已提交
191 192 193 194
		this.storageService.store(HISTORY_STORAGE_KEY, JSON.stringify(Repl.HISTORY.save()), StorageScope.WORKSPACE);
	}

	public dispose(): void {
I
isidor 已提交
195
		// destroy container
J
Joao Moreno 已提交
196
		this.toDispose = lifecycle.dispose(this.toDispose);
E
Erich Gamma 已提交
197 198 199 200

		super.dispose();
	}
}