openEditorsView.ts 11.5 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
import {IAction, IActionRunner} from 'vs/base/common/actions';
I
isidor 已提交
11
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
import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation';
17
import {IEditorGroupService} from 'vs/workbench/services/group/common/groupService';
I
isidor 已提交
18
import {IConfigurationService} from 'vs/platform/configuration/common/configuration';
19
import {IKeybindingService} from 'vs/platform/keybinding/common/keybinding';
20
import {IEditorStacksModel, IStacksModelChangeEvent, IEditorGroup} from 'vs/workbench/common/editor';
I
isidor 已提交
21
import {SaveAllAction} from 'vs/workbench/parts/files/browser/fileActions';
22
import {AdaptiveCollapsibleViewletView} from 'vs/workbench/browser/viewlet';
23
import {ITextFileService, IFilesConfiguration, VIEWLET_ID, AutoSaveMode} from 'vs/workbench/parts/files/common/files';
24
import {IViewletService} from 'vs/workbench/services/viewlet/common/viewletService';
I
isidor 已提交
25
import {Renderer, DataSource, Controller, AccessibilityProvider,  ActionProvider, OpenEditor, DragAndDrop} from 'vs/workbench/parts/files/browser/views/openEditorsViewer';
26
import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService';
I
isidor 已提交
27
import {CloseAllEditorsAction} from 'vs/workbench/browser/parts/editor/editorActions';
28
import {IEventService} from 'vs/platform/event/common/event';
I
isidor 已提交
29

J
Joao Moreno 已提交
30
const $ = dom.$;
I
isidor 已提交
31

32
export class OpenEditorsView extends AdaptiveCollapsibleViewletView {
I
isidor 已提交
33 34

	private static MEMENTO_COLLAPSED = 'openEditors.memento.collapsed';
35
	private static DEFAULT_VISIBLE_OPEN_EDITORS = 9;
I
isidor 已提交
36 37 38
	private static DEFAULT_DYNAMIC_HEIGHT = true;

	private settings: any;
39
	private visibleOpenEditors: number;
I
isidor 已提交
40 41
	private dynamicHeight: boolean;

I
isidor 已提交
42
	private model: IEditorStacksModel;
I
isidor 已提交
43
	private dirtyCountElement: HTMLElement;
44
	private structuralTreeRefreshScheduler: RunOnceScheduler;
45
	private structuralRefreshDelay: number;
46 47
	private groupToRefresh: IEditorGroup;
	private fullRefreshNeeded: boolean;
I
isidor 已提交
48 49 50

	constructor(actionRunner: IActionRunner, settings: any,
		@IMessageService messageService: IMessageService,
I
isidor 已提交
51
		@IInstantiationService private instantiationService: IInstantiationService,
I
isidor 已提交
52 53
		@IContextMenuService contextMenuService: IContextMenuService,
		@ITextFileService private textFileService: ITextFileService,
54
		@IEditorGroupService private editorGroupService: IEditorGroupService,
I
isidor 已提交
55
		@IConfigurationService private configurationService: IConfigurationService,
B
Benjamin Pasero 已提交
56
		@IKeybindingService keybindingService: IKeybindingService,
57
		@IUntitledEditorService private untitledEditorService: IUntitledEditorService,
58 59
		@IViewletService private viewletService: IViewletService,
		@IEventService private eventService: IEventService
I
isidor 已提交
60
	) {
61
		super(actionRunner, OpenEditorsView.computeExpandedBodySize(editorGroupService.getStacksModel()), !!settings[OpenEditorsView.MEMENTO_COLLAPSED], nls.localize('openEditosrSection', "Open Editors Section"), messageService, keybindingService, contextMenuService);
I
isidor 已提交
62 63

		this.settings = settings;
64
		this.model = editorGroupService.getStacksModel();
65

66 67
		this.structuralRefreshDelay = 0;
		this.structuralTreeRefreshScheduler = new RunOnceScheduler(() => this.structuralTreeUpdate(), this.structuralRefreshDelay);
I
isidor 已提交
68 69 70 71 72 73 74 75 76 77 78 79 80
	}

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

I
isidor 已提交
81 82 83
	public getActions(): IAction[] {
		return [
			this.instantiationService.createInstance(SaveAllAction, SaveAllAction.ID, SaveAllAction.LABEL),
I
isidor 已提交
84
			this.instantiationService.createInstance(CloseAllEditorsAction, CloseAllEditorsAction.ID, CloseAllEditorsAction.LABEL)
I
isidor 已提交
85 86 87
		];
	}

I
isidor 已提交
88 89 90 91
	public renderBody(container: HTMLElement): void {
		this.treeContainer = super.renderViewTree(container);
		dom.addClass(this.treeContainer, 'explorer-open-editors');

I
isidor 已提交
92
		const dataSource = this.instantiationService.createInstance(DataSource);
93
		const actionProvider = this.instantiationService.createInstance(ActionProvider, this.model);
I
isidor 已提交
94
		const renderer = this.instantiationService.createInstance(Renderer, actionProvider, this.model);
I
isidor 已提交
95
		const controller = this.instantiationService.createInstance(Controller, actionProvider, this.model);
I
isidor 已提交
96
		const accessibilityProvider = this.instantiationService.createInstance(AccessibilityProvider);
I
isidor 已提交
97
		const dnd = this.instantiationService.createInstance(DragAndDrop);
I
isidor 已提交
98 99 100

		this.tree = new Tree(this.treeContainer, {
			dataSource,
I
isidor 已提交
101
			renderer,
I
isidor 已提交
102
			controller,
I
isidor 已提交
103 104
			accessibilityProvider,
			dnd
I
isidor 已提交
105 106
		}, {
			indentPixels: 0,
107
			twistiePixels: 20,
I
isidor 已提交
108 109 110
			ariaLabel: nls.localize('treeAriaLabel', "Open Editors")
		});

111 112
		this.fullRefreshNeeded = true;
		this.structuralTreeUpdate();
I
isidor 已提交
113 114
	}

I
isidor 已提交
115 116 117 118 119 120 121 122 123 124 125 126 127 128 129
	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
130
		this.toDispose.push(this.model.onModelChanged(e => this.onEditorStacksModelChanged(e)));
I
isidor 已提交
131 132 133

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

135
		// Also handle dirty count indicator #10556
136
		this.toDispose.push(this.textFileService.models.onModelDirty(e => this.updateDirtyIndicator()));
137

138
		// We are not updating the tree while the viewlet is not visible. Thus refresh when viewlet becomes visible #6702
139 140
		this.toDispose.push(this.viewletService.onDidViewletOpen(viewlet => {
			if (viewlet.getId() === VIEWLET_ID) {
141 142
				this.fullRefreshNeeded = true;
				this.structuralTreeUpdate();
143
				this.updateDirtyIndicator();
144 145
			}
		}));
I
isidor 已提交
146 147
	}

148 149
	private onEditorStacksModelChanged(e: IStacksModelChangeEvent): void {
		if (this.isDisposed || !this.isVisible || !this.tree) {
150 151 152
			return;
		}

153 154 155 156 157 158
		// Do a minimal tree update based on if the change is structural or not #6670
		if (e.structural) {
			// If an editor changed structurally it is enough to refresh the group, otherwise a group changed structurally and we need the full refresh.
			// If there are multiple groups to refresh - refresh the whole tree.
			if (e.editor && !this.groupToRefresh) {
				this.groupToRefresh = e.group;
159
			} else {
160
				this.fullRefreshNeeded = true;
161
			}
162
			this.structuralTreeRefreshScheduler.schedule(this.structuralRefreshDelay);
163 164 165
		} else {
			const toRefresh = e.editor ? new OpenEditor(e.editor, e.group) : e.group;
			this.updateDirtyIndicator();
I
isidor 已提交
166
			this.tree.refresh(toRefresh, false).done(() => this.highlightActiveEditor(), errors.onUnexpectedError);
167
		}
168 169
	}

170 171
	private structuralTreeUpdate(): void {
		// View size
172
		this.expandedBodySize = this.getExpandedBodySize(this.model);
173 174
		// Show groups only if there is more than 1 group
		const treeInput = this.model.groups.length === 1 ? this.model.groups[0] : this.model;
175 176
		// TODO@Isidor temporary workaround due to a partial tree refresh issue
		this.fullRefreshNeeded = true;
177 178 179 180 181 182 183
		const toRefresh = this.fullRefreshNeeded ? null : this.groupToRefresh;

		(treeInput !== this.tree.getInput() ? this.tree.setInput(treeInput) : this.tree.refresh(toRefresh)).done(() => {
			this.fullRefreshNeeded = false;
			this.groupToRefresh = null;

			// Always expand all the groups as they are unclickable
I
isidor 已提交
184
			return this.tree.expandAll(this.model.groups).then(() => this.highlightActiveEditor());
185 186
		}, errors.onUnexpectedError);
	}
187

188 189 190 191 192 193 194 195 196
	private highlightActiveEditor(): void {
		if (this.model.activeGroup && this.model.activeGroup.activeEditor /* could be empty */) {
			const openEditor = new OpenEditor(this.model.activeGroup.activeEditor, this.model.activeGroup);
			this.tree.clearFocus();
			this.tree.clearSelection();

			if (openEditor) {
				this.tree.setFocus(openEditor);
				this.tree.setSelection([openEditor]);
197 198 199 200 201
				const relativeTop = this.tree.getRelativeTop(openEditor);
				if (relativeTop <= 0 || relativeTop >= 1) {
					// Only reveal the element if it is not visible #8279
					this.tree.reveal(openEditor).done(null, errors.onUnexpectedError);
				}
202
			}
I
isidor 已提交
203 204
		}
	}
205

I
isidor 已提交
206
	private onConfigurationUpdated(configuration: IFilesConfiguration): void {
B
Benjamin Pasero 已提交
207 208 209 210
		if (this.isDisposed) {
			return; // guard against possible race condition when config change causes recreate of views
		}

211
		let visibleOpenEditors = configuration && configuration.explorer && configuration.explorer.openEditors && configuration.explorer.openEditors.visible;
I
isidor 已提交
212
		if (typeof visibleOpenEditors === 'number') {
213
			this.visibleOpenEditors = visibleOpenEditors;
I
isidor 已提交
214
		} else {
215
			this.visibleOpenEditors = OpenEditorsView.DEFAULT_VISIBLE_OPEN_EDITORS;
I
isidor 已提交
216 217
		}

I
isidor 已提交
218
		let dynamicHeight = configuration && configuration.explorer && configuration.explorer.openEditors && configuration.explorer.openEditors.dynamicHeight;
I
isidor 已提交
219 220 221 222 223
		if (typeof dynamicHeight === 'boolean') {
			this.dynamicHeight = dynamicHeight;
		} else {
			this.dynamicHeight = OpenEditorsView.DEFAULT_DYNAMIC_HEIGHT;
		}
224 225

		// Adjust expanded body size
226
		this.expandedBodySize = this.getExpandedBodySize(this.model);
I
isidor 已提交
227 228
	}

I
isidor 已提交
229
	private updateDirtyIndicator(): void {
230 231
		let dirty = this.textFileService.getAutoSaveMode() !== AutoSaveMode.AFTER_SHORT_DELAY ? this.textFileService.getDirty().length
			: this.untitledEditorService.getDirty().length;
I
isidor 已提交
232
		if (dirty === 0) {
I
isidor 已提交
233
			dom.addClass(this.dirtyCountElement, 'hidden');
I
isidor 已提交
234 235
		} else {
			this.dirtyCountElement.textContent = nls.localize('dirtyCounter', "{0} unsaved", dirty);
I
isidor 已提交
236
			dom.removeClass(this.dirtyCountElement, 'hidden');
I
isidor 已提交
237 238 239
		}
	}

240 241
	private getExpandedBodySize(model: IEditorStacksModel): number {
		return OpenEditorsView.computeExpandedBodySize(model, this.visibleOpenEditors, this.dynamicHeight);
242 243
	}

244
	private static computeExpandedBodySize(model: IEditorStacksModel, visibleOpenEditors = OpenEditorsView.DEFAULT_VISIBLE_OPEN_EDITORS, dynamicHeight = OpenEditorsView.DEFAULT_DYNAMIC_HEIGHT): number {
245 246 247 248 249
		let entryCount = model.groups.reduce((sum, group) => sum + group.count, 0);
		// We only show the group labels if there is more than 1 group
		if (model.groups.length > 1) {
			entryCount += model.groups.length;
		}
250 251 252

		let itemsToShow: number;
		if (dynamicHeight) {
253
			itemsToShow = Math.min(Math.max(visibleOpenEditors, 1), entryCount);
254
		} else {
255
			itemsToShow = Math.max(visibleOpenEditors, 1);
256 257 258 259 260
		}

		return itemsToShow * Renderer.ITEM_HEIGHT;
	}

261 262 263 264
	public setStructuralRefreshDelay(delay: number): void {
		this.structuralRefreshDelay = delay;
	}

I
isidor 已提交
265 266 267 268 269 270 271 272 273 274 275
	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 已提交
276
}