/*******************************************************************************
* Copyright (c) 2008 Scott Stanchfield, based on ANTLR-Eclipse plugin
* by Torsten Juergeleit.
* 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
* Torsten Juergeleit - original ANTLR Eclipse plugin
* Scott Stanchfield - modifications for ANTXR
*******************************************************************************/
package com.javadude.antxr.eclipse.ui.editor;
import java.util.Iterator;
import org.eclipse.core.resources.IMarker;
import org.eclipse.jdt.ui.actions.IJavaEditorActionDefinitionIds;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextOperationTarget;
import org.eclipse.jface.text.ITextSelection;
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.ISourceViewer;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.ui.IEditorActionBarContributor;
import org.eclipse.ui.IPageLayout;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.ide.IGotoMarker;
import org.eclipse.ui.part.EditorActionBarContributor;
import org.eclipse.ui.texteditor.AbstractDecoratedTextEditor;
import org.eclipse.ui.texteditor.AbstractTextEditor;
import org.eclipse.ui.texteditor.ContentAssistAction;
import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
import org.eclipse.ui.texteditor.MarkerAnnotation;
import org.eclipse.ui.texteditor.MarkerUtilities;
import org.eclipse.ui.texteditor.TextOperationAction;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
import org.eclipse.ui.views.tasklist.TaskList;
import com.javadude.antxr.eclipse.core.parser.ISegment;
import com.javadude.antxr.eclipse.ui.AntxrUIPlugin;
import com.javadude.antxr.eclipse.ui.actions.GotoRuleAction;
import com.javadude.antxr.eclipse.ui.actions.IAntxrActionConstants;
import com.javadude.antxr.eclipse.ui.actions.IAntxrActionDefinitionIds;
import com.javadude.antxr.eclipse.ui.editor.outline.AntxrOutlinePage;
/**
* An editor for an ANTXR grammar
*/
public class AntxrEditor extends AbstractDecoratedTextEditor implements IGotoMarker {
private static final String PREFIX = "Editor.";
private AntxrMultiPageEditor fMultiPageEditor;
private ModelTools fModelTools;
private AntxrReconcilingStrategy fReconcilingStrategy;
/** The outline page */
private AntxrOutlinePage fOutlinePage;
/** The status line clearer */
private ISelectionChangedListener fStatusLineClearer;
/** Last cursor position (line) handled in
* <code>handleCursorPositionChanged()</code> */
private int fLastCursorLine;
/**
* Create the editor
* @param aMultiPageEditor the multi-page editor we're in
*/
public AntxrEditor(AntxrMultiPageEditor aMultiPageEditor) {
fMultiPageEditor = aMultiPageEditor;
fModelTools = new ModelTools(this);
fReconcilingStrategy = new AntxrReconcilingStrategy(this);
setEditorContextMenuId("#AntxrGrammarFilePopupContext"); //$NON-NLS-1$
setRulerContextMenuId("#AntxrGrammarFileRulerContext"); //$NON-NLS-1$
// setOutlinerContextMenuId("#ClassFileOutlinerContext"); //$NON-NLS-1$
}
/* (non-Javadoc)
* @see org.eclipse.ui.editors.text.TextEditor#initializeEditor()
*/
protected void initializeEditor() {
super.initializeEditor();
EditorEnvironment.connect();
setDocumentProvider(new AntxrDocumentProvider());
setSourceViewerConfiguration(new AntxrConfiguration(this));
}
/* (non-Javadoc)
* @see org.eclipse.ui.editors.text.TextEditor#initializeKeyBindingScopes()
*/
protected void initializeKeyBindingScopes() {
setKeyBindingScopes(new String[] { "com.javadude.antxr.ui.antxrEditorScope" });
}
/* (non-Javadoc)
* @see org.eclipse.ui.texteditor.AbstractTextEditor#createActions()
*/
protected void createActions() {
super.createActions();
// Add goto rule action
IAction action = new GotoRuleAction(
AntxrUIPlugin.getDefault().getResourceBundle(),
AntxrEditor.PREFIX + "GotoRule.", this);
action.setActionDefinitionId(IAntxrActionDefinitionIds.GOTO_RULE);
setAction(IAntxrActionConstants.GOTO_RULE, action);
// Add content assist propsal action
action = new ContentAssistAction(
AntxrUIPlugin.getDefault().getResourceBundle(),
AntxrEditor.PREFIX + "ContentAssist.", this);
action.setActionDefinitionId(
ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS);
setAction(IAntxrActionConstants.CONTENT_ASSIST, action);
// Add comment action
action = new TextOperationAction(
AntxrUIPlugin.getDefault().getResourceBundle(),
AntxrEditor.PREFIX + "Comment.", this, ITextOperationTarget.PREFIX);
action.setActionDefinitionId(IJavaEditorActionDefinitionIds.COMMENT);
setAction(IAntxrActionConstants.COMMENT, action);
// Add uncomment action
action = new TextOperationAction(
AntxrUIPlugin.getDefault().getResourceBundle(),
AntxrEditor.PREFIX + "Uncomment.", this, ITextOperationTarget.STRIP_PREFIX);
action.setActionDefinitionId(IJavaEditorActionDefinitionIds.UNCOMMENT);
setAction(IAntxrActionConstants.UNCOMMENT, action);
}
/**
* The <code>AntxrEditor</code> implementation of this
* <code>AbstractTextEditor</code> method gets the ANTXR content outline
* page if request is for a an outline page.
* @param aClass the type to adapt to
* @return the adapter
*/
public Object getAdapter(Class aClass) {
Object adapter;
if (aClass.equals(IContentOutlinePage.class)) {
if (fOutlinePage == null || fOutlinePage.isDisposed()) {
fOutlinePage = new AntxrOutlinePage(this);
if (getEditorInput() != null) {
fOutlinePage.setInput(getEditorInput());
}
}
adapter = fOutlinePage;
} else {
adapter = super.getAdapter(aClass);
}
return adapter;
}
/**
* The <code>AntxrEditor</code> implementation of this
* <code>AbstractTextEditor</code> method performs any extra
* disposal actions required by the ANTXR editor.
*
* @see org.eclipse.ui.IWorkbenchPart#dispose()
*/
public void dispose() {
EditorEnvironment.disconnect();
if (fOutlinePage != null && !fOutlinePage.isDisposed()) {
fOutlinePage.dispose();
fOutlinePage = null;
}
super.dispose();
}
/* (non-Javadoc)
* @see org.eclipse.ui.texteditor.AbstractTextEditor#editorContextMenuAboutToShow(org.eclipse.jface.action.IMenuManager)
*/
protected void editorContextMenuAboutToShow(IMenuManager aMenu) {
super.editorContextMenuAboutToShow(aMenu);
addAction(aMenu, IWorkbenchActionConstants.MB_ADDITIONS,
IAntxrActionConstants.GOTO_RULE);
addAction(aMenu, IWorkbenchActionConstants.MB_ADDITIONS,
IAntxrActionConstants.COMMENT);
addAction(aMenu, IWorkbenchActionConstants.MB_ADDITIONS,
IAntxrActionConstants.UNCOMMENT);
}
/* (non-Javadoc)
* @see org.eclipse.ui.texteditor.AbstractTextEditor#handleCursorPositionChanged()
*/
protected void handleCursorPositionChanged() {
super.handleCursorPositionChanged();
int line = getCursorLine();
if (line > 0 && line != fLastCursorLine) {
fLastCursorLine = line;
if (fOutlinePage != null && !fOutlinePage.isDisposed()) {
fOutlinePage.selectSegment(line, false);
}
}
}
/** {@inheritDoc} */
public void markInNavigationHistory() {
getEditorSite().getPage().getNavigationHistory().markLocation(
fMultiPageEditor);
}
/**
* Get the document we're editing
* @return the document
*/
public IDocument getDocument() {
return getSourceViewer().getDocument();
}
/**
* Get the line containing the cursor
* @return the line containing the cursor
*/
public int getCursorLine() {
int line = -1;
ISourceViewer sourceViewer = getSourceViewer();
if (sourceViewer != null) {
StyledText styledText = sourceViewer.getTextWidget();
int caret = AbstractTextEditor.widgetOffset2ModelOffset(sourceViewer,
styledText.getCaretOffset());
IDocument document = sourceViewer.getDocument();
if (document != null) {
try {
line = document.getLineOfOffset(caret) + 1;
} catch (BadLocationException e) {
AntxrUIPlugin.log(e);
}
}
}
return line;
}
/**
* Get the rules defined for this editor
* @param aPrefix The prefix
* @return the rules
*/
public String[] getRules(String aPrefix) {
return fModelTools.getRules(aPrefix);
}
/**
* Get the segment containing the line
* @param aLine the line
* @return the segment
*/
public ISegment getSegment(int aLine) {
return fModelTools.getSegment(aLine);
}
/**
* Get the named segment
* @param aName the name
* @return the segment
*/
public ISegment getSegment(String aName) {
return fModelTools.getSegment(aName);
}
/**
* Highlight the segment
* @param aSegment the segment
* @param aMoveCursor should we move the cursor?
*/
public void highlightSegment(ISegment aSegment, boolean aMoveCursor) {
IDocument doc = getDocument();
try {
int offset = doc.getLineOffset(aSegment.getStartLine() - 1);
IRegion endLine = doc.getLineInformation(aSegment.getEndLine() - 1);
int length = endLine.getOffset() + endLine.getLength() - offset;
setHighlightRange(offset, length, aMoveCursor);
} catch (BadLocationException e) {
resetHighlightRange();
}
fMultiPageEditor.activateEditor();
}
/**
* Ensure the segment is visible
* @param aSegment the segment
*/
public void revealSegment(ISegment aSegment) {
ISourceViewer viewer = getSourceViewer();
if (viewer != null) {
IDocument doc = getDocument();
try {
int offset = doc.getLineOffset(aSegment.getStartLine() - 1);
IRegion endLine = doc.getLineInformation(
aSegment.getEndLine() - 1);
int length = endLine.getOffset() + endLine.getLength() - offset;
// Reveal segment's text area in document
StyledText widget = getSourceViewer().getTextWidget();
widget.setRedraw(false);
viewer.revealRange(offset, length);
widget.setRedraw(true);
} catch (BadLocationException e) {
resetHighlightRange();
}
}
fMultiPageEditor.activateEditor();
}
/**
* Jump to the named rule
* @param aName the rule
*/
public void gotoRule(String aName) {
ISegment segment = fModelTools.getSegment(aName);
if (segment != null) {
markInNavigationHistory();
highlightSegment(segment, true);
markInNavigationHistory();
}
}
/**
* Jumps to the next or previous error according to the given direction.
* @param anIsForward Move forward to next error (false = previous error)
*/
public void gotoError(boolean anIsForward) {
ISelectionProvider provider = getSelectionProvider();
if (fStatusLineClearer != null) {
provider.removeSelectionChangedListener(fStatusLineClearer);
fStatusLineClearer= null;
}
ITextSelection s = (ITextSelection)provider.getSelection();
IMarker nextError = getNextError(s.getOffset(), anIsForward);
if (nextError != null) {
IGotoMarker gotoMarker = (IGotoMarker) getAdapter(IGotoMarker.class);
if (gotoMarker != null) {
gotoMarker.gotoMarker(nextError);
}
IWorkbenchPage page = getSite().getPage();
IViewPart view = page.findView(IPageLayout.ID_TASK_LIST);
if (view != null && view instanceof TaskList) {
StructuredSelection ss = new StructuredSelection(nextError);
((TaskList)view).setSelection(ss, true);
}
getStatusLineManager().setErrorMessage(nextError.getAttribute(
IMarker.MESSAGE, ""));
fStatusLineClearer = new ISelectionChangedListener() {
public void selectionChanged(SelectionChangedEvent event) {
getSelectionProvider().removeSelectionChangedListener(
fStatusLineClearer);
fStatusLineClearer = null;
getStatusLineManager().setErrorMessage("");
}
};
provider.addSelectionChangedListener(fStatusLineClearer);
} else {
getStatusLineManager().setErrorMessage("");
}
}
private IMarker getNextError(int anOffset, boolean anIsForward) {
IMarker nextError = null;
IDocument document = getDocument();
int endOfDocument = document.getLength();
int distance = 0;
IAnnotationModel model = getDocumentProvider().getAnnotationModel(
getEditorInput());
Iterator iter = model.getAnnotationIterator();
while (iter.hasNext()) {
Annotation a = (Annotation)iter.next();
if (a instanceof MarkerAnnotation) {
IMarker marker = ((MarkerAnnotation)a).getMarker();
if (MarkerUtilities.isMarkerType(marker, IMarker.PROBLEM)) {
Position p = model.getPosition(a);
if (!p.includes(anOffset)) {
int currentDistance = 0;
if (anIsForward) {
currentDistance = p.getOffset() - anOffset;
if (currentDistance < 0) {
currentDistance = endOfDocument - anOffset +
p.getOffset();
}
} else {
currentDistance = anOffset - p.getOffset();
if (currentDistance < 0) {
currentDistance = anOffset + endOfDocument -
p.getOffset();
}
}
if (nextError == null || currentDistance < distance) {
distance = currentDistance;
nextError = marker;
}
}
}
}
}
return nextError;
}
/**
* Get the reconciler
* @return the reconciler
*/
public AntxrReconcilingStrategy getReconcilingStrategy() {
return fReconcilingStrategy;
}
/**
* Get the root elements
* @return the root elements
*/
public Object[] getRootElements() {
return fReconcilingStrategy.getRootElements();
}
/**
* Get the root segment
* @return the root segment
*/
public ISegment getRootSegment() {
return fReconcilingStrategy.getRootSegment();
}
/**
* Update the outline page
*/
public void updateOutlinePage() {
if (fOutlinePage != null) {
fOutlinePage.update();
}
}
/**
* Move the cursor to the specified line
* @param aLine the target line
*/
public void moveCursor(int aLine) {
ISourceViewer sourceViewer = getSourceViewer();
try {
int offset = getDocument().getLineOffset(aLine - 1);
sourceViewer.setSelectedRange(offset, 0);
sourceViewer.revealRange(offset, 0);
} catch (BadLocationException e) {
AntxrUIPlugin.log(e);
}
}
/**
* Returns the desktop's StatusLineManager.
* @return the statusline manager
*/
protected IStatusLineManager getStatusLineManager() {
IStatusLineManager manager;
IEditorActionBarContributor contributor =
fMultiPageEditor.getEditorSite().getActionBarContributor();
if (contributor != null &&
contributor instanceof EditorActionBarContributor) {
manager = ((EditorActionBarContributor)contributor).
getActionBars().getStatusLineManager();
} else {
manager = null;
}
return manager;
}
/**
* Displays an error message in editor's status line.
* @param aMessage the message to display
*/
public void displayErrorMessage(String aMessage) {
IStatusLineManager manager = getStatusLineManager();
if (manager != null) {
manager.setErrorMessage(aMessage);
}
}
}