From aefef2181db8a536d29345b8691ee185c1c3feda Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 8 Feb 2024 13:12:26 +0700 Subject: [PATCH] chore(web): further PR cleanup --- .../src/text/outputTarget.ts | 6 ++--- .../src/text/stringDivergence.ts | 21 ++++++++------- .../tests/node/transcriptions.js | 22 +++++++-------- common/web/utils/src/index.ts | 2 ++ common/web/utils/src/surrogates.ts | 27 +++++++++++++++++++ 5 files changed, 55 insertions(+), 23 deletions(-) create mode 100644 common/web/utils/src/surrogates.ts diff --git a/common/web/keyboard-processor/src/text/outputTarget.ts b/common/web/keyboard-processor/src/text/outputTarget.ts index 8399bbb834..ab9656b699 100644 --- a/common/web/keyboard-processor/src/text/outputTarget.ts +++ b/common/web/keyboard-processor/src/text/outputTarget.ts @@ -1,7 +1,7 @@ /// import { extendString } from "@keymanapp/web-utils"; -import { searchStringDivergence } from "./stringDivergence.js"; +import { findCommonSubstringEndIndex } from "./stringDivergence.js"; extendString(); @@ -124,14 +124,14 @@ export default abstract class OutputTarget { const toLeft = this.getTextBeforeCaret(); const fromLeft = original.getTextBeforeCaret(); - const leftDivergenceIndex = searchStringDivergence(fromLeft, toLeft, false); + const leftDivergenceIndex = findCommonSubstringEndIndex(fromLeft, toLeft, false); const deletedLeft = fromLeft.substring(leftDivergenceIndex)._kmwLength(); // No need for our specialized variant here. const insertedText = toLeft.substring(leftDivergenceIndex); const toRight = this.getTextAfterCaret(); const fromRight = original.getTextAfterCaret(); - const rightDivergenceIndex = searchStringDivergence(fromRight, toRight, true); + const rightDivergenceIndex = findCommonSubstringEndIndex(fromRight, toRight, true); // Right insertions aren't supported, but right deletions will matter in some scenarios. // In particular, once we allow right-deletion for pred-text suggestions applied with the diff --git a/common/web/keyboard-processor/src/text/stringDivergence.ts b/common/web/keyboard-processor/src/text/stringDivergence.ts index f2de629b73..79509d51a7 100644 --- a/common/web/keyboard-processor/src/text/stringDivergence.ts +++ b/common/web/keyboard-processor/src/text/stringDivergence.ts @@ -1,11 +1,15 @@ +// Future TODO: import from @keymanapp/common-types... once we no longer need to support ES5. +import { Uni_IsSurrogate1, Uni_IsSurrogate2 } from '@keymanapp/web-utils'; + /** - * Returns the index for the code point divergence point in code unit coordinates. + * Returns the index for the code point divergence point between two strings, as measured in code + * unit coordinates. * @param str1 * @param str2 * @param commonSuffix If false, asserts a common prefix to the strings. If true, asserts a common suffix. * @returns The code unit index within `str1` for the start of the code point not common to both. */ -export function searchStringDivergence(str1: string, str2: string, commonSuffix: boolean): number { +export function findCommonSubstringEndIndex(str1: string, str2: string, commonSuffix: boolean): number { /** * The maximum number of iterations to consider; exceeding this would go past a string boundary. */ @@ -67,16 +71,15 @@ export function searchStringDivergence(str1: string, str2: string, commonSuffix: const divergentChar1 = str1.charCodeAt(index); const divergentChar2 = str2.charCodeAt(index + offset); - const isHigh = (charCode: number) => charCode >= 0xD800 && charCode <= 0xDBFF; - const isLow = (charCode: number) => charCode >= 0xDC00 && charCode <= 0xDFFF; - const commonChecker = commonSuffix ? isLow : isHigh; - const divergentChecker = commonSuffix ? isHigh : isLow; + const commonSurrogateChecker = commonSuffix ? Uni_IsSurrogate2 : Uni_IsSurrogate1; + const divergentSurrogateChecker = commonSuffix ? Uni_IsSurrogate1 : Uni_IsSurrogate2; - // If the last common char qualifies as a direction-appropriate SMP surrogate... - if(commonChecker(commonPotentialSurrogate)) { + // If the last common character if of the direction-appropriate surrogate type (for + // comprising a potential split surrogate pair representing a non-BMP char)... + if(commonSurrogateChecker(commonPotentialSurrogate)) { // And one of the two divergent chars is a qualifying match - a surrogate // of the opposite type... - if(divergentChecker(divergentChar1) || divergentChecker(divergentChar2)) { + if(divergentSurrogateChecker(divergentChar1) || divergentSurrogateChecker(divergentChar2)) { // Our current index would split a surrogate pair; decrement the index to // preserve the pair. return index - inc; diff --git a/common/web/keyboard-processor/tests/node/transcriptions.js b/common/web/keyboard-processor/tests/node/transcriptions.js index 98c47a21f1..ef89ae1a07 100644 --- a/common/web/keyboard-processor/tests/node/transcriptions.js +++ b/common/web/keyboard-processor/tests/node/transcriptions.js @@ -7,11 +7,11 @@ extendString(); // Ensure KMW's string-extension functionality is available. String.kmwEnableSupplementaryPlane(false); -// A unicode-coding like alias for use in constructing SMP strings. +// A unicode-coding like alias for use in constructing non-BMP strings. const u = String.fromCodePoint; /** - * Returns the "Mathematical Sans-Serif Small" SMP encoding for + * Returns the "Mathematical Sans-Serif Small" non-BMP encoding for * a passed-in lowercase char between 'a' and 'z', inclusive. * @param {*} char * @returns @@ -39,7 +39,7 @@ describe("String divergence calculations", function() { assert.equal(result2, 0); }); - it("SMP text", () => { + it("non-BMP text", () => { const smp_ify = (str) => str.split('').map(ss).join(''); const result1 = searchStringDivergence( @@ -48,7 +48,7 @@ describe("String divergence calculations", function() { false ); - // 2 per SMP char; is in code-unit... units. + // 2 per non-BMP char; is in code-unit... units. // Will avoid splitting code points, though. assert.equal(result1, 8); @@ -61,7 +61,7 @@ describe("String divergence calculations", function() { assert.equal(result2, 8); }); - it("SMP edge cases", () => { + it("non-BMP edge cases", () => { const smp_ify = (str) => str.split('').map(ss).join(''); const result1 = searchStringDivergence( @@ -106,7 +106,7 @@ describe("String divergence calculations", function() { assert.equal(result2, "post-caret text".length-1); }) - it("SMP text", () => { + it("non-BMP text", () => { const smp_ify = (str) => str.split('').map(ss).join(''); // att|endance @@ -117,7 +117,7 @@ describe("String divergence calculations", function() { true ); - // 2 per SMP char; is in code-unit... units. + // 2 per non-BMP char; is in code-unit... units. // Will avoid splitting code points; is odd b/c we get the index of the LAST char of the pair. assert.equal(result1, 5); @@ -132,7 +132,7 @@ describe("String divergence calculations", function() { }); - it("SMP edge cases", () => { + it("non-BMP edge cases", () => { const smp_ify = (str) => str.split('').map(ss).join(''); // If the two are equal... @@ -155,7 +155,7 @@ describe("String divergence calculations", function() { }); describe("Transcriptions and Transforms", function() { - // Built in-line via function. Looks functionally equivalent to "apple", but with SMP characters. + // Built in-line via function. Looks functionally equivalent to "apple", but with non-BMP characters. let smpApple = u(0x1d5ba)+u(0x1d5c9)+u(0x1d5c9)+u(0x1d5c5)+u(0x1d5be); it("does not store an alias for related OutputTargets", function() { @@ -263,7 +263,7 @@ but not himself.`; // Sheev Palpatine, in the Star Wars prequels. assert.equal(transcription.transform.deleteRight, 1, "Incorrect count for right-of-caret deletions"); }); - it("handles deletions around the caret without text insertion (SMP text)", function() { + it("handles deletions around the caret without text insertion (non-BMP text)", function() { try { String.kmwEnableSupplementaryPlane(true); var target = new Mock(smpApple, 2); @@ -355,7 +355,7 @@ but not himself.`; // Sheev Palpatine, in the Star Wars prequels. assert.equal(transcription.transform.deleteRight, 3, "Incorrect count for right-of-caret deletions"); }); - it("handles deletions around the caret with text insertion (SMP text)", function() { + it("handles deletions around the caret with text insertion (non-BMP text)", function() { try { String.kmwEnableSupplementaryPlane(true); diff --git a/common/web/utils/src/index.ts b/common/web/utils/src/index.ts index fdd49e60f2..1d1e540a80 100644 --- a/common/web/utils/src/index.ts +++ b/common/web/utils/src/index.ts @@ -21,6 +21,8 @@ export { default as extendString } from "./kmwstring.js"; export { default as ManagedPromise } from "./managedPromise.js"; export { default as TimeoutPromise, timedPromise } from "./timeoutPromise.js"; +export { Uni_IsSurrogate1, Uni_IsSurrogate2 } from "./surrogates.js"; + // // Uncomment the following line and run the bundled output to verify successful // // esbuild bundling of this submodule: // console.log(Version.CURRENT.toString()); \ No newline at end of file diff --git a/common/web/utils/src/surrogates.ts b/common/web/utils/src/surrogates.ts new file mode 100644 index 0000000000..53c1a1e3fa --- /dev/null +++ b/common/web/utils/src/surrogates.ts @@ -0,0 +1,27 @@ +/* + * The definitions below are duplicated from common/web/types/util/util.ts; + * we can't downcompile the originals to ES5 when bundling with esbuild. + * `import type` stuff is fine, but not non-type `import` statements. + * + * TODO: Use those instead, once we're no longer building ES5 versions of Web. + */ + +export const Uni_LEAD_SURROGATE_START = 0xD800; +export const Uni_LEAD_SURROGATE_END = 0xDBFF; +export const Uni_TRAIL_SURROGATE_START = 0xDC00; +export const Uni_TRAIL_SURROGATE_END = 0xDFFF; + +/** + * @brief True if a lead surrogate + * \def Uni_IsSurrogate1 + */ +export function Uni_IsSurrogate1(ch : number) { + return ((ch) >= Uni_LEAD_SURROGATE_START && (ch) <= Uni_LEAD_SURROGATE_END); +} +/** + * @brief True if a trail surrogate + * \def Uni_IsSurrogate2 + */ +export function Uni_IsSurrogate2(ch : number) { + return ((ch) >= Uni_TRAIL_SURROGATE_START && (ch) <= Uni_TRAIL_SURROGATE_END); +}