| /******************************************************************************* |
| * 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); |
| setForegroundColor(color); |
| |
| color= store.getBoolean(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT) |
| ? null |
| : createColor(store, AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND, styledText.getDisplay()); |
| styledText.setBackground(color); |
| 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) { |
| setBackgroundColor(null); |
| } |
| if (getForegroundColor() != null) { |
| 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; |
| } |
| |
| } |