blob: bc8b9638e725b3b95e94834c6cd9552a61202197 [file] [log] [blame]
/*
* Copyright 2000-2014 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.codeInsight.daemon.GutterMark;
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.HighlighterLayer;
import com.intellij.openapi.editor.markup.HighlighterTargetArea;
import com.intellij.openapi.editor.markup.RangeHighlighter;
import com.intellij.openapi.editor.markup.TextAttributes;
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) {
GutterMark 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(), Pair.create(String.valueOf(pair1.getFirst()) + "-" +
String.valueOf(pair2.getFirst()), type));
} else {
Pair<Integer, TextDiffTypeEnum> pair = value.iterator().next();
whereTo.put(entry.getKey(), Pair.create(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;
}
}