1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Desktop: Resolves #11063: Improve the performance of GoToAnything (#11064)

This commit is contained in:
Henry Heino 2024-09-16 14:20:44 -07:00 committed by GitHub
parent e0daf807a6
commit 11c1c0638d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 31 additions and 27 deletions

View File

@ -2,11 +2,10 @@ import * as React from 'react';
import { AppState } from '../app.reducer'; import { AppState } from '../app.reducer';
import CommandService, { SearchResult as CommandSearchResult } from '@joplin/lib/services/CommandService'; import CommandService, { SearchResult as CommandSearchResult } from '@joplin/lib/services/CommandService';
import KeymapService from '@joplin/lib/services/KeymapService'; import KeymapService from '@joplin/lib/services/KeymapService';
import shim from '@joplin/lib/shim';
const { connect } = require('react-redux'); const { connect } = require('react-redux');
import { _ } from '@joplin/lib/locale'; import { _ } from '@joplin/lib/locale';
import { themeStyle } from '@joplin/lib/theme'; import { themeStyle } from '@joplin/lib/theme';
import SearchEngine from '@joplin/lib/services/search/SearchEngine'; import SearchEngine, { ComplexTerm } from '@joplin/lib/services/search/SearchEngine';
import gotoAnythingStyleQuery from '@joplin/lib/services/search/gotoAnythingStyleQuery'; import gotoAnythingStyleQuery from '@joplin/lib/services/search/gotoAnythingStyleQuery';
import BaseModel, { ModelType } from '@joplin/lib/BaseModel'; import BaseModel, { ModelType } from '@joplin/lib/BaseModel';
import Tag from '@joplin/lib/models/Tag'; import Tag from '@joplin/lib/models/Tag';
@ -14,7 +13,7 @@ import Folder from '@joplin/lib/models/Folder';
import Note from '@joplin/lib/models/Note'; import Note from '@joplin/lib/models/Note';
import ItemList from '../gui/ItemList'; import ItemList from '../gui/ItemList';
import HelpButton from '../gui/HelpButton'; import HelpButton from '../gui/HelpButton';
const { surroundKeywords, nextWhitespaceIndex, removeDiacritics } = require('@joplin/lib/string-utils.js'); import { surroundKeywords, nextWhitespaceIndex, removeDiacritics } from '@joplin/lib/string-utils';
import { mergeOverlappingIntervals } from '@joplin/lib/ArrayUtils'; import { mergeOverlappingIntervals } from '@joplin/lib/ArrayUtils';
import markupLanguageUtils from '../utils/markupLanguageUtils'; import markupLanguageUtils from '../utils/markupLanguageUtils';
import focusEditorIfEditorCommand from '@joplin/lib/services/commands/focusEditorIfEditorCommand'; import focusEditorIfEditorCommand from '@joplin/lib/services/commands/focusEditorIfEditorCommand';
@ -23,6 +22,7 @@ import { MarkupLanguage, MarkupToHtml } from '@joplin/renderer';
import Resource from '@joplin/lib/models/Resource'; import Resource from '@joplin/lib/models/Resource';
import { NoteEntity, ResourceEntity } from '@joplin/lib/services/database/types'; import { NoteEntity, ResourceEntity } from '@joplin/lib/services/database/types';
import Dialog from '../gui/Dialog'; import Dialog from '../gui/Dialog';
import AsyncActionQueue from '@joplin/lib/AsyncActionQueue';
const logger = Logger.create('GotoAnything'); const logger = Logger.create('GotoAnything');
@ -129,8 +129,7 @@ class DialogComponent extends React.PureComponent<Props, State> {
private inputRef: any; private inputRef: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
private itemListRef: any; private itemListRef: any;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied private listUpdateQueue_: AsyncActionQueue;
private listUpdateIID_: any;
private markupToHtml_: MarkupToHtml; private markupToHtml_: MarkupToHtml;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
private userCallback_: any = null; private userCallback_: any = null;
@ -141,6 +140,7 @@ class DialogComponent extends React.PureComponent<Props, State> {
const startString = props?.userData?.startString ? props?.userData?.startString : ''; const startString = props?.userData?.startString ? props?.userData?.startString : '';
this.userCallback_ = props?.userData?.callback; this.userCallback_ = props?.userData?.callback;
this.listUpdateQueue_ = new AsyncActionQueue(100);
this.state = { this.state = {
query: startString, query: startString,
@ -235,7 +235,7 @@ class DialogComponent extends React.PureComponent<Props, State> {
} }
public componentWillUnmount() { public componentWillUnmount() {
if (this.listUpdateIID_) shim.clearTimeout(this.listUpdateIID_); void this.listUpdateQueue_.reset();
this.props.dispatch({ this.props.dispatch({
type: 'VISIBLE_DIALOGS_REMOVE', type: 'VISIBLE_DIALOGS_REMOVE',
@ -263,12 +263,7 @@ class DialogComponent extends React.PureComponent<Props, State> {
} }
public scheduleListUpdate() { public scheduleListUpdate() {
if (this.listUpdateIID_) shim.clearTimeout(this.listUpdateIID_); this.listUpdateQueue_.push(() => this.updateList());
this.listUpdateIID_ = shim.setTimeout(async () => {
await this.updateList();
this.listUpdateIID_ = null;
}, 100);
} }
public async keywords(searchQuery: string) { public async keywords(searchQuery: string) {
@ -360,7 +355,6 @@ class DialogComponent extends React.PureComponent<Props, State> {
} }
} else { } else {
const limit = 20; const limit = 20;
const searchKeywords = await this.keywords(searchQuery);
// Note: any filtering must be done **before** fetching the notes, because we're // Note: any filtering must be done **before** fetching the notes, because we're
// going to apply a limit to the number of fetched notes. // going to apply a limit to the number of fetched notes.
@ -381,6 +375,10 @@ class DialogComponent extends React.PureComponent<Props, State> {
results = results.filter(r => !!notesById[r.id]) results = results.filter(r => !!notesById[r.id])
.map(r => ({ ...r, title: notesById[r.id].title })); .map(r => ({ ...r, title: notesById[r.id].title }));
const normalizedKeywords = (await this.keywords(searchQuery)).map(
({ valueRegex }: ComplexTerm) => new RegExp(removeDiacritics(valueRegex), 'ig'),
);
for (let i = 0; i < results.length; i++) { for (let i = 0; i < results.length; i++) {
const row = results[i]; const row = results[i];
const path = Folder.folderPathString(this.props.folders, row.parent_id); const path = Folder.folderPathString(this.props.folders, row.parent_id);
@ -388,21 +386,14 @@ class DialogComponent extends React.PureComponent<Props, State> {
if (row.fields.includes('body')) { if (row.fields.includes('body')) {
let fragments = '...'; let fragments = '...';
if (i < limit) { // Display note fragments of search keyword matches const loadFragments = (markupLanguage: MarkupLanguage, content: string) => {
const { markupLanguage, content } = getContentMarkupLanguageAndBody(
row,
notesById,
resources,
);
const indices = []; const indices = [];
const body = this.markupToHtml().stripMarkup(markupLanguage, content, { collapseWhiteSpaces: true }); const body = this.markupToHtml().stripMarkup(markupLanguage, content, { collapseWhiteSpaces: true });
const normalizedBody = removeDiacritics(body);
// Iterate over all matches in the body for each search keyword // Iterate over all matches in the body for each search keyword
for (let { valueRegex } of searchKeywords) { for (const keywordRegex of normalizedKeywords) {
valueRegex = removeDiacritics(valueRegex); for (const match of normalizedBody.matchAll(keywordRegex)) {
for (const match of removeDiacritics(body).matchAll(new RegExp(valueRegex, 'ig'))) {
// Populate 'indices' with [begin index, end index] of each note fragment // Populate 'indices' with [begin index, end index] of each note fragment
// Begins at the regex matching index, ends at the next whitespace after seeking 15 characters to the right // Begins at the regex matching index, ends at the next whitespace after seeking 15 characters to the right
indices.push([match.index, nextWhitespaceIndex(body, match.index + match[0].length + 15)]); indices.push([match.index, nextWhitespaceIndex(body, match.index + match[0].length + 15)]);
@ -418,6 +409,19 @@ class DialogComponent extends React.PureComponent<Props, State> {
fragments = mergedIndices.map((f: any) => body.slice(f[0], f[1])).join(' ... '); fragments = mergedIndices.map((f: any) => body.slice(f[0], f[1])).join(' ... ');
// Add trailing ellipsis if the final fragment doesn't end where the note is ending // Add trailing ellipsis if the final fragment doesn't end where the note is ending
if (mergedIndices.length && mergedIndices[mergedIndices.length - 1][1] !== body.length) fragments += ' ...'; if (mergedIndices.length && mergedIndices[mergedIndices.length - 1][1] !== body.length) fragments += ' ...';
};
if (i < limit) { // Display note fragments of search keyword matches
const { markupLanguage, content } = getContentMarkupLanguageAndBody(
row,
notesById,
resources,
);
// Don't load fragments for long notes -- doing so can lead to UI freezes.
if (content.length < 100_000) {
loadFragments(markupLanguage, content);
}
} }
results[i] = { ...row, path, fragments }; results[i] = { ...row, path, fragments };

View File

@ -16,7 +16,7 @@ import { isCallbackUrl, parseCallbackUrl } from '../../callbackUrlUtils';
import replaceUnsupportedCharacters from '../../utils/replaceUnsupportedCharacters'; import replaceUnsupportedCharacters from '../../utils/replaceUnsupportedCharacters';
import { htmlentitiesDecode } from '@joplin/utils/html'; import { htmlentitiesDecode } from '@joplin/utils/html';
const { sprintf } = require('sprintf-js'); const { sprintf } = require('sprintf-js');
const { pregQuote, scriptType, removeDiacritics } = require('../../string-utils.js'); import { pregQuote, scriptType, removeDiacritics } from '../../string-utils';
enum SearchType { enum SearchType {
Auto = 'auto', Auto = 'auto',
@ -59,7 +59,7 @@ export interface ComplexTerm {
value: string; value: string;
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
scriptType: any; scriptType: any;
valueRegex?: RegExp; valueRegex?: string;
} }
export interface Terms { export interface Terms {

View File

@ -2,7 +2,7 @@ const Entities = require('html-entities').AllHtmlEntities;
const htmlentities = new Entities().encode; const htmlentities = new Entities().encode;
const stringUtilsCommon = require('./string-utils-common.js'); const stringUtilsCommon = require('./string-utils-common.js');
export const pregQuote = stringUtilsCommon.pregQuote; export const pregQuote = stringUtilsCommon.pregQuote as (str: string, delimiter?: string)=> string;
export const replaceRegexDiacritics = stringUtilsCommon.replaceRegexDiacritics; export const replaceRegexDiacritics = stringUtilsCommon.replaceRegexDiacritics;
const defaultDiacriticsRemovalMap = [ const defaultDiacriticsRemovalMap = [