提交 2058bac9 编写于 作者: J Joao Moreno

ITypeLabelProvider

fixes #64233
上级 34f0b187
...@@ -52,4 +52,8 @@ export interface IListContextMenuEvent<T> { ...@@ -52,4 +52,8 @@ export interface IListContextMenuEvent<T> {
export interface IIdentityProvider<T> { export interface IIdentityProvider<T> {
getId(element: T): { toString(): string; }; getId(element: T): { toString(): string; };
}
export interface ITypeLabelProvider<T> {
getTypeLabel(element: T): { toString(): string; };
} }
\ No newline at end of file
...@@ -14,9 +14,9 @@ import * as platform from 'vs/base/common/platform'; ...@@ -14,9 +14,9 @@ import * as platform from 'vs/base/common/platform';
import { Gesture } from 'vs/base/browser/touch'; import { Gesture } from 'vs/base/browser/touch';
import { KeyCode } from 'vs/base/common/keyCodes'; import { KeyCode } from 'vs/base/common/keyCodes';
import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent';
import { Event, Emitter, EventBufferer, chain, mapEvent, anyEvent } from 'vs/base/common/event'; import { Event, Emitter, EventBufferer, chain, mapEvent, anyEvent, debounceEvent, reduceEvent } from 'vs/base/common/event';
import { domEvent } from 'vs/base/browser/event'; import { domEvent } from 'vs/base/browser/event';
import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IIdentityProvider } from './list'; import { IListVirtualDelegate, IListRenderer, IListEvent, IListContextMenuEvent, IListMouseEvent, IListTouchEvent, IListGestureEvent, IIdentityProvider, ITypeLabelProvider } from './list';
import { ListView, IListViewOptions } from './listView'; import { ListView, IListViewOptions } from './listView';
import { Color } from 'vs/base/common/color'; import { Color } from 'vs/base/common/color';
import { mixin } from 'vs/base/common/objects'; import { mixin } from 'vs/base/common/objects';
...@@ -24,6 +24,7 @@ import { ScrollbarVisibility } from 'vs/base/common/scrollable'; ...@@ -24,6 +24,7 @@ import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { ISpliceable } from 'vs/base/common/sequence'; import { ISpliceable } from 'vs/base/common/sequence';
import { CombinedSpliceable } from 'vs/base/browser/ui/list/splice'; import { CombinedSpliceable } from 'vs/base/browser/ui/list/splice';
import { clamp } from 'vs/base/common/numbers'; import { clamp } from 'vs/base/common/numbers';
import { matchesPrefix } from 'vs/base/common/filters';
interface ITraitChangeEvent { interface ITraitChangeEvent {
indexes: number[]; indexes: number[];
...@@ -322,6 +323,69 @@ class KeyboardController<T> implements IDisposable { ...@@ -322,6 +323,69 @@ class KeyboardController<T> implements IDisposable {
} }
} }
enum TypeLabelControllerState {
Idle,
Typing
}
class TypeLabelController<T> implements IDisposable {
private state: TypeLabelControllerState = TypeLabelControllerState.Idle;
private disposables: IDisposable[] = [];
constructor(
private list: List<T>,
private view: ListView<T>,
private typeLabelProvider: ITypeLabelProvider<T>
) {
const onChar = chain(domEvent(view.domNode, 'keydown'))
.map(event => new StandardKeyboardEvent(event))
.filter(event => {
if (event.ctrlKey || event.metaKey || event.altKey) {
return false;
}
return (event.keyCode >= KeyCode.KEY_A && event.keyCode <= KeyCode.KEY_Z)
|| (event.keyCode >= KeyCode.KEY_0 && event.keyCode <= KeyCode.KEY_9)
|| (event.keyCode >= KeyCode.US_SEMICOLON && event.keyCode <= KeyCode.US_QUOTE);
})
.map(event => event.browserEvent.key)
.event;
const onClear = debounceEvent<string, null>(onChar, () => null, 800);
const onInput = reduceEvent<string | null, string | null>(anyEvent(onChar, onClear), (r, i) => i === null ? null : ((r || '') + i));
onInput(this.onInput, this, this.disposables);
}
private onInput(word: string | null): void {
if (!word) {
this.state = TypeLabelControllerState.Idle;
return;
}
const focus = this.list.getFocus();
const start = focus.length > 0 ? focus[0] : 0;
const delta = this.state === TypeLabelControllerState.Idle ? 1 : 0;
this.state = TypeLabelControllerState.Typing;
for (let i = 0; i < this.list.length; i++) {
const index = (start + i + delta) % this.list.length;
const label = this.typeLabelProvider.getTypeLabel(this.view.element(index));
if (matchesPrefix(word, label.toString())) {
this.list.setFocus([index]);
this.list.reveal(index);
return;
}
}
}
dispose() {
this.disposables = dispose(this.disposables);
}
}
class DOMFocusController<T> implements IDisposable { class DOMFocusController<T> implements IDisposable {
private disposables: IDisposable[] = []; private disposables: IDisposable[] = [];
...@@ -666,6 +730,7 @@ export class DefaultStyleController implements IStyleController { ...@@ -666,6 +730,7 @@ export class DefaultStyleController implements IStyleController {
export interface IListOptions<T> extends IListViewOptions, IListStyles { export interface IListOptions<T> extends IListViewOptions, IListStyles {
identityProvider?: IIdentityProvider<T>; identityProvider?: IIdentityProvider<T>;
typeLabelProvider?: ITypeLabelProvider<T>;
ariaLabel?: string; ariaLabel?: string;
mouseSupport?: boolean; mouseSupport?: boolean;
selectOnMouseDown?: boolean; selectOnMouseDown?: boolean;
...@@ -1002,6 +1067,11 @@ export class List<T> implements ISpliceable<T>, IDisposable { ...@@ -1002,6 +1067,11 @@ export class List<T> implements ISpliceable<T>, IDisposable {
this.disposables.push(controller); this.disposables.push(controller);
} }
if (options.typeLabelProvider) {
const controller = new TypeLabelController(this, this.view, options.typeLabelProvider);
this.disposables.push(controller);
}
if (typeof options.mouseSupport === 'boolean' ? options.mouseSupport : true) { if (typeof options.mouseSupport === 'boolean' ? options.mouseSupport : true) {
this.disposables.push(new MouseController(this, this.view, options)); this.disposables.push(new MouseController(this, this.view, options));
} }
......
...@@ -34,6 +34,11 @@ function asListOptions<T, TFilterData>(options?: IAbstractTreeOptions<T, TFilter ...@@ -34,6 +34,11 @@ function asListOptions<T, TFilterData>(options?: IAbstractTreeOptions<T, TFilter
getAriaLabel(e) { getAriaLabel(e) {
return options.accessibilityProvider!.getAriaLabel(e.element); return options.accessibilityProvider!.getAriaLabel(e.element);
} }
},
typeLabelProvider: options.typeLabelProvider && {
getTypeLabel(e) {
return options.typeLabelProvider!.getTypeLabel(e.element);
}
} }
}; };
} }
......
...@@ -147,8 +147,13 @@ function asObjectTreeOptions<T, TFilterData>(options?: IAsyncDataTreeOptions<T, ...@@ -147,8 +147,13 @@ function asObjectTreeOptions<T, TFilterData>(options?: IAsyncDataTreeOptions<T,
} }
}, },
filter: options.filter && { filter: options.filter && {
filter(element, parentVisibility) { filter(e, parentVisibility) {
return options.filter!.filter(element.element!, parentVisibility); return options.filter!.filter(e.element!, parentVisibility);
}
},
typeLabelProvider: options.typeLabelProvider && {
getTypeLabel(e) {
return options.typeLabelProvider!.getTypeLabel(e.element!);
} }
} }
}; };
......
...@@ -410,6 +410,15 @@ export function anyEvent<T>(...events: Event<T>[]): Event<T> { ...@@ -410,6 +410,15 @@ export function anyEvent<T>(...events: Event<T>[]): Event<T> {
return (listener, thisArgs = null, disposables?) => combinedDisposable(events.map(event => event(e => listener.call(thisArgs, e), null, disposables))); return (listener, thisArgs = null, disposables?) => combinedDisposable(events.map(event => event(e => listener.call(thisArgs, e), null, disposables)));
} }
export function reduceEvent<I, O>(event: Event<I>, merger: (last: O | undefined, event: I) => O): Event<O> {
let output: O | undefined = undefined;
return mapEvent<I, O>(event, e => {
output = merger(output, e);
return output;
});
}
export function debounceEvent<T>(event: Event<T>, merger: (last: T, event: T) => T, delay?: number, leading?: boolean, leakWarningThreshold?: number): Event<T>; export function debounceEvent<T>(event: Event<T>, merger: (last: T, event: T) => T, delay?: number, leading?: boolean, leakWarningThreshold?: number): Event<T>;
export function debounceEvent<I, O>(event: Event<I>, merger: (last: O | undefined, event: I) => O, delay?: number, leading?: boolean, leakWarningThreshold?: number): Event<O>; export function debounceEvent<I, O>(event: Event<I>, merger: (last: O | undefined, event: I) => O, delay?: number, leading?: boolean, leakWarningThreshold?: number): Event<O>;
export function debounceEvent<I, O>(event: Event<I>, merger: (last: O | undefined, event: I) => O, delay: number = 100, leading = false, leakWarningThreshold?: number): Event<O> { export function debounceEvent<I, O>(event: Event<I>, merger: (last: O | undefined, event: I) => O, delay: number = 100, leading = false, leakWarningThreshold?: number): Event<O> {
......
...@@ -13,7 +13,7 @@ import { PanelViewlet, ViewletPanel, IViewletPanelOptions } from 'vs/workbench/b ...@@ -13,7 +13,7 @@ import { PanelViewlet, ViewletPanel, IViewletPanelOptions } from 'vs/workbench/b
import { append, $, addClass, toggleClass, trackFocus, Dimension, addDisposableListener, removeClass } from 'vs/base/browser/dom'; import { append, $, addClass, toggleClass, trackFocus, Dimension, addDisposableListener, removeClass } from 'vs/base/browser/dom';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { List } from 'vs/base/browser/ui/list/listWidget'; import { List } from 'vs/base/browser/ui/list/listWidget';
import { IListVirtualDelegate, IListRenderer, IListContextMenuEvent, IListEvent } from 'vs/base/browser/ui/list/list'; import { IListVirtualDelegate, IListRenderer, IListContextMenuEvent, IListEvent, ITypeLabelProvider, IIdentityProvider } from 'vs/base/browser/ui/list/list';
import { VIEWLET_ID, VIEW_CONTAINER } from 'vs/workbench/parts/scm/common/scm'; import { VIEWLET_ID, VIEW_CONTAINER } from 'vs/workbench/parts/scm/common/scm';
import { FileLabel } from 'vs/workbench/browser/labels'; import { FileLabel } from 'vs/workbench/browser/labels';
import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge';
...@@ -557,7 +557,7 @@ class ProviderListDelegate implements IListVirtualDelegate<ISCMResourceGroup | I ...@@ -557,7 +557,7 @@ class ProviderListDelegate implements IListVirtualDelegate<ISCMResourceGroup | I
} }
} }
const scmResourceIdentityProvider = { const scmResourceIdentityProvider = new class implements IIdentityProvider<ISCMResourceGroup | ISCMResource> {
getId(r: ISCMResourceGroup | ISCMResource): string { getId(r: ISCMResourceGroup | ISCMResource): string {
if (isSCMResource(r)) { if (isSCMResource(r)) {
const group = r.resourceGroup; const group = r.resourceGroup;
...@@ -570,6 +570,16 @@ const scmResourceIdentityProvider = { ...@@ -570,6 +570,16 @@ const scmResourceIdentityProvider = {
} }
}; };
const scmTypeLabelProvider = new class implements ITypeLabelProvider<ISCMResourceGroup | ISCMResource> {
getTypeLabel(e: ISCMResourceGroup | ISCMResource) {
if (isSCMResource(e)) {
return basename(e.sourceUri.fsPath);
} else {
return e.label;
}
}
};
function isGroupVisible(group: ISCMResourceGroup) { function isGroupVisible(group: ISCMResourceGroup) {
return group.elements.length > 0 || !group.hideWhenEmpty; return group.elements.length > 0 || !group.hideWhenEmpty;
} }
...@@ -869,7 +879,8 @@ export class RepositoryPanel extends ViewletPanel { ...@@ -869,7 +879,8 @@ export class RepositoryPanel extends ViewletPanel {
]; ];
this.list = this.instantiationService.createInstance(WorkbenchList, this.listContainer, delegate, renderers, { this.list = this.instantiationService.createInstance(WorkbenchList, this.listContainer, delegate, renderers, {
identityProvider: scmResourceIdentityProvider identityProvider: scmResourceIdentityProvider,
typeLabelProvider: scmTypeLabelProvider
}) as WorkbenchList<ISCMResourceGroup | ISCMResource>; }) as WorkbenchList<ISCMResourceGroup | ISCMResource>;
chain(this.list.onOpen) chain(this.list.onOpen)
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册