J2K, minor: clean copy-paste conversion code
This commit is contained in:
+63
-44
@@ -54,6 +54,7 @@ import java.util.*
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
class ConvertJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransferableData>() {
|
||||
@Suppress("PrivatePropertyName")
|
||||
private val LOG = Logger.getInstance(ConvertJavaCopyPasteProcessor::class.java)
|
||||
|
||||
override fun extractTransferableData(content: Transferable): List<TextBlockTransferableData> {
|
||||
@@ -61,20 +62,30 @@ class ConvertJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransferab
|
||||
if (content.isDataFlavorSupported(CopiedJavaCode.DATA_FLAVOR)) {
|
||||
return listOf(content.getTransferData(CopiedJavaCode.DATA_FLAVOR) as TextBlockTransferableData)
|
||||
}
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
} catch (e: Throwable) {
|
||||
LOG.error(e)
|
||||
}
|
||||
return listOf()
|
||||
}
|
||||
|
||||
override fun collectTransferableData(file: PsiFile, editor: Editor, startOffsets: IntArray, endOffsets: IntArray): List<TextBlockTransferableData> {
|
||||
override fun collectTransferableData(
|
||||
file: PsiFile,
|
||||
editor: Editor,
|
||||
startOffsets: IntArray,
|
||||
endOffsets: IntArray
|
||||
): List<TextBlockTransferableData> {
|
||||
if (file !is PsiJavaFile) return listOf()
|
||||
|
||||
return listOf(CopiedJavaCode(file.getText()!!, startOffsets, endOffsets))
|
||||
}
|
||||
|
||||
override fun processTransferableData(project: Project, editor: Editor, bounds: RangeMarker, caretOffset: Int, indented: Ref<Boolean>, values: List<TextBlockTransferableData>) {
|
||||
override fun processTransferableData(
|
||||
project: Project,
|
||||
editor: Editor,
|
||||
bounds: RangeMarker,
|
||||
caretOffset: Int,
|
||||
indented: Ref<Boolean>,
|
||||
values: List<TextBlockTransferableData>
|
||||
) {
|
||||
if (DumbService.getInstance(project).isDumb) return
|
||||
if (!KotlinEditorOptions.getInstance().isEnableJavaToKotlinConversion) return
|
||||
|
||||
@@ -104,7 +115,11 @@ class ConvertJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransferab
|
||||
return Result(text, referenceData, result.importsToAdd, result.converterContext)
|
||||
}
|
||||
|
||||
fun insertImports(bounds: TextRange, referenceData: Collection<KotlinReferenceData>, explicitImports: Collection<FqName>): TextRange? {
|
||||
fun insertImports(
|
||||
bounds: TextRange,
|
||||
referenceData: Collection<KotlinReferenceData>,
|
||||
explicitImports: Collection<FqName>
|
||||
): TextRange? {
|
||||
if (referenceData.isEmpty() && explicitImports.isEmpty()) return bounds
|
||||
|
||||
PsiDocumentManager.getInstance(project).commitAllDocuments()
|
||||
@@ -133,31 +148,31 @@ class ConvertJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransferab
|
||||
|
||||
if (conversionResult!!.text != null) return false
|
||||
|
||||
insertImports(bounds.range ?: return true, conversionResult!!.referenceData, conversionResult!!.explicitImports)
|
||||
insertImports(
|
||||
bounds.range ?: return true,
|
||||
conversionResult!!.referenceData,
|
||||
conversionResult!!.explicitImports
|
||||
)
|
||||
return true
|
||||
}
|
||||
|
||||
val textLength = data.startOffsets.indices.sumBy { data.endOffsets[it] - data.startOffsets[it] }
|
||||
if (textLength < 1000) { // if the text to convert is short enough, try to do conversion without permission from user and skip the dialog if nothing converted
|
||||
if (doConversionAndInsertImportsIfUnchanged()) return
|
||||
}
|
||||
// if the text to convert is short enough, try to do conversion without permission from user and skip the dialog if nothing converted
|
||||
if (textLength < 1000 && doConversionAndInsertImportsIfUnchanged()) return
|
||||
|
||||
fun convert() {
|
||||
if (conversionResult == null) {
|
||||
if (doConversionAndInsertImportsIfUnchanged()) return
|
||||
}
|
||||
if (conversionResult == null && doConversionAndInsertImportsIfUnchanged()) return
|
||||
val (text, referenceData, explicitImports) = conversionResult!!
|
||||
text!! // otherwise we should get true from doConversionAndInsertImportsIfUnchanged and return above
|
||||
|
||||
val boundsAfterReplace =
|
||||
runWriteAction {
|
||||
val startOffset = bounds.startOffset
|
||||
document.replaceString(startOffset, bounds.endOffset, text)
|
||||
val boundsAfterReplace = runWriteAction {
|
||||
val startOffset = bounds.startOffset
|
||||
document.replaceString(startOffset, bounds.endOffset, text)
|
||||
|
||||
val endOffsetAfterCopy = startOffset + text.length
|
||||
editor.caretModel.moveToOffset(endOffsetAfterCopy)
|
||||
TextRange(startOffset, endOffsetAfterCopy)
|
||||
}
|
||||
val endOffsetAfterCopy = startOffset + text.length
|
||||
editor.caretModel.moveToOffset(endOffsetAfterCopy)
|
||||
TextRange(startOffset, endOffsetAfterCopy)
|
||||
}
|
||||
|
||||
val newBounds = insertImports(boundsAfterReplace, referenceData, explicitImports)
|
||||
|
||||
@@ -181,7 +196,12 @@ class ConvertJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransferab
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildReferenceData(text: String, parseContext: ParseContext, importsAndPackage: String, targetFile: KtFile): Collection<KotlinReferenceData> {
|
||||
private fun buildReferenceData(
|
||||
text: String,
|
||||
parseContext: ParseContext,
|
||||
importsAndPackage: String,
|
||||
targetFile: KtFile
|
||||
): Collection<KotlinReferenceData> {
|
||||
var blockStart: Int? = null
|
||||
var blockEnd: Int? = null
|
||||
val fileText = buildString {
|
||||
@@ -202,7 +222,6 @@ class ConvertJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransferab
|
||||
}
|
||||
|
||||
val dummyFile = KtPsiFactory(targetFile.project).createAnalyzableFile("dummy.kt", fileText, targetFile)
|
||||
|
||||
return KotlinCopyPasteReferenceProcessor().collectReferenceData(dummyFile, intArrayOf(blockStart!!), intArrayOf(blockEnd!!))
|
||||
}
|
||||
|
||||
@@ -216,16 +235,17 @@ class ConvertJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransferab
|
||||
}
|
||||
|
||||
companion object {
|
||||
@get:TestOnly var conversionPerformed: Boolean = false
|
||||
@get:TestOnly
|
||||
var conversionPerformed: Boolean = false
|
||||
}
|
||||
}
|
||||
|
||||
internal class ConversionResult(
|
||||
val text: String,
|
||||
val parseContext: ParseContext,
|
||||
val importsToAdd: Set<FqName>,
|
||||
val textChanged: Boolean,
|
||||
val converterContext: ConverterContext?
|
||||
val text: String,
|
||||
val parseContext: ParseContext,
|
||||
val importsToAdd: Set<FqName>,
|
||||
val textChanged: Boolean,
|
||||
val converterContext: ConverterContext?
|
||||
)
|
||||
|
||||
internal fun ElementAndTextList.convertCodeToKotlin(project: Project, targetModule: Module?, useNewJ2k: Boolean): ConversionResult {
|
||||
@@ -237,16 +257,16 @@ internal fun ElementAndTextList.convertCodeToKotlin(project: Project, targetModu
|
||||
IdeaJavaToKotlinServices
|
||||
)
|
||||
|
||||
val inputElements = this.toList().filterIsInstance<PsiElement>()
|
||||
val inputElements = toList().filterIsInstance<PsiElement>()
|
||||
val (results, _, converterContext) =
|
||||
ProgressManager.getInstance().runProcessWithProgressSynchronously(
|
||||
ThrowableComputable<Result, Exception> {
|
||||
runReadAction { converter.elementsToKotlin(inputElements) }
|
||||
},
|
||||
JavaToKotlinAction.title,
|
||||
false,
|
||||
project
|
||||
)
|
||||
ProgressManager.getInstance().runProcessWithProgressSynchronously(
|
||||
ThrowableComputable<Result, Exception> {
|
||||
runReadAction { converter.elementsToKotlin(inputElements) }
|
||||
},
|
||||
JavaToKotlinAction.title,
|
||||
false,
|
||||
project
|
||||
)
|
||||
|
||||
|
||||
val importsToAdd = LinkedHashSet<FqName>()
|
||||
@@ -267,8 +287,7 @@ internal fun ElementAndTextList.convertCodeToKotlin(project: Project, targetModu
|
||||
parseContext = result.parseContext
|
||||
}
|
||||
importsToAdd.addAll(result.importsToAdd)
|
||||
}
|
||||
else { // failed to convert element to Kotlin, insert "as is"
|
||||
} else { // failed to convert element to Kotlin, insert "as is"
|
||||
convertedCodeBuilder.append(originalText)
|
||||
}
|
||||
}
|
||||
@@ -297,11 +316,11 @@ internal fun isNoConversionPosition(file: KtFile, offset: Int): Boolean {
|
||||
if (token !is PsiWhiteSpace && token.endOffset != offset) return true // pasting into the middle of token
|
||||
|
||||
for (element in token.parentsWithSelf) {
|
||||
if (element is PsiComment) {
|
||||
return element.node.elementType == KtTokens.EOL_COMMENT || offset != element.endOffset
|
||||
when (element) {
|
||||
is PsiComment -> return element.node.elementType == KtTokens.EOL_COMMENT || offset != element.endOffset
|
||||
is KtStringTemplateEntryWithExpression -> return false
|
||||
is KtStringTemplateExpression -> return true
|
||||
}
|
||||
if (element is KtStringTemplateEntryWithExpression) return false
|
||||
if (element is KtStringTemplateExpression) return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
+46
-27
@@ -61,11 +61,17 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransf
|
||||
override fun setOffsets(offsets: IntArray?, index: Int) = index
|
||||
|
||||
companion object {
|
||||
val DATA_FLAVOR: DataFlavor = DataFlavor(ConvertTextJavaCopyPasteProcessor::class.java, "class: ConvertTextJavaCopyPasteProcessor")
|
||||
val DATA_FLAVOR: DataFlavor =
|
||||
DataFlavor(ConvertTextJavaCopyPasteProcessor::class.java, "class: ConvertTextJavaCopyPasteProcessor")
|
||||
}
|
||||
}
|
||||
|
||||
override fun collectTransferableData(file: PsiFile, editor: Editor, startOffsets: IntArray, endOffsets: IntArray): List<TextBlockTransferableData> {
|
||||
override fun collectTransferableData(
|
||||
file: PsiFile,
|
||||
editor: Editor,
|
||||
startOffsets: IntArray,
|
||||
endOffsets: IntArray
|
||||
): List<TextBlockTransferableData> {
|
||||
if (file is KtFile) return listOf(CopiedKotlinCode(file.text, startOffsets, endOffsets))
|
||||
return emptyList()
|
||||
}
|
||||
@@ -73,22 +79,28 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransf
|
||||
override fun extractTransferableData(content: Transferable): List<TextBlockTransferableData> {
|
||||
try {
|
||||
if (content.isDataFlavorSupported(DataFlavor.stringFlavor)) {
|
||||
|
||||
if (content.isDataFlavorSupported(CopiedKotlinCode.DATA_FLAVOR) ||
|
||||
/* Handled by ConvertJavaCopyPasteProcessor */
|
||||
content.isDataFlavorSupported(CopiedJavaCode.DATA_FLAVOR)) return emptyList()
|
||||
content.isDataFlavorSupported(CopiedJavaCode.DATA_FLAVOR)
|
||||
) return emptyList()
|
||||
|
||||
val text = content.getTransferData(DataFlavor.stringFlavor) as String
|
||||
return listOf(MyTransferableData(text))
|
||||
}
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
} catch (e: Throwable) {
|
||||
LOG.error(e)
|
||||
}
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
override fun processTransferableData(project: Project, editor: Editor, bounds: RangeMarker, caretOffset: Int, indented: Ref<Boolean>, values: List<TextBlockTransferableData>) {
|
||||
override fun processTransferableData(
|
||||
project: Project,
|
||||
editor: Editor,
|
||||
bounds: RangeMarker,
|
||||
caretOffset: Int,
|
||||
indented: Ref<Boolean>,
|
||||
values: List<TextBlockTransferableData>
|
||||
) {
|
||||
if (DumbService.getInstance(project).isDumb) return
|
||||
if (!KotlinEditorOptions.getInstance().isEnableJavaToKotlinConversion) return //TODO: use another option?
|
||||
|
||||
@@ -100,11 +112,8 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransf
|
||||
val useNewJ2k = checkUseNewJ2k(targetFile)
|
||||
|
||||
val targetModule = targetFile.module
|
||||
|
||||
val pasteTarget = detectPasteTarget(targetFile, bounds.startOffset, bounds.endOffset) ?: return
|
||||
|
||||
val conversionContext = detectConversionContext(pasteTarget.pasteContext, text, project) ?: return
|
||||
|
||||
if (!confirmConvertJavaOnPaste(project, isPlainText = true)) return
|
||||
|
||||
val copiedJavaCode = prepareCopiedJavaCodeByContext(text, conversionContext, pasteTarget)
|
||||
@@ -121,6 +130,7 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransf
|
||||
val importsInsertOffset = targetFile.importList?.endOffset ?: 0
|
||||
if (targetFile.importDirectives.isEmpty() && importsInsertOffset > 0)
|
||||
convertedImportsText = "\n" + convertedImportsText
|
||||
|
||||
if (convertedImportsText.isNotBlank())
|
||||
editor.document.insertString(importsInsertOffset, convertedImportsText)
|
||||
|
||||
@@ -139,9 +149,7 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransf
|
||||
conversionPerformed = true
|
||||
}
|
||||
|
||||
val conversionTime = measureTimeMillis {
|
||||
convert()
|
||||
}
|
||||
val conversionTime = measureTimeMillis { convert() }
|
||||
logJ2kConversionStatistics(
|
||||
ConversionType.TEXT_EXPRESSION,
|
||||
checkUseNewJ2k(targetFile),
|
||||
@@ -151,7 +159,7 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransf
|
||||
)
|
||||
}
|
||||
|
||||
private fun DataForConversion.convertCodeToKotlin(project: Project, targetModule: Module?,useNewJ2k: Boolean): ConversionResult {
|
||||
private fun DataForConversion.convertCodeToKotlin(project: Project, targetModule: Module?, useNewJ2k: Boolean): ConversionResult {
|
||||
return elementsAndTexts.convertCodeToKotlin(project, targetModule, useNewJ2k)
|
||||
}
|
||||
|
||||
@@ -168,7 +176,7 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransf
|
||||
|
||||
val fileText = file.text
|
||||
val dummyDeclarationText = "fun dummy(){}"
|
||||
val newFileText = fileText.substring(0, startOffset) + " " + dummyDeclarationText + "\n" + fileText.substring(endOffset)
|
||||
val newFileText = "${fileText.substring(0, startOffset)} $dummyDeclarationText\n${fileText.substring(endOffset)}"
|
||||
|
||||
val newFile = parseAsFile(newFileText, KotlinFileType.INSTANCE, file.project)
|
||||
(newFile as KtFile).analysisContext = file
|
||||
@@ -183,9 +191,8 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransf
|
||||
private fun detectConversionContext(pasteContext: KotlinContext, text: String, project: Project): JavaContext? {
|
||||
if (isParsedAsKotlinCode(text, pasteContext, project)) return null
|
||||
|
||||
fun JavaContext.check(): JavaContext? {
|
||||
return takeIf { isParsedAsJavaCode(text, it, project) }
|
||||
}
|
||||
fun JavaContext.check(): JavaContext? =
|
||||
takeIf { isParsedAsJavaCode(text, it, project) }
|
||||
|
||||
when (pasteContext) {
|
||||
KotlinContext.TOP_LEVEL -> {
|
||||
@@ -238,19 +245,21 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransf
|
||||
}
|
||||
|
||||
private fun parseAsFile(text: String, fileType: LanguageFileType, project: Project): PsiFile {
|
||||
return PsiFileFactory.getInstance(project).createFileFromText("Dummy." + fileType.defaultExtension, fileType, text, LocalTimeCounter.currentTime(), true)
|
||||
return PsiFileFactory.getInstance(project)
|
||||
.createFileFromText("Dummy." + fileType.defaultExtension, fileType, text, LocalTimeCounter.currentTime(), true)
|
||||
}
|
||||
|
||||
private fun DataForConversion.tryResolveImports(targetFile: KtFile): ElementAndTextList {
|
||||
val importResolver = PlainTextPasteImportResolver(this, targetFile)
|
||||
importResolver.addImportsFromTargetFile()
|
||||
importResolver.tryResolveReferences()
|
||||
return ElementAndTextList(importResolver.addedImports.flatMap { listOf("\n", it) } + "\n\n") //TODO Non-manual formatting for import list
|
||||
return ElementAndTextList(importResolver.addedImports.flatMap { importStatement ->
|
||||
listOf("\n", importStatement)
|
||||
} + "\n\n") //TODO Non-manual formatting for import list
|
||||
}
|
||||
|
||||
private fun prepareCopiedJavaCodeByContext(text: String, context: JavaContext, target: KtElement): CopiedJavaCode {
|
||||
val targetFile = target.containingFile as KtFile
|
||||
|
||||
val (localDeclarations, memberDeclarations) = javaContextDeclarationRenderer.render(target)
|
||||
|
||||
val prefix = buildString {
|
||||
@@ -265,16 +274,25 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransf
|
||||
val classDef = when (context) {
|
||||
JavaContext.TOP_LEVEL -> ""
|
||||
|
||||
JavaContext.CLASS_BODY,
|
||||
JavaContext.IN_BLOCK,
|
||||
JavaContext.EXPRESSION -> {
|
||||
JavaContext.CLASS_BODY, JavaContext.IN_BLOCK, JavaContext.EXPRESSION -> {
|
||||
val lightClass = target.getParentOfType<KtClass>(false)?.toLightClass()
|
||||
|
||||
buildString {
|
||||
append("class ")
|
||||
append(lightClass?.name ?: "Dummy")
|
||||
lightClass?.extendsListTypes?.ifNotEmpty { joinTo(this@buildString, prefix = " extends ") { it.getCanonicalText(true) } }
|
||||
lightClass?.implementsListTypes?.ifNotEmpty { joinTo(this@buildString, prefix = " implements ") { it.getCanonicalText(true) } }
|
||||
lightClass?.extendsListTypes?.ifNotEmpty {
|
||||
joinTo(
|
||||
this@buildString,
|
||||
prefix = " extends "
|
||||
) { it.getCanonicalText(true) }
|
||||
}
|
||||
lightClass?.implementsListTypes?.ifNotEmpty {
|
||||
joinTo(this@buildString, prefix = " implements ") {
|
||||
it.getCanonicalText(
|
||||
true
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -300,6 +318,7 @@ class ConvertTextJavaCopyPasteProcessor : CopyPastePostProcessor<TextBlockTransf
|
||||
}
|
||||
|
||||
companion object {
|
||||
@get:TestOnly var conversionPerformed: Boolean = false
|
||||
@get:TestOnly
|
||||
var conversionPerformed: Boolean = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,11 +34,10 @@ import org.jetbrains.kotlin.types.KotlinType
|
||||
import java.util.*
|
||||
|
||||
|
||||
|
||||
data class DataForConversion private constructor(
|
||||
val elementsAndTexts: ElementAndTextList /* list consisting of PsiElement's to convert and plain String's */,
|
||||
val importsAndPackage: String,
|
||||
val file: PsiJavaFile
|
||||
val elementsAndTexts: ElementAndTextList /* list consisting of PsiElement's to convert and plain String's */,
|
||||
val importsAndPackage: String,
|
||||
val file: PsiJavaFile
|
||||
) {
|
||||
companion object {
|
||||
fun prepare(copiedCode: CopiedJavaCode, project: Project): DataForConversion {
|
||||
@@ -65,7 +64,6 @@ data class DataForConversion private constructor(
|
||||
return DataForConversion(elementsAndTexts, importsAndPackage, file)
|
||||
}
|
||||
|
||||
|
||||
private fun clipTextIfNeeded(file: PsiJavaFile, fileText: String, startOffsets: IntArray, endOffsets: IntArray): String? {
|
||||
val ranges = startOffsets.indices.map { TextRange(startOffsets[it], endOffsets[it]) }.sortedBy { it.start }
|
||||
|
||||
@@ -126,8 +124,7 @@ data class DataForConversion private constructor(
|
||||
val offset = this[i]
|
||||
if (offset >= range.end) {
|
||||
this[i] = offset - range.length
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
assert(offset <= range.start)
|
||||
}
|
||||
}
|
||||
@@ -141,7 +138,10 @@ data class DataForConversion private constructor(
|
||||
}
|
||||
|
||||
private fun PsiElement.maximalParentToClip(range: TextRange): PsiElement? {
|
||||
val firstNotInRange = parentsWithSelf.takeWhile { it !is PsiDirectory }.firstOrNull { it.range !in range } ?: return null
|
||||
val firstNotInRange = parentsWithSelf
|
||||
.takeWhile { it !is PsiDirectory }
|
||||
.firstOrNull { it.range !in range }
|
||||
?: return null
|
||||
return firstNotInRange.parentsWithSelf.lastOrNull { it.minimizedTextRange() in range }
|
||||
}
|
||||
|
||||
@@ -155,41 +155,42 @@ data class DataForConversion private constructor(
|
||||
private fun canDropElementFromText(element: PsiElement): Boolean {
|
||||
return when (element) {
|
||||
is PsiWhiteSpace, is PsiComment, is PsiModifierList, is PsiAnnotation -> true
|
||||
|
||||
is PsiJavaToken -> {
|
||||
when (element.tokenType) {
|
||||
// modifiers
|
||||
JavaTokenType.PUBLIC_KEYWORD, JavaTokenType.PROTECTED_KEYWORD, JavaTokenType.PRIVATE_KEYWORD,
|
||||
JavaTokenType.STATIC_KEYWORD, JavaTokenType.ABSTRACT_KEYWORD, JavaTokenType.FINAL_KEYWORD,
|
||||
JavaTokenType.NATIVE_KEYWORD, JavaTokenType.SYNCHRONIZED_KEYWORD, JavaTokenType.STRICTFP_KEYWORD,
|
||||
JavaTokenType.TRANSIENT_KEYWORD, JavaTokenType.VOLATILE_KEYWORD, JavaTokenType.DEFAULT_KEYWORD -> element.getParent() is PsiModifierList
|
||||
|
||||
JavaTokenType.TRANSIENT_KEYWORD, JavaTokenType.VOLATILE_KEYWORD, JavaTokenType.DEFAULT_KEYWORD ->
|
||||
element.getParent() is PsiModifierList
|
||||
JavaTokenType.SEMICOLON -> true
|
||||
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
is PsiCodeBlock -> element.getParent() is PsiMethod
|
||||
|
||||
else -> element.firstChild == null
|
||||
}
|
||||
}
|
||||
|
||||
private fun tryClipLeftSide(element: PsiElement, leftBound: Int)
|
||||
= tryClipSide(element, leftBound, { range }, { allChildren })
|
||||
private fun tryClipLeftSide(element: PsiElement, leftBound: Int) =
|
||||
tryClipSide(element, leftBound, { range }, { allChildren })
|
||||
|
||||
private fun tryClipRightSide(element: PsiElement, rightBound: Int): Int? {
|
||||
fun Int.transform() = Int.MAX_VALUE - this
|
||||
fun TextRange.transform() = TextRange(end.transform(), start.transform())
|
||||
return tryClipSide(element, rightBound.transform(), { range.transform() }, { lastChild.siblings(forward = false) })?.transform()
|
||||
return tryClipSide(
|
||||
element,
|
||||
rightBound.transform(),
|
||||
{ range.transform() },
|
||||
{ lastChild.siblings(forward = false) }
|
||||
)?.transform()
|
||||
}
|
||||
|
||||
private fun tryClipSide(
|
||||
element: PsiElement,
|
||||
rangeBound: Int,
|
||||
rangeFunction: PsiElement.() -> TextRange,
|
||||
childrenFunction: PsiElement.() -> Sequence<PsiElement>
|
||||
element: PsiElement,
|
||||
rangeBound: Int,
|
||||
rangeFunction: PsiElement.() -> TextRange,
|
||||
childrenFunction: PsiElement.() -> Sequence<PsiElement>
|
||||
): Int? {
|
||||
if (element.firstChild == null) return null
|
||||
|
||||
@@ -202,12 +203,10 @@ data class DataForConversion private constructor(
|
||||
|
||||
if (childRange.start >= rangeBound) { // we have cut enough already
|
||||
break
|
||||
}
|
||||
else if (childRange.end <= rangeBound) { // need to drop the whole element
|
||||
} else if (childRange.end <= rangeBound) { // need to drop the whole element
|
||||
if (!canDropElementFromText(child)) return null
|
||||
clipTo = childRange.end
|
||||
}
|
||||
else { // rangeBound is inside child's range
|
||||
} else { // rangeBound is inside child's range
|
||||
if (child is PsiWhiteSpace) break // no need to cut whitespace - we can leave it as is
|
||||
return tryClipSide(child, rangeBound, rangeFunction, childrenFunction)
|
||||
}
|
||||
@@ -217,15 +216,14 @@ data class DataForConversion private constructor(
|
||||
}
|
||||
|
||||
private fun ElementAndTextList.collectElementsToConvert(
|
||||
file: PsiJavaFile,
|
||||
fileText: String,
|
||||
range: TextRange
|
||||
file: PsiJavaFile,
|
||||
fileText: String,
|
||||
range: TextRange
|
||||
) {
|
||||
val elements = file.elementsInRange(range)
|
||||
if (elements.isEmpty()) {
|
||||
add(fileText.substring(range.start, range.end))
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
add(fileText.substring(range.start, elements.first().range.start))
|
||||
elements.forEach {
|
||||
if (shouldExpandToChildren(it))
|
||||
@@ -243,7 +241,7 @@ data class DataForConversion private constructor(
|
||||
private fun buildImportsAndPackage(sourceFile: PsiJavaFile): String {
|
||||
return buildString {
|
||||
val packageName = sourceFile.packageName
|
||||
if (!packageName.isEmpty()) {
|
||||
if (packageName.isNotEmpty()) {
|
||||
append("package $packageName\n")
|
||||
}
|
||||
|
||||
@@ -253,8 +251,7 @@ data class DataForConversion private constructor(
|
||||
val qualifiedName = import.qualifiedName ?: continue
|
||||
if (import.isOnDemand) {
|
||||
append("import $qualifiedName.*\n")
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
val fqName = FqNameUnsafe(qualifiedName)
|
||||
// skip explicit imports of platform classes mapped into Kotlin classes
|
||||
if (fqName.isSafe && JavaToKotlinClassMap.isJavaPlatformClass(fqName.toSafe())) continue
|
||||
|
||||
@@ -42,10 +42,10 @@ class ElementAndTextList() {
|
||||
fun toList(): List<Any> = elementsAndTexts.toList()
|
||||
|
||||
fun process(processor: ElementsAndTextsProcessor) {
|
||||
elementsAndTexts.forEach {
|
||||
when (it) {
|
||||
is PsiElement -> processor.processElement(it)
|
||||
is String -> processor.processText(it)
|
||||
elementsAndTexts.forEach { element ->
|
||||
when (element) {
|
||||
is PsiElement -> processor.processElement(element)
|
||||
is String -> processor.processText(element)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,6 @@ data class ContextDeclarations(
|
||||
val memberDeclarationsJavaStubs: String
|
||||
)
|
||||
|
||||
|
||||
class JavaContextDeclarationRenderer {
|
||||
private val KtElement.memberDeclarations
|
||||
get() = parents()
|
||||
|
||||
@@ -34,6 +34,7 @@ class KotlinFilePasteProvider : PasteProvider {
|
||||
|
||||
val ktFile = KtPsiFactory(project).createFile(text)
|
||||
val fileName = (ktFile.declarations.firstOrNull()?.name ?: return) + ".kt"
|
||||
@Suppress("UsePropertyAccessSyntax")
|
||||
val directory = ideView.getOrChooseDirectory() ?: return
|
||||
project.executeWriteCommand("Create Kotlin file") {
|
||||
val file = try {
|
||||
@@ -47,9 +48,9 @@ class KotlinFilePasteProvider : PasteProvider {
|
||||
if (document != null) {
|
||||
document.setText(text)
|
||||
documentManager.commitDocument(document)
|
||||
val qualName = JavaDirectoryService.getInstance()?.getPackage(directory)?.qualifiedName
|
||||
if (qualName != null && file is KtFile) {
|
||||
file.packageFqName = FqName(qualName)
|
||||
val qualifiedName = JavaDirectoryService.getInstance()?.getPackage(directory)?.qualifiedName
|
||||
if (qualifiedName != null && file is KtFile) {
|
||||
file.packageFqName = FqName(qualifiedName)
|
||||
}
|
||||
OpenFileDescriptor(project, file.virtualFile).navigate(true)
|
||||
}
|
||||
@@ -64,7 +65,8 @@ class KotlinFilePasteProvider : PasteProvider {
|
||||
//todo: KT-25329, to remove these heuristics
|
||||
if (text.contains(";\n") ||
|
||||
((text.contains("public interface") || text.contains("public class")) &&
|
||||
!text.contains("fun "))) return false //Optimisation for Java. Kotlin doesn't need that...
|
||||
!text.contains("fun "))
|
||||
) return false //Optimisation for Java. Kotlin doesn't need that...
|
||||
val file = KtPsiFactory(project).createFile(text)
|
||||
return !PsiTreeUtil.hasErrorElements(file)
|
||||
}
|
||||
|
||||
+53
-51
@@ -37,8 +37,7 @@ import org.jetbrains.kotlin.psi.KtImportDirective
|
||||
import org.jetbrains.kotlin.psi.psiUtil.referenceExpression
|
||||
import java.util.*
|
||||
|
||||
class PlainTextPasteImportResolver(val dataForConversion: DataForConversion, val targetFile: KtFile) {
|
||||
|
||||
class PlainTextPasteImportResolver(private val dataForConversion: DataForConversion, val targetFile: KtFile) {
|
||||
private val file = dataForConversion.file
|
||||
private val project = targetFile.project
|
||||
|
||||
@@ -59,8 +58,8 @@ class PlainTextPasteImportResolver(val dataForConversion: DataForConversion, val
|
||||
|
||||
private fun canBeImported(descriptor: DeclarationDescriptorWithVisibility?): Boolean {
|
||||
return descriptor != null
|
||||
&& descriptor.canBeReferencedViaImport()
|
||||
&& descriptor.isVisible(targetFile, null, bindingContext, resolutionFacade)
|
||||
&& descriptor.canBeReferencedViaImport()
|
||||
&& descriptor.isVisible(targetFile, null, bindingContext, resolutionFacade)
|
||||
}
|
||||
|
||||
private fun addImport(importStatement: PsiImportStatementBase, shouldAddToTarget: Boolean = false) {
|
||||
@@ -76,46 +75,54 @@ class PlainTextPasteImportResolver(val dataForConversion: DataForConversion, val
|
||||
val importedReference = importDirective.importedReference
|
||||
if (importPath != null && !importPath.hasAlias() && importedReference is KtDotQualifiedExpression) {
|
||||
val receiver = importedReference
|
||||
.receiverExpression
|
||||
.referenceExpression()
|
||||
?.mainReference
|
||||
?.resolve()
|
||||
.receiverExpression
|
||||
.referenceExpression()
|
||||
?.mainReference
|
||||
?.resolve()
|
||||
val selector = importedReference
|
||||
.selectorExpression
|
||||
?.referenceExpression()
|
||||
?.mainReference
|
||||
?.resolve()
|
||||
.selectorExpression
|
||||
?.referenceExpression()
|
||||
?.mainReference
|
||||
?.resolve()
|
||||
|
||||
val isPackageReceiver = receiver is PsiPackage
|
||||
val isClassReceiver = receiver is PsiClass
|
||||
val isClassSelector = selector is PsiClass
|
||||
|
||||
if (importPath.isAllUnder) {
|
||||
if (isClassReceiver)
|
||||
addImport(psiElementFactory.createImportStaticStatement(receiver as PsiClass, "*"))
|
||||
else if (isPackageReceiver)
|
||||
addImport(psiElementFactory.createImportStatementOnDemand((receiver as PsiPackage).qualifiedName))
|
||||
}
|
||||
else {
|
||||
if (isClassSelector)
|
||||
addImport(psiElementFactory.createImportStatement(selector as PsiClass))
|
||||
else if (isClassReceiver)
|
||||
addImport(psiElementFactory.createImportStaticStatement(receiver as PsiClass, importPath.importedName!!.asString()))
|
||||
when {
|
||||
isClassReceiver ->
|
||||
addImport(psiElementFactory.createImportStaticStatement(receiver as PsiClass, "*"))
|
||||
isPackageReceiver ->
|
||||
addImport(psiElementFactory.createImportStatementOnDemand((receiver as PsiPackage).qualifiedName))
|
||||
}
|
||||
} else {
|
||||
when {
|
||||
isClassSelector ->
|
||||
addImport(psiElementFactory.createImportStatement(selector as PsiClass))
|
||||
isClassReceiver ->
|
||||
addImport(
|
||||
psiElementFactory.createImportStaticStatement(
|
||||
receiver as PsiClass,
|
||||
importPath.importedName!!.asString()
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (importList !in dataForConversion.elementsAndTexts.toList())
|
||||
if (importList !in dataForConversion.elementsAndTexts.toList()) {
|
||||
runWriteAction {
|
||||
targetFile.importDirectives.forEach(::tryConvertKotlinImport)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun tryResolveReferences() {
|
||||
|
||||
val elementsWithUnresolvedRef = PsiTreeUtil.collectElements(file) {
|
||||
it.reference != null
|
||||
&& it.reference is PsiQualifiedReference
|
||||
&& it.reference?.resolve() == null
|
||||
val elementsWithUnresolvedRef = PsiTreeUtil.collectElements(file) { element ->
|
||||
element.reference != null
|
||||
&& element.reference is PsiQualifiedReference
|
||||
&& element.reference?.resolve() == null
|
||||
}
|
||||
|
||||
fun tryResolveReference(reference: PsiQualifiedReference): Boolean {
|
||||
@@ -123,28 +130,27 @@ class PlainTextPasteImportResolver(val dataForConversion: DataForConversion, val
|
||||
val referenceName = reference.referenceName ?: return false
|
||||
if (referenceName in failedToResolveReferenceNames) return false
|
||||
val classes = shortNameCache.getClassesByName(referenceName, scope)
|
||||
.mapNotNull { psiClass ->
|
||||
val containingFile = psiClass.containingFile
|
||||
if (ProjectRootsUtil.isInProjectOrLibraryContent(containingFile)) {
|
||||
psiClass to psiClass.getJavaMemberDescriptor() as? ClassDescriptor
|
||||
}
|
||||
else {
|
||||
null
|
||||
}
|
||||
}
|
||||
.filter { canBeImported(it.second) }
|
||||
.mapNotNull { psiClass ->
|
||||
val containingFile = psiClass.containingFile
|
||||
if (ProjectRootsUtil.isInProjectOrLibraryContent(containingFile)) {
|
||||
psiClass to psiClass.getJavaMemberDescriptor() as? ClassDescriptor
|
||||
} else null
|
||||
}.filter { canBeImported(it.second) }
|
||||
|
||||
classes.find { (_, descriptor) -> JavaToKotlinClassMap.mapPlatformClass(descriptor!!).isNotEmpty() }
|
||||
?.let { (psiClass, _) -> addImport(psiElementFactory.createImportStatement(psiClass)) }
|
||||
classes.find { (_, descriptor) ->
|
||||
JavaToKotlinClassMap.mapPlatformClass(descriptor!!).isNotEmpty()
|
||||
}?.let { (psiClass, _) ->
|
||||
addImport(psiElementFactory.createImportStatement(psiClass))
|
||||
}
|
||||
if (reference.resolve() != null) return true
|
||||
|
||||
classes.singleOrNull()?.let { (psiClass, _) ->
|
||||
addImport(psiElementFactory.createImportStatement(psiClass), true)
|
||||
}
|
||||
|
||||
if (reference.resolve() != null) return true
|
||||
else {
|
||||
if (classes.isNotEmpty()) {
|
||||
when {
|
||||
reference.resolve() != null -> return true
|
||||
classes.isNotEmpty() -> {
|
||||
ambiguityInResolution = true
|
||||
return false
|
||||
}
|
||||
@@ -163,14 +169,10 @@ class PlainTextPasteImportResolver(val dataForConversion: DataForConversion, val
|
||||
addImport(psiElementFactory.createImportStaticStatement(psiMember.containingClass!!, psiMember.name!!), true)
|
||||
}
|
||||
|
||||
if (reference.resolve() != null) return false
|
||||
else {
|
||||
if (members.isNotEmpty()) {
|
||||
ambiguityInResolution = true
|
||||
}
|
||||
else {
|
||||
couldNotResolve = true
|
||||
}
|
||||
when {
|
||||
reference.resolve() != null -> return false
|
||||
members.isNotEmpty() -> ambiguityInResolution = true
|
||||
else -> couldNotResolve = true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user