/*******************************************************************************
* Copyright (c) 2006, 2009 QNX Software 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:
* QNX Software Systems - initial API and implementation
* Sergey Prigogin, Google
* Anton Leherbauer (Wind River Systems)
* Markus Schorn (Wind River Systems)
*******************************************************************************/
package org.eclipse.cdt.internal.ui.editor;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentRewriteSession;
import org.eclipse.jface.text.DocumentRewriteSessionType;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension4;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextPresentationListener;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.jface.text.contentassist.IContentAssistant;
import org.eclipse.jface.text.information.IInformationPresenter;
import org.eclipse.jface.text.source.IOverviewRuler;
import org.eclipse.jface.text.source.IVerticalRuler;
import org.eclipse.jface.text.source.SourceViewerConfiguration;
import org.eclipse.jface.text.source.projection.ProjectionViewer;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.Color;
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.AbstractDecoratedTextEditorPreferenceConstants;
import org.eclipse.ui.texteditor.AbstractTextEditor;
import org.eclipse.cdt.ui.PreferenceConstants;
import org.eclipse.cdt.ui.text.CSourceViewerConfiguration;
/**
* Source viewer for C/C++ et al.
*/
public class CSourceViewer extends ProjectionViewer implements IPropertyChangeListener {
/** Show outline operation id. */
public static final int SHOW_OUTLINE= 101;
/** Show type hierarchy operation id. */
public static final int SHOW_HIERARCHY= 102;
/** Show macro explorer operation id. */
public static final int SHOW_MACRO_EXPLORER= 103;
/** Presents outline. */
private IInformationPresenter fOutlinePresenter;
/** Presents type hierarchy. */
private IInformationPresenter fHierarchyPresenter;
/** Presents macro explorer. */
private IInformationPresenter fMacroExplorationPresenter;
/**
* This viewer's foreground color.
* @since 4.0
*/
private Color fForegroundColor;
/**
* The viewer's background color.
* @since 4.0
*/
private Color fBackgroundColor;
/**
* This viewer's selection foreground color.
* @since 4.0
*/
private Color fSelectionForegroundColor;
/**
* The viewer's selection background color.
* @since 4.0
*/
private Color fSelectionBackgroundColor;
/**
* The preference store.
*
* @since 4.0
*/
private IPreferenceStore fPreferenceStore;
/**
* Is this source viewer configured?
*
* @since 4.0
*/
private boolean fIsConfigured;
/**
* Whether to delay setting the visual document until the projection has been computed.
* <p>
* Added for performance optimization.
* </p>
* @see #prepareDelayedProjection()
* @since 4.0
*/
private boolean fIsSetVisibleDocumentDelayed;
/**
* Whether projection mode was enabled when switching to segmented mode.
* Workaround for https://bugs.eclipse.org/bugs/show_bug.cgi?id=195808
*/
private boolean fWasProjectionMode;
/**
* The configured indent width.
*/
private int fIndentWidth= 4;
/**
* Flag indicating whether to use spaces exclusively for indentation.
*/
private boolean fUseSpaces;
/**
* Creates new source viewer.
* @param parent
* @param ruler
* @param overviewRuler
* @param isOverviewRulerShowing
* @param styles
* @param store
*/
public CSourceViewer(
Composite parent,
IVerticalRuler ruler,
IOverviewRuler overviewRuler,
boolean isOverviewRulerShowing,
int styles,
IPreferenceStore store) {
super(parent, ruler, overviewRuler, isOverviewRulerShowing, styles);
setPreferenceStore(store);
}
public IContentAssistant getContentAssistant() {
return fContentAssistant;
}
/*
* @see ISourceViewer#configure(SourceViewerConfiguration)
*/
@Override
public void configure(SourceViewerConfiguration configuration) {
// Prevent access to colors disposed in unconfigure().
StyledText textWidget= getTextWidget();
if (textWidget != null && !textWidget.isDisposed()) {
Color foregroundColor= textWidget.getForeground();
if (foregroundColor != null && foregroundColor.isDisposed())
textWidget.setForeground(null);
Color backgroundColor= textWidget.getBackground();
if (backgroundColor != null && backgroundColor.isDisposed())
textWidget.setBackground(null);
}
if (configuration instanceof CSourceViewerConfiguration) {
CSourceViewerConfiguration cConfiguration= (CSourceViewerConfiguration)configuration;
cConfiguration.resetScanners();
}
super.configure(configuration);
if (configuration instanceof CSourceViewerConfiguration) {
CSourceViewerConfiguration cConfiguration= (CSourceViewerConfiguration)configuration;
fOutlinePresenter= cConfiguration.getOutlinePresenter(this);
if (fOutlinePresenter != null)
fOutlinePresenter.install(this);
fHierarchyPresenter= cConfiguration.getHierarchyPresenter(this);
if (fHierarchyPresenter != null)
fHierarchyPresenter.install(this);
fMacroExplorationPresenter= cConfiguration.getMacroExplorationPresenter(this);
if (fMacroExplorationPresenter != null) {
fMacroExplorationPresenter.install(this);
}
String[] defaultIndentPrefixes= (String[])fIndentChars.get(IDocument.DEFAULT_CONTENT_TYPE);
if (defaultIndentPrefixes != null && defaultIndentPrefixes.length > 0) {
final int indentWidth= cConfiguration.getIndentWidth(this);
final boolean useSpaces= cConfiguration.useSpacesOnly(this);
configureIndentation(indentWidth, useSpaces);
}
}
if (fPreferenceStore != null) {
fPreferenceStore.addPropertyChangeListener(this);
initializeViewerColors();
// init flag here in case we start in segmented mode
fWasProjectionMode= fPreferenceStore.getBoolean(PreferenceConstants.EDITOR_FOLDING_ENABLED);
}
fIsConfigured= true;
}
protected void initializeViewerColors() {
if (fPreferenceStore != null) {
StyledText styledText= getTextWidget();
// ----------- foreground color --------------------
Color color= fPreferenceStore.getBoolean(AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND_SYSTEM_DEFAULT)
? null
: createColor(fPreferenceStore, AbstractTextEditor.PREFERENCE_COLOR_FOREGROUND, styledText.getDisplay());
styledText.setForeground(color);
if (fForegroundColor != null)
fForegroundColor.dispose();
fForegroundColor= color;
// ---------- background color ----------------------
color= fPreferenceStore.getBoolean(AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND_SYSTEM_DEFAULT)
? null
: createColor(fPreferenceStore, AbstractTextEditor.PREFERENCE_COLOR_BACKGROUND, styledText.getDisplay());
styledText.setBackground(color);
if (fBackgroundColor != null)
fBackgroundColor.dispose();
fBackgroundColor= color;
// ----------- selection foreground color --------------------
color= fPreferenceStore.getBoolean(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SELECTION_FOREGROUND_DEFAULT_COLOR)
? null
: createColor(fPreferenceStore, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SELECTION_FOREGROUND_COLOR, styledText.getDisplay());
styledText.setSelectionForeground(color);
if (fSelectionForegroundColor != null)
fSelectionForegroundColor.dispose();
fSelectionForegroundColor= color;
// ---------- selection background color ----------------------
color= fPreferenceStore.getBoolean(AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SELECTION_BACKGROUND_DEFAULT_COLOR)
? null
: createColor(fPreferenceStore, AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SELECTION_BACKGROUND_COLOR, styledText.getDisplay());
styledText.setSelectionBackground(color);
if (fSelectionBackgroundColor != null)
fSelectionBackgroundColor.dispose();
fSelectionBackgroundColor= color;
}
}
/**
* Creates a color from the information stored in the given preference store.
* Returns <code>null</code> if there is no such information available.
*
* @param store the store to read from
* @param key the key used for the lookup in the preference store
* @param display the display used create the color
* @return the created color according to the specification in the preference store
*/
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;
}
/*
* @see org.eclipse.jface.text.source.SourceViewer#unconfigure()
*/
@Override
public void unconfigure() {
if (fOutlinePresenter != null) {
fOutlinePresenter.uninstall();
fOutlinePresenter= null;
}
if (fHierarchyPresenter != null) {
fHierarchyPresenter.uninstall();
fHierarchyPresenter= null;
}
if (fMacroExplorationPresenter != null) {
fMacroExplorationPresenter.uninstall();
fMacroExplorationPresenter= null;
}
if (fForegroundColor != null) {
fForegroundColor.dispose();
fForegroundColor= null;
}
if (fBackgroundColor != null) {
fBackgroundColor.dispose();
fBackgroundColor= null;
}
if (fPreferenceStore != null)
fPreferenceStore.removePropertyChangeListener(this);
super.unconfigure();
fIsConfigured= false;
}
/*
* @see org.eclipse.jface.util.IPropertyChangeListener#propertyChange(org.eclipse.jface.util.PropertyChangeEvent)
*/
public void propertyChange(PropertyChangeEvent event) {
String property= event.getProperty();
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)
|| AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SELECTION_FOREGROUND_COLOR.equals(property)
|| AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SELECTION_FOREGROUND_DEFAULT_COLOR.equals(property)
|| AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SELECTION_BACKGROUND_COLOR.equals(property)
|| AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SELECTION_BACKGROUND_DEFAULT_COLOR.equals(property))
{
initializeViewerColors();
}
}
/**
* Sets the preference store on this viewer.
*
* @param store the preference store
*
* @since 4.0
*/
public void setPreferenceStore(IPreferenceStore store) {
if (fIsConfigured && fPreferenceStore != null)
fPreferenceStore.removePropertyChangeListener(this);
fPreferenceStore= store;
if (fIsConfigured && fPreferenceStore != null) {
fPreferenceStore.addPropertyChangeListener(this);
initializeViewerColors();
}
}
/*
* @see org.eclipse.jface.text.source.SourceViewer#createControl(org.eclipse.swt.widgets.Composite, int)
*/
@Override
protected void createControl(Composite parent, int styles) {
// Use LEFT_TO_RIGHT unless otherwise specified.
if ((styles & SWT.RIGHT_TO_LEFT) == 0 && (styles & SWT.LEFT_TO_RIGHT) == 0)
styles |= SWT.LEFT_TO_RIGHT;
super.createControl(parent, styles);
}
/*
* @see org.eclipse.jface.text.ITextOperationTarget#doOperation(int)
*/
@Override
public void doOperation(int operation) {
if (getTextWidget() == null) {
return;
}
switch (operation) {
case SHOW_OUTLINE:
fOutlinePresenter.showInformation();
return;
case SHOW_HIERARCHY:
fHierarchyPresenter.showInformation();
return;
case SHOW_MACRO_EXPLORER:
fMacroExplorationPresenter.showInformation();
}
super.doOperation(operation);
}
/*
* @see org.eclipse.jface.text.source.projection.ProjectionViewer#canDoOperation(int)
*/
@Override
public boolean canDoOperation(int operation) {
switch (operation) {
case SHOW_OUTLINE:
return fOutlinePresenter != null;
case SHOW_HIERARCHY:
return fHierarchyPresenter != null;
case SHOW_MACRO_EXPLORER:
return fMacroExplorationPresenter != null;
}
return super.canDoOperation(operation);
}
/**
* Prepend given listener to the list of presentation listeners
*
* @param listener The listener to be added.
*
* @see TextViewer#addTextPresentationListener(ITextPresentationListener)
* @since 4.0
*/
public void prependTextPresentationListener(ITextPresentationListener listener) {
Assert.isNotNull(listener);
@SuppressWarnings("unchecked") // using list from base class
List<ITextPresentationListener> textPresentationListeners= fTextPresentationListeners;
if (textPresentationListeners == null)
fTextPresentationListeners= textPresentationListeners= new ArrayList<ITextPresentationListener>();
textPresentationListeners.remove(listener);
textPresentationListeners.add(0, listener);
}
/**
* Delays setting the visual document until after the projection has been computed.
* This method must only be called before the document is set on the viewer.
* <p>
* This is a performance optimization to reduce the computation of
* the text presentation triggered by <code>setVisibleDocument(IDocument)</code>.
* </p>
*
* @see #setVisibleDocument(IDocument)
* @since 4.0
*/
void prepareDelayedProjection() {
Assert.isTrue(!fIsSetVisibleDocumentDelayed);
fIsSetVisibleDocumentDelayed= true;
}
/**
* {@inheritDoc}
* <p>
* This is a performance optimization to reduce the computation of
* the text presentation triggered by {@link #setVisibleDocument(IDocument)}
* </p>
* @since 4.0
*/
@Override
protected void setVisibleDocument(IDocument document) {
if (fIsSetVisibleDocumentDelayed) {
fIsSetVisibleDocumentDelayed= false;
IDocument previous= getVisibleDocument();
enableProjection(); // will set the visible document if anything is folded
IDocument current= getVisibleDocument();
// if the visible document was not replaced, continue as usual
if (current != null && current != previous)
return;
}
super.setVisibleDocument(document);
}
/**
* {@inheritDoc}
* <p>
* Performance optimization: since we know at this place
* that none of the clients expects the given range to be
* untouched we reuse the given range as return value.
* </p>
*/
@Override
protected StyleRange modelStyleRange2WidgetStyleRange(StyleRange range) {
IRegion region= modelRange2WidgetRange(new Region(range.start, range.length));
if (region != null) {
// don't clone the style range, but simply reuse it.
range.start= region.getOffset();
range.length= region.getLength();
return range;
}
return null;
}
/*
* @see org.eclipse.jface.text.source.projection.ProjectionViewer#setVisibleRegion(int, int)
*/
@Override
public void setVisibleRegion(int start, int length) {
// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=195808
if (!fWasProjectionMode && isProjectionMode()) {
fWasProjectionMode= true;
}
super.setVisibleRegion(start, length);
}
/*
* @see org.eclipse.jface.text.source.projection.ProjectionViewer#resetVisibleRegion()
*/
@Override
public void resetVisibleRegion() {
super.resetVisibleRegion();
// see https://bugs.eclipse.org/bugs/show_bug.cgi?id=195808
if (fWasProjectionMode) {
fWasProjectionMode= false;
enableProjection();
}
}
/**
* Configure the indentation mode for this viewer.
*
* @param indentWidth the indentation width
* @param useSpaces if <code>true</code>, only spaces are used for indentation
*/
public void configureIndentation(int indentWidth, boolean useSpaces) {
fIndentWidth= indentWidth;
fUseSpaces= useSpaces;
}
/*
* @see org.eclipse.jface.text.TextViewer#shift(boolean, boolean, boolean)
*/
@Override
protected void shift(boolean useDefaultPrefixes, boolean right, boolean ignoreWhitespace) {
if (!useDefaultPrefixes) {
// simple shift case
adjustIndent(right, fIndentWidth, fUseSpaces);
return;
}
super.shift(useDefaultPrefixes, right, ignoreWhitespace);
}
/**
* Increase/decrease indentation of current selection.
*
* @param increase if <code>true</code>, indent is increased by one unit
* @param shiftWidth width in spaces of one indent unit
* @param useSpaces if <code>true</code>, only spaces are used for indentation
*/
protected void adjustIndent(boolean increase, int shiftWidth, boolean useSpaces) {
if (fUndoManager != null) {
fUndoManager.beginCompoundChange();
}
IDocument d= getDocument();
DocumentRewriteSession rewriteSession= null;
try {
if (d instanceof IDocumentExtension4) {
IDocumentExtension4 extension= (IDocumentExtension4) d;
rewriteSession= extension.startRewriteSession(DocumentRewriteSessionType.SEQUENTIAL);
}
Point selection= getSelectedRange();
// perform the adjustment
int tabWidth= getTextWidget().getTabs();
int startLine= d.getLineOfOffset(selection.x);
int endLine= selection.y == 0 ? startLine : d.getLineOfOffset(selection.x + selection.y - 1);
for (int line= startLine; line <= endLine; ++line) {
IRegion lineRegion= d.getLineInformation(line);
String indent= IndentUtil.getCurrentIndent(d, line, false);
int indentWidth= IndentUtil.computeVisualLength(indent, tabWidth);
int newIndentWidth= Math.max(0, indentWidth + (increase ? shiftWidth : -shiftWidth));
String newIndent= IndentUtil.changePrefix(indent.trim(), newIndentWidth, tabWidth, useSpaces);
int commonLen= getCommonPrefixLength(indent, newIndent);
if (commonLen < Math.max(indent.length(), newIndent.length())) {
if (commonLen > 0) {
indent= indent.substring(commonLen);
newIndent= newIndent.substring(commonLen);
}
final int offset= lineRegion.getOffset() + commonLen;
if (!increase && newIndent.length() > indent.length() && indent.length() > 0) {
d.replace(offset, indent.length(), ""); //$NON-NLS-1$
d.replace(offset, 0, newIndent);
} else {
d.replace(offset, indent.length(), newIndent);
}
}
}
} catch (BadLocationException x) {
// ignored
} finally {
if (rewriteSession != null) {
((IDocumentExtension4)d).stopRewriteSession(rewriteSession);
}
if (fUndoManager != null) {
fUndoManager.endCompoundChange();
}
}
}
/**
* Compute the length of the common prefix of two strings.
*
* @param s1
* @param s2
* @return the length of the common prefix
*/
private static int getCommonPrefixLength(String s1, String s2) {
final int l1= s1.length();
final int l2= s2.length();
int i= 0;
while (i < l1 && i < l2 && s1.charAt(i) == s2.charAt(i)) {
++i;
}
return i;
}
/*
* work around for memory leak in TextViewer$WidgetCommand
*/
@Override
protected void updateTextListeners(WidgetCommand cmd) {
super.updateTextListeners(cmd);
cmd.preservedText= null;
cmd.event= null;
cmd.text= null;
}
}