settingsEditor2.ts 26.3 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 34
import { ISettingsEditorViewState, SearchResultIdx, SearchResultModel, SettingsAccessibilityProvider, SettingsDataSource, SettingsRenderer, SettingsTreeController, SettingsTreeElement, SettingsTreeFilter, SettingsTreeModel } from 'vs/workbench/parts/preferences/browser/settingsTree';
import { getTOCElement, 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;
53
	private savedExpandedGroups: any[];
R
Rob Lourens 已提交
54

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

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

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

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

71
	private selectedElement: SettingsTreeElement;
72

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

165 166 167 168 169 170 171 172 173 174 175 176 177 178
		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()));

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

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

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

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

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

	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 已提交
213 214 215 216 217
	}

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

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

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

R
Rob Lourens 已提交
226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
	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,
				renderer
			},
			{
				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 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350

		// TODO@roblou - This is slow
		if (this.viewState.showConfiguredOnly) {
			this.savedExpandedGroups = this.settingsTree.getExpandedElements();
			const nav = this.settingsTree.getNavigator();
			let element;
			while (element = nav.next()) {
				this.settingsTree.expand(element);
			}
		} else if (this.savedExpandedGroups) {
			const nav = this.settingsTree.getNavigator();
			let element;
			while (element = nav.next()) {
				this.settingsTree.collapse(element);
			}

			this.settingsTree.expandAll(this.savedExpandedGroups);
			this.savedExpandedGroups = null;
		}
351 352
	}

R
Rob Lourens 已提交
353
	private onDidChangeSetting(key: string, value: any): void {
354 355
		if (this.pendingSettingUpdate && this.pendingSettingUpdate.key !== key) {
			this.updateChangedSetting(key, value);
356 357
		}

358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379
		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);
			});
380 381 382
	}

	private reportModifiedSetting(props: { key: string, query: string, searchResults: ISearchResult[], rawResults: ISearchResult[], showConfiguredOnly: boolean, isReset: boolean, settingsTarget: SettingsTarget }): void {
383
		this.pendingSettingUpdate = null;
384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400

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

401 402 403 404 405 406
			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;
				}
407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425
			}
		}

		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__
426
			"settingsEditor.settingModified" : {
427 428 429 430 431 432 433 434 435 436
				"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" }
			}
		*/
437
		this.telemetryService.publicLog('settingsEditor.settingModified', data);
R
Rob Lourens 已提交
438 439
	}

440
	private render(token: CancellationToken): TPromise<any> {
R
Rob Lourens 已提交
441 442
		if (this.input) {
			return this.input.resolve()
443 444 445 446 447 448
				.then((model: DefaultSettingsEditorModel) => {
					if (token.isCancellationRequested) {
						return void 0;
					}

					this.defaultSettingsEditorModel = model;
R
Rob Lourens 已提交
449
					// if (!this.settingsTree.getInput()) {
450
					this.tocTree.setInput(getTOCElement(tocData));
R
Rob Lourens 已提交
451

452 453 454 455
					this.settingsTreeModel = this.instantiationService.createInstance(SettingsTreeModel, this.viewState, tocData, this.defaultSettingsEditorModel.settingsGroups.slice(1));
					this.settingsTree.setInput(this.settingsTreeModel.root);
					this.expandAll(this.settingsTree);
					this.expandAll(this.tocTree);
R
Rob Lourens 已提交
456
					// }
457
				});
R
Rob Lourens 已提交
458 459 460 461
		}
		return TPromise.as(null);
	}

R
Rob Lourens 已提交
462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481
	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);
					}
				}
			}
		});
482 483
	}

484
	private onSearchInputChanged(): void {
R
Rob Lourens 已提交
485 486
		const query = this.searchWidget.getValue().trim();
		this.delayedFilterLogging.cancel();
487
		this.triggerSearch(query).then(() => {
488
			if (query && this.searchResultModel) {
489
				this.delayedFilterLogging.trigger(() => this.reportFilteringUsed(query, this.searchResultModel.getUniqueResults()));
490 491
			}
		});
R
Rob Lourens 已提交
492 493 494 495
	}

	private triggerSearch(query: string): TPromise<void> {
		if (query) {
R
Rob Lourens 已提交
496
			return this.searchInProgress = TPromise.join([
R
Rob Lourens 已提交
497
				this.localSearchDelayer.trigger(() => this.localFilterPreferences(query)),
498
				this.remoteSearchThrottle.trigger(() => this.remoteSearchPreferences(query), 500)
R
Rob Lourens 已提交
499 500 501
			]).then(() => {
				this.searchInProgress = null;
			});
R
Rob Lourens 已提交
502 503 504
		} else {
			this.localSearchDelayer.cancel();
			this.remoteSearchThrottle.cancel();
R
Rob Lourens 已提交
505 506 507
			if (this.searchInProgress && this.searchInProgress.cancel) {
				this.searchInProgress.cancel();
			}
R
Rob Lourens 已提交
508

509
			this.searchResultModel = null;
510
			this.settingsTree.setInput(this.settingsTreeModel.root);
R
Rob Lourens 已提交
511 512
			this.expandAll(this.settingsTree);
			this.expandAll(this.tocTree);
513

R
Rob Lourens 已提交
514 515 516 517
			return TPromise.wrap(null);
		}
	}

R
Rob Lourens 已提交
518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533
	private expandAll(tree: ITree): void {
		// const expandSubtree = (element: IResolvedTOCEntry) => {
		// 	const nav = tree.getNavigator();
		// 	while (nav.)
		// 	if (element.children) {
		// 		element.children.forEach(expandSubtree);
		// 	}
		// };

		// expandSubtree(this.resolvedTocData);

		const nav = tree.getNavigator();
		let cur;
		while (cur = nav.next()) {
			tree.expand(cur);
		}
534 535
	}

536 537 538 539 540 541 542 543 544 545 546
	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;
547 548
		if (nlpResult) {
			counts['nlpResult'] = nlpResult.filterMatches.length;
549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568
		}

		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 }
			}
		*/
569
		this.telemetryService.publicLog('settingsEditor.filter', data);
570 571
	}

R
Rob Lourens 已提交
572
	private localFilterPreferences(query: string): TPromise<void> {
573 574
		const localSearchProvider = this.preferencesSearchService.getLocalSearchProvider(query);
		return this.filterOrSearchPreferences(query, SearchResultIdx.Local, localSearchProvider);
R
Rob Lourens 已提交
575 576 577
	}

	private remoteSearchPreferences(query: string): TPromise<void> {
578 579
		const remoteSearchProvider = this.preferencesSearchService.getRemoteSearchProvider(query);
		return this.filterOrSearchPreferences(query, SearchResultIdx.Remote, remoteSearchProvider);
R
Rob Lourens 已提交
580 581 582 583 584
	}

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

R
Rob Lourens 已提交
585 586 587 588 589 590 591
		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;
				}
592

R
Rob Lourens 已提交
593 594 595 596 597 598 599 600 601 602 603
				const [result] = results;
				if (!this.searchResultModel) {
					this.searchResultModel = new SearchResultModel();
					this.settingsTree.setInput(this.searchResultModel);
				}

				this.searchResultModel.setResult(type, result);
				resolve(this.refreshTreeAndMaintainFocus());
			});
		}, () => {
			isCanceled = true;
R
Rob Lourens 已提交
604 605 606 607 608 609 610 611 612 613 614
		});
	}

	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__
615
						"settingsEditor.searchError" : {
R
Rob Lourens 已提交
616 617 618 619 620 621 622
							"message": { "classification": "CallstackOrException", "purpose": "FeatureInsight" },
							"filter": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
						}
					*/
					const message = getErrorMessage(err).trim();
					if (message && message !== 'Error') {
						// "Error" = any generic network error
623
						this.telemetryService.publicLog('settingsEditor.searchError', { message, filter });
R
Rob Lourens 已提交
624 625 626 627 628 629 630
						this.logService.info('Setting search error: ' + message);
					}
					return null;
				}
			});
	}

631 632
	private layoutSettingsList(dimension: DOM.Dimension): void {
		const listHeight = dimension.height - (DOM.getDomNodePagePosition(this.headerContainer).height + 12 /*padding*/);
633
		this.settingsTreeContainer.style.height = `${listHeight}px`;
R
Rob Lourens 已提交
634
		this.tocTreeContainer.style.height = `${listHeight}px`;
635
		this.settingsTree.layout(listHeight, 800);
R
Rob Lourens 已提交
636
		this.tocTree.layout(listHeight, 200);
637
	}
R
Rob Lourens 已提交
638
}