openEditorsView.ts 10.0 KB
Newer Older
I
isidor 已提交
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 nls = require('vs/nls');
I
isidor 已提交
7
import errors = require('vs/base/common/errors');
8
import {RunOnceScheduler} from 'vs/base/common/async';
I
isidor 已提交
9
import {TPromise} from 'vs/base/common/winjs.base';
I
isidor 已提交
10 11
import {IActionRunner} from 'vs/base/common/actions';
import dom = require('vs/base/browser/dom');
I
isidor 已提交
12
import {CollapsibleState} from 'vs/base/browser/ui/splitview/splitview';
I
isidor 已提交
13
import {Tree} from 'vs/base/parts/tree/browser/treeImpl';
I
isidor 已提交
14 15
import {IContextMenuService} from 'vs/platform/contextview/browser/contextView';
import {IMessageService} from 'vs/platform/message/common/message';
I
isidor 已提交
16 17 18 19
import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation';
import {IEventService} from 'vs/platform/event/common/event';
import {IConfigurationService} from 'vs/platform/configuration/common/configuration';
import {EventType as WorkbenchEventType, UntitledEditorEvent} from 'vs/workbench/common/events';
I
isidor 已提交
20
import {AdaptiveCollapsibleViewletView} from 'vs/workbench/browser/viewlet';
I
isidor 已提交
21
import {ITextFileService, TextFileChangeEvent, EventType as FileEventType, AutoSaveMode, IFilesConfiguration} from 'vs/workbench/parts/files/common/files';
I
isidor 已提交
22
import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService';
23
import {IEditorStacksModel} from 'vs/workbench/common/editor/editorStacksModel';
I
isidor 已提交
24
import {Renderer, DataSource, Controller, AccessibilityProvider, OpenEditor} from 'vs/workbench/parts/files/browser/views/openEditorsViewer';
I
isidor 已提交
25 26 27 28 29 30

const $ = dom.emmet;

export class OpenEditorsView extends AdaptiveCollapsibleViewletView {

	private static MEMENTO_COLLAPSED = 'openEditors.memento.collapsed';
I
isidor 已提交
31
	private static DEFAULT_MAX_VISIBLE_OPEN_EDITORS = 9;
I
isidor 已提交
32 33 34 35 36 37
	private static DEFAULT_DYNAMIC_HEIGHT = true;

	private settings: any;
	private maxVisibleOpenEditors: number;
	private dynamicHeight: boolean;

I
isidor 已提交
38
	private model: IEditorStacksModel;
I
isidor 已提交
39 40
	private dirtyCountElement: HTMLElement;
	private lastDirtyCount: number;
I
isidor 已提交
41
	// Use a scheduler to update the tree as many update events come at some time so to prevent over-reacting.
42
	private updateTreeScheduler: RunOnceScheduler;
I
isidor 已提交
43 44 45

	constructor(actionRunner: IActionRunner, settings: any,
		@IMessageService messageService: IMessageService,
I
isidor 已提交
46
		@IEventService private eventService: IEventService,
I
isidor 已提交
47
		@IInstantiationService private instantiationService: IInstantiationService,
I
isidor 已提交
48 49
		@IContextMenuService contextMenuService: IContextMenuService,
		@ITextFileService private textFileService: ITextFileService,
I
isidor 已提交
50
		@IConfigurationService private configurationService: IConfigurationService,
I
isidor 已提交
51 52 53 54 55
		@IWorkbenchEditorService private editorService: IWorkbenchEditorService
	) {
		super(actionRunner, OpenEditorsView.computeExpandedBodySize(editorService.getStacksModel()), !!settings[OpenEditorsView.MEMENTO_COLLAPSED], nls.localize('openEditosrSection', "Open Editors Section"), messageService, contextMenuService);

		this.settings = settings;
I
isidor 已提交
56
		this.model = editorService.getStacksModel();
I
isidor 已提交
57
		this.lastDirtyCount = 0;
58
		this.updateTreeScheduler = new RunOnceScheduler(() => this.updateTree(), 0);
I
isidor 已提交
59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75
	}

	public renderHeader(container: HTMLElement): void {
		const titleDiv = dom.append(container, $('.title'));
		const titleSpan = dom.append(titleDiv, $('span'));
		titleSpan.textContent = nls.localize('openEditors', "Open Editors");

		this.dirtyCountElement = dom.append(titleDiv, $('.monaco-count-badge'));
		this.updateDirtyIndicator();

		super.renderHeader(container);
	}

	public renderBody(container: HTMLElement): void {
		this.treeContainer = super.renderViewTree(container);
		dom.addClass(this.treeContainer, 'explorer-open-editors');

I
isidor 已提交
76 77
		const dataSource = this.instantiationService.createInstance(DataSource);
		const renderer = this.instantiationService.createInstance(Renderer);
I
isidor 已提交
78
		const controller = this.instantiationService.createInstance(Controller);
I
isidor 已提交
79
		const accessibilityProvider = this.instantiationService.createInstance(AccessibilityProvider);
I
isidor 已提交
80 81 82

		this.tree = new Tree(this.treeContainer, {
			dataSource,
I
isidor 已提交
83
			renderer,
I
isidor 已提交
84 85
			controller,
			accessibilityProvider
I
isidor 已提交
86 87
		}, {
			indentPixels: 0,
88
			twistiePixels: 20,
I
isidor 已提交
89 90 91
			ariaLabel: nls.localize('treeAriaLabel', "Open Editors")
		});

92
		this.updateTree();
I
isidor 已提交
93 94
	}

I
isidor 已提交
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
	public create(): TPromise<void> {

		// Load Config
		const configuration = this.configurationService.getConfiguration<IFilesConfiguration>();
		this.onConfigurationUpdated(configuration);

		// listeners
		this.registerListeners();

		return super.create();
	}

	private registerListeners(): void {

		// update on model changes
110
		this.toDispose.push(this.model.onModelChanged(e => this.updateTreeScheduler.schedule()));
I
isidor 已提交
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125

		// listen to untitled
		this.toDispose.push(this.eventService.addListener2(WorkbenchEventType.UNTITLED_FILE_DIRTY, (e: UntitledEditorEvent) => this.onUntitledFileDirty()));
		this.toDispose.push(this.eventService.addListener2(WorkbenchEventType.UNTITLED_FILE_DELETED, (e: UntitledEditorEvent) => this.onUntitledFileDeleted()));

		// listen to files being changed locally
		this.toDispose.push(this.eventService.addListener2(FileEventType.FILE_DIRTY, (e: TextFileChangeEvent) => this.onTextFileDirty(e)));
		this.toDispose.push(this.eventService.addListener2(FileEventType.FILE_SAVED, (e: TextFileChangeEvent) => this.onTextFileSaved(e)));
		this.toDispose.push(this.eventService.addListener2(FileEventType.FILE_SAVE_ERROR, (e: TextFileChangeEvent) => this.onTextFileSaveError(e)));
		this.toDispose.push(this.eventService.addListener2(FileEventType.FILE_REVERTED, (e: TextFileChangeEvent) => this.onTextFileReverted(e)));

		// Also handle configuration updates
		this.toDispose.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated(e.config)));
	}

126
	private updateTree(): void {
127 128 129 130 131 132 133 134
		if (this.isDisposed) {
			return;
		}

		// View size
		this.expandedBodySize = this.getExpandedBodySize(this.model);

		if (this.tree) {
135
			// Show groups only if there is more than 1 group
136
			const treeInput = this.model.groups.length === 1 ? this.model.groups[0] : this.model;
137 138 139
			(treeInput !== this.tree.getInput() ? this.tree.setInput(treeInput) : this.tree.refresh())
			// Always expand all the groups as they are unclickable
				.done(() => this.tree.expandAll(this.model.groups), errors.onUnexpectedError);
140

I
isidor 已提交
141
			// Make sure to keep active open editor highlighted
142
			if (this.model.activeGroup) {
I
isidor 已提交
143
				this.highlightEntry(new OpenEditor(this.model.activeGroup.activeEditor, this.model.activeGroup));
144 145 146 147
			}
		}
	}

I
isidor 已提交
148 149 150
	private highlightEntry(entry: OpenEditor): void {
		this.tree.clearFocus();
		this.tree.clearSelection();
151

I
isidor 已提交
152 153 154 155 156 157
		if (entry) {
			this.tree.setFocus(entry);
			this.tree.setSelection([entry]);
			this.tree.reveal(entry).done(null, errors.onUnexpectedError);
		}
	}
158

I
isidor 已提交
159
	private onConfigurationUpdated(configuration: IFilesConfiguration): void {
I
isidor 已提交
160
		let visibleOpenEditors = configuration && configuration.explorer && configuration.explorer.openEditors && configuration.explorer.openEditors.maxVisible;
I
isidor 已提交
161 162 163 164 165 166
		if (typeof visibleOpenEditors === 'number') {
			this.maxVisibleOpenEditors = visibleOpenEditors;
		} else {
			this.maxVisibleOpenEditors = OpenEditorsView.DEFAULT_MAX_VISIBLE_OPEN_EDITORS;
		}

I
isidor 已提交
167
		let dynamicHeight = configuration && configuration.explorer && configuration.explorer.openEditors && configuration.explorer.openEditors.dynamicHeight;
I
isidor 已提交
168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209
		if (typeof dynamicHeight === 'boolean') {
			this.dynamicHeight = dynamicHeight;
		} else {
			this.dynamicHeight = OpenEditorsView.DEFAULT_DYNAMIC_HEIGHT;
		}

		// Adjust expanded body size
		this.expandedBodySize = this.getExpandedBodySize(this.model);
	}

	private onTextFileDirty(e: TextFileChangeEvent): void {
		if (this.textFileService.getAutoSaveMode() !== AutoSaveMode.AFTER_SHORT_DELAY) {
			this.updateDirtyIndicator(); // no indication needed when auto save is enabled for short delay
		}
	}

	private onTextFileSaved(e: TextFileChangeEvent): void {
		if (this.lastDirtyCount > 0) {
			this.updateDirtyIndicator();
		}
	}

	private onTextFileSaveError(e: TextFileChangeEvent): void {
		this.updateDirtyIndicator();
	}

	private onTextFileReverted(e: TextFileChangeEvent): void {
		if (this.lastDirtyCount > 0) {
			this.updateDirtyIndicator();
		}
	}

	private onUntitledFileDirty(): void {
		this.updateDirtyIndicator();
	}

	private onUntitledFileDeleted(): void {
		if (this.lastDirtyCount > 0) {
			this.updateDirtyIndicator();
		}
	}

I
isidor 已提交
210 211 212 213
	private updateDirtyIndicator(): void {
		let dirty = this.textFileService.getDirty().length;
		this.lastDirtyCount = dirty;
		if (dirty === 0) {
I
isidor 已提交
214
			dom.addClass(this.dirtyCountElement, 'hidden');
I
isidor 已提交
215 216
		} else {
			this.dirtyCountElement.textContent = nls.localize('dirtyCounter', "{0} unsaved", dirty);
I
isidor 已提交
217
			dom.removeClass(this.dirtyCountElement, 'hidden');
I
isidor 已提交
218 219 220
		}
	}

I
isidor 已提交
221
	private getExpandedBodySize(model: IEditorStacksModel): number {
I
isidor 已提交
222 223 224
		return OpenEditorsView.computeExpandedBodySize(model, this.maxVisibleOpenEditors, this.dynamicHeight);
	}

I
isidor 已提交
225
	private static computeExpandedBodySize(model: IEditorStacksModel, maxVisibleOpenEditors = OpenEditorsView.DEFAULT_MAX_VISIBLE_OPEN_EDITORS, dynamicHeight = OpenEditorsView.DEFAULT_DYNAMIC_HEIGHT): number {
I
isidor 已提交
226
		const entryCount = model.groups.reduce((sum, group) => sum + group.count, 0);
I
isidor 已提交
227 228 229 230 231 232 233 234 235 236 237 238

		let itemsToShow: number;
		if (dynamicHeight) {
			itemsToShow = Math.min(Math.max(maxVisibleOpenEditors, 1), entryCount);
		} else {
			itemsToShow = Math.max(maxVisibleOpenEditors, 1);
		}
		// We only show the group labels if there is more than 1 group
		if (model.groups.length > 1) {
			itemsToShow += model.groups.length;
		}

I
isidor 已提交
239
		return itemsToShow * Renderer.ITEM_HEIGHT;
I
isidor 已提交
240
	}
I
isidor 已提交
241 242 243 244 245 246 247 248 249 250 251 252

	public getOptimalWidth():number {
		let parentNode = this.tree.getHTMLElement();
		let childNodes = [].slice.call(parentNode.querySelectorAll('.monaco-file-label > .file-name'));
		return dom.getLargestChildWidth(parentNode, childNodes);
	}

	public shutdown(): void {
		this.settings[OpenEditorsView.MEMENTO_COLLAPSED] = (this.state === CollapsibleState.COLLAPSED);

		super.shutdown();
	}
I
isidor 已提交
253
}