/*******************************************************************************
* Copyright (c) 2007, 2015 Wind River Systems and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.cdt.dsf.debug.internal.ui.disassembly.presentation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.cdt.internal.ui.editor.SemanticHighlightings;
import org.eclipse.cdt.ui.PreferenceConstants;
import org.eclipse.cdt.ui.text.ICPartitions;
import org.eclipse.cdt.ui.text.IColorManager;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.TextAttribute;
import org.eclipse.jface.text.TextPresentation;
import org.eclipse.jface.text.rules.DefaultDamagerRepairer;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.ITokenScanner;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.RGB;
/**
*
*/
public class SourceTagDamagerRepairer extends DefaultDamagerRepairer implements ISourceTagListener {
private ISourceTagProvider fSourceTagProvider;
private Map<String, ITokenScanner> fScannerMap= new HashMap<String, ITokenScanner>();
private List<ISourceTag> fSourceTags = new ArrayList<ISourceTag>();
private IColorManager fColorManager;
private IPreferenceStore fPreferenceStore;
private Map<String, TextAttribute> fAttributeMap= new HashMap<String, TextAttribute>();
private final static String[] KEYS= {
SemanticHighlightings.CLASS,
SemanticHighlightings.METHOD_DECLARATION,
SemanticHighlightings.FUNCTION_DECLARATION,
SemanticHighlightings.FIELD,
SemanticHighlightings.GLOBAL_VARIABLE,
SemanticHighlightings.TYPEDEF,
SemanticHighlightings.MACRO_DEFINITION,
SemanticHighlightings.ENUMERATOR,
SemanticHighlightings.ENUM,
};
/**
* @param scanner
* @param sourceTagProvider
*/
public SourceTagDamagerRepairer(ITokenScanner scanner, ISourceTagProvider sourceTagProvider, IColorManager colorManager, IPreferenceStore store) {
super(scanner);
fSourceTagProvider= sourceTagProvider;
fColorManager= colorManager;
fPreferenceStore= store;
fDefaultTextAttribute = new TextAttribute(null, null, SWT.NORMAL);
if (fSourceTagProvider != null) {
fSourceTagProvider.addSourceTagListener(this);
sourceTagsChanged(fSourceTagProvider);
}
}
private void initTextAttributes() {
boolean shEnabled= fPreferenceStore.getBoolean(PreferenceConstants.EDITOR_SEMANTIC_HIGHLIGHTING_ENABLED);
for (int i= 0; i < KEYS.length; i++) {
String enabledKey= PreferenceConstants.EDITOR_SEMANTIC_HIGHLIGHTING_PREFIX + KEYS[i] + PreferenceConstants.EDITOR_SEMANTIC_HIGHLIGHTING_ENABLED_SUFFIX;
String colorKey= PreferenceConstants.EDITOR_SEMANTIC_HIGHLIGHTING_PREFIX + KEYS[i] + PreferenceConstants.EDITOR_SEMANTIC_HIGHLIGHTING_COLOR_SUFFIX;
boolean enabled= shEnabled && fPreferenceStore.getBoolean(enabledKey);
if (enabled) {
String boldKey= PreferenceConstants.EDITOR_SEMANTIC_HIGHLIGHTING_PREFIX + KEYS[i] + PreferenceConstants.EDITOR_SEMANTIC_HIGHLIGHTING_BOLD_SUFFIX;
String italicKey= PreferenceConstants.EDITOR_SEMANTIC_HIGHLIGHTING_PREFIX + KEYS[i] + PreferenceConstants.EDITOR_SEMANTIC_HIGHLIGHTING_ITALIC_SUFFIX;
String strikethroughKey= PreferenceConstants.EDITOR_SEMANTIC_HIGHLIGHTING_PREFIX + KEYS[i] + PreferenceConstants.EDITOR_SEMANTIC_HIGHLIGHTING_STRIKETHROUGH_SUFFIX;
String underlineKey= PreferenceConstants.EDITOR_SEMANTIC_HIGHLIGHTING_PREFIX + KEYS[i] + PreferenceConstants.EDITOR_SEMANTIC_HIGHLIGHTING_UNDERLINE_SUFFIX;
addTextAttribute(KEYS[i], colorKey, boldKey, italicKey, strikethroughKey, underlineKey);
} else {
removeTextAttribute(KEYS[i], colorKey);
}
}
}
private void removeTextAttribute(String key, String colorKey) {
if (fColorManager != null && colorKey != null) {
Color color= fColorManager.getColor(colorKey);
if (color != null) {
fColorManager.unbindColor(colorKey);
}
}
fAttributeMap.remove(key);
}
private void addTextAttribute(String key, String colorKey, String boldKey, String italicKey, String strikethroughKey, String underlineKey) {
if (fColorManager != null && colorKey != null) {
RGB rgb= PreferenceConverter.getColor(fPreferenceStore, colorKey);
Color color= fColorManager.getColor(colorKey);
if (color == null || !rgb.equals(color.getRGB())) {
fColorManager.unbindColor(colorKey);
fColorManager.bindColor(colorKey, rgb);
}
}
TextAttribute textAttribute= createTextAttribute(colorKey, boldKey, italicKey, strikethroughKey, underlineKey);
fAttributeMap.put(key, textAttribute);
}
private TextAttribute createTextAttribute(String colorKey, String boldKey, String italicKey, String strikethroughKey, String underlineKey) {
Color color= null;
if (colorKey != null)
color= fColorManager.getColor(colorKey);
int style= fPreferenceStore.getBoolean(boldKey) ? SWT.BOLD : SWT.NORMAL;
if (fPreferenceStore.getBoolean(italicKey))
style |= SWT.ITALIC;
if (fPreferenceStore.getBoolean(strikethroughKey))
style |= TextAttribute.STRIKETHROUGH;
if (fPreferenceStore.getBoolean(underlineKey))
style |= TextAttribute.UNDERLINE;
return new TextAttribute(color, null, style);
}
/**
* Set scanner for contentType.
* @param contentType
* @param scanner
*/
public void setScanner(String contentType, ITokenScanner scanner) {
fScannerMap.put(contentType, scanner);
}
/*
* @see org.eclipse.jface.text.presentation.IPresentationRepairer#createPresentation(org.eclipse.jface.text.TextPresentation, org.eclipse.jface.text.ITypedRegion)
*/
@Override
public void createPresentation(TextPresentation presentation, ITypedRegion region) {
if (fAttributeMap.isEmpty()) {
initTextAttributes();
}
String contentType= region.getType();
fScanner = fScannerMap.get(contentType);
if (!contentType.equals(IDocument.DEFAULT_CONTENT_TYPE) && !contentType.equals(ICPartitions.C_PREPROCESSOR)) {
super.createPresentation(presentation, region);
return;
}
if (fScanner == null) {
return;
}
int lastStart = region.getOffset();
int regionEnd = lastStart + region.getLength();
int length = 0;
int sourceTagCount = fSourceTags.size();
int sourceTagIdx = 0;
ISourceTag sourceTag = null;
ISourceRange range = null;
int sourceTagStart = 0;
int sourceTagEnd = 0;
if (sourceTagCount > 0 && fDocument.getLength() > 0) {
int left = 0;
int mid = (int) (sourceTagCount * ((float)lastStart / fDocument.getLength()));
int right = sourceTagCount - 1;
while (true) {
sourceTag = fSourceTags.get(mid);
range = sourceTag.getRangeOfIdentifier();
sourceTagStart = range.getBeginOffset();
sourceTagEnd = range.getEndOffset() + 1;
if (mid == left) {
break;
} else if (mid < right && sourceTagEnd < lastStart) {
left = mid;
mid = (mid + right) / 2;
} else if (sourceTagStart >= regionEnd) {
right = mid;
mid = (left + mid) / 2;
} else if (sourceTagStart > lastStart) {
--mid;
right = mid;
} else {
break;
}
}
// set to next index
sourceTagIdx = mid + 1;
}
TextAttribute lastAttribute = fDefaultTextAttribute;
fScanner.setRange(fDocument, lastStart, region.getLength());
while (true) {
IToken token = fScanner.nextToken();
// if the attribute is the same as the previous, extend range and continue
TextAttribute attribute = getTokenTextAttribute(token);
int tokenLength = fScanner.getTokenLength();
if (tokenLength > 0
&& (lastAttribute == attribute || lastAttribute != null && lastAttribute.equals(attribute))) {
length += tokenLength;
continue;
}
// attribute has changed, now add the style range
while (sourceTag != null && length > 0) {
if (sourceTagStart >= regionEnd) {
// we are past the region boundary -> no more source tags
sourceTag = null;
break;
}
if (sourceTagStart >= lastStart) {
if (sourceTagStart < lastStart + length) {
String sourceTagStyle = getSourceTagStyle(sourceTag.getStyleCode());
if (sourceTagStyle != null) {
if (sourceTagStart > lastStart) {
addRange(presentation, lastStart, Math.min(sourceTagStart - lastStart, length), lastAttribute);
}
int rangeEnd = Math.min(sourceTagEnd, regionEnd);
addRange(
presentation,
sourceTagStart,
rangeEnd - sourceTagStart,
getSourceTagTextAttribute(sourceTagStyle));
length = lastStart + length - rangeEnd;
lastStart = rangeEnd;
} else {
fSourceTags.remove(--sourceTagIdx);
--sourceTagCount;
}
} else {
break;
}
}
sourceTag = sourceTagIdx < sourceTagCount ? fSourceTags.get(sourceTagIdx++) : null;
if (sourceTag != null) {
range = sourceTag.getRangeOfIdentifier();
sourceTagStart = range.getBeginOffset();
sourceTagEnd = range.getEndOffset() + 1;
}
}
if (token.isEOF()) {
break;
}
if (length > 0) {
addRange(presentation, lastStart, length, lastAttribute);
lastAttribute = attribute;
lastStart = fScanner.getTokenOffset();
length = tokenLength;
} else {
lastAttribute = attribute;
length = fScanner.getTokenOffset() - lastStart + tokenLength;
}
}
addRange(presentation, lastStart, length, lastAttribute);
}
/**
* @param sourceTagStyle
* @return
*/
private TextAttribute getSourceTagTextAttribute(String sourceTagStyle) {
return fAttributeMap.get(sourceTagStyle);
}
/**
* Get the style id for a source tag style code.
* @param styleCode
* @return the associated style id or <code>null</code>
*/
private String getSourceTagStyle(int styleCode) {
switch (styleCode) {
case ISourceTag.STYLE_None :
return null;
case ISourceTag.STYLE_Class :
return SemanticHighlightings.CLASS;
case ISourceTag.STYLE_Struct :
return SemanticHighlightings.CLASS;
case ISourceTag.STYLE_Union :
return SemanticHighlightings.CLASS;
case ISourceTag.STYLE_Function :
return SemanticHighlightings.FUNCTION_DECLARATION;
case ISourceTag.STYLE_Method :
return SemanticHighlightings.METHOD_DECLARATION;
case ISourceTag.STYLE_Variable :
return SemanticHighlightings.GLOBAL_VARIABLE;
case ISourceTag.STYLE_MemberVariable :
return SemanticHighlightings.FIELD;
case ISourceTag.STYLE_Enumerator :
return SemanticHighlightings.ENUMERATOR;
case ISourceTag.STYLE_Macro :
return SemanticHighlightings.MACRO_DEFINITION;
case ISourceTag.STYLE_Include :
// include is colored by the scanner
return null;
case ISourceTag.STYLE_Enumeration :
return SemanticHighlightings.ENUM;
case ISourceTag.STYLE_Undefined :
return null;
case ISourceTag.STYLE_Typedef :
return SemanticHighlightings.TYPEDEF;
default :
return null;
}
}
@Override
public void sourceTagsChanged(ISourceTagProvider sourceTagProvider) {
fSourceTags.clear();
if (sourceTagProvider != null) {
sourceTagProvider.getSourceTags(fSourceTags);
Collections.sort(fSourceTags, new Comparator<Object>() {
@Override
public int compare(Object o1, Object o2) {
ISourceRange sr1 = ((ISourceTag)o1).getRangeOfIdentifier();
ISourceRange sr2 = ((ISourceTag)o2).getRangeOfIdentifier();
return (sr1.getBeginOffset() - sr2.getBeginOffset());
}
});
}
}
/*
* @see org.eclipse.jface.text.rules.DefaultDamagerRepairer#addRange(org.eclipse.jface.text.TextPresentation, int, int, org.eclipse.jface.text.TextAttribute)
*/
@Override
protected void addRange(TextPresentation presentation, int offset, int length, TextAttribute attr) {
if (length > 0 && attr != null) {
presentation.addStyleRange(
new StyleRange(offset, length, attr.getForeground(), attr.getBackground(), attr.getStyle()));
}
}
/**
* Test whether the given preference change affects us.
*
* @param event
* @return <code>true</code> if the given event affects the behavior.
*/
public boolean affectsBahvior(PropertyChangeEvent event) {
return event.getProperty().startsWith(PreferenceConstants.EDITOR_SEMANTIC_HIGHLIGHTING_PREFIX);
}
/**
* Adapt to changes in the preferences.
*
* @param event
*/
public void handlePropertyChangeEvent(PropertyChangeEvent event) {
initTextAttributes();
}
}