openFileHandler.ts 10.1 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.
 *--------------------------------------------------------------------------------------------*/
'use strict';

J
Johannes Rieken 已提交
7
import { TPromise } from 'vs/base/common/winjs.base';
8
import errors = require('vs/base/common/errors');
E
Erich Gamma 已提交
9 10 11
import nls = require('vs/nls');
import paths = require('vs/base/common/paths');
import labels = require('vs/base/common/labels');
12
import * as objects from 'vs/base/common/objects';
13
import { defaultGenerator } from 'vs/base/common/idGenerator';
E
Erich Gamma 已提交
14
import URI from 'vs/base/common/uri';
J
Johannes Rieken 已提交
15 16 17 18
import { IIconLabelOptions } from 'vs/base/browser/ui/iconLabel/iconLabel';
import { IModeService } from 'vs/editor/common/services/modeService';
import { getIconClasses } from 'vs/workbench/browser/labels';
import { IModelService } from 'vs/editor/common/services/modelService';
19
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
J
Johannes Rieken 已提交
20 21 22
import { IAutoFocus } from 'vs/base/parts/quickopen/common/quickOpen';
import { QuickOpenEntry, QuickOpenModel } from 'vs/base/parts/quickopen/browser/quickOpenModel';
import { QuickOpenHandler, EditorQuickOpenEntry } from 'vs/workbench/browser/quickopen';
23
import { QueryBuilder } from 'vs/workbench/parts/search/common/queryBuilder';
B
Benjamin Pasero 已提交
24
import { EditorInput, IWorkbenchEditorConfiguration } from 'vs/workbench/common/editor';
J
Johannes Rieken 已提交
25 26 27 28 29 30
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { IResourceInput } from 'vs/platform/editor/common/editor';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IQueryOptions, ISearchService, ISearchStats, ISearchQuery } from 'vs/platform/search/common/search';
31
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
32 33
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IRange } from 'vs/editor/common/core/range';
34
import { getOutOfWorkspaceEditorResources } from 'vs/workbench/parts/search/common/search';
35
import { IExperimentService } from 'vs/platform/telemetry/common/experiments';
E
Erich Gamma 已提交
36

37 38
export class FileQuickOpenModel extends QuickOpenModel {

39
	constructor(entries: QuickOpenEntry[], public stats?: ISearchStats) {
40 41 42 43
		super(entries);
	}
}

E
Erich Gamma 已提交
44 45 46
export class FileEntry extends EditorQuickOpenEntry {
	private range: IRange;

47
	constructor(
48 49 50 51
		private resource: URI,
		private name: string,
		private description: string,
		private icon: string,
E
Erich Gamma 已提交
52
		@IWorkbenchEditorService editorService: IWorkbenchEditorService,
B
Benjamin Pasero 已提交
53
		@IModeService private modeService: IModeService,
54
		@IModelService private modelService: IModelService,
55
		@IConfigurationService private configurationService: IConfigurationService,
E
Erich Gamma 已提交
56 57 58 59 60 61 62 63 64
		@IWorkspaceContextService contextService: IWorkspaceContextService
	) {
		super(editorService);
	}

	public getLabel(): string {
		return this.name;
	}

B
Benjamin Pasero 已提交
65 66
	public getLabelOptions(): IIconLabelOptions {
		return {
67
			extraClasses: getIconClasses(this.modelService, this.modeService, this.resource)
B
Benjamin Pasero 已提交
68 69 70
		};
	}

71 72 73 74
	public getAriaLabel(): string {
		return nls.localize('entryAriaLabel', "{0}, file picker", this.getLabel());
	}

E
Erich Gamma 已提交
75 76 77 78 79
	public getDescription(): string {
		return this.description;
	}

	public getIcon(): string {
80
		return this.icon;
E
Erich Gamma 已提交
81 82 83 84 85 86 87 88 89 90
	}

	public getResource(): URI {
		return this.resource;
	}

	public setRange(range: IRange): void {
		this.range = range;
	}

91 92 93 94
	public isFile(): boolean {
		return true; // TODO@Ben debt with editor history merging
	}

95
	public getInput(): IResourceInput | EditorInput {
96
		const input: IResourceInput = {
E
Erich Gamma 已提交
97
			resource: this.resource,
98
			options: {
99
				pinned: !this.configurationService.getConfiguration<IWorkbenchEditorConfiguration>().workbench.editor.enablePreviewFromQuickOpen
100
			}
E
Erich Gamma 已提交
101 102 103
		};

		if (this.range) {
104
			input.options.selection = this.range;
E
Erich Gamma 已提交
105 106 107 108 109 110
		}

		return input;
	}
}

111
export interface IOpenFileOptions {
B
Benjamin Pasero 已提交
112
	forceUseIcons: boolean;
113
}
114

115 116
export class OpenFileHandler extends QuickOpenHandler {
	private options: IOpenFileOptions;
E
Erich Gamma 已提交
117
	private queryBuilder: QueryBuilder;
118
	private cacheState: CacheState;
E
Erich Gamma 已提交
119 120

	constructor(
121
		@IEditorGroupService private editorGroupService: IEditorGroupService,
E
Erich Gamma 已提交
122
		@IInstantiationService private instantiationService: IInstantiationService,
123
		@IWorkbenchThemeService private themeService: IWorkbenchThemeService,
E
Erich Gamma 已提交
124
		@IWorkspaceContextService private contextService: IWorkspaceContextService,
125
		@ISearchService private searchService: ISearchService,
126
		@IExperimentService private experimentService: IExperimentService,
127
		@IEnvironmentService private environmentService: IEnvironmentService
E
Erich Gamma 已提交
128 129 130 131 132 133
	) {
		super();

		this.queryBuilder = this.instantiationService.createInstance(QueryBuilder);
	}

134 135 136 137
	public setOptions(options: IOpenFileOptions) {
		this.options = options;
	}

138
	public getResults(searchValue: string, maxSortedResults?: number): TPromise<FileQuickOpenModel> {
E
Erich Gamma 已提交
139 140 141 142
		searchValue = searchValue.trim();

		// Respond directly to empty search
		if (!searchValue) {
143
			return TPromise.as(new FileQuickOpenModel([]));
E
Erich Gamma 已提交
144 145
		}

146 147
		// Do find results
		return this.doFindResults(searchValue, this.cacheState.cacheKey, maxSortedResults);
E
Erich Gamma 已提交
148 149
	}

150
	private doFindResults(searchValue: string, cacheKey?: string, maxSortedResults?: number): TPromise<FileQuickOpenModel> {
151
		const query: IQueryOptions = {
152
			extraFileResources: getOutOfWorkspaceEditorResources(this.editorGroupService, this.contextService),
153 154
			filePattern: searchValue,
			cacheKey: cacheKey
155
		};
156

157
		if (typeof maxSortedResults === 'number') {
158 159 160
			query.maxResults = maxSortedResults;
			query.sortByScore = true;
		}
E
Erich Gamma 已提交
161

B
Benjamin Pasero 已提交
162 163 164 165 166
		let iconClass: string;
		if (this.options && this.options.forceUseIcons && !this.themeService.getFileIconTheme()) {
			iconClass = 'file'; // only use a generic file icon if we are forced to use an icon and have no icon theme set otherwise
		}

167
		const folderResources = this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? this.contextService.getWorkspace().roots : [];
R
Rob Lourens 已提交
168
		return this.searchService.search(this.queryBuilder.file(folderResources, query)).then((complete) => {
169
			const results: QuickOpenEntry[] = [];
170
			for (let i = 0; i < complete.results.length; i++) {
171
				const fileMatch = complete.results[i];
172

173
				const label = paths.basename(fileMatch.resource.fsPath);
174
				const description = labels.getPathLabel(paths.dirname(fileMatch.resource.fsPath), this.contextService, this.environmentService);
175

B
Benjamin Pasero 已提交
176
				results.push(this.instantiationService.createInstance(FileEntry, fileMatch.resource, label, description, iconClass));
177 178
			}

179
			return new FileQuickOpenModel(results, complete.stats);
E
Erich Gamma 已提交
180 181 182
		});
	}

183 184 185 186
	public hasShortResponseTime(): boolean {
		return this.isCacheLoaded;
	}

187
	public onOpen(): void {
188 189
		this.cacheState = new CacheState(cacheKey => this.cacheQuery(cacheKey), query => this.searchService.search(query), cacheKey => this.searchService.clearCache(cacheKey), this.cacheState);
		this.cacheState.load();
190 191
	}

192 193
	private cacheQuery(cacheKey: string): ISearchQuery {
		const options: IQueryOptions = {
B
Benjamin Pasero 已提交
194
			extraFileResources: getOutOfWorkspaceEditorResources(this.editorGroupService, this.contextService),
195 196 197
			filePattern: '',
			cacheKey: cacheKey,
			maxResults: 0,
198 199
			sortByScore: true,
			useRipgrep: this.experimentService.getExperiments().ripgrepQuickSearch
200
		};
201

202
		const folderResources = this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY ? this.contextService.getWorkspace().roots : [];
R
Rob Lourens 已提交
203
		const query = this.queryBuilder.file(folderResources, options);
204

205 206 207 208 209
		return query;
	}

	public get isCacheLoaded(): boolean {
		return this.cacheState && this.cacheState.isLoaded;
210 211
	}

E
Erich Gamma 已提交
212 213 214 215 216 217 218 219 220
	public getGroupLabel(): string {
		return nls.localize('searchResults', "search results");
	}

	public getAutoFocus(searchValue: string): IAutoFocus {
		return {
			autoFocusFirstEntry: true
		};
	}
221 222
}

223 224 225 226 227 228 229
enum LoadingPhase {
	Created = 1,
	Loading,
	Loaded,
	Errored,
	Disposed
}
230

231 232 233 234
/**
 * Exported for testing.
 */
export class CacheState {
235

236
	private _cacheKey = defaultGenerator.nextId();
237
	private query: ISearchQuery;
238

239
	private loadingPhase = LoadingPhase.Created;
240 241
	private promise: TPromise<void>;

242
	constructor(cacheQuery: (cacheKey: string) => ISearchQuery, private doLoad: (query: ISearchQuery) => TPromise<any>, private doDispose: (cacheKey: string) => TPromise<void>, private previous: CacheState) {
243 244 245 246 247 248 249 250 251 252 253 254
		this.query = cacheQuery(this._cacheKey);
		if (this.previous) {
			const current = objects.assign({}, this.query, { cacheKey: null });
			const previous = objects.assign({}, this.previous.query, { cacheKey: null });
			if (!objects.equals(current, previous)) {
				this.previous.dispose();
				this.previous = null;
			}
		}
	}

	public get cacheKey(): string {
255
		return this.loadingPhase === LoadingPhase.Loaded || !this.previous ? this._cacheKey : this.previous.cacheKey;
256 257 258
	}

	public get isLoaded(): boolean {
259 260 261 262 263 264 265
		const isLoaded = this.loadingPhase === LoadingPhase.Loaded;
		return isLoaded || !this.previous ? isLoaded : this.previous.isLoaded;
	}

	public get isUpdating(): boolean {
		const isUpdating = this.loadingPhase === LoadingPhase.Loading;
		return isUpdating || !this.previous ? isUpdating : this.previous.isUpdating;
266 267 268
	}

	public load(): void {
269 270 271 272
		if (this.isUpdating) {
			return;
		}
		this.loadingPhase = LoadingPhase.Loading;
273 274
		this.promise = this.doLoad(this.query)
			.then(() => {
275
				this.loadingPhase = LoadingPhase.Loaded;
276 277 278 279 280
				if (this.previous) {
					this.previous.dispose();
					this.previous = null;
				}
			}, err => {
281
				this.loadingPhase = LoadingPhase.Errored;
282
				errors.onUnexpectedError(err);
283 284 285 286
			});
	}

	public dispose(): void {
287 288 289 290 291 292 293 294 295 296 297
		if (this.promise) {
			this.promise.then(null, () => { })
				.then(() => {
					this.loadingPhase = LoadingPhase.Disposed;
					return this.doDispose(this._cacheKey);
				}).then(null, err => {
					errors.onUnexpectedError(err);
				});
		} else {
			this.loadingPhase = LoadingPhase.Disposed;
		}
298 299 300 301 302
		if (this.previous) {
			this.previous.dispose();
			this.previous = null;
		}
	}
303
}