Rewrite KotlinClassInnerStuffCache into Kt, p2
This commit is contained in:
+177
-195
@@ -3,237 +3,219 @@
|
|||||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.jetbrains.kotlin.asJava.classes;
|
package org.jetbrains.kotlin.asJava.classes
|
||||||
|
|
||||||
import com.intellij.openapi.diagnostic.Logger;
|
import com.intellij.openapi.diagnostic.Logger
|
||||||
import com.intellij.openapi.util.SimpleModificationTracker;
|
import com.intellij.openapi.util.SimpleModificationTracker
|
||||||
import com.intellij.psi.*;
|
import com.intellij.psi.*
|
||||||
import com.intellij.psi.augment.PsiAugmentProvider;
|
import com.intellij.psi.augment.PsiAugmentProvider
|
||||||
import com.intellij.psi.impl.PsiClassImplUtil;
|
import com.intellij.psi.impl.PsiClassImplUtil
|
||||||
import com.intellij.psi.impl.PsiImplUtil;
|
import com.intellij.psi.impl.PsiImplUtil
|
||||||
import com.intellij.psi.impl.light.LightMethod;
|
import com.intellij.psi.impl.light.LightMethod
|
||||||
import com.intellij.psi.impl.source.PsiExtensibleClass;
|
import com.intellij.psi.impl.source.PsiExtensibleClass
|
||||||
import com.intellij.psi.scope.ElementClassHint;
|
import com.intellij.psi.scope.ElementClassHint
|
||||||
import com.intellij.psi.scope.NameHint;
|
import com.intellij.psi.scope.NameHint
|
||||||
import com.intellij.psi.scope.PsiScopeProcessor;
|
import com.intellij.psi.scope.PsiScopeProcessor
|
||||||
import com.intellij.psi.util.CachedValueProvider;
|
import com.intellij.psi.util.CachedValueProvider
|
||||||
import com.intellij.psi.util.CachedValuesManager;
|
import com.intellij.psi.util.CachedValuesManager
|
||||||
import com.intellij.util.ArrayUtil;
|
import com.intellij.util.ArrayUtil
|
||||||
import com.intellij.util.SmartList;
|
import com.intellij.util.containers.ContainerUtil
|
||||||
import com.intellij.util.containers.ContainerUtil;
|
import gnu.trove.THashMap
|
||||||
import gnu.trove.THashMap;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.Collections;
|
class KotlinClassInnerStuffCache(val myClass: PsiExtensibleClass, externalDependencies: List<Any>) {
|
||||||
import java.util.List;
|
private val myTracker = SimpleModificationTracker()
|
||||||
import java.util.Map;
|
private val dependencies: List<Any> = externalDependencies + myTracker
|
||||||
|
|
||||||
import static com.intellij.util.ObjectUtils.notNull;
|
|
||||||
|
|
||||||
// Copy of ClassInnerStuffCache with custom list of caching dependencies
|
|
||||||
@SuppressWarnings({"WeakerAccess", "Java8MapApi"})
|
|
||||||
public class KotlinClassInnerStuffCache {
|
|
||||||
private final PsiExtensibleClass myClass;
|
|
||||||
private final SimpleModificationTracker myTracker = new SimpleModificationTracker();
|
|
||||||
private final List<Object> dependencies;
|
|
||||||
|
|
||||||
public KotlinClassInnerStuffCache(@NotNull PsiExtensibleClass aClass, @NotNull List<Object> externalDependencies) {
|
|
||||||
myClass = aClass;
|
|
||||||
|
|
||||||
dependencies = new SmartList<>();
|
|
||||||
dependencies.addAll(externalDependencies);
|
|
||||||
dependencies.add(myTracker);
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public PsiMethod[] getConstructors() {
|
|
||||||
return copy(CachedValuesManager.getCachedValue(myClass, () -> makeResult(PsiImplUtil.getConstructors(myClass))));
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public PsiField[] getFields() {
|
|
||||||
return copy(CachedValuesManager.getCachedValue(myClass, () -> makeResult(getAllFields())));
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public PsiMethod[] getMethods() {
|
|
||||||
return copy(CachedValuesManager.getCachedValue(myClass, () -> makeResult(getAllMethods())));
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public PsiClass[] getInnerClasses() {
|
|
||||||
return copy(CachedValuesManager.getCachedValue(myClass, () -> makeResult(getAllInnerClasses())));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public PsiField findFieldByName(String name, boolean checkBases) {
|
|
||||||
if (checkBases) {
|
|
||||||
return PsiClassImplUtil.findFieldByName(myClass, name, true);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return CachedValuesManager.getCachedValue(myClass, () -> makeResult(getFieldsMap())).get(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@NotNull
|
|
||||||
public PsiMethod[] findMethodsByName(String name, boolean checkBases) {
|
|
||||||
if (checkBases) {
|
|
||||||
return PsiClassImplUtil.findMethodsByName(myClass, name, true);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return copy(notNull(CachedValuesManager.getCachedValue(myClass, () -> makeResult(getMethodsMap())).get(name), PsiMethod.EMPTY_ARRAY));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public PsiClass findInnerClassByName(String name, boolean checkBases) {
|
|
||||||
if (checkBases) {
|
|
||||||
return PsiClassImplUtil.findInnerByName(myClass, name, true);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return CachedValuesManager.getCachedValue(myClass, () -> makeResult(getInnerClassesMap())).get(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public PsiMethod getValuesMethod() {
|
|
||||||
return myClass.isEnum() && myClass.getName() != null ? CachedValuesManager.getCachedValue(myClass, () -> makeResult(makeValuesMethod())) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public PsiMethod getValueOfMethod() {
|
|
||||||
return myClass.isEnum() && myClass.getName() != null ? CachedValuesManager.getCachedValue(myClass, () -> makeResult(makeValueOfMethod())) : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static <T> T[] copy(T[] value) {
|
|
||||||
return value.length == 0 ? value : value.clone();
|
|
||||||
}
|
|
||||||
|
|
||||||
// This method is modified
|
// This method is modified
|
||||||
private <T> CachedValueProvider.Result<T> makeResult(T value) {
|
private fun <T> makeProvider(value: () -> T): CachedValueProvider.Result<T> {
|
||||||
return CachedValueProvider.Result.create(value, dependencies);
|
return CachedValueProvider.Result.create(value(), dependencies)
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
private fun <T : Any> get(myClass: PsiExtensibleClass, provider: CachedValueProvider<T>) = lazyPub {
|
||||||
private PsiField[] getAllFields() {
|
CachedValuesManager.getCachedValue(myClass, provider)
|
||||||
List<PsiField> own = myClass.getOwnFields();
|
|
||||||
List<PsiField> ext = PsiAugmentProvider.collectAugments(myClass, PsiField.class);
|
|
||||||
return ArrayUtil.mergeCollections(own, ext, PsiField.ARRAY_FACTORY);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
private val _getConstructors: Array<PsiMethod> by get(
|
||||||
private PsiMethod[] getAllMethods() {
|
myClass,
|
||||||
List<PsiMethod> own = myClass.getOwnMethods();
|
CachedValueProvider { makeProvider { PsiImplUtil.getConstructors(myClass) } })
|
||||||
List<PsiMethod> ext = PsiAugmentProvider.collectAugments(myClass, PsiMethod.class);
|
|
||||||
return ArrayUtil.mergeCollections(own, ext, PsiMethod.ARRAY_FACTORY);
|
val constructors: Array<PsiMethod>
|
||||||
|
get() = _getConstructors
|
||||||
|
|
||||||
|
private val _getFields: Array<PsiField> by get(myClass, CachedValueProvider { makeProvider { this.getAllFields() } })
|
||||||
|
|
||||||
|
val fields: Array<PsiField>
|
||||||
|
get() = _getFields
|
||||||
|
|
||||||
|
private val _getMethods: Array<PsiMethod> by get(myClass, CachedValueProvider { makeProvider { this.getAllMethods() } })
|
||||||
|
|
||||||
|
val methods: Array<PsiMethod>
|
||||||
|
get() = _getMethods
|
||||||
|
|
||||||
|
private val _getAllInnerClasses: Array<PsiClass> by get(myClass, CachedValueProvider { makeProvider { this.getAllInnerClasses() } })
|
||||||
|
|
||||||
|
val innerClasses: Array<out PsiClass>
|
||||||
|
get() = _getAllInnerClasses
|
||||||
|
|
||||||
|
private val _getFieldsMap: Map<String, PsiField> by get(myClass, CachedValueProvider { makeProvider { this.getFieldsMap() } })
|
||||||
|
|
||||||
|
fun findFieldByName(name: String, checkBases: Boolean): PsiField? {
|
||||||
|
return if (checkBases) {
|
||||||
|
PsiClassImplUtil.findFieldByName(myClass, name, true)
|
||||||
|
} else {
|
||||||
|
_getFieldsMap[name]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
private val _getMethodsMap: Map<String, Array<PsiMethod>> by get(myClass, CachedValueProvider { makeProvider { this.getMethodsMap() } })
|
||||||
private PsiClass[] getAllInnerClasses() {
|
|
||||||
List<PsiClass> own = myClass.getOwnInnerClasses();
|
fun findMethodsByName(name: String, checkBases: Boolean): Array<PsiMethod> {
|
||||||
List<PsiClass> ext = PsiAugmentProvider.collectAugments(myClass, PsiClass.class);
|
return if (checkBases) {
|
||||||
return ArrayUtil.mergeCollections(own, ext, PsiClass.ARRAY_FACTORY);
|
PsiClassImplUtil.findMethodsByName(myClass, name, true)
|
||||||
|
} else {
|
||||||
|
copy(_getMethodsMap[name] ?: PsiMethod.EMPTY_ARRAY)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
private val _getInnerClassesMap: Map<String, PsiClass> by get(myClass,
|
||||||
private Map<String, PsiField> getFieldsMap() {
|
CachedValueProvider { makeProvider { this.getInnerClassesMap() } })
|
||||||
PsiField[] fields = getFields();
|
|
||||||
if (fields.length == 0) return Collections.emptyMap();
|
|
||||||
|
|
||||||
Map<String, PsiField> cachedFields = new THashMap<>();
|
fun findInnerClassByName(name: String, checkBases: Boolean): PsiClass? {
|
||||||
for (PsiField field : fields) {
|
return if (checkBases) {
|
||||||
String name = field.getName();
|
PsiClassImplUtil.findInnerByName(myClass, name, true)
|
||||||
|
} else {
|
||||||
|
_getInnerClassesMap[name]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val _makeValuesMethod: PsiMethod by get(myClass, CachedValueProvider { makeProvider { this.makeValuesMethod() } })
|
||||||
|
|
||||||
|
fun getValuesMethod(): PsiMethod? = if (myClass.isEnum && myClass.name != null) _makeValuesMethod else null
|
||||||
|
|
||||||
|
private val _makeValueOfMethod: PsiMethod by get(myClass, CachedValueProvider { makeProvider { this.makeValueOfMethod() } })
|
||||||
|
|
||||||
|
fun getValueOfMethod(): PsiMethod? = if (myClass.isEnum && myClass.name != null) _makeValueOfMethod else null
|
||||||
|
|
||||||
|
private fun <T> copy(value: Array<T>): Array<T> {
|
||||||
|
return if (value.isEmpty()) value else value.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAllFields(): Array<PsiField> {
|
||||||
|
val own = myClass.ownFields
|
||||||
|
val ext = PsiAugmentProvider.collectAugments(myClass, PsiField::class.java)
|
||||||
|
return ArrayUtil.mergeCollections(own, ext, PsiField.ARRAY_FACTORY)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAllMethods(): Array<PsiMethod> {
|
||||||
|
val own = myClass.ownMethods
|
||||||
|
val ext = PsiAugmentProvider.collectAugments(myClass, PsiMethod::class.java)
|
||||||
|
return ArrayUtil.mergeCollections(own, ext, PsiMethod.ARRAY_FACTORY)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAllInnerClasses(): Array<PsiClass> {
|
||||||
|
val own = myClass.ownInnerClasses
|
||||||
|
val ext = PsiAugmentProvider.collectAugments(myClass, PsiClass::class.java)
|
||||||
|
return ArrayUtil.mergeCollections(own, ext, PsiClass.ARRAY_FACTORY)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getFieldsMap(): Map<String, PsiField> {
|
||||||
|
val fields = this.fields
|
||||||
|
if (fields.isEmpty()) return emptyMap()
|
||||||
|
|
||||||
|
val cachedFields = THashMap<String, PsiField>()
|
||||||
|
for (field in fields) {
|
||||||
|
val name = field.name
|
||||||
if (!cachedFields.containsKey(name)) {
|
if (!cachedFields.containsKey(name)) {
|
||||||
cachedFields.put(name, field);
|
cachedFields[name] = field
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cachedFields;
|
return cachedFields
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
private fun getMethodsMap(): Map<String, Array<PsiMethod>> {
|
||||||
private Map<String, PsiMethod[]> getMethodsMap() {
|
val methods = this.methods
|
||||||
PsiMethod[] methods = getMethods();
|
if (methods.isEmpty()) return emptyMap()
|
||||||
if (methods.length == 0) return Collections.emptyMap();
|
|
||||||
|
|
||||||
Map<String, List<PsiMethod>> collectedMethods = ContainerUtil.newHashMap();
|
val collectedMethods = ContainerUtil.newHashMap<String, MutableList<PsiMethod>>()
|
||||||
for (PsiMethod method : methods) {
|
for (method in methods) {
|
||||||
List<PsiMethod> list = collectedMethods.get(method.getName());
|
var list: MutableList<PsiMethod>? = collectedMethods[method.name]
|
||||||
if (list == null) {
|
if (list == null) {
|
||||||
collectedMethods.put(method.getName(), list = ContainerUtil.newSmartList());
|
list = ContainerUtil.newSmartList()
|
||||||
|
collectedMethods[method.name] = list
|
||||||
}
|
}
|
||||||
list.add(method);
|
list.add(method)
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, PsiMethod[]> cachedMethods = ContainerUtil.newTroveMap();
|
val cachedMethods = ContainerUtil.newTroveMap<String, Array<PsiMethod>>()
|
||||||
for (Map.Entry<String, List<PsiMethod>> entry : collectedMethods.entrySet()) {
|
for ((key, list) in collectedMethods) {
|
||||||
List<PsiMethod> list = entry.getValue();
|
cachedMethods[key] = list.toTypedArray()
|
||||||
cachedMethods.put(entry.getKey(), list.toArray(PsiMethod.EMPTY_ARRAY));
|
|
||||||
}
|
}
|
||||||
return cachedMethods;
|
return cachedMethods
|
||||||
}
|
}
|
||||||
|
|
||||||
@NotNull
|
private fun getInnerClassesMap(): Map<String, PsiClass> {
|
||||||
private Map<String, PsiClass> getInnerClassesMap() {
|
val classes = this.innerClasses
|
||||||
PsiClass[] classes = getInnerClasses();
|
if (classes.isEmpty()) return emptyMap()
|
||||||
if (classes.length == 0) return Collections.emptyMap();
|
|
||||||
|
|
||||||
Map<String, PsiClass> cachedInners = new THashMap<>();
|
val cachedInners = THashMap<String, PsiClass>()
|
||||||
for (PsiClass psiClass : classes) {
|
for (psiClass in classes) {
|
||||||
String name = psiClass.getName();
|
val name = psiClass.name
|
||||||
if (name == null) {
|
if (name == null) {
|
||||||
Logger.getInstance(KotlinClassInnerStuffCache.class).error(psiClass);
|
Logger.getInstance(KotlinClassInnerStuffCache::class.java).error(psiClass)
|
||||||
}
|
} else if (psiClass !is ExternallyDefinedPsiElement || !cachedInners.containsKey(name)) {
|
||||||
else if (!(psiClass instanceof ExternallyDefinedPsiElement) || !cachedInners.containsKey(name)) {
|
cachedInners[name] = psiClass
|
||||||
cachedInners.put(name, psiClass);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return cachedInners;
|
return cachedInners
|
||||||
}
|
}
|
||||||
|
|
||||||
private PsiMethod makeValuesMethod() {
|
private fun makeValuesMethod(): PsiMethod {
|
||||||
return getSyntheticMethod("public static " + myClass.getName() + "[] values() { }");
|
return getSyntheticMethod("public static " + myClass.name + "[] values() { }")
|
||||||
}
|
}
|
||||||
|
|
||||||
private PsiMethod makeValueOfMethod() {
|
private fun makeValueOfMethod(): PsiMethod {
|
||||||
return getSyntheticMethod("public static " + myClass.getName() + " valueOf(java.lang.String name) throws java.lang.IllegalArgumentException { }");
|
return getSyntheticMethod("public static " + myClass.name + " valueOf(java.lang.String name) throws java.lang.IllegalArgumentException { }")
|
||||||
}
|
}
|
||||||
|
|
||||||
private PsiMethod getSyntheticMethod(String text) {
|
private fun getSyntheticMethod(text: String): PsiMethod {
|
||||||
PsiElementFactory factory = JavaPsiFacade.getElementFactory(myClass.getProject());
|
val factory = JavaPsiFacade.getElementFactory(myClass.project)
|
||||||
PsiMethod method = factory.createMethodFromText(text, myClass);
|
val method = factory.createMethodFromText(text, myClass)
|
||||||
return new LightMethod(myClass.getManager(), method, myClass) {
|
return object : LightMethod(myClass.manager, method, myClass) {
|
||||||
@Override
|
override fun getTextOffset(): Int {
|
||||||
public int getTextOffset() {
|
return myClass.textOffset
|
||||||
return myClass.getTextOffset();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public void dropCaches() {
|
|
||||||
myTracker.incModificationCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final String VALUES_METHOD = "values";
|
|
||||||
private static final String VALUE_OF_METHOD = "valueOf";
|
|
||||||
|
|
||||||
// Copy of PsiClassImplUtil.processDeclarationsInEnum for own cache class
|
|
||||||
public static boolean processDeclarationsInEnum(@NotNull PsiScopeProcessor processor,
|
|
||||||
@NotNull ResolveState state,
|
|
||||||
@NotNull KotlinClassInnerStuffCache innerStuffCache) {
|
|
||||||
ElementClassHint classHint = processor.getHint(ElementClassHint.KEY);
|
|
||||||
if (classHint == null || classHint.shouldProcess(ElementClassHint.DeclarationKind.METHOD)) {
|
|
||||||
NameHint nameHint = processor.getHint(NameHint.KEY);
|
|
||||||
if (nameHint == null || VALUES_METHOD.equals(nameHint.getName(state))) {
|
|
||||||
PsiMethod method = innerStuffCache.getValuesMethod();
|
|
||||||
if (method != null && !processor.execute(method, ResolveState.initial())) return false;
|
|
||||||
}
|
|
||||||
if (nameHint == null || VALUE_OF_METHOD.equals(nameHint.getName(state))) {
|
|
||||||
PsiMethod method = innerStuffCache.getValueOfMethod();
|
|
||||||
if (method != null && !processor.execute(method, ResolveState.initial())) return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
fun dropCaches() {
|
||||||
|
myTracker.incModificationCount()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val VALUES_METHOD = "values"
|
||||||
|
private const val VALUE_OF_METHOD = "valueOf"
|
||||||
|
|
||||||
|
// Copy of PsiClassImplUtil.processDeclarationsInEnum for own cache class
|
||||||
|
@JvmStatic
|
||||||
|
fun processDeclarationsInEnum(
|
||||||
|
processor: PsiScopeProcessor,
|
||||||
|
state: ResolveState,
|
||||||
|
innerStuffCache: KotlinClassInnerStuffCache
|
||||||
|
): Boolean {
|
||||||
|
val classHint = processor.getHint(ElementClassHint.KEY)
|
||||||
|
if (classHint == null || classHint.shouldProcess(ElementClassHint.DeclarationKind.METHOD)) {
|
||||||
|
val nameHint = processor.getHint(NameHint.KEY)
|
||||||
|
if (nameHint == null || VALUES_METHOD == nameHint.getName(state)) {
|
||||||
|
val method = innerStuffCache.getValuesMethod()
|
||||||
|
if (method != null && !processor.execute(method, ResolveState.initial())) return false
|
||||||
|
}
|
||||||
|
if (nameHint == null || VALUE_OF_METHOD == nameHint.getName(state)) {
|
||||||
|
val method = innerStuffCache.getValueOfMethod()
|
||||||
|
if (method != null && !processor.execute(method, ResolveState.initial())) return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@ import com.intellij.psi.impl.light.AbstractLightClass
|
|||||||
import com.intellij.psi.impl.source.PsiExtensibleClass
|
import com.intellij.psi.impl.source.PsiExtensibleClass
|
||||||
import com.intellij.psi.scope.PsiScopeProcessor
|
import com.intellij.psi.scope.PsiScopeProcessor
|
||||||
import org.jetbrains.kotlin.analyzer.KotlinModificationTrackerService
|
import org.jetbrains.kotlin.analyzer.KotlinModificationTrackerService
|
||||||
import org.jetbrains.kotlin.asJava.classes.KotlinClassInnerStuffCache.processDeclarationsInEnum
|
import org.jetbrains.kotlin.asJava.classes.KotlinClassInnerStuffCache.Companion.processDeclarationsInEnum
|
||||||
import org.jetbrains.kotlin.asJava.elements.KtLightFieldImpl
|
import org.jetbrains.kotlin.asJava.elements.KtLightFieldImpl
|
||||||
import org.jetbrains.kotlin.asJava.elements.KtLightMethodImpl
|
import org.jetbrains.kotlin.asJava.elements.KtLightMethodImpl
|
||||||
import org.jetbrains.kotlin.idea.KotlinLanguage
|
import org.jetbrains.kotlin.idea.KotlinLanguage
|
||||||
|
|||||||
Reference in New Issue
Block a user