package com.intellij.openapi.diff.impl.highlighting; import com.intellij.openapi.diff.DiffColors; import com.intellij.openapi.diff.impl.FragmentNumberGutterIconRenderer; import com.intellij.openapi.diff.impl.fragments.Fragment; import com.intellij.openapi.diff.impl.fragments.FragmentHighlighterImpl; import com.intellij.openapi.diff.impl.fragments.LineFragment; import com.intellij.openapi.diff.impl.util.TextDiffTypeEnum; import com.intellij.openapi.editor.colors.TextAttributesKey; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.editor.markup.*; import com.intellij.openapi.util.Pair; import com.intellij.openapi.util.TextRange; import com.intellij.util.containers.MultiMap; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.*; /** * @author irengrig * Date: 8/12/11 * Time: 4:01 PM */ public class NumberedFragmentHighlighter extends FragmentHighlighterImpl { private final boolean myDrawNumber; private final Map<Integer, Pair<String, TextDiffTypeEnum>> myLeftPrecalculated; private final Map<Integer, Pair<String, TextDiffTypeEnum>> myRightPrecalculated; private int myPreviousLineLeft; private int myPreviousLineRight; private NumberedFragmentHighlighter.MyPropertyChangeListener myPropertyChangeListener; public NumberedFragmentHighlighter(DiffMarkup appender1, DiffMarkup appender2, boolean drawNumber) { super(appender1, appender2); myDrawNumber = drawNumber; myLeftPrecalculated = new HashMap<Integer, Pair<String, TextDiffTypeEnum>>(); myRightPrecalculated = new HashMap<Integer, Pair<String, TextDiffTypeEnum>>(); myPreviousLineLeft = -1; myPreviousLineRight = -1; } private class MyPropertyChangeListener implements PropertyChangeListener { @Override public void propertyChange(PropertyChangeEvent evt) { if (!EditorEx.PROP_FONT_SIZE.equals(evt.getPropertyName())) return; if (evt.getOldValue().equals(evt.getNewValue())) return; RangeHighlighter[] allHighlighters = myAppender1.getEditor().getMarkupModel().getAllHighlighters(); resetFont(allHighlighters); RangeHighlighter[] allHighlighters2 = myAppender2.getEditor().getMarkupModel().getAllHighlighters(); resetFont(allHighlighters2); } private void resetFont(RangeHighlighter[] allHighlighters) { for (RangeHighlighter highlighter : allHighlighters) { GutterIconRenderer renderer = highlighter.getGutterIconRenderer(); if (renderer instanceof FragmentNumberGutterIconRenderer) { ((FragmentNumberGutterIconRenderer)renderer).resetFont(myAppender1.getEditor()); } } } } private TextAttributesKey getColorAttributesKey(final TextDiffTypeEnum textDiffTypeEnum) { if (TextDiffTypeEnum.CHANGED.equals(textDiffTypeEnum)) { return DiffColors.DIFF_MODIFIED; } else if (TextDiffTypeEnum.INSERT.equals(textDiffTypeEnum)) { return DiffColors.DIFF_INSERTED; } else if (TextDiffTypeEnum.DELETED.equals(textDiffTypeEnum)) { return DiffColors.DIFF_DELETED; } else if (TextDiffTypeEnum.CONFLICT.equals(textDiffTypeEnum)) { return DiffColors.DIFF_CONFLICT; } else { return null; } } @Override protected void highlightFragmentImpl(Fragment fragment) { if (! myDrawNumber || fragment.getType() == null || TextDiffTypeEnum.NONE.equals(fragment.getType())) { myAppender1.highlightText(fragment, null); myAppender2.highlightText(fragment, null); return; } int lineLeft = myAppender1.getDocument().getLineNumber(fragment.getRange(FragmentSide.SIDE1).getStartOffset()); int lineRight = myAppender2.getDocument().getLineNumber(fragment.getRange(FragmentSide.SIDE2).getStartOffset()); Pair<String, TextDiffTypeEnum> left = myLeftPrecalculated.get(lineLeft); if (myPreviousLineLeft == lineLeft || left == null) { myAppender1.highlightText(fragment, null); } else { // draw border == true for range marker with highlighting and number be set anyway, even if range is empty myAppender1.highlightText(fragment, new FragmentNumberGutterIconRenderer(left.getFirst(), getColorAttributesKey(left.getSecond()), myAppender1.getEditor().getScrollPane(), myAppender1.getEditor())); myPreviousLineLeft = lineLeft; } Pair<String, TextDiffTypeEnum> right = myRightPrecalculated.get(lineRight); if (myPreviousLineRight == lineRight || right == null) { myAppender2.highlightText(fragment, null); } else { // draw border == true for range marker with highlighting and number be set anyway, even if range is empty myAppender2.highlightText(fragment, new FragmentNumberGutterIconRenderer(right.getFirst(), getColorAttributesKey(right.getSecond()), myAppender1.getEditor().getScrollPane(), myAppender1.getEditor())); myPreviousLineRight = lineRight; } } public void addRangeHighlighter(final boolean left, int start, int end, final TextAttributes attributes) { if (left) { myAppender1.getEditor().getMarkupModel().addRangeHighlighter(start, end, HighlighterLayer.SELECTION - 3, attributes, HighlighterTargetArea.EXACT_RANGE); } else { myAppender2.getEditor().getMarkupModel().addRangeHighlighter(start, end, HighlighterLayer.SELECTION - 3, attributes, HighlighterTargetArea.EXACT_RANGE); } } public void reset() { myLeftPrecalculated.clear(); myRightPrecalculated.clear(); myPreviousLineLeft = -1; myPreviousLineRight = -1; } public void precalculateNumbers(List<LineFragment> lines) { if (myPropertyChangeListener == null) { myPropertyChangeListener = new MyPropertyChangeListener(); myAppender1.getEditor().addPropertyChangeListener(myPropertyChangeListener); } final MultiMap<Integer, Pair<Integer, TextDiffTypeEnum>> leftMap = new MultiMap<Integer, Pair<Integer, TextDiffTypeEnum>>(); final MultiMap<Integer, Pair<Integer, TextDiffTypeEnum>> rightMap = new MultiMap<Integer, Pair<Integer, TextDiffTypeEnum>>(); int cnt = 1; for (LineFragment line : lines) { if (line.getType() == null || TextDiffTypeEnum.NONE.equals(line.getType())) continue; final Iterator<Fragment> iterator = line.getChildrenIterator(); if (iterator == null) { TextRange left = line.getRange(FragmentSide.SIDE1); TextRange right = line.getRange(FragmentSide.SIDE2); leftMap.putValue(myAppender1.getDocument().getLineNumber(left.getStartOffset()), new Pair<Integer, TextDiffTypeEnum>(cnt, line.getType())); rightMap.putValue(myAppender2.getDocument().getLineNumber(right.getStartOffset()), new Pair<Integer, TextDiffTypeEnum>(cnt, line.getType())); ++ cnt; continue; } while (iterator.hasNext()) { final Fragment next = iterator.next(); if (next.getType() == null || TextDiffTypeEnum.NONE.equals(next.getType())) continue; TextRange left = next.getRange(FragmentSide.SIDE1); TextRange right = next.getRange(FragmentSide.SIDE2); leftMap.putValue(myAppender1.getDocument().getLineNumber(left.getStartOffset()), new Pair<Integer, TextDiffTypeEnum>(cnt, next.getType())); rightMap.putValue(myAppender2.getDocument().getLineNumber(right.getStartOffset()), new Pair<Integer, TextDiffTypeEnum>(cnt, next.getType())); ++ cnt; } } // merge merge(leftMap, myLeftPrecalculated); merge(rightMap, myRightPrecalculated); } private void merge(MultiMap<Integer, Pair<Integer, TextDiffTypeEnum>> leftMap, final Map<Integer, Pair<String, TextDiffTypeEnum>> whereTo) { for (Map.Entry<Integer, Collection<Pair<Integer, TextDiffTypeEnum>>> entry : leftMap.entrySet()) { List<Pair<Integer, TextDiffTypeEnum>> value = (List<Pair<Integer, TextDiffTypeEnum>>) entry.getValue(); if (value.size() > 1) { Pair<Integer, TextDiffTypeEnum> pair1 = value.iterator().next(); Pair<Integer, TextDiffTypeEnum> pair2 = value.get(value.size() - 1); TextDiffTypeEnum type = mergeDiffType(value); whereTo.put(entry.getKey(), new Pair<String, TextDiffTypeEnum>(String.valueOf(pair1.getFirst()) + "-" + String.valueOf(pair2.getFirst()), type)); } else { Pair<Integer, TextDiffTypeEnum> pair = value.iterator().next(); whereTo.put(entry.getKey(), new Pair<String, TextDiffTypeEnum>(String.valueOf(pair.getFirst()), pair.getSecond())); } } } private TextDiffTypeEnum mergeDiffType(List<Pair<Integer, TextDiffTypeEnum>> value) { TextDiffTypeEnum previous = null; for (Pair<Integer, TextDiffTypeEnum> pair : value) { if (previous == null) { previous = pair.getSecond(); continue; } if (! previous.equals(pair.getSecond())) return TextDiffTypeEnum.CHANGED; } return previous; } public List<Integer> getLeftLines() { List<Integer> list = new ArrayList<Integer>(myLeftPrecalculated.keySet()); Collections.sort(list); return list; } public List<Integer> getRightLines() { List<Integer> list = new ArrayList<Integer>(myRightPrecalculated.keySet()); Collections.sort(list); return list; } }