未验证 提交 5aed010f 编写于 作者: L Ladislau Szomoru 提交者: GitHub

SCM viewlet filter on type improvements (#109170)

上级 0f190483
......@@ -66,11 +66,11 @@ export interface IIdentityProvider<T> {
export interface IKeyboardNavigationLabelProvider<T> {
/**
* Return a keyboard navigation label which will be used by the
* list for filtering/navigating. Return `undefined` to make an
* element always match.
* Return a keyboard navigation label(s) which will be used by
* the list for filtering/navigating. Return `undefined` to make
* an element always match.
*/
getKeyboardNavigationLabel(element: T): { toString(): string | undefined; } | undefined;
getKeyboardNavigationLabel(element: T): { toString(): string | undefined; } | { toString(): string | undefined; }[] | undefined;
}
export interface IKeyboardNavigationDelegate {
......
......@@ -507,8 +507,9 @@ class TreeRenderer<T, TFilterData, TRef, TTemplateData> implements IListRenderer
}
}
class TypeFilter<T> implements ITreeFilter<T, FuzzyScore>, IDisposable {
export type LabelFuzzyScore = { label: string; score: FuzzyScore };
class TypeFilter<T> implements ITreeFilter<T, FuzzyScore | LabelFuzzyScore>, IDisposable {
private _totalCount = 0;
get totalCount(): number { return this._totalCount; }
private _matchCount = 0;
......@@ -531,7 +532,7 @@ class TypeFilter<T> implements ITreeFilter<T, FuzzyScore>, IDisposable {
tree.onWillRefilter(this.reset, this, this.disposables);
}
filter(element: T, parentVisibility: TreeVisibility): TreeFilterResult<FuzzyScore> {
filter(element: T, parentVisibility: TreeVisibility): TreeFilterResult<FuzzyScore | LabelFuzzyScore> {
if (this._filter) {
const result = this._filter.filter(element, parentVisibility);
......@@ -562,27 +563,28 @@ class TypeFilter<T> implements ITreeFilter<T, FuzzyScore>, IDisposable {
}
const label = this.keyboardNavigationLabelProvider.getKeyboardNavigationLabel(element);
const labelStr = label && label.toString();
const labels = Array.isArray(label) ? label : [label];
if (typeof labelStr === 'undefined') {
return { data: FuzzyScore.Default, visibility: true };
}
const score = fuzzyScore(this._pattern, this._lowercasePattern, 0, labelStr, labelStr.toLowerCase(), 0, true);
if (!score) {
if (this.tree.options.filterOnType) {
return TreeVisibility.Recurse;
} else {
for (const l of labels) {
const labelStr = l && l.toString();
if (typeof labelStr === 'undefined') {
return { data: FuzzyScore.Default, visibility: true };
}
// DEMO: smarter filter ?
// return parentVisibility === TreeVisibility.Visible ? true : TreeVisibility.Recurse;
const score = fuzzyScore(this._pattern, this._lowercasePattern, 0, labelStr, labelStr.toLowerCase(), 0, true);
if (score) {
this._matchCount++;
return labels.length === 1 ?
{ data: score, visibility: true } :
{ data: { label: labelStr, score: score }, visibility: true };
}
}
this._matchCount++;
return { data: score, visibility: true };
if (this.tree.options.filterOnType) {
return TreeVisibility.Recurse;
} else {
return { data: FuzzyScore.Default, visibility: true };
}
}
private reset(): void {
......
......@@ -77,42 +77,10 @@ import { RepositoryRenderer } from 'vs/workbench/contrib/scm/browser/scmReposito
import { IPosition } from 'vs/editor/common/core/position';
import { ColorScheme } from 'vs/platform/theme/common/theme';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { LabelFuzzyScore } from 'vs/base/browser/ui/tree/abstractTree';
type TreeElement = ISCMRepository | ISCMInput | ISCMResourceGroup | IResourceNode<ISCMResource, ISCMResourceGroup> | ISCMResource;
function splitMatches(uri: URI, filterData: FuzzyScore | undefined): [IMatch[] | undefined, IMatch[] | undefined] {
let matches: IMatch[] | undefined;
let descriptionMatches: IMatch[] | undefined;
if (filterData) {
matches = [];
descriptionMatches = [];
const fileName = basename(uri);
const allMatches = createMatches(filterData);
for (const match of allMatches) {
if (match.start < fileName.length) {
matches!.push(
{
start: match.start,
end: Math.min(match.end, fileName.length)
}
);
} else {
descriptionMatches!.push(
{
start: match.start - (fileName.length + 1),
end: match.end - (fileName.length + 1)
}
);
}
}
}
return [matches, descriptionMatches];
}
interface ISCMLayout {
height: number | undefined;
width: number | undefined;
......@@ -343,7 +311,7 @@ class RepositoryPaneActionRunner extends ActionRunner {
}
}
class ResourceRenderer implements ICompressibleTreeRenderer<ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>, FuzzyScore, ResourceTemplate> {
class ResourceRenderer implements ICompressibleTreeRenderer<ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>, FuzzyScore | LabelFuzzyScore, ResourceTemplate> {
static readonly TEMPLATE_ID = 'resource';
get templateId(): string { return ResourceRenderer.TEMPLATE_ID; }
......@@ -373,7 +341,7 @@ class ResourceRenderer implements ICompressibleTreeRenderer<ISCMResource | IReso
return { element, name, fileLabel, decorationIcon, actionBar, elementDisposables: Disposable.None, disposables };
}
renderElement(node: ITreeNode<ISCMResource, FuzzyScore> | ITreeNode<ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>, FuzzyScore>, index: number, template: ResourceTemplate): void {
renderElement(node: ITreeNode<ISCMResource, FuzzyScore | LabelFuzzyScore> | ITreeNode<ISCMResource | IResourceNode<ISCMResource, ISCMResourceGroup>, FuzzyScore | LabelFuzzyScore>, index: number, template: ResourceTemplate): void {
template.elementDisposables.dispose();
const elementDisposables = new DisposableStore();
......@@ -397,13 +365,14 @@ class ResourceRenderer implements ICompressibleTreeRenderer<ISCMResource | IReso
template.name.classList.toggle('strike-through', resourceOrFolder.element.decorations.strikeThrough);
template.element.classList.toggle('faded', resourceOrFolder.element.decorations.faded);
} else {
matches = createMatches(node.filterData as FuzzyScore | undefined);
const menus = this.scmViewService.menus.getRepositoryMenus(resourceOrFolder.context.provider);
elementDisposables.add(connectPrimaryMenuToInlineActionBar(menus.getResourceFolderMenu(resourceOrFolder.context), template.actionBar));
template.name.classList.remove('strike-through');
template.element.classList.remove('faded');
}
} else {
[matches, descriptionMatches] = splitMatches(uri, node.filterData);
[matches, descriptionMatches] = this._processFilterData(uri, node.filterData);
const menus = this.scmViewService.menus.getRepositoryMenus(resourceOrFolder.resourceGroup.provider);
elementDisposables.add(connectPrimaryMenuToInlineActionBar(menus.getResourceMenu(resourceOrFolder), template.actionBar));
template.name.classList.toggle('strike-through', resourceOrFolder.decorations.strikeThrough);
......@@ -440,11 +409,11 @@ class ResourceRenderer implements ICompressibleTreeRenderer<ISCMResource | IReso
template.elementDisposables = elementDisposables;
}
disposeElement(resource: ITreeNode<ISCMResource, FuzzyScore> | ITreeNode<IResourceNode<ISCMResource, ISCMResourceGroup>, FuzzyScore>, index: number, template: ResourceTemplate): void {
disposeElement(resource: ITreeNode<ISCMResource, FuzzyScore | LabelFuzzyScore> | ITreeNode<IResourceNode<ISCMResource, ISCMResourceGroup>, FuzzyScore | LabelFuzzyScore>, index: number, template: ResourceTemplate): void {
template.elementDisposables.dispose();
}
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<ISCMResource> | ICompressedTreeNode<IResourceNode<ISCMResource, ISCMResourceGroup>>, FuzzyScore>, index: number, template: ResourceTemplate, height: number | undefined): void {
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<ISCMResource> | ICompressedTreeNode<IResourceNode<ISCMResource, ISCMResourceGroup>>, FuzzyScore | LabelFuzzyScore>, index: number, template: ResourceTemplate, height: number | undefined): void {
template.elementDisposables.dispose();
const elementDisposables = new DisposableStore();
......@@ -454,9 +423,11 @@ class ResourceRenderer implements ICompressibleTreeRenderer<ISCMResource | IReso
const label = compressed.elements.map(e => e.name).join('/');
const fileKind = FileKind.FOLDER;
const matches = createMatches(node.filterData as FuzzyScore | undefined);
template.fileLabel.setResource({ resource: folder.uri, name: label }, {
fileDecorations: { colors: false, badges: true },
fileKind
fileKind,
matches
});
template.actionBar.clear();
......@@ -474,7 +445,7 @@ class ResourceRenderer implements ICompressibleTreeRenderer<ISCMResource | IReso
template.elementDisposables = elementDisposables;
}
disposeCompressedElements(node: ITreeNode<ICompressedTreeNode<ISCMResource> | ICompressedTreeNode<IResourceNode<ISCMResource, ISCMResourceGroup>>, FuzzyScore>, index: number, template: ResourceTemplate, height: number | undefined): void {
disposeCompressedElements(node: ITreeNode<ICompressedTreeNode<ISCMResource> | ICompressedTreeNode<IResourceNode<ISCMResource, ISCMResourceGroup>>, FuzzyScore | LabelFuzzyScore>, index: number, template: ResourceTemplate, height: number | undefined): void {
template.elementDisposables.dispose();
}
......@@ -482,6 +453,56 @@ class ResourceRenderer implements ICompressibleTreeRenderer<ISCMResource | IReso
template.elementDisposables.dispose();
template.disposables.dispose();
}
private _processFilterData(uri: URI, filterData: FuzzyScore | LabelFuzzyScore | undefined): [IMatch[] | undefined, IMatch[] | undefined] {
if (!filterData) {
return [undefined, undefined];
}
if (!(filterData as LabelFuzzyScore).label) {
const matches = createMatches(filterData as FuzzyScore);
return [matches, undefined];
}
const fileName = basename(uri);
const label = (filterData as LabelFuzzyScore).label;
const pathLength = label.length - fileName.length;
const matches = createMatches((filterData as LabelFuzzyScore).score);
// FileName match
if (label === fileName) {
return [matches, undefined];
}
// FilePath match
let labelMatches: IMatch[] = [];
let descriptionMatches: IMatch[] = [];
for (const match of matches) {
if (match.start > pathLength) {
// Label match
labelMatches.push({
start: match.start - pathLength,
end: match.end - pathLength
});
} else if (match.end < pathLength) {
// Description match
descriptionMatches.push(match);
} else {
// Spanning match
labelMatches.push({
start: 0,
end: match.end - pathLength
});
descriptionMatches.push({
start: match.start,
end: pathLength
});
}
}
return [labelMatches, descriptionMatches];
}
}
class ListDelegate implements IListVirtualDelegate<TreeElement> {
......@@ -601,7 +622,7 @@ export class SCMTreeKeyboardNavigationLabelProvider implements ICompressibleKeyb
@ILabelService private readonly labelService: ILabelService,
) { }
getKeyboardNavigationLabel(element: TreeElement): { toString(): string; } | undefined {
getKeyboardNavigationLabel(element: TreeElement): { toString(): string; } | { toString(): string; }[] | undefined {
if (ResourceTree.isResourceNode(element)) {
return element.name;
} else if (isSCMRepository(element)) {
......@@ -614,11 +635,13 @@ export class SCMTreeKeyboardNavigationLabelProvider implements ICompressibleKeyb
const viewModel = this.viewModelProvider();
if (viewModel.mode === ViewModelMode.List) {
// In List mode match using the file name and the path.
// Since a match in the file name takes precedence over a match
// in the folder name we are returning the label as file folder.
// Since we want to match both on the file name and the
// full path we return an array of labels. A match in the
// file name takes precedence over a match in the path.
const fileName = basename(element.sourceUri);
const filePath = this.labelService.getUriLabel(dirname(element.sourceUri), { relative: true });
return filePath.length !== 0 ? `${fileName} ${filePath}` : fileName;
const filePath = this.labelService.getUriLabel(element.sourceUri, { relative: true });
return [fileName, filePath];
} else {
// In Tree mode only match using the file name
return basename(element.sourceUri);
......@@ -1637,7 +1660,7 @@ export class SCMViewPane extends ViewPane {
this._register(actionRunner);
this._register(actionRunner.onDidBeforeRun(() => this.tree.domFocus()));
const renderers: ICompressibleTreeRenderer<any, FuzzyScore, any>[] = [
const renderers: ICompressibleTreeRenderer<any, any, any>[] = [
this.instantiationService.createInstance(RepositoryRenderer, actionViewItemProvider),
this.inputRenderer,
this.instantiationService.createInstance(ResourceGroupRenderer, actionViewItemProvider),
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册