/*
* 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.codeInsight.upDownMover;
import com.intellij.codeInsight.editorActions.moveUpDown.LineMover;
import com.intellij.codeInsight.editorActions.moveUpDown.LineRange;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.kotlin.idea.refactoring.KotlinRefactoringUtilKt;
import org.jetbrains.kotlin.psi.KtBlockExpression;
import org.jetbrains.kotlin.psi.KtFile;
import org.jetbrains.kotlin.psi.KtFunctionLiteral;
public abstract class AbstractKotlinUpDownMover extends LineMover {
protected AbstractKotlinUpDownMover() {
}
protected abstract boolean checkSourceElement(@NotNull PsiElement element);
protected abstract LineRange getElementSourceLineRange(
@NotNull PsiElement element,
@NotNull Editor editor,
@NotNull LineRange oldRange
);
@Nullable
protected LineRange getSourceRange(
@NotNull PsiElement firstElement,
@NotNull PsiElement lastElement,
@NotNull Editor editor,
LineRange oldRange
) {
PsiElement parent = PsiTreeUtil.findCommonParent(firstElement, lastElement);
int topExtension = 0;
int bottomExtension = 0;
if (parent instanceof KtFunctionLiteral) {
KtBlockExpression block = ((KtFunctionLiteral) parent).getBodyExpression();
if (block != null) {
PsiElement comment = null;
boolean extendDown = false;
if (checkCommentAtBlockBound(firstElement, lastElement, block)) {
comment = lastElement;
extendDown = true;
lastElement = block.getLastChild();
}
else if (checkCommentAtBlockBound(lastElement, firstElement, block)) {
comment = firstElement;
firstElement = block.getFirstChild();
}
if (comment != null) {
int extension = KotlinRefactoringUtilKt.getLineCount(comment);
if (extendDown) {
bottomExtension = extension;
}
else {
topExtension = extension;
}
}
parent = PsiTreeUtil.findCommonParent(firstElement, lastElement);
}
}
if (parent == null) return null;
Pair<PsiElement, PsiElement> originalRange = getElementRange(parent, firstElement, lastElement);
if (!checkSourceElement(originalRange.first) || !checkSourceElement(originalRange.second)) return null;
LineRange lineRange1 = getElementSourceLineRange(originalRange.first, editor, oldRange);
if (lineRange1 == null) return null;
LineRange lineRange2 = getElementSourceLineRange(originalRange.second, editor, oldRange);
if (lineRange2 == null) return null;
LineRange parentLineRange = getElementSourceLineRange(parent, editor, oldRange);
LineRange sourceRange = new LineRange(lineRange1.startLine - topExtension, lineRange2.endLine + bottomExtension);
if (parentLineRange != null
&& sourceRange.startLine == parentLineRange.startLine
&& sourceRange.endLine == parentLineRange.endLine
) {
sourceRange.firstElement = sourceRange.lastElement = parent;
}
else {
sourceRange.firstElement = originalRange.first;
sourceRange.lastElement = originalRange.second;
}
return sourceRange;
}
protected static int getElementLine(PsiElement element, Editor editor, boolean first) {
if (element == null) return -1;
Document doc = editor.getDocument();
TextRange spaceRange = element.getTextRange();
return first ? doc.getLineNumber(spaceRange.getStartOffset()) : doc.getLineNumber(spaceRange.getEndOffset());
}
protected static PsiElement getLastNonWhiteSiblingInLine(@Nullable PsiElement element, @NotNull Editor editor, boolean down) {
if (element == null) return null;
int line = getElementLine(element, editor, down);
PsiElement lastElement = element;
while (true) {
if (lastElement == null) return null;
PsiElement sibling = firstNonWhiteSibling(lastElement, down);
if (getElementLine(sibling, editor, down) == line) {
lastElement = sibling;
}
else break;
}
return lastElement;
}
private static boolean checkCommentAtBlockBound(PsiElement blockElement, PsiElement comment, KtBlockExpression block) {
return PsiTreeUtil.isAncestor(block, blockElement, true) && comment instanceof PsiComment;
}
@Nullable
protected static PsiElement getSiblingOfType(@NotNull PsiElement element, boolean down, @NotNull Class<? extends PsiElement> type) {
return down ? PsiTreeUtil.getNextSiblingOfType(element, type) : PsiTreeUtil.getPrevSiblingOfType(element, type);
}
@Nullable
protected static PsiElement firstNonWhiteSibling(@NotNull LineRange lineRange, boolean down) {
return firstNonWhiteElement(down ? lineRange.lastElement.getNextSibling() : lineRange.firstElement.getPrevSibling(), down);
}
@Nullable
protected static PsiElement firstNonWhiteSibling(@NotNull PsiElement element, boolean down) {
return firstNonWhiteElement(down ? element.getNextSibling() : element.getPrevSibling(), down);
}
@Override
public boolean checkAvailable(@NotNull Editor editor, @NotNull PsiFile file, @NotNull MoveInfo info, boolean down) {
return (file instanceof KtFile) && super.checkAvailable(editor, file, info, down);
}
}