/* * Copyright 2000-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 com.intellij.diff.tools.fragmented; import com.intellij.diff.fragments.LineFragment; import com.intellij.diff.util.LineRange; import com.intellij.diff.util.Side; import com.intellij.openapi.editor.Document; import com.intellij.openapi.util.TextRange; import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; // This class works incorrectly with non-fair differences (when chunk of matched lines has different length in left/right files) class UnifiedFragmentBuilder { @NotNull private final List<LineFragment> myFragments; @NotNull private final Document myDocument1; @NotNull private final Document myDocument2; @NotNull private final Side myMasterSide; @NotNull private final StringBuilder myBuilder = new StringBuilder(); @NotNull private final List<ChangedBlock> myBlocks = new ArrayList<ChangedBlock>(); @NotNull private final List<HighlightRange> myRanges = new ArrayList<HighlightRange>(); @NotNull private final LineNumberConvertor.Builder myConvertor = new LineNumberConvertor.Builder(); @NotNull private final List<LineRange> myChangedLines = new ArrayList<LineRange>(); public UnifiedFragmentBuilder(@NotNull List<LineFragment> fragments, @NotNull Document document1, @NotNull Document document2, @NotNull Side masterSide) { myFragments = fragments; myDocument1 = document1; myDocument2 = document2; myMasterSide = masterSide; } private boolean myEqual = false; private int lastProcessedLine1 = -1; private int lastProcessedLine2 = -1; private int totalLines = 0; public void exec() { if (myFragments.isEmpty()) { myEqual = true; appendTextMaster(0, 0, getLineCount(myDocument1) - 1, getLineCount(myDocument2) - 1); return; } for (LineFragment fragment : myFragments) { processEquals(fragment.getStartLine1() - 1, fragment.getStartLine2() - 1); processChanged(fragment); } processEquals(getLineCount(myDocument1) - 1, getLineCount(myDocument2) - 1); } private void processEquals(int endLine1, int endLine2) { int startLine1 = lastProcessedLine1 + 1; int startLine2 = lastProcessedLine2 + 1; appendTextMaster(startLine1, startLine2, endLine1, endLine2); } @SuppressWarnings("UnnecessaryLocalVariable") private void processChanged(@NotNull LineFragment fragment) { int startLine1 = fragment.getStartLine1(); int endLine1 = fragment.getEndLine1() - 1; int lines1 = endLine1 - startLine1; int startLine2 = fragment.getStartLine2(); int endLine2 = fragment.getEndLine2() - 1; int lines2 = endLine2 - startLine2; int linesBefore = totalLines; int linesAfter; if (lines1 >= 0) { int startOffset = myDocument1.getLineStartOffset(startLine1); int endOffset = myDocument1.getLineEndOffset(endLine1); appendTextSide(Side.LEFT, startOffset, endOffset, lines1, startLine1, -1); } int linesBetween = totalLines; if (lines2 >= 0) { int startOffset = myDocument2.getLineStartOffset(startLine2); int endOffset = myDocument2.getLineEndOffset(endLine2); appendTextSide(Side.RIGHT, startOffset, endOffset, lines2, -1, startLine2); } linesAfter = totalLines; int blockStartLine1 = linesBefore; int blockEndLine1 = linesBetween; int blockStartLine2 = linesBetween; int blockEndLine2 = linesAfter; myBlocks.add(new ChangedBlock(linesBefore, linesAfter, new LineRange(blockStartLine1, blockEndLine1), new LineRange(blockStartLine2, blockEndLine2), fragment)); lastProcessedLine1 = endLine1; lastProcessedLine2 = endLine2; } private void appendTextMaster(int startLine1, int startLine2, int endLine1, int endLine2) { int lines = myMasterSide.isLeft() ? endLine1 - startLine1 : endLine2 - startLine2; if (lines >= 0) { int startOffset = myMasterSide.isLeft() ? myDocument1.getLineStartOffset(startLine1) : myDocument2.getLineStartOffset(startLine2); int endOffset = myMasterSide.isLeft() ? myDocument1.getLineEndOffset(endLine1) : myDocument2.getLineEndOffset(endLine2); appendText(myMasterSide, startOffset, endOffset, lines, startLine1, startLine2); } } private void appendTextSide(@NotNull Side side, int offset1, int offset2, int lines, int startLine1, int startLine2) { int linesBefore = totalLines; appendText(side, offset1, offset2, lines, startLine1, startLine2); int linesAfter = totalLines; myChangedLines.add(new LineRange(linesBefore, linesAfter)); } private void appendText(@NotNull Side side, int offset1, int offset2, int lines, int startLine1, int startLine2) { Document document = side.select(myDocument1, myDocument2); int newline = document.getTextLength() > offset2 + 1 ? 1 : 0; TextRange base = new TextRange(myBuilder.length(), myBuilder.length() + offset2 - offset1 + newline); TextRange changed = new TextRange(offset1, offset2 + newline); myRanges.add(new HighlightRange(side, base, changed)); myBuilder.append(document.getCharsSequence().subSequence(offset1, offset2)); myBuilder.append('\n'); if (startLine1 != -1) { myConvertor.put1(totalLines, startLine1, lines + 1); } if (startLine2 != -1) { myConvertor.put2(totalLines, startLine2, lines + 1); } totalLines += lines + 1; } private static int getLineCount(@NotNull Document document) { return Math.max(document.getLineCount(), 1); } // // Result // public boolean isEqual() { return myEqual; } @NotNull public CharSequence getText() { return myBuilder; } @NotNull public List<ChangedBlock> getBlocks() { return myBlocks; } @NotNull public List<HighlightRange> getRanges() { return myRanges; } @NotNull public LineNumberConvertor getConvertor() { return myConvertor.build(); } @NotNull public List<LineRange> getChangedLines() { return myChangedLines; } }