All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jetbrains.kotlin.psi.psiUtil.psiUtils.kt Maven / Gradle / Ivy

There is a newer version: 2.0.0
Show newest version
/*
 * Copyright 2010-2015 JetBrains s.r.o.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jetbrains.kotlin.psi.psiUtil

import com.intellij.openapi.util.TextRange
import com.intellij.psi.*
import com.intellij.psi.search.PsiSearchScopeUtil
import com.intellij.psi.search.SearchScope
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.diagnostics.DiagnosticUtils
import java.util.*

// NOTE: in this file we collect only LANGUAGE INDEPENDENT methods working with PSI and not modifying it

// ----------- Walking children/siblings/parents -------------------------------------------------------------------------------------------

public val PsiElement.allChildren: PsiChildRange
    get() {
        val first = getFirstChild()
        return if (first != null) PsiChildRange(first, getLastChild()) else PsiChildRange.EMPTY
    }

public fun PsiElement.siblings(forward: Boolean = true, withItself: Boolean = true): Sequence {
    return object : Sequence {
        override fun iterator(): Iterator {
            var next: PsiElement? = this@siblings
            return object : Iterator {
                init {
                    if (!withItself) next()
                }

                override fun hasNext(): Boolean = next != null
                override fun next(): PsiElement {
                    val result = next ?: throw NoSuchElementException()
                    if (forward)
                        next = result.nextSibling
                    else
                        next = result.prevSibling
                    return result
                }
            }
        }
    }
}

public val PsiElement.parentsWithSelf: Sequence
    get() = sequence(this) { if (it is PsiFile) null else it.getParent() }

public val PsiElement.parents: Sequence
    get() = parentsWithSelf.drop(1)

public fun PsiElement.prevLeaf(skipEmptyElements: Boolean = false): PsiElement?
        = PsiTreeUtil.prevLeaf(this, skipEmptyElements)

public fun PsiElement.nextLeaf(skipEmptyElements: Boolean = false): PsiElement?
        = PsiTreeUtil.nextLeaf(this, skipEmptyElements)

public fun PsiElement.prevLeaf(filter: (PsiElement) -> Boolean): PsiElement? {
    var leaf = prevLeaf()
    while (leaf != null && !filter(leaf)) {
        leaf = leaf.prevLeaf()
    }
    return leaf
}

public fun PsiElement.nextLeaf(filter: (PsiElement) -> Boolean): PsiElement? {
    var leaf = nextLeaf()
    while (leaf != null && !filter(leaf)) {
        leaf = leaf.nextLeaf()
    }
    return leaf
}

public fun PsiElement.getParentOfTypesAndPredicate(
        strict: Boolean = false, vararg parentClasses: Class, predicate: (T) -> Boolean
): T? {
    var element = if (strict) getParent() else this
    while (element != null) {
        @Suppress("UNCHECKED_CAST")
        when {
            (parentClasses.isEmpty() || parentClasses.any { parentClass -> parentClass.isInstance(element) }) && predicate(element!! as T) ->
                return element as T
            element is PsiFile ->
                return null
            else ->
                element = element!!.getParent()
        }
    }

    return null
}

public fun PsiElement.getNonStrictParentOfType(parentClass: Class): T? {
    return PsiTreeUtil.getParentOfType(this, parentClass, false)
}

inline public fun PsiElement.getParentOfType(strict: Boolean): T? {
    return PsiTreeUtil.getParentOfType(this, javaClass(), strict)
}

inline public fun PsiElement.getStrictParentOfType(): T? {
    return PsiTreeUtil.getParentOfType(this, javaClass(), true)
}

inline public fun PsiElement.getNonStrictParentOfType(): T? {
    return PsiTreeUtil.getParentOfType(this, javaClass(), false)
}

inline public fun PsiElement.getChildOfType(): T? {
    return PsiTreeUtil.getChildOfType(this, javaClass())
}

inline public fun PsiElement.getChildrenOfType(): Array {
    return PsiTreeUtil.getChildrenOfType(this, javaClass()) ?: arrayOf()
}

public fun PsiElement.getNextSiblingIgnoringWhitespaceAndComments(): PsiElement? {
    return siblings(withItself = false).filter { it !is PsiWhiteSpace && it !is PsiComment }.firstOrNull()
}

public fun PsiElement?.isAncestor(element: PsiElement, strict: Boolean = false): Boolean {
    return PsiTreeUtil.isAncestor(this, element, strict)
}

public fun  T.getIfChildIsInBranch(element: PsiElement, branch: T.() -> PsiElement?): T? {
    return if (branch().isAncestor(element)) this else null
}

public inline fun PsiElement.getParentOfTypeAndBranch(strict: Boolean = false, noinline branch: T.() -> PsiElement?): T? {
    return getParentOfType(strict)?.getIfChildIsInBranch(this, branch)
}

public tailrec fun PsiElement.getOutermostParentContainedIn(container: PsiElement): PsiElement? {
    val parent = getParent()
    return if (parent == container) this else parent?.getOutermostParentContainedIn(container)
}

public fun PsiElement.isInsideOf(elements: Iterable): Boolean = elements.any { it.isAncestor(this) }

// -------------------- Recursive tree visiting --------------------------------------------------------------------------------------------

public inline fun  PsiElement.forEachDescendantOfType(noinline action: (T) -> Unit) {
    forEachDescendantOfType({ true }, action)
}

public inline fun  PsiElement.forEachDescendantOfType(crossinline canGoInside: (PsiElement) -> Boolean, noinline action: (T) -> Unit) {
    this.accept(object : PsiRecursiveElementVisitor() {
        override fun visitElement(element: PsiElement) {
            if (canGoInside(element)) {
                super.visitElement(element)
            }

            if (element is T) {
                action(element)
            }
        }
    })
}

public inline fun  PsiElement.anyDescendantOfType(noinline predicate: (T) -> Boolean = { true }): Boolean {
    return findDescendantOfType(predicate) != null
}

public inline fun  PsiElement.anyDescendantOfType(crossinline canGoInside: (PsiElement) -> Boolean, noinline predicate: (T) -> Boolean = { true }): Boolean {
    return findDescendantOfType(canGoInside, predicate) != null
}

public inline fun  PsiElement.findDescendantOfType(noinline predicate: (T) -> Boolean = { true }): T? {
    return findDescendantOfType({ true }, predicate)
}

public inline fun  PsiElement.findDescendantOfType(crossinline canGoInside: (PsiElement) -> Boolean, noinline predicate: (T) -> Boolean = { true }): T? {
    var result: T? = null
    this.accept(object : PsiRecursiveElementWalkingVisitor() {
        override fun visitElement(element: PsiElement) {
            if (element is T && predicate(element)) {
                result = element
                stopWalking()
                return
            }

            if (canGoInside(element)) {
                super.visitElement(element)
            }
        }
    })
    return result
}

public inline fun  PsiElement.collectDescendantsOfType(noinline predicate: (T) -> Boolean = { true }): List {
    return collectDescendantsOfType({ true }, predicate)
}

public inline fun  PsiElement.collectDescendantsOfType(crossinline canGoInside: (PsiElement) -> Boolean, noinline predicate: (T) -> Boolean = { true }): List {
    val result = ArrayList()
    forEachDescendantOfType(canGoInside) {
        if (predicate(it)) {
            result.add(it)
        }
    }
    return result
}

// ----------- Working with offsets, ranges and texts ----------------------------------------------------------------------------------------------

public val PsiElement.startOffset: Int
    get() = getTextRange().getStartOffset()

public val PsiElement.endOffset: Int
    get() = getTextRange().getEndOffset()

public fun PsiElement.getStartOffsetIn(ancestor: PsiElement): Int {
    var offset = 0
    var parent = this
    while (parent != ancestor) {
        offset += parent.getStartOffsetInParent()
        parent = parent.getParent()
    }
    return offset
}

public fun TextRange.containsInside(offset: Int): Boolean = getStartOffset() < offset && offset < getEndOffset()

public val PsiChildRange.textRange: TextRange?
    get() {
        if (isEmpty) return null
        return TextRange(first!!.startOffset, last!!.endOffset)
    }

public fun PsiChildRange.getText(): String {
    if (isEmpty) return ""
    return this.map { it.getText() }.joinToString("")
}

public fun PsiFile.elementsInRange(range: TextRange): List {
    var offset = range.getStartOffset()
    val result = ArrayList()
    while (offset < range.getEndOffset()) {
        val currentRange = TextRange(offset, range.getEndOffset())
        val leaf = findFirstLeafWhollyInRange(this, currentRange) ?: break

        val element = leaf
                .parentsWithSelf
                .first {
                    val parent = it.getParent()
                    it is PsiFile || parent.getTextRange() !in currentRange
                }
        result.add(element)

        offset = element.endOffset
    }
    return result
}

private fun findFirstLeafWhollyInRange(file: PsiFile, range: TextRange): PsiElement? {
    var element = file.findElementAt(range.getStartOffset()) ?: return null
    var elementRange = element.getTextRange()
    if (elementRange.getStartOffset() < range.getStartOffset()) {
        element = element.nextLeaf(skipEmptyElements = true) ?: return null
        elementRange = element.getTextRange()
    }
    assert(elementRange.getStartOffset() >= range.getStartOffset())
    return if (elementRange.getEndOffset() <= range.getEndOffset()) element else null
}

// ---------------------------------- Debug/logging ----------------------------------------------------------------------------------------

public fun PsiElement.getElementTextWithContext(): String {
    assert(isValid) { "Invalid element $this" }

    if (this is PsiFile) {
        return containingFile.getText()
    }

    // Find parent for element among file children
    val topLevelElement = PsiTreeUtil.findFirstParent(this, { it.getParent() is PsiFile }) ?:
                          throw AssertionError("For non-file element we should always be able to find parent in file children")

    val startContextOffset = topLevelElement.startOffset
    val elementContextOffset = getTextRange().getStartOffset()

    val inFileParentOffset = elementContextOffset - startContextOffset


    return StringBuilder(topLevelElement.getText())
            .insert(inFileParentOffset, "")
            .insert(0, "File name: ${containingFile.getName()} Physical: ${containingFile.isPhysical}\n")
            .toString()
}

public fun PsiElement.getTextWithLocation(): String = "'${this.getText()}' at ${DiagnosticUtils.atLocation(this)}"

// -----------------------------------------------------------------------------------------------------------------------------------------

public fun SearchScope.contains(element: PsiElement): Boolean = PsiSearchScopeUtil.isInScope(this, element)

public fun  E.createSmartPointer(): SmartPsiElementPointer =
        SmartPointerManager.getInstance(getProject()).createSmartPsiElementPointer(this)




© 2015 - 2024 Weber Informatics LLC | Privacy Policy