findWidget.ts 37.9 KB
Newer Older
E
Erich Gamma 已提交
1 2 3 4 5 6 7 8
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/

'use strict';

import 'vs/css!./findWidget';
A
Alex Dima 已提交
9
import * as nls from 'vs/nls';
J
Johannes Rieken 已提交
10 11
import { onUnexpectedError } from 'vs/base/common/errors';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
12
import * as platform from 'vs/base/common/platform';
A
Alex Dima 已提交
13
import * as strings from 'vs/base/common/strings';
14
import { Delayer } from 'vs/base/common/async';
A
Alex Dima 已提交
15
import * as dom from 'vs/base/browser/dom';
J
Johannes Rieken 已提交
16
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
17
import { IMouseEvent } from 'vs/base/browser/mouseEvent';
J
Johannes Rieken 已提交
18
import { IContextViewProvider } from 'vs/base/browser/ui/contextview/contextview';
19
import { FindInput, IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput';
20
import { IMessage as InputBoxMessage, HistoryInputBox } from 'vs/base/browser/ui/inputbox/inputBox';
J
Johannes Rieken 已提交
21
import { Widget } from 'vs/base/browser/ui/widget';
22
import { Sash, IHorizontalSashLayoutProvider, ISashEvent, Orientation } from 'vs/base/browser/ui/sash/sash';
J
Johannes Rieken 已提交
23
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
24
import { ICodeEditor, IOverlayWidget, IOverlayWidgetPosition, IViewZone, OverlayWidgetPositionPreference } from 'vs/editor/browser/editorBrowser';
25
import { FIND_IDS, MATCHES_LIMIT, CONTEXT_FIND_INPUT_FOCUSED, CONTEXT_REPLACE_INPUT_FOCUSED } from 'vs/editor/contrib/find/findModel';
26
import { FindReplaceState, FindReplaceStateChangedEvent } from 'vs/editor/contrib/find/findState';
J
Johannes Rieken 已提交
27 28
import { Range } from 'vs/editor/common/core/range';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
29
import { ITheme, registerThemingParticipant, IThemeService } from 'vs/platform/theme/common/themeService';
30
import { Color } from 'vs/base/common/color';
31
import { IConfigurationChangedEvent } from 'vs/editor/common/config/editorOptions';
32
import { editorFindRangeHighlight, editorFindMatch, editorFindMatchHighlight, contrastBorder, inputBackground, editorWidgetBackground, inputActiveOptionBorder, widgetShadow, inputForeground, inputBorder, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationErrorBackground, inputValidationErrorBorder, errorForeground, editorWidgetBorder, editorFindMatchBorder, editorFindMatchHighlightBorder, editorFindRangeHighlightBorder, editorWidgetResizeBorder } from 'vs/platform/theme/common/colorRegistry';
33
import { ContextScopedFindInput, ContextScopedHistoryInputBox } from 'vs/platform/widget/browser/contextScopedHistoryWidget';
34

35

E
Erich Gamma 已提交
36 37 38
export interface IFindController {
	replace(): void;
	replaceAll(): void;
39
	getGlobalBufferTerm(): string;
E
Erich Gamma 已提交
40 41
}

42 43 44 45 46 47 48 49 50 51 52
const NLS_FIND_INPUT_LABEL = nls.localize('label.find', "Find");
const NLS_FIND_INPUT_PLACEHOLDER = nls.localize('placeholder.find', "Find");
const NLS_PREVIOUS_MATCH_BTN_LABEL = nls.localize('label.previousMatchButton', "Previous match");
const NLS_NEXT_MATCH_BTN_LABEL = nls.localize('label.nextMatchButton', "Next match");
const NLS_TOGGLE_SELECTION_FIND_TITLE = nls.localize('label.toggleSelectionFind', "Find in selection");
const NLS_CLOSE_BTN_LABEL = nls.localize('label.closeButton', "Close");
const NLS_REPLACE_INPUT_LABEL = nls.localize('label.replace', "Replace");
const NLS_REPLACE_INPUT_PLACEHOLDER = nls.localize('placeholder.replace', "Replace");
const NLS_REPLACE_BTN_LABEL = nls.localize('label.replaceButton', "Replace");
const NLS_REPLACE_ALL_BTN_LABEL = nls.localize('label.replaceAllButton', "Replace All");
const NLS_TOGGLE_REPLACE_MODE_BTN_LABEL = nls.localize('label.toggleReplaceButton', "Toggle Replace mode");
A
Alex Dima 已提交
53
const NLS_MATCHES_COUNT_LIMIT_TITLE = nls.localize('title.matchesCountLimit', "Only the first {0} results are highlighted, but all find operations work on the entire text.", MATCHES_LIMIT);
54
const NLS_MATCHES_LOCATION = nls.localize('label.matchesLocation', "{0} of {1}");
I
isidor 已提交
55
const NLS_NO_RESULTS = nls.localize('label.noResults', "No Results");
56

57 58 59 60 61
const FIND_WIDGET_INITIAL_WIDTH = 411;
const PART_WIDTH = 275;
const FIND_INPUT_AREA_WIDTH = PART_WIDTH - 54;
const REPLACE_INPUT_AREA_WIDTH = FIND_INPUT_AREA_WIDTH;

62
let MAX_MATCHES_COUNT_WIDTH = 69;
63 64 65 66
let FIND_ALL_CONTROLS_WIDTH = 17/** Find Input margin-left */ + (MAX_MATCHES_COUNT_WIDTH + 3 + 1) /** Match Results */ + 23 /** Button */ * 4 + 2/** sash */;

const FIND_INPUT_AREA_HEIGHT = 34; // The height of Find Widget when Replace Input is not visible.
const FIND_REPLACE_AREA_HEIGHT = 64; // The height of Find Widget when Replace Input is  visible.
67

68

69
export class FindWidgetViewZone implements IViewZone {
M
Matt Bierner 已提交
70
	public readonly afterLineNumber: number;
71
	public heightInPx: number;
M
Matt Bierner 已提交
72 73
	public readonly suppressMouseDown: boolean;
	public readonly domNode: HTMLElement;
74 75 76 77

	constructor(afterLineNumber: number) {
		this.afterLineNumber = afterLineNumber;

78
		this.heightInPx = FIND_INPUT_AREA_HEIGHT;
79 80 81 82 83 84
		this.suppressMouseDown = false;
		this.domNode = document.createElement('div');
		this.domNode.className = 'dock-find-viewzone';
	}
}

R
rebornix 已提交
85
export class FindWidget extends Widget implements IOverlayWidget, IHorizontalSashLayoutProvider {
86
	private static readonly ID = 'editor.contrib.findWidget';
M
Matt Bierner 已提交
87
	private readonly _codeEditor: ICodeEditor;
A
Alex Dima 已提交
88
	private _state: FindReplaceState;
E
Erich Gamma 已提交
89
	private _controller: IFindController;
M
Matt Bierner 已提交
90 91
	private readonly _contextViewProvider: IContextViewProvider;
	private readonly _keybindingService: IKeybindingService;
S
#50583  
Sandeep Somavarapu 已提交
92
	private readonly _contextKeyService: IContextKeyService;
E
Erich Gamma 已提交
93

A
Alex Dima 已提交
94 95
	private _domNode: HTMLElement;
	private _findInput: FindInput;
96
	private _replaceInputBox: HistoryInputBox;
E
Erich Gamma 已提交
97

A
Alex Dima 已提交
98
	private _toggleReplaceBtn: SimpleButton;
99
	private _matchesCount: HTMLElement;
A
Alex Dima 已提交
100 101
	private _prevBtn: SimpleButton;
	private _nextBtn: SimpleButton;
102
	private _toggleSelectionFind: SimpleCheckbox;
A
Alex Dima 已提交
103 104 105
	private _closeBtn: SimpleButton;
	private _replaceBtn: SimpleButton;
	private _replaceAllBtn: SimpleButton;
E
Erich Gamma 已提交
106

A
Alex Dima 已提交
107 108
	private _isVisible: boolean;
	private _isReplaceVisible: boolean;
E
Erich Gamma 已提交
109

110
	private _findFocusTracker: dom.IFocusTracker;
A
Alex Dima 已提交
111
	private _findInputFocused: IContextKey<boolean>;
112 113
	private _replaceFocusTracker: dom.IFocusTracker;
	private _replaceInputFocused: IContextKey<boolean>;
114 115
	private _viewZone: FindWidgetViewZone;
	private _viewZoneId: number;
116

R
rebornix 已提交
117
	private _resizeSash: Sash;
118
	private _resized: boolean;
119
	private _updateHistoryDelayer: Delayer<void>;
R
rebornix 已提交
120

121
	constructor(
A
Alex Dima 已提交
122
		codeEditor: ICodeEditor,
A
Alex Dima 已提交
123 124 125
		controller: IFindController,
		state: FindReplaceState,
		contextViewProvider: IContextViewProvider,
S
Sandeep Somavarapu 已提交
126
		keybindingService: IKeybindingService,
127 128
		contextKeyService: IContextKeyService,
		themeService: IThemeService
129
	) {
A
Alex Dima 已提交
130
		super();
E
Erich Gamma 已提交
131 132
		this._codeEditor = codeEditor;
		this._controller = controller;
A
Alex Dima 已提交
133
		this._state = state;
E
Erich Gamma 已提交
134
		this._contextViewProvider = contextViewProvider;
135
		this._keybindingService = keybindingService;
S
#50583  
Sandeep Somavarapu 已提交
136
		this._contextKeyService = contextKeyService;
E
Erich Gamma 已提交
137 138 139 140

		this._isVisible = false;
		this._isReplaceVisible = false;

141
		this._updateHistoryDelayer = new Delayer<void>(500);
R
rebornix 已提交
142
		this._register(this._state.onFindReplaceStateChange((e) => this._onStateChanged(e)));
E
Erich Gamma 已提交
143
		this._buildDomNode();
144
		this._updateButtons();
145
		this._tryUpdateWidgetWidth();
146

J
Johannes Rieken 已提交
147
		this._register(this._codeEditor.onDidChangeConfiguration((e: IConfigurationChangedEvent) => {
148
			if (e.readOnly) {
149 150 151 152 153 154
				if (this._codeEditor.getConfiguration().readOnly) {
					// Hide replace part if editor becomes read only
					this._state.change({ isReplaceRevealed: false }, false);
				}
				this._updateButtons();
			}
155
			if (e.layoutInfo) {
156
				this._tryUpdateWidgetWidth();
157
			}
158
		}));
A
Alex Dima 已提交
159
		this._register(this._codeEditor.onDidChangeCursorSelection(() => {
160 161
			if (this._isVisible) {
				this._updateToggleSelectionFindButton();
162 163
			}
		}));
A
Alex Dima 已提交
164
		this._register(this._codeEditor.onDidFocusEditorWidget(() => {
165 166 167 168 169 170 171 172
			if (this._isVisible) {
				let globalBufferTerm = this._controller.getGlobalBufferTerm();
				if (globalBufferTerm && globalBufferTerm !== this._state.searchString) {
					this._state.change({ searchString: globalBufferTerm }, true);
					this._findInput.select();
				}
			}
		}));
A
Alex Dima 已提交
173
		this._findInputFocused = CONTEXT_FIND_INPUT_FOCUSED.bindTo(contextKeyService);
174 175
		this._findFocusTracker = this._register(dom.trackFocus(this._findInput.inputBox.inputElement));
		this._register(this._findFocusTracker.onDidFocus(() => {
A
Alex Dima 已提交
176
			this._findInputFocused.set(true);
177
			this._updateSearchScope();
178
		}));
179
		this._register(this._findFocusTracker.onDidBlur(() => {
A
Alex Dima 已提交
180
			this._findInputFocused.set(false);
181
		}));
182

183 184 185 186 187 188 189 190 191 192
		this._replaceInputFocused = CONTEXT_REPLACE_INPUT_FOCUSED.bindTo(contextKeyService);
		this._replaceFocusTracker = this._register(dom.trackFocus(this._replaceInputBox.inputElement));
		this._register(this._replaceFocusTracker.onDidFocus(() => {
			this._replaceInputFocused.set(true);
			this._updateSearchScope();
		}));
		this._register(this._replaceFocusTracker.onDidBlur(() => {
			this._replaceInputFocused.set(false);
		}));

A
Alex Dima 已提交
193
		this._codeEditor.addOverlayWidget(this);
194
		this._viewZone = new FindWidgetViewZone(0); // Put it before the first line then users can scroll beyond the first line.
195 196 197

		this._applyTheme(themeService.getTheme());
		this._register(themeService.onThemeChange(this._applyTheme.bind(this)));
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213

		this._register(this._codeEditor.onDidChangeModel((e) => {
			if (!this._isVisible) {
				return;
			}

			if (this._viewZoneId === undefined) {
				return;
			}

			this._codeEditor.changeViewZones((accessor) => {
				accessor.removeZone(this._viewZoneId);
				this._viewZoneId = undefined;
			});
		}));

R
rebornix 已提交
214

215
		this._register(this._codeEditor.onDidScrollChange((e) => {
216 217
			if (e.scrollTopChanged) {
				this._layoutViewZone();
R
rebornix 已提交
218
				return;
219
			}
R
rebornix 已提交
220 221 222 223 224

			// for other scroll changes, layout the viewzone in next tick to avoid ruining current rendering.
			setTimeout(() => {
				this._layoutViewZone();
			}, 0);
225
		}));
E
Erich Gamma 已提交
226 227 228 229 230 231 232 233 234 235 236 237
	}

	// ----- IOverlayWidget API

	public getId(): string {
		return FindWidget.ID;
	}

	public getDomNode(): HTMLElement {
		return this._domNode;
	}

A
Alex Dima 已提交
238
	public getPosition(): IOverlayWidgetPosition {
E
Erich Gamma 已提交
239 240
		if (this._isVisible) {
			return {
A
Alex Dima 已提交
241
				preference: OverlayWidgetPositionPreference.TOP_RIGHT_CORNER
E
Erich Gamma 已提交
242 243 244 245 246
			};
		}
		return null;
	}

A
Alex Dima 已提交
247
	// ----- React to state changes
E
Erich Gamma 已提交
248

J
Johannes Rieken 已提交
249
	private _onStateChanged(e: FindReplaceStateChangedEvent): void {
A
Alex Dima 已提交
250 251
		if (e.searchString) {
			this._findInput.setValue(this._state.searchString);
252
			this._updateButtons();
A
Alex Dima 已提交
253 254 255 256 257 258 259 260 261 262 263 264 265
		}
		if (e.replaceString) {
			this._replaceInputBox.value = this._state.replaceString;
		}
		if (e.isRevealed) {
			if (this._state.isRevealed) {
				this._reveal(true);
			} else {
				this._hide(true);
			}
		}
		if (e.isReplaceRevealed) {
			if (this._state.isReplaceRevealed) {
266 267
				if (!this._codeEditor.getConfiguration().readOnly && !this._isReplaceVisible) {
					this._isReplaceVisible = true;
Y
Yang Liu 已提交
268
					this._replaceInputBox.width = this._findInput.inputBox.width;
269 270
					this._updateButtons();
				}
A
Alex Dima 已提交
271
			} else {
272 273 274 275
				if (this._isReplaceVisible) {
					this._isReplaceVisible = false;
					this._updateButtons();
				}
A
Alex Dima 已提交
276 277 278 279 280 281 282 283 284 285 286 287 288
			}
		}
		if (e.isRegex) {
			this._findInput.setRegex(this._state.isRegex);
		}
		if (e.wholeWord) {
			this._findInput.setWholeWords(this._state.wholeWord);
		}
		if (e.matchCase) {
			this._findInput.setCaseSensitive(this._state.matchCase);
		}
		if (e.searchScope) {
			if (this._state.searchScope) {
289
				this._toggleSelectionFind.checked = true;
A
Alex Dima 已提交
290
			} else {
291
				this._toggleSelectionFind.checked = false;
A
Alex Dima 已提交
292 293 294
			}
			this._updateToggleSelectionFindButton();
		}
295
		if (e.searchString || e.matchesCount || e.matchesPosition) {
A
Alex Dima 已提交
296
			let showRedOutline = (this._state.searchString.length > 0 && this._state.matchesCount === 0);
A
Alex Dima 已提交
297
			dom.toggleClass(this._domNode, 'no-results', showRedOutline);
A
Alex Dima 已提交
298

299 300
			this._updateMatchesCount();
		}
301 302 303
		if (e.searchString || e.currentMatch) {
			this._layoutViewZone();
		}
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319
		if (e.updateHistory) {
			this._delayedUpdateHistory();
		}
	}

	private _delayedUpdateHistory() {
		this._updateHistoryDelayer.trigger(this._updateHistory.bind(this));
	}

	private _updateHistory() {
		if (this._state.searchString) {
			this._findInput.inputBox.addToHistory(this._state.searchString);
		}
		if (this._state.replaceString) {
			this._replaceInputBox.addToHistory(this._state.replaceString);
		}
320 321 322
	}

	private _updateMatchesCount(): void {
323
		this._matchesCount.style.minWidth = MAX_MATCHES_COUNT_WIDTH + 'px';
324 325 326 327 328
		if (this._state.matchesCount >= MATCHES_LIMIT) {
			this._matchesCount.title = NLS_MATCHES_COUNT_LIMIT_TITLE;
		} else {
			this._matchesCount.title = '';
		}
A
Alex Dima 已提交
329

330 331 332 333 334 335
		// remove previous content
		if (this._matchesCount.firstChild) {
			this._matchesCount.removeChild(this._matchesCount.firstChild);
		}

		let label: string;
336
		if (this._state.matchesCount > 0) {
J
Johannes Rieken 已提交
337
			let matchesCount: string = String(this._state.matchesCount);
A
Alex Dima 已提交
338 339 340
			if (this._state.matchesCount >= MATCHES_LIMIT) {
				matchesCount += '+';
			}
J
Johannes Rieken 已提交
341
			let matchesPosition: string = String(this._state.matchesPosition);
342 343 344
			if (matchesPosition === '0') {
				matchesPosition = '?';
			}
A
Alex Dima 已提交
345
			label = strings.format(NLS_MATCHES_LOCATION, matchesPosition, matchesCount);
346
		} else {
347
			label = NLS_NO_RESULTS;
E
Erich Gamma 已提交
348
		}
349
		this._matchesCount.appendChild(document.createTextNode(label));
350 351

		MAX_MATCHES_COUNT_WIDTH = Math.max(MAX_MATCHES_COUNT_WIDTH, this._matchesCount.clientWidth);
E
Erich Gamma 已提交
352 353
	}

354 355 356 357
	// ----- actions

	/**
	 * If 'selection find' is ON we should not disable the button (its function is to cancel 'selection find').
358
	 * If 'selection find' is OFF we enable the button only if there is a selection.
359 360 361
	 */
	private _updateToggleSelectionFindButton(): void {
		let selection = this._codeEditor.getSelection();
362
		let isSelection = selection ? (selection.startLineNumber !== selection.endLineNumber || selection.startColumn !== selection.endColumn) : false;
363 364
		let isChecked = this._toggleSelectionFind.checked;

365
		this._toggleSelectionFind.setEnabled(this._isVisible && (isChecked || isSelection));
366 367 368 369 370 371 372 373 374 375 376 377 378 379
	}

	private _updateButtons(): void {
		this._findInput.setEnabled(this._isVisible);
		this._replaceInputBox.setEnabled(this._isVisible && this._isReplaceVisible);
		this._updateToggleSelectionFindButton();
		this._closeBtn.setEnabled(this._isVisible);

		let findInputIsNonEmpty = (this._state.searchString.length > 0);
		this._prevBtn.setEnabled(this._isVisible && findInputIsNonEmpty);
		this._nextBtn.setEnabled(this._isVisible && findInputIsNonEmpty);
		this._replaceBtn.setEnabled(this._isVisible && this._isReplaceVisible && findInputIsNonEmpty);
		this._replaceAllBtn.setEnabled(this._isVisible && this._isReplaceVisible && findInputIsNonEmpty);

A
Alex Dima 已提交
380
		dom.toggleClass(this._domNode, 'replaceToggled', this._isReplaceVisible);
381 382 383 384 385 386 387 388
		this._toggleReplaceBtn.toggleClass('collapse', !this._isReplaceVisible);
		this._toggleReplaceBtn.toggleClass('expand', this._isReplaceVisible);
		this._toggleReplaceBtn.setExpanded(this._isReplaceVisible);

		let canReplace = !this._codeEditor.getConfiguration().readOnly;
		this._toggleReplaceBtn.setEnabled(this._isVisible && canReplace);
	}

J
Johannes Rieken 已提交
389
	private _reveal(animate: boolean): void {
390 391 392
		if (!this._isVisible) {
			this._isVisible = true;

R
rebornix 已提交
393 394 395 396 397 398 399
			let selection = this._codeEditor.getSelection();
			let isSelection = selection ? (selection.startLineNumber !== selection.endLineNumber || selection.startColumn !== selection.endColumn) : false;
			if (isSelection && this._codeEditor.getConfiguration().contribInfo.find.autoFindInSelection) {
				this._toggleSelectionFind.checked = true;
			} else {
				this._toggleSelectionFind.checked = false;
			}
400
			this._tryUpdateWidgetWidth();
401 402 403
			this._updateButtons();

			setTimeout(() => {
A
Alex Dima 已提交
404
				dom.addClass(this._domNode, 'visible');
405
				this._domNode.setAttribute('aria-hidden', 'false');
406 407
			}, 0);
			this._codeEditor.layoutOverlayWidget(this);
R
rebornix 已提交
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432

			let adjustEditorScrollTop = true;
			if (this._codeEditor.getConfiguration().contribInfo.find.seedSearchStringFromSelection && selection) {
				let editorCoords = dom.getDomNodePagePosition(this._codeEditor.getDomNode());
				let startCoords = this._codeEditor.getScrolledVisiblePosition(selection.getStartPosition());
				let startLeft = editorCoords.left + startCoords.left;
				let startTop = startCoords.top;

				if (startTop < this._viewZone.heightInPx) {
					if (selection.endLineNumber > selection.startLineNumber) {
						adjustEditorScrollTop = false;
					}

					let leftOfFindWidget = dom.getTopLeftOffset(this._domNode).left;
					if (startLeft > leftOfFindWidget) {
						adjustEditorScrollTop = false;
					}
					let endCoords = this._codeEditor.getScrolledVisiblePosition(selection.getEndPosition());
					let endLeft = editorCoords.left + endCoords.left;
					if (endLeft > leftOfFindWidget) {
						adjustEditorScrollTop = false;
					}
				}
			}
			this._showViewZone(adjustEditorScrollTop);
433 434 435
		}
	}

J
Johannes Rieken 已提交
436
	private _hide(focusTheEditor: boolean): void {
437 438 439 440 441
		if (this._isVisible) {
			this._isVisible = false;

			this._updateButtons();

A
Alex Dima 已提交
442
			dom.removeClass(this._domNode, 'visible');
443
			this._domNode.setAttribute('aria-hidden', 'true');
444 445 446 447
			if (focusTheEditor) {
				this._codeEditor.focus();
			}
			this._codeEditor.layoutOverlayWidget(this);
448 449 450 451 452 453 454 455 456 457 458 459 460
			this._codeEditor.changeViewZones((accessor) => {
				if (this._viewZoneId !== undefined) {
					accessor.removeZone(this._viewZoneId);
					this._viewZoneId = undefined;
					this._codeEditor.setScrollTop(this._codeEditor.getScrollTop() - this._viewZone.heightInPx);
				}
			});
		}
	}

	private _layoutViewZone() {
		if (!this._isVisible) {
			return;
461
		}
462 463 464 465 466 467 468

		if (this._viewZoneId !== undefined) {
			return;
		}

		this._codeEditor.changeViewZones((accessor) => {
			if (this._state.isReplaceRevealed) {
469
				this._viewZone.heightInPx = FIND_REPLACE_AREA_HEIGHT;
470
			} else {
471
				this._viewZone.heightInPx = FIND_INPUT_AREA_HEIGHT;
472 473 474
			}

			this._viewZoneId = accessor.addZone(this._viewZone);
R
rebornix 已提交
475
			// scroll top adjust to make sure the editor doesn't scroll when adding viewzone at the beginning.
476 477 478 479
			this._codeEditor.setScrollTop(this._codeEditor.getScrollTop() + this._viewZone.heightInPx);
		});
	}

R
rebornix 已提交
480
	private _showViewZone(adjustScroll: boolean = true) {
481 482 483 484 485
		if (!this._isVisible) {
			return;
		}

		this._codeEditor.changeViewZones((accessor) => {
486
			let scrollAdjustment = FIND_INPUT_AREA_HEIGHT;
487 488 489

			if (this._viewZoneId !== undefined) {
				if (this._state.isReplaceRevealed) {
490 491
					this._viewZone.heightInPx = FIND_REPLACE_AREA_HEIGHT;
					scrollAdjustment = FIND_REPLACE_AREA_HEIGHT - FIND_INPUT_AREA_HEIGHT;
492
				} else {
493 494
					this._viewZone.heightInPx = FIND_INPUT_AREA_HEIGHT;
					scrollAdjustment = FIND_INPUT_AREA_HEIGHT - FIND_REPLACE_AREA_HEIGHT;
495 496 497
				}
				accessor.removeZone(this._viewZoneId);
			} else {
498
				this._viewZone.heightInPx = FIND_INPUT_AREA_HEIGHT;
499 500
			}
			this._viewZoneId = accessor.addZone(this._viewZone);
R
rebornix 已提交
501 502 503 504

			if (adjustScroll) {
				this._codeEditor.setScrollTop(this._codeEditor.getScrollTop() + scrollAdjustment);
			}
505
		});
506 507
	}

508
	private _applyTheme(theme: ITheme) {
509 510 511 512
		let inputStyles: IFindInputStyles = {
			inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder),
			inputBackground: theme.getColor(inputBackground),
			inputForeground: theme.getColor(inputForeground),
513
			inputBorder: theme.getColor(inputBorder),
B
Benjamin Pasero 已提交
514 515 516 517 518 519
			inputValidationInfoBackground: theme.getColor(inputValidationInfoBackground),
			inputValidationInfoBorder: theme.getColor(inputValidationInfoBorder),
			inputValidationWarningBackground: theme.getColor(inputValidationWarningBackground),
			inputValidationWarningBorder: theme.getColor(inputValidationWarningBorder),
			inputValidationErrorBackground: theme.getColor(inputValidationErrorBackground),
			inputValidationErrorBorder: theme.getColor(inputValidationErrorBorder)
520
		};
521
		this._findInput.style(inputStyles);
522
		this._replaceInputBox.style(inputStyles);
523 524
	}

525 526 527 528 529 530 531 532 533 534
	private _tryUpdateWidgetWidth() {
		if (!this._isVisible) {
			return;
		}
		let editorWidth = this._codeEditor.getConfiguration().layoutInfo.width;
		let minimapWidth = this._codeEditor.getConfiguration().layoutInfo.minimapWidth;
		let collapsedFindWidget = false;
		let reducedFindWidget = false;
		let narrowFindWidget = false;

535 536 537 538 539 540 541 542 543
		if (this._resized) {
			let widgetWidth = dom.getTotalWidth(this._domNode);

			if (widgetWidth > FIND_WIDGET_INITIAL_WIDTH) {
				// as the widget is resized by users, we may need to change the max width of the widget as the editor width changes.
				this._domNode.style.maxWidth = `${editorWidth - 28 - minimapWidth - 15}px`;
				this._replaceInputBox.inputElement.style.width = `${dom.getTotalWidth(this._findInput.inputBox.inputElement)}px`;
				return;
			}
544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563
		}

		if (FIND_WIDGET_INITIAL_WIDTH + 28 + minimapWidth >= editorWidth) {
			reducedFindWidget = true;
		}
		if (FIND_WIDGET_INITIAL_WIDTH + 28 + minimapWidth - MAX_MATCHES_COUNT_WIDTH >= editorWidth) {
			narrowFindWidget = true;
		}
		if (FIND_WIDGET_INITIAL_WIDTH + 28 + minimapWidth - MAX_MATCHES_COUNT_WIDTH >= editorWidth + 50) {
			collapsedFindWidget = true;
		}
		dom.toggleClass(this._domNode, 'collapsed-find-widget', collapsedFindWidget);
		dom.toggleClass(this._domNode, 'narrow-find-widget', narrowFindWidget);
		dom.toggleClass(this._domNode, 'reduced-find-widget', reducedFindWidget);

		if (!narrowFindWidget && !collapsedFindWidget) {
			// the minimal left offset of findwidget is 15px.
			this._domNode.style.maxWidth = `${editorWidth - 28 - minimapWidth - 15}px`;
		}

564 565 566 567 568
		if (this._resized) {
			let findInputWidth = dom.getTotalWidth(this._findInput.inputBox.inputElement);
			if (findInputWidth > 0) {
				this._replaceInputBox.inputElement.style.width = `${findInputWidth}px`;
			}
569 570 571
		}
	}

E
Erich Gamma 已提交
572 573
	// ----- Public

574 575 576 577 578 579 580 581 582 583 584 585
	public focusFindInput(): void {
		this._findInput.select();
		// Edge browser requires focus() in addition to select()
		this._findInput.focus();
	}

	public focusReplaceInput(): void {
		this._replaceInputBox.select();
		// Edge browser requires focus() in addition to select()
		this._replaceInputBox.focus();
	}

586 587 588 589
	public highlightFindOptions(): void {
		this._findInput.highlightFindOptions();
	}

590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605
	private _updateSearchScope(): void {
		if (this._toggleSelectionFind.checked) {
			let selection = this._codeEditor.getSelection();
			if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) {
				selection = selection.setEndPosition(selection.endLineNumber - 1, 1);
			}
			let currentMatch = this._state.currentMatch;
			if (selection.startLineNumber !== selection.endLineNumber) {
				if (!Range.equalsRange(selection, currentMatch)) {
					// Reseed find scope
					this._state.change({ searchScope: selection }, true);
				}
			}
		}
	}

606 607 608 609 610 611 612
	private _onFindInputMouseDown(e: IMouseEvent): void {
		// on linux, middle key does pasting.
		if (e.middleButton) {
			e.stopPropagation();
		}
	}

J
Johannes Rieken 已提交
613
	private _onFindInputKeyDown(e: IKeyboardEvent): void {
E
Erich Gamma 已提交
614

A
Alex Dima 已提交
615 616 617 618 619
		if (e.equals(KeyCode.Enter)) {
			this._codeEditor.getAction(FIND_IDS.NextMatchFindAction).run().done(null, onUnexpectedError);
			e.preventDefault();
			return;
		}
620

A
Alex Dima 已提交
621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640
		if (e.equals(KeyMod.Shift | KeyCode.Enter)) {
			this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction).run().done(null, onUnexpectedError);
			e.preventDefault();
			return;
		}

		if (e.equals(KeyCode.Tab)) {
			if (this._isReplaceVisible) {
				this._replaceInputBox.focus();
			} else {
				this._findInput.focusOnCaseSensitive();
			}
			e.preventDefault();
			return;
		}

		if (e.equals(KeyMod.CtrlCmd | KeyCode.DownArrow)) {
			this._codeEditor.focus();
			e.preventDefault();
			return;
E
Erich Gamma 已提交
641 642 643
		}
	}

J
Johannes Rieken 已提交
644
	private _onReplaceInputKeyDown(e: IKeyboardEvent): void {
E
Erich Gamma 已提交
645

A
Alex Dima 已提交
646 647 648 649 650
		if (e.equals(KeyCode.Enter)) {
			this._controller.replace();
			e.preventDefault();
			return;
		}
E
Erich Gamma 已提交
651

A
Alex Dima 已提交
652 653 654 655 656
		if (e.equals(KeyMod.CtrlCmd | KeyCode.Enter)) {
			this._controller.replaceAll();
			e.preventDefault();
			return;
		}
657

A
Alex Dima 已提交
658 659 660 661 662
		if (e.equals(KeyCode.Tab)) {
			this._findInput.focusOnCaseSensitive();
			e.preventDefault();
			return;
		}
663

A
Alex Dima 已提交
664 665 666 667 668
		if (e.equals(KeyMod.Shift | KeyCode.Tab)) {
			this._findInput.focus();
			e.preventDefault();
			return;
		}
669

A
Alex Dima 已提交
670 671 672 673
		if (e.equals(KeyMod.CtrlCmd | KeyCode.DownArrow)) {
			this._codeEditor.focus();
			e.preventDefault();
			return;
E
Erich Gamma 已提交
674 675 676
		}
	}

R
rebornix 已提交
677 678 679 680 681 682 683 684 685 686 687
	// ----- sash
	public getHorizontalSashTop(sash: Sash): number {
		return 0;
	}
	public getHorizontalSashLeft?(sash: Sash): number {
		return 0;
	}
	public getHorizontalSashWidth?(sash: Sash): number {
		return 500;
	}

A
Alex Dima 已提交
688
	// ----- initialization
E
Erich Gamma 已提交
689

J
Johannes Rieken 已提交
690
	private _keybindingLabelFor(actionId: string): string {
691
		let kb = this._keybindingService.lookupKeybinding(actionId);
B
Benjamin Pasero 已提交
692
		if (!kb) {
693 694
			return '';
		}
695
		return ` (${kb.getLabel()})`;
696 697
	}

E
Erich Gamma 已提交
698 699
	private _buildFindPart(): HTMLElement {
		// Find input
S
#50583  
Sandeep Somavarapu 已提交
700
		this._findInput = this._register(new ContextScopedFindInput(null, this._contextViewProvider, {
701
			width: FIND_INPUT_AREA_WIDTH,
702 703
			label: NLS_FIND_INPUT_LABEL,
			placeholder: NLS_FIND_INPUT_PLACEHOLDER,
A
Alex Dima 已提交
704 705 706
			appendCaseSensitiveLabel: this._keybindingLabelFor(FIND_IDS.ToggleCaseSensitiveCommand),
			appendWholeWordsLabel: this._keybindingLabelFor(FIND_IDS.ToggleWholeWordCommand),
			appendRegexLabel: this._keybindingLabelFor(FIND_IDS.ToggleRegexCommand),
J
Johannes Rieken 已提交
707
			validation: (value: string): InputBoxMessage => {
E
Erich Gamma 已提交
708 709 710 711 712 713 714
				if (value.length === 0) {
					return null;
				}
				if (!this._findInput.getRegex()) {
					return null;
				}
				try {
A
tslint  
Alex Dima 已提交
715
					/* tslint:disable:no-unused-expression */
A
tslint  
Alex Dima 已提交
716
					new RegExp(value);
A
tslint  
Alex Dima 已提交
717
					/* tslint:enable:no-unused-expression */
E
Erich Gamma 已提交
718 719 720 721 722
					return null;
				} catch (e) {
					return { content: e.message };
				}
			}
S
#50583  
Sandeep Somavarapu 已提交
723
		}, this._contextKeyService));
724 725 726
		this._findInput.setRegex(!!this._state.isRegex);
		this._findInput.setCaseSensitive(!!this._state.matchCase);
		this._findInput.setWholeWords(!!this._state.wholeWord);
A
Alex Dima 已提交
727
		this._register(this._findInput.onKeyDown((e) => this._onFindInputKeyDown(e)));
728
		this._register(this._findInput.inputBox.onDidChange(() => {
729 730
			this._state.change({ searchString: this._findInput.getValue() }, true);
		}));
A
Alex Dima 已提交
731
		this._register(this._findInput.onDidOptionChange(() => {
A
Alex Dima 已提交
732 733 734 735 736
			this._state.change({
				isRegex: this._findInput.getRegex(),
				wholeWord: this._findInput.getWholeWords(),
				matchCase: this._findInput.getCaseSensitive()
			}, true);
A
Alex Dima 已提交
737
		}));
738
		this._register(this._findInput.onCaseSensitiveKeyDown((e) => {
A
Alexandru Dima 已提交
739
			if (e.equals(KeyMod.Shift | KeyCode.Tab)) {
740 741 742 743 744 745
				if (this._isReplaceVisible) {
					this._replaceInputBox.focus();
					e.preventDefault();
				}
			}
		}));
746 747 748
		if (platform.isLinux) {
			this._register(this._findInput.onMouseDown((e) => this._onFindInputMouseDown(e)));
		}
E
Erich Gamma 已提交
749

750 751 752 753
		this._matchesCount = document.createElement('div');
		this._matchesCount.className = 'matchesCount';
		this._updateMatchesCount();

E
Erich Gamma 已提交
754
		// Previous button
A
Alex Dima 已提交
755
		this._prevBtn = this._register(new SimpleButton({
A
Alex Dima 已提交
756
			label: NLS_PREVIOUS_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.PreviousMatchFindAction),
A
Alex Dima 已提交
757 758
			className: 'previous',
			onTrigger: () => {
A
Alex Dima 已提交
759
				this._codeEditor.getAction(FIND_IDS.PreviousMatchFindAction).run().done(null, onUnexpectedError);
M
Matt Bierner 已提交
760
			}
A
Alex Dima 已提交
761
		}));
E
Erich Gamma 已提交
762 763

		// Next button
A
Alex Dima 已提交
764
		this._nextBtn = this._register(new SimpleButton({
A
Alex Dima 已提交
765
			label: NLS_NEXT_MATCH_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.NextMatchFindAction),
A
Alex Dima 已提交
766 767
			className: 'next',
			onTrigger: () => {
A
Alex Dima 已提交
768
				this._codeEditor.getAction(FIND_IDS.NextMatchFindAction).run().done(null, onUnexpectedError);
M
Matt Bierner 已提交
769
			}
A
Alex Dima 已提交
770
		}));
E
Erich Gamma 已提交
771

A
Alex Dima 已提交
772
		let findPart = document.createElement('div');
E
Erich Gamma 已提交
773 774
		findPart.className = 'find-part';
		findPart.appendChild(this._findInput.domNode);
775
		findPart.appendChild(this._matchesCount);
E
Erich Gamma 已提交
776 777 778 779
		findPart.appendChild(this._prevBtn.domNode);
		findPart.appendChild(this._nextBtn.domNode);

		// Toggle selection button
780 781
		this._toggleSelectionFind = this._register(new SimpleCheckbox({
			parent: findPart,
R
rebornix 已提交
782
			title: NLS_TOGGLE_SELECTION_FIND_TITLE + this._keybindingLabelFor(FIND_IDS.ToggleSearchScopeCommand),
783 784
			onChange: () => {
				if (this._toggleSelectionFind.checked) {
785
					let selection = this._codeEditor.getSelection();
786
					if (selection.endColumn === 1 && selection.endLineNumber > selection.startLineNumber) {
787 788
						selection = selection.setEndPosition(selection.endLineNumber - 1, 1);
					}
789 790 791
					if (!selection.isEmpty()) {
						this._state.change({ searchScope: selection }, true);
					}
792 793 794
				} else {
					this._state.change({ searchScope: null }, true);
				}
E
Erich Gamma 已提交
795 796 797 798
			}
		}));

		// Close button
A
Alex Dima 已提交
799
		this._closeBtn = this._register(new SimpleButton({
A
Alex Dima 已提交
800
			label: NLS_CLOSE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.CloseFindWidgetCommand),
A
Alex Dima 已提交
801 802
			className: 'close-fw',
			onTrigger: () => {
803
				this._state.change({ isRevealed: false, searchScope: null }, false);
A
Alex Dima 已提交
804 805
			},
			onKeyDown: (e) => {
A
Alexandru Dima 已提交
806
				if (e.equals(KeyCode.Tab)) {
A
Alex Dima 已提交
807
					if (this._isReplaceVisible) {
808 809 810 811 812
						if (this._replaceBtn.isEnabled()) {
							this._replaceBtn.focus();
						} else {
							this._codeEditor.focus();
						}
A
Alex Dima 已提交
813 814
						e.preventDefault();
					}
A
Alex Dima 已提交
815
				}
E
Erich Gamma 已提交
816
			}
A
Alex Dima 已提交
817
		}));
E
Erich Gamma 已提交
818 819 820 821 822 823 824 825

		findPart.appendChild(this._closeBtn.domNode);

		return findPart;
	}

	private _buildReplacePart(): HTMLElement {
		// Replace input
A
Alex Dima 已提交
826
		let replaceInput = document.createElement('div');
E
Erich Gamma 已提交
827
		replaceInput.className = 'replace-input';
828
		replaceInput.style.width = REPLACE_INPUT_AREA_WIDTH + 'px';
S
#50583  
Sandeep Somavarapu 已提交
829
		this._replaceInputBox = this._register(new ContextScopedHistoryInputBox(replaceInput, null, {
830
			ariaLabel: NLS_REPLACE_INPUT_LABEL,
831 832
			placeholder: NLS_REPLACE_INPUT_PLACEHOLDER,
			history: []
S
#50583  
Sandeep Somavarapu 已提交
833
		}, this._contextKeyService));
E
Erich Gamma 已提交
834

A
Alex Dima 已提交
835 836
		this._register(dom.addStandardDisposableListener(this._replaceInputBox.inputElement, 'keydown', (e) => this._onReplaceInputKeyDown(e)));
		this._register(dom.addStandardDisposableListener(this._replaceInputBox.inputElement, 'input', (e) => {
837 838
			this._state.change({ replaceString: this._replaceInputBox.value }, false);
		}));
E
Erich Gamma 已提交
839 840

		// Replace one button
A
Alex Dima 已提交
841
		this._replaceBtn = this._register(new SimpleButton({
842
			label: NLS_REPLACE_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.ReplaceOneAction),
A
Alex Dima 已提交
843 844 845 846
			className: 'replace',
			onTrigger: () => {
				this._controller.replace();
			},
A
Alex Dima 已提交
847
			onKeyDown: (e) => {
A
Alexandru Dima 已提交
848
				if (e.equals(KeyMod.Shift | KeyCode.Tab)) {
A
Alex Dima 已提交
849 850 851 852 853
					this._closeBtn.focus();
					e.preventDefault();
				}
			}
		}));
E
Erich Gamma 已提交
854 855

		// Replace all button
A
Alex Dima 已提交
856
		this._replaceAllBtn = this._register(new SimpleButton({
857
			label: NLS_REPLACE_ALL_BTN_LABEL + this._keybindingLabelFor(FIND_IDS.ReplaceAllAction),
A
Alex Dima 已提交
858 859 860
			className: 'replace-all',
			onTrigger: () => {
				this._controller.replaceAll();
M
Matt Bierner 已提交
861
			}
A
Alex Dima 已提交
862
		}));
E
Erich Gamma 已提交
863

A
Alex Dima 已提交
864
		let replacePart = document.createElement('div');
E
Erich Gamma 已提交
865 866 867 868 869 870 871 872 873 874
		replacePart.className = 'replace-part';
		replacePart.appendChild(replaceInput);
		replacePart.appendChild(this._replaceBtn.domNode);
		replacePart.appendChild(this._replaceAllBtn.domNode);

		return replacePart;
	}

	private _buildDomNode(): void {
		// Find part
A
Alex Dima 已提交
875
		let findPart = this._buildFindPart();
E
Erich Gamma 已提交
876 877

		// Replace part
A
Alex Dima 已提交
878
		let replacePart = this._buildReplacePart();
E
Erich Gamma 已提交
879 880

		// Toggle replace button
A
Alex Dima 已提交
881
		this._toggleReplaceBtn = this._register(new SimpleButton({
A
Alex Dima 已提交
882 883 884
			label: NLS_TOGGLE_REPLACE_MODE_BTN_LABEL,
			className: 'toggle left',
			onTrigger: () => {
885
				this._state.change({ isReplaceRevealed: !this._isReplaceVisible }, false);
886 887 888
				if (this._isReplaceVisible) {
					this._replaceInputBox.width = this._findInput.inputBox.width;
				}
889
				this._showViewZone();
M
Matt Bierner 已提交
890
			}
A
Alex Dima 已提交
891
		}));
E
Erich Gamma 已提交
892 893 894 895 896 897 898
		this._toggleReplaceBtn.toggleClass('expand', this._isReplaceVisible);
		this._toggleReplaceBtn.toggleClass('collapse', !this._isReplaceVisible);
		this._toggleReplaceBtn.setExpanded(this._isReplaceVisible);

		// Widget
		this._domNode = document.createElement('div');
		this._domNode.className = 'editor-widget find-widget';
899
		this._domNode.setAttribute('aria-hidden', 'true');
900 901
		// We need to set this explicitly, otherwise on IE11, the width inheritence of flex doesn't work.
		this._domNode.style.width = `${FIND_WIDGET_INITIAL_WIDTH}px`;
E
Erich Gamma 已提交
902 903 904 905

		this._domNode.appendChild(this._toggleReplaceBtn.domNode);
		this._domNode.appendChild(findPart);
		this._domNode.appendChild(replacePart);
906 907 908 909 910 911

		this._buildSash();
	}

	private _buildSash() {
		this._resizeSash = new Sash(this._domNode, this, { orientation: Orientation.VERTICAL });
912
		this._resized = false;
913 914
		let originalWidth = FIND_WIDGET_INITIAL_WIDTH;

I
isidor 已提交
915
		this._register(this._resizeSash.onDidStart((e: ISashEvent) => {
916 917 918
			originalWidth = dom.getTotalWidth(this._domNode);
		}));

I
isidor 已提交
919
		this._register(this._resizeSash.onDidChange((evt: ISashEvent) => {
920
			this._resized = true;
921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937
			let width = originalWidth + evt.startX - evt.currentX;

			if (width < FIND_WIDGET_INITIAL_WIDTH) {
				// narrow down the find widget should be handled by CSS.
				return;
			}

			let inputBoxWidth = width - FIND_ALL_CONTROLS_WIDTH;
			let maxWidth = parseFloat(dom.getComputedStyle(this._domNode).maxWidth) || 0;
			if (width > maxWidth) {
				return;
			}
			this._domNode.style.width = `${width}px`;
			if (this._isReplaceVisible) {
				this._replaceInputBox.width = inputBoxWidth;
			}
		}));
E
Erich Gamma 已提交
938 939 940
	}
}

941
interface ISimpleCheckboxOpts {
M
Matt Bierner 已提交
942 943 944
	readonly parent: HTMLElement;
	readonly title: string;
	readonly onChange: () => void;
945 946 947
}

class SimpleCheckbox extends Widget {
A
Alex Dima 已提交
948 949

	private static _COUNTER = 0;
E
Erich Gamma 已提交
950

M
Matt Bierner 已提交
951 952 953 954
	private readonly _opts: ISimpleCheckboxOpts;
	private readonly _domNode: HTMLElement;
	private readonly _checkbox: HTMLInputElement;
	private readonly _label: HTMLLabelElement;
E
Erich Gamma 已提交
955

J
Johannes Rieken 已提交
956
	constructor(opts: ISimpleCheckboxOpts) {
A
Alex Dima 已提交
957
		super();
958 959
		this._opts = opts;

E
Erich Gamma 已提交
960 961
		this._domNode = document.createElement('div');
		this._domNode.className = 'monaco-checkbox';
962
		this._domNode.title = this._opts.title;
963
		this._domNode.tabIndex = 0;
E
Erich Gamma 已提交
964 965 966 967

		this._checkbox = document.createElement('input');
		this._checkbox.type = 'checkbox';
		this._checkbox.className = 'checkbox';
968
		this._checkbox.id = 'checkbox-' + SimpleCheckbox._COUNTER++;
969
		this._checkbox.tabIndex = -1;
E
Erich Gamma 已提交
970

A
Alex Dima 已提交
971 972
		this._label = document.createElement('label');
		this._label.className = 'label';
2
284km 已提交
973
		// Connect the label and the checkbox. Checkbox will get checked when the label receives a click.
A
Alex Dima 已提交
974
		this._label.htmlFor = this._checkbox.id;
975
		this._label.tabIndex = -1;
E
Erich Gamma 已提交
976 977

		this._domNode.appendChild(this._checkbox);
A
Alex Dima 已提交
978
		this._domNode.appendChild(this._label);
E
Erich Gamma 已提交
979

980 981 982 983 984
		this._opts.parent.appendChild(this._domNode);

		this.onchange(this._checkbox, (e) => {
			this._opts.onChange();
		});
E
Erich Gamma 已提交
985 986 987 988 989 990
	}

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

991 992 993 994
	public get checked(): boolean {
		return this._checkbox.checked;
	}

J
Johannes Rieken 已提交
995
	public set checked(newValue: boolean) {
996
		this._checkbox.checked = newValue;
E
Erich Gamma 已提交
997 998 999 1000 1001 1002
	}

	public focus(): void {
		this._checkbox.focus();
	}

1003
	private enable(): void {
E
Erich Gamma 已提交
1004 1005 1006
		this._checkbox.removeAttribute('disabled');
	}

1007
	private disable(): void {
E
Erich Gamma 已提交
1008 1009
		this._checkbox.disabled = true;
	}
1010

J
Johannes Rieken 已提交
1011
	public setEnabled(enabled: boolean): void {
1012 1013
		if (enabled) {
			this.enable();
1014
			this.domNode.tabIndex = 0;
1015 1016
		} else {
			this.disable();
1017
			this.domNode.tabIndex = -1;
1018 1019
		}
	}
E
Erich Gamma 已提交
1020 1021
}

R
rebornix 已提交
1022
export interface ISimpleButtonOpts {
M
Matt Bierner 已提交
1023 1024 1025 1026
	readonly label: string;
	readonly className: string;
	readonly onTrigger: () => void;
	readonly onKeyDown?: (e: IKeyboardEvent) => void;
A
Alex Dima 已提交
1027
}
E
Erich Gamma 已提交
1028

R
rebornix 已提交
1029
export class SimpleButton extends Widget {
E
Erich Gamma 已提交
1030

M
Matt Bierner 已提交
1031 1032
	private readonly _opts: ISimpleButtonOpts;
	private readonly _domNode: HTMLElement;
E
Erich Gamma 已提交
1033

J
Johannes Rieken 已提交
1034
	constructor(opts: ISimpleButtonOpts) {
A
Alex Dima 已提交
1035
		super();
A
Alex Dima 已提交
1036
		this._opts = opts;
E
Erich Gamma 已提交
1037 1038

		this._domNode = document.createElement('div');
A
Alex Dima 已提交
1039
		this._domNode.title = this._opts.label;
A
Alex Dima 已提交
1040
		this._domNode.tabIndex = 0;
A
Alex Dima 已提交
1041
		this._domNode.className = 'button ' + this._opts.className;
E
Erich Gamma 已提交
1042
		this._domNode.setAttribute('role', 'button');
A
Alex Dima 已提交
1043
		this._domNode.setAttribute('aria-label', this._opts.label);
E
Erich Gamma 已提交
1044

A
Alex Dima 已提交
1045
		this.onclick(this._domNode, (e) => {
A
Alex Dima 已提交
1046
			this._opts.onTrigger();
E
Erich Gamma 已提交
1047
			e.preventDefault();
A
Alex Dima 已提交
1048
		});
M
Matt Bierner 已提交
1049

A
Alex Dima 已提交
1050
		this.onkeydown(this._domNode, (e) => {
A
Alexandru Dima 已提交
1051
			if (e.equals(KeyCode.Space) || e.equals(KeyCode.Enter)) {
A
Alex Dima 已提交
1052
				this._opts.onTrigger();
E
Erich Gamma 已提交
1053 1054 1055
				e.preventDefault();
				return;
			}
M
Matt Bierner 已提交
1056 1057 1058
			if (this._opts.onKeyDown) {
				this._opts.onKeyDown(e);
			}
A
Alex Dima 已提交
1059
		});
E
Erich Gamma 已提交
1060 1061 1062 1063 1064 1065
	}

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

1066 1067 1068 1069
	public isEnabled(): boolean {
		return (this._domNode.tabIndex >= 0);
	}

E
Erich Gamma 已提交
1070 1071 1072 1073
	public focus(): void {
		this._domNode.focus();
	}

J
Johannes Rieken 已提交
1074
	public setEnabled(enabled: boolean): void {
A
Alex Dima 已提交
1075
		dom.toggleClass(this._domNode, 'disabled', !enabled);
E
Erich Gamma 已提交
1076 1077 1078 1079
		this._domNode.setAttribute('aria-disabled', String(!enabled));
		this._domNode.tabIndex = enabled ? 0 : -1;
	}

J
Johannes Rieken 已提交
1080
	public setExpanded(expanded: boolean): void {
1081
		this._domNode.setAttribute('aria-expanded', String(!!expanded));
E
Erich Gamma 已提交
1082 1083
	}

J
Johannes Rieken 已提交
1084
	public toggleClass(className: string, shouldHaveIt: boolean): void {
A
Alex Dima 已提交
1085
		dom.toggleClass(this._domNode, className, shouldHaveIt);
E
Erich Gamma 已提交
1086 1087
	}
}
1088

1089 1090 1091
// theming

registerThemingParticipant((theme, collector) => {
1092
	const addBackgroundColorRule = (selector: string, color: Color): void => {
1093
		if (color) {
1094
			collector.addRule(`.monaco-editor ${selector} { background-color: ${color}; }`);
1095
		}
1096
	};
1097 1098

	addBackgroundColorRule('.findMatch', theme.getColor(editorFindMatchHighlight));
1099
	addBackgroundColorRule('.currentFindMatch', theme.getColor(editorFindMatch));
1100 1101
	addBackgroundColorRule('.findScope', theme.getColor(editorFindRangeHighlight));

1102
	const widgetBackground = theme.getColor(editorWidgetBackground);
1103 1104
	addBackgroundColorRule('.find-widget', widgetBackground);

1105
	const widgetShadowColor = theme.getColor(widgetShadow);
1106
	if (widgetShadowColor) {
1107
		collector.addRule(`.monaco-editor .find-widget { box-shadow: 0 2px 8px ${widgetShadowColor}; }`);
1108 1109
	}

1110
	const findMatchHighlightBorder = theme.getColor(editorFindMatchHighlightBorder);
1111
	if (findMatchHighlightBorder) {
1112
		collector.addRule(`.monaco-editor .findMatch { border: 1px ${theme.type === 'hc' ? 'dotted' : 'solid'} ${findMatchHighlightBorder}; box-sizing: border-box; }`);
1113
	}
1114 1115

	const findMatchBorder = theme.getColor(editorFindMatchBorder);
1116
	if (findMatchBorder) {
1117
		collector.addRule(`.monaco-editor .currentFindMatch { border: 2px solid ${findMatchBorder}; padding: 1px; box-sizing: border-box; }`);
1118
	}
1119 1120

	const findRangeHighlightBorder = theme.getColor(editorFindRangeHighlightBorder);
1121
	if (findRangeHighlightBorder) {
1122
		collector.addRule(`.monaco-editor .findScope { border: 1px ${theme.type === 'hc' ? 'dashed' : 'solid'} ${findRangeHighlightBorder}; }`);
1123 1124
	}

1125
	const hcBorder = theme.getColor(contrastBorder);
1126
	if (hcBorder) {
1127 1128 1129
		collector.addRule(`.monaco-editor .find-widget { border: 2px solid ${hcBorder}; }`);
	}

1130
	const error = theme.getColor(errorForeground);
1131 1132
	if (error) {
		collector.addRule(`.monaco-editor .find-widget.no-results .matchesCount { color: ${error}; }`);
1133
	}
1134

1135
	const resizeBorderBackground = theme.getColor(editorWidgetResizeBorder);
1136 1137 1138 1139 1140 1141 1142
	if (resizeBorderBackground) {
		collector.addRule(`.monaco-editor .find-widget .monaco-sash { background-color: ${resizeBorderBackground}; width: 3px !important; margin-left: -4px;}`);
	} else {
		const border = theme.getColor(editorWidgetBorder);
		if (border) {
			collector.addRule(`.monaco-editor .find-widget .monaco-sash { background-color: ${border}; width: 3px !important; margin-left: -4px;}`);
		}
1143
	}
1144

1145
});