blob: da2022ed3880c8838022b10e1708471b4072795b [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2016 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.debug.ui;
import org.eclipse.jdt.internal.debug.ui.display.DisplayViewerConfiguration;
import org.eclipse.jdt.ui.PreferenceConstants;
import org.eclipse.jdt.ui.text.IJavaPartitions;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.text.contentassist.ContentAssistant;
import org.eclipse.jface.text.contentassist.IContentAssistant;
import org.eclipse.jface.text.source.IOverviewRuler;
import org.eclipse.jface.text.source.IVerticalRuler;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.text.source.SourceViewerConfiguration;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.BidiSegmentEvent;
import org.eclipse.swt.custom.BidiSegmentListener;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.texteditor.AbstractTextEditor;
import com.ibm.icu.text.Bidi;
/**
* A source viewer configured to display Java source. This
* viewer obeys the font and color preferences specified in
* the Java UI plugin.
*/
public class JDISourceViewer extends SourceViewer implements IPropertyChangeListener {
/**
* BIDI delimtiers.
*
* @since 3.4
*/
private static final String BIDI_DELIMITERS= "[ \\p{Punct}&&[^_]]"; //$NON-NLS-1$
private Font fFont;
private Color fBackgroundColor;
private Color fForegroundColor;
private IPreferenceStore fStore;
private DisplayViewerConfiguration fConfiguration;
public JDISourceViewer(Composite parent, IVerticalRuler ruler, int styles) {
this(parent, ruler, null, false, styles);
}
public JDISourceViewer(Composite parent, IVerticalRuler ruler, IOverviewRuler overviewRuler, boolean isOverviewRulerVisible, int styles) {
super(parent, ruler, overviewRuler, isOverviewRulerVisible, styles);
StyledText text= this.getTextWidget();
final int baseLevel= (styles & SWT.RIGHT_TO_LEFT) != 0 ? Bidi.DIRECTION_RIGHT_TO_LEFT : Bidi.DIRECTION_LEFT_TO_RIGHT;
text.addBidiSegmentListener(new BidiSegmentListener() {
@Override
public void lineGetSegments(BidiSegmentEvent event) {
try {
event.segments= getBidiLineSegments(getDocument(), baseLevel, widgetOffset2ModelOffset(event.lineOffset), event.lineText);
} catch (BadLocationException x) {
// ignore
}
}
});
}
/**
* Updates the viewer's font to match the preferences.
*/
private void updateViewerFont() {
IPreferenceStore store= getPreferenceStore();
if (store != null) {
FontData data= null;
if (store.contains(PreferenceConstants.EDITOR_TEXT_FONT) && !store.isDefault(PreferenceConstants.EDITOR_TEXT_FONT)) {
data= PreferenceConverter.getFontData(store, PreferenceConstants.EDITOR_TEXT_FONT);
} else {
data = PreferenceConverter.getDefaultFontData(store, PreferenceConstants.EDITOR_TEXT_FONT);
}
if (data != null) {
Font font= new Font(getTextWidget().getDisplay(), data);
applyFont(font);
if (getFont() != null) {
getFont().dispose();
}
setFont(font);
return;
}
}
// if all the preferences failed
applyFont(JFaceResources.getTextFont());
}
/**
* Sets the current font.
*
* @param font the new font
*/
private void setFont(Font font) {
fFont= font;
}
/**
* Returns the current font.
*
* @return the current font
*/
private Font getFont() {
return fFont;
}
/**
* Sets the font for the given viewer sustaining selection and scroll position.
*
* @param font the font
*/
private void applyFont(Font font) {
IDocument doc= getDocument();
if (doc != null && doc.getLength() > 0) {
Point selection= getSelectedRange();
int topIndex= getTopIndex();
StyledText styledText= getTextWidget();
styledText.setRedraw(false);
styledText.setFont(font);
setSelectedRange(selection.x , selection.y);
setTopIndex(topIndex);
styledText.setRedraw(true);
} else {
getTextWidget().setFont(font);
}
}
/**
* Updates the given viewer's colors to match the preferences.
*/
public void updateViewerColors() {
IPreferenceStore store= getPreferenceStore();
if (store != null) {
StyledText styledText= getTextWidget();
Color color= store.getBoolean(AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT)
? null
: createColor(store, AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND, styledText.getDisplay());
styledText.setForeground(color);
if (getForegroundColor() != null) {
getForegroundColor().dispose();
}
setForegroundColor(color);
color= store.getBoolean(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT)
? null
: createColor(store, AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND, styledText.getDisplay());
styledText.setBackground(color);
if (getBackgroundColor() != null) {
getBackgroundColor().dispose();
}
setBackgroundColor(color);
}
}
/**
* Creates a color from the information stored in the given preference store.
* Returns <code>null</code> if there is no such information available.
*/
private Color createColor(IPreferenceStore store, String key, Display display) {
RGB rgb= null;
if (store.contains(key)) {
if (store.isDefault(key)) {
rgb= PreferenceConverter.getDefaultColor(store, key);
} else {
rgb= PreferenceConverter.getColor(store, key);
}
if (rgb != null) {
return new Color(display, rgb);
}
}
return null;
}
/**
* Returns the current background color.
*
* @return the current background color
*/
protected Color getBackgroundColor() {
return fBackgroundColor;
}
/**
* Sets the current background color.
*
* @param backgroundColor the new background color
*/
protected void setBackgroundColor(Color backgroundColor) {
fBackgroundColor = backgroundColor;
}
/**
* Returns the current foreground color.
*
* @return the current foreground color
*/
protected Color getForegroundColor() {
return fForegroundColor;
}
/**
* Sets the current foreground color.
*
* @param foregroundColor the new foreground color
*/
protected void setForegroundColor(Color foregroundColor) {
fForegroundColor = foregroundColor;
}
/**
* @see IPropertyChangeListener#propertyChange(PropertyChangeEvent)
*/
@Override
public void propertyChange(PropertyChangeEvent event) {
IContentAssistant assistant= getContentAssistant();
if (assistant instanceof ContentAssistant) {
JDIContentAssistPreference.changeConfiguration((ContentAssistant) assistant, event);
}
String property= event.getProperty();
if (PreferenceConstants.EDITOR_TEXT_FONT.equals(property)) {
updateViewerFont();
}
if (AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND.equals(property) || AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT.equals(property) ||
AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND.equals(property) || AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT.equals(property)) {
updateViewerColors();
}
if (fConfiguration != null) {
if (fConfiguration.affectsTextPresentation(event)) {
fConfiguration.handlePropertyChangeEvent(event);
invalidateTextPresentation();
}
}
}
/**
* Returns the current content assistant.
*
* @return the current content assistant
*/
public IContentAssistant getContentAssistant() {
return fContentAssistant;
}
/**
* Disposes the system resources currently in use by this viewer.
*/
public void dispose() {
if (getFont() != null) {
getFont().dispose();
setFont(null);
}
if (getBackgroundColor() != null) {
getBackgroundColor().dispose();
setBackgroundColor(null);
}
if (getForegroundColor() != null) {
getForegroundColor().dispose();
setForegroundColor(null);
}
if (fStore != null) {
fStore.removePropertyChangeListener(this);
fStore = null;
}
}
/* (non-Javadoc)
* @see org.eclipse.jface.text.source.SourceViewer#configure(org.eclipse.jface.text.source.SourceViewerConfiguration)
*/
@Override
public void configure(SourceViewerConfiguration configuration) {
super.configure(configuration);
if (fStore != null) {
fStore.removePropertyChangeListener(this);
fStore = null;
}
if (configuration instanceof DisplayViewerConfiguration) {
fConfiguration = (DisplayViewerConfiguration) configuration;
fStore = fConfiguration.getTextPreferenceStore();
fStore.addPropertyChangeListener(this);
}
updateViewerFont();
updateViewerColors();
}
/**
* Returns the preference store used to configure this source viewer or
* <code>null</code> if none;
*/
private IPreferenceStore getPreferenceStore() {
return fStore;
}
/**
* Returns a segmentation of the line of the given document appropriate for Bidi rendering.
*
* @param document the document
* @param baseLevel the base level of the line
* @param lineStart the offset of the line
* @param lineText Text of the line to retrieve Bidi segments for
* @return the line's Bidi segmentation
* @throws BadLocationException in case lineOffset is not valid in document
*/
protected static int[] getBidiLineSegments(IDocument document, int baseLevel, int lineStart, String lineText) throws BadLocationException {
if (lineText == null || document == null) {
return null;
}
int lineLength= lineText.length();
if (lineLength <= 2) {
return null;
}
// Have ICU compute embedding levels. Consume these levels to reduce
// the Bidi impact, by creating selective segments (preceding
// character runs with a level mismatching the base level).
// XXX: Alternatively, we could apply TextLayout. Pros would be full
// synchronization with the underlying StyledText's (i.e. native) Bidi
// implementation. Cons are performance penalty because of
// unavailability of such methods as isLeftToRight and getLevels.
Bidi bidi= new Bidi(lineText, baseLevel);
if (bidi.isLeftToRight()) {
// Bail out if this is not Bidi text.
return null;
}
IRegion line= document.getLineInformationOfOffset(lineStart);
ITypedRegion[] linePartitioning= TextUtilities.computePartitioning(document, IJavaPartitions.JAVA_PARTITIONING, lineStart, line.getLength(), false);
if (linePartitioning == null || linePartitioning.length < 1) {
return null;
}
int segmentIndex= 1;
int[] segments= new int[lineLength + 1];
byte[] levels= bidi.getLevels();
int nPartitions= linePartitioning.length;
for (int partitionIndex= 0; partitionIndex < nPartitions; partitionIndex++) {
ITypedRegion partition= linePartitioning[partitionIndex];
int lineOffset= partition.getOffset() - lineStart;
//Assert.isTrue(lineOffset >= 0 && lineOffset < lineLength);
if (lineOffset > 0 && isMismatchingLevel(levels[lineOffset], baseLevel) && isMismatchingLevel(levels[lineOffset - 1], baseLevel)) {
// Indicate a Bidi segment at the partition start - provided
// levels of both character at the current offset and its
// preceding character mismatch the base paragraph level.
// Partition end will be covered either by the start of the next
// partition, a delimiter inside a next partition, or end of line.
segments[segmentIndex++]= lineOffset;
}
if (IDocument.DEFAULT_CONTENT_TYPE.equals(partition.getType())) {
int partitionEnd= Math.min(lineLength, lineOffset + partition.getLength());
while (++lineOffset < partitionEnd) {
if (isMismatchingLevel(levels[lineOffset], baseLevel) && String.valueOf(lineText.charAt(lineOffset)).matches(BIDI_DELIMITERS)) {
// For default content types, indicate a segment before
// a delimiting character with a mismatching embedding
// level.
segments[segmentIndex++]= lineOffset;
}
}
}
}
if (segmentIndex <= 1) {
return null;
}
segments[0]= 0;
if (segments[segmentIndex - 1] != lineLength) {
segments[segmentIndex++]= lineLength;
}
if (segmentIndex == segments.length) {
return segments;
}
int[] newSegments= new int[segmentIndex];
System.arraycopy(segments, 0, newSegments, 0, segmentIndex);
return newSegments;
}
/**
* Checks if the given embedding level is consistent with the base level.
*
* @param level Character embedding level to check.
* @param baseLevel Base level (direction) of the text.
* @return <code>true</code> if the character level is odd and the base level is even OR the character level is even and the base level is odd, and return <code>false</code> otherwise.
*
* @since 3.4
*/
private static boolean isMismatchingLevel(int level, int baseLevel) {
return ((level ^ baseLevel) & 1) != 0;
}
}