Skip to content

Commit

Permalink
Performance: Avoid string-allocation on keypress with inputRule
Browse files Browse the repository at this point in the history
While checking for inline code snippets surrounded by the backtick
we have been allocating new substrings from input strings when
looking for those characters, but we don't need to do this.

In this patch we're directly checking for the character on the input
string using string indexing and then passing the `position`
parameter to the `lastIndexOf` function so that it is able to scan
the original input string without copying it.

The performance impact of this change should be small and hard to
measure, but it should reduce pressure on the garbage collector and
be worthwhile even without clear measured results.
  • Loading branch information
dmsnell committed Jan 25, 2023
1 parent 14e8390 commit f56a7c7
Showing 1 changed file with 70 additions and 14 deletions.
84 changes: 70 additions & 14 deletions packages/format-library/src/code/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import {
} from '@wordpress/block-editor';
import { code as codeIcon } from '@wordpress/icons';

/** @typedef {import('@wordpress/rich-text/src/create.js').RichTextValue} RichTextValue */

const name = 'core/code';
const title = __( 'Inline code' );

Expand All @@ -17,33 +19,87 @@ export const code = {
title,
tagName: 'code',
className: null,

/**
* Scan backwards in the RichTextValue starting at the selection
* to find just-closed inline code regions, and format them.
*
* Example:
* Models ending in `-J3 were built last year.
* ^ started with the selection here.
* Models ending in `-J3` were built last year.
* ^ inserting the ` nudged forward the selection.
* Models ending in `-J3` were built last year.
* ^ we want to scan backwards to find this one.
* Models ending in `-J3` were built last year.
* ³²¹
* 1. This is `start` when entering this function.
* 2. We only consider a search if `start - 1` is a backtick.
* 3. Any matching backtick must be at `start - 2` or earlier.
* 4. Obviously no match can exist before `0`.
*
* Once we identify a now-closed inline code region we want to remove
* the backtick markers and apply the format to that inner region.
*
* @param {RichTextValue} value Value to adjust, with possible inline code regions surrounded by backticks.
* @return {RichTextValue} Unmodified value if no inline code region found, else new value with region formatted and without backticks.
*/
__unstableInputRule( value ) {
const BACKTICK = '`';
const { start, text } = value;
const characterBefore = text.slice( start - 1, start );

// Quick check the text for the necessary character.
if ( characterBefore !== BACKTICK ) {
if (
undefined === start ||
start - 2 < 0 ||
text[ start - 1 ] !== BACKTICK
) {
return value;
}

const textBefore = text.slice( 0, start - 1 );
const indexBefore = textBefore.lastIndexOf( BACKTICK );

if ( indexBefore === -1 ) {
const closerAt = start - 1;
const openerAt = text.lastIndexOf( BACKTICK, closerAt - 1 );
if ( openerAt === -1 ) {
return value;
}

const startIndex = indexBefore;
const endIndex = start - 2;

if ( startIndex === endIndex ) {
// Ignore double-backticks if we find them. Note that if we later
// find a third one, it will steal the closer we just passed.
//
// Example:
// Not inline code: ``.
// ^ ignore this because the opening backtick
// is the immediate predecessor to the closer
// Not inline code: ``. A third traps it.
// Not inline code: ``. A third `traps it.
// ^ upon entering this ` we close the span.
// Not inline code: `{. A third }traps it.
// ^^^^^^^^^^^^ and we've "stolen" the previous backtick,
// formatting this long span as inline code.
//
// To eliminate this we can perform a third check to see if the immediate
// predecessor to the opening backtick is also a backtick and then ignore it.
//
// This also happens over long spans, such as when we manually undo an inline
// code format that was auto-applied, but later start a new one. There's no easy
// way to determine if we should steal the earlier backtick and format the lengthy
// region automatically. We might consider scanning all the way to the beginning
// to determine if there are already a balanced set (even count) of backticks, or
// compare the candidate span's length to some heuristic threshold.
//
// Example:
// This `region` was manually entered as textual backticks. This steals the content.
// ^
// This `region` was manually entered as textual backticks. This `steals the content.
// ^ we've entered a new backtick
// This `region{ was manually entered as textual backticks. This }steals the content.
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ and have "stolen" the previous chunk.
if ( openerAt === closerAt - 1 ) {
return value;
}

value = remove( value, startIndex, startIndex + 1 );
value = remove( value, endIndex, endIndex + 1 );
value = applyFormat( value, { type: name }, startIndex, endIndex );
value = remove( value, openerAt, openerAt + 1 );
value = remove( value, closerAt - 1, closerAt );
value = applyFormat( value, { type: name }, openerAt, closerAt );

return value;
},
Expand Down

0 comments on commit f56a7c7

Please sign in to comment.