From ca057d075448b0d06606b5b5e7a49dd53abc3a58 Mon Sep 17 00:00:00 2001 From: Alexander Zolotov Date: Tue, 11 Nov 2025 14:20:10 +0100 Subject: [PATCH] refactoring [fleet, codepoints]: get rid of the external library for handling unicode surrogates GitOrigin-RevId: d937ebff4de4b43ce4c8904acdca5d2bbc5b2370 --- ...e_cketti_unicode_kotlin_codepoints_jvm.xml | 18 ----- fleet/util/codepoints/BUILD.bazel | 11 ++- .../util/codepoints/fleet.util.codepoints.iml | 8 +- .../codepoints/gradlebuild/build.gradle.kts | 10 ++- fleet/util/codepoints/module-content.yaml | 5 +- .../fleet/codepoints/AppendableExtensions.kt | 12 +++ .../codepoints/CharSequenceExtensions.kt | 73 +++++++++++++++++++ .../fleet/codepoints/Codepoint.kt | 55 ++------------ .../codepoints/MultiplatformCodepoint.kt | 37 ++++++++++ .../codepoints/MultiplatformCodepoint.jvm.kt | 19 +++++ .../MultiplatformCodepoint.native.kt | 17 +++++ .../codepoints/MultiplatformCodepoint.wasm.kt | 17 +++++ lib/BUILD.bazel | 7 -- lib/MODULE.bazel | 14 ---- .../build/CommunityLibraryLicenses.kt | 3 - 15 files changed, 206 insertions(+), 100 deletions(-) delete mode 100644 .idea/libraries/de_cketti_unicode_kotlin_codepoints_jvm.xml create mode 100644 fleet/util/codepoints/srcCommonMain/fleet/codepoints/AppendableExtensions.kt create mode 100644 fleet/util/codepoints/srcCommonMain/fleet/codepoints/CharSequenceExtensions.kt create mode 100644 fleet/util/codepoints/srcCommonMain/fleet/codepoints/MultiplatformCodepoint.kt create mode 100644 fleet/util/codepoints/srcJvmMain/fleet/codepoints/MultiplatformCodepoint.jvm.kt create mode 100644 fleet/util/codepoints/srcNativeMain/fleet/codepoints/MultiplatformCodepoint.native.kt create mode 100644 fleet/util/codepoints/srcWasmJsMain/fleet/codepoints/MultiplatformCodepoint.wasm.kt diff --git a/.idea/libraries/de_cketti_unicode_kotlin_codepoints_jvm.xml b/.idea/libraries/de_cketti_unicode_kotlin_codepoints_jvm.xml deleted file mode 100644 index 3a34960f459a..000000000000 --- a/.idea/libraries/de_cketti_unicode_kotlin_codepoints_jvm.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - d642f145f6123739e779d4156145cc5fe7c2173bb58d437b813961a31c337ff9 - - - - - - - - - - - - \ No newline at end of file diff --git a/fleet/util/codepoints/BUILD.bazel b/fleet/util/codepoints/BUILD.bazel index 1db677424145..3c8b31714b26 100644 --- a/fleet/util/codepoints/BUILD.bazel +++ b/fleet/util/codepoints/BUILD.bazel @@ -1,6 +1,6 @@ ### auto-generated section `build fleet.util.codepoints` start load("//build:compiler-options.bzl", "create_kotlinc_options") -load("@rules_jvm//:jvm.bzl", "jvm_library", "resourcegroup") +load("@rules_jvm//:jvm.bzl", "jvm_library", "jvm_provided_library", "resourcegroup") create_kotlinc_options( name = "custom_codepoints", @@ -15,17 +15,22 @@ resourcegroup( strip_prefix = "resources" ) +jvm_provided_library( + name = "fleet_util_multiplatform_provided", + lib = "//fleet/util/multiplatform" +) + jvm_library( name = "codepoints", module_name = "fleet.util.codepoints", visibility = ["//visibility:public"], - srcs = glob(["srcCommonMain/**/*.kt", "srcCommonMain/**/*.java", "srcCommonMain/**/*.form"], allow_empty = True, exclude = ["**/module-info.java"]), + srcs = glob(["srcCommonMain/**/*.kt", "srcCommonMain/**/*.java", "srcCommonMain/**/*.form", "srcJvmMain/**/*.kt", "srcJvmMain/**/*.java", "srcJvmMain/**/*.form"], allow_empty = True, exclude = ["**/module-info.java"]), resources = [":codepoints_resources"], kotlinc_opts = ":custom_codepoints", deps = [ "@lib//:kotlin-stdlib", "//fleet/util/core", - "@lib//:de-cketti-unicode-kotlin-codepoints-jvm", + ":fleet_util_multiplatform_provided", ] ) ### auto-generated section `build fleet.util.codepoints` end \ No newline at end of file diff --git a/fleet/util/codepoints/fleet.util.codepoints.iml b/fleet/util/codepoints/fleet.util.codepoints.iml index d520303e21f6..98da95895082 100644 --- a/fleet/util/codepoints/fleet.util.codepoints.iml +++ b/fleet/util/codepoints/fleet.util.codepoints.iml @@ -12,6 +12,11 @@ + + + $MAVEN_REPOSITORY$/jetbrains/fleet/expects-compiler-plugin/2.2.21-RC2-0.1/expects-compiler-plugin-2.2.21-RC2-0.1.jar + + @@ -21,11 +26,12 @@ + - + \ No newline at end of file diff --git a/fleet/util/codepoints/gradlebuild/build.gradle.kts b/fleet/util/codepoints/gradlebuild/build.gradle.kts index 9b3540e29ec2..76d661da9335 100644 --- a/fleet/util/codepoints/gradlebuild/build.gradle.kts +++ b/fleet/util/codepoints/gradlebuild/build.gradle.kts @@ -12,6 +12,7 @@ plugins { alias(libs.plugins.dokka) // GRADLE_PLUGINS__MARKER_START id("fleet-module") + alias(jps.plugins.expects) // GRADLE_PLUGINS__MARKER_END } @@ -57,10 +58,13 @@ kotlin { implementation(jps.org.jetbrains.kotlin.kotlin.stdlib1993400674.get().let { "${it.group}:${it.name}:${it.version}" }) { exclude(group = "org.jetbrains", module = "annotations") } - implementation(jps.de.cketti.unicode.kotlin.codepoints.jvm1960123061.get().let { "${it.group}:kotlin-codepoints:${it.version}" }) { - isTransitive = false - } implementation(project(":fleet.util.core")) } + sourceSets.jvmMain.dependencies { + compileOnly(project(":fleet.util.multiplatform")) + } + sourceSets.wasmJsMain.dependencies { + implementation(project(":fleet.util.multiplatform")) + } // KOTLIN__MARKER_END } \ No newline at end of file diff --git a/fleet/util/codepoints/module-content.yaml b/fleet/util/codepoints/module-content.yaml index d87532ab08ab..29fa9f6b6d49 100644 --- a/fleet/util/codepoints/module-content.yaml +++ b/fleet/util/codepoints/module-content.yaml @@ -1,6 +1,3 @@ - name: dist.all/lib/fleet.util.codepoints.jar modules: - - name: fleet.util.codepoints - library: de.cketti.unicode.kotlin.codepoints.jvm - files: - - name: $MAVEN_REPOSITORY$/de/cketti/unicode/kotlin-codepoints-jvm/0/kotlin-codepoints-jvm-0.jar \ No newline at end of file + - name: fleet.util.codepoints \ No newline at end of file diff --git a/fleet/util/codepoints/srcCommonMain/fleet/codepoints/AppendableExtensions.kt b/fleet/util/codepoints/srcCommonMain/fleet/codepoints/AppendableExtensions.kt new file mode 100644 index 000000000000..4d3a22ae3d94 --- /dev/null +++ b/fleet/util/codepoints/srcCommonMain/fleet/codepoints/AppendableExtensions.kt @@ -0,0 +1,12 @@ +package fleet.codepoints + +fun T.appendCodePoint(codepoint: Codepoint): T { + if (codepoint.isBmpCodePoint()) { + append(codepoint.codepoint.toChar()) + } + else { + append(highSurrogatePlatformSpecific(codepoint.codepoint)) + append(lowSurrogatePlatformSpecific(codepoint.codepoint)) + } + return this +} diff --git a/fleet/util/codepoints/srcCommonMain/fleet/codepoints/CharSequenceExtensions.kt b/fleet/util/codepoints/srcCommonMain/fleet/codepoints/CharSequenceExtensions.kt new file mode 100644 index 000000000000..baaf02adcef6 --- /dev/null +++ b/fleet/util/codepoints/srcCommonMain/fleet/codepoints/CharSequenceExtensions.kt @@ -0,0 +1,73 @@ +package fleet.codepoints + +fun CharSequence.codePointAt(index: Int): Codepoint { + if (index !in indices) throw IndexOutOfBoundsException("Index out of range: $index, size: $length") + + val firstChar = this[index] + if (firstChar.isHighSurrogate() && index + 1 < length) { + val secondChar = this[index + 1] + if (secondChar.isLowSurrogate()) { + return Codepoint.fromChars(firstChar, secondChar) + } + } + return Codepoint(firstChar.code) +} + +fun CharSequence.codePointBefore(index: Int): Codepoint { + val startIndex = index - 1 + if (startIndex < 0 || index !in indices) throw IndexOutOfBoundsException("Index out of range: $index, size: $length") + + val secondChar = this[startIndex] + if (secondChar.isLowSurrogate() && startIndex - 1 >= 0) { + val firstChar = this[startIndex - 1] + if (firstChar.isHighSurrogate()) { + return Codepoint.fromChars(firstChar, secondChar) + } + } + + return Codepoint(secondChar.code) +} + +fun CharSequence.codepoints(offset: Int, direction: Direction = Direction.FORWARD): Iterator = + when (direction) { + Direction.FORWARD -> iterator { + var i = offset + val len = length + while (i < len) { + val c1 = get(i++) + if (c1.isHighSurrogate()) { + if (i < len) { + val c2 = get(i++) + if (c2.isLowSurrogate()) { + yield(Codepoint.fromChars(c1, c2)) + } + } + } + else { + yield(Codepoint(c1.code)) + } + } + } + Direction.BACKWARD -> iterator { + var i = offset - 1 + while (i >= 0) { + val c2 = get(i--) + if (c2.isLowSurrogate()) { + if (i >= 0) { + val c1 = get(i--) + if (c1.isHighSurrogate()) { + yield(Codepoint.fromChars(c1, c2)) + } + } + } + else { + yield(Codepoint(c2.code)) + } + } + } + } + +enum class Direction { + FORWARD, + BACKWARD, +} \ No newline at end of file diff --git a/fleet/util/codepoints/srcCommonMain/fleet/codepoints/Codepoint.kt b/fleet/util/codepoints/srcCommonMain/fleet/codepoints/Codepoint.kt index adeee5eb3878..2900e5de88a2 100644 --- a/fleet/util/codepoints/srcCommonMain/fleet/codepoints/Codepoint.kt +++ b/fleet/util/codepoints/srcCommonMain/fleet/codepoints/Codepoint.kt @@ -1,14 +1,19 @@ // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package fleet.codepoints -import de.cketti.codepoints.CodePoints import kotlin.jvm.JvmInline @JvmInline value class Codepoint(val codepoint: Int) { - val charCount: Int get() = CodePoints.charCount(codepoint) + val charCount: Int + get() = if (codepoint < MIN_SUPPLEMENTARY_CODE_POINT) 1 else 2 + + fun asString(): String = toString(codepoint) + + internal fun isBmpCodePoint(): Boolean = codepoint ushr 16 == 0 companion object { + fun fromChars(highSurrogate: Char, lowSurrogate: Char ): Codepoint = codepointOfPlatformSpecific(highSurrogate, lowSurrogate) fun isUnicodeIdentifierStart(codepoint: Int): Boolean = isCodepointInRanges(codepoint, unicodeIdStartRanges) fun isUnicodeIdentifierPart(codepoint: Int): Boolean = isCodepointInRanges(codepoint, unicodeIdContinueRanges) fun isIdentifierIgnorable(codepoint: Int): Boolean = isCodepointInRanges(codepoint, identifierIgnorableRanges) @@ -39,51 +44,7 @@ value class Codepoint(val codepoint: Int) { } fun toString(codepoint: Int): String { - return CodePoints.toString(codepoint) + return codePointsToStringPlatformSpecific(codepoint) } } } - -fun CharSequence.codepoints(offset: Int, direction: Direction = Direction.FORWARD): Iterator = - when (direction) { - Direction.FORWARD -> iterator { - var i = offset - val len = length - while (i < len) { - val c1 = get(i++) - if (c1.isHighSurrogate()) { - if (i < len) { - val c2 = get(i++) - if (c2.isLowSurrogate()) { - yield(Codepoint(CodePoints.toCodePoint(c1, c2))) - } - } - } - else { - yield(Codepoint(c1.code)) - } - } - } - Direction.BACKWARD -> iterator { - var i = offset - 1 - while (i >= 0) { - val c2 = get(i--) - if (c2.isLowSurrogate()) { - if (i >= 0) { - val c1 = get(i--) - if (c1.isHighSurrogate()) { - yield(Codepoint(CodePoints.toCodePoint(c1, c2))) - } - } - } - else { - yield(Codepoint(c2.code)) - } - } - } - } - -enum class Direction { - FORWARD, - BACKWARD, -} \ No newline at end of file diff --git a/fleet/util/codepoints/srcCommonMain/fleet/codepoints/MultiplatformCodepoint.kt b/fleet/util/codepoints/srcCommonMain/fleet/codepoints/MultiplatformCodepoint.kt new file mode 100644 index 000000000000..277abae26d83 --- /dev/null +++ b/fleet/util/codepoints/srcCommonMain/fleet/codepoints/MultiplatformCodepoint.kt @@ -0,0 +1,37 @@ +package fleet.codepoints + +import fleet.util.multiplatform.linkToActual + +internal const val MIN_SUPPLEMENTARY_CODE_POINT = 0x10000 +private const val MIN_HIGH_SURROGATE = 0xD800 +private const val MIN_LOW_SURROGATE = 0xDC00 +private const val SURROGATE_DECODE_OFFSET = MIN_SUPPLEMENTARY_CODE_POINT - (MIN_HIGH_SURROGATE shl 10) - MIN_LOW_SURROGATE +private const val HIGH_SURROGATE_ENCODE_OFFSET = (MIN_HIGH_SURROGATE - (MIN_SUPPLEMENTARY_CODE_POINT ushr 10)) + +internal fun codePointsToStringPlatformSpecific(vararg codepoints: Int): String = linkToActual() + +internal fun codePointsToStringMultiplatform(vararg codepoints: Int): String { + return buildString(capacity = codepoints.size * 2) { + for (codePoint in codepoints) { + appendCodePoint(Codepoint(codePoint)) + } + } +} + +internal fun codepointOfPlatformSpecific(highSurrogate: Char, lowSurrogate: Char): Codepoint = linkToActual() + +internal fun codepointOfMultiplatform(highSurrogate: Char, lowSurrogate: Char): Codepoint { + return Codepoint((highSurrogate.code shl 10) + lowSurrogate.code + SURROGATE_DECODE_OFFSET) +} + +internal fun highSurrogatePlatformSpecific(codepoint: Int): Char = linkToActual() + +internal fun highSurrogateMultiplatform(codepoint: Int): Char { + return ((codepoint ushr 10) + HIGH_SURROGATE_ENCODE_OFFSET).toChar() +} + +internal fun lowSurrogatePlatformSpecific(codepoint: Int): Char = linkToActual() + +internal fun lowSurrogateMultiplatform(codepoint: Int): Char { + return ((codepoint and 0x3FF) + MIN_LOW_SURROGATE).toChar() +} \ No newline at end of file diff --git a/fleet/util/codepoints/srcJvmMain/fleet/codepoints/MultiplatformCodepoint.jvm.kt b/fleet/util/codepoints/srcJvmMain/fleet/codepoints/MultiplatformCodepoint.jvm.kt new file mode 100644 index 000000000000..aefce7846c5a --- /dev/null +++ b/fleet/util/codepoints/srcJvmMain/fleet/codepoints/MultiplatformCodepoint.jvm.kt @@ -0,0 +1,19 @@ +package fleet.codepoints + +import fleet.util.multiplatform.Actual + +@Actual +internal fun codePointsToStringPlatformSpecificJvm(vararg codepoints: Int): String { + return String(codepoints, 0, codepoints.size) +} + +@Actual +internal fun codepointOfPlatformSpecificJvm(highSurrogate: Char, lowSurrogate: Char): Codepoint { + return Codepoint(Character.toCodePoint(highSurrogate, lowSurrogate)) +} + +@Actual +internal fun highSurrogatePlatformSpecificJvm(codepoint: Int): Char = Character.highSurrogate(codepoint) + +@Actual +internal fun lowSurrogatePlatformSpecificJvm(codepoint: Int): Char = Character.lowSurrogate(codepoint) \ No newline at end of file diff --git a/fleet/util/codepoints/srcNativeMain/fleet/codepoints/MultiplatformCodepoint.native.kt b/fleet/util/codepoints/srcNativeMain/fleet/codepoints/MultiplatformCodepoint.native.kt new file mode 100644 index 000000000000..84f8eb6804ee --- /dev/null +++ b/fleet/util/codepoints/srcNativeMain/fleet/codepoints/MultiplatformCodepoint.native.kt @@ -0,0 +1,17 @@ +package fleet.codepoints + +@fleet.util.multiplatform.Actual +internal fun codePointsToStringPlatformSpecificNative(vararg codepoints: Int): String { + return codePointsToStringMultiplatform(*codepoints) +} + +@fleet.util.multiplatform.Actual +internal fun codepointOfPlatformSpecificNative(highSurrogate: Char, lowSurrogate: Char): Codepoint { + return codepointOfMultiplatform(highSurrogate, lowSurrogate) +} + +@fleet.util.multiplatform.Actual +internal fun highSurrogatePlatformSpecificNative(codepoint: Int): Char = highSurrogateMultiplatform(codepoint) + +@fleet.util.multiplatform.Actual +internal fun lowSurrogatePlatformSpecificNative(codepoint: Int): Char = lowSurrogateMultiplatform(codepoint \ No newline at end of file diff --git a/fleet/util/codepoints/srcWasmJsMain/fleet/codepoints/MultiplatformCodepoint.wasm.kt b/fleet/util/codepoints/srcWasmJsMain/fleet/codepoints/MultiplatformCodepoint.wasm.kt new file mode 100644 index 000000000000..b5bad79592f1 --- /dev/null +++ b/fleet/util/codepoints/srcWasmJsMain/fleet/codepoints/MultiplatformCodepoint.wasm.kt @@ -0,0 +1,17 @@ +package fleet.codepoints + +@fleet.util.multiplatform.Actual +internal fun codePointsToStringPlatformSpecificWasmJs(vararg codepoints: Int): String { + return codePointsToStringMultiplatform(*codepoints) +} + +@fleet.util.multiplatform.Actual +internal fun codepointOfPlatformSpecificWasmJs(highSurrogate: Char, lowSurrogate: Char): Codepoint { + return codepointOfMultiplatform(highSurrogate, lowSurrogate) +} + +@fleet.util.multiplatform.Actual +internal fun highSurrogatePlatformSpecificWasmJs(codepoint: Int): Char = highSurrogateMultiplatform(codepoint) + +@fleet.util.multiplatform.Actual +internal fun lowSurrogatePlatformSpecificWasmJs(codepoint: Int): Char = lowSurrogateMultiplatform(codepoint) \ No newline at end of file diff --git a/lib/BUILD.bazel b/lib/BUILD.bazel index d1ff90c4408b..e5acd200c978 100644 --- a/lib/BUILD.bazel +++ b/lib/BUILD.bazel @@ -1074,13 +1074,6 @@ jvm_import( visibility = ["//visibility:public"] ) -jvm_import( - name = "de-cketti-unicode-kotlin-codepoints-jvm", - jar = "@de_cketti_unicode-kotlin-codepoints-jvm-0_9_0_http//file", - source_jar = "@de_cketti_unicode-kotlin-codepoints-jvm-0_9_0-sources_http//file", - visibility = ["//visibility:public"] -) - jvm_import( name = "download-pgp-verifier", jar = "@com_jetbrains_infra-download-pgp-verifier-1_1_4_http//file", diff --git a/lib/MODULE.bazel b/lib/MODULE.bazel index ba54ea98f202..6c6628f1cd99 100644 --- a/lib/MODULE.bazel +++ b/lib/MODULE.bazel @@ -1322,20 +1322,6 @@ http_file( downloaded_file_path = "cucumber-jvm-deps-1.0.5-sources.jar" ) -http_file( - name = "de_cketti_unicode-kotlin-codepoints-jvm-0_9_0_http", - url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/de/cketti/unicode/kotlin-codepoints-jvm/0.9.0/kotlin-codepoints-jvm-0.9.0.jar", - sha256 = "d642f145f6123739e779d4156145cc5fe7c2173bb58d437b813961a31c337ff9", - downloaded_file_path = "kotlin-codepoints-jvm-0.9.0.jar" -) - -http_file( - name = "de_cketti_unicode-kotlin-codepoints-jvm-0_9_0-sources_http", - url = "https://cache-redirector.jetbrains.com/repo1.maven.org/maven2/de/cketti/unicode/kotlin-codepoints-jvm/0.9.0/kotlin-codepoints-jvm-0.9.0-sources.jar", - sha256 = "713ed1a4bf439eda3a304bce8a713380fc60221dbd34c49bcda0999f4cdf6937", - downloaded_file_path = "kotlin-codepoints-jvm-0.9.0-sources.jar" -) - http_file( name = "com_jetbrains_infra-download-pgp-verifier-1_1_4_http", url = "https://cache-redirector.jetbrains.com/packages.jetbrains.team/maven/p/ij/intellij-dependencies/com/jetbrains/infra/download-pgp-verifier/1.1.4/download-pgp-verifier-1.1.4.jar", diff --git a/platform/build-scripts/src/org/jetbrains/intellij/build/CommunityLibraryLicenses.kt b/platform/build-scripts/src/org/jetbrains/intellij/build/CommunityLibraryLicenses.kt index 64abd198b3d1..b61d61e8a614 100644 --- a/platform/build-scripts/src/org/jetbrains/intellij/build/CommunityLibraryLicenses.kt +++ b/platform/build-scripts/src/org/jetbrains/intellij/build/CommunityLibraryLicenses.kt @@ -792,9 +792,6 @@ object CommunityLibraryLicenses { .apache("https://github.com/JetBrains/kotlin/blob/master/license/LICENSE.txt") .suppliedByOrganizations(Suppliers.JETBRAINS), - LibraryLicense("kotlin-codepoints", libraryName = "de.cketti.unicode.kotlin.codepoints.jvm", url = "https://github.com/cketti/kotlin-codepoints") - .mit("https://github.com/cketti/kotlin-codepoints/blob/main/LICENSE"), - LibraryLicense("kotlin-metadata", libraryName = "kotlin-metadata", url = "https://github.com/JetBrains/kotlin") .apache("https://github.com/JetBrains/kotlin/blob/master/license/LICENSE.txt") .suppliedByOrganizations(Suppliers.JETBRAINS),