提交 a9b91a0a 编写于 作者: S Sandeep Somavarapu

Externalize focussing input boxes using actions and key bindings

上级 87462bb8
......@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import nls = require('vs/nls');
import * as dom from 'vs/base/browser/dom';
import strings = require('vs/base/common/strings');
import { $ } from 'vs/base/browser/builder';
import { Widget } from 'vs/base/browser/ui/widget';
......@@ -26,6 +27,8 @@ export class PatternInputWidget extends Widget {
static OPTION_CHANGE: string = 'optionChange';
public inputFocusTracker: dom.IFocusTracker;
private onOptionChange: (event: Event) => void;
private width: number;
private placeholder: string;
......@@ -65,6 +68,9 @@ export class PatternInputWidget extends Widget {
this.toDispose.forEach((element) => {
element();
});
if (this.inputFocusTracker) {
this.inputFocusTracker.dispose();
}
this.toDispose = [];
}
......@@ -132,6 +138,10 @@ export class PatternInputWidget extends Widget {
this.inputBox.focus();
}
public inputHasFocus(): boolean {
return this.inputBox.hasFocus();
}
public isGlobPattern(): boolean {
return this.pattern.checked;
}
......@@ -159,7 +169,7 @@ export class PatternInputWidget extends Widget {
showMessage: true
}
});
this.inputFocusTracker = dom.trackFocus(this.inputBox.inputElement);
this.onkeyup(this.inputBox.inputElement, (keyboardEvent) => this.onInputKeyUp(keyboardEvent));
this.pattern = new Checkbox({
......
......@@ -25,10 +25,11 @@ import {IKeybindings} from 'vs/platform/keybinding/common/keybinding';
import {IQuickOpenService} from 'vs/workbench/services/quickopen/common/quickOpenService';
import {IViewletService} from 'vs/workbench/services/viewlet/common/viewletService';
import {KeyMod, KeyCode} from 'vs/base/common/keyCodes';
import {OpenSearchViewletAction, ReplaceInFilesAction} from 'vs/workbench/parts/search/browser/searchActions';
import {VIEWLET_ID, SearchViewletVisible} from 'vs/workbench/parts/search/common/constants';
import {OpenSearchViewletAction, ReplaceInFilesAction, FocusNextInputAction, FocusPreviousInputAction} from 'vs/workbench/parts/search/browser/searchActions';
import * as Constants from 'vs/workbench/parts/search/common/constants';
import { registerContributions as replaceContributions } from 'vs/workbench/parts/search/browser/replaceContributions';
import { registerContributions as searchWidgetContributions } from 'vs/workbench/parts/search/browser/searchWidget';
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
replaceContributions();
searchWidgetContributions();
......@@ -36,11 +37,11 @@ searchWidgetContributions();
KeybindingsRegistry.registerCommandAndKeybindingRule({
id: 'workbench.action.search.toggleQueryDetails',
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: SearchViewletVisible,
when: Constants.SearchViewletVisibleKey,
primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_J,
handler: accessor => {
let viewletService = accessor.get(IViewletService);
viewletService.openViewlet(VIEWLET_ID, true)
viewletService.openViewlet(Constants.VIEWLET_ID, true)
.then(viewlet => (<any>viewlet).toggleFileTypes());
}
});
......@@ -105,7 +106,7 @@ class ShowAllSymbolsAction extends QuickOpenAction {
(<ViewletRegistry>Registry.as(ViewletExtensions.Viewlets)).registerViewlet(new ViewletDescriptor(
'vs/workbench/parts/search/browser/searchViewlet',
'SearchViewlet',
VIEWLET_ID,
Constants.VIEWLET_ID,
nls.localize('name', "Search"),
'search',
10
......@@ -164,6 +165,14 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(ShowAllSymbolsAction,
primary: KeyMod.CtrlCmd | KeyCode.KEY_T
}), 'Show All Symbols');
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusNextInputAction, FocusNextInputAction.ID, FocusNextInputAction.LABEL, {
primary: KeyCode.DownArrow
}, ContextKeyExpr.and(Constants.SearchViewletVisibleKey, Constants.InputBoxFocussedKey)), '');
registry.registerWorkbenchAction(new SyncActionDescriptor(FocusPreviousInputAction, FocusPreviousInputAction.ID, FocusPreviousInputAction.LABEL, {
primary: KeyCode.UpArrow
}, ContextKeyExpr.and(Constants.SearchViewletVisibleKey, Constants.InputBoxFocussedKey, Constants.SearchInputBoxFocussedKey.toNegated())), '');
// Configuration
const configurationRegistry = <IConfigurationRegistry>Registry.as(ConfigurationExtensions.Configuration);
configurationRegistry.registerConfiguration({
......
......@@ -39,6 +39,36 @@ export function appendKeyBindingLabel(label: string, keyBinding: any, keyBinding
return label + ' (' + keyBindingService2.getLabelFor(keyBinding) + ')';
}
export class FocusNextInputAction extends Action {
public static ID = 'search.focus.nextInputBox';
public static LABEL = nls.localize('focusNextInputbox', "Focus next input box");
constructor(id: string, label: string, @IViewletService private viewletService: IViewletService) {
super(id, label);
}
public run(): TPromise<any> {
(<SearchViewlet>this.viewletService.getActiveViewlet()).focusNextInputBox();
return TPromise.as(null);
}
}
export class FocusPreviousInputAction extends Action {
public static ID = 'search.focus.previousInputbox';
public static LABEL = nls.localize('focusPreviousInputbox', "Focus previous input box");
constructor(id: string, label: string, @IViewletService private viewletService: IViewletService) {
super(id, label);
}
public run(): TPromise<any> {
(<SearchViewlet>this.viewletService.getActiveViewlet()).focusPreviousInputBox();
return TPromise.as(null);
}
}
export class OpenSearchViewletAction extends ToggleViewletAction {
public static ID = Constants.VIEWLET_ID;
......
......@@ -31,7 +31,6 @@ import {FileChangeType, FileChangesEvent, EventType as FileEventType} from 'vs/p
import {Viewlet} from 'vs/workbench/browser/viewlet';
import {Match, FileMatch, SearchModel, FileMatchOrMatch, IChangeEvent} from 'vs/workbench/parts/search/common/searchModel';
import {getExcludes, QueryBuilder} from 'vs/workbench/parts/search/common/searchQuery';
import {VIEWLET_ID, SearchViewletVisible} from 'vs/workbench/parts/search/common/constants';
import {MessageType, InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
import {ISearchProgressItem, ISearchComplete, ISearchQuery, IQueryOptions, ISearchConfiguration} from 'vs/platform/search/common/search';
import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService';
......@@ -55,6 +54,7 @@ import { RefreshAction, CollapseAllAction, ClearSearchResultsAction, ConfigureGl
import { IReplaceService } from 'vs/workbench/parts/search/common/replace';
import Severity from 'vs/base/common/severity';
import {IUntitledEditorService} from 'vs/workbench/services/untitled/common/untitledEditorService';
import * as Constants from 'vs/workbench/parts/search/common/constants';
export class SearchViewlet extends Viewlet {
......@@ -70,6 +70,7 @@ export class SearchViewlet extends Viewlet {
private callOnModelChange: lifecycle.IDisposable[];
private viewletVisible: IContextKey<boolean>;
private inputBoxFocussed: IContextKey<boolean>;
private actionRegistry: { [key: string]: Action; };
private tree: ITree;
private viewletSettings: any;
......@@ -105,10 +106,11 @@ export class SearchViewlet extends Viewlet {
@IReplaceService private replaceService: IReplaceService,
@IUntitledEditorService private untitledEditorService: IUntitledEditorService
) {
super(VIEWLET_ID, telemetryService);
super(Constants.VIEWLET_ID, telemetryService);
this.toDispose = [];
this.viewletVisible = SearchViewletVisible.bindTo(contextKeyService);
this.viewletVisible = Constants.SearchViewletVisibleKey.bindTo(contextKeyService);
this.inputBoxFocussed = Constants.InputBoxFocussedKey.bindTo(this.contextKeyService);
this.callOnModelChange = [];
this.queryBuilder = this.instantiationService.createInstance(QueryBuilder);
......@@ -117,6 +119,7 @@ export class SearchViewlet extends Viewlet {
this.toUnbind.push(this.eventService.addListener2(FileEventType.FILE_CHANGES, (e) => this.onFilesChanged(e)));
this.toUnbind.push(this.untitledEditorService.onDidChangeDirty(e => this.onUntitledDidChangeDirty(e)));
this.toUnbind.push(this.configurationService.onDidUpdateConfiguration(e => this.onConfigurationUpdated(e.config)));
}
private onConfigurationUpdated(configuration: any): void {
......@@ -172,21 +175,12 @@ export class SearchViewlet extends Viewlet {
this.inputPatternIncludes.setValue(patternIncludes);
this.inputPatternIncludes
.on(dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
let keyboardEvent = new StandardKeyboardEvent(e);
if (keyboardEvent.equals(CommonKeybindings.UP_ARROW)) {
dom.EventHelper.stop(e);
this.searchWidget.focus(true, true);
} else if (keyboardEvent.equals(CommonKeybindings.DOWN_ARROW)) {
dom.EventHelper.stop(e);
this.inputPatternExclusions.focus();
this.inputPatternExclusions.select();
}
}).on(FindInput.OPTION_CHANGE, (e) => {
.on(FindInput.OPTION_CHANGE, (e) => {
this.onQueryChanged(false);
});
this.inputPatternIncludes.onSubmit(() => this.onQueryChanged(true));
this.inputPatternIncludes.onSubmit(() => this.onQueryChanged(true));
this.trackInputBox(this.inputPatternIncludes.inputFocusTracker);
});
//pattern exclusion list
......@@ -202,21 +196,12 @@ export class SearchViewlet extends Viewlet {
this.inputPatternExclusions.setValue(patternExclusions);
this.inputPatternExclusions
.on(dom.EventType.KEY_DOWN, (e: KeyboardEvent) => {
let keyboardEvent = new StandardKeyboardEvent(e);
if (keyboardEvent.equals(CommonKeybindings.UP_ARROW)) {
dom.EventHelper.stop(e);
this.inputPatternIncludes.focus();
this.inputPatternIncludes.select();
} else if (keyboardEvent.equals(CommonKeybindings.DOWN_ARROW)) {
dom.EventHelper.stop(e);
this.selectTreeIfNotSelected();
}
}).on(FindInput.OPTION_CHANGE, (e) => {
.on(FindInput.OPTION_CHANGE, (e) => {
this.onQueryChanged(false);
});
this.inputPatternExclusions.onSubmit(() => this.onQueryChanged(true));
this.inputPatternExclusions.onSubmit(() => this.onQueryChanged(true));
this.trackInputBox(this.inputPatternExclusions.inputFocusTracker);
});
// add hint if we have global exclusion
......@@ -293,14 +278,21 @@ export class SearchViewlet extends Viewlet {
this.tree.refresh();
}));
this.toUnbind.push(this.searchWidget.onKeyDownArrow(() => {
if (this.showsFileTypes()) {
this.toggleFileTypes(true, this.showsFileTypes());
} else {
this.selectTreeIfNotSelected();
}
}));
this.toUnbind.push(this.searchWidget.onReplaceAll(() => this.replaceAll()));
this.trackInputBox(this.searchWidget.searchInputFocusTracker);
this.trackInputBox(this.searchWidget.replaceInputFocusTracker);
}
private trackInputBox(inputFocusTracker: dom.IFocusTracker): void {
this.toUnbind.push(inputFocusTracker.addFocusListener(() => {
this.inputBoxFocussed.set(true);
}));
this.toUnbind.push(inputFocusTracker.addBlurListener(() => {
this.inputBoxFocussed.set(this.searchWidget.searchInputHasFocus()
|| this.searchWidget.replaceInputHasFocus()
|| this.inputPatternIncludes.inputHasFocus()
|| this.inputPatternExclusions.inputHasFocus());
}));
}
private onReplaceToggled(): void {
......@@ -493,6 +485,55 @@ export class SearchViewlet extends Viewlet {
this.searchWidget.focus();
}
public focusNextInputBox(): void {
if (this.searchWidget.searchInputHasFocus()) {
this.searchWidget.focus(true, true);
return;
}
if (this.searchWidget.replaceInputHasFocus()) {
if (this.showsFileTypes()) {
this.toggleFileTypes(true, this.showsFileTypes());
} else {
this.selectTreeIfNotSelected();
}
return;
}
if (this.inputPatternIncludes.inputHasFocus()) {
this.inputPatternExclusions.focus();
this.inputPatternExclusions.select();
return;
}
if (this.inputPatternExclusions.inputHasFocus()) {
this.selectTreeIfNotSelected();
return;
}
}
public focusPreviousInputBox(): void {
if (this.searchWidget.searchInputHasFocus()) {
return;
}
if (this.searchWidget.replaceInputHasFocus()) {
this.searchWidget.focus(true);
return;
}
if (this.inputPatternIncludes.inputHasFocus()) {
this.searchWidget.focus(true, true);
return;
}
if (this.inputPatternExclusions.inputHasFocus()) {
this.inputPatternIncludes.focus();
this.inputPatternIncludes.select();
return;
}
}
public moveFocusFromResults(): void {
if (this.showsFileTypes()) {
this.toggleFileTypes(true, true, false, true);
......
......@@ -3,9 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import nls = require('vs/nls');
import strings = require('vs/base/common/strings');
import dom = require('vs/base/browser/dom');
import * as nls from 'vs/nls';
import * as strings from 'vs/base/common/strings';
import * as dom from 'vs/base/browser/dom';
import { TPromise } from 'vs/base/common/winjs.base';
import { Widget } from 'vs/base/browser/ui/widget';
import { Action } from 'vs/base/common/actions';
......@@ -16,7 +16,7 @@ import { Button } from 'vs/base/browser/ui/button/button';
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { ContextKeyExpr, RawContextKey, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { ContextKeyExpr, IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
import Event, { Emitter } from 'vs/base/common/event';
......@@ -25,6 +25,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { IViewletService } from 'vs/workbench/services/viewlet/common/viewletService';
import { isSearchViewletFocussed, appendKeyBindingLabel } from 'vs/workbench/parts/search/browser/searchActions';
import { CONTEXT_FIND_WIDGET_NOT_VISIBLE } from 'vs/editor/contrib/find/common/findController';
import * as Constants from 'vs/workbench/parts/search/common/constants';
export interface ISearchWidgetOptions {
value?:string;
......@@ -65,7 +66,6 @@ class ReplaceAllAction extends Action {
export class SearchWidget extends Widget {
static REPLACE_ACTIVE_CONTEXT_KEY= new RawContextKey<boolean>('replaceActive', false);
private static REPLACE_ALL_DISABLED_LABEL= nls.localize('search.action.replaceAll.disabled.label', "Replace All (Submit Search to Enable)");
private static REPLACE_ALL_ENABLED_LABEL=(keyBindingService2: IKeybindingService):string=>{
let keybindings = keyBindingService2.lookupKeybindings(ReplaceAllAction.ID);
......@@ -74,8 +74,12 @@ export class SearchWidget extends Widget {
public domNode: HTMLElement;
public searchInput: FindInput;
private searchInputBoxFocussed: IContextKey<boolean>;
private replaceInput: InputBox;
public searchInputFocusTracker: dom.IFocusTracker;
public replaceInputFocusTracker: dom.IFocusTracker;
private replaceContainer: HTMLElement;
private toggleReplaceButton: Button;
private replaceAllAction: ReplaceAllAction;
......@@ -97,16 +101,14 @@ export class SearchWidget extends Widget {
private _onReplaceValueChanged = this._register(new Emitter<string>());
public onReplaceValueChanged: Event<string> = this._onReplaceValueChanged.event;
private _onKeyDownArrow = this._register(new Emitter<void>());
public onKeyDownArrow: Event<void> = this._onKeyDownArrow.event;
private _onReplaceAll = this._register(new Emitter<void>());
public onReplaceAll: Event<void> = this._onReplaceAll.event;
constructor(container: Builder, private contextViewService: IContextViewService, options: ISearchWidgetOptions= Object.create(null),
private keyBindingService: IContextKeyService, private keyBindingService2: IKeybindingService, private instantiationService: IInstantiationService) {
super();
this.replaceActive = SearchWidget.REPLACE_ACTIVE_CONTEXT_KEY.bindTo(this.keyBindingService);
this.replaceActive = Constants.ReplaceActiveKey.bindTo(this.keyBindingService);
this.searchInputBoxFocussed = Constants.SearchInputBoxFocussedKey.bindTo(this.keyBindingService);
this.render(container, options);
}
......@@ -154,6 +156,14 @@ export class SearchWidget extends Widget {
}
}
public searchInputHasFocus(): boolean {
return this.searchInputBoxFocussed.get();
}
public replaceInputHasFocus(): boolean {
return this.replaceInput.hasFocus();
}
private render(container: Builder, options: ISearchWidgetOptions): void {
this.domNode = container.div({ 'class': 'search-widget' }).style({ position: 'relative' }).getHTMLElement();
this.renderToggleReplaceButton(this.domNode);
......@@ -179,11 +189,18 @@ export class SearchWidget extends Widget {
let searchInputContainer= dom.append(parent, dom.$('.search-container.input-box'));
this.searchInput = this._register(new FindInput(searchInputContainer, this.contextViewService, inputOptions));
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);
this.searchInput.setWholeWords(!!options.isWholeWords);
this.searchInputFocusTracker = dom.trackFocus(this.searchInput.inputBox.inputElement);
this._register(this.searchInputFocusTracker.addFocusListener(() => {
this.searchInputBoxFocussed.set(true);
}));
this._register(this.searchInputFocusTracker.addBlurListener(() => {
this.searchInputBoxFocussed.set(false);
}));
}
private renderReplaceInput(parent: HTMLElement): void {
......@@ -193,7 +210,6 @@ 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")
}));
this.onkeydown(this.replaceInput.inputElement, (keyboardEvent) => this.onReplaceInputKeyDown(keyboardEvent));
this.onkeyup(this.replaceInput.inputElement, (keyboardEvent) => this.onReplaceInputKeyUp(keyboardEvent));
this.replaceInput.onDidChange(() => this._onReplaceValueChanged.fire());
this.searchInput.inputBox.onDidChange(() => this.onSearchInputChanged());
......@@ -203,6 +219,8 @@ export class SearchWidget extends Widget {
this.replaceAllAction.label = SearchWidget.REPLACE_ALL_DISABLED_LABEL;
this.replaceActionBar = this._register(new ActionBar(this.replaceContainer));
this.replaceActionBar.push([this.replaceAllAction], { icon: true, label: false });
this.replaceInputFocusTracker = dom.trackFocus(this.replaceInput.inputElement);
}
triggerReplaceAll(): TPromise<any> {
......@@ -274,23 +292,6 @@ export class SearchWidget extends Widget {
}
}
private onSearchInputKeyDown(keyboardEvent: IKeyboardEvent) {
let handled= false;
switch (keyboardEvent.keyCode) {
case KeyCode.DownArrow:
if (this.isReplaceShown()) {
this.focus(true, true);
} else {
this._onKeyDownArrow.fire();
}
handled= true;
break;
}
if (handled) {
keyboardEvent.preventDefault();
}
}
private onReplaceInputKeyUp(keyboardEvent: IKeyboardEvent) {
switch (keyboardEvent.keyCode) {
case KeyCode.Enter:
......@@ -305,23 +306,6 @@ export class SearchWidget extends Widget {
}
}
private onReplaceInputKeyDown(keyboardEvent: IKeyboardEvent) {
let handled= false;
switch (keyboardEvent.keyCode) {
case KeyCode.UpArrow:
this.focus(true);
handled= true;
break;
case KeyCode.DownArrow:
this._onKeyDownArrow.fire();
handled= true;
break;
}
if (handled) {
keyboardEvent.preventDefault();
}
}
private submitSearch(refresh: boolean= true): void {
if (this.searchInput.getValue()) {
this._onSearchSubmit.fire(refresh);
......@@ -332,6 +316,12 @@ export class SearchWidget extends Widget {
this.setReplaceAllActionState(false);
this.replaceAllAction.searchWidget= null;
this.replaceActionBar = null;
if (this.searchInputFocusTracker) {
this.searchInputFocusTracker.dispose();
}
if (this.replaceInputFocusTracker) {
this.replaceInputFocusTracker.dispose();
}
super.dispose();
}
}
......@@ -339,7 +329,7 @@ export class SearchWidget extends Widget {
export function registerContributions() {
KeybindingsRegistry.registerCommandAndKeybindingRule({id: ReplaceAllAction.ID,
weight: KeybindingsRegistry.WEIGHT.workbenchContrib(),
when: ContextKeyExpr.and(ContextKeyExpr.has('searchViewletVisible'), SearchWidget.REPLACE_ACTIVE_CONTEXT_KEY, CONTEXT_FIND_WIDGET_NOT_VISIBLE),
when: ContextKeyExpr.and(ContextKeyExpr.has('searchViewletVisible'), Constants.ReplaceActiveKey, CONTEXT_FIND_WIDGET_NOT_VISIBLE),
primary: KeyMod.Alt | KeyMod.CtrlCmd | KeyCode.Enter,
handler: accessor => {
if (isSearchViewletFocussed(accessor.get(IViewletService))) {
......
......@@ -7,4 +7,7 @@ import {RawContextKey} from 'vs/platform/contextkey/common/contextkey';
export const VIEWLET_ID = 'workbench.view.search';
export const SearchViewletVisible = new RawContextKey<boolean>('searchViewletVisible', true);
export const SearchViewletVisibleKey = new RawContextKey<boolean>('searchViewletVisible', true);
export const InputBoxFocussedKey = new RawContextKey<boolean>('inputBoxFocus', false);
export const SearchInputBoxFocussedKey = new RawContextKey<boolean>('searchInputBoxFocus', false);
export const ReplaceActiveKey= new RawContextKey<boolean>('replaceActive', false);
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册