All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.intellij.json.editor.lineMover.JsonLineMover Maven / Gradle / Ivy

package com.intellij.json.editor.lineMover;

import com.intellij.codeInsight.editorActions.moveUpDown.LineMover;
import com.intellij.codeInsight.editorActions.moveUpDown.LineRange;
import com.intellij.json.JsonElementTypes;
import com.intellij.json.psi.*;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiComment;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.impl.source.tree.TreeUtil;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

/**
 * @author Mikhail Golubev
 */
public class JsonLineMover extends LineMover {

  private boolean myShouldAddComma = false;

  @Override
  public boolean checkAvailable(@NotNull Editor editor, @NotNull PsiFile file, @NotNull MoveInfo info, boolean down) {
    myShouldAddComma = false;

    if (!(file instanceof JsonFile) || !super.checkAvailable(editor, file, info, down)) {
      return false;
    }

    Pair movedElementRange = getElementRange(editor, file, info.toMove);
    if (!isValidElementRange(movedElementRange)) {
      return false;
    }

    // Tweak range to move if it's necessary
    movedElementRange = expandCommentsInRange(movedElementRange);
    info.toMove = new LineRange(movedElementRange.getFirst(), movedElementRange.getSecond());

    // 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 (movedElementRange.getFirst() instanceof PsiComment && movedElementRange.getSecond() instanceof PsiComment) {
      return true;
    }

    // Check whether additional comma is needed
    final Pair destElementRange = getElementRange(editor, file, info.toMove2);
    if (isValidElementRange(destElementRange) && movedElementRange.getFirst().getParent() == destElementRange.getSecond().getParent()) {
      final PsiElement commonParent = movedElementRange.getFirst().getParent();
      final PsiElement lowerRightElement = down ? destElementRange.getSecond() : movedElementRange.getSecond();
      // Destination rightmost element is not closing brace or bracket
      if (lowerRightElement instanceof JsonElement) {
        if (commonParent instanceof JsonArray && notFollowedByNextElementOrComma(lowerRightElement, JsonValue.class) ||
            commonParent instanceof JsonObject && notFollowedByNextElementOrComma(lowerRightElement, JsonProperty.class)) {
          myShouldAddComma = true;
        }
      }
    }
    return true;
  }

  @NotNull
  private Pair expandCommentsInRange(@NotNull Pair 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) {
    if (myShouldAddComma) {
      final Document document = editor.getDocument();
      final int lineBelow = down ? info.toMove2.endLine - 1 : info.toMove.endLine - 1;
      document.insertString(document.getLineEndOffset(lineBelow), ",");

      final int lineAbove = down ? info.toMove.endLine - 1 : info.toMove2.endLine - 1;
      final int lineAboveEndOffset = document.getLineEndOffset(lineAbove);
      final String aboveLineEnding = document.getText(new TextRange(lineAboveEndOffset - 1, lineAboveEndOffset));
      if (aboveLineEnding.equals(",")) {
        document.deleteString(lineAboveEndOffset - 1, lineAboveEndOffset);
      }
      final Project project = editor.getProject();
      assert project != null;
      PsiDocumentManager.getInstance(project).commitDocument(document);
    }
  }

  private boolean notFollowedByNextElementOrComma(@NotNull PsiElement anchor, @NotNull Class nextElementType) {
    return PsiTreeUtil.getNextSiblingOfType(anchor, nextElementType) == null &&
           TreeUtil.findSibling(anchor.getNode(), JsonElementTypes.COMMA) == null;
  }

  private boolean isValidElementRange(@Nullable Pair elementRange) {
    if (elementRange == null) {
      return false;
    }
    return elementRange.getFirst().getParent() == elementRange.getSecond().getParent();
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy