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

import 'vs/css!./simpleFindWidget';
import * as nls from 'vs/nls';
A
Alex Dima 已提交
8
import * as dom from 'vs/base/browser/dom';
9
import { FindInput, IFindInputStyles } from 'vs/base/browser/ui/findinput/findInput';
10
import { Widget } from 'vs/base/browser/ui/widget';
11
import { Delayer } from 'vs/base/common/async';
12
import { KeyCode } from 'vs/base/common/keyCodes';
A
Alex Dima 已提交
13
import { FindReplaceState } from 'vs/editor/contrib/find/findState';
M
mgquan@myseneca.ca 已提交
14
import { IMessage as InputBoxMessage } from 'vs/base/browser/ui/inputbox/inputBox';
15
import { SimpleButton, findPreviousMatchIcon, findNextMatchIcon, findCloseIcon } from 'vs/editor/contrib/find/findWidget';
A
Alex Dima 已提交
16
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
17
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
18
import { editorWidgetBackground, inputActiveOptionBorder, inputActiveOptionBackground, inputActiveOptionForeground, inputBackground, inputBorder, inputForeground, inputValidationErrorBackground, inputValidationErrorBorder, inputValidationErrorForeground, inputValidationInfoBackground, inputValidationInfoBorder, inputValidationInfoForeground, inputValidationWarningBackground, inputValidationWarningBorder, inputValidationWarningForeground, widgetShadow, editorWidgetForeground } from 'vs/platform/theme/common/colorRegistry';
M
Martin Aeschlimann 已提交
19
import { IColorTheme, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
20
import { ContextScopedFindInput } from 'vs/platform/browser/contextScopedHistoryWidget';
21 22 23 24 25 26 27 28

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_CLOSE_BTN_LABEL = nls.localize('label.closeButton', "Close");

export abstract class SimpleFindWidget extends Widget {
L
Logan Ramos 已提交
29
	private readonly _findInput: FindInput;
30 31 32 33 34
	private readonly _domNode: HTMLElement;
	private readonly _innerDomNode: HTMLElement;
	private readonly _focusTracker: dom.IFocusTracker;
	private readonly _findInputFocusTracker: dom.IFocusTracker;
	private readonly _updateHistoryDelayer: Delayer<void>;
M
Matt Bierner 已提交
35 36 37 38 39
	private readonly prevBtn: SimpleButton;
	private readonly nextBtn: SimpleButton;

	private _isVisible: boolean = false;
	private foundMatch: boolean = false;
40

41
	constructor(
S
#50583  
Sandeep Somavarapu 已提交
42
		@IContextViewService private readonly _contextViewService: IContextViewService,
43 44
		@IContextKeyService contextKeyService: IContextKeyService,
		private readonly _state: FindReplaceState = new FindReplaceState(),
45
		showOptionButtons?: boolean
46 47
	) {
		super();
M
Matt Bierner 已提交
48

S
#50583  
Sandeep Somavarapu 已提交
49
		this._findInput = this._register(new ContextScopedFindInput(null, this._contextViewService, {
50 51
			label: NLS_FIND_INPUT_LABEL,
			placeholder: NLS_FIND_INPUT_PLACEHOLDER,
M
mgquan@myseneca.ca 已提交
52
			validation: (value: string): InputBoxMessage | null => {
M
updates  
mgquan@myseneca.ca 已提交
53
				if (value.length === 0 || !this._findInput.getRegex()) {
M
mgquan@myseneca.ca 已提交
54 55 56 57 58 59
					return null;
				}
				try {
					new RegExp(value);
					return null;
				} catch (e) {
60
					this.foundMatch = false;
61
					this.updateButtons(this.foundMatch);
M
mgquan@myseneca.ca 已提交
62 63 64
					return { content: e.message };
				}
			}
65
		}, contextKeyService, showOptionButtons));
66

67 68 69
		// Find History with update delayer
		this._updateHistoryDelayer = new Delayer<void>(500);

70
		this.oninput(this._findInput.domNode, (e) => {
71
			this.foundMatch = this.onInputChanged();
72
			this.updateButtons(this.foundMatch);
73
			this._delayedUpdateHistory();
74 75
		});

76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91
		this._findInput.setRegex(!!this._state.isRegex);
		this._findInput.setCaseSensitive(!!this._state.matchCase);
		this._findInput.setWholeWords(!!this._state.wholeWord);

		this._register(this._findInput.onDidOptionChange(() => {
			this._state.change({
				isRegex: this._findInput.getRegex(),
				wholeWord: this._findInput.getWholeWords(),
				matchCase: this._findInput.getCaseSensitive()
			}, true);
		}));

		this._register(this._state.onFindReplaceStateChange(() => {
			this._findInput.setRegex(this._state.isRegex);
			this._findInput.setWholeWords(this._state.wholeWord);
			this._findInput.setCaseSensitive(this._state.matchCase);
A
Arash Arbabi 已提交
92
			this.findFirst();
93 94
		}));

D
Daniel Imms 已提交
95
		this.prevBtn = this._register(new SimpleButton({
96
			label: NLS_PREVIOUS_MATCH_BTN_LABEL,
97
			className: findPreviousMatchIcon.classNames,
98 99
			onTrigger: () => {
				this.find(true);
M
Matt Bierner 已提交
100
			}
101
		}));
102

D
Daniel Imms 已提交
103
		this.nextBtn = this._register(new SimpleButton({
104
			label: NLS_NEXT_MATCH_BTN_LABEL,
105
			className: findNextMatchIcon.classNames,
106 107
			onTrigger: () => {
				this.find(false);
M
Matt Bierner 已提交
108
			}
109
		}));
110

111
		const closeBtn = this._register(new SimpleButton({
112
			label: NLS_CLOSE_BTN_LABEL,
113
			className: findCloseIcon.classNames,
114 115
			onTrigger: () => {
				this.hide();
M
Matt Bierner 已提交
116
			}
117
		}));
118

119 120 121
		this._innerDomNode = document.createElement('div');
		this._innerDomNode.classList.add('simple-find-part');
		this._innerDomNode.appendChild(this._findInput.domNode);
122 123
		this._innerDomNode.appendChild(this.prevBtn.domNode);
		this._innerDomNode.appendChild(this.nextBtn.domNode);
124 125 126
		this._innerDomNode.appendChild(closeBtn.domNode);

		// _domNode wraps _innerDomNode, ensuring that
127
		this._domNode = document.createElement('div');
128 129
		this._domNode.classList.add('simple-find-part-wrapper');
		this._domNode.appendChild(this._innerDomNode);
130

131
		this.onkeyup(this._innerDomNode, e => {
132 133 134 135 136 137 138
			if (e.equals(KeyCode.Escape)) {
				this.hide();
				e.preventDefault();
				return;
			}
		});

139
		this._focusTracker = this._register(dom.trackFocus(this._innerDomNode));
140 141
		this._register(this._focusTracker.onDidFocus(this.onFocusTrackerFocus.bind(this)));
		this._register(this._focusTracker.onDidBlur(this.onFocusTrackerBlur.bind(this)));
142

143 144 145 146
		this._findInputFocusTracker = this._register(dom.trackFocus(this._findInput.domNode));
		this._register(this._findInputFocusTracker.onDidFocus(this.onFindInputFocusTrackerFocus.bind(this)));
		this._register(this._findInputFocusTracker.onDidBlur(this.onFindInputFocusTrackerBlur.bind(this)));

147
		this._register(dom.addDisposableListener(this._innerDomNode, 'click', (event) => {
148 149 150 151
			event.stopPropagation();
		}));
	}

152
	protected abstract onInputChanged(): boolean;
153
	protected abstract find(previous: boolean): void;
A
Arash Arbabi 已提交
154
	protected abstract findFirst(): void;
155 156 157 158
	protected abstract onFocusTrackerFocus(): void;
	protected abstract onFocusTrackerBlur(): void;
	protected abstract onFindInputFocusTrackerFocus(): void;
	protected abstract onFindInputFocusTrackerBlur(): void;
C
cleidigh 已提交
159

160 161 162 163
	protected get inputValue() {
		return this._findInput.getValue();
	}

164
	public get focusTracker(): dom.IFocusTracker {
165
		return this._focusTracker;
166 167
	}

M
Martin Aeschlimann 已提交
168
	public updateTheme(theme: IColorTheme): void {
169
		const inputStyles: IFindInputStyles = {
170
			inputActiveOptionBorder: theme.getColor(inputActiveOptionBorder),
171
			inputActiveOptionForeground: theme.getColor(inputActiveOptionForeground),
172
			inputActiveOptionBackground: theme.getColor(inputActiveOptionBackground),
173 174 175 176 177 178 179 180 181 182 183 184
			inputBackground: theme.getColor(inputBackground),
			inputForeground: theme.getColor(inputForeground),
			inputBorder: theme.getColor(inputBorder),
			inputValidationInfoBackground: theme.getColor(inputValidationInfoBackground),
			inputValidationInfoForeground: theme.getColor(inputValidationInfoForeground),
			inputValidationInfoBorder: theme.getColor(inputValidationInfoBorder),
			inputValidationWarningBackground: theme.getColor(inputValidationWarningBackground),
			inputValidationWarningForeground: theme.getColor(inputValidationWarningForeground),
			inputValidationWarningBorder: theme.getColor(inputValidationWarningBorder),
			inputValidationErrorBackground: theme.getColor(inputValidationErrorBackground),
			inputValidationErrorForeground: theme.getColor(inputValidationErrorForeground),
			inputValidationErrorBorder: theme.getColor(inputValidationErrorBorder)
185 186 187 188
		};
		this._findInput.style(inputStyles);
	}

M
Matt Bierner 已提交
189
	dispose() {
190 191 192 193 194 195 196
		super.dispose();

		if (this._domNode && this._domNode.parentElement) {
			this._domNode.parentElement.removeChild(this._domNode);
		}
	}

M
Matt Bierner 已提交
197
	public getDomNode() {
198 199 200
		return this._domNode;
	}

201
	public reveal(initialInput?: string): void {
202 203 204 205
		if (initialInput) {
			this._findInput.setValue(initialInput);
		}

206
		if (this._isVisible) {
207 208 209 210 211
			this._findInput.select();
			return;
		}

		this._isVisible = true;
212
		this.updateButtons(this.foundMatch);
213 214

		setTimeout(() => {
215
			dom.addClass(this._innerDomNode, 'visible');
216
			dom.addClass(this._innerDomNode, 'visible-transition');
217
			this._innerDomNode.setAttribute('aria-hidden', 'false');
218
			this._findInput.select();
219 220 221
		}, 0);
	}

222 223 224 225 226 227 228 229 230
	public show(initialInput?: string): void {
		if (initialInput && !this._isVisible) {
			this._findInput.setValue(initialInput);
		}

		this._isVisible = true;

		setTimeout(() => {
			dom.addClass(this._innerDomNode, 'visible');
231
			dom.addClass(this._innerDomNode, 'visible-transition');
232 233 234 235
			this._innerDomNode.setAttribute('aria-hidden', 'false');
		}, 0);
	}

236 237
	public hide(): void {
		if (this._isVisible) {
R
rebornix 已提交
238
			this._innerDomNode.classList.remove('visible-transition');
239
			this._innerDomNode.setAttribute('aria-hidden', 'true');
240 241
			// Need to delay toggling visibility until after Transition, then visibility hidden - removes from tabIndex list
			setTimeout(() => {
242
				this._isVisible = false;
243
				this.updateButtons(this.foundMatch);
R
rebornix 已提交
244
				this._innerDomNode.classList.remove('visible');
245
			}, 200);
246 247
		}
	}
248 249 250 251 252 253

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

	protected _updateHistory() {
S
Sandeep Somavarapu 已提交
254
		this._findInput.inputBox.addToHistory();
255
	}
256 257 258 259 260 261 262 263 264 265 266 267

	protected _getRegexValue(): boolean {
		return this._findInput.getRegex();
	}

	protected _getWholeWordValue(): boolean {
		return this._findInput.getWholeWords();
	}

	protected _getCaseSensitiveValue(): boolean {
		return this._findInput.getCaseSensitive();
	}
268

269 270 271 272
	protected updateButtons(foundMatch: boolean) {
		const hasInput = this.inputValue.length > 0;
		this.prevBtn.setEnabled(this._isVisible && hasInput && foundMatch);
		this.nextBtn.setEnabled(this._isVisible && hasInput && foundMatch);
273
	}
274 275 276 277 278 279 280 281 282
}

// theming
registerThemingParticipant((theme, collector) => {
	const findWidgetBGColor = theme.getColor(editorWidgetBackground);
	if (findWidgetBGColor) {
		collector.addRule(`.monaco-workbench .simple-find-part { background-color: ${findWidgetBGColor} !important; }`);
	}

M
Martin Aeschlimann 已提交
283 284 285 286 287
	const widgetForeground = theme.getColor(editorWidgetForeground);
	if (widgetForeground) {
		collector.addRule(`.monaco-workbench .simple-find-part { color: ${widgetForeground}; }`);
	}

M
Matt Bierner 已提交
288
	const widgetShadowColor = theme.getColor(widgetShadow);
289 290 291
	if (widgetShadowColor) {
		collector.addRule(`.monaco-workbench .simple-find-part { box-shadow: 0 2px 8px ${widgetShadowColor}; }`);
	}
292
});