settingsEditor2.ts 33.0 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
	private layoutDelayer: Delayer<void>;

91 92 93
	/** Don't spam warnings */
	private hasWarnedMissingSettings: boolean;

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

113 114
		this.settingUpdateDelayer = new Delayer<void>(500);

115 116
		this.inSettingsEditorContextKey = CONTEXT_SETTINGS_EDITOR.bindTo(contextKeyService);
		this.searchFocusContextKey = CONTEXT_SETTINGS_SEARCH_FOCUS.bindTo(contextKeyService);
117
		this.firstRowFocused = CONTEXT_SETTINGS_FIRST_ROW_FOCUS.bindTo(contextKeyService);
118
		this.rowFocused = CONTEXT_SETTINGS_ROW_FOCUS.bindTo(contextKeyService);
119
		this.tocRowFocused = CONTEXT_TOC_ROW_FOCUS.bindTo(contextKeyService);
120

121 122 123 124 125 126 127
		this._register(configurationService.onDidChangeConfiguration(e => {
			this.onConfigUpdate();

			if (e.affectsConfiguration('workbench.settings.tocVisible')) {
				this.updateTOCVisible();
			}
		}));
R
Rob Lourens 已提交
128 129 130
	}

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

134 135
		this.createHeader(this.rootElement);
		this.createBody(this.rootElement);
R
Rob Lourens 已提交
136 137
	}

138
	setInput(input: SettingsEditor2Input, options: EditorOptions, token: CancellationToken): Thenable<void> {
139
		this.inSettingsEditorContextKey.set(true);
140
		return super.setInput(input, options, token)
R
Rob Lourens 已提交
141
			.then(() => {
142 143
				return this.render(token);
			}).then(() => new Promise(process.nextTick)); // Force setInput to be async
R
Rob Lourens 已提交
144 145
	}

146 147 148 149 150
	clearInput(): void {
		this.inSettingsEditorContextKey.set(false);
		super.clearInput();
	}

R
Rob Lourens 已提交
151 152
	layout(dimension: DOM.Dimension): void {
		this.searchWidget.layout(dimension);
153 154 155
		this.layoutTrees(dimension);

		DOM.toggleClass(this.rootElement, 'narrow', dimension.width < 600);
156 157

		this.layoutDelayer.trigger(() => this.refreshTreeAndMaintainFocus());
R
Rob Lourens 已提交
158 159 160
	}

	focus(): void {
161 162 163
		this.focusSearch();
	}

164 165 166 167 168 169 170 171 172 173 174
	focusSettings(): void {
		const selection = this.settingsTree.getSelection();
		if (selection && selection[0]) {
			this.settingsTree.setFocus(selection[0]);
		} else {
			this.settingsTree.focusFirst();
		}

		this.settingsTree.domFocus();
	}

175
	focusSearch(): void {
R
Rob Lourens 已提交
176 177 178
		this.searchWidget.focus();
	}

179 180 181 182 183 184 185 186
	editSelectedSetting(): void {
		const focus = this.settingsTree.getFocus();
		if (focus instanceof SettingsTreeSettingElement) {
			const itemId = focus.id.replace(/\./g, '_');
			this.focusEditControlForRow(itemId);
		}
	}

187 188 189 190
	clearSearchResults(): void {
		this.searchWidget.clear();
	}

191
	search(text: string): void {
192
		if (this.searchWidget) {
193
			this.searchWidget.focus();
194
			this.searchWidget.setValue(text);
195 196 197
		}
	}

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

201
		const previewHeader = DOM.append(this.headerContainer, $('.settings-preview-header'));
R
Rob Lourens 已提交
202 203 204 205 206

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

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

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

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

			this.settingsTreeModel.update();
			this.refreshTreeAndMaintainFocus();
228
		});
R
Rob Lourens 已提交
229

230
		this.createHeaderControls(headerControlsContainer);
R
Rob Lourens 已提交
231 232
	}

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

236 237 238 239
		this.toolbar = new ToolBar(headerControlsContainerRight, this.contextMenuService, {
			ariaLabel: localize('settingsToolbarLabel', "Settings Editor Actions"),
			actionRunner: this.actionRunner
		});
240

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

258 259 260 261 262
	private revealSetting(settingName: string): void {
		const element = this.settingsTreeModel.getElementByName(settingName);
		if (element) {
			this.settingsTree.setSelection([element]);
			this.settingsTree.setFocus(element);
263 264
			this.settingsTree.reveal(element, 0);
			this.settingsTree.domFocus();
265 266 267
		}
	}

268 269 270 271 272 273 274 275 276 277
	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 已提交
278 279 280 281 282
	}

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

R
Rob Lourens 已提交
283
		this.createSettingsTree(bodyContainer);
284
		this.createTOC(bodyContainer);
285 286 287 288

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

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

294 295 296
		const tocDataSource = this.instantiationService.createInstance(TOCDataSource);
		const tocRenderer = this.instantiationService.createInstance(TOCRenderer);
		this.tocTreeModel = new TOCTreeModel();
R
Rob Lourens 已提交
297 298 299

		this.tocTree = this.instantiationService.createInstance(WorkbenchTree, this.tocTreeContainer,
			<ITreeConfiguration>{
300 301
				dataSource: tocDataSource,
				renderer: tocRenderer,
302
				controller: this.instantiationService.createInstance(WorkbenchTreeController, { openMode: OpenMode.DOUBLE_CLICK }),
303 304
				filter: this.instantiationService.createInstance(SettingsTreeFilter, this.viewState),
				styler: new DefaultTreestyler(DOM.createStyleSheet(), 'settings-toc-tree'),
R
Rob Lourens 已提交
305 306
			},
			{
307 308
				showLoading: false,
				twistiePixels: 15
R
Rob Lourens 已提交
309
			});
310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325
		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 已提交
326

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

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

		this._register(this.tocTree.onDidBlur(() => {
			this.tocRowFocused.set(false);
R
Rob Lourens 已提交
347
		}));
348 349 350 351 352 353 354

		this.updateTOCVisible();
	}

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

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

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

371 372 373
		this.settingsTree = this.instantiationService.createInstance(SettingsTree,
			this.settingsTreeContainer,
			this.viewState,
374
			{
375
				renderer
376
			});
377

378
		this._register(this.settingsTree.onDidChangeFocus(e => {
379
			this.settingsTree.setSelection([e.focus]);
380 381
			if (this.selectedElement) {
				this.settingsTree.refresh(this.selectedElement);
382
			}
383

384 385
			if (e.focus) {
				this.settingsTree.refresh(e.focus);
386
			}
387 388

			this.selectedElement = e.focus;
389 390
		}));

391 392 393 394 395
		this._register(this.settingsTree.onDidBlur(() => {
			this.rowFocused.set(false);
			this.firstRowFocused.set(false);
		}));

396
		this._register(this.settingsTree.onDidChangeSelection(e => {
397
			this.updateTreeScrollSync();
398 399

			let firstRowFocused = false;
400
			let rowFocused = false;
401 402
			const selection: SettingsTreeElement = e.selection[0];
			if (selection) {
403
				rowFocused = true;
404 405 406 407 408 409 410 411
				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;
				}
			}

412
			this.rowFocused.set(rowFocused);
413
			this.firstRowFocused.set(firstRowFocused);
414
		}));
415

416 417
		this._register(this.settingsTree.onDidScroll(() => {
			this.updateTreeScrollSync();
418
		}));
419 420
	}

421 422 423 424 425 426 427 428 429 430 431 432
	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 已提交
433
	private onDidChangeSetting(key: string, value: any): void {
434 435
		if (this.pendingSettingUpdate && this.pendingSettingUpdate.key !== key) {
			this.updateChangedSetting(key, value);
436 437
		}

438 439 440 441
		this.pendingSettingUpdate = { key, value };
		this.settingUpdateDelayer.trigger(() => this.updateChangedSetting(key, value));
	}

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 467 468 469 470 471
	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);
			}

472 473
			this.tocTree.setSelection([element]);
			this.tocTree.setFocus(element, { fromScroll: true });
474 475 476
		}
	}

477 478 479
	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 已提交
480 481 482 483 484 485 486 487 488 489 490 491
		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)
492 493 494 495 496 497 498
			.then(() => this.refreshTreeAndMaintainFocus())
			.then(() => {
				const reportModifiedProps = {
					key,
					query: this.searchWidget.getValue(),
					searchResults: this.searchResultModel && this.searchResultModel.getUniqueResults(),
					rawResults: this.searchResultModel && this.searchResultModel.getRawResults(),
499
					showConfiguredOnly: this.viewState.tagFilters && this.viewState.tagFilters.has(MODIFIED_SETTING_TAG),
500 501 502 503 504 505
					isReset: typeof value === 'undefined',
					settingsTarget: this.settingsTargetsWidget.settingsTarget as SettingsTarget
				};

				return this.reportModifiedSetting(reportModifiedProps);
			});
506 507 508
	}

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

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

527 528 529 530 531 532
			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;
				}
533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551
			}
		}

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

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

574
					this._register(model.onDidChangeGroups(() => this.onConfigUpdate()));
575
					this.defaultSettingsEditorModel = model;
576
					return this.onConfigUpdate();
577
				});
R
Rob Lourens 已提交
578 579 580 581
		}
		return TPromise.as(null);
	}

582
	private toggleSearchMode(): void {
583 584 585 586
		DOM.removeClass(this.rootElement, 'search-mode');
		if (this.configurationService.getValue('workbench.settings.settingsSearchTocBehavior') === 'hide') {
			DOM.toggleClass(this.rootElement, 'search-mode', !!this.searchResultModel);
		}
587 588
	}

589
	private onConfigUpdate(): TPromise<void> {
590
		const groups = this.defaultSettingsEditorModel.settingsGroups.slice(1); // Without commonlyUsed
591
		const dividedGroups = collections.groupBy(groups, g => g.contributedByExtension ? 'extension' : 'core');
592 593 594 595 596 597 598 599 600 601 602 603 604 605
		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;
		}

606
		const commonlyUsed = resolveSettingsTree(commonlyUsedData, dividedGroups.core);
607
		resolvedSettingsRoot.children.unshift(commonlyUsed.tree);
608

609 610
		resolvedSettingsRoot.children.push(resolveExtensionsSettings(dividedGroups.extension || []));

611 612 613 614
		if (this.searchResultModel) {
			this.searchResultModel.updateChildren();
		}

615 616 617 618
		if (this.settingsTreeModel) {
			this.settingsTreeModel.update(resolvedSettingsRoot);
		} else {
			this.settingsTreeModel = this.instantiationService.createInstance(SettingsTreeModel, this.viewState, resolvedSettingsRoot);
619
			this.settingsTree.setInput(this.settingsTreeModel.root);
620 621 622 623 624 625 626

			this.tocTreeModel.settingsTreeRoot = this.settingsTreeModel.root as SettingsTreeGroupElement;
			if (this.tocTree.getInput()) {
				this.tocTree.refresh();
			} else {
				this.tocTree.setInput(this.tocTreeModel);
			}
627 628 629 630 631
		}

		return this.refreshTreeAndMaintainFocus();
	}

R
Rob Lourens 已提交
632 633 634 635 636 637 638 639
	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;

640 641 642
		return this.settingsTree.refresh()
			.then(() => {
				if (focusedRowId) {
643
					this.focusEditControlForRow(focusedRowId, selection);
R
Rob Lourens 已提交
644
				}
645 646 647 648
			})
			.then(() => {
				return this.tocTree.refresh();
			});
649 650
	}

651 652 653 654 655 656 657 658 659 660 661
	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);
			}
		}
	}

662
	private onSearchInputChanged(): void {
R
Rob Lourens 已提交
663 664
		const query = this.searchWidget.getValue().trim();
		this.delayedFilterLogging.cancel();
665
		this.triggerSearch(query).then(() => {
666
			if (query && this.searchResultModel) {
667
				this.delayedFilterLogging.trigger(() => this.reportFilteringUsed(query, this.searchResultModel.getUniqueResults()));
668 669
			}
		});
R
Rob Lourens 已提交
670 671 672
	}

	private triggerSearch(query: string): TPromise<void> {
673 674
		this.viewState.tagFilters = new Set<string>();
		if (query) {
675 676 677 678 679 680 681 682
			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 '';
			});
683
		}
684
		query = query.trim();
R
Rob Lourens 已提交
685
		if (query) {
R
Rob Lourens 已提交
686
			return this.searchInProgress = TPromise.join([
R
Rob Lourens 已提交
687
				this.localSearchDelayer.trigger(() => this.localFilterPreferences(query)),
688
				this.remoteSearchThrottle.trigger(() => this.remoteSearchPreferences(query), 500)
R
Rob Lourens 已提交
689 690 691
			]).then(() => {
				this.searchInProgress = null;
			});
R
Rob Lourens 已提交
692 693 694
		} else {
			this.localSearchDelayer.cancel();
			this.remoteSearchThrottle.cancel();
R
Rob Lourens 已提交
695 696 697
			if (this.searchInProgress && this.searchInProgress.cancel) {
				this.searchInProgress.cancel();
			}
R
Rob Lourens 已提交
698

699
			this.searchResultModel = null;
700
			this.tocTreeModel.currentSearchModel = null;
701
			this.viewState.filterToCategory = null;
702
			this.tocTree.refresh();
703
			this.toggleSearchMode();
704
			this.settingsTree.setInput(this.settingsTreeModel.root);
705

R
Rob Lourens 已提交
706 707 708 709
			return TPromise.wrap(null);
		}
	}

710 711 712 713 714 715 716 717 718 719
	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];
720 721 722 723
		if (filterResult) {
			counts['filterResult'] = filterResult.filterMatches.length;
		}

724 725
		if (nlpResult) {
			counts['nlpResult'] = nlpResult.filterMatches.length;
726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745
		}

		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 }
			}
		*/
746
		this.telemetryService.publicLog('settingsEditor.filter', data);
747 748
	}

R
Rob Lourens 已提交
749
	private localFilterPreferences(query: string): TPromise<void> {
750 751
		const localSearchProvider = this.preferencesSearchService.getLocalSearchProvider(query);
		return this.filterOrSearchPreferences(query, SearchResultIdx.Local, localSearchProvider);
R
Rob Lourens 已提交
752 753 754
	}

	private remoteSearchPreferences(query: string): TPromise<void> {
755 756
		const remoteSearchProvider = this.preferencesSearchService.getRemoteSearchProvider(query);
		return this.filterOrSearchPreferences(query, SearchResultIdx.Remote, remoteSearchProvider);
R
Rob Lourens 已提交
757 758 759
	}

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

R
Rob Lourens 已提交
768
				if (!this.searchResultModel) {
769
					this.searchResultModel = this.instantiationService.createInstance(SearchResultModel, this.viewState);
770
					this.searchResultModel.setResult(type, result);
771
					this.tocTreeModel.currentSearchModel = this.searchResultModel;
772
					this.toggleSearchMode();
R
Rob Lourens 已提交
773
					this.settingsTree.setInput(this.searchResultModel);
774 775
				} else {
					this.searchResultModel.setResult(type, result);
R
Rob Lourens 已提交
776 777
				}

778
				this.tocTreeModel.update();
R
Rob Lourens 已提交
779 780 781 782
				resolve(this.refreshTreeAndMaintainFocus());
			});
		}, () => {
			isCanceled = true;
R
Rob Lourens 已提交
783 784 785 786 787 788 789 790 791 792 793
		});
	}

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

810
	private layoutTrees(dimension: DOM.Dimension): void {
811
		const listHeight = dimension.height - (DOM.getDomNodePagePosition(this.headerContainer).height + 11 /*padding*/);
812 813 814
		const settingsTreeHeight = listHeight - 14;
		this.settingsTreeContainer.style.height = `${settingsTreeHeight}px`;
		this.settingsTree.layout(settingsTreeHeight, 800);
815

816 817 818 819 820
		const selectedSetting = this.settingsTree.getSelection()[0];
		if (selectedSetting) {
			this.settingsTree.refresh(selectedSetting);
		}

821 822 823
		const tocTreeHeight = listHeight - 16;
		this.tocTreeContainer.style.height = `${tocTreeHeight}px`;
		this.tocTree.layout(tocTreeHeight, 175);
824
	}
R
Rob Lourens 已提交
825
}
826

827 828 829 830
interface ISettingsToolbarContext {
	target: SettingsTarget;
}

831 832
class OpenSettingsAction extends Action {
	static readonly ID = 'settings.openSettingsJson';
833
	static readonly LABEL = localize('openSettingsJsonLabel', "Open settings.json");
834 835 836 837 838 839 840 841

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


842
	run(context?: ISettingsToolbarContext): TPromise<void> {
843 844 845 846
		return this._run(context)
			.then(() => { });
	}

847 848 849
	private _run(context?: ISettingsToolbarContext): TPromise<any> {
		const target = context && context.target;
		if (target === ConfigurationTarget.USER) {
850
			return this.preferencesService.openGlobalSettings();
851
		} else if (target === ConfigurationTarget.WORKSPACE) {
852
			return this.preferencesService.openWorkspaceSettings();
853 854
		} else if (URI.isUri(target)) {
			return this.preferencesService.openFolderSettings(target);
855 856 857 858 859 860
		}

		return TPromise.wrap(null);
	}
}

861 862
class FilterByTagAction extends Action {
	static readonly ID = 'settings.filterByTag';
863 864 865 866

	constructor(
		label: string,
		private tag: string,
867
		private settingsEditor: SettingsEditor2
868
	) {
869
		super(FilterByTagAction.ID, label, 'toggle-filter-tag');
870 871 872
	}

	run(): TPromise<void> {
873
		this.settingsEditor.search(this.tag === MODIFIED_SETTING_TAG ? `@${this.tag} ` : `@tag:${this.tag} `);
874
		return TPromise.as(null);
875 876
	}
}