preferencesWidgets.ts 28.4 KB
Newer Older
1 2 3 4 5
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

6
import * as DOM from 'vs/base/browser/dom';
S
Sandeep Somavarapu 已提交
7
import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
R
Rob Lourens 已提交
8
import { ActionBar, ActionsOrientation, BaseActionItem } from 'vs/base/browser/ui/actionbar/actionbar';
9 10 11 12
import { IInputOptions, InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import { Widget } from 'vs/base/browser/ui/widget';
import { Action, IAction } from 'vs/base/common/actions';
import { Emitter, Event } from 'vs/base/common/event';
13
import { MarkdownString } from 'vs/base/common/htmlContent';
14 15 16 17
import { KeyCode } from 'vs/base/common/keyCodes';
import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { TPromise } from 'vs/base/common/winjs.base';
J
Joao Moreno 已提交
18
import { IMarginData } from 'vs/editor/browser/controller/mouseTarget';
19 20 21
import { ICodeEditor, IEditorMouseEvent, IViewZone, MouseTargetType } from 'vs/editor/browser/editorBrowser';
import { ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
import { Position } from 'vs/editor/common/core/position';
A
Alex Dima 已提交
22
import { IModelDeltaDecoration, TrackedRangeStickiness } from 'vs/editor/common/model';
23 24 25 26 27 28 29 30 31 32 33
import { localize } from 'vs/nls';
import { ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { activeContrastBorder, badgeBackground, badgeForeground, contrastBorder, errorForeground, focusBorder } from 'vs/platform/theme/common/colorRegistry';
import { attachInputBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler';
import { ICssStyleCollector, ITheme, IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
import { IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { PANEL_ACTIVE_TITLE_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND } from 'vs/workbench/common/theme';
import { ISettingsGroup } from 'vs/workbench/services/preferences/common/preferences';
34

S
Sandeep Somavarapu 已提交
35 36 37 38 39
export class SettingsHeaderWidget extends Widget implements IViewZone {

	private id: number;
	private _domNode: HTMLElement;

40
	protected titleContainer: HTMLElement;
S
Sandeep Somavarapu 已提交
41 42
	private messageElement: HTMLElement;

43
	constructor(protected editor: ICodeEditor, private title: string) {
S
Sandeep Somavarapu 已提交
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
		super();
		this.create();
		this._register(this.editor.onDidChangeConfiguration(() => this.layout()));
		this._register(this.editor.onDidLayoutChange(() => this.layout()));
	}

	get domNode(): HTMLElement {
		return this._domNode;
	}

	get heightInLines(): number {
		return 1;
	}

	get afterLineNumber(): number {
		return 0;
	}

62
	protected create() {
S
Sandeep Somavarapu 已提交
63 64 65
		this._domNode = DOM.$('.settings-header-widget');

		this.titleContainer = DOM.append(this._domNode, DOM.$('.title-container'));
S
Sandeep Somavarapu 已提交
66 67 68
		if (this.title) {
			DOM.append(this.titleContainer, DOM.$('.title')).textContent = this.title;
		}
S
Sandeep Somavarapu 已提交
69
		this.messageElement = DOM.append(this.titleContainer, DOM.$('.message'));
S
Sandeep Somavarapu 已提交
70 71 72
		if (this.title) {
			this.messageElement.style.paddingLeft = '12px';
		}
S
Sandeep Somavarapu 已提交
73 74 75 76 77 78 79 80 81 82 83 84 85 86

		this.editor.changeViewZones(accessor => {
			this.id = accessor.addZone(this);
			this.layout();
		});
	}

	public setMessage(message: string): void {
		this.messageElement.textContent = message;
	}

	private layout(): void {
		const configuration = this.editor.getConfiguration();
		this.titleContainer.style.fontSize = configuration.fontInfo.fontSize + 'px';
S
Sandeep Somavarapu 已提交
87
		if (!configuration.contribInfo.folding) {
S
Sandeep Somavarapu 已提交
88
			this.titleContainer.style.paddingLeft = '6px';
S
Sandeep Somavarapu 已提交
89
		}
S
Sandeep Somavarapu 已提交
90 91 92 93 94 95 96 97 98 99
	}

	public dispose() {
		this.editor.changeViewZones(accessor => {
			accessor.removeZone(this.id);
		});
		super.dispose();
	}
}

100 101 102
export class DefaultSettingsHeaderWidget extends SettingsHeaderWidget {

	private _onClick = this._register(new Emitter<void>());
103
	public readonly onClick: Event<void> = this._onClick.event;
104 105 106 107

	protected create() {
		super.create();

108
		this.toggleMessage(true);
109 110
	}

111
	public toggleMessage(hasSettings: boolean): void {
112 113 114 115 116 117 118 119
		if (hasSettings) {
			this.setMessage(localize('defaultSettings', "Place your settings in the right hand side editor to override."));
		} else {
			this.setMessage(localize('noSettingsFound', "No Settings Found."));
		}
	}
}

S
Sandeep Somavarapu 已提交
120 121 122 123 124
export class SettingsGroupTitleWidget extends Widget implements IViewZone {

	private id: number;
	private _afterLineNumber: number;
	private _domNode: HTMLElement;
125

S
Sandeep Somavarapu 已提交
126
	private titleContainer: HTMLElement;
S
Sandeep Somavarapu 已提交
127 128
	private icon: HTMLElement;
	private title: HTMLElement;
129

S
Sandeep Somavarapu 已提交
130
	private _onToggled = this._register(new Emitter<boolean>());
131
	public readonly onToggled: Event<boolean> = this._onToggled.event;
132

A
Alex Dima 已提交
133
	private previousPosition: Position;
134

S
Sandeep Somavarapu 已提交
135 136
	constructor(private editor: ICodeEditor, public settingsGroup: ISettingsGroup) {
		super();
S
Sandeep Somavarapu 已提交
137
		this.create();
S
Sandeep Somavarapu 已提交
138
		this._register(this.editor.onDidChangeConfiguration(() => this.layout()));
S
Sandeep Somavarapu 已提交
139
		this._register(this.editor.onDidLayoutChange(() => this.layout()));
140
		this._register(this.editor.onDidChangeCursorPosition((e) => this.onCursorChange(e)));
S
Sandeep Somavarapu 已提交
141
	}
142

S
Sandeep Somavarapu 已提交
143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158
	get domNode(): HTMLElement {
		return this._domNode;
	}

	get heightInLines(): number {
		return 1.5;
	}

	get afterLineNumber(): number {
		return this._afterLineNumber;
	}

	private create() {
		this._domNode = DOM.$('.settings-group-title-widget');

		this.titleContainer = DOM.append(this._domNode, DOM.$('.title-container'));
159
		this.titleContainer.tabIndex = 0;
S
Sandeep Somavarapu 已提交
160
		this.onclick(this.titleContainer, () => this.toggle());
161 162
		this.onkeydown(this.titleContainer, (e) => this.onKeyDown(e));
		const focusTracker = this._register(DOM.trackFocus(this.titleContainer));
163 164 165

		this._register(focusTracker.onDidFocus(() => this.toggleFocus(true)));
		this._register(focusTracker.onDidBlur(() => this.toggleFocus(false)));
S
Sandeep Somavarapu 已提交
166 167 168 169 170

		this.icon = DOM.append(this.titleContainer, DOM.$('.expand-collapse-icon'));
		this.title = DOM.append(this.titleContainer, DOM.$('.title'));
		this.title.textContent = this.settingsGroup.title + ` (${this.settingsGroup.sections.reduce((count, section) => count + section.settings.length, 0)})`;

S
Sandeep Somavarapu 已提交
171
		this.layout();
172 173
	}

S
Sandeep Somavarapu 已提交
174
	public render() {
S
Sandeep Somavarapu 已提交
175 176 177
		this._afterLineNumber = this.settingsGroup.range.startLineNumber - 2;
		this.editor.changeViewZones(accessor => {
			this.id = accessor.addZone(this);
S
Sandeep Somavarapu 已提交
178
			this.layout();
S
Sandeep Somavarapu 已提交
179
		});
S
Sandeep Somavarapu 已提交
180 181
	}

S
Sandeep Somavarapu 已提交
182 183
	public toggleCollapse(collapse: boolean) {
		DOM.toggleClass(this.titleContainer, 'collapsed', collapse);
184 185
	}

186 187 188 189
	public toggleFocus(focus: boolean): void {
		DOM.toggleClass(this.titleContainer, 'focused', focus);
	}

S
Sandeep Somavarapu 已提交
190 191 192 193
	public isCollapsed(): boolean {
		return DOM.hasClass(this.titleContainer, 'collapsed');
	}

S
Sandeep Somavarapu 已提交
194
	private layout(): void {
S
Sandeep Somavarapu 已提交
195 196
		const configuration = this.editor.getConfiguration();
		const layoutInfo = this.editor.getLayoutInfo();
S
Sandeep Somavarapu 已提交
197
		this._domNode.style.width = layoutInfo.contentWidth - layoutInfo.verticalScrollbarWidth + 'px';
S
Sandeep Somavarapu 已提交
198
		this.titleContainer.style.lineHeight = configuration.lineHeight + 3 + 'px';
S
Sandeep Somavarapu 已提交
199
		this.titleContainer.style.height = configuration.lineHeight + 3 + 'px';
S
Sandeep Somavarapu 已提交
200
		this.titleContainer.style.fontSize = configuration.fontInfo.fontSize + 'px';
S
Sandeep Somavarapu 已提交
201
		this.icon.style.minWidth = `${this.getIconSize(16)}px`;
S
Sandeep Somavarapu 已提交
202 203
	}

S
Sandeep Somavarapu 已提交
204
	private getIconSize(minSize: number): number {
S
Sandeep Somavarapu 已提交
205
		const fontSize = this.editor.getConfiguration().fontInfo.fontSize;
S
Sandeep Somavarapu 已提交
206
		return fontSize > 8 ? Math.max(fontSize, minSize) : 12;
207 208
	}

209 210 211 212
	private onKeyDown(keyboardEvent: IKeyboardEvent): void {
		switch (keyboardEvent.keyCode) {
			case KeyCode.Enter:
			case KeyCode.Space:
S
Sandeep Somavarapu 已提交
213 214 215 216 217 218 219
				this.toggle();
				break;
			case KeyCode.LeftArrow:
				this.collapse(true);
				break;
			case KeyCode.RightArrow:
				this.collapse(false);
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
				break;
			case KeyCode.UpArrow:
				if (this.settingsGroup.range.startLineNumber - 3 !== 1) {
					this.editor.focus();
					const lineNumber = this.settingsGroup.range.startLineNumber - 2;
					this.editor.setPosition({ lineNumber, column: this.editor.getModel().getLineMinColumn(lineNumber) });
				}
				break;
			case KeyCode.DownArrow:
				const lineNumber = this.isCollapsed() ? this.settingsGroup.range.startLineNumber : this.settingsGroup.range.startLineNumber - 1;
				this.editor.focus();
				this.editor.setPosition({ lineNumber, column: this.editor.getModel().getLineMinColumn(lineNumber) });
				break;
		}
	}

S
Sandeep Somavarapu 已提交
236 237 238 239 240 241 242 243 244 245 246
	private toggle() {
		this.collapse(!this.isCollapsed());
	}

	private collapse(collapse: boolean) {
		if (collapse !== this.isCollapsed()) {
			DOM.toggleClass(this.titleContainer, 'collapsed', collapse);
			this._onToggled.fire(collapse);
		}
	}

247
	private onCursorChange(e: ICursorPositionChangedEvent): void {
S
Sandeep Somavarapu 已提交
248
		if (e.source !== 'mouse' && this.focusTitle(e.position)) {
249 250 251 252
			this.titleContainer.focus();
		}
	}

A
Alex Dima 已提交
253
	private focusTitle(currentPosition: Position): boolean {
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
		const previousPosition = this.previousPosition;
		this.previousPosition = currentPosition;
		if (!previousPosition) {
			return false;
		}
		if (previousPosition.lineNumber === currentPosition.lineNumber) {
			return false;
		}
		if (currentPosition.lineNumber === this.settingsGroup.range.startLineNumber - 1 || currentPosition.lineNumber === this.settingsGroup.range.startLineNumber - 2) {
			return true;
		}
		if (this.isCollapsed() && currentPosition.lineNumber === this.settingsGroup.range.endLineNumber) {
			return true;
		}
		return false;
	}

S
Sandeep Somavarapu 已提交
271 272 273 274 275 276
	public dispose() {
		this.editor.changeViewZones(accessor => {
			accessor.removeZone(this.id);
		});
		super.dispose();
	}
S
Sandeep Somavarapu 已提交
277 278
}

S
Sandeep Somavarapu 已提交
279
export class FolderSettingsActionItem extends BaseActionItem {
280

S
Sandeep Somavarapu 已提交
281
	private _folder: IWorkspaceFolder;
282
	private _folderSettingCounts = new Map<string, number>();
283

S
Sandeep Somavarapu 已提交
284 285 286 287 288
	private container: HTMLElement;
	private anchorElement: HTMLElement;
	private labelElement: HTMLElement;
	private detailsElement: HTMLElement;
	private dropDownElement: HTMLElement;
289

S
Sandeep Somavarapu 已提交
290
	private disposables: IDisposable[] = [];
291

S
Sandeep Somavarapu 已提交
292 293 294 295 296 297 298 299 300 301
	constructor(
		action: IAction,
		@IWorkspaceContextService private contextService: IWorkspaceContextService,
		@IContextMenuService private contextMenuService: IContextMenuService
	) {
		super(null, action);
		const workspace = this.contextService.getWorkspace();
		this._folder = workspace.folders.length === 1 ? workspace.folders[0] : null;
		this.disposables.push(this.contextService.onDidChangeWorkspaceFolders(() => this.onWorkspaceFoldersChanged()));
	}
302

S
Sandeep Somavarapu 已提交
303 304
	get folder(): IWorkspaceFolder {
		return this._folder;
305 306
	}

S
Sandeep Somavarapu 已提交
307 308 309
	set folder(folder: IWorkspaceFolder) {
		this._folder = folder;
		this.update();
S
Sandeep Somavarapu 已提交
310 311
	}

312 313 314
	public setCount(settingsTarget: URI, count: number): void {
		const folder = this.contextService.getWorkspaceFolder(settingsTarget).uri;
		this._folderSettingCounts.set(folder.toString(), count);
315 316 317
		this.update();
	}

S
Sandeep Somavarapu 已提交
318
	public render(container: HTMLElement): void {
B
Benjamin Pasero 已提交
319
		this.element = container;
S
Sandeep Somavarapu 已提交
320 321 322 323 324

		this.container = container;
		this.labelElement = DOM.$('.action-title');
		this.detailsElement = DOM.$('.action-details');
		this.dropDownElement = DOM.$('.dropdown-icon.octicon.octicon-triangle-down.hide');
325
		this.anchorElement = DOM.$('a.action-label.folder-settings', {
S
Sandeep Somavarapu 已提交
326 327
			role: 'button',
			'aria-haspopup': 'true',
S
Sandeep Somavarapu 已提交
328
			'tabindex': '0'
S
Sandeep Somavarapu 已提交
329
		}, this.labelElement, this.detailsElement, this.dropDownElement);
330
		this._register(DOM.addDisposableListener(this.anchorElement, DOM.EventType.MOUSE_DOWN, e => DOM.EventHelper.stop(e)));
S
Sandeep Somavarapu 已提交
331
		this.disposables.push(DOM.addDisposableListener(this.anchorElement, DOM.EventType.CLICK, e => this.onClick(e)));
S
Sandeep Somavarapu 已提交
332
		this.disposables.push(DOM.addDisposableListener(this.anchorElement, DOM.EventType.KEY_UP, e => this.onKeyUp(e)));
S
Sandeep Somavarapu 已提交
333 334 335 336

		DOM.append(this.container, this.anchorElement);

		this.update();
337 338
	}

S
Sandeep Somavarapu 已提交
339 340 341 342 343 344 345 346 347 348
	private onKeyUp(event: any): void {
		const keyboardEvent = new StandardKeyboardEvent(event);
		switch (keyboardEvent.keyCode) {
			case KeyCode.Enter:
			case KeyCode.Space:
				this.onClick(event);
				return;
		}
	}

S
Sandeep Somavarapu 已提交
349 350 351 352 353 354 355 356
	public onClick(event: DOM.EventLike): void {
		DOM.EventHelper.stop(event, true);
		if (!this.folder || this._action.checked) {
			this.showMenu();
		} else {
			this._action.run(this._folder);
		}
	}
357

358
	protected updateEnabled(): void {
S
Sandeep Somavarapu 已提交
359 360
		this.update();
	}
361

362
	protected updateChecked(): void {
S
Sandeep Somavarapu 已提交
363 364
		this.update();
	}
365

S
Sandeep Somavarapu 已提交
366 367 368 369 370 371 372 373 374
	private onWorkspaceFoldersChanged(): void {
		const oldFolder = this._folder;
		const workspace = this.contextService.getWorkspace();
		if (this._folder) {
			this._folder = workspace.folders.filter(folder => folder.uri.toString() === this._folder.uri.toString())[0] || workspace.folders[0];
		}
		this._folder = this._folder ? this._folder : workspace.folders.length === 1 ? workspace.folders[0] : null;

		this.update();
375

S
Sandeep Somavarapu 已提交
376 377 378 379 380 381 382
		if (this._action.checked) {
			if ((oldFolder || !this._folder)
				|| (!oldFolder || this._folder)
				|| (oldFolder && this._folder && oldFolder.uri.toString() === this._folder.uri.toString())) {
				this._action.run(this._folder);
			}
		}
383 384
	}

S
Sandeep Somavarapu 已提交
385
	private update(): void {
386 387 388
		let total = 0;
		this._folderSettingCounts.forEach(n => total += n);

S
Sandeep Somavarapu 已提交
389 390 391
		const workspace = this.contextService.getWorkspace();
		if (this._folder) {
			this.labelElement.textContent = this._folder.name;
S
Sandeep Somavarapu 已提交
392
			this.anchorElement.title = this._folder.name;
393
			const detailsText = this.labelWithCount(this._action.label, total);
394
			this.detailsElement.textContent = detailsText;
S
Sandeep Somavarapu 已提交
395 396
			DOM.toggleClass(this.dropDownElement, 'hide', workspace.folders.length === 1 || !this._action.checked);
		} else {
397
			const labelText = this.labelWithCount(this._action.label, total);
398
			this.labelElement.textContent = labelText;
S
Sandeep Somavarapu 已提交
399
			this.detailsElement.textContent = '';
S
Sandeep Somavarapu 已提交
400
			this.anchorElement.title = this._action.label;
S
Sandeep Somavarapu 已提交
401 402 403 404
			DOM.removeClass(this.dropDownElement, 'hide');
		}
		DOM.toggleClass(this.anchorElement, 'checked', this._action.checked);
		DOM.toggleClass(this.container, 'disabled', !this._action.enabled);
405 406
	}

S
Sandeep Somavarapu 已提交
407
	private showMenu(): void {
408
		this.contextMenuService.showContextMenu({
S
Sandeep Somavarapu 已提交
409 410
			getAnchor: () => this.container,
			getActions: () => TPromise.as(this.getDropdownMenuActions()),
B
Benjamin Pasero 已提交
411
			getActionItem: () => null,
S
Sandeep Somavarapu 已提交
412 413 414
			onHide: () => {
				this.anchorElement.blur();
			}
415 416 417
		});
	}

S
Sandeep Somavarapu 已提交
418
	private getDropdownMenuActions(): IAction[] {
419
		const actions: IAction[] = [];
S
Sandeep Somavarapu 已提交
420 421
		const workspaceFolders = this.contextService.getWorkspace().folders;
		if (this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE && workspaceFolders.length > 0) {
422
			actions.push(...workspaceFolders.map((folder, index) => {
423
				const folderCount = this._folderSettingCounts.get(folder.uri.toString());
424 425
				return <IAction>{
					id: 'folderSettingsTarget' + index,
426
					label: this.labelWithCount(folder.name, folderCount),
S
Sandeep Somavarapu 已提交
427
					checked: this.folder && this.folder.uri.toString() === folder.uri.toString(),
428
					enabled: true,
S
Sandeep Somavarapu 已提交
429
					run: () => this._action.run(folder)
430
				};
431
			}));
432 433 434 435
		}
		return actions;
	}

436 437 438 439 440 441 442 443 444
	private labelWithCount(label: string, count: number | undefined): string {
		// Append the count if it's >0 and not undefined
		if (count) {
			label += ` (${count})`;
		}

		return label;
	}

S
Sandeep Somavarapu 已提交
445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461
	public dispose(): void {
		dispose(this.disposables);
		super.dispose();
	}
}

export type SettingsTarget = ConfigurationTarget.USER | ConfigurationTarget.WORKSPACE | URI;

export class SettingsTargetsWidget extends Widget {

	private settingsSwitcherBar: ActionBar;
	private userSettings: Action;
	private workspaceSettings: Action;
	private folderSettings: FolderSettingsActionItem;

	private _settingsTarget: SettingsTarget;

M
Matt Bierner 已提交
462
	private readonly _onDidTargetChange: Emitter<SettingsTarget> = new Emitter<SettingsTarget>();
S
Sandeep Somavarapu 已提交
463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478
	public readonly onDidTargetChange: Event<SettingsTarget> = this._onDidTargetChange.event;

	constructor(
		parent: HTMLElement,
		@IWorkspaceContextService private contextService: IWorkspaceContextService,
		@IInstantiationService private instantiationService: IInstantiationService
	) {
		super();
		this.create(parent);
		this._register(this.contextService.onDidChangeWorkbenchState(() => this.onWorkbenchStateChanged()));
		this._register(this.contextService.onDidChangeWorkspaceFolders(() => this.update()));
	}

	private create(parent: HTMLElement): void {
		const settingsTabsWidget = DOM.append(parent, DOM.$('.settings-tabs-widget'));
		this.settingsSwitcherBar = this._register(new ActionBar(settingsTabsWidget, {
S
Sandeep Somavarapu 已提交
479
			orientation: ActionsOrientation.HORIZONTAL,
S
Sandeep Somavarapu 已提交
480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495
			ariaLabel: localize('settingsSwitcherBarAriaLabel', "Settings Switcher"),
			animated: false,
			actionItemProvider: (action: Action) => action.id === 'folderSettings' ? this.folderSettings : null
		}));

		this.userSettings = new Action('userSettings', localize('userSettings', "User Settings"), '.settings-tab', true, () => this.updateTarget(ConfigurationTarget.USER));
		this.userSettings.tooltip = this.userSettings.label;

		this.workspaceSettings = new Action('workspaceSettings', localize('workspaceSettings', "Workspace Settings"), '.settings-tab', false, () => this.updateTarget(ConfigurationTarget.WORKSPACE));
		this.workspaceSettings.tooltip = this.workspaceSettings.label;

		const folderSettingsAction = new Action('folderSettings', localize('folderSettings', "Folder Settings"), '.settings-tab', false, (folder: IWorkspaceFolder) => this.updateTarget(folder ? folder.uri : ConfigurationTarget.USER));
		this.folderSettings = this.instantiationService.createInstance(FolderSettingsActionItem, folderSettingsAction);

		this.update();

S
Sandeep Somavarapu 已提交
496
		this.settingsSwitcherBar.push([this.userSettings, this.workspaceSettings, folderSettingsAction]);
S
Sandeep Somavarapu 已提交
497 498 499 500 501 502 503 504 505 506 507 508 509 510 511
	}

	public get settingsTarget(): SettingsTarget {
		return this._settingsTarget;
	}

	public set settingsTarget(settingsTarget: SettingsTarget) {
		this._settingsTarget = settingsTarget;
		this.userSettings.checked = ConfigurationTarget.USER === this.settingsTarget;
		this.workspaceSettings.checked = ConfigurationTarget.WORKSPACE === this.settingsTarget;
		if (this.settingsTarget instanceof URI) {
			this.folderSettings.getAction().checked = true;
			this.folderSettings.folder = this.contextService.getWorkspaceFolder(this.settingsTarget as URI);
		} else {
			this.folderSettings.getAction().checked = false;
512
		}
513 514
	}

515 516 517 518 519 520 521 522 523 524 525 526 527 528 529
	public setResultCount(settingsTarget: SettingsTarget, count: number): void {
		if (settingsTarget === ConfigurationTarget.WORKSPACE) {
			let label = localize('workspaceSettings', "Workspace Settings");
			if (count) {
				label += ` (${count})`;
			}

			this.workspaceSettings.label = label;
		} else if (settingsTarget === ConfigurationTarget.USER) {
			let label = localize('userSettings', "User Settings");
			if (count) {
				label += ` (${count})`;
			}

			this.userSettings.label = label;
530
		} else if (settingsTarget instanceof URI) {
531
			this.folderSettings.setCount(settingsTarget, count);
532 533 534
		}
	}

S
Sandeep Somavarapu 已提交
535 536 537 538 539 540
	private onWorkbenchStateChanged(): void {
		this.folderSettings.folder = null;
		this.update();
		if (this.settingsTarget === ConfigurationTarget.WORKSPACE && this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
			this.updateTarget(ConfigurationTarget.USER);
		}
541 542
	}

S
Sandeep Somavarapu 已提交
543 544 545 546 547
	private updateTarget(settingsTarget: SettingsTarget): TPromise<void> {
		const isSameTarget = this.settingsTarget === settingsTarget || settingsTarget instanceof URI && this.settingsTarget instanceof URI && this.settingsTarget.toString() === settingsTarget.toString();
		if (!isSameTarget) {
			this.settingsTarget = settingsTarget;
			this._onDidTargetChange.fire(this.settingsTarget);
548
		}
S
Sandeep Somavarapu 已提交
549
		return TPromise.as(null);
550
	}
S
Sandeep Somavarapu 已提交
551 552 553 554 555 556 557

	private update(): void {
		DOM.toggleClass(this.settingsSwitcherBar.domNode, 'empty-workbench', this.contextService.getWorkbenchState() === WorkbenchState.EMPTY);
		this.workspaceSettings.enabled = this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY;
		this.folderSettings.getAction().enabled = this.contextService.getWorkbenchState() === WorkbenchState.WORKSPACE && this.contextService.getWorkspace().folders.length > 0;
	}

558 559
}

S
Sandeep Somavarapu 已提交
560
export interface SearchOptions extends IInputOptions {
S
Sandeep Somavarapu 已提交
561
	focusKey?: IContextKey<boolean>;
562
	showResultCount?: boolean;
S
Sandeep Somavarapu 已提交
563 564
	ariaLive?: string;
	ariaLabelledBy?: string;
S
Sandeep Somavarapu 已提交
565 566
}

567
export class SearchWidget extends Widget {
S
Sandeep Somavarapu 已提交
568

S
Sandeep Somavarapu 已提交
569
	public domNode: HTMLElement;
570

571
	private countElement: HTMLElement;
S
Sandeep Somavarapu 已提交
572
	private searchContainer: HTMLElement;
573
	inputBox: InputBox;
574
	private controlsDiv: HTMLElement;
S
Sandeep Somavarapu 已提交
575

M
Matt Bierner 已提交
576
	private readonly _onDidChange: Emitter<string> = this._register(new Emitter<string>());
S
Sandeep Somavarapu 已提交
577
	public readonly onDidChange: Event<string> = this._onDidChange.event;
S
Sandeep Somavarapu 已提交
578

M
Matt Bierner 已提交
579
	private readonly _onFocus: Emitter<void> = this._register(new Emitter<void>());
S
Sandeep Somavarapu 已提交
580 581
	public readonly onFocus: Event<void> = this._onFocus.event;

S
Sandeep Somavarapu 已提交
582
	constructor(parent: HTMLElement, protected options: SearchOptions,
S
Sandeep Somavarapu 已提交
583
		@IContextViewService private contextViewService: IContextViewService,
B
Benjamin Pasero 已提交
584 585
		@IInstantiationService protected instantiationService: IInstantiationService,
		@IThemeService private themeService: IThemeService
S
Sandeep Somavarapu 已提交
586 587
	) {
		super();
S
Sandeep Somavarapu 已提交
588
		this.create(parent);
589 590
	}

S
Sandeep Somavarapu 已提交
591 592
	private create(parent: HTMLElement) {
		this.domNode = DOM.append(parent, DOM.$('div.settings-header-widget'));
593
		this.createSearchContainer(DOM.append(this.domNode, DOM.$('div.settings-search-container')));
594 595
		this.controlsDiv = DOM.append(this.domNode, DOM.$('div.settings-search-controls'));

596 597 598 599 600
		if (this.options.showResultCount) {
			this.countElement = DOM.append(this.controlsDiv, DOM.$('.settings-count-widget'));
			this._register(attachStylerCallback(this.themeService, { badgeBackground, contrastBorder }, colors => {
				const background = colors.badgeBackground ? colors.badgeBackground.toString() : null;
				const border = colors.contrastBorder ? colors.contrastBorder.toString() : null;
601

602
				this.countElement.style.backgroundColor = background;
603

604 605 606
				this.countElement.style.borderWidth = border ? '1px' : null;
				this.countElement.style.borderStyle = border ? 'solid' : null;
				this.countElement.style.borderColor = border;
607

608 609 610
				this.styleCountElementForeground();
			}));
		}
S
Sandeep Somavarapu 已提交
611

S
Sandeep Somavarapu 已提交
612 613 614 615
		this.inputBox.inputElement.setAttribute('aria-live', this.options.ariaLive || 'off');
		if (this.options.ariaLabelledBy) {
			this.inputBox.inputElement.setAttribute('aria-labelledBy', this.options.ariaLabelledBy);
		}
S
Sandeep Somavarapu 已提交
616
		const focusTracker = this._register(DOM.trackFocus(this.inputBox.inputElement));
617
		this._register(focusTracker.onDidFocus(() => this._onFocus.fire()));
S
Sandeep Somavarapu 已提交
618

S
Sandeep Somavarapu 已提交
619
		if (this.options.focusKey) {
620 621
			this._register(focusTracker.onDidFocus(() => this.options.focusKey.set(true)));
			this._register(focusTracker.onDidBlur(() => this.options.focusKey.set(false)));
S
Sandeep Somavarapu 已提交
622
		}
S
Sandeep Somavarapu 已提交
623 624
	}

S
Sandeep Somavarapu 已提交
625
	private createSearchContainer(searchContainer: HTMLElement) {
S
Sandeep Somavarapu 已提交
626 627
		this.searchContainer = searchContainer;
		const searchInput = DOM.append(this.searchContainer, DOM.$('div.settings-search-input'));
S
Sandeep Somavarapu 已提交
628 629
		this.inputBox = this._register(this.createInputBox(searchInput));
		this._register(this.inputBox.onDidChange(value => this._onDidChange.fire(value)));
S
Sandeep Somavarapu 已提交
630 631
	}

632
	protected createInputBox(parent: HTMLElement): InputBox {
B
Benjamin Pasero 已提交
633 634 635 636
		const box = this._register(new InputBox(parent, this.contextViewService, this.options));
		this._register(attachInputBoxStyler(box, this.themeService));

		return box;
637 638
	}

639
	public showMessage(message: string, count: number): void {
640
		// Avoid setting the aria-label unnecessarily, the screenreader will read the count every time it's set, since it's aria-live:assertive. #50968
641
		if (this.countElement && message !== this.countElement.textContent) {
642 643 644 645 646 647
			this.countElement.textContent = message;
			this.inputBox.inputElement.setAttribute('aria-label', message);
			DOM.toggleClass(this.countElement, 'no-results', count === 0);
			this.inputBox.inputElement.style.paddingRight = this.getControlsWidth() + 'px';
			this.styleCountElementForeground();
		}
648 649 650 651 652 653
	}

	private styleCountElementForeground() {
		const colorId = DOM.hasClass(this.countElement, 'no-results') ? errorForeground : badgeForeground;
		const color = this.themeService.getTheme().getColor(colorId);
		this.countElement.style.color = color ? color.toString() : null;
S
Sandeep Somavarapu 已提交
654 655
	}

656
	public layout(dimension: DOM.Dimension) {
S
Sandeep Somavarapu 已提交
657
		if (dimension.width < 400) {
658 659 660 661
			if (this.countElement) {
				DOM.addClass(this.countElement, 'hide');
			}

S
Sandeep Somavarapu 已提交
662 663
			this.inputBox.inputElement.style.paddingRight = '0px';
		} else {
664 665 666 667
			if (this.countElement) {
				DOM.removeClass(this.countElement, 'hide');
			}

668
			this.inputBox.inputElement.style.paddingRight = this.getControlsWidth() + 'px';
S
Sandeep Somavarapu 已提交
669
		}
S
Sandeep Somavarapu 已提交
670
	}
671

672
	private getControlsWidth(): number {
673
		const countWidth = this.countElement ? DOM.getTotalWidth(this.countElement) : 0;
674
		return countWidth + 20;
675 676
	}

677 678
	public focus() {
		this.inputBox.focus();
S
Sandeep Somavarapu 已提交
679
		if (this.getValue()) {
S
Sandeep Somavarapu 已提交
680 681
			this.inputBox.select();
		}
682 683
	}

S
Sandeep Somavarapu 已提交
684 685 686 687
	public hasFocus(): boolean {
		return this.inputBox.hasFocus();
	}

S
Sandeep Somavarapu 已提交
688 689 690 691
	public clear() {
		this.inputBox.value = '';
	}

S
Sandeep Somavarapu 已提交
692
	public getValue(): string {
693 694 695
		return this.inputBox.value;
	}

S
Sandeep Somavarapu 已提交
696 697 698 699
	public setValue(value: string): string {
		return this.inputBox.value = value;
	}

S
Sandeep Somavarapu 已提交
700 701 702 703 704 705
	public dispose(): void {
		if (this.options.focusKey) {
			this.options.focusKey.set(false);
		}
		super.dispose();
	}
706
}
707

S
Sandeep Somavarapu 已提交
708
export class EditPreferenceWidget<T> extends Disposable {
709

M
Matt Bierner 已提交
710
	public static readonly GLYPH_MARGIN_CLASS_NAME = 'edit-preferences-widget';
711 712 713 714

	private _line: number;
	private _preferences: T[];

S
Sandeep Somavarapu 已提交
715
	private _editPreferenceDecoration: string[];
716

M
Matt Bierner 已提交
717
	private readonly _onClick: Emitter<IEditorMouseEvent> = new Emitter<IEditorMouseEvent>();
S
Sandeep Somavarapu 已提交
718
	public get onClick(): Event<IEditorMouseEvent> { return this._onClick.event; }
719

S
Sandeep Somavarapu 已提交
720
	constructor(private editor: ICodeEditor
721 722
	) {
		super();
S
Sandeep Somavarapu 已提交
723 724
		this._editPreferenceDecoration = [];
		this._register(this.editor.onMouseDown((e: IEditorMouseEvent) => {
J
Joao Moreno 已提交
725 726
			const data = e.target.detail as IMarginData;
			if (e.target.type !== MouseTargetType.GUTTER_GLYPH_MARGIN || data.isAfterLines || !this.isVisible()) {
S
Sandeep Somavarapu 已提交
727
				return;
728
			}
S
Sandeep Somavarapu 已提交
729
			this._onClick.fire(e);
730 731 732
		}));
	}

S
Sandeep Somavarapu 已提交
733 734
	get preferences(): T[] {
		return this._preferences;
735 736 737 738 739 740
	}

	getLine(): number {
		return this._line;
	}

S
Sandeep Somavarapu 已提交
741
	show(line: number, hoverMessage: string, preferences: T[]): void {
742
		this._preferences = preferences;
743
		const newDecoration: IModelDeltaDecoration[] = [];
S
Sandeep Somavarapu 已提交
744 745 746 747
		this._line = line;
		newDecoration.push({
			options: {
				glyphMarginClassName: EditPreferenceWidget.GLYPH_MARGIN_CLASS_NAME,
748
				glyphMarginHoverMessage: new MarkdownString().appendText(hoverMessage),
749
				stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges,
S
Sandeep Somavarapu 已提交
750 751 752 753 754 755 756 757 758
			},
			range: {
				startLineNumber: line,
				startColumn: 1,
				endLineNumber: line,
				endColumn: 1
			}
		});
		this._editPreferenceDecoration = this.editor.deltaDecorations(this._editPreferenceDecoration, newDecoration);
759 760 761
	}

	hide(): void {
S
Sandeep Somavarapu 已提交
762
		this._editPreferenceDecoration = this.editor.deltaDecorations(this._editPreferenceDecoration, []);
763 764
	}

S
Sandeep Somavarapu 已提交
765 766 767
	isVisible(): boolean {
		return this._editPreferenceDecoration.length > 0;
	}
768

S
Sandeep Somavarapu 已提交
769 770 771
	dispose(): void {
		this.hide();
		super.dispose();
772
	}
773
}
S
Sandeep Somavarapu 已提交
774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833

registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => {

	collector.addRule(`
		.settings-tabs-widget > .monaco-action-bar .action-item .action-label:focus,
		.settings-tabs-widget > .monaco-action-bar .action-item .action-label.checked {
			border-bottom: 1px solid;
		}
	`);
	// Title Active
	const titleActive = theme.getColor(PANEL_ACTIVE_TITLE_FOREGROUND);
	const titleActiveBorder = theme.getColor(PANEL_ACTIVE_TITLE_BORDER);
	if (titleActive || titleActiveBorder) {
		collector.addRule(`
			.settings-tabs-widget > .monaco-action-bar .action-item .action-label:hover,
			.settings-tabs-widget > .monaco-action-bar .action-item .action-label.checked {
				color: ${titleActive};
				border-bottom-color: ${titleActiveBorder};
			}
		`);
	}

	// Title Inactive
	const titleInactive = theme.getColor(PANEL_INACTIVE_TITLE_FOREGROUND);
	if (titleInactive) {
		collector.addRule(`
			.settings-tabs-widget > .monaco-action-bar .action-item .action-label {
				color: ${titleInactive};
			}
		`);
	}

	// Title focus
	const focusBorderColor = theme.getColor(focusBorder);
	if (focusBorderColor) {
		collector.addRule(`
			.settings-tabs-widget > .monaco-action-bar .action-item .action-label:focus {
				border-bottom-color: ${focusBorderColor} !important;
			}
			`);
		collector.addRule(`
			.settings-tabs-widget > .monaco-action-bar .action-item .action-label:focus {
				outline: none;
			}
			`);
	}

	// Styling with Outline color (e.g. high contrast theme)
	const outline = theme.getColor(activeContrastBorder);
	if (outline) {
		const outline = theme.getColor(activeContrastBorder);

		collector.addRule(`
			.settings-tabs-widget > .monaco-action-bar .action-item .action-label.checked,
			.settings-tabs-widget > .monaco-action-bar .action-item .action-label:hover {
				outline-color: ${outline};
				outline-width: 1px;
				outline-style: solid;
				border-bottom: none;
				padding-bottom: 0;
S
Sandeep Somavarapu 已提交
834
				outline-offset: 2px;
S
Sandeep Somavarapu 已提交
835 836 837 838 839 840 841
			}

			.settings-tabs-widget > .monaco-action-bar .action-item .action-label:not(.checked):hover {
				outline-style: dashed;
			}
		`);
	}
842
});