settingsEditor2.ts 32.7 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 9
import { ToolBar } from 'vs/base/browser/ui/toolbar/toolbar';
import { Action } from 'vs/base/common/actions';
10
import * as arrays from 'vs/base/common/arrays';
R
Rob Lourens 已提交
11
import { Delayer, ThrottledDelayer } from 'vs/base/common/async';
R
Rob Lourens 已提交
12
import { CancellationToken } from 'vs/base/common/cancellation';
R
Rob Lourens 已提交
13
import * as collections from 'vs/base/common/collections';
14
import { getErrorMessage, isPromiseCanceledError } from 'vs/base/common/errors';
R
Rob Lourens 已提交
15
import URI from 'vs/base/common/uri';
R
Rob Lourens 已提交
16
import { TPromise } from 'vs/base/common/winjs.base';
17
import { ITreeConfiguration } from 'vs/base/parts/tree/browser/tree';
18
import { OpenMode, DefaultTreestyler } from 'vs/base/parts/tree/browser/treeDefaults';
R
Rob Lourens 已提交
19 20
import 'vs/css!./media/settingsEditor2';
import { localize } from 'vs/nls';
R
Rob Lourens 已提交
21
import { ConfigurationTarget, IConfigurationOverrides, IConfigurationService } from 'vs/platform/configuration/common/configuration';
R
Rob Lourens 已提交
22
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
23
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
R
Rob Lourens 已提交
24
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
R
Rob Lourens 已提交
25
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
26
import { WorkbenchTree, WorkbenchTreeController } from 'vs/platform/list/browser/listService';
27
import { ILogService } from 'vs/platform/log/common/log';
R
Rob Lourens 已提交
28
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
29
import { attachButtonStyler, attachStyler } from 'vs/platform/theme/common/styler';
30
import { IThemeService } from 'vs/platform/theme/common/themeService';
R
Rob Lourens 已提交
31
import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor';
32
import { EditorOptions, IEditor } from 'vs/workbench/common/editor';
33
import { SearchWidget, SettingsTarget, SettingsTargetsWidget } from 'vs/workbench/parts/preferences/browser/preferencesWidgets';
R
Rob Lourens 已提交
34
import { commonlyUsedData, tocData } from 'vs/workbench/parts/preferences/browser/settingsLayout';
35
import { ISettingsEditorViewState, resolveExtensionsSettings, resolveSettingsTree, SearchResultIdx, SearchResultModel, SettingsRenderer, SettingsTree, SettingsTreeElement, SettingsTreeFilter, SettingsTreeGroupElement, SettingsTreeModel, SettingsTreeSettingElement, MODIFIED_SETTING_TAG, ONLINE_SERVICES_SETTING_TAG } from 'vs/workbench/parts/preferences/browser/settingsTree';
36
import { TOCDataSource, TOCRenderer, TOCTreeModel } from 'vs/workbench/parts/preferences/browser/tocTree';
37
import { CONTEXT_SETTINGS_EDITOR, CONTEXT_SETTINGS_FIRST_ROW_FOCUS, CONTEXT_SETTINGS_ROW_FOCUS, CONTEXT_SETTINGS_SEARCH_FOCUS, CONTEXT_TOC_ROW_FOCUS, IPreferencesSearchService, ISearchProvider } from 'vs/workbench/parts/preferences/common/preferences';
38
import { IPreferencesService, ISearchResult, ISettingsEditorModel } from 'vs/workbench/services/preferences/common/preferences';
39
import { SettingsEditor2Input } from 'vs/workbench/services/preferences/common/preferencesEditorInput';
R
Rob Lourens 已提交
40
import { DefaultSettingsEditorModel } from 'vs/workbench/services/preferences/common/preferencesModels';
41
import { editorBackground, foreground } from 'vs/platform/theme/common/colorRegistry';
42
import { settingsHeaderForeground } from 'vs/workbench/parts/preferences/browser/settingsWidgets';
43
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
44
import { PreferencesEditor } from 'vs/workbench/parts/preferences/browser/preferencesEditor';
R
Rob Lourens 已提交
45

46 47
const $ = DOM.$;

R
Rob Lourens 已提交
48 49 50 51 52 53
export class SettingsEditor2 extends BaseEditor {

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

	private defaultSettingsEditorModel: DefaultSettingsEditorModel;

54
	private rootElement: HTMLElement;
R
Rob Lourens 已提交
55 56 57
	private headerContainer: HTMLElement;
	private searchWidget: SearchWidget;
	private settingsTargetsWidget: SettingsTargetsWidget;
58
	private toolbar: ToolBar;
R
Rob Lourens 已提交
59

60 61
	private settingsTreeContainer: HTMLElement;
	private settingsTree: WorkbenchTree;
62
	private tocTreeModel: TOCTreeModel;
63
	private settingsTreeModel: SettingsTreeModel;
R
Rob Lourens 已提交
64

R
Rob Lourens 已提交
65 66 67
	private tocTreeContainer: HTMLElement;
	private tocTree: WorkbenchTree;

R
Rob Lourens 已提交
68 69 70
	private delayedFilterLogging: Delayer<void>;
	private localSearchDelayer: Delayer<void>;
	private remoteSearchThrottle: ThrottledDelayer<void>;
R
Rob Lourens 已提交
71
	private searchInProgress: TPromise<void>;
R
Rob Lourens 已提交
72

73 74
	private settingUpdateDelayer: Delayer<void>;
	private pendingSettingUpdate: { key: string, value: any };
R
Rob Lourens 已提交
75

76
	private selectedElement: SettingsTreeElement;
77

78
	private viewState: ISettingsEditorViewState;
79
	private searchResultModel: SearchResultModel;
80 81

	private firstRowFocused: IContextKey<boolean>;
82
	private rowFocused: IContextKey<boolean>;
83
	private tocRowFocused: IContextKey<boolean>;
84 85
	private inSettingsEditorContextKey: IContextKey<boolean>;
	private searchFocusContextKey: IContextKey<boolean>;
86

87 88
	private tagRegex = /(^|\s)@tag:("([^"]*)"|[^"]\S*)/g;

89 90 91
	/** Don't spam warnings */
	private hasWarnedMissingSettings: boolean;

R
Rob Lourens 已提交
92 93 94 95 96
	constructor(
		@ITelemetryService telemetryService: ITelemetryService,
		@IConfigurationService private configurationService: IConfigurationService,
		@IThemeService themeService: IThemeService,
		@IPreferencesService private preferencesService: IPreferencesService,
R
Rob Lourens 已提交
97 98
		@IInstantiationService private instantiationService: IInstantiationService,
		@IPreferencesSearchService private preferencesSearchService: IPreferencesSearchService,
99
		@ILogService private logService: ILogService,
100
		@IEnvironmentService private environmentService: IEnvironmentService,
101 102
		@IContextKeyService contextKeyService: IContextKeyService,
		@IContextMenuService private contextMenuService: IContextMenuService
R
Rob Lourens 已提交
103 104
	) {
		super(SettingsEditor2.ID, telemetryService, themeService);
R
Rob Lourens 已提交
105 106 107
		this.delayedFilterLogging = new Delayer<void>(1000);
		this.localSearchDelayer = new Delayer(100);
		this.remoteSearchThrottle = new ThrottledDelayer(200);
108
		this.viewState = { settingsTarget: ConfigurationTarget.USER };
R
Rob Lourens 已提交
109

110 111
		this.settingUpdateDelayer = new Delayer<void>(500);

112 113
		this.inSettingsEditorContextKey = CONTEXT_SETTINGS_EDITOR.bindTo(contextKeyService);
		this.searchFocusContextKey = CONTEXT_SETTINGS_SEARCH_FOCUS.bindTo(contextKeyService);
114
		this.firstRowFocused = CONTEXT_SETTINGS_FIRST_ROW_FOCUS.bindTo(contextKeyService);
115
		this.rowFocused = CONTEXT_SETTINGS_ROW_FOCUS.bindTo(contextKeyService);
116
		this.tocRowFocused = CONTEXT_TOC_ROW_FOCUS.bindTo(contextKeyService);
117

118 119 120 121 122 123 124
		this._register(configurationService.onDidChangeConfiguration(e => {
			this.onConfigUpdate();

			if (e.affectsConfiguration('workbench.settings.tocVisible')) {
				this.updateTOCVisible();
			}
		}));
R
Rob Lourens 已提交
125 126 127
	}

	createEditor(parent: HTMLElement): void {
128
		parent.setAttribute('tabindex', '-1');
129
		this.rootElement = DOM.append(parent, $('.settings-editor'));
R
Rob Lourens 已提交
130

131 132
		this.createHeader(this.rootElement);
		this.createBody(this.rootElement);
R
Rob Lourens 已提交
133 134
	}

135
	setInput(input: SettingsEditor2Input, options: EditorOptions, token: CancellationToken): Thenable<void> {
136
		this.inSettingsEditorContextKey.set(true);
137
		return super.setInput(input, options, token)
R
Rob Lourens 已提交
138
			.then(() => {
139
				this.render(token);
R
Rob Lourens 已提交
140 141 142
			});
	}

143 144 145 146 147
	clearInput(): void {
		this.inSettingsEditorContextKey.set(false);
		super.clearInput();
	}

R
Rob Lourens 已提交
148 149
	layout(dimension: DOM.Dimension): void {
		this.searchWidget.layout(dimension);
150 151 152
		this.layoutTrees(dimension);

		DOM.toggleClass(this.rootElement, 'narrow', dimension.width < 600);
R
Rob Lourens 已提交
153 154 155
	}

	focus(): void {
156 157 158
		this.focusSearch();
	}

159 160 161 162 163 164 165 166 167 168 169
	focusSettings(): void {
		const selection = this.settingsTree.getSelection();
		if (selection && selection[0]) {
			this.settingsTree.setFocus(selection[0]);
		} else {
			this.settingsTree.focusFirst();
		}

		this.settingsTree.domFocus();
	}

170
	focusSearch(): void {
R
Rob Lourens 已提交
171 172 173
		this.searchWidget.focus();
	}

174 175 176 177 178 179 180 181
	editSelectedSetting(): void {
		const focus = this.settingsTree.getFocus();
		if (focus instanceof SettingsTreeSettingElement) {
			const itemId = focus.id.replace(/\./g, '_');
			this.focusEditControlForRow(itemId);
		}
	}

182 183 184 185
	clearSearchResults(): void {
		this.searchWidget.clear();
	}

186
	search(text: string): void {
187
		if (this.searchWidget) {
188
			this.searchWidget.focus();
189
			this.searchWidget.setValue(text);
190 191 192
		}
	}

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

196
		const previewHeader = DOM.append(this.headerContainer, $('.settings-preview-header'));
R
Rob Lourens 已提交
197 198 199 200 201

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

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

R
Rob Lourens 已提交
204 205 206
		const searchContainer = DOM.append(this.headerContainer, $('.search-container'));
		this.searchWidget = this._register(this.instantiationService.createInstance(SearchWidget, searchContainer, {
			ariaLabel: localize('SearchSettings.AriaLabel', "Search settings"),
207
			placeholder: localize('SearchSettings.Placeholder', "Search settings"),
S
Sandeep Somavarapu 已提交
208 209
			focusKey: this.searchFocusContextKey,
			ariaLive: 'assertive'
R
Rob Lourens 已提交
210
		}));
211
		this._register(this.searchWidget.onDidChange(() => this.onSearchInputChanged()));
R
Rob Lourens 已提交
212

213
		const headerControlsContainer = DOM.append(this.headerContainer, $('.settings-header-controls'));
R
Rob Lourens 已提交
214 215 216
		const targetWidgetContainer = DOM.append(headerControlsContainer, $('.settings-target-container'));
		this.settingsTargetsWidget = this._register(this.instantiationService.createInstance(SettingsTargetsWidget, targetWidgetContainer));
		this.settingsTargetsWidget.settingsTarget = ConfigurationTarget.USER;
217 218
		this.settingsTargetsWidget.onDidTargetChange(() => {
			this.viewState.settingsTarget = this.settingsTargetsWidget.settingsTarget;
219
			this.toolbar.context = <ISettingsToolbarContext>{ target: this.settingsTargetsWidget.settingsTarget };
220 221 222

			this.settingsTreeModel.update();
			this.refreshTreeAndMaintainFocus();
223
		});
R
Rob Lourens 已提交
224

225
		this.createHeaderControls(headerControlsContainer);
R
Rob Lourens 已提交
226 227
	}

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

231 232 233 234
		this.toolbar = new ToolBar(headerControlsContainerRight, this.contextMenuService, {
			ariaLabel: localize('settingsToolbarLabel', "Settings Editor Actions"),
			actionRunner: this.actionRunner
		});
235

236
		const actions = [
237
			this.instantiationService.createInstance(FilterByTagAction,
238
				localize('filterModifiedLabel', "Show modified settings"),
239
				MODIFIED_SETTING_TAG,
240
				this),
241
			this.instantiationService.createInstance(
242
				FilterByTagAction,
243 244
				localize('filterOnlineServicesLabel', "Show settings for online services"),
				ONLINE_SERVICES_SETTING_TAG,
245 246
				this),
			new Separator(),
247 248 249
			this.instantiationService.createInstance(OpenSettingsAction)
		];
		this.toolbar.setActions([], actions)();
250
		this.toolbar.context = <ISettingsToolbarContext>{ target: this.settingsTargetsWidget.settingsTarget };
251 252
	}

253 254 255 256 257
	private revealSetting(settingName: string): void {
		const element = this.settingsTreeModel.getElementByName(settingName);
		if (element) {
			this.settingsTree.setSelection([element]);
			this.settingsTree.setFocus(element);
258 259
			this.settingsTree.reveal(element, 0);
			this.settingsTree.domFocus();
260 261 262
		}
	}

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

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

278
		this.createTOC(bodyContainer);
R
Rob Lourens 已提交
279
		this.createSettingsTree(bodyContainer);
280 281 282 283

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

R
Rob Lourens 已提交
286 287 288
	private createTOC(parent: HTMLElement): void {
		this.tocTreeContainer = DOM.append(parent, $('.settings-toc-container'));

289 290 291
		const tocDataSource = this.instantiationService.createInstance(TOCDataSource);
		const tocRenderer = this.instantiationService.createInstance(TOCRenderer);
		this.tocTreeModel = new TOCTreeModel();
R
Rob Lourens 已提交
292 293 294

		this.tocTree = this.instantiationService.createInstance(WorkbenchTree, this.tocTreeContainer,
			<ITreeConfiguration>{
295 296
				dataSource: tocDataSource,
				renderer: tocRenderer,
297
				controller: this.instantiationService.createInstance(WorkbenchTreeController, { openMode: OpenMode.DOUBLE_CLICK }),
298 299
				filter: this.instantiationService.createInstance(SettingsTreeFilter, this.viewState),
				styler: new DefaultTreestyler(DOM.createStyleSheet(), 'settings-toc-tree'),
R
Rob Lourens 已提交
300 301
			},
			{
302 303
				showLoading: false,
				twistiePixels: 15
R
Rob Lourens 已提交
304
			});
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320
		this.tocTree.getHTMLElement().classList.add('settings-toc-tree');

		this._register(attachStyler(this.themeService, {
			listActiveSelectionBackground: editorBackground,
			listActiveSelectionForeground: settingsHeaderForeground,
			listFocusAndSelectionBackground: editorBackground,
			listFocusAndSelectionForeground: settingsHeaderForeground,
			listFocusBackground: editorBackground,
			listFocusForeground: settingsHeaderForeground,
			listHoverForeground: foreground,
			listHoverBackground: editorBackground,
			listInactiveSelectionBackground: editorBackground,
			listInactiveSelectionForeground: settingsHeaderForeground,
		}, colors => {
			this.tocTree.style(colors);
		}));
R
Rob Lourens 已提交
321

322 323
		this._register(this.tocTree.onDidChangeFocus(e => {
			const element = e.focus;
324 325 326 327
			if (this.searchResultModel) {
				this.viewState.filterToCategory = element;
				this.refreshTreeAndMaintainFocus();
			} else if (this.settingsTreeModel) {
R
Rob Lourens 已提交
328
				if (element && !e.payload.fromScroll) {
329 330 331 332
					this.settingsTree.reveal(element, 0);
					this.settingsTree.setSelection([element]);
					this.settingsTree.setFocus(element);
				}
333
			}
334 335 336 337 338 339 340 341
		}));

		this._register(this.tocTree.onDidFocus(() => {
			this.tocRowFocused.set(true);
		}));

		this._register(this.tocTree.onDidBlur(() => {
			this.tocRowFocused.set(false);
R
Rob Lourens 已提交
342
		}));
343 344 345 346 347 348 349

		this.updateTOCVisible();
	}

	private updateTOCVisible(): void {
		const visible = !!this.configurationService.getValue('workbench.settings.tocVisible');
		DOM.toggleClass(this.tocTreeContainer, 'hidden', !visible);
R
Rob Lourens 已提交
350 351 352
	}

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

355
		const renderer = this.instantiationService.createInstance(SettingsRenderer, this.settingsTreeContainer);
356
		this._register(renderer.onDidChangeSetting(e => this.onDidChangeSetting(e.key, e.value)));
357 358 359 360 361 362 363
		this._register(renderer.onDidOpenSettings(settingKey => {
			this.openSettingsFile().then(editor => {
				if (editor instanceof PreferencesEditor && settingKey) {
					editor.focusSearch(settingKey);
				}
			});
		}));
364
		this._register(renderer.onDidClickSettingLink(settingName => this.revealSetting(settingName)));
365

366 367 368
		this.settingsTree = this.instantiationService.createInstance(SettingsTree,
			this.settingsTreeContainer,
			this.viewState,
369
			{
370
				renderer
371
			});
372

373
		this._register(this.settingsTree.onDidChangeFocus(e => {
374
			this.settingsTree.setSelection([e.focus]);
375 376
			if (this.selectedElement) {
				this.settingsTree.refresh(this.selectedElement);
377
			}
378

379 380
			if (e.focus) {
				this.settingsTree.refresh(e.focus);
381
			}
382 383

			this.selectedElement = e.focus;
384 385
		}));

386 387 388 389 390
		this._register(this.settingsTree.onDidBlur(() => {
			this.rowFocused.set(false);
			this.firstRowFocused.set(false);
		}));

391
		this._register(this.settingsTree.onDidChangeSelection(e => {
392
			this.updateTreeScrollSync();
393 394

			let firstRowFocused = false;
395
			let rowFocused = false;
396 397
			const selection: SettingsTreeElement = e.selection[0];
			if (selection) {
398
				rowFocused = true;
399 400 401 402 403 404 405 406
				if (this.searchResultModel) {
					firstRowFocused = selection.id === this.searchResultModel.getChildren()[0].id;
				} else {
					const firstRowId = this.settingsTreeModel.root.children[0] && this.settingsTreeModel.root.children[0].id;
					firstRowFocused = selection.id === firstRowId;
				}
			}

407
			this.rowFocused.set(rowFocused);
408
			this.firstRowFocused.set(firstRowFocused);
409
		}));
410

411 412
		this._register(this.settingsTree.onDidScroll(() => {
			this.updateTreeScrollSync();
413
		}));
414 415
	}

416 417 418 419 420 421 422 423 424 425 426 427
	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');
		}));
	}

R
Rob Lourens 已提交
428
	private onDidChangeSetting(key: string, value: any): void {
429 430
		if (this.pendingSettingUpdate && this.pendingSettingUpdate.key !== key) {
			this.updateChangedSetting(key, value);
431 432
		}

433 434 435 436
		this.pendingSettingUpdate = { key, value };
		this.settingUpdateDelayer.trigger(() => this.updateChangedSetting(key, value));
	}

437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466
	private updateTreeScrollSync(): void {
		if (this.searchResultModel) {
			return;
		}

		if (!this.tocTree.getInput()) {
			return;
		}

		let elementToSync = this.settingsTree.getFirstVisibleElement();
		const selection = this.settingsTree.getSelection()[0];
		if (selection) {
			const selectionPos = this.settingsTree.getRelativeTop(selection);
			if (selectionPos >= 0 && selectionPos <= 1) {
				elementToSync = selection;
			}
		}

		const element = elementToSync instanceof SettingsTreeSettingElement ? elementToSync.parent :
			elementToSync instanceof SettingsTreeGroupElement ? elementToSync :
				null;

		if (element && this.tocTree.getSelection()[0] !== element) {
			const elementTop = this.tocTree.getRelativeTop(element);
			if (elementTop < 0) {
				this.tocTree.reveal(element, 0);
			} else if (elementTop > 1) {
				this.tocTree.reveal(element, 1);
			}

467 468
			this.tocTree.setSelection([element]);
			this.tocTree.setFocus(element, { fromScroll: true });
469 470 471
		}
	}

472 473 474
	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
R
Rob Lourens 已提交
475 476 477 478 479 480 481 482 483 484 485 486
		const settingsTarget = this.settingsTargetsWidget.settingsTarget;
		const resource = URI.isUri(settingsTarget) ? settingsTarget : undefined;
		const configurationTarget = <ConfigurationTarget>(resource ? undefined : settingsTarget);
		const overrides: IConfigurationOverrides = { resource };

		// If the user is changing the value back to the default, do a 'reset' instead
		const inspected = this.configurationService.inspect(key, overrides);
		if (inspected.default === value) {
			value = undefined;
		}

		return this.configurationService.updateValue(key, value, overrides, configurationTarget)
487 488 489 490 491 492 493
			.then(() => this.refreshTreeAndMaintainFocus())
			.then(() => {
				const reportModifiedProps = {
					key,
					query: this.searchWidget.getValue(),
					searchResults: this.searchResultModel && this.searchResultModel.getUniqueResults(),
					rawResults: this.searchResultModel && this.searchResultModel.getRawResults(),
494
					showConfiguredOnly: this.viewState.tagFilters && this.viewState.tagFilters.has(MODIFIED_SETTING_TAG),
495 496 497 498 499 500
					isReset: typeof value === 'undefined',
					settingsTarget: this.settingsTargetsWidget.settingsTarget as SettingsTarget
				};

				return this.reportModifiedSetting(reportModifiedProps);
			});
501 502 503
	}

	private reportModifiedSetting(props: { key: string, query: string, searchResults: ISearchResult[], rawResults: ISearchResult[], showConfiguredOnly: boolean, isReset: boolean, settingsTarget: SettingsTarget }): void {
504
		this.pendingSettingUpdate = null;
505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521

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

522 523 524 525 526 527
			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;
				}
528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546
			}
		}

		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__
547
			"settingsEditor.settingModified" : {
548 549 550 551 552 553 554 555 556 557
				"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" }
			}
		*/
558
		this.telemetryService.publicLog('settingsEditor.settingModified', data);
R
Rob Lourens 已提交
559 560
	}

561
	private render(token: CancellationToken): TPromise<any> {
R
Rob Lourens 已提交
562 563
		if (this.input) {
			return this.input.resolve()
564 565 566 567 568 569
				.then((model: DefaultSettingsEditorModel) => {
					if (token.isCancellationRequested) {
						return void 0;
					}

					this.defaultSettingsEditorModel = model;
570
					this.onConfigUpdate();
571
				});
R
Rob Lourens 已提交
572 573 574 575
		}
		return TPromise.as(null);
	}

576
	private toggleSearchMode(): void {
577 578 579 580
		DOM.removeClass(this.rootElement, 'search-mode');
		if (this.configurationService.getValue('workbench.settings.settingsSearchTocBehavior') === 'hide') {
			DOM.toggleClass(this.rootElement, 'search-mode', !!this.searchResultModel);
		}
581 582
	}

583
	private onConfigUpdate(): TPromise<void> {
584
		const groups = this.defaultSettingsEditorModel.settingsGroups.slice(1); // Without commonlyUsed
585
		const dividedGroups = collections.groupBy(groups, g => g.contributedByExtension ? 'extension' : 'core');
586 587 588 589 590 591 592 593 594 595 596 597 598 599
		const settingsResult = resolveSettingsTree(tocData, dividedGroups.core);
		const resolvedSettingsRoot = settingsResult.tree;

		// Warn for settings not included in layout
		if (settingsResult.leftoverSettings.size && !this.hasWarnedMissingSettings) {
			let settingKeyList = [];
			settingsResult.leftoverSettings.forEach(s => {
				settingKeyList.push(s.key);
			});

			this.logService.warn(`SettingsEditor2: Settings not included in settingsLayout.ts: ${settingKeyList.join(', ')}`);
			this.hasWarnedMissingSettings = true;
		}

600
		const commonlyUsed = resolveSettingsTree(commonlyUsedData, dividedGroups.core);
601
		resolvedSettingsRoot.children.unshift(commonlyUsed.tree);
602

603 604
		resolvedSettingsRoot.children.push(resolveExtensionsSettings(dividedGroups.extension || []));

605 606 607 608
		if (this.searchResultModel) {
			this.searchResultModel.updateChildren();
		}

609 610 611 612
		if (this.settingsTreeModel) {
			this.settingsTreeModel.update(resolvedSettingsRoot);
		} else {
			this.settingsTreeModel = this.instantiationService.createInstance(SettingsTreeModel, this.viewState, resolvedSettingsRoot);
613
			this.settingsTree.setInput(this.settingsTreeModel.root);
614 615 616 617 618 619 620

			this.tocTreeModel.settingsTreeRoot = this.settingsTreeModel.root as SettingsTreeGroupElement;
			if (this.tocTree.getInput()) {
				this.tocTree.refresh();
			} else {
				this.tocTree.setInput(this.tocTreeModel);
			}
621 622 623 624 625
		}

		return this.refreshTreeAndMaintainFocus();
	}

R
Rob Lourens 已提交
626 627 628 629 630 631 632 633
	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;

634 635 636
		return this.settingsTree.refresh()
			.then(() => {
				if (focusedRowId) {
637
					this.focusEditControlForRow(focusedRowId, selection);
R
Rob Lourens 已提交
638
				}
639 640 641 642
			})
			.then(() => {
				return this.tocTree.refresh();
			});
643 644
	}

645 646 647 648 649 650 651 652 653 654 655
	private focusEditControlForRow(id: string, selection?: number): void {
		const rowSelector = `.setting-item#${id}`;
		const inputElementToFocus: HTMLElement = this.settingsTreeContainer.querySelector(`${rowSelector} input, ${rowSelector} select, ${rowSelector} a, ${rowSelector} .monaco-custom-checkbox`);
		if (inputElementToFocus) {
			inputElementToFocus.focus();
			if (typeof selection === 'number') {
				(<HTMLInputElement>inputElementToFocus).setSelectionRange(selection, selection);
			}
		}
	}

656
	private onSearchInputChanged(): void {
R
Rob Lourens 已提交
657 658
		const query = this.searchWidget.getValue().trim();
		this.delayedFilterLogging.cancel();
659
		this.triggerSearch(query).then(() => {
660
			if (query && this.searchResultModel) {
661
				this.delayedFilterLogging.trigger(() => this.reportFilteringUsed(query, this.searchResultModel.getUniqueResults()));
662 663
			}
		});
R
Rob Lourens 已提交
664 665 666
	}

	private triggerSearch(query: string): TPromise<void> {
667 668
		this.viewState.tagFilters = new Set<string>();
		if (query) {
669 670 671 672 673 674 675 676
			query = query.replace(this.tagRegex, (_, __, quotedTag, tag) => {
				this.viewState.tagFilters.add(tag || quotedTag);
				return '';
			});
			query = query.replace(`@${MODIFIED_SETTING_TAG}`, () => {
				this.viewState.tagFilters.add(MODIFIED_SETTING_TAG);
				return '';
			});
677
		}
678
		query = query.trim();
R
Rob Lourens 已提交
679
		if (query) {
R
Rob Lourens 已提交
680
			return this.searchInProgress = TPromise.join([
R
Rob Lourens 已提交
681
				this.localSearchDelayer.trigger(() => this.localFilterPreferences(query)),
682
				this.remoteSearchThrottle.trigger(() => this.remoteSearchPreferences(query), 500)
R
Rob Lourens 已提交
683 684 685
			]).then(() => {
				this.searchInProgress = null;
			});
R
Rob Lourens 已提交
686 687 688
		} else {
			this.localSearchDelayer.cancel();
			this.remoteSearchThrottle.cancel();
R
Rob Lourens 已提交
689 690 691
			if (this.searchInProgress && this.searchInProgress.cancel) {
				this.searchInProgress.cancel();
			}
R
Rob Lourens 已提交
692

693
			this.searchResultModel = null;
694
			this.tocTreeModel.currentSearchModel = null;
695
			this.viewState.filterToCategory = null;
696
			this.tocTree.refresh();
697
			this.toggleSearchMode();
698
			this.settingsTree.setInput(this.settingsTreeModel.root);
699

R
Rob Lourens 已提交
700 701 702 703
			return TPromise.wrap(null);
		}
	}

704 705 706 707 708 709 710 711 712 713
	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];
714 715 716 717
		if (filterResult) {
			counts['filterResult'] = filterResult.filterMatches.length;
		}

718 719
		if (nlpResult) {
			counts['nlpResult'] = nlpResult.filterMatches.length;
720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739
		}

		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 }
			}
		*/
740
		this.telemetryService.publicLog('settingsEditor.filter', data);
741 742
	}

R
Rob Lourens 已提交
743
	private localFilterPreferences(query: string): TPromise<void> {
744 745
		const localSearchProvider = this.preferencesSearchService.getLocalSearchProvider(query);
		return this.filterOrSearchPreferences(query, SearchResultIdx.Local, localSearchProvider);
R
Rob Lourens 已提交
746 747 748
	}

	private remoteSearchPreferences(query: string): TPromise<void> {
749 750
		const remoteSearchProvider = this.preferencesSearchService.getRemoteSearchProvider(query);
		return this.filterOrSearchPreferences(query, SearchResultIdx.Remote, remoteSearchProvider);
R
Rob Lourens 已提交
751 752 753
	}

	private filterOrSearchPreferences(query: string, type: SearchResultIdx, searchProvider: ISearchProvider): TPromise<void> {
R
Rob Lourens 已提交
754 755
		let isCanceled = false;
		return new TPromise(resolve => {
756
			return this._filterOrSearchPreferencesModel(query, this.defaultSettingsEditorModel, searchProvider).then(result => {
R
Rob Lourens 已提交
757 758 759 760
				if (isCanceled) {
					// Handle cancellation like this because cancellation is lost inside the search provider due to async/await
					return null;
				}
761

R
Rob Lourens 已提交
762
				if (!this.searchResultModel) {
763
					this.searchResultModel = this.instantiationService.createInstance(SearchResultModel, this.viewState);
764
					this.searchResultModel.setResult(type, result);
765
					this.tocTreeModel.currentSearchModel = this.searchResultModel;
766
					this.toggleSearchMode();
R
Rob Lourens 已提交
767
					this.settingsTree.setInput(this.searchResultModel);
768 769
				} else {
					this.searchResultModel.setResult(type, result);
R
Rob Lourens 已提交
770 771
				}

772
				this.tocTreeModel.update();
R
Rob Lourens 已提交
773 774 775 776
				resolve(this.refreshTreeAndMaintainFocus());
			});
		}, () => {
			isCanceled = true;
R
Rob Lourens 已提交
777 778 779 780 781 782 783 784 785 786 787
		});
	}

	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__
788
						"settingsEditor.searchError" : {
R
Rob Lourens 已提交
789 790 791 792 793 794 795
							"message": { "classification": "CallstackOrException", "purpose": "FeatureInsight" },
							"filter": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
						}
					*/
					const message = getErrorMessage(err).trim();
					if (message && message !== 'Error') {
						// "Error" = any generic network error
796
						this.telemetryService.publicLog('settingsEditor.searchError', { message, filter });
R
Rob Lourens 已提交
797 798 799 800 801 802 803
						this.logService.info('Setting search error: ' + message);
					}
					return null;
				}
			});
	}

804
	private layoutTrees(dimension: DOM.Dimension): void {
805
		const listHeight = dimension.height - (DOM.getDomNodePagePosition(this.headerContainer).height + 11 /*padding*/);
806 807 808
		const settingsTreeHeight = listHeight - 14;
		this.settingsTreeContainer.style.height = `${settingsTreeHeight}px`;
		this.settingsTree.layout(settingsTreeHeight, 800);
809

810 811 812 813 814
		const selectedSetting = this.settingsTree.getSelection()[0];
		if (selectedSetting) {
			this.settingsTree.refresh(selectedSetting);
		}

815 816 817
		const tocTreeHeight = listHeight - 16;
		this.tocTreeContainer.style.height = `${tocTreeHeight}px`;
		this.tocTree.layout(tocTreeHeight, 175);
818
	}
R
Rob Lourens 已提交
819
}
820

821 822 823 824
interface ISettingsToolbarContext {
	target: SettingsTarget;
}

825 826
class OpenSettingsAction extends Action {
	static readonly ID = 'settings.openSettingsJson';
827
	static readonly LABEL = localize('openSettingsJsonLabel', "Open settings.json");
828 829 830 831 832 833 834 835

	constructor(
		@IPreferencesService private readonly preferencesService: IPreferencesService,
	) {
		super(OpenSettingsAction.ID, OpenSettingsAction.LABEL, 'open-settings-json');
	}


836
	run(context?: ISettingsToolbarContext): TPromise<void> {
837 838 839 840
		return this._run(context)
			.then(() => { });
	}

841 842 843
	private _run(context?: ISettingsToolbarContext): TPromise<any> {
		const target = context && context.target;
		if (target === ConfigurationTarget.USER) {
844
			return this.preferencesService.openGlobalSettings();
845
		} else if (target === ConfigurationTarget.WORKSPACE) {
846
			return this.preferencesService.openWorkspaceSettings();
847 848
		} else if (URI.isUri(target)) {
			return this.preferencesService.openFolderSettings(target);
849 850 851 852 853 854
		}

		return TPromise.wrap(null);
	}
}

855 856
class FilterByTagAction extends Action {
	static readonly ID = 'settings.filterByTag';
857 858 859 860

	constructor(
		label: string,
		private tag: string,
861
		private settingsEditor: SettingsEditor2
862
	) {
863
		super(FilterByTagAction.ID, label, 'toggle-filter-tag');
864 865 866
	}

	run(): TPromise<void> {
867
		this.settingsEditor.search(this.tag === MODIFIED_SETTING_TAG ? `@${this.tag} ` : `@tag:${this.tag} `);
868
		return TPromise.as(null);
869 870
	}
}