settingsEditor2.ts 25.6 KB
Newer Older
R
Rob Lourens 已提交
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 * as DOM from 'vs/base/browser/dom';
7
import { Button } from 'vs/base/browser/ui/button/button';
8
import * as arrays from 'vs/base/common/arrays';
R
Rob Lourens 已提交
9
import { Delayer, ThrottledDelayer } from 'vs/base/common/async';
R
Rob Lourens 已提交
10
import { CancellationToken } from 'vs/base/common/cancellation';
R
Rob Lourens 已提交
11
import { Color } from 'vs/base/common/color';
12
import { getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors';
13
import { KeyCode } from 'vs/base/common/keyCodes';
R
Rob Lourens 已提交
14
import { TPromise } from 'vs/base/common/winjs.base';
15
import { ITree, ITreeConfiguration } from 'vs/base/parts/tree/browser/tree';
16
import { DefaultTreestyler } from 'vs/base/parts/tree/browser/treeDefaults';
R
Rob Lourens 已提交
17 18 19
import 'vs/css!./media/settingsEditor2';
import { localize } from 'vs/nls';
import { ConfigurationTarget, IConfigurationService } from 'vs/platform/configuration/common/configuration';
R
Rob Lourens 已提交
20 21
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
R
Rob Lourens 已提交
22
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
23
import { WorkbenchTree } from 'vs/platform/list/browser/listService';
24
import { ILogService } from 'vs/platform/log/common/log';
R
Rob Lourens 已提交
25
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
26
import { editorBackground, foreground, listActiveSelectionBackground, listInactiveSelectionBackground } from 'vs/platform/theme/common/colorRegistry';
27 28
import { attachButtonStyler, attachStyler } from 'vs/platform/theme/common/styler';
import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
R
Rob Lourens 已提交
29
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
30
import { EditorOptions, IEditor } from 'vs/workbench/common/editor';
31
import { SearchWidget, SettingsTarget, SettingsTargetsWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets';
32
import { tocData } from 'vs/workbench/parts/preferences/browser/settingsLayout';
33
import { ISettingsEditorViewState, SearchResultIdx, SearchResultModel, SettingsAccessibilityProvider, SettingsDataSource, SettingsRenderer, SettingsTreeController, SettingsTreeElement, SettingsTreeFilter, SettingsTreeModel } from 'vs/workbench/parts/preferences/browser/settingsTree';
34
import { TOCDataSource, TOCRenderer } from 'vs/workbench/parts/preferences/browser/tocTree';
R
Rob Lourens 已提交
35
import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_SEARCH_FOCUS, IPreferencesSearchService, ISearchProvider } from 'vs/workbench/parts/preferences/common/preferences';
36
import { IPreferencesService, ISearchResult, ISettingsEditorModel } from 'vs/workbench/services/preferences/common/preferences';
37
import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput';
R
Rob Lourens 已提交
38
import { DefaultSettingsEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels';
R
Rob Lourens 已提交
39

40 41
const $ = DOM.$;

R
Rob Lourens 已提交
42 43 44 45 46 47 48 49 50 51
export class SettingsEditor2 extends BaseEditor {

	public static readonly ID: string = 'workbench.editor.settings2';

	private defaultSettingsEditorModel: DefaultSettingsEditorModel;

	private headerContainer: HTMLElement;
	private searchWidget: SearchWidget;
	private settingsTargetsWidget: SettingsTargetsWidget;

52
	private showConfiguredSettingsOnlyCheckbox: HTMLInputElement;
R
Rob Lourens 已提交
53

54 55 56
	private settingsTreeContainer: HTMLElement;
	private settingsTree: WorkbenchTree;
	private treeDataSource: SettingsDataSource;
57
	private settingsTreeModel: SettingsTreeModel;
R
Rob Lourens 已提交
58

R
Rob Lourens 已提交
59 60 61
	private tocTreeContainer: HTMLElement;
	private tocTree: WorkbenchTree;

R
Rob Lourens 已提交
62 63 64
	private delayedFilterLogging: Delayer<void>;
	private localSearchDelayer: Delayer<void>;
	private remoteSearchThrottle: ThrottledDelayer<void>;
R
Rob Lourens 已提交
65
	private searchInProgress: TPromise<void>;
R
Rob Lourens 已提交
66

67 68
	private settingUpdateDelayer: Delayer<void>;
	private pendingSettingUpdate: { key: string, value: any };
R
Rob Lourens 已提交
69

70
	private selectedElement: SettingsTreeElement;
71

72
	private viewState: ISettingsEditorViewState;
73
	private searchResultModel: SearchResultModel;
74 75
	private inSettingsEditorContextKey: IContextKey<boolean>;
	private searchFocusContextKey: IContextKey<boolean>;
76

R
Rob Lourens 已提交
77 78 79 80 81
	constructor(
		@ITelemetryService telemetryService: ITelemetryService,
		@IConfigurationService private configurationService: IConfigurationService,
		@IThemeService themeService: IThemeService,
		@IPreferencesService private preferencesService: IPreferencesService,
R
Rob Lourens 已提交
82 83
		@IInstantiationService private instantiationService: IInstantiationService,
		@IPreferencesSearchService private preferencesSearchService: IPreferencesSearchService,
84
		@ILogService private logService: ILogService,
85 86
		@IEnvironmentService private environmentService: IEnvironmentService,
		@IContextKeyService contextKeyService: IContextKeyService
R
Rob Lourens 已提交
87 88
	) {
		super(SettingsEditor2.ID, telemetryService, themeService);
R
Rob Lourens 已提交
89 90 91
		this.delayedFilterLogging = new Delayer<void>(1000);
		this.localSearchDelayer = new Delayer(100);
		this.remoteSearchThrottle = new ThrottledDelayer(200);
92
		this.viewState = { settingsTarget: ConfigurationTarget.USER };
R
Rob Lourens 已提交
93

94 95
		this.settingUpdateDelayer = new Delayer<void>(500);

96 97 98
		this.inSettingsEditorContextKey = CONTEXT_SETTINGS_EDITOR.bindTo(contextKeyService);
		this.searchFocusContextKey = CONTEXT_SETTINGS_SEARCH_FOCUS.bindTo(contextKeyService);

R
Rob Lourens 已提交
99
		this._register(configurationService.onDidChangeConfiguration(() => this.refreshTreeAndMaintainFocus()));
R
Rob Lourens 已提交
100 101 102 103 104 105 106 107 108
	}

	createEditor(parent: HTMLElement): void {
		const prefsEditorElement = DOM.append(parent, $('div', { class: 'settings-editor' }));

		this.createHeader(prefsEditorElement);
		this.createBody(prefsEditorElement);
	}

109
	setInput(input: SettingsEditor2Input, options: EditorOptions, token: CancellationToken): Thenable<void> {
110
		this.inSettingsEditorContextKey.set(true);
111
		return super.setInput(input, options, token)
R
Rob Lourens 已提交
112
			.then(() => {
113
				this.render(token);
R
Rob Lourens 已提交
114 115 116
			});
	}

117 118 119 120 121
	clearInput(): void {
		this.inSettingsEditorContextKey.set(false);
		super.clearInput();
	}

R
Rob Lourens 已提交
122 123
	layout(dimension: DOM.Dimension): void {
		this.searchWidget.layout(dimension);
124
		this.layoutSettingsList(dimension);
R
Rob Lourens 已提交
125 126 127
	}

	focus(): void {
128 129 130 131
		this.focusSearch();
	}

	focusSearch(): void {
R
Rob Lourens 已提交
132 133 134
		this.searchWidget.focus();
	}

135 136 137 138
	clearSearchResults(): void {
		this.searchWidget.clear();
	}

R
Rob Lourens 已提交
139 140 141
	private createHeader(parent: HTMLElement): void {
		this.headerContainer = DOM.append(parent, $('.settings-header'));

142
		const previewHeader = DOM.append(this.headerContainer, $('.settings-preview-header'));
R
Rob Lourens 已提交
143 144 145 146 147

		const previewAlert = DOM.append(previewHeader, $('span.settings-preview-warning'));
		previewAlert.textContent = localize('previewWarning', "Preview");

		const previewTextLabel = DOM.append(previewHeader, $('span.settings-preview-label'));
148
		previewTextLabel.textContent = localize('previewLabel', "This is a preview of our new settings editor");
149

R
Rob Lourens 已提交
150 151 152
		const searchContainer = DOM.append(this.headerContainer, $('.search-container'));
		this.searchWidget = this._register(this.instantiationService.createInstance(SearchWidget, searchContainer, {
			ariaLabel: localize('SearchSettings.AriaLabel', "Search settings"),
153 154
			placeholder: localize('SearchSettings.Placeholder', "Search settings"),
			focusKey: this.searchFocusContextKey
R
Rob Lourens 已提交
155
		}));
156
		this._register(this.searchWidget.onDidChange(() => this.onSearchInputChanged()));
157 158
		this._register(DOM.addStandardDisposableListener(this.searchWidget.domNode, 'keydown', e => {
			if (e.keyCode === KeyCode.DownArrow) {
159 160
				this.settingsTree.focusFirst();
				this.settingsTree.domFocus();
161 162
			}
		}));
R
Rob Lourens 已提交
163

164 165 166 167 168 169 170 171 172 173 174 175 176 177
		const advancedCustomization = DOM.append(this.headerContainer, $('.settings-advanced-customization'));
		const advancedCustomizationLabel = DOM.append(advancedCustomization, $('span.settings-advanced-customization-label'));
		advancedCustomizationLabel.textContent = localize('advancedCustomizationLabel', "For advanced customizations open and edit") + ' ';
		const openSettingsButton = this._register(new Button(advancedCustomization, { title: true, buttonBackground: null, buttonHoverBackground: null }));
		this._register(attachButtonStyler(openSettingsButton, this.themeService, {
			buttonBackground: Color.transparent.toString(),
			buttonHoverBackground: Color.transparent.toString(),
			buttonForeground: foreground
		}));
		openSettingsButton.label = localize('openSettingsLabel', "settings.json");
		openSettingsButton.element.classList.add('open-settings-button');

		this._register(openSettingsButton.onDidClick(() => this.openSettingsFile()));

178
		const headerControlsContainer = DOM.append(this.headerContainer, $('.settings-header-controls'));
R
Rob Lourens 已提交
179 180 181
		const targetWidgetContainer = DOM.append(headerControlsContainer, $('.settings-target-container'));
		this.settingsTargetsWidget = this._register(this.instantiationService.createInstance(SettingsTargetsWidget, targetWidgetContainer));
		this.settingsTargetsWidget.settingsTarget = ConfigurationTarget.USER;
182 183 184 185
		this.settingsTargetsWidget.onDidTargetChange(() => {
			this.viewState.settingsTarget = this.settingsTargetsWidget.settingsTarget;
			this.settingsTree.refresh();
		});
R
Rob Lourens 已提交
186

187
		this.createHeaderControls(headerControlsContainer);
R
Rob Lourens 已提交
188 189
	}

190 191 192
	private createHeaderControls(parent: HTMLElement): void {
		const headerControlsContainerRight = DOM.append(parent, $('.settings-header-controls-right'));

193 194 195
		this.showConfiguredSettingsOnlyCheckbox = DOM.append(headerControlsContainerRight, $('input#configured-only-checkbox'));
		this.showConfiguredSettingsOnlyCheckbox.type = 'checkbox';
		const showConfiguredSettingsOnlyLabel = <HTMLLabelElement>DOM.append(headerControlsContainerRight, $('label.configured-only-label'));
196
		showConfiguredSettingsOnlyLabel.textContent = localize('showOverriddenOnly', "Show modified only");
197
		showConfiguredSettingsOnlyLabel.htmlFor = 'configured-only-checkbox';
198

199
		this._register(DOM.addDisposableListener(this.showConfiguredSettingsOnlyCheckbox, 'change', e => this.onShowConfiguredOnlyClicked()));
200 201 202 203 204 205 206 207 208 209 210 211
	}

	private openSettingsFile(): TPromise<IEditor> {
		const currentSettingsTarget = this.settingsTargetsWidget.settingsTarget;

		if (currentSettingsTarget === ConfigurationTarget.USER) {
			return this.preferencesService.openGlobalSettings();
		} else if (currentSettingsTarget === ConfigurationTarget.WORKSPACE) {
			return this.preferencesService.openWorkspaceSettings();
		} else {
			return this.preferencesService.openFolderSettings(currentSettingsTarget);
		}
R
Rob Lourens 已提交
212 213 214 215 216
	}

	private createBody(parent: HTMLElement): void {
		const bodyContainer = DOM.append(parent, $('.settings-body'));

R
Rob Lourens 已提交
217 218
		this.createTOC(bodyContainer);
		this.createSettingsTree(bodyContainer);
219 220 221 222

		if (this.environmentService.appQuality !== 'stable') {
			this.createFeedbackButton(bodyContainer);
		}
R
Rob Lourens 已提交
223 224
	}

R
Rob Lourens 已提交
225 226 227 228 229 230 231 232 233
	private createTOC(parent: HTMLElement): void {
		this.tocTreeContainer = DOM.append(parent, $('.settings-toc-container'));

		const tocTreeDataSource = this.instantiationService.createInstance(TOCDataSource);
		const renderer = this.instantiationService.createInstance(TOCRenderer);

		this.tocTree = this.instantiationService.createInstance(WorkbenchTree, this.tocTreeContainer,
			<ITreeConfiguration>{
				dataSource: tocTreeDataSource,
234 235
				renderer,
				filter: this.instantiationService.createInstance(SettingsTreeFilter, this.viewState)
R
Rob Lourens 已提交
236 237 238 239 240 241
			},
			{
				showLoading: false
			});

		this._register(this.tocTree.onDidChangeSelection(e => {
242 243 244 245
			if (this.settingsTreeModel) {
				const element = this.settingsTreeModel.getElementById(e.selection[0] && e.selection[0].id);
				this.settingsTree.reveal(element, 0);
			}
R
Rob Lourens 已提交
246 247 248 249
		}));
	}

	private createSettingsTree(parent: HTMLElement): void {
250
		this.settingsTreeContainer = DOM.append(parent, $('.settings-tree-container'));
R
Rob Lourens 已提交
251

252
		this.treeDataSource = this.instantiationService.createInstance(SettingsDataSource, this.viewState);
253
		const renderer = this.instantiationService.createInstance(SettingsRenderer, this.settingsTreeContainer);
254
		this._register(renderer.onDidChangeSetting(e => this.onDidChangeSetting(e.key, e.value)));
255
		this._register(renderer.onDidOpenSettings(() => this.openSettingsFile()));
256

257
		const treeClass = 'settings-editor-tree';
258
		this.settingsTree = this.instantiationService.createInstance(WorkbenchTree, this.settingsTreeContainer,
259
			<ITreeConfiguration>{
260
				dataSource: this.treeDataSource,
R
Rob Lourens 已提交
261
				renderer,
262 263
				controller: this.instantiationService.createInstance(SettingsTreeController),
				accessibilityProvider: this.instantiationService.createInstance(SettingsAccessibilityProvider),
264 265
				filter: this.instantiationService.createInstance(SettingsTreeFilter, this.viewState),
				styler: new DefaultTreestyler(DOM.createStyleSheet(), treeClass)
266 267 268 269
			},
			{
				ariaLabel: localize('treeAriaLabel', "Settings"),
				showLoading: false,
R
Rob Lourens 已提交
270
				// indentPixels: 0,
271
				twistiePixels: 15,
272
			});
273

274
		this._register(registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {
275 276 277
			const activeBorderColor = theme.getColor(listActiveSelectionBackground);
			if (activeBorderColor) {
				collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .monaco-tree:focus .monaco-tree-row.focused {outline: solid 1px ${activeBorderColor}; outline-offset: -1px; }`);
278 279
			}

280 281 282
			const inactiveBorderColor = theme.getColor(listInactiveSelectionBackground);
			if (inactiveBorderColor) {
				collector.addRule(`.settings-editor > .settings-body > .settings-tree-container .monaco-tree .monaco-tree-row.focused {outline: solid 1px ${inactiveBorderColor}; outline-offset: -1px; }`);
283
			}
284 285
		}));

286 287
		this.settingsTree.getHTMLElement().classList.add(treeClass);

288
		attachStyler(this.themeService, {
289 290 291 292 293 294 295 296 297 298
			listActiveSelectionBackground: editorBackground,
			listActiveSelectionForeground: foreground,
			listFocusAndSelectionBackground: editorBackground,
			listFocusAndSelectionForeground: foreground,
			listFocusBackground: editorBackground,
			listFocusForeground: foreground,
			listHoverForeground: foreground,
			listHoverBackground: editorBackground,
			listInactiveSelectionBackground: editorBackground,
			listInactiveSelectionForeground: foreground
299 300
		}, colors => {
			this.settingsTree.style(colors);
301
		});
302

303
		this.settingsTree.onDidChangeFocus(e => {
304
			this.settingsTree.setSelection([e.focus]);
305 306
			if (this.selectedElement) {
				this.settingsTree.refresh(this.selectedElement);
307
			}
308

309 310
			if (e.focus) {
				this.settingsTree.refresh(e.focus);
311
			}
312 313

			this.selectedElement = e.focus;
314
		});
315 316
	}

317 318 319 320 321 322 323 324 325 326 327 328
	private createFeedbackButton(parent: HTMLElement): void {
		const feedbackButton = this._register(new Button(parent));
		feedbackButton.label = localize('feedbackButtonLabel', "Provide Feedback");
		feedbackButton.element.classList.add('settings-feedback-button');

		this._register(attachButtonStyler(feedbackButton, this.themeService));
		this._register(feedbackButton.onDidClick(() => {
			// Github master issue
			window.open('https://go.microsoft.com/fwlink/?linkid=2000807');
		}));
	}

329
	private onShowConfiguredOnlyClicked(): void {
330
		this.viewState.showConfiguredOnly = this.showConfiguredSettingsOnlyCheckbox.checked;
R
Rob Lourens 已提交
331
		this.refreshTreeAndMaintainFocus();
332 333
		this.tocTree.refresh();
		this.settingsTree.setScrollPosition(0);
334
		this.expandAll(this.settingsTree);
335 336
	}

R
Rob Lourens 已提交
337
	private onDidChangeSetting(key: string, value: any): void {
338 339
		if (this.pendingSettingUpdate && this.pendingSettingUpdate.key !== key) {
			this.updateChangedSetting(key, value);
340 341
		}

342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363
		this.pendingSettingUpdate = { key, value };
		this.settingUpdateDelayer.trigger(() => this.updateChangedSetting(key, value));
	}

	private updateChangedSetting(key: string, value: any): TPromise<void> {
		// ConfigurationService displays the error if this fails.
		// Force a render afterwards because onDidConfigurationUpdate doesn't fire if the update doesn't result in an effective setting value change
		return this.configurationService.updateValue(key, value, <ConfigurationTarget>this.settingsTargetsWidget.settingsTarget)
			.then(() => this.refreshTreeAndMaintainFocus())
			.then(() => {
				const reportModifiedProps = {
					key,
					query: this.searchWidget.getValue(),
					searchResults: this.searchResultModel && this.searchResultModel.getUniqueResults(),
					rawResults: this.searchResultModel && this.searchResultModel.getRawResults(),
					showConfiguredOnly: this.viewState.showConfiguredOnly,
					isReset: typeof value === 'undefined',
					settingsTarget: this.settingsTargetsWidget.settingsTarget as SettingsTarget
				};

				return this.reportModifiedSetting(reportModifiedProps);
			});
364 365 366
	}

	private reportModifiedSetting(props: { key: string, query: string, searchResults: ISearchResult[], rawResults: ISearchResult[], showConfiguredOnly: boolean, isReset: boolean, settingsTarget: SettingsTarget }): void {
367
		this.pendingSettingUpdate = null;
368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384

		const remoteResult = props.searchResults && props.searchResults[SearchResultIdx.Remote];
		const localResult = props.searchResults && props.searchResults[SearchResultIdx.Local];

		let groupId = undefined;
		let nlpIndex = undefined;
		let displayIndex = undefined;
		if (props.searchResults) {
			const localIndex = arrays.firstIndex(localResult.filterMatches, m => m.setting.key === props.key);
			groupId = localIndex >= 0 ?
				'local' :
				'remote';

			displayIndex = localIndex >= 0 ?
				localIndex :
				remoteResult && (arrays.firstIndex(remoteResult.filterMatches, m => m.setting.key === props.key) + localResult.filterMatches.length);

385 386 387 388 389 390
			if (this.searchResultModel) {
				const rawResults = this.searchResultModel.getRawResults();
				if (rawResults[SearchResultIdx.Remote]) {
					const _nlpIndex = arrays.firstIndex(rawResults[SearchResultIdx.Remote].filterMatches, m => m.setting.key === props.key);
					nlpIndex = _nlpIndex >= 0 ? _nlpIndex : undefined;
				}
391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409
			}
		}

		const reportedTarget = props.settingsTarget === ConfigurationTarget.USER ? 'user' :
			props.settingsTarget === ConfigurationTarget.WORKSPACE ? 'workspace' :
				'folder';

		const data = {
			key: props.key,
			query: props.query,
			groupId,
			nlpIndex,
			displayIndex,
			showConfiguredOnly: props.showConfiguredOnly,
			isReset: props.isReset,
			target: reportedTarget
		};

		/* __GDPR__
410
			"settingsEditor.settingModified" : {
411 412 413 414 415 416 417 418 419 420
				"key" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
				"query" : { "classification": "CustomerContent", "purpose": "FeatureInsight" },
				"groupId" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
				"nlpIndex" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
				"displayIndex" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
				"showConfiguredOnly" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
				"isReset" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
				"target" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
			}
		*/
421
		this.telemetryService.publicLog('settingsEditor.settingModified', data);
R
Rob Lourens 已提交
422 423
	}

424
	private render(token: CancellationToken): TPromise<any> {
R
Rob Lourens 已提交
425 426
		if (this.input) {
			return this.input.resolve()
427 428 429 430 431 432
				.then((model: DefaultSettingsEditorModel) => {
					if (token.isCancellationRequested) {
						return void 0;
					}

					this.defaultSettingsEditorModel = model;
433
					this.settingsTreeModel = this.instantiationService.createInstance(SettingsTreeModel, this.viewState, tocData, this.defaultSettingsEditorModel.settingsGroups.slice(1));
434
					this.tocTree.setInput(this.settingsTreeModel.root);
435
					this.settingsTree.setInput(this.settingsTreeModel.root);
436
				});
R
Rob Lourens 已提交
437 438 439 440
		}
		return TPromise.as(null);
	}

441 442 443 444
	private toggleSearchMode(): void {
		DOM.toggleClass(this.getContainer(), 'search-mode', !!this.searchResultModel);
	}

R
Rob Lourens 已提交
445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464
	private refreshTreeAndMaintainFocus(): TPromise<any> {
		// Sort of a hack to maintain focus on the focused control across a refresh
		const focusedRowItem = DOM.findParentWithClass(<HTMLElement>document.activeElement, 'setting-item');
		const focusedRowId = focusedRowItem && focusedRowItem.id;
		const selection = focusedRowId && document.activeElement.tagName.toLowerCase() === 'input' ?
			(<HTMLInputElement>document.activeElement).selectionStart :
			null;

		return this.settingsTree.refresh().then(() => {
			if (focusedRowId) {
				const rowSelector = `.setting-item#${focusedRowId}`;
				const inputElementToFocus: HTMLElement = this.settingsTreeContainer.querySelector(`${rowSelector} input, ${rowSelector} select, ${rowSelector} a`);
				if (inputElementToFocus) {
					inputElementToFocus.focus();
					if (typeof selection === 'number') {
						(<HTMLInputElement>inputElementToFocus).setSelectionRange(selection, selection);
					}
				}
			}
		});
465 466
	}

467
	private onSearchInputChanged(): void {
R
Rob Lourens 已提交
468 469
		const query = this.searchWidget.getValue().trim();
		this.delayedFilterLogging.cancel();
470
		this.triggerSearch(query).then(() => {
471
			if (query && this.searchResultModel) {
472
				this.delayedFilterLogging.trigger(() => this.reportFilteringUsed(query, this.searchResultModel.getUniqueResults()));
473 474
			}
		});
R
Rob Lourens 已提交
475 476 477 478
	}

	private triggerSearch(query: string): TPromise<void> {
		if (query) {
R
Rob Lourens 已提交
479
			return this.searchInProgress = TPromise.join([
R
Rob Lourens 已提交
480
				this.localSearchDelayer.trigger(() => this.localFilterPreferences(query)),
481
				this.remoteSearchThrottle.trigger(() => this.remoteSearchPreferences(query), 500)
R
Rob Lourens 已提交
482 483 484
			]).then(() => {
				this.searchInProgress = null;
			});
R
Rob Lourens 已提交
485 486 487
		} else {
			this.localSearchDelayer.cancel();
			this.remoteSearchThrottle.cancel();
R
Rob Lourens 已提交
488 489 490
			if (this.searchInProgress && this.searchInProgress.cancel) {
				this.searchInProgress.cancel();
			}
R
Rob Lourens 已提交
491

492
			this.searchResultModel = null;
493
			this.toggleSearchMode();
494
			this.settingsTree.setInput(this.settingsTreeModel.root);
495

R
Rob Lourens 已提交
496 497 498 499
			return TPromise.wrap(null);
		}
	}

R
Rob Lourens 已提交
500 501 502 503 504 505
	private expandAll(tree: ITree): void {
		const nav = tree.getNavigator();
		let cur;
		while (cur = nav.next()) {
			tree.expand(cur);
		}
506 507
	}

508 509 510 511 512 513 514 515 516 517 518
	private reportFilteringUsed(query: string, results: ISearchResult[]): void {
		const nlpResult = results[SearchResultIdx.Remote];
		const nlpMetadata = nlpResult && nlpResult.metadata;

		const durations = {};
		durations['nlpResult'] = nlpMetadata && nlpMetadata.duration;

		// Count unique results
		const counts = {};
		const filterResult = results[SearchResultIdx.Local];
		counts['filterResult'] = filterResult.filterMatches.length;
519 520
		if (nlpResult) {
			counts['nlpResult'] = nlpResult.filterMatches.length;
521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540
		}

		const requestCount = nlpMetadata && nlpMetadata.requestCount;

		const data = {
			query,
			durations,
			counts,
			requestCount
		};

		/* __GDPR__
			"settingsEditor.filter" : {
				"query": { "classification": "CustomerContent", "purpose": "FeatureInsight" },
				"durations.nlpResult" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
				"counts.nlpResult" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
				"counts.filterResult" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
				"requestCount" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
			}
		*/
541
		this.telemetryService.publicLog('settingsEditor.filter', data);
542 543
	}

R
Rob Lourens 已提交
544
	private localFilterPreferences(query: string): TPromise<void> {
545 546
		const localSearchProvider = this.preferencesSearchService.getLocalSearchProvider(query);
		return this.filterOrSearchPreferences(query, SearchResultIdx.Local, localSearchProvider);
R
Rob Lourens 已提交
547 548 549
	}

	private remoteSearchPreferences(query: string): TPromise<void> {
550 551
		const remoteSearchProvider = this.preferencesSearchService.getRemoteSearchProvider(query);
		return this.filterOrSearchPreferences(query, SearchResultIdx.Remote, remoteSearchProvider);
R
Rob Lourens 已提交
552 553 554 555 556
	}

	private filterOrSearchPreferences(query: string, type: SearchResultIdx, searchProvider: ISearchProvider): TPromise<void> {
		const filterPs: TPromise<ISearchResult>[] = [this._filterOrSearchPreferencesModel(query, this.defaultSettingsEditorModel, searchProvider)];

R
Rob Lourens 已提交
557 558 559 560 561 562 563
		let isCanceled = false;
		return new TPromise(resolve => {
			return TPromise.join(filterPs).then(results => {
				if (isCanceled) {
					// Handle cancellation like this because cancellation is lost inside the search provider due to async/await
					return null;
				}
564

R
Rob Lourens 已提交
565 566 567
				const [result] = results;
				if (!this.searchResultModel) {
					this.searchResultModel = new SearchResultModel();
568
					this.toggleSearchMode();
R
Rob Lourens 已提交
569 570 571 572 573 574 575 576
					this.settingsTree.setInput(this.searchResultModel);
				}

				this.searchResultModel.setResult(type, result);
				resolve(this.refreshTreeAndMaintainFocus());
			});
		}, () => {
			isCanceled = true;
R
Rob Lourens 已提交
577 578 579 580 581 582 583 584 585 586 587
		});
	}

	private _filterOrSearchPreferencesModel(filter: string, model: ISettingsEditorModel, provider: ISearchProvider): TPromise<ISearchResult> {
		const searchP = provider ? provider.searchModel(model) : TPromise.wrap(null);
		return searchP
			.then<ISearchResult>(null, err => {
				if (isPromiseCanceledError(err)) {
					return TPromise.wrapError(err);
				} else {
					/* __GDPR__
588
						"settingsEditor.searchError" : {
R
Rob Lourens 已提交
589 590 591 592 593 594 595
							"message": { "classification": "CallstackOrException", "purpose": "FeatureInsight" },
							"filter": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
						}
					*/
					const message = getErrorMessage(err).trim();
					if (message && message !== 'Error') {
						// "Error" = any generic network error
596
						this.telemetryService.publicLog('settingsEditor.searchError', { message, filter });
R
Rob Lourens 已提交
597 598 599 600 601 602 603
						this.logService.info('Setting search error: ' + message);
					}
					return null;
				}
			});
	}

604 605
	private layoutSettingsList(dimension: DOM.Dimension): void {
		const listHeight = dimension.height - (DOM.getDomNodePagePosition(this.headerContainer).height + 12 /*padding*/);
606
		this.settingsTreeContainer.style.height = `${listHeight}px`;
R
Rob Lourens 已提交
607
		this.tocTreeContainer.style.height = `${listHeight}px`;
608
		this.settingsTree.layout(listHeight, 800);
R
Rob Lourens 已提交
609
		this.tocTree.layout(listHeight, 200);
610
	}
R
Rob Lourens 已提交
611
}