More correct and easy comment restoring range management

This commit is contained in:
Valentin Kipyatkov
2016-04-21 19:52:31 +03:00
parent 27063bcd9b
commit a07bbf4aa8
4 changed files with 98 additions and 33 deletions
@@ -27,9 +27,7 @@ abstract class ReplaceLoopResultTransformation(override val loop: KtForExpressio
override val commentSavingRange = PsiChildRange.singleElement(loop.unwrapIfLabeled())
override fun commentRestoringRange(convertLoopResult: KtExpression) = PsiChildRange.singleElement(convertLoopResult)
override fun convertLoop(resultCallChain: KtExpression): KtExpression {
override fun convertLoop(resultCallChain: KtExpression, commentSavingRangeHolder: CommentSavingRangeHolder): KtExpression {
return loop.unwrapIfLabeled().replaced(resultCallChain)
}
}
@@ -41,17 +39,13 @@ abstract class AssignToVariableResultTransformation(
override val commentSavingRange = PsiChildRange(initialization.initializationStatement, loop.unwrapIfLabeled())
//TODO: does not work in case of move
private val commentRestoringRange = commentSavingRange.withoutLastStatement()
override fun commentRestoringRange(convertLoopResult: KtExpression) = commentRestoringRange
override fun convertLoop(resultCallChain: KtExpression): KtExpression {
override fun convertLoop(resultCallChain: KtExpression, commentSavingRangeHolder: CommentSavingRangeHolder): KtExpression {
initialization.initializer.replace(resultCallChain)
val previousStatement = loop.unwrapIfLabeled().previousStatement()
val loopUnwrapped = loop.unwrapIfLabeled()
val previousStatement = loopUnwrapped.previousStatement()
loop.deleteWithLabels()
loopUnwrapped.delete()
if (initialization.variable.isVar && !initialization.variable.hasWriteUsages()) { // change variable to 'val' if possible
initialization.variable.valOrVarKeyword.replace(KtPsiFactory(initialization.variable).createValKeyword())
@@ -64,6 +58,9 @@ abstract class AssignToVariableResultTransformation(
assert(block is KtBlockExpression)
val movedInitializationStatement = block.addAfter(initializationStatement, previousStatement) as KtExpression
block.addAfter(KtPsiFactory(block).createNewLine(), previousStatement)
commentSavingRangeHolder.remove(initializationStatement)
initializationStatement.delete()
initializationStatement = movedInitializationStatement
}
@@ -16,10 +16,14 @@
package org.jetbrains.kotlin.idea.intentions.loopToCallChain
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiWhiteSpace
import org.jetbrains.kotlin.psi.KtCallableDeclaration
import org.jetbrains.kotlin.psi.KtExpression
import org.jetbrains.kotlin.psi.KtForExpression
import org.jetbrains.kotlin.psi.psiUtil.PsiChildRange
import org.jetbrains.kotlin.psi.psiUtil.parentsWithSelf
import org.jetbrains.kotlin.psi.psiUtil.siblings
/**
* An abstraction for generating a chained call that knows about receiver expression and handles proper formatting
@@ -73,9 +77,12 @@ interface ResultTransformation : Transformation {
fun mergeWithPrevious(previousTransformation: SequenceTransformation): ResultTransformation? = null
val commentSavingRange: PsiChildRange
fun commentRestoringRange(convertLoopResult: KtExpression): PsiChildRange
fun convertLoop(resultCallChain: KtExpression): KtExpression
/**
* Implementations of this method are obliged to update [commentSavingRangeHolder] when deleting or adding any element into the tree
* except for the loop itself and the result element returned from this method
*/
fun convertLoop(resultCallChain: KtExpression, commentSavingRangeHolder: CommentSavingRangeHolder): KtExpression
}
/**
@@ -124,3 +131,68 @@ class ResultTransformationMatch(
constructor(resultTransformation: ResultTransformation, vararg sequenceTransformations: SequenceTransformation)
: this(resultTransformation, sequenceTransformations.asList())
}
/**
* Helper class for holding and updating PsiChildRange to be used for [CommentSaver.restore] call
*/
class CommentSavingRangeHolder(range: PsiChildRange) {
var range = range
private set
/**
* Call this method when a new element to be included into the range is added into the tree
*/
fun add(element: PsiElement) {
if (range.isEmpty) {
range = PsiChildRange.singleElement(element)
return
}
val rangeParent = range.first!!.parent
val elementToAdd = element.parentsWithSelf.takeWhile { it != rangeParent }.last()
when (elementToAdd) {
in range -> return
in range.first!!.siblingsBefore() -> range = PsiChildRange(elementToAdd, range.last)
else -> range = PsiChildRange(range.first, elementToAdd)
}
}
/**
* Call this method before deletion of any element which can belong to the range
*/
fun remove(element: PsiElement) {
when {
range.isEmpty -> return
element == range.first -> {
val newFirst = element
.siblings(forward = true, withItself = false)
.takeWhile { it != range.last!!.nextSibling }
.firstOrNull { it !is PsiWhiteSpace }
if (newFirst != null) {
range = PsiChildRange(newFirst, range.last)
}
else {
range = PsiChildRange.EMPTY
}
}
element == range.last -> {
val newLast = element
.siblings(forward = false, withItself = false)
.takeWhile { it != range.first!!.prevSibling }
.firstOrNull { it !is PsiWhiteSpace }
if (newLast != null) {
range = PsiChildRange(range.first, newLast)
}
else {
range = PsiChildRange.EMPTY
}
}
}
}
private fun PsiElement.siblingsBefore() = if (prevSibling != null) PsiChildRange(parent.firstChild, prevSibling) else PsiChildRange.EMPTY
}
@@ -17,8 +17,6 @@
package org.jetbrains.kotlin.idea.intentions.loopToCallChain
import com.intellij.openapi.util.Key
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.codeStyle.CodeStyleManager
import org.jetbrains.kotlin.idea.analysis.analyzeInContext
import org.jetbrains.kotlin.idea.caches.resolve.analyze
import org.jetbrains.kotlin.idea.caches.resolve.getResolutionFacade
@@ -140,24 +138,26 @@ fun match(loop: KtForExpression): MatchResult? {
//TODO: offer to use of .asSequence() as an option
fun convertLoop(loop: KtForExpression, matchResult: MatchResult): KtExpression {
val resultTransformation = matchResult.transformationMatch.resultTransformation
val commentSaver = CommentSaver(resultTransformation.commentSavingRange)
val commentSavingRange = resultTransformation.commentSavingRange
val commentSaver = CommentSaver(commentSavingRange)
val commentSavingRangeHolder = CommentSavingRangeHolder(commentSavingRange)
matchResult.initializationStatementsToDelete.forEach { commentSavingRangeHolder.add(it) }
val callChain = matchResult.generateCallChain(loop)
val result = resultTransformation.convertLoop(callChain)
commentSavingRangeHolder.remove(loop.unwrapIfLabeled()) // loop will be deleted in all cases
val result = resultTransformation.convertLoop(callChain, commentSavingRangeHolder)
commentSavingRangeHolder.add(result)
//TODO: preserve comments?
matchResult.initializationStatementsToDelete.forEach { it.delete() }
for (statement in matchResult.initializationStatementsToDelete) {
commentSavingRangeHolder.remove(statement)
statement.delete()
}
commentSaver.restore(resultTransformation.commentRestoringRange(result))
// need to manually adjust indent of the result because in some cases it's made incorrect when moving closer to the loop
// TODO: use forceAdjustIndent = true of CommentSaver.restore
val file = result.containingFile
val psiDocumentManager = PsiDocumentManager.getInstance(file.project)
psiDocumentManager.doPostponedOperationsAndUnblockDocument(psiDocumentManager.getDocument(file)!!)
val codeStyleManager = CodeStyleManager.getInstance(file.project)
codeStyleManager.adjustLineIndent(file, result.textRange)
// we need to adjust indent of the result because in some cases it's made incorrect when moving statement closer to the loop
commentSaver.restore(commentSavingRangeHolder.range, forceAdjustIndent = true)
return result
}
@@ -38,10 +38,6 @@ class FindAndReturnTransformation(
override val commentSavingRange = PsiChildRange(loop.unwrapIfLabeled(), endReturn)
private val commentRestoringRange = commentSavingRange.withoutFirstStatement()
override fun commentRestoringRange(convertLoopResult: KtExpression) = commentRestoringRange
override val presentation: String
get() = generator.functionName + (if (filter != null) "{}" else "()")
@@ -55,7 +51,7 @@ class FindAndReturnTransformation(
return generator.generate(chainedCallGenerator, filter)
}
override fun convertLoop(resultCallChain: KtExpression): KtExpression {
override fun convertLoop(resultCallChain: KtExpression, commentSavingRangeHolder: CommentSavingRangeHolder): KtExpression {
endReturn.returnedExpression!!.replace(resultCallChain)
loop.deleteWithLabels()
return endReturn