/* * Copyright 2000-2013 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.openapi.diff.impl.highlighting; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diff.impl.string.DiffString; import com.intellij.openapi.diff.ex.DiffFragment; import com.intellij.openapi.util.Comparing; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.diff.Diff; import gnu.trove.TIntHashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.StringTokenizer; public class Util { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.diff.impl.highlighting.Util"); private static final String DELIMITERS = " \n\r\t(){}[],./?`~!@#$%^&*-=+|\\;:'\"<>"; public static final TIntHashSet DELIMITERS_SET = new TIntHashSet(); static { char[] delimiters = DELIMITERS.toCharArray(); for (int i = 0; i < delimiters.length; i++) { char delimiter = delimiters[i]; DELIMITERS_SET.add(delimiter); } } @NotNull static String[] splitByWord(@NotNull String string) { BufferedStringList stringList = new BufferedStringList(); StringTokenizer tokenizer = new StringTokenizer(string, DELIMITERS, true); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken(); if (token.length() == 1 && DELIMITERS_SET.contains(token.charAt(0))) { char delimiter = token.charAt(0); if (delimiter == '\n') { stringList.appendToLast(token); stringList.flushLast(); continue; } if (Character.isWhitespace(delimiter)) { stringList.appendToLast(token); continue; } } stringList.add(token); } return stringList.toArray(); } static boolean isSpaceOnly(@NotNull DiffFragment fragment) { return isSpaceOnly(fragment.getText1()) && isSpaceOnly(fragment.getText2()); } private static boolean isSpaceOnly(@Nullable DiffString string) { if (string == null) return true; return string.isEmptyOrSpaces(); } @NotNull static DiffFragment[] splitByLines(@NotNull DiffFragment fragment) { DiffString[] lines1 = splitByLines(fragment.getText1()); DiffString[] lines2 = splitByLines(fragment.getText2()); if (lines1 != null && lines2 != null && lines1.length != lines2.length) { LOG.error("1:<" + fragment.getText1() + "> 2:<" + fragment.getText2() + ">"); } int length = lines1 == null ? lines2.length : lines1.length; DiffFragment[] lines = new DiffFragment[length]; for (int i = 0; i < lines.length; i++) { lines[i] = new DiffFragment(lines1 == null? null : lines1[i], lines2 == null ? null : lines2[i]); } return lines; } @Nullable private static DiffString[] splitByLines(@Nullable DiffString string) { if (string == null) return null; if (string.indexOf('\n') == -1) return new DiffString[]{string}; return string.tokenize(); } @NotNull public static DiffFragment[][] splitByUnchangedLines(@NotNull DiffFragment[] fragments) { List2D result = new List2D(); for (int i = 0; i < fragments.length; i++) { DiffFragment fragment = fragments[i]; if (!fragment.isEqual()) { result.add(fragment); continue; } DiffString text1 = fragment.getText1(); DiffString text2 = fragment.getText2(); assert text1 != null; assert text2 != null; if (StringUtil.endsWithChar(text1, '\n') && StringUtil.endsWithChar(text2, '\n')) { result.add(fragment); result.newRow(); continue; } while (true) { int newLine1 = text1.indexOf('\n'); int newLine2 = text2.indexOf('\n'); if (newLine1 == -1 || newLine2 == -1) { result.add(DiffFragment.unchanged(text1, text2)); break; } result.add(DiffFragment.unchanged(text1.substring(0, newLine1 + 1), text2.substring(0, newLine2 + 1))); result.newRow(); text1 = text1.substring(newLine1 + 1); text2 = text2.substring(newLine2 + 1); int length1 = text1.length(); int length2 = text2.length(); if (length1 == 0 || length2 == 0) { if (length1 != 0 || length2 != 0) result.add(DiffFragment.unchanged(text1, text2)); break; } } } return result.toArray(); } public static Diff.Change concatEquals(Diff.Change change, @NotNull Object[] left, @NotNull Object[] right) { MyChange startChange = new MyChange(0, 0, 0, 0); MyChange lastChange = startChange; while (change != null) { if (change.inserted > 0 && change.deleted > 0) { lastChange = lastChange.copyNext(change); } else if (change.inserted > 0) { int shift = calcShift(right, lastChange.getEnd2(), change.line1, change.inserted); lastChange = lastChange.copyNext(change, shift); } else if (change.deleted > 0) { int shift = calcShift(left, lastChange.getEnd1(), change.line0, change.deleted); lastChange = lastChange.copyNext(change, shift); } else { LOG.assertTrue(false); } change = change.link; } return concatSingleSide(startChange.link); } private static Diff.Change concatSingleSide(Diff.Change change) { MyChange startChange = new MyChange(0, 0, 0, 0); MyChange lastChange = startChange; MyChange prevChange = null; while (change != null) { if (prevChange == null || (change.inserted > 0 && change.deleted > 0)) { prevChange = lastChange; lastChange = lastChange.copyNext(change); } else { MyChange newChange = null; if (change.deleted == 0 && lastChange.deleted == 0 && change.line1 == lastChange.getEnd2()) { newChange = new MyChange(lastChange.line0, lastChange.line1, 0, lastChange.inserted + change.inserted); } else if (change.inserted == 0 && lastChange.inserted == 0 && change.line0 == lastChange.getEnd1()) { newChange = new MyChange(lastChange.line0, lastChange.line1, lastChange.deleted + change.deleted, 0); } if (newChange != null) { prevChange.setNext(newChange); lastChange = newChange; } else { prevChange = lastChange; lastChange = lastChange.copyNext(change); } } change = change.link; } return startChange.link; } static int calcShift(@NotNull Object[] list, int limit, int start, int length) { int shift = start - limit; for (int i = 0; i < shift; i++) { if (!list[limit + i].equals(list[start + length - shift + i])) return 0; } return -shift; } @NotNull public static DiffFragment unite(@NotNull DiffFragment fragment1, @NotNull DiffFragment fragment2) { LOG.assertTrue(isSameType(fragment1, fragment2)); if (!fragment1.isOneSide()) { DiffString unitedText1 = DiffString.concatenateNullable(fragment1.getText1(), fragment2.getText1()); DiffString unitedText2 = DiffString.concatenateNullable(fragment1.getText2(), fragment2.getText2()); LOG.assertTrue(fragment1.isEqual() == fragment2.isEqual()); return fragment1.isEqual() ? DiffFragment.unchanged(unitedText1, unitedText2) : new DiffFragment(unitedText1, unitedText2); } FragmentSide side = FragmentSide.chooseSide(fragment1); return side .createFragment(DiffString.concatenateNullable(side.getText(fragment1), side.getText(fragment2)), null, fragment1.isModified()); } public static boolean isSameType(@NotNull DiffFragment fragment1, @NotNull DiffFragment fragment2) { if (fragment1.isEqual()) return fragment2.isEqual(); if (fragment1.isChange()) return fragment2.isChange(); if (fragment1.getText1() == null) return fragment2.getText1() == null; if (fragment1.getText2() == null) return fragment2.getText2() == null; LOG.assertTrue(false); return false; } @NotNull public static DiffString getText(@NotNull DiffFragment[] fragments, @NotNull FragmentSide side) { DiffString[] data = new DiffString[fragments.length]; for (int i = 0; i < fragments.length; i++) { DiffFragment fragment = fragments[i]; data[i] = side.getText(fragment); } return DiffString.concatenate(data); } @NotNull public static DiffFragment concatenate(@NotNull DiffFragment[] line) { return concatenate(line, 0, line.length); } @NotNull public static DiffFragment concatenate(@NotNull DiffFragment[] line, int from, int to) { DiffString[] data1 = new DiffString[to - from]; DiffString[] data2 = new DiffString[to - from]; boolean isEqual = true; for (int i = 0; i < to - from; i++) { DiffFragment fragment = line[from + i]; isEqual &= fragment.isEqual(); data1[i] = fragment.getText1(); data2[i] = fragment.getText2(); } DiffString text1 = notEmptyContent(DiffString.concatenate(data1)); DiffString text2 = notEmptyContent(DiffString.concatenate(data2)); return isEqual ? DiffFragment.unchanged(text1, text2) : new DiffFragment(text1, text2); } @Nullable private static DiffString notEmptyContent(@NotNull DiffString string) { return string.length() > 0 ? string : null; } @NotNull public static DiffFragment[][] uniteFormattingOnly(@NotNull DiffFragment[][] lines) { List2D result = new List2D(); for (int i = 0; i < lines.length; i++) { DiffFragment[] line = lines[i]; if (!areEqual(line) && areEqualOrFormatting(line)) result.addAll(line); else { result.newRow(); result.addAll(line); result.newRow(); } } return result.toArray(); } private static boolean areEqualOrFormatting(@NotNull DiffFragment[] fragments) { for (int i = 0; i < fragments.length; i++) { DiffFragment fragment = fragments[i]; if (fragment.isEqual()) continue; for (int side = 0; side < 2; side++) { DiffString text = FragmentSide.fromIndex(side).getText(fragment); if (text == null || text.isEmptyOrSpaces()) continue; return false; } } return true; } private static boolean areEqual(@NotNull DiffFragment[] fragments) { for (int i = 0; i < fragments.length; i++) { DiffFragment fragment = fragments[i]; if (!fragment.isEqual()) return false; } return true; } @NotNull public static DiffFragment[] cutFirst(@NotNull DiffFragment[] fragments) { fragments = transformHeadInsert(fragments, FragmentSide.SIDE1); fragments = transformHeadInsert(fragments, FragmentSide.SIDE2); int nullCount = 0; for (int sideIndex = 0; sideIndex < 2; sideIndex++) { FragmentSide side = FragmentSide.fromIndex(sideIndex); for (int i = 0; i < fragments.length; i++) { DiffFragment fragment = fragments[i]; if (fragment == null) continue; DiffString text = side.getText(fragment); if (text == null || text.isEmpty()) continue; text = text.length() > 1 ? text.substring(1) : null; DiffString otherText = side.getOtherText(fragment); if (otherText == null && text == null) { fragments[i] = null; nullCount++; } else fragments[i] = side.createFragment(text, otherText, fragment.isModified()); break; } } if (nullCount == 0) return fragments; DiffFragment[] result = new DiffFragment[fragments.length - nullCount]; int dstIndex = 0; for (int i = 0; i < fragments.length; i++) { DiffFragment fragment = fragments[i]; if (fragment == null) continue; result[dstIndex] = fragment; dstIndex++; } return result; } @NotNull private static DiffFragment[] transformHeadInsert(@NotNull DiffFragment[] fragments, @NotNull FragmentSide side) { // transforms {abc}abcd into a{bca}bcd if (fragments.length >= 2) { DiffFragment first = fragments[0]; DiffFragment second = fragments[1]; if (first == null || second == null) { return fragments; } if (side.getText(first) != null) { return fragments; } DiffString rightText = side.getOtherText(first); DiffString secondText = side.getText(second); if (!Comparing.equal(side.getOtherText(second), secondText)) { return fragments; } if (secondText.charAt(0) == rightText.charAt(0)) { List<DiffFragment> result = new ArrayList<DiffFragment>(); result.add(side.createFragment(rightText.substring(0, 1), rightText.substring(0, 1), false)); result.add(side.createFragment(null, DiffString.concatenate(rightText.substring(1), secondText.substring(0, 1)), true)); result.add(side.createFragment(secondText.substring(1), secondText.substring(1), second.isModified())); result.addAll(Arrays.asList(fragments).subList(2, fragments.length)); return result.toArray(new DiffFragment[result.size()]); } } return fragments; } private static class MyChange extends Diff.Change { public MyChange(int line0, int line1, int deleted, int inserted) { super(line0, line1, deleted, inserted, null); } public MyChange copyNext(@NotNull Diff.Change change) { return copyNext(change, 0); } public MyChange copyNext(@NotNull Diff.Change change, int shift) { MyChange result = new MyChange(change.line0 + shift, change.line1 + shift, change.deleted, change.inserted); setNext(result); return result; } public void setNext(MyChange change) { link = change; } public int getEnd1() { return line0 + deleted; } public int getEnd2() { return line1 + inserted; } } }