@Override
 protected void moveOffsetAfter(boolean success) {
   if (getLocalVariable() != null && getLocalVariable().isValid()) {
     myEditor.getCaretModel().moveToOffset(getLocalVariable().getTextOffset());
     myEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
   } else if (getExprMarker() != null) {
     final RangeMarker exprMarker = getExprMarker();
     if (exprMarker.isValid()) {
       myEditor.getCaretModel().moveToOffset(exprMarker.getStartOffset());
       myEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
     }
   }
   super.moveOffsetAfter(success);
   if (myLocalMarker != null && !isRestart()) {
     myLocalMarker.dispose();
   }
   if (success) {
     performPostIntroduceTasks();
     final String refactoringId = getRefactoringId();
     if (refactoringId != null) {
       final RefactoringEventData afterData = new RefactoringEventData();
       afterData.addElement(getVariable());
       myProject
           .getMessageBus()
           .syncPublisher(RefactoringEventListener.REFACTORING_EVENT_TOPIC)
           .refactoringDone(refactoringId, afterData);
     }
   }
 }
 public TextRange surroundExpression(
     final Project project, final Editor editor, PsiExpression expr)
     throws IncorrectOperationException {
   assert expr.isValid();
   PsiType[] types = GuessManager.getInstance(project).guessTypeToCast(expr);
   final boolean parenthesesNeeded =
       expr instanceof PsiPolyadicExpression
           || expr instanceof PsiConditionalExpression
           || expr instanceof PsiAssignmentExpression;
   String exprText = parenthesesNeeded ? "(" + expr.getText() + ")" : expr.getText();
   final Template template = generateTemplate(project, exprText, types);
   TextRange range;
   if (expr.isPhysical()) {
     range = expr.getTextRange();
   } else {
     final RangeMarker rangeMarker = expr.getUserData(ElementToWorkOn.TEXT_RANGE);
     if (rangeMarker == null) return null;
     range = new TextRange(rangeMarker.getStartOffset(), rangeMarker.getEndOffset());
   }
   editor.getDocument().deleteString(range.getStartOffset(), range.getEndOffset());
   editor.getCaretModel().moveToOffset(range.getStartOffset());
   editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
   TemplateManager.getInstance(project).startTemplate(editor, template);
   return null;
 }
  protected void restoreState(@NotNull final V psiField) {
    if (!ReadonlyStatusHandler.ensureDocumentWritable(
        myProject, InjectedLanguageUtil.getTopLevelEditor(myEditor).getDocument())) return;
    ApplicationManager.getApplication()
        .runWriteAction(
            () -> {
              final PsiFile containingFile = psiField.getContainingFile();
              final RangeMarker exprMarker = getExprMarker();
              if (exprMarker != null) {
                myExpr = restoreExpression(containingFile, psiField, exprMarker, myExprText);
              }

              if (myLocalMarker != null) {
                final PsiElement refVariableElement =
                    containingFile.findElementAt(myLocalMarker.getStartOffset());
                if (refVariableElement != null) {
                  final PsiElement parent = refVariableElement.getParent();
                  if (parent instanceof PsiNamedElement) {
                    ((PsiNamedElement) parent).setName(myLocalName);
                  }
                }

                final V localVariable = getLocalVariable();
                if (localVariable != null && localVariable.isPhysical()) {
                  myLocalVariable = localVariable;
                  final PsiElement nameIdentifier = localVariable.getNameIdentifier();
                  if (nameIdentifier != null) {
                    myLocalMarker = createMarker(nameIdentifier);
                  }
                }
              }
              final List<RangeMarker> occurrenceMarkers = getOccurrenceMarkers();
              for (int i = 0, occurrenceMarkersSize = occurrenceMarkers.size();
                  i < occurrenceMarkersSize;
                  i++) {
                RangeMarker marker = occurrenceMarkers.get(i);
                if (getExprMarker() != null
                    && marker.getStartOffset() == getExprMarker().getStartOffset()
                    && myExpr != null) {
                  myOccurrences[i] = myExpr;
                  continue;
                }
                final E psiExpression =
                    restoreExpression(
                        containingFile,
                        psiField,
                        marker,
                        getLocalVariable() != null ? myLocalName : myExprText);
                if (psiExpression != null) {
                  myOccurrences[i] = psiExpression;
                }
              }

              if (myExpr != null && myExpr.isPhysical()) {
                myExprMarker = createMarker(myExpr);
              }
              myOccurrenceMarkers = null;
              deleteTemplateField(psiField);
            });
  }
  public void testDeletePoint() throws Exception {
    RangeMarker marker = createMarker("0123456789", 2, 2);

    marker.getDocument().deleteString(1, 3);

    assertFalse(marker.isValid());
  }
  private static void restoreBlockSelection(
      Editor editor, List<RangeMarker> caretsAfter, int caretLine) {
    int column = -1;
    int minLine = Integer.MAX_VALUE;
    int maxLine = -1;
    for (RangeMarker marker : caretsAfter) {
      if (marker.isValid()) {
        LogicalPosition lp = editor.offsetToLogicalPosition(marker.getStartOffset());
        if (column == -1) {
          column = lp.column;
        } else if (column != lp.column) {
          return;
        }
        minLine = Math.min(minLine, lp.line);
        maxLine = Math.max(maxLine, lp.line);

        if (lp.line == caretLine) {
          editor.getCaretModel().moveToLogicalPosition(lp);
        }
      }
    }
    editor
        .getSelectionModel()
        .setBlockSelection(
            new LogicalPosition(minLine, column), new LogicalPosition(maxLine, column));
  }
  public void testReplaceRangeToSingleChar() throws Exception {
    RangeMarker marker = createMarker("0123456789", 1, 7);

    marker.getDocument().replaceString(2, 5, " ");

    assertTrue(marker.isValid());
  }
  @Nullable
  public PsiElement getSubstituted() {
    if (mySubstituted != null && mySubstituted.isValid()) {
      if (mySubstituted instanceof PsiNameIdentifierOwner) {
        if (Comparing.strEqual(myOldName, ((PsiNameIdentifierOwner) mySubstituted).getName()))
          return mySubstituted;

        final RangeMarker rangeMarker =
            mySubstitutedRange != null ? mySubstitutedRange : myRenameOffset;
        if (rangeMarker != null)
          return PsiTreeUtil.getParentOfType(
              mySubstituted.getContainingFile().findElementAt(rangeMarker.getStartOffset()),
              PsiNameIdentifierOwner.class);
      }
      return mySubstituted;
    }
    if (mySubstitutedRange != null) {
      final PsiFile psiFile =
          PsiDocumentManager.getInstance(myProject).getPsiFile(myEditor.getDocument());
      if (psiFile != null) {
        return PsiTreeUtil.getParentOfType(
            psiFile.findElementAt(mySubstitutedRange.getStartOffset()),
            PsiNameIdentifierOwner.class);
      }
    }
    return getVariable();
  }
  public void testDeleteRangeInside() throws Exception {
    RangeMarker marker = createMarker("0123456789", 1, 7);

    marker.getDocument().deleteString(2, 5);

    assertTrue(marker.isValid());
  }
  public void testDeleteRightPart2() throws Exception {
    RangeMarker marker = createMarker("0123456789", 2, 5);

    marker.getDocument().deleteString(4, 5);

    assertValidMarker(marker, 2, 4);
  }
  public void testCreation() throws Exception {
    RangeMarker marker = createMarker("0123456789", 2, 5);

    assertEquals(2, marker.getStartOffset());
    assertEquals(5, marker.getEndOffset());
    assertTrue(marker.isValid());
  }
  public void testReplaceRightPartInvalid() throws Exception {
    RangeMarker marker = createMarker("0123456789", 2, 5);

    marker.getDocument().replaceString(4, 6, "xxx");

    assertValidMarker(marker, 2, 4);
  }
  @Override
  @Nullable
  protected TextRange surroundStatement(
      @NotNull Project project, @NotNull Editor editor, @NotNull PsiElement[] elements)
      throws IncorrectOperationException {
    PyTryExceptStatement tryStatement =
        PyElementGenerator.getInstance(project)
            .createFromText(LanguageLevel.getDefault(), PyTryExceptStatement.class, getTemplate());
    final PsiElement parent = elements[0].getParent();
    final PyStatementList statementList = tryStatement.getTryPart().getStatementList();
    statementList.addRange(elements[0], elements[elements.length - 1]);
    statementList.getFirstChild().delete();
    tryStatement = (PyTryExceptStatement) parent.addBefore(tryStatement, elements[0]);
    parent.deleteChildRange(elements[0], elements[elements.length - 1]);

    final PsiFile psiFile = parent.getContainingFile();
    final Document document = psiFile.getViewProvider().getDocument();
    final TextRange range = tryStatement.getTextRange();
    assert document != null;
    final RangeMarker rangeMarker = document.createRangeMarker(range);

    final PsiElement element = psiFile.findElementAt(rangeMarker.getStartOffset());
    tryStatement = PsiTreeUtil.getParentOfType(element, PyTryExceptStatement.class);
    if (tryStatement != null) {
      return getResultRange(tryStatement);
    }
    return null;
  }
  public void testInsertIntoStart() throws Exception {
    RangeMarker marker = createMarker("0123456789", 2, 5);

    marker.getDocument().insertString(2, "xxx");

    assertValidMarker(marker, 5, 8);
  }
  public void testDeleteFirstChar() throws Exception {
    RangeMarker marker = createMarker("0123456789", 0, 5);

    marker.getDocument().deleteString(0, 1);

    assertValidMarker(marker, 0, 4);
  }
  public void testReplaceRightIncludingFirstChar() throws Exception {
    String s = "12345\n \n12345";
    RangeMarker marker = createMarker(s, 6, 8);

    marker.getDocument().replaceString(0, s.length(), s.replaceAll(" ", ""));

    assertValidMarker(marker, 6, 7);
  }
 void clear() {
   myPsiElements.clear();
   for (RangeMarker marker : myRangeMarkers) {
     if (!(marker instanceof FoldRegion)) marker.dispose();
   }
   myRangeMarkers.clear();
   mySerializedElements.clear();
 }
 private void setDirtyScope(int passId, RangeMarker scope) {
   RangeMarker marker = dirtyScopes.get(passId);
   if (marker != scope) {
     if (marker != null) {
       marker.dispose();
     }
     dirtyScopes.put(passId, scope);
   }
 }
  public void testDeleteBeforeStart() throws Exception {
    RangeMarker marker = createMarker("01[234]56789");

    marker.getDocument().deleteString(0, 1);

    assertEquals(1, marker.getStartOffset());
    assertEquals(4, marker.getEndOffset());
    assertTrue(marker.isValid());
  }
 @Override
 public void dispose() {
   for (Pair<Integer, RangeMarker> pair : myRangesToReindent) {
     RangeMarker marker = pair.second;
     if (marker.isValid()) {
       marker.dispose();
     }
   }
 }
  public void testInsertIntoEnd() throws Exception {
    RangeMarker marker = createMarker("0123456789", 2, 5);

    marker.getDocument().insertString(5, "xxx");

    assertEquals(2, marker.getStartOffset());
    assertEquals(5, marker.getEndOffset());
    assertTrue(marker.isValid());
  }
  public void testPersistentMarkerDoesntImpactNormalMarkers() {
    Document doc = new DocumentImpl("text");
    RangeMarker normal = doc.createRangeMarker(1, 3);
    RangeMarker persistent = doc.createRangeMarker(1, 3, true);

    doc.replaceString(0, 4, "before\ntext\nafter");

    assertTrue(persistent.isValid());
    assertFalse(normal.isValid());
  }
 protected void onOriginalChanged(DocumentEvent event, Document copy) {
   if (!myRangeMarker.isValid()) {
     fireContentInvalid();
     return;
   }
   replaceString(
       copy,
       0,
       copy.getTextLength(),
       subText(event.getDocument(), myRangeMarker.getStartOffset(), getLength()));
 }
  private void altCommitToOriginal(@NotNull DocumentEvent e) {
    final PsiFile origPsiFile =
        PsiDocumentManager.getInstance(myProject).getPsiFile(myOrigDocument);
    String newText = myNewDocument.getText();
    // prepare guarded blocks
    LinkedHashMap<String, String> replacementMap = new LinkedHashMap<String, String>();
    int count = 0;
    for (RangeMarker o : ContainerUtil.reverse(((DocumentEx) myNewDocument).getGuardedBlocks())) {
      String replacement = o.getUserData(REPLACEMENT_KEY);
      String tempText = "REPLACE" + (count++) + Long.toHexString(StringHash.calc(replacement));
      newText =
          newText.substring(0, o.getStartOffset()) + tempText + newText.substring(o.getEndOffset());
      replacementMap.put(tempText, replacement);
    }
    // run preformat processors
    final int hostStartOffset = myAltFullRange.getStartOffset();
    myEditor.getCaretModel().moveToOffset(hostStartOffset);
    for (CopyPastePreProcessor preProcessor :
        Extensions.getExtensions(CopyPastePreProcessor.EP_NAME)) {
      newText = preProcessor.preprocessOnPaste(myProject, origPsiFile, myEditor, newText, null);
    }
    myOrigDocument.replaceString(hostStartOffset, myAltFullRange.getEndOffset(), newText);
    // replace temp strings for guarded blocks
    for (String tempText : replacementMap.keySet()) {
      int idx =
          CharArrayUtil.indexOf(
              myOrigDocument.getCharsSequence(),
              tempText,
              hostStartOffset,
              myAltFullRange.getEndOffset());
      myOrigDocument.replaceString(idx, idx + tempText.length(), replacementMap.get(tempText));
    }
    // JAVA: fix occasional char literal concatenation
    fixDocumentQuotes(myOrigDocument, hostStartOffset - 1);
    fixDocumentQuotes(myOrigDocument, myAltFullRange.getEndOffset());

    // reformat
    PsiDocumentManager.getInstance(myProject).commitDocument(myOrigDocument);
    Runnable task =
        () -> {
          try {
            CodeStyleManager.getInstance(myProject)
                .reformatRange(origPsiFile, hostStartOffset, myAltFullRange.getEndOffset(), true);
          } catch (IncorrectOperationException e1) {
            // LOG.error(e);
          }
        };
    DocumentUtil.executeInBulk(myOrigDocument, true, task);

    PsiElement newInjected =
        InjectedLanguageManager.getInstance(myProject)
            .findInjectedElementAt(origPsiFile, hostStartOffset);
    DocumentWindow documentWindow =
        newInjected == null ? null : InjectedLanguageUtil.getDocumentWindow(newInjected);
    if (documentWindow != null) {
      myEditor.getCaretModel().moveToOffset(documentWindow.injectedToHost(e.getOffset()));
      myEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
    }
  }
  public void testPersistent() throws Exception {
    String text = "xxx\nzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz";
    Document document = EditorFactory.getInstance().createDocument(text);
    int startOffset = text.indexOf('z');
    int endOffset = text.lastIndexOf('z');
    RangeMarker marker = document.createRangeMarker(startOffset, endOffset, true);

    document.replaceString(startOffset + 1, endOffset - 1, "ccc");

    assertTrue(marker.isValid());
  }
  public void testLL() {
    RangeMarker marker1 = createMarker("012345678901234567", 5, 6);
    DocumentEx document = (DocumentEx) marker1.getDocument();
    document.createRangeMarker(4, 5);
    document.createRangeMarker(6, 7);
    document.createRangeMarker(0, 4);
    document.deleteString(1, 2);

    document.createRangeMarker(0, 7);
    document.createRangeMarker(0, 7);
  }
 static TextRange processDocument(
     Document document, RangeMarker marker, Commenter commenter, boolean escape) {
   if (commenter instanceof EscapingCommenter) {
     if (escape) {
       ((EscapingCommenter) commenter).escape(document, marker);
     } else {
       ((EscapingCommenter) commenter).unescape(document, marker);
     }
   }
   return TextRange.create(marker.getStartOffset(), marker.getEndOffset());
 }
 public boolean changesRange(TextRange range) {
   if (myAltFullRange != null) {
     return range.intersects(myAltFullRange.getStartOffset(), myAltFullRange.getEndOffset());
   } else if (!myMarkers.isEmpty()) {
     TextRange hostRange =
         TextRange.create(
             myMarkers.get(0).first.getStartOffset(),
             myMarkers.get(myMarkers.size() - 1).first.getEndOffset());
     return range.intersects(hostRange);
   }
   return false;
 }
 public void testBranched() {
   RangeMarker marker1 = createMarker("01234567890123456", 0, 1);
   DocumentEx document = (DocumentEx) marker1.getDocument();
   RangeMarker marker2 = document.createRangeMarker(2, 3);
   RangeMarker marker3 = document.createRangeMarker(4, 5);
   RangeMarker marker4 = document.createRangeMarker(6, 7);
   RangeMarker marker5 = document.createRangeMarker(8, 9);
   RangeMarker marker6 = document.createRangeMarker(10, 11);
   RangeMarker marker7 = document.createRangeMarker(12, 13);
   RangeMarker marker8 = document.createRangeMarker(14, 15);
   document.deleteString(1, 2);
 }
 protected Document createCopy() {
   final Document originalDocument = myRangeMarker.getDocument();
   String textInRange =
       originalDocument
           .getCharsSequence()
           .subSequence(myRangeMarker.getStartOffset(), myRangeMarker.getEndOffset())
           .toString();
   final Document result = EditorFactory.getInstance().createDocument(textInRange);
   result.setReadOnly(!originalDocument.isWritable());
   result.putUserData(ORIGINAL_DOCUMENT, originalDocument);
   return result;
 }
  public void testRangeHighlighterDisposeVsRemoveAllConflict() throws Exception {
    Document document = EditorFactory.getInstance().createDocument("[xxxxxxxxxxxxxx]");

    MarkupModel markupModel = DocumentMarkupModel.forDocument(document, ourProject, true);
    RangeMarker m =
        markupModel.addRangeHighlighter(1, 6, 0, null, HighlighterTargetArea.EXACT_RANGE);
    assertTrue(m.isValid());
    markupModel.removeAllHighlighters();
    assertFalse(m.isValid());
    assertEmpty(markupModel.getAllHighlighters());
    m.dispose();
    assertFalse(m.isValid());
  }