Skip to content

Commit

Permalink
Fix file browser search highlighting bug (jupyterlab#12538)
Browse files Browse the repository at this point in the history
* Fix file browser search highlighting bug

* Move to use IScore | null to prevent API break

* Fix failing test

* Revert type of IScore to original

* Update packages/ui-components/src/components/search.tsx

Co-authored-by: Frédéric Collonval <[email protected]>
  • Loading branch information
Martha Cryan and fcollonval authored May 13, 2022
1 parent d5d5fb9 commit 222b472
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 32 deletions.
11 changes: 9 additions & 2 deletions packages/filebrowser/src/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { Contents, ServerConnection } from '@jupyterlab/services';
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
import {
FilenameSearcher,
IScore,
ReactWidget,
SidePanel
} from '@jupyterlab/ui-components';
Expand Down Expand Up @@ -97,7 +98,10 @@ export class FileBrowser extends SidePanel {
this.listing.addClass(LISTING_CLASS);

this._filenameSearcher = FilenameSearcher({
updateFilter: (filterFn: (item: string) => boolean) => {
updateFilter: (
filterFn: (item: string) => Partial<IScore> | null,
query?: string
) => {
this.listing.model.setFilter(value => {
return filterFn(value.name.toLowerCase());
});
Expand Down Expand Up @@ -161,7 +165,10 @@ export class FileBrowser extends SidePanel {
this._filenameSearcher.dispose();

this._filenameSearcher = FilenameSearcher({
updateFilter: (filterFn: (item: string) => boolean) => {
updateFilter: (
filterFn: (item: string) => Partial<IScore> | null,
query?: string
) => {
this.listing.model.setFilter(value => {
return filterFn(value.name.toLowerCase());
});
Expand Down
17 changes: 12 additions & 5 deletions packages/filebrowser/src/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {
nullTranslator,
TranslationBundle
} from '@jupyterlab/translation';
import { IScore } from '@jupyterlab/ui-components';
import {
ArrayExt,
ArrayIterator,
Expand Down Expand Up @@ -773,7 +774,11 @@ export namespace TogglableHiddenFileBrowserModel {
export class FilterFileBrowserModel extends TogglableHiddenFileBrowserModel {
constructor(options: FilterFileBrowserModel.IOptions) {
super(options);
this._filter = options.filter ? options.filter : model => true;
this._filter =
options.filter ??
(model => {
return {};
});
this._filterDirectories = options.filterDirectories ?? true;
}

Expand All @@ -797,17 +802,19 @@ export class FilterFileBrowserModel extends TogglableHiddenFileBrowserModel {
if (!this._filterDirectories && value.type === 'directory') {
return true;
} else {
return this._filter(value);
const filtered = this._filter(value);
value.indices = filtered?.indices;
return !!filtered;
}
});
}

setFilter(filter: (value: Contents.IModel) => boolean): void {
setFilter(filter: (value: Contents.IModel) => Partial<IScore> | null): void {
this._filter = filter;
void this.refresh();
}

private _filter: (value: Contents.IModel) => boolean;
private _filter: (value: Contents.IModel) => Partial<IScore> | null;
private _filterDirectories: boolean;
}

Expand All @@ -822,7 +829,7 @@ export namespace FilterFileBrowserModel {
/**
* Filter function on file browser item model
*/
filter?: (value: Contents.IModel) => boolean;
filter?: (value: Contents.IModel) => Partial<IScore> | null;

/**
* Filter directories
Expand Down
12 changes: 7 additions & 5 deletions packages/filebrowser/src/opendialog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { PathExt } from '@jupyterlab/coreutils';
import { IDocumentManager } from '@jupyterlab/docmanager';
import { Contents } from '@jupyterlab/services';
import { ITranslator, nullTranslator } from '@jupyterlab/translation';
import { newFolderIcon, refreshIcon } from '@jupyterlab/ui-components';
import { IScore, newFolderIcon, refreshIcon } from '@jupyterlab/ui-components';
import { toArray } from '@lumino/algorithm';
import { PanelLayout, Widget } from '@lumino/widgets';
import { FileBrowser } from './browser';
Expand Down Expand Up @@ -53,7 +53,7 @@ export namespace FileDialog {
/**
* Filter function on file browser item model
*/
filter?: (value: Contents.IModel) => boolean;
filter?: (value: Contents.IModel) => Partial<IScore> | null;

/**
* The application language translator.
Expand Down Expand Up @@ -108,7 +108,9 @@ export namespace FileDialog {
): Promise<Dialog.IResult<Contents.IModel[]>> {
return getOpenFiles({
...options,
filter: model => model.type === 'directory'
filter: model => {
return model.type === 'directory' ? {} : null;
}
});
}
}
Expand All @@ -121,7 +123,7 @@ class OpenDialog
implements Dialog.IBodyWidget<Contents.IModel[]> {
constructor(
manager: IDocumentManager,
filter?: (value: Contents.IModel) => boolean,
filter?: (value: Contents.IModel) => Partial<IScore> | null,
translator?: ITranslator,
filterDirectories?: boolean
) {
Expand Down Expand Up @@ -230,7 +232,7 @@ namespace Private {
export const createFilteredFileBrowser = (
id: string,
manager: IDocumentManager,
filter?: (value: Contents.IModel) => boolean,
filter?: (value: Contents.IModel) => Partial<IScore> | null,
options: IFileBrowserFactory.IOptions = {},
translator?: ITranslator,
filterDirectories?: boolean
Expand Down
15 changes: 9 additions & 6 deletions packages/filebrowser/test/openfiledialog.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ describe('@jupyterlab/filebrowser', () => {
it('should accept filter option', () => {
const model = new FilterFileBrowserModel({
manager,
filter: (model: Contents.IModel) => false
filter: (model: Contents.IModel) => null
});
expect(model).toBeInstanceOf(FilterFileBrowserModel);
});
Expand All @@ -78,7 +78,7 @@ describe('@jupyterlab/filebrowser', () => {
it('should list all directories if filterDirectories is false', async () => {
const filteredModel = new FilterFileBrowserModel({
manager,
filter: (model: Contents.IModel) => false,
filter: (model: Contents.IModel) => null,
filterDirectories: false
});
await filteredModel.cd();
Expand All @@ -94,7 +94,7 @@ describe('@jupyterlab/filebrowser', () => {
it('should filter files and directories if filterDirectories is true', async () => {
const filteredModel = new FilterFileBrowserModel({
manager,
filter: (model: Contents.IModel) => false,
filter: (model: Contents.IModel) => null,
filterDirectories: true
});
await filteredModel.cd();
Expand All @@ -108,7 +108,8 @@ describe('@jupyterlab/filebrowser', () => {
it('should respect the filter', async () => {
const filteredModel = new FilterFileBrowserModel({
manager,
filter: (model: Contents.IModel) => model.type === 'notebook'
filter: (model: Contents.IModel) =>
model.type === 'notebook' ? {} : null
});
await filteredModel.cd();
const model = new FileBrowserModel({ manager });
Expand Down Expand Up @@ -151,7 +152,8 @@ describe('@jupyterlab/filebrowser', () => {
manager,
title: 'Select a notebook',
host: node,
filter: (value: Contents.IModel) => value.type === 'notebook'
filter: (value: Contents.IModel) =>
value.type === 'notebook' ? {} : null
});

await acceptDialog();
Expand All @@ -174,7 +176,8 @@ describe('@jupyterlab/filebrowser', () => {
manager,
title: 'Select a notebook',
host: node,
filter: (value: Contents.IModel) => value.type === 'notebook'
filter: (value: Contents.IModel) =>
value.type === 'notebook' ? {} : null
});

await waitForDialog();
Expand Down
8 changes: 6 additions & 2 deletions packages/settingeditor/src/pluginlist.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { ITranslator, nullTranslator } from '@jupyterlab/translation';
import {
classes,
FilterBox,
IScore,
LabIcon,
settingsIcon,
updateFilterFunction
Expand Down Expand Up @@ -241,7 +242,7 @@ export class PluginList extends ReactWidget {
* @returns - String array of properties that match the search results.
*/
getFilterString(
filter: (item: string) => boolean,
filter: (item: string) => Partial<IScore> | null,
props: ISettingRegistry.IProperty,
definitions?: any,
ref?: string
Expand Down Expand Up @@ -319,7 +320,10 @@ export class PluginList extends ReactWidget {
* Updates the filter when the search bar value changes.
* @param filter Filter function passed by search bar based on search value.
*/
setFilter(filter: (item: string) => boolean, query?: string): void {
setFilter(
filter: (item: string) => Partial<IScore> | null,
query?: string
): void {
this._filter = (plugin: ISettingRegistry.IPlugin): string[] | null => {
if (filter(plugin.schema.title ?? '')) {
return null;
Expand Down
25 changes: 13 additions & 12 deletions packages/ui-components/src/components/search.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@ export interface IFilterBoxProps {
/**
* A function to callback when filter is updated.
*/
updateFilter: (filterFn: (item: string) => boolean, query?: string) => void;
updateFilter: (
filterFn: (item: string) => Partial<IScore> | null,
query?: string
) => void;

/**
* Whether to use the fuzzy filter.
Expand Down Expand Up @@ -44,7 +47,7 @@ export interface IFilterBoxProps {
/**
* A text match score with associated content item.
*/
interface IScore {
export interface IScore {
/**
* The numerical score for the text match.
*/
Expand All @@ -59,7 +62,7 @@ interface IScore {
/**
* Perform a fuzzy search on a single item.
*/
function fuzzySearch(source: string, query: string): IScore | null {
export function fuzzySearch(source: string, query: string): IScore | null {
// Set up the match score and indices array.
let score = Infinity;
let indices: number[] | null = null;
Expand Down Expand Up @@ -111,26 +114,24 @@ export const updateFilterFunction = (
useFuzzyFilter: boolean,
caseSensitive?: boolean
) => {
return (item: string) => {
return (item: string): Partial<IScore> | null => {
if (useFuzzyFilter) {
// Run the fuzzy search for the item and query.
const query = value.toLowerCase();
let score = fuzzySearch(item, query);
// Ignore the item if it is not a match.
if (!score) {
return false;
}
return true;
return fuzzySearch(item, query);
}
if (!caseSensitive) {
item = item.toLocaleLowerCase();
value = value.toLocaleLowerCase();
}
const i = item.indexOf(value);
if (i === -1) {
return false;
return null;
}
return true;
return {
indices: [...Array(item.length).keys()].map(x => x + 1)
};
};
};

Expand All @@ -140,7 +141,7 @@ export const FilterBox = (props: IFilterBoxProps) => {
if (props.forceRefresh) {
useEffect(() => {
props.updateFilter((item: string) => {
return true;
return {};
});
}, []);
}
Expand Down

0 comments on commit 222b472

Please sign in to comment.