/******************************************************************************* * Copyright (c) 2000, 2016 IBM Corporation 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: * 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; } }