diff --git a/src/vs/base/browser/ui/findinput/findInput.ts b/src/vs/base/browser/ui/findinput/findInput.ts index 139ec12651f5168f07795f4cf7138192ed2932c3..253f2424ed4b6bb5f372ffa2c5fb7f167eda9ea6 100644 --- a/src/vs/base/browser/ui/findinput/findInput.ts +++ b/src/vs/base/browser/ui/findinput/findInput.ts @@ -83,6 +83,9 @@ export class FindInput extends Widget { private _onCaseSensitiveKeyDown = this._register(new Emitter()); public readonly onCaseSensitiveKeyDown: Event = this._onCaseSensitiveKeyDown.event; + private _onRegexKeyDown = this._register(new Emitter()); + public readonly onRegexKeyDown: Event = this._onRegexKeyDown.event; + constructor(parent: HTMLElement, contextViewProvider: IContextViewProvider, options?: IFindInputOptions) { super(); this.contextViewProvider = contextViewProvider; @@ -248,6 +251,10 @@ export class FindInput extends Widget { this.caseSensitive.focus(); } + public focusOnRegex(): void { + this.regex.focus(); + } + private _lastHighlightFindOptions: number = 0; public highlightFindOptions(): void { dom.removeClass(this.domNode, 'highlight-' + (this._lastHighlightFindOptions)); @@ -294,6 +301,9 @@ export class FindInput extends Widget { this.setInputWidth(); this.validate(); }, + onKeyDown: (e) => { + this._onRegexKeyDown.fire(e); + }, inputActiveOptionBorder: this.inputActiveOptionBorder })); this.wholeWords = this._register(new WholeWordsCheckbox({ diff --git a/src/vs/platform/widget/browser/widget.contribution.ts b/src/vs/platform/widget/browser/widget.contribution.ts index 1a4def1eac253069cc873d836361dee663683652..6fc2103906878bef1ca177bc75cce174107d548d 100644 --- a/src/vs/platform/widget/browser/widget.contribution.ts +++ b/src/vs/platform/widget/browser/widget.contribution.ts @@ -5,7 +5,7 @@ 'use strict'; import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { KeyCode } from 'vs/base/common/keyCodes'; import { ContextKeyDefinedExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { HistoryInputBoxContext } from 'vs/platform/widget/browser/input'; import { HistoryInputBox } from 'vs/base/browser/ui/inputbox/inputbox'; @@ -14,7 +14,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'input.action.historyPrevious', weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), when: new ContextKeyDefinedExpr(HistoryInputBoxContext), - primary: KeyMod.Alt | KeyCode.UpArrow, + primary: KeyCode.UpArrow, handler: (accessor, arg2) => { const historyInputBox: HistoryInputBox = accessor.get(IContextKeyService).getContext(document.activeElement).getValue(HistoryInputBoxContext); historyInputBox.showPreviousValue(); @@ -25,7 +25,7 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ id: 'input.action.historyNext', weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), when: new ContextKeyDefinedExpr(HistoryInputBoxContext), - primary: KeyMod.Alt | KeyCode.DownArrow, + primary: KeyCode.DownArrow, handler: (accessor, arg2) => { const historyInputBox: HistoryInputBox = accessor.get(IContextKeyService).getContext(document.activeElement).getValue(HistoryInputBoxContext); historyInputBox.showNextValue(); diff --git a/src/vs/workbench/parts/search/browser/searchActions.ts b/src/vs/workbench/parts/search/browser/searchActions.ts index 6e5abe0ab8bfbc5a85af8db8c464f8ed26b6f011..1050396fc0fd66920f7185e5c32c05d837cd6910 100644 --- a/src/vs/workbench/parts/search/browser/searchActions.ts +++ b/src/vs/workbench/parts/search/browser/searchActions.ts @@ -251,42 +251,6 @@ export class ShowPreviousReplaceTermAction extends Action { } } -export class FocusNextInputAction extends Action { - - public static readonly ID = 'search.focus.nextInputBox'; - - constructor(id: string, label: string, - @IViewletService private viewletService: IViewletService, - @IPanelService private panelService: IPanelService - ) { - super(id, label); - } - - public run(): TPromise { - const searchView = getSearchView(this.viewletService, this.panelService); - searchView.focusNextInputBox(); - return TPromise.as(null); - } -} - -export class FocusPreviousInputAction extends Action { - - public static readonly ID = 'search.focus.previousInputBox'; - - constructor(id: string, label: string, - @IViewletService private viewletService: IViewletService, - @IPanelService private panelService: IPanelService - ) { - super(id, label); - } - - public run(): TPromise { - const searchView = getSearchView(this.viewletService, this.panelService); - searchView.focusPreviousInputBox(); - return TPromise.as(null); - } -} - export const FocusActiveEditorCommand = (accessor: ServicesAccessor) => { const editorService = accessor.get(IEditorService); const activeControl = editorService.activeControl; diff --git a/src/vs/workbench/parts/search/browser/searchView.ts b/src/vs/workbench/parts/search/browser/searchView.ts index 931184d7d4c44528853ea76ace21bcf262170bd2..3782699ea9a16323017e4531004cef62c1ed426a 100644 --- a/src/vs/workbench/parts/search/browser/searchView.ts +++ b/src/vs/workbench/parts/search/browser/searchView.ts @@ -15,7 +15,7 @@ import { IAction } from 'vs/base/common/actions'; import { Delayer } from 'vs/base/common/async'; import * as errors from 'vs/base/common/errors'; import { debounceEvent, Emitter } from 'vs/base/common/event'; -import { KeyCode } from 'vs/base/common/keyCodes'; +import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import * as paths from 'vs/base/common/paths'; import * as env from 'vs/base/common/platform'; import * as strings from 'vs/base/common/strings'; @@ -98,6 +98,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { private searchWidget: SearchWidget; private size: dom.Dimension; private queryDetails: HTMLElement; + private toggleQueryDetailsButton: HTMLElement; private inputPatternExcludes: ExcludePatternInputWidget; private inputPatternIncludes: PatternInputWidget; private results: Builder; @@ -222,7 +223,7 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { } this.queryDetails = this.searchWidgetsContainer.div({ 'class': ['query-details'] }, (builder) => { - builder.div({ 'class': 'more', 'tabindex': 0, 'role': 'button', 'title': nls.localize('moreSearch', "Toggle Search Details") }) + this.toggleQueryDetailsButton = builder.div({ 'class': 'more', 'tabindex': 0, 'role': 'button', 'title': nls.localize('moreSearch', "Toggle Search Details") }) .on(dom.EventType.CLICK, (e) => { dom.EventHelper.stop(e); this.toggleQueryDetails(); @@ -233,7 +234,18 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { dom.EventHelper.stop(e); this.toggleQueryDetails(false); } - }); + }).on(dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { + let event = new StandardKeyboardEvent(e); + + if (event.equals(KeyMod.Shift | KeyCode.Tab)) { + if (this.searchWidget.isReplaceActive()) { + this.searchWidget.focusReplaceAllAction(); + } else { + this.searchWidget.focusRegexAction(); + } + dom.EventHelper.stop(e); + } + }).getHTMLElement(); //folder includes list builder.div({ 'class': 'file-types includes' }, (builder) => { @@ -360,6 +372,10 @@ export class SearchView extends Viewlet implements IViewlet, IPanel { this.delayedRefresh.trigger(() => this.tree.refresh()); })); + this.toUnbind.push(this.searchWidget.onBlur(() => { + this.toggleQueryDetailsButton.focus(); + })); + this.toUnbind.push(this.searchWidget.onReplaceAll(() => this.replaceAll())); this.trackInputBox(this.searchWidget.searchInputFocusTracker); this.trackInputBox(this.searchWidget.replaceInputFocusTracker); diff --git a/src/vs/workbench/parts/search/browser/searchWidget.ts b/src/vs/workbench/parts/search/browser/searchWidget.ts index 537b3d154fa7f4ea31f7ff9845d23e958e0cfb44..f9fe8deed65383de4409e4e8ddb4e5f44f67e9f4 100644 --- a/src/vs/workbench/parts/search/browser/searchWidget.ts +++ b/src/vs/workbench/parts/search/browser/searchWidget.ts @@ -116,20 +116,23 @@ export class SearchWidget extends Widget { private _onReplaceAll = this._register(new Emitter()); public readonly onReplaceAll: Event = this._onReplaceAll.event; + private _onBlur = this._register(new Emitter()); + public readonly onBlur: Event = this._onBlur.event; + constructor( container: Builder, options: ISearchWidgetOptions, @IContextViewService private contextViewService: IContextViewService, @IThemeService private themeService: IThemeService, - @IContextKeyService private keyBindingService: IContextKeyService, - @IKeybindingService private keyBindingService2: IKeybindingService, + @IContextKeyService private contextKeyService: IContextKeyService, + @IKeybindingService private keyBindingService: IKeybindingService, @IClipboardService private clipboardServce: IClipboardService, @IConfigurationService private configurationService: IConfigurationService ) { super(); - this.replaceActive = Constants.ReplaceActiveKey.bindTo(this.keyBindingService); - this.searchInputBoxFocused = Constants.SearchInputBoxFocusedKey.bindTo(this.keyBindingService); - this.replaceInputBoxFocused = Constants.ReplaceInputBoxFocusedKey.bindTo(this.keyBindingService); + this.replaceActive = Constants.ReplaceActiveKey.bindTo(this.contextKeyService); + this.searchInputBoxFocused = Constants.SearchInputBoxFocusedKey.bindTo(this.contextKeyService); + this.replaceInputBoxFocused = Constants.ReplaceInputBoxFocusedKey.bindTo(this.contextKeyService); this.render(container, options); } @@ -164,6 +167,10 @@ export class SearchWidget extends Widget { return !dom.hasClass(this.replaceContainer, 'disabled'); } + isReplaceActive(): boolean { + return this.replaceActive.get(); + } + public getReplaceValue(): string { return this.replaceInput.value; } @@ -210,6 +217,14 @@ export class SearchWidget extends Widget { return this.replaceInput.hasFocus(); } + public focusReplaceAllAction(): void { + this.replaceActionBar.focus(true); + } + + public focusRegexAction(): void { + this.searchInput.focusOnRegex(); + } + private render(container: Builder, options: ISearchWidgetOptions): void { this.domNode = container.div({ 'class': 'search-widget' }).style({ position: 'relative' }).getHTMLElement(); this.renderToggleReplaceButton(this.domNode); @@ -237,16 +252,16 @@ export class SearchWidget extends Widget { label: nls.localize('label.Search', 'Search: Type Search Term and press Enter to search or Escape to cancel'), validation: (value: string) => this.validateSearchInput(value), placeholder: nls.localize('search.placeHolder', "Search"), - appendCaseSensitiveLabel: appendKeyBindingLabel('', this.keyBindingService2.lookupKeybinding(Constants.ToggleCaseSensitiveCommandId), this.keyBindingService2), - appendWholeWordsLabel: appendKeyBindingLabel('', this.keyBindingService2.lookupKeybinding(Constants.ToggleWholeWordCommandId), this.keyBindingService2), - appendRegexLabel: appendKeyBindingLabel('', this.keyBindingService2.lookupKeybinding(Constants.ToggleRegexCommandId), this.keyBindingService2), + appendCaseSensitiveLabel: appendKeyBindingLabel('', this.keyBindingService.lookupKeybinding(Constants.ToggleCaseSensitiveCommandId), this.keyBindingService), + appendWholeWordsLabel: appendKeyBindingLabel('', this.keyBindingService.lookupKeybinding(Constants.ToggleWholeWordCommandId), this.keyBindingService), + appendRegexLabel: appendKeyBindingLabel('', this.keyBindingService.lookupKeybinding(Constants.ToggleRegexCommandId), this.keyBindingService), history: options.searchHistory }; let searchInputContainer = dom.append(parent, dom.$('.search-container.input-box')); - this.searchInput = this._register(new ContextScopedFindInput(searchInputContainer, this.contextViewService, inputOptions, this.keyBindingService)); + this.searchInput = this._register(new ContextScopedFindInput(searchInputContainer, this.contextViewService, inputOptions, this.contextKeyService)); this._register(attachFindInputBoxStyler(this.searchInput, this.themeService)); - this.searchInput.onKeyUp((keyboardEvent: IKeyboardEvent) => this.onSearchInputKeyUp(keyboardEvent)); + this.searchInput.onKeyDown((keyboardEvent: IKeyboardEvent) => this.onSearchInputKeyDown(keyboardEvent)); this.searchInput.setValue(options.value || ''); this.searchInput.setRegex(!!options.isRegex); this.searchInput.setCaseSensitive(!!options.isCaseSensitive); @@ -254,6 +269,8 @@ export class SearchWidget extends Widget { this._register(this.onSearchSubmit(() => { this.searchInput.inputBox.addToHistory(this.searchInput.getValue()); })); + this.searchInput.onCaseSensitiveKeyDown((keyboardEvent: IKeyboardEvent) => this.onCaseSensitiveKeyDown(keyboardEvent)); + this.searchInput.onRegexKeyDown((keyboardEvent: IKeyboardEvent) => this.onRegexKeyDown(keyboardEvent)); this._register(this.onReplaceValueChanged(() => { this.replaceInput.addToHistory(this.replaceInput.value); @@ -287,9 +304,9 @@ export class SearchWidget extends Widget { ariaLabel: nls.localize('label.Replace', 'Replace: Type replace term and press Enter to preview or Escape to cancel'), placeholder: nls.localize('search.replace.placeHolder', "Replace"), history: options.replaceHistory || [] - }, this.keyBindingService)); + }, this.contextKeyService)); this._register(attachInputBoxStyler(this.replaceInput, this.themeService)); - this.onkeyup(this.replaceInput.inputElement, (keyboardEvent) => this.onReplaceInputKeyUp(keyboardEvent)); + this.onkeydown(this.replaceInput.inputElement, (keyboardEvent) => this.onReplaceInputKeyDown(keyboardEvent)); this.replaceInput.onDidChange(() => this._onReplaceValueChanged.fire()); this.searchInput.inputBox.onDidChange(() => this.onSearchInputChanged()); @@ -320,15 +337,11 @@ export class SearchWidget extends Widget { public setReplaceAllActionState(enabled: boolean): void { if (this.replaceAllAction.enabled !== enabled) { this.replaceAllAction.enabled = enabled; - this.replaceAllAction.label = enabled ? SearchWidget.REPLACE_ALL_ENABLED_LABEL(this.keyBindingService2) : SearchWidget.REPLACE_ALL_DISABLED_LABEL; + this.replaceAllAction.label = enabled ? SearchWidget.REPLACE_ALL_ENABLED_LABEL(this.keyBindingService) : SearchWidget.REPLACE_ALL_DISABLED_LABEL; this.updateReplaceActiveState(); } } - private isReplaceActive(): boolean { - return this.replaceActive.get(); - } - private updateReplaceActiveState(): void { let currentState = this.isReplaceActive(); let newState = this.isReplaceShown() && this.replaceAllAction.enabled; @@ -366,26 +379,61 @@ export class SearchWidget extends Widget { this.setReplaceAllActionState(false); } - private onSearchInputKeyUp(keyboardEvent: IKeyboardEvent) { - switch (keyboardEvent.keyCode) { - case KeyCode.Enter: - this.submitSearch(); - return; - case KeyCode.Escape: - this._onSearchCancel.fire(); - return; - default: - return; + private onSearchInputKeyDown(keyboardEvent: IKeyboardEvent) { + if (keyboardEvent.equals(KeyCode.Enter)) { + this.submitSearch(); + keyboardEvent.preventDefault(); + } + + else if (keyboardEvent.equals(KeyCode.Escape)) { + this._onSearchCancel.fire(); + keyboardEvent.preventDefault(); + } + + else if (keyboardEvent.equals(KeyCode.Tab)) { + if (this.isReplaceShown()) { + this.replaceInput.focus(); + } else { + this.searchInput.focusOnCaseSensitive(); + } + keyboardEvent.preventDefault(); + } + } + + private onCaseSensitiveKeyDown(keyboardEvent: IKeyboardEvent) { + if (keyboardEvent.equals(KeyMod.Shift | KeyCode.Tab)) { + if (this.isReplaceShown()) { + this.replaceInput.focus(); + keyboardEvent.preventDefault(); + } + } + } + + private onRegexKeyDown(keyboardEvent: IKeyboardEvent) { + if (keyboardEvent.equals(KeyCode.Tab)) { + if (this.isReplaceActive()) { + this.focusReplaceAllAction(); + } else { + this._onBlur.fire(); + } + keyboardEvent.preventDefault(); } } - private onReplaceInputKeyUp(keyboardEvent: IKeyboardEvent) { - switch (keyboardEvent.keyCode) { - case KeyCode.Enter: - this.submitSearch(); - return; - default: - return; + private onReplaceInputKeyDown(keyboardEvent: IKeyboardEvent) { + if (keyboardEvent.equals(KeyCode.Enter)) { + this.submitSearch(); + keyboardEvent.preventDefault(); + } + + else if (keyboardEvent.equals(KeyCode.Tab)) { + this.searchInput.focusOnCaseSensitive(); + keyboardEvent.preventDefault(); + } + + else if (keyboardEvent.equals(KeyMod.Shift | KeyCode.Tab)) { + this.searchInput.focus(); + keyboardEvent.preventDefault(); } } diff --git a/src/vs/workbench/parts/search/electron-browser/search.contribution.ts b/src/vs/workbench/parts/search/electron-browser/search.contribution.ts index 56e3314df56402f609289054f5cf08ec6b3f5563..e36535783fb0441d5ad9126ab2712867abe00b22 100644 --- a/src/vs/workbench/parts/search/electron-browser/search.contribution.ts +++ b/src/vs/workbench/parts/search/electron-browser/search.contribution.ts @@ -52,7 +52,7 @@ import { getMultiSelectedResources } from 'vs/workbench/parts/files/browser/file import { Schemas } from 'vs/base/common/network'; import { PanelRegistry, Extensions as PanelExtensions, PanelDescriptor } from 'vs/workbench/browser/panel'; import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; -import { openSearchView, getSearchView, ReplaceAllInFolderAction, ReplaceAllAction, CloseReplaceAction, FocusNextInputAction, FocusPreviousInputAction, FocusNextSearchResultAction, FocusPreviousSearchResultAction, ReplaceInFilesAction, FindInFilesAction, FocusActiveEditorCommand, toggleCaseSensitiveCommand, ShowNextSearchTermAction, ShowPreviousSearchTermAction, toggleRegexCommand, ShowPreviousSearchIncludeAction, ShowNextSearchIncludeAction, CollapseDeepestExpandedLevelAction, toggleWholeWordCommand, RemoveAction, ReplaceAction, ClearSearchResultsAction, copyPathCommand, copyMatchCommand, copyAllCommand, ShowNextSearchExcludeAction, ShowPreviousSearchExcludeAction, clearHistoryCommand, ShowNextReplaceTermAction, ShowPreviousReplaceTermAction } from 'vs/workbench/parts/search/browser/searchActions'; +import { openSearchView, getSearchView, ReplaceAllInFolderAction, ReplaceAllAction, CloseReplaceAction, FocusNextSearchResultAction, FocusPreviousSearchResultAction, ReplaceInFilesAction, FindInFilesAction, FocusActiveEditorCommand, toggleCaseSensitiveCommand, ShowNextSearchTermAction, ShowPreviousSearchTermAction, toggleRegexCommand, ShowPreviousSearchIncludeAction, ShowNextSearchIncludeAction, CollapseDeepestExpandedLevelAction, toggleWholeWordCommand, RemoveAction, ReplaceAction, ClearSearchResultsAction, copyPathCommand, copyMatchCommand, copyAllCommand, ShowNextSearchExcludeAction, ShowPreviousSearchExcludeAction, clearHistoryCommand, ShowNextReplaceTermAction, ShowPreviousReplaceTermAction } from 'vs/workbench/parts/search/browser/searchActions'; import { VIEW_ID, ISearchConfigurationProperties } from 'vs/platform/search/common/search'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; @@ -179,26 +179,6 @@ KeybindingsRegistry.registerCommandAndKeybindingRule({ } }); -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: FocusNextInputAction.ID, - weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), - when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.InputBoxFocusedKey), - primary: KeyCode.DownArrow, - handler: (accessor, args: any) => { - accessor.get(IInstantiationService).createInstance(FocusNextInputAction, FocusNextInputAction.ID, '').run(); - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: FocusPreviousInputAction.ID, - weight: KeybindingsRegistry.WEIGHT.workbenchContrib(), - when: ContextKeyExpr.and(Constants.SearchViewVisibleKey, Constants.InputBoxFocusedKey, Constants.SearchInputBoxFocusedKey.toNegated()), - primary: KeyCode.UpArrow, - handler: (accessor, args: any) => { - accessor.get(IInstantiationService).createInstance(FocusPreviousInputAction, FocusPreviousInputAction.ID, '').run(); - } -}); - MenuRegistry.appendMenuItem(MenuId.SearchContext, { command: { id: Constants.ReplaceActionId,