mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-16 14:23:28 +07:00
PartiallyKnownString decoupled for UAST and moved to *lang-api*, *microservices* no more depends on UAST
GitOrigin-RevId: 4175fab68bed81acac8f84940450c15ff9a8a059
This commit is contained in:
committed by
intellij-monorepo-bot
parent
3b608e17af
commit
4aff9c7c9e
@@ -0,0 +1,179 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.psi.util
|
||||
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.ElementManipulators
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiLanguageInjectionHost
|
||||
import com.intellij.util.SmartList
|
||||
import com.intellij.util.containers.toHeadAndTail
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
@ApiStatus.Experimental
|
||||
sealed class StringEntry {
|
||||
abstract val sourcePsi: PsiElement? // maybe it should be PsiLanguageInjectionHost and only for `Known` values
|
||||
abstract val range: TextRange
|
||||
|
||||
class Known(val value: String, override val sourcePsi: PsiElement?, override val range: TextRange) : StringEntry()
|
||||
class Unknown(override val sourcePsi: PsiElement?, override val range: TextRange) : StringEntry()
|
||||
|
||||
val rangeAlignedToHost: TextRange?
|
||||
get() {
|
||||
val entry = this
|
||||
val sourcePsi = entry.sourcePsi ?: return null
|
||||
if (sourcePsi is PsiLanguageInjectionHost) return entry.range
|
||||
if (sourcePsi.parent is PsiLanguageInjectionHost) { // Kotlin interpolated string, TODO: encapsulate this logic to range retrieval
|
||||
return entry.range.shiftRight(sourcePsi.startOffsetInParent - ElementManipulators.getValueTextRange(sourcePsi.parent).startOffset)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@ApiStatus.Experimental
|
||||
class PartiallyKnownString(val segments: List<StringEntry>) {
|
||||
|
||||
val valueIfKnown: String?
|
||||
get() {
|
||||
(segments.singleOrNull() as? StringEntry.Known)?.let { return it.value }
|
||||
|
||||
val stringBuffer = StringBuffer()
|
||||
for (segment in segments) {
|
||||
when (segment) {
|
||||
is StringEntry.Known -> stringBuffer.append(segment.value)
|
||||
is StringEntry.Unknown -> return null
|
||||
}
|
||||
}
|
||||
return stringBuffer.toString()
|
||||
}
|
||||
|
||||
override fun toString(): String = segments.joinToString { segment ->
|
||||
when (segment) {
|
||||
is StringEntry.Known -> segment.value
|
||||
is StringEntry.Unknown -> "<???>"
|
||||
}
|
||||
}
|
||||
|
||||
constructor(single: StringEntry) : this(listOf(single))
|
||||
|
||||
constructor(string: String, sourcePsi: PsiElement?, textRange: TextRange) : this(
|
||||
StringEntry.Known(string, sourcePsi, textRange))
|
||||
|
||||
fun findIndexOfInKnown(pattern: String): Int {
|
||||
var accumulated = 0
|
||||
for (segment in segments) {
|
||||
when (segment) {
|
||||
is StringEntry.Known -> {
|
||||
val i = segment.value.indexOf(pattern)
|
||||
if (i >= 0) return accumulated + i
|
||||
accumulated += segment.value.length
|
||||
}
|
||||
is StringEntry.Unknown -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
fun splitAtInKnown(splitAt: Int): Pair<PartiallyKnownString, PartiallyKnownString> {
|
||||
var accumulated = 0
|
||||
val left = SmartList<StringEntry>()
|
||||
for ((i, segment) in segments.withIndex()) {
|
||||
when (segment) {
|
||||
is StringEntry.Known -> {
|
||||
if (accumulated + segment.value.length < splitAt) {
|
||||
accumulated += segment.value.length
|
||||
left.add(segment)
|
||||
}
|
||||
else {
|
||||
val leftPart = segment.value.substring(0, splitAt - accumulated)
|
||||
val rightPart = segment.value.substring(splitAt - accumulated)
|
||||
left.add(StringEntry.Known(leftPart, segment.sourcePsi, /* TODO: should also be splitted */
|
||||
segment.range))
|
||||
|
||||
return PartiallyKnownString(left) to PartiallyKnownString(
|
||||
ArrayList<StringEntry>(segments.lastIndex - i + 1).apply {
|
||||
if (rightPart.isNotEmpty())
|
||||
add(StringEntry.Known(rightPart, segment.sourcePsi, /* TODO: should also be splitted */
|
||||
segment.range))
|
||||
addAll(segments.subList(i, segments.size))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
is StringEntry.Unknown -> {
|
||||
left.add(segment)
|
||||
}
|
||||
}
|
||||
}
|
||||
return this to empty
|
||||
}
|
||||
|
||||
fun split(pattern: String): List<PartiallyKnownString> {
|
||||
|
||||
tailrec fun collectPaths(result: MutableList<PartiallyKnownString>,
|
||||
pending: MutableList<StringEntry>,
|
||||
segments: List<StringEntry>): MutableList<PartiallyKnownString> {
|
||||
|
||||
val (head, tail) = segments.toHeadAndTail() ?: return result.apply {
|
||||
add(
|
||||
PartiallyKnownString(pending))
|
||||
}
|
||||
|
||||
when (head) {
|
||||
is StringEntry.Unknown -> return collectPaths(result, pending.apply { add(head) }, tail)
|
||||
is StringEntry.Known -> {
|
||||
val value = head.value
|
||||
|
||||
val stringPaths = splitToTextRanges(value, pattern).toList()
|
||||
if (stringPaths.size == 1) {
|
||||
return collectPaths(result, pending.apply { add(head) }, tail)
|
||||
}
|
||||
else {
|
||||
return collectPaths(
|
||||
result.apply {
|
||||
add(PartiallyKnownString(
|
||||
pending.apply {
|
||||
add(StringEntry.Known(stringPaths.first().substring(value), head.sourcePsi,
|
||||
stringPaths.first()))
|
||||
}))
|
||||
addAll(stringPaths.subList(1, stringPaths.size - 1).map {
|
||||
PartiallyKnownString(it.substring(value), head.sourcePsi, it)
|
||||
})
|
||||
},
|
||||
mutableListOf(StringEntry.Known(stringPaths.last().substring(value), head.sourcePsi,
|
||||
stringPaths.last())),
|
||||
tail
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return collectPaths(SmartList(), mutableListOf(), segments)
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
val empty = PartiallyKnownString(emptyList())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ApiStatus.Experimental
|
||||
fun splitToTextRanges(charSequence: CharSequence, pattern: String): Sequence<TextRange> {
|
||||
var lastMatch = 0
|
||||
return sequence {
|
||||
while (true) {
|
||||
val start = charSequence.indexOf(pattern, lastMatch)
|
||||
if (start == -1) {
|
||||
yield(TextRange(lastMatch, charSequence.length))
|
||||
return@sequence
|
||||
}
|
||||
yield(TextRange(lastMatch, start))
|
||||
lastMatch = start + pattern.length
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,5 +11,6 @@
|
||||
<orderEntry type="module" module-name="intellij.java.psi" exported="" />
|
||||
<orderEntry type="module" module-name="intellij.java.psi.impl" />
|
||||
<orderEntry type="module" module-name="intellij.platform.util.ex" />
|
||||
<orderEntry type="module" module-name="intellij.platform.lang" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -5,8 +5,8 @@ import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.psi.ElementManipulators
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.psi.PsiLanguageInjectionHost
|
||||
import com.intellij.util.SmartList
|
||||
import com.intellij.util.containers.toHeadAndTail
|
||||
import com.intellij.psi.util.PartiallyKnownString
|
||||
import com.intellij.psi.util.StringEntry
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.uast.*
|
||||
|
||||
@@ -82,8 +82,8 @@ class UStringConcatenationsFacade @ApiStatus.Experimental constructor(uContext:
|
||||
@ApiStatus.Experimental
|
||||
fun asPartiallyKnownString() = PartiallyKnownString(segments.map { segment ->
|
||||
segment.value?.let { value ->
|
||||
StringEntry.Known(value, segment.uExpression, getSegmentInnerTextRange(segment))
|
||||
} ?: StringEntry.Unknown(segment.uExpression, getSegmentInnerTextRange(segment))
|
||||
StringEntry.Known(value, segment.uExpression.sourcePsi, getSegmentInnerTextRange(segment))
|
||||
} ?: StringEntry.Unknown(segment.uExpression.sourcePsi, getSegmentInnerTextRange(segment))
|
||||
})
|
||||
|
||||
private fun getSegmentInnerTextRange(segment: Segment): TextRange {
|
||||
@@ -108,160 +108,3 @@ class UStringConcatenationsFacade @ApiStatus.Experimental constructor(uContext:
|
||||
|
||||
}
|
||||
|
||||
@ApiStatus.Experimental
|
||||
sealed class StringEntry {
|
||||
abstract val uExpression: UExpression
|
||||
abstract val range: TextRange
|
||||
|
||||
class Known(val value: String, override val uExpression: UExpression, override val range: TextRange) : StringEntry()
|
||||
class Unknown(override val uExpression: UExpression, override val range: TextRange) : StringEntry()
|
||||
|
||||
val rangeAlignedToHost: TextRange?
|
||||
get() {
|
||||
val entry = this
|
||||
val sourcePsi = entry.uExpression.sourcePsi ?: return null
|
||||
if (sourcePsi is PsiLanguageInjectionHost) return entry.range
|
||||
if (sourcePsi.parent is PsiLanguageInjectionHost) { // Kotlin interpolated string, TODO: encapsulate this logic to range retrieval
|
||||
return entry.range.shiftRight(sourcePsi.startOffsetInParent - ElementManipulators.getValueTextRange(sourcePsi.parent).startOffset)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
@ApiStatus.Experimental
|
||||
class PartiallyKnownString(val segments: List<StringEntry>) {
|
||||
|
||||
val valueIfKnown: String?
|
||||
get() {
|
||||
(segments.singleOrNull() as? StringEntry.Known)?.let { return it.value }
|
||||
|
||||
val stringBuffer = StringBuffer()
|
||||
for (segment in segments) {
|
||||
when (segment) {
|
||||
is StringEntry.Known -> stringBuffer.append(segment.value)
|
||||
is StringEntry.Unknown -> return null
|
||||
}
|
||||
}
|
||||
return stringBuffer.toString()
|
||||
}
|
||||
|
||||
override fun toString(): String = segments.joinToString { segment ->
|
||||
when (segment) {
|
||||
is StringEntry.Known -> segment.value
|
||||
is StringEntry.Unknown -> "<???>"
|
||||
}
|
||||
}
|
||||
|
||||
constructor(single: StringEntry) : this(listOf(single))
|
||||
|
||||
constructor(string: String, uExpression: UExpression, textRange: TextRange) : this(StringEntry.Known(string, uExpression, textRange))
|
||||
|
||||
fun findIndexOfInKnown(pattern: String): Int {
|
||||
var accumulated = 0
|
||||
for (segment in segments) {
|
||||
when (segment) {
|
||||
is StringEntry.Known -> {
|
||||
val i = segment.value.indexOf(pattern)
|
||||
if (i >= 0) return accumulated + i
|
||||
accumulated += segment.value.length
|
||||
}
|
||||
is StringEntry.Unknown -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
fun splitAtInKnown(splitAt: Int): Pair<PartiallyKnownString, PartiallyKnownString> {
|
||||
var accumulated = 0
|
||||
val left = SmartList<StringEntry>()
|
||||
for ((i, segment) in segments.withIndex()) {
|
||||
when (segment) {
|
||||
is StringEntry.Known -> {
|
||||
if (accumulated + segment.value.length < splitAt) {
|
||||
accumulated += segment.value.length
|
||||
left.add(segment)
|
||||
}
|
||||
else {
|
||||
val leftPart = segment.value.substring(0, splitAt - accumulated)
|
||||
val rightPart = segment.value.substring(splitAt - accumulated)
|
||||
left.add(StringEntry.Known(leftPart, segment.uExpression, /* TODO: should also be splitted */ segment.range))
|
||||
|
||||
return PartiallyKnownString(left) to PartiallyKnownString(
|
||||
ArrayList<StringEntry>(segments.lastIndex - i + 1).apply {
|
||||
if (rightPart.isNotEmpty())
|
||||
add(StringEntry.Known(rightPart, segment.uExpression, /* TODO: should also be splitted */ segment.range))
|
||||
addAll(segments.subList(i, segments.size))
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
is StringEntry.Unknown -> {
|
||||
left.add(segment)
|
||||
}
|
||||
}
|
||||
}
|
||||
return this to PartiallyKnownString.empty
|
||||
}
|
||||
|
||||
fun split(pattern: String): List<PartiallyKnownString> {
|
||||
|
||||
tailrec fun collectPaths(result: MutableList<PartiallyKnownString>,
|
||||
pending: MutableList<StringEntry>,
|
||||
segments: List<StringEntry>): MutableList<PartiallyKnownString> {
|
||||
|
||||
val (head, tail) = segments.toHeadAndTail() ?: return result.apply { add(PartiallyKnownString(pending)) }
|
||||
|
||||
when (head) {
|
||||
is StringEntry.Unknown -> return collectPaths(result, pending.apply { add(head) }, tail)
|
||||
is StringEntry.Known -> {
|
||||
val value = head.value
|
||||
|
||||
val stringPaths = splitToTextRanges(value, pattern).toList()
|
||||
if (stringPaths.size == 1) {
|
||||
return collectPaths(result, pending.apply { add(head) }, tail)
|
||||
}
|
||||
else {
|
||||
return collectPaths(
|
||||
result.apply {
|
||||
add(PartiallyKnownString(
|
||||
pending.apply { add(StringEntry.Known(stringPaths.first().substring(value), head.uExpression, stringPaths.first())) }))
|
||||
addAll(stringPaths.subList(1, stringPaths.size - 1).map { PartiallyKnownString(it.substring(value), head.uExpression, it) })
|
||||
},
|
||||
mutableListOf(StringEntry.Known(stringPaths.last().substring(value), head.uExpression, stringPaths.last())),
|
||||
tail
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return collectPaths(SmartList(), mutableListOf(), segments)
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
val empty = PartiallyKnownString(emptyList())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ApiStatus.Experimental
|
||||
fun splitToTextRanges(charSequence: CharSequence, pattern: String): Sequence<TextRange> {
|
||||
var lastMatch = 0
|
||||
return sequence {
|
||||
while (true) {
|
||||
val start = charSequence.indexOf(pattern, lastMatch)
|
||||
if (start == -1) {
|
||||
yield(TextRange(lastMatch, charSequence.length))
|
||||
return@sequence
|
||||
}
|
||||
yield(TextRange(lastMatch, start))
|
||||
lastMatch = start + pattern.length
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user