UNPKG

@wordpress/editor

Version:
8 lines (7 loc) 8.79 kB
{ "version": 3, "sources": ["../../../src/components/post-text-editor/utils.js"], "sourcesContent": ["import { diffChars } from 'diff/lib/diff/character.js';\nimport { diffLines } from 'diff/lib/diff/line.js';\n\n// Character diffing (Myers) is cheap when the edit distance is small but\n// degrades toward O(n^2) as the two strings diverge: a full replacement of\n// ~10k differing chars blocks the main thread for seconds. Cap the input handed\n// to diffChars so the worst case stays small (~100ms); larger windows fall back\n// to a line diff, with the cursor remapped by getCursorOffsetFromCommonText...\nconst STRING_TOO_LARGE_THRESHOLD = 1000;\n\n/**\n * Diffs two strings into a list of `{ value, added?, removed? }` change parts.\n *\n * @param {string} oldValue The previous string.\n * @param {string} newValue The next string.\n * @return {Array<Object>} The diff change parts.\n */\nexport function getDiff( oldValue, newValue ) {\n\tconst maxStringLength = Math.max( oldValue.length, newValue.length );\n\n\tif ( maxStringLength <= STRING_TOO_LARGE_THRESHOLD ) {\n\t\treturn diffChars( oldValue, newValue );\n\t}\n\n\tlet start = 0;\n\tconst minStringLength = Math.min( oldValue.length, newValue.length );\n\n\twhile (\n\t\tstart < minStringLength &&\n\t\toldValue[ start ] === newValue[ start ]\n\t) {\n\t\tstart++;\n\t}\n\n\tlet oldEnd = oldValue.length;\n\tlet newEnd = newValue.length;\n\n\twhile (\n\t\toldEnd > start &&\n\t\tnewEnd > start &&\n\t\toldValue[ oldEnd - 1 ] === newValue[ newEnd - 1 ]\n\t) {\n\t\toldEnd--;\n\t\tnewEnd--;\n\t}\n\n\tconst oldChangedValue = oldValue.slice( start, oldEnd );\n\tconst newChangedValue = newValue.slice( start, newEnd );\n\tconst maxChangedStringLength = Math.max(\n\t\toldChangedValue.length,\n\t\tnewChangedValue.length\n\t);\n\tlet changes;\n\n\tif ( maxChangedStringLength <= STRING_TOO_LARGE_THRESHOLD ) {\n\t\tchanges = diffChars( oldChangedValue, newChangedValue );\n\t} else {\n\t\tchanges = diffLines( oldChangedValue, newChangedValue );\n\t}\n\n\tif ( start > 0 ) {\n\t\tchanges.unshift( { value: oldValue.slice( 0, start ) } );\n\t}\n\n\tif ( oldEnd < oldValue.length ) {\n\t\tchanges.push( { value: oldValue.slice( oldEnd ) } );\n\t}\n\n\treturn changes;\n}\n\nfunction clampCursorPosition( position, value ) {\n\treturn Math.max( 0, Math.min( value.length, position ) );\n}\n\n// A coarse (line-level) diff reports a whole changed region as one replacement,\n// which would otherwise snap the cursor to the region's end. Recover a precise\n// position by finding the longest run of text ending at the cursor that is\n// preserved in the replacement, then placing the cursor just past where that\n// run starts in the new text. This uses indexOf (near-linear in V8) rather than\n// a character diff, so it stays cheap even across a large single-line region.\nfunction getCursorOffsetFromCommonTextBeforeCursor(\n\toldValue,\n\tnewValue,\n\tposition\n) {\n\tconst oldValueBeforeCursor = oldValue.slice( 0, position );\n\tlet low = 1;\n\tlet high = oldValueBeforeCursor.length;\n\tlet cursorOffset;\n\n\twhile ( low <= high ) {\n\t\tconst length = Math.floor( ( low + high ) / 2 );\n\t\tconst index = newValue.indexOf(\n\t\t\toldValueBeforeCursor.slice( oldValueBeforeCursor.length - length )\n\t\t);\n\n\t\tif ( index === -1 ) {\n\t\t\thigh = length - 1;\n\t\t} else {\n\t\t\tcursorOffset = index + length;\n\t\t\tlow = length + 1;\n\t\t}\n\t}\n\n\treturn cursorOffset;\n}\n\n/**\n * Maps a cursor position from the old string to the new string given a diff.\n *\n * @param {number} position The cursor position in the old string.\n * @param {Array<Object>} changes The diff returned by `getDiff`.\n * @param {string} oldValue The previous string.\n * @param {string} newValue The next string.\n * @return {number} The adjusted cursor position in the new string.\n */\nexport function adjustPosition( position, changes, oldValue, newValue ) {\n\tlet oldIndex = 0;\n\tlet newIndex = 0;\n\tlet adjustedPosition = position;\n\n\tfor ( let i = 0; i < changes.length; i++ ) {\n\t\tconst change = changes[ i ];\n\t\tif ( ! change.added && ! change.removed ) {\n\t\t\toldIndex += change.value.length;\n\t\t\tnewIndex += change.value.length;\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst oldStart = oldIndex;\n\t\tconst newStart = newIndex;\n\t\tlet oldLength = 0;\n\t\tlet newLength = 0;\n\n\t\twhile (\n\t\t\ti < changes.length &&\n\t\t\t( changes[ i ].added || changes[ i ].removed )\n\t\t) {\n\t\t\tif ( changes[ i ].removed ) {\n\t\t\t\toldLength += changes[ i ].value.length;\n\t\t\t} else {\n\t\t\t\tnewLength += changes[ i ].value.length;\n\t\t\t}\n\t\t\ti++;\n\t\t}\n\t\ti--;\n\n\t\tconst oldEnd = oldStart + oldLength;\n\t\tconst newEnd = newStart + newLength;\n\n\t\tif ( position < oldStart ) {\n\t\t\t// The cursor is before this change, so nothing here can move it.\n\t\t\tbreak;\n\t\t}\n\n\t\tif ( oldLength === 0 ) {\n\t\t\t// Pure insertion at or before the cursor: shift it right by the\n\t\t\t// inserted length.\n\t\t\tadjustedPosition += newLength;\n\t\t} else if ( position <= oldEnd ) {\n\t\t\t// The cursor falls inside a replaced region; recover its precise\n\t\t\t// spot from the text preserved around it.\n\t\t\tconst cursorOffset = getCursorOffsetFromCommonTextBeforeCursor(\n\t\t\t\toldValue.slice( oldStart, oldEnd ),\n\t\t\t\tnewValue.slice( newStart, newEnd ),\n\t\t\t\tposition - oldStart\n\t\t\t);\n\n\t\t\tadjustedPosition =\n\t\t\t\tcursorOffset === undefined ? newEnd : newStart + cursorOffset;\n\t\t\tbreak;\n\t\t} else {\n\t\t\t// The cursor is after this change: shift it by the net length delta.\n\t\t\tadjustedPosition += newLength - oldLength;\n\t\t}\n\n\t\toldIndex += oldLength;\n\t\tnewIndex += newLength;\n\t}\n\n\treturn clampCursorPosition( adjustedPosition, newValue );\n}\n\n/**\n * Diffs `oldValue` against `newValue` and maps a cursor `position` from the old\n * string to the new one.\n *\n * @param {number} position The cursor position in the old string.\n * @param {string} oldValue The previous string.\n * @param {string} newValue The next string.\n * @return {number} The adjusted cursor position in the new string.\n */\nexport function getAdjustedCursorPosition( position, oldValue, newValue ) {\n\treturn adjustPosition(\n\t\tposition,\n\t\tgetDiff( oldValue, newValue ),\n\t\toldValue,\n\t\tnewValue\n\t);\n}\n"], "mappings": "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", "names": [] }