Move out JVM debugger functionality

This commit is contained in:
Yan Zhulanow
2019-04-05 15:10:14 +03:00
parent 5843336d42
commit ae7550c5af
318 changed files with 1068 additions and 723 deletions
+1
View File
@@ -7,6 +7,7 @@ plugins {
dependencies {
compile(kotlinStdlib())
compileOnly(project(":kotlin-reflect-api"))
compile(project(":compiler:psi"))
compile(project(":core:descriptors"))
compile(project(":core:descriptors.jvm"))
compile(project(":compiler:frontend"))
@@ -0,0 +1,39 @@
/*
* 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.idea.core;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.FileTypeConsumer;
import com.intellij.openapi.fileTypes.FileTypeFactory;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.idea.KotlinFileType;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class KotlinFileTypeFactory extends FileTypeFactory {
public final static String[] KOTLIN_EXTENSIONS = new String[] { "kt", "kts" };
private final static FileType[] KOTLIN_FILE_TYPES = new FileType[] { KotlinFileType.INSTANCE };
public final static Set<FileType> KOTLIN_FILE_TYPES_SET = new HashSet<>(Arrays.asList(KOTLIN_FILE_TYPES));
@Override
public void createFileTypes(@NotNull FileTypeConsumer consumer) {
consumer.consume(KotlinFileType.INSTANCE, "kt;kts");
}
}
@@ -0,0 +1,33 @@
/*
* Copyright 2010-2017 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.
*/
/*
* The old package name left for compatibility reasons with Android IDE plugin
* (it's bundled inside Android Studio).
*/
package org.jetbrains.kotlin.idea.completion
import com.intellij.openapi.extensions.ExtensionPointName
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor
interface CompletionInformationProvider {
companion object {
val EP_NAME: ExtensionPointName<CompletionInformationProvider> =
ExtensionPointName.create("org.jetbrains.kotlin.completionInformationProvider")
}
fun getContainerAndReceiverInformation(descriptor: DeclarationDescriptor): String?
}
@@ -0,0 +1,29 @@
/*
* Copyright 2000-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.idea.core.surroundWith;
import com.intellij.lang.surroundWith.SurroundDescriptor;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.idea.core.util.CodeInsightUtils;
import org.jetbrains.kotlin.psi.KtExpression;
public abstract class KotlinExpressionSurroundDescriptorBase implements SurroundDescriptor {
@Override
@NotNull
public PsiElement[] getElementsToSurround(PsiFile file, int startOffset, int endOffset) {
KtExpression expression = (KtExpression) CodeInsightUtils.findElement(
file, startOffset, endOffset, CodeInsightUtils.ElementKind.EXPRESSION);
return expression == null ? PsiElement.EMPTY_ARRAY : new PsiElement[] {expression};
}
@Override
public boolean isExclusive() {
return false;
}
}
@@ -0,0 +1,69 @@
/*
* Copyright 2000-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.idea.core.surroundWith;
import com.intellij.lang.surroundWith.Surrounder;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionUtils;
import org.jetbrains.kotlin.psi.KtCallExpression;
import org.jetbrains.kotlin.psi.KtExpression;
import org.jetbrains.kotlin.psi.KtQualifiedExpression;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode;
import org.jetbrains.kotlin.types.KotlinType;
import static org.jetbrains.kotlin.builtins.KotlinBuiltIns.isUnit;
import static org.jetbrains.kotlin.idea.core.surroundWith.KotlinSurrounderUtils.isUsedAsStatement;
public abstract class KotlinExpressionSurrounder implements Surrounder {
@Override
public boolean isApplicable(@NotNull PsiElement[] elements) {
if (elements.length != 1 || !(elements[0] instanceof KtExpression)) {
return false;
}
KtExpression expression = (KtExpression) elements[0];
if (expression instanceof KtCallExpression && expression.getParent() instanceof KtQualifiedExpression) {
return false;
}
return isApplicable(expression);
}
protected boolean isApplicable(@NotNull KtExpression expression) {
BindingContext context = ResolutionUtils.analyze(expression, BodyResolveMode.PARTIAL);
KotlinType type = context.getType(expression);
if (type == null || (isUnit(type) && isApplicableToStatements())) {
return false;
}
if (!isApplicableToStatements() && isUsedAsStatement(expression)) {
return false;
}
return true;
}
protected boolean isApplicableToStatements() {
return true;
}
@Nullable
@Override
public TextRange surroundElements(@NotNull Project project, @NotNull Editor editor, @NotNull PsiElement[] elements) {
assert elements.length == 1 : "KotlinExpressionSurrounder should be applicable only for 1 expression: " + elements.length;
return surroundExpression(project, editor, (KtExpression) elements[0]);
}
@Nullable
protected abstract TextRange surroundExpression(@NotNull Project project, @NotNull Editor editor, @NotNull KtExpression expression);
}
@@ -0,0 +1,49 @@
/*
* Copyright 2000-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.idea.core.surroundWith;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.kotlin.idea.KotlinBundle;
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionUtils;
import org.jetbrains.kotlin.idea.core.util.CodeInsightUtils;
import org.jetbrains.kotlin.psi.KtBlockExpression;
import org.jetbrains.kotlin.psi.KtExpression;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.bindingContextUtil.BindingContextUtilsKt;
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode;
public class KotlinSurrounderUtils {
public static String SURROUND_WITH = KotlinBundle.message("surround.with");
public static String SURROUND_WITH_ERROR = KotlinBundle.message("surround.with.cannot.perform.action");
private KotlinSurrounderUtils() {
}
public static void addStatementsInBlock(
@NotNull KtBlockExpression block,
@NotNull PsiElement[] statements
) {
PsiElement lBrace = block.getFirstChild();
block.addRangeAfter(statements[0], statements[statements.length - 1], lBrace);
}
public static void showErrorHint(@NotNull Project project, @NotNull Editor editor, @NotNull String message) {
CodeInsightUtils.showErrorHint(project, editor, message, SURROUND_WITH, null);
}
public static boolean isUsedAsStatement(@NotNull KtExpression expression) {
BindingContext context = ResolutionUtils.analyze(expression, BodyResolveMode.PARTIAL_WITH_CFA);
return BindingContextUtilsKt.isUsedAsStatement(expression, context);
}
public static boolean isUsedAsExpression(@NotNull KtExpression expression) {
BindingContext context = ResolutionUtils.analyze(expression, BodyResolveMode.PARTIAL_WITH_CFA);
return BindingContextUtilsKt.isUsedAsExpression(expression, context);
}
}
@@ -0,0 +1,39 @@
/*
* Copyright 2000-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.idea.core.util
import com.intellij.diagnostic.AttachmentFactory
import com.intellij.openapi.diagnostic.Attachment
import com.intellij.psi.PsiFile
fun attachmentByPsiFile(file: PsiFile?): Attachment? {
if (file == null) return null
val virtualFile = file.virtualFile
if (virtualFile != null) return AttachmentFactory.createAttachment(virtualFile)
val text = try { file.text
} catch(e: Exception) { null }
val name = try { file.name
} catch(e: Exception) { null }
if (text != null && name != null) return Attachment(name, text)
return null
}
fun mergeAttachments(vararg attachments: Attachment?): Attachment {
val builder = StringBuilder()
attachments.forEach {
if (it != null) {
builder.append("----- START ${it.path} -----\n")
builder.append(it.displayText)
builder.append("\n----- END ${it.path} -----\n\n")
}
}
return Attachment("message.txt", builder.toString())
}
@@ -0,0 +1,377 @@
/*
* Copyright 2000-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.idea.core.util;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.util.text.CharArrayUtil;
import kotlin.collections.ArraysKt;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.descriptors.ClassKind;
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionUtils;
import org.jetbrains.kotlin.lexer.KtTokens;
import org.jetbrains.kotlin.psi.*;
import org.jetbrains.kotlin.psi.psiUtil.KtPsiUtilKt;
import org.jetbrains.kotlin.resolve.BindingContext;
import org.jetbrains.kotlin.resolve.scopes.receivers.ClassQualifier;
import org.jetbrains.kotlin.resolve.scopes.receivers.Qualifier;
import org.jetbrains.kotlin.types.KotlinType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static org.jetbrains.kotlin.builtins.KotlinBuiltIns.*;
public class CodeInsightUtils {
@Nullable
public static PsiElement findElement(
@NotNull PsiFile file,
int startOffset,
int endOffset,
@NotNull CodeInsightUtils.ElementKind elementKind
) {
Class<? extends KtElement> elementClass;
switch (elementKind) {
case EXPRESSION: elementClass = KtExpression.class;
break;
case TYPE_ELEMENT: elementClass = KtTypeElement.class;
break;
case TYPE_CONSTRUCTOR: elementClass = KtSimpleNameExpression.class;
break;
default: throw new IllegalArgumentException(elementKind.name());
}
PsiElement element = findElementOfClassAtRange(file, startOffset, endOffset, elementClass);
if (elementKind == ElementKind.TYPE_ELEMENT) return element;
if (elementKind == ElementKind.TYPE_CONSTRUCTOR) {
return element != null && KtPsiUtilKt.isTypeConstructorReference(element) ? element : null;
}
if (element instanceof KtScriptInitializer) {
element = ((KtScriptInitializer) element).getBody();
}
if (element == null) return null;
// TODO: Support binary operations in "Introduce..." refactorings
if (element instanceof KtOperationReferenceExpression
&& ((KtOperationReferenceExpression) element).getReferencedNameElementType() != KtTokens.IDENTIFIER
&& element.getParent() instanceof KtBinaryExpression) {
return null;
}
// For cases like 'this@outerClass', don't return the label part
if (KtPsiUtil.isLabelIdentifierExpression(element)) {
element = PsiTreeUtil.getParentOfType(element, KtExpression.class);
}
if (element instanceof KtBlockExpression) {
List<KtExpression> statements = ((KtBlockExpression) element).getStatements();
if (statements.size() == 1) {
KtExpression statement = statements.get(0);
if (statement.getText().equals(element.getText())) {
return statement;
}
}
}
KtExpression expression = (KtExpression) element;
BindingContext context = ResolutionUtils.analyze(expression);
Qualifier qualifier = context.get(BindingContext.QUALIFIER, expression);
if (qualifier != null) {
if (!(qualifier instanceof ClassQualifier)) return null;
if (((ClassQualifier) qualifier).getDescriptor().getKind() != ClassKind.OBJECT) return null;
}
return expression;
}
public enum ElementKind {
EXPRESSION,
TYPE_ELEMENT,
TYPE_CONSTRUCTOR
}
@NotNull
public static PsiElement[] findElements(@NotNull PsiFile file, int startOffset, int endOffset, @NotNull ElementKind kind) {
PsiElement element1 = getElementAtOffsetIgnoreWhitespaceBefore(file, startOffset);
PsiElement element2 = getElementAtOffsetIgnoreWhitespaceAfter(file, endOffset);
if (element1 == null || element2 == null) return PsiElement.EMPTY_ARRAY;
startOffset = element1.getTextRange().getStartOffset();
endOffset = element2.getTextRange().getEndOffset();
if (startOffset >= endOffset) return PsiElement.EMPTY_ARRAY;
PsiElement parent = PsiTreeUtil.findCommonParent(element1, element2);
if (parent == null) return PsiElement.EMPTY_ARRAY;
while (true) {
if (parent instanceof KtBlockExpression) break;
if (parent == null || parent instanceof KtFile) return PsiElement.EMPTY_ARRAY;
parent = parent.getParent();
}
element1 = getTopmostParentInside(element1, parent);
if (startOffset != element1.getTextRange().getStartOffset()) return PsiElement.EMPTY_ARRAY;
element2 = getTopmostParentInside(element2, parent);
if (endOffset != element2.getTextRange().getEndOffset()) return PsiElement.EMPTY_ARRAY;
List<PsiElement> array = new ArrayList<PsiElement>();
PsiElement stopElement = element2.getNextSibling();
for (PsiElement currentElement = element1; currentElement != stopElement; currentElement = currentElement.getNextSibling()) {
if (!(currentElement instanceof PsiWhiteSpace)) {
array.add(currentElement);
}
}
for (PsiElement element : array) {
boolean correctType = kind == ElementKind.EXPRESSION && element instanceof KtExpression
|| kind == ElementKind.TYPE_ELEMENT && element instanceof KtTypeElement
|| kind == ElementKind.TYPE_CONSTRUCTOR && KtPsiUtilKt.isTypeConstructorReference(element);
if (!(correctType
|| element.getNode().getElementType() == KtTokens.SEMICOLON
|| element instanceof PsiWhiteSpace
|| element instanceof PsiComment)) {
return PsiElement.EMPTY_ARRAY;
}
}
return PsiUtilCore.toPsiElementArray(array);
}
@Nullable
public static <T extends PsiElement> T findElementOfClassAtRange(@NotNull PsiFile file, int startOffset, int endOffset, Class<T> aClass) {
// When selected range is this@Fo<select>o</select> we'd like to return `@Foo`
// But it's PSI looks like: (AT IDENTIFIER):JetLabel
// So if we search parent starting exactly at IDENTIFIER then we find nothing
// Solution is to retrieve label if we are on AT or IDENTIFIER
PsiElement element1 = getParentLabelOrElement(getElementAtOffsetIgnoreWhitespaceBefore(file, startOffset));
PsiElement element2 = getParentLabelOrElement(getElementAtOffsetIgnoreWhitespaceAfter(file, endOffset));
if (element1 == null || element2 == null) return null;
startOffset = element1.getTextRange().getStartOffset();
endOffset = element2.getTextRange().getEndOffset();
T newElement = PsiTreeUtil.findElementOfClassAtRange(file, startOffset, endOffset, aClass);
if (newElement == null ||
newElement.getTextRange().getStartOffset() != startOffset ||
newElement.getTextRange().getEndOffset() != endOffset) {
return null;
}
return newElement;
}
private static PsiElement getParentLabelOrElement(@Nullable PsiElement element) {
if (element != null && element.getParent() instanceof KtLabelReferenceExpression) {
return element.getParent();
}
return element;
}
@NotNull
public static List<PsiElement> findElementsOfClassInRange(@NotNull PsiFile file, int startOffset, int endOffset, Class<? extends PsiElement> ... classes) {
PsiElement element1 = getElementAtOffsetIgnoreWhitespaceBefore(file, startOffset);
PsiElement element2 = getElementAtOffsetIgnoreWhitespaceAfter(file, endOffset);
if (element1 == null || element2 == null) return Collections.emptyList();
startOffset = element1.getTextRange().getStartOffset();
endOffset = element2.getTextRange().getEndOffset();
PsiElement parent = PsiTreeUtil.findCommonParent(element1, element2);
if (parent == null) return Collections.emptyList();
element1 = getTopmostParentInside(element1, parent);
if (startOffset != element1.getTextRange().getStartOffset()) return Collections.emptyList();
element2 = getTopmostParentInside(element2, parent);
if (endOffset != element2.getTextRange().getEndOffset()) return Collections.emptyList();
PsiElement stopElement = element2.getNextSibling();
List<PsiElement> result = new ArrayList<PsiElement>();
for (PsiElement currentElement = element1; currentElement != stopElement && currentElement != null; currentElement = currentElement.getNextSibling()) {
for (Class aClass : classes) {
if (aClass.isInstance(currentElement)) {
result.add(currentElement);
}
result.addAll(PsiTreeUtil.findChildrenOfType(currentElement, aClass));
}
}
return result;
}
@NotNull
private static PsiElement getTopmostParentInside(@NotNull PsiElement element, @NotNull PsiElement parent) {
if (!parent.equals(element)) {
while (!parent.equals(element.getParent())) {
element = element.getParent();
}
}
return element;
}
@Nullable
public static PsiElement getElementAtOffsetIgnoreWhitespaceBefore(@NotNull PsiFile file, int offset) {
PsiElement element = file.findElementAt(offset);
if (element instanceof PsiWhiteSpace) {
return file.findElementAt(element.getTextRange().getEndOffset());
}
return element;
}
@Nullable
public static PsiElement getElementAtOffsetIgnoreWhitespaceAfter(@NotNull PsiFile file, int offset) {
PsiElement element = file.findElementAt(offset - 1);
if (element instanceof PsiWhiteSpace) {
return file.findElementAt(element.getTextRange().getStartOffset() - 1);
}
return element;
}
@Nullable
public static String defaultInitializer(KotlinType type) {
if (type.isMarkedNullable()) {
return "null";
}
else if (isInt(type) || isLong(type) || isShort(type) || isByte(type)) {
return "0";
}
else if (isFloat(type)) {
return "0.0f";
}
else if (isDouble(type)) {
return "0.0";
}
else if (isChar(type)) {
return "'\\u0000'";
}
else if (isBoolean(type)) {
return "false";
}
else if (isUnit(type)) {
return "Unit";
}
else if (isString(type)) {
return "\"\"";
}
return null;
}
public static void showErrorHint(
@NotNull Project project, @NotNull Editor editor,
@NotNull String message, @NotNull String title,
@Nullable String helpId
) {
if (ApplicationManager.getApplication().isUnitTestMode()) throw new CommonRefactoringUtil.RefactoringErrorHintException(message);
CommonRefactoringUtil.showErrorHint(project, editor, message, title, helpId);
}
private CodeInsightUtils() {
}
@Nullable
public static Integer getStartLineOffset(@NotNull PsiFile file, int line) {
Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file);
if (document == null) return null;
if (line >= document.getLineCount()) {
return null;
}
int lineStartOffset = document.getLineStartOffset(line);
return CharArrayUtil.shiftForward(document.getCharsSequence(), lineStartOffset, " \t");
}
@Nullable
public static Integer getEndLineOffset(@NotNull PsiFile file, int line) {
Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file);
if (document == null) return null;
if (line >= document.getLineCount()) {
return null;
}
int lineStartOffset = document.getLineEndOffset(line);
return CharArrayUtil.shiftBackward(document.getCharsSequence(), lineStartOffset, " \t");
}
@NotNull
public static PsiElement getTopmostElementAtOffset(@NotNull PsiElement element, int offset) {
do {
PsiElement parent = element.getParent();
if (parent == null || (parent.getTextOffset() < offset) || parent instanceof KtBlockExpression) {
break;
}
element = parent;
}
while(true);
return element;
}
@NotNull
public static PsiElement getTopParentWithEndOffset(@NotNull PsiElement element, @NotNull Class<?> stopAt) {
int endOffset = element.getTextOffset() + element.getTextLength();
do {
PsiElement parent = element.getParent();
if (parent == null || (parent.getTextOffset() + parent.getTextLength()) != endOffset) {
break;
}
element = parent;
if (stopAt.isInstance(element)) {
break;
}
}
while(true);
return element;
}
@Nullable
public static <T> T getTopmostElementAtOffset(@NotNull PsiElement element, int offset, @NotNull Class<? extends T>... classes) {
T lastElementOfType = null;
if (anyIsInstance(element, classes)) {
lastElementOfType = (T) element;
}
do {
PsiElement parent = element.getParent();
if (parent == null || (parent.getTextOffset() < offset) || parent instanceof KtBlockExpression) {
break;
}
if (anyIsInstance(parent, classes)) {
lastElementOfType = (T) parent;
}
element = parent;
}
while(true);
return lastElementOfType;
}
private static <T> boolean anyIsInstance(PsiElement finalElement, @NotNull Class<? extends T>[] klass) {
return ArraysKt.any(klass, aClass -> aClass.isInstance(finalElement));
}
}
@@ -0,0 +1,26 @@
/*
* Copyright 2000-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.idea.core.util
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiDirectory
import com.intellij.psi.PsiFile
import com.intellij.psi.PsiManager
import java.io.File
fun File.toPsiFile(project: Project): PsiFile? = toVirtualFile()?.toPsiFile(project)
fun File.toVirtualFile(): VirtualFile? = LocalFileSystem.getInstance().findFileByIoFile(this)
fun File.toPsiDirectory(project: Project): PsiDirectory? {
return toVirtualFile()?.let { vfile -> PsiManager.getInstance(project).findDirectory(vfile) }
}
fun VirtualFile.toPsiFile(project: Project): PsiFile? = PsiManager.getInstance(project).findFile(this)
fun VirtualFile.toPsiDirectory(project: Project): PsiDirectory? = PsiManager.getInstance(project).findDirectory(this)
@@ -0,0 +1,35 @@
/*
* Copyright 2000-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.idea.core.util
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.progress.ProcessCanceledException
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.progress.util.ProgressIndicatorUtils
import com.intellij.openapi.project.Project
fun <T : Any> runInReadActionWithWriteActionPriorityWithPCE(f: () -> T): T =
runInReadActionWithWriteActionPriority(f) ?: throw ProcessCanceledException()
fun <T : Any> runInReadActionWithWriteActionPriority(f: () -> T): T? {
if (with(ApplicationManager.getApplication()) { isDispatchThread && isUnitTestMode }) {
return f()
}
var r: T? = null
val complete = ProgressIndicatorUtils.runInReadActionWithWriteActionPriority {
r = f()
}
if (!complete) return null
return r!!
}
fun <T : Any> Project.runSynchronouslyWithProgress(progressTitle: String, canBeCanceled: Boolean, action: () -> T): T? {
var result: T? = null
ProgressManager.getInstance().runProcessWithProgressSynchronously({ result = action() }, progressTitle, canBeCanceled, this)
return result
}
@@ -0,0 +1,55 @@
/*
* Copyright 2000-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.idea.core.util
import com.intellij.openapi.util.TextRange
import com.intellij.psi.*
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.psi.psiUtil.endOffset
import org.jetbrains.kotlin.psi.psiUtil.startOffset
fun PsiFile.getLineStartOffset(line: Int): Int? {
val doc = viewProvider.document ?: PsiDocumentManager.getInstance(project).getDocument(this)
if (doc != null && line >= 0 && line < doc.lineCount) {
val startOffset = doc.getLineStartOffset(line)
val element = findElementAt(startOffset) ?: return startOffset
if (element is PsiWhiteSpace || element is PsiComment) {
return PsiTreeUtil.skipSiblingsForward(element, PsiWhiteSpace::class.java, PsiComment::class.java)?.startOffset ?: startOffset
}
return startOffset
}
return null
}
fun PsiFile.getLineEndOffset(line: Int): Int? {
val document = viewProvider.document ?: PsiDocumentManager.getInstance(project).getDocument(this)
return document?.getLineEndOffset(line)
}
fun PsiElement.getLineNumber(start: Boolean = true): Int {
val document = containingFile.viewProvider.document ?: PsiDocumentManager.getInstance(project).getDocument(containingFile)
return document?.getLineNumber(if (start) this.startOffset else this.endOffset) ?: 0
}
fun PsiElement.getLineCount(): Int {
val doc = containingFile?.let { file -> PsiDocumentManager.getInstance(project).getDocument(file) }
if (doc != null) {
val spaceRange = textRange ?: TextRange.EMPTY_RANGE
if (spaceRange.endOffset <= doc.textLength) {
val startLine = doc.getLineNumber(spaceRange.startOffset)
val endLine = doc.getLineNumber(spaceRange.endOffset)
return endLine - startLine
}
}
return (text ?: "").count { it == '\n' } + 1
}
fun PsiElement.isMultiLine(): Boolean = getLineCount() > 1
@@ -0,0 +1,31 @@
/*
* Copyright 2000-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.idea.core.util
import com.intellij.openapi.editor.RangeMarker
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiElement
val TextRange.start: Int
get() = startOffset
val TextRange.end: Int
get() = endOffset
val PsiElement.range: TextRange
get() = textRange!!
val RangeMarker.range: TextRange?
get() = if (isValid) {
val start = startOffset
val end = endOffset
if (start in 0..end) {
TextRange(start, end)
} else {
// Probably a race condition had happened and range marker is invalidated
null
}
} else null
@@ -0,0 +1,22 @@
/*
* Copyright 2000-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.idea.core.util
import com.intellij.openapi.project.Project
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.PsiClass
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.idea.util.runWithAlternativeResolveEnabled
fun getKotlinJvmRuntimeMarkerClass(project: Project, scope: GlobalSearchScope): PsiClass? {
return runReadAction {
project.runWithAlternativeResolveEnabled {
JavaPsiFacade.getInstance(project).findClass(KotlinBuiltIns.FQ_NAMES.unit.asString(), scope)
}
}
}
@@ -0,0 +1,20 @@
/*
* Copyright 2000-2019 JetBrains s.r.o. and Kotlin Programming Language contributors.
* 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.idea.core.util
import com.intellij.ui.DocumentAdapter
import javax.swing.event.DocumentEvent
import javax.swing.text.JTextComponent
fun JTextComponent.onTextChange(action: (DocumentEvent) -> Unit) {
document.addDocumentListener(
object : DocumentAdapter() {
override fun textChanged(e: DocumentEvent) {
action(e)
}
}
)
}