summaryrefslogtreecommitdiff
path: root/json/src/com/intellij/json/editor/lineMover/JsonLineMover.java
diff options
context:
space:
mode:
Diffstat (limited to 'json/src/com/intellij/json/editor/lineMover/JsonLineMover.java')
-rw-r--r--json/src/com/intellij/json/editor/lineMover/JsonLineMover.java197
1 files changed, 197 insertions, 0 deletions
diff --git a/json/src/com/intellij/json/editor/lineMover/JsonLineMover.java b/json/src/com/intellij/json/editor/lineMover/JsonLineMover.java
new file mode 100644
index 00000000..ff82d1c8
--- /dev/null
+++ b/json/src/com/intellij/json/editor/lineMover/JsonLineMover.java
@@ -0,0 +1,197 @@
+package com.intellij.json.editor.lineMover;
+
+import com.intellij.codeInsight.editorActions.moveUpDown.LineMover;
+import com.intellij.codeInsight.editorActions.moveUpDown.LineRange;
+import com.intellij.json.psi.*;
+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.PsiWhiteSpace;
+import com.intellij.psi.util.PsiTreeUtil;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+public class JsonLineMover extends LineMover {
+ private enum Direction {
+ Same,
+ Inside,
+ Outside
+ }
+
+ private Direction myDirection = Direction.Same;
+
+ @Override
+ public boolean checkAvailable(@NotNull Editor editor, @NotNull PsiFile file, @NotNull MoveInfo info, boolean down) {
+ myDirection = Direction.Same;
+
+ if (!(file instanceof JsonFile) || !super.checkAvailable(editor, file, info, down)) {
+ return false;
+ }
+
+ Pair<PsiElement, PsiElement> movedElementRange = getElementRange(editor, file, info.toMove);
+ if (!isValidElementRange(movedElementRange)) {
+ return false;
+ }
+
+ // Tweak range to move if it's necessary
+ movedElementRange = expandCommentsInRange(movedElementRange);
+
+ PsiElement movedSecond = movedElementRange.getSecond();
+ PsiElement movedFirst = movedElementRange.getFirst();
+
+ info.toMove = new LineRange(movedFirst, movedSecond);
+
+ // Adjust destination range to prevent illegal offsets
+ final int lineCount = editor.getDocument().getLineCount();
+ if (down) {
+ info.toMove2 = new LineRange(info.toMove.endLine, Math.min(info.toMove.endLine + 1, lineCount));
+ }
+ else {
+ info.toMove2 = new LineRange(Math.max(info.toMove.startLine - 1, 0), info.toMove.startLine);
+ }
+
+ if (movedFirst instanceof PsiComment && movedSecond instanceof PsiComment) {
+ return true;
+ }
+
+ // Check whether additional comma is needed
+ final Pair<PsiElement, PsiElement> destElementRange = getElementRange(editor, file, info.toMove2);
+
+ if (destElementRange != null) {
+ PsiElement destFirst = destElementRange.getFirst();
+ PsiElement destSecond = destElementRange.getSecond();
+
+ if (destFirst == destSecond && !(destFirst instanceof JsonProperty) && !(destFirst instanceof JsonValue)) {
+ PsiElement parent = destFirst.getParent();
+ if (((JsonFile)parent.getContainingFile()).getTopLevelValue() == parent) {
+ info.prohibitMove();
+ return true;
+ }
+ }
+
+ PsiElement firstParent = destFirst.getParent();
+ PsiElement secondParent = destSecond.getParent();
+
+ JsonValue firstParentParent = PsiTreeUtil.getParentOfType(firstParent, JsonObject.class, JsonArray.class);
+ if (firstParentParent == secondParent) {
+ myDirection = down ? Direction.Outside : Direction.Inside;
+ }
+ JsonValue secondParentParent = PsiTreeUtil.getParentOfType(secondParent, JsonObject.class, JsonArray.class);
+ if (firstParent == secondParentParent) {
+ myDirection = down ? Direction.Inside : Direction.Outside;
+ }
+ }
+ return true;
+ }
+
+ @NotNull
+ private static Pair<PsiElement, PsiElement> expandCommentsInRange(@NotNull Pair<PsiElement, PsiElement> range) {
+ final PsiElement upper = JsonPsiUtil.findFurthestSiblingOfSameType(range.getFirst(), false);
+ final PsiElement lower = JsonPsiUtil.findFurthestSiblingOfSameType(range.getSecond(), true);
+ return Pair.create(upper, lower);
+ }
+
+ @Override
+ public void beforeMove(@NotNull Editor editor, @NotNull MoveInfo info, boolean down) {
+
+ }
+
+ @Override
+ public void afterMove(@NotNull Editor editor, @NotNull PsiFile file, @NotNull MoveInfo info, boolean down) {
+ int diff = (info.toMove.endLine - info.toMove.startLine) - (info.toMove2.endLine - info.toMove2.startLine);
+ switch (myDirection) {
+ case Same:
+ addCommaIfNeeded(editor.getDocument(), down ? info.toMove.endLine - 1 - diff : info.toMove2.endLine - 1 + diff);
+ trimCommaIfNeeded(editor.getDocument(), file, down ? info.toMove.endLine : info.toMove2.endLine + diff);
+ break;
+ case Inside:
+ if (!down) {
+ addCommaIfNeeded(editor.getDocument(), info.toMove2.startLine - 1);
+ }
+ trimCommaIfNeeded(editor.getDocument(), file, down ? info.toMove.startLine : info.toMove2.startLine);
+ trimCommaIfNeeded(editor.getDocument(), file, down ? info.toMove.endLine : info.toMove2.endLine + diff);
+ break;
+ case Outside:
+ addCommaIfNeeded(editor.getDocument(), down ? info.toMove.startLine : info.toMove2.startLine);
+ trimCommaIfNeeded(editor.getDocument(), file, down ? info.toMove.endLine : info.toMove2.endLine + diff);
+ if (down) {
+ trimCommaIfNeeded(editor.getDocument(), file, info.toMove.startLine - 1);
+ addCommaIfNeeded(editor.getDocument(), info.toMove.endLine);
+ trimCommaIfNeeded(editor.getDocument(), file, info.toMove.endLine);
+ }
+ break;
+ }
+ }
+
+ private static int getForwardLineNumber(Document document, PsiElement element) {
+ while (element instanceof PsiWhiteSpace || element instanceof PsiComment) {
+ element = element.getNextSibling();
+ }
+ if (element == null) return -1;
+
+ TextRange range = element.getTextRange();
+ return document.getLineNumber(range.getEndOffset());
+ }
+
+ private static int getBackwardLineNumber(Document document, PsiElement element) {
+ while (element instanceof PsiWhiteSpace || element instanceof PsiComment) {
+ element = element.getPrevSibling();
+ }
+ if (element == null) return -1;
+
+ TextRange range = element.getTextRange();
+ return document.getLineNumber(range.getEndOffset());
+ }
+
+ private static void trimCommaIfNeeded(Document document, PsiFile file, int line) {
+ int offset = document.getLineEndOffset(line);
+ if (doTrimComma(document, offset + 1, offset)) return;
+
+ PsiElement element = file.findElementAt(offset - 1);
+ int forward = getForwardLineNumber(document, element);
+ int backward = getBackwardLineNumber(document, element);
+ if (forward < 0 || backward < 0) return;
+ doTrimComma(document, document.getLineEndOffset(forward) - 1, document.getLineEndOffset(backward));
+ }
+
+ private static boolean doTrimComma(Document document, int forwardOffset, int backwardOffset) {
+ CharSequence charSequence = document.getCharsSequence();
+ if (backwardOffset <= 0) return true;
+ if (charSequence.charAt(backwardOffset - 1) == ',') {
+ int offsetAfter = skipWhitespaces(charSequence, forwardOffset);
+ if (offsetAfter >= charSequence.length()) return true;
+ char ch = charSequence.charAt(offsetAfter);
+
+ if (ch == ']' || ch == '}') {
+ document.deleteString(backwardOffset - 1, backwardOffset);
+ }
+ if (ch != '/') return true;
+ }
+ return false;
+ }
+
+ private static int skipWhitespaces(CharSequence charSequence, int offset2) {
+ while (offset2 < charSequence.length() && Character.isWhitespace(charSequence.charAt(offset2))) {
+ offset2++;
+ }
+ return offset2;
+ }
+
+ private static void addCommaIfNeeded(Document document, int line) {
+ int offset = document.getLineEndOffset(line);
+ if (offset > 0 && document.getCharsSequence().charAt(offset - 1) != ',') {
+ document.insertString(offset, ",");
+ }
+ }
+
+ private static boolean isValidElementRange(@Nullable Pair<PsiElement, PsiElement> elementRange) {
+ if (elementRange == null) {
+ return false;
+ }
+ return elementRange.getFirst().getParent() == elementRange.getSecond().getParent();
+ }
+}