/*******************************************************************************
* Copyright (c) 2005, 2011 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
* QNX Software System
* Wind River Systems
*******************************************************************************/
package org.eclipse.cdt.internal.ui.editor.asm;
import java.util.Iterator;
import org.eclipse.jface.action.GroupMarker;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.ITextViewerExtension5;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.source.Annotation;
import org.eclipse.jface.text.source.IAnnotationModel;
import org.eclipse.jface.text.source.IAnnotationModelExtension2;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.SourceViewerConfiguration;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IEditorActionBarContributor;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IPartService;
import org.eclipse.ui.IWorkbenchWindow;
import org.eclipse.ui.editors.text.TextEditor;
import org.eclipse.ui.navigator.ICommonMenuConstants;
import org.eclipse.ui.part.EditorActionBarContributor;
import org.eclipse.ui.texteditor.ITextEditorActionConstants;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
import org.eclipse.cdt.core.model.CModelException;
import org.eclipse.cdt.core.model.ICElement;
import org.eclipse.cdt.core.model.ISourceRange;
import org.eclipse.cdt.core.model.ISourceReference;
import org.eclipse.cdt.core.model.ITranslationUnit;
import org.eclipse.cdt.core.model.IWorkingCopy;
import org.eclipse.cdt.ui.CDTUITools;
import org.eclipse.cdt.ui.CUIPlugin;
import org.eclipse.cdt.ui.IWorkingCopyManager;
import org.eclipse.cdt.ui.text.AsmSourceViewerConfiguration;
import org.eclipse.cdt.ui.text.ICPartitions;
import org.eclipse.cdt.ui.text.IColorManager;
import org.eclipse.cdt.internal.ui.IContextMenuConstants;
import org.eclipse.cdt.internal.ui.editor.AbstractCModelOutlinePage;
import org.eclipse.cdt.internal.ui.editor.CAnnotationIterator;
import org.eclipse.cdt.internal.ui.editor.ICAnnotation;
/**
* Assembly text editor.
*/
public class AsmTextEditor extends TextEditor implements ISelectionChangedListener {
/**
* Updates the outline page selection and this editor's range indicator.
*/
private class EditorSelectionChangedListener extends AbstractSelectionChangedListener {
/*
* @see org.eclipse.jface.viewers.ISelectionChangedListener#selectionChanged(org.eclipse.jface.viewers.SelectionChangedEvent)
*/
public void selectionChanged(SelectionChangedEvent event) {
AsmTextEditor.this.selectionChanged();
}
}
private AbstractCModelOutlinePage fOutlinePage;
private EditorSelectionChangedListener fEditorSelectionChangedListener;
/**
* Creates a new assembly text editor.
*/
public AsmTextEditor() {
super();
}
/**
* Initializes this editor.
*/
@Override
protected void initializeEditor() {
IPreferenceStore store= CUIPlugin.getDefault().getCombinedPreferenceStore();
// FIXME: Should this editor have a different preference store ?
// For now we are sharing with the CEditor and any changes in the
// setting of the CEditor will be reflected in this editor.
setPreferenceStore(store);
final IColorManager colorManager = CDTUITools.getColorManager();
setSourceViewerConfiguration(new AsmSourceViewerConfiguration(colorManager, store, this, ICPartitions.C_PARTITIONING));
setDocumentProvider(CUIPlugin.getDefault().getDocumentProvider());
setEditorContextMenuId("#ASMEditorContext"); //$NON-NLS-1$
setRulerContextMenuId("#ASMEditorRulerContext"); //$NON-NLS-1$
}
/*
* @see org.eclipse.ui.texteditor.AbstractDecoratedTextEditor#collectContextMenuPreferencePages()
*/
@Override
protected String[] collectContextMenuPreferencePages() {
// Add Assembly Editor relevant pages
String[] parentPrefPageIds = super.collectContextMenuPreferencePages();
String[] prefPageIds = new String[parentPrefPageIds.length + 1];
int nIds = 0;
prefPageIds[nIds++] = "org.eclipse.cdt.ui.preferences.CodeColoringPreferencePage"; //$NON-NLS-1$
System.arraycopy(parentPrefPageIds, 0, prefPageIds, nIds, parentPrefPageIds.length);
return prefPageIds;
}
/*
* @see org.eclipse.ui.editors.text.TextEditor#getAdapter(java.lang.Class)
*/
@SuppressWarnings("rawtypes")
@Override
public Object getAdapter(Class adapter) {
if (IContentOutlinePage.class.equals(adapter)) {
return getOutlinePage();
}
return super.getAdapter(adapter);
}
/*
* @see org.eclipse.ui.texteditor.AbstractDecoratedTextEditor#createPartControl(org.eclipse.swt.widgets.Composite)
*/
@Override
public void createPartControl(Composite parent) {
super.createPartControl(parent);
// PlatformUI.getWorkbench().getHelpSystem().setHelp(parent, ICHelpContextIds.ASMEDITOR_VIEW);
fEditorSelectionChangedListener = new EditorSelectionChangedListener();
fEditorSelectionChangedListener.install(getSelectionProvider());
}
/*
* @see org.eclipse.ui.editors.text.TextEditor#dispose()
*/
@Override
public void dispose() {
if (fOutlinePage != null) {
fOutlinePage.dispose();
fOutlinePage = null;
}
if (fEditorSelectionChangedListener != null) {
fEditorSelectionChangedListener.uninstall(getSelectionProvider());
fEditorSelectionChangedListener = null;
}
super.dispose();
}
/*
* @see AbstractTextEditor#affectsTextPresentation(PropertyChangeEvent)
* Pulled in from 2.0
*/
@Override
protected boolean affectsTextPresentation(PropertyChangeEvent event) {
SourceViewerConfiguration configuration = getSourceViewerConfiguration();
if (configuration instanceof AsmSourceViewerConfiguration) {
return ((AsmSourceViewerConfiguration)configuration).affectsTextPresentation(event);
}
return false;
}
/*
* @see org.eclipse.ui.editors.text.TextEditor#handlePreferenceStoreChanged(org.eclipse.jface.util.PropertyChangeEvent)
*/
@Override
protected void handlePreferenceStoreChanged(PropertyChangeEvent event) {
SourceViewerConfiguration configuration = getSourceViewerConfiguration();
if (configuration instanceof AsmSourceViewerConfiguration) {
((AsmSourceViewerConfiguration)configuration).handlePropertyChangeEvent(event);
}
super.handlePreferenceStoreChanged(event);
}
/*
* @see org.eclipse.ui.editors.text.TextEditor#editorContextMenuAboutToShow(org.eclipse.jface.action.IMenuManager)
*/
@Override
protected void editorContextMenuAboutToShow(IMenuManager menu) {
// marker for contributions to the top
menu.add(new GroupMarker(ICommonMenuConstants.GROUP_TOP));
// separator for debug related actions (similar to ruler context menu)
menu.add(new Separator(IContextMenuConstants.GROUP_DEBUG));
menu.add(new GroupMarker(IContextMenuConstants.GROUP_DEBUG+".end")); //$NON-NLS-1$
super.editorContextMenuAboutToShow(menu);
}
/**
* Gets the outline page for this editor.
* @return Outline page.
*/
public AbstractCModelOutlinePage getOutlinePage() {
if (fOutlinePage == null) {
fOutlinePage = new AsmContentOutlinePage(this);
fOutlinePage.addSelectionChangedListener(this);
}
setOutlinePageInput(fOutlinePage, getEditorInput());
return fOutlinePage;
}
/**
* Sets an input for the outline page.
* @param page Page to set the input.
* @param input Input to set.
*/
public static void setOutlinePageInput(AbstractCModelOutlinePage page, IEditorInput input) {
if (page != null) {
IWorkingCopyManager manager = CUIPlugin.getDefault().getWorkingCopyManager();
page.setInput(manager.getWorkingCopy(input));
}
}
/**
* React to changed selection in the editor.
*/
protected void selectionChanged() {
if (getSelectionProvider() == null)
return;
ISourceReference element= computeHighlightRangeSourceReference();
updateStatusLine();
synchronizeOutlinePage();
setSelection(element, false);
}
/**
* Synchronizes the outline view selection with the given element
* position in the editor.
*/
protected void synchronizeOutlinePage() {
if(fOutlinePage != null && fOutlinePage.isLinkingEnabled()) {
fOutlinePage.removeSelectionChangedListener(this);
fOutlinePage.synchronizeSelectionWithEditor();
fOutlinePage.addSelectionChangedListener(this);
}
}
protected void updateStatusLine() {
ITextSelection selection = (ITextSelection) getSelectionProvider().getSelection();
Annotation annotation = getAnnotation(selection.getOffset(), selection.getLength());
setStatusLineErrorMessage(null);
setStatusLineMessage(null);
if (annotation != null) {
updateMarkerViews(annotation);
if (annotation instanceof ICAnnotation && ((ICAnnotation) annotation).isProblem())
setStatusLineMessage(annotation.getText());
}
}
/**
* Returns the annotation overlapping with the given range or <code>null</code>.
*
* @param offset the region offset
* @param length the region length
* @return the found annotation or <code>null</code>
*/
private Annotation getAnnotation(int offset, int length) {
IAnnotationModel model= getDocumentProvider().getAnnotationModel(getEditorInput());
if (model == null)
return null;
@SuppressWarnings("rawtypes")
Iterator parent;
if (model instanceof IAnnotationModelExtension2) {
parent= ((IAnnotationModelExtension2)model).getAnnotationIterator(offset, length, true, true);
} else {
parent= model.getAnnotationIterator();
}
@SuppressWarnings("unchecked")
Iterator<Annotation> e= new CAnnotationIterator(parent, false);
while (e.hasNext()) {
Annotation a = e.next();
if (!isNavigationTarget(a))
continue;
Position p = model.getPosition(a);
if (p != null && p.overlapsWith(offset, length))
return a;
}
return null;
}
/**
* Get the StatusLineManager.
*/
@Override
protected IStatusLineManager getStatusLineManager() {
IEditorActionBarContributor contributor = getEditorSite().getActionBarContributor();
if (contributor instanceof EditorActionBarContributor) {
return ((EditorActionBarContributor) contributor).getActionBars().getStatusLineManager();
}
return null;
}
/**
* Computes and returns the source reference that includes the caret and
* serves as provider for the outline page selection and the editor range
* indication.
*
* @return the computed source reference
*/
protected ISourceReference computeHighlightRangeSourceReference() {
ISourceViewer sourceViewer= getSourceViewer();
if (sourceViewer == null)
return null;
StyledText styledText= sourceViewer.getTextWidget();
if (styledText == null)
return null;
int caret= 0;
if (sourceViewer instanceof ITextViewerExtension5) {
ITextViewerExtension5 extension= (ITextViewerExtension5)sourceViewer;
caret= extension.widgetOffset2ModelOffset(styledText.getSelection().x);
} else {
int offset= sourceViewer.getVisibleRegion().getOffset();
caret= offset + styledText.getSelection().x;
}
ICElement element= getElementAt(caret, false);
if ( !(element instanceof ISourceReference))
return null;
return (ISourceReference) element;
}
/**
* Returns the most narrow element including the given offset. If <code>reconcile</code>
* is <code>true</code> the editor's input element is reconciled in advance. If it is
* <code>false</code> this method only returns a result if the editor's input element
* does not need to be reconciled.
*
* @param offset the offset included by the retrieved element
* @param reconcile <code>true</code> if working copy should be reconciled
* @return the most narrow element which includes the given offset
*/
protected ICElement getElementAt(int offset, boolean reconcile) {
ITranslationUnit unit= (ITranslationUnit)getInputCElement();
if (unit != null) {
try {
if (reconcile && unit instanceof IWorkingCopy) {
synchronized (unit) {
((IWorkingCopy) unit).reconcile();
}
return unit.getElementAtOffset(offset);
} else if (unit.isConsistent()) {
return unit.getElementAtOffset(offset);
}
} catch (CModelException x) {
CUIPlugin.log(x.getStatus());
// nothing found, be tolerant and go on
}
}
return null;
}
/**
* Returns the C element wrapped by this editors input.
*
* @return the C element wrapped by this editors input.
*/
public ICElement getInputCElement () {
return CUIPlugin.getDefault().getWorkingCopyManager().getWorkingCopy(getEditorInput());
}
/**
* React to changed selection in the outline view.
* @see org.eclipse.jface.viewers.ISelectionChangedListener#selectionChanged(org.eclipse.jface.viewers.SelectionChangedEvent)
*/
public void selectionChanged(SelectionChangedEvent event) {
ISelection sel = event.getSelection();
if (sel instanceof IStructuredSelection) {
IStructuredSelection selection = (IStructuredSelection) sel;
Object obj = selection.getFirstElement();
if (obj instanceof ISourceReference) {
try {
ISourceRange range = ((ISourceReference) obj).getSourceRange();
if (range != null) {
setSelection(range, !isActivePart());
}
} catch (CModelException e) {
// Selection change not applied.
}
}
}
}
/**
* Checks is the editor active part.
* @return <code>true</code> if editor is the active part of the workbench.
*/
private boolean isActivePart() {
IWorkbenchWindow window = getSite().getWorkbenchWindow();
IPartService service = window.getPartService();
return (this == service.getActivePart());
}
/**
* Sets selection for C element.
* @param element Element to select.
*/
public void setSelection(ICElement element) {
if (element instanceof ISourceReference && !(element instanceof ITranslationUnit)) {
ISourceReference reference = (ISourceReference) element;
// set hightlight range
setSelection(reference, true);
}
}
/**
* Sets selection for source reference.
* @param element Source reference to set.
* @param moveCursor Should cursor be moved.
*/
public void setSelection(ISourceReference element, boolean moveCursor) {
if (element != null) {
StyledText textWidget = null;
ISourceViewer sourceViewer = getSourceViewer();
if (sourceViewer != null)
textWidget = sourceViewer.getTextWidget();
if (textWidget == null)
return;
try {
setSelection(element.getSourceRange(), moveCursor);
} catch (CModelException e) {
// Selection not applied.
}
}
}
/**
* Sets the current editor selection to the source range. Optionally
* sets the current editor position.
*
* @param element the source range to be shown in the editor, can be null.
* @param moveCursor if true the editor is scrolled to show the range.
*/
public void setSelection(ISourceRange element, boolean moveCursor) {
if (getSelectionProvider() == null)
return;
ISelection selection= getSelectionProvider().getSelection();
if (selection instanceof ITextSelection) {
ITextSelection textSelection= (ITextSelection) selection;
// PR 39995: [navigation] Forward history cleared after going back in navigation history:
// mark only in navigation history if the cursor is being moved (which it isn't if
// this is called from a PostSelectionEvent that should only update the magnet)
if (moveCursor && (textSelection.getOffset() != 0 || textSelection.getLength() != 0))
markInNavigationHistory();
}
if (element != null) {
StyledText textWidget= null;
ISourceViewer sourceViewer= getSourceViewer();
if (sourceViewer == null)
return;
textWidget= sourceViewer.getTextWidget();
if (textWidget == null)
return;
try {
IRegion alternateRegion = null;
int start = element.getStartPos();
int length = element.getLength();
// Sanity check sometimes the parser may throw wrong numbers.
if (start < 0 || length < 0) {
start = 0;
length = 0;
}
// 0 length and start and non-zero start line says we know
// the line for some reason, but not the offset.
if (length == 0 && start == 0 && element.getStartLine() > 0) {
// We have the information in term of lines, we can work it out.
// Binary elements return the first executable statement so we have to subtract -1
start = getDocumentProvider().getDocument(getEditorInput()).getLineOffset(element.getStartLine() - 1);
if (element.getEndLine() > 0) {
length = getDocumentProvider().getDocument(getEditorInput()).getLineOffset(element.getEndLine()) - start;
} else {
length = start;
}
// create an alternate region for the keyword highlight.
alternateRegion = getDocumentProvider().getDocument(getEditorInput()).getLineInformation(element.getStartLine() - 1);
if (start == length || length < 0) {
if (alternateRegion != null) {
start = alternateRegion.getOffset();
length = alternateRegion.getLength();
}
}
}
setHighlightRange(start, length, moveCursor);
if (moveCursor) {
start = element.getIdStartPos();
length = element.getIdLength();
if (start == 0 && length == 0 && alternateRegion != null) {
start = alternateRegion.getOffset();
length = alternateRegion.getLength();
}
if (start > -1 && length > 0) {
try {
textWidget.setRedraw(false);
sourceViewer.revealRange(start, length);
sourceViewer.setSelectedRange(start, length);
} finally {
textWidget.setRedraw(true);
}
markInNavigationHistory();
}
updateStatusField(ITextEditorActionConstants.STATUS_CATEGORY_INPUT_POSITION);
}
} catch (IllegalArgumentException x) {
// No information to the user
} catch (BadLocationException e) {
// No information to the user
}
} else if (moveCursor) {
resetHighlightRange();
markInNavigationHistory();
}
}
}