Java tutorial
/******************************************************************************* * Copyright (c) 2007 IBM Corporation. * 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: * Robert Fuhrer (rfuhrer@watson.ibm.com) - initial API and implementation *******************************************************************************/ package com.redhat.ceylon.eclipse.code.editor; import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.CORRECT_INDENTATION; import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.FORMAT; import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.GOTO_MATCHING_FENCE; import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.GOTO_NEXT_TARGET; import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.GOTO_PREVIOUS_TARGET; import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.RESTORE_PREVIOUS; import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.SELECT_ENCLOSING; import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.SHOW_OUTLINE; import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.TOGGLE_COMMENT; import static com.redhat.ceylon.eclipse.code.editor.EditorInputUtils.getFile; import static com.redhat.ceylon.eclipse.code.editor.EditorInputUtils.getPath; import static com.redhat.ceylon.eclipse.code.editor.SourceArchiveDocumentProvider.isSrcArchive; import static com.redhat.ceylon.eclipse.code.parse.TreeLifecycleListener.Stage.TYPE_ANALYSIS; import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.PROBLEM_MARKER_ID; import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.TASK_MARKER_ID; import static com.redhat.ceylon.eclipse.ui.CeylonPlugin.PLUGIN_ID; import static java.util.ResourceBundle.getBundle; import static org.eclipse.core.resources.IResourceChangeEvent.POST_BUILD; import static org.eclipse.core.resources.IncrementalProjectBuilder.CLEAN_BUILD; import static org.eclipse.core.resources.ResourcesPlugin.getWorkspace; import static org.eclipse.ui.PlatformUI.getWorkbench; import static org.eclipse.ui.texteditor.ITextEditorActionConstants.GROUP_RULERS; import static org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS; import static org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds.DELETE_NEXT_WORD; import static org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds.DELETE_PREVIOUS_WORD; import static org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds.SELECT_WORD_NEXT; import static org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds.SELECT_WORD_PREVIOUS; import static org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds.WORD_NEXT; import static org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds.WORD_PREVIOUS; import static org.eclipse.ui.texteditor.spelling.SpellingService.PREFERENCE_SPELLING_ENABLED; import java.io.IOException; import java.lang.reflect.Method; import java.text.BreakIterator; import java.text.CharacterIterator; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.ResourceBundle; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.debug.ui.actions.IToggleBreakpointsTarget; import org.eclipse.debug.ui.actions.ToggleBreakpointAction; import org.eclipse.jdt.internal.ui.IJavaHelpContextIds; import org.eclipse.jdt.internal.ui.viewsupport.IProblemChangedListener; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.preference.PreferenceConverter; import org.eclipse.jface.resource.FontRegistry; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentListener; 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.Region; import org.eclipse.jface.text.link.LinkedModeModel; import org.eclipse.jface.text.link.LinkedPosition; import org.eclipse.jface.text.source.Annotation; import org.eclipse.jface.text.source.DefaultCharacterPairMatcher; import org.eclipse.jface.text.source.IAnnotationModel; import org.eclipse.jface.text.source.IAnnotationModelListener; import org.eclipse.jface.text.source.ICharacterPairMatcher; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.text.source.IVerticalRuler; import org.eclipse.jface.text.source.projection.ProjectionAnnotationModel; import org.eclipse.jface.text.source.projection.ProjectionSupport; 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.ST; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Composite; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IFileEditorInput; import org.eclipse.ui.IPropertyListener; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.editors.text.TextEditor; import org.eclipse.ui.internal.WorkbenchPlugin; import org.eclipse.ui.texteditor.AbstractTextEditor; import org.eclipse.ui.texteditor.ContentAssistAction; import org.eclipse.ui.texteditor.IDocumentProvider; import org.eclipse.ui.texteditor.IEditorStatusLine; import org.eclipse.ui.texteditor.ITextEditorActionConstants; import org.eclipse.ui.texteditor.IUpdate; import org.eclipse.ui.texteditor.MarkerAnnotation; import org.eclipse.ui.texteditor.SourceViewerDecorationSupport; import org.eclipse.ui.texteditor.TextNavigationAction; import org.eclipse.ui.texteditor.TextOperationAction; import org.eclipse.ui.themes.ITheme; import org.eclipse.ui.views.contentoutline.IContentOutlinePage; import ceylon.language.StringBuilder; import com.redhat.ceylon.eclipse.code.outline.CeylonLabelProvider; import com.redhat.ceylon.eclipse.code.outline.CeylonOutlineBuilder; import com.redhat.ceylon.eclipse.code.outline.CeylonOutlinePage; import com.redhat.ceylon.eclipse.code.parse.CeylonParseController; import com.redhat.ceylon.eclipse.code.parse.CeylonParserScheduler; import com.redhat.ceylon.eclipse.code.parse.TreeLifecycleListener; import com.redhat.ceylon.eclipse.ui.CeylonPlugin; import com.redhat.ceylon.eclipse.ui.CeylonResources; /** * An editor for Ceylon source code. * * @author Gavin King * @author Chris Laffra * @author Robert M. Fuhrer */ public class CeylonEditor extends TextEditor { public static final String MESSAGE_BUNDLE = "com.redhat.ceylon.eclipse.code.editor.EditorActionMessages"; private static final int REPARSE_SCHEDULE_DELAY = 100; /** * Parent annotation ID */ public static final String PARSE_ANNOTATION_TYPE = PLUGIN_ID + ".parseAnnotation"; /** * Annotation ID for a parser annotation w/ severity = error. * Must match the ID of the corresponding annotationTypes * extension in the plugin.xml. */ public static final String PARSE_ANNOTATION_TYPE_ERROR = PARSE_ANNOTATION_TYPE + ".error"; /** * Annotation ID for a parser annotation w/ severity = warning. * Must match the ID of the corresponding annotationTypes * extension in the plugin.xml. */ public static final String PARSE_ANNOTATION_TYPE_WARNING = PARSE_ANNOTATION_TYPE + ".warning"; /** * Annotation ID for a parser annotation w/ severity = info. * Must match the ID of the corresponding annotationTypes * extension in the plugin.xml. */ public static final String PARSE_ANNOTATION_TYPE_INFO = PARSE_ANNOTATION_TYPE + ".info"; /** Preference key for matching brackets */ protected final static String MATCHING_BRACKETS = "matchingBrackets"; /** Preference key for matching brackets color */ protected final static String MATCHING_BRACKETS_COLOR = "matchingBracketsColor"; private CeylonParserScheduler parserScheduler; private ProblemMarkerManager problemMarkerManager; private ICharacterPairMatcher bracketMatcher; //private SubActionBars fActionBars; //private DefaultPartListener fRefreshContributions; private ToggleBreakpointAction toggleBreakpointAction; private IAction enableDisableBreakpointAction; private IResourceChangeListener buildListener; private IResourceChangeListener moveListener; private IDocumentListener documentListener; private FoldingActionGroup foldingActionGroup; private SourceArchiveDocumentProvider sourceArchiveDocumentProvider; private ToggleBreakpointAdapter toggleBreakpointTarget; private CeylonOutlinePage outlinePage; private boolean backgroundParsingPaused; private CeylonParseController parseController; //public static ResourceBundle fgBundleForConstructedKeys= getBundle(MESSAGE_BUNDLE); //public static final String IMP_CODING_ACTION_SET = RuntimePlugin.IMP_RUNTIME + ".codingActionSet"; //public static final String IMP_OPEN_ACTION_SET = RuntimePlugin.IMP_RUNTIME + ".openActionSet"; public CeylonEditor() { // SMS 4 Apr 2007 // Do not set preference store with store obtained from plugin; one is // already defined for the parent text editor and populated with relevant // preferences // setPreferenceStore(CeylonPlugin.getInstance().getPreferenceStore()); setSourceViewerConfiguration(createSourceViewerConfiguration()); configureInsertMode(SMART_INSERT, true); setInsertMode(SMART_INSERT); problemMarkerManager = new ProblemMarkerManager(); } static String[][] getFences() { return new String[][] { { "(", ")" }, { "[", "]" }, { "{", "}" } }; } public synchronized void pauseBackgroundParsing() { backgroundParsingPaused = true; } public synchronized void unpauseBackgroundParsing() { backgroundParsingPaused = false; } public synchronized boolean isBackgroundParsingPaused() { return backgroundParsingPaused; } /** * Sub-classes may override this method to extend the behavior provided by IMP's * standard StructuredSourceViewerConfiguration. * @return the StructuredSourceViewerConfiguration to use with this editor */ protected CeylonSourceViewerConfiguration createSourceViewerConfiguration() { return new CeylonSourceViewerConfiguration(getPreferenceStore(), this); } public IPreferenceStore getPrefStore() { return super.getPreferenceStore(); } @SuppressWarnings("rawtypes") public Object getAdapter(Class required) { if (IContentOutlinePage.class.equals(required)) { return getOutlinePage(); } if (IToggleBreakpointsTarget.class.equals(required)) { return getToggleBreakpointAdapter(); } /*if (IContextProvider.class.equals(required)) { return IMPHelp.getHelpContextProvider(this, fLanguageServiceManager, IMP_EDITOR_CONTEXT); }*/ return super.getAdapter(required); } public Object getToggleBreakpointAdapter() { if (toggleBreakpointTarget == null) { toggleBreakpointTarget = new ToggleBreakpointAdapter(); } return toggleBreakpointTarget; } public Object getOutlinePage() { if (outlinePage == null) { outlinePage = new CeylonOutlinePage(getParseController(), new CeylonOutlineBuilder()); parserScheduler.addModelListener(outlinePage); getSourceViewer().getTextWidget().addCaretListener(outlinePage); //myOutlinePage.update(parseController); } return outlinePage; } protected void createActions() { super.createActions(); final ResourceBundle bundle = getBundle(MESSAGE_BUNDLE); Action action = new ContentAssistAction(bundle, "ContentAssistProposal.", this); action.setActionDefinitionId(CONTENT_ASSIST_PROPOSALS); setAction("ContentAssistProposal", action); markAsStateDependentAction("ContentAssistProposal", true); toggleBreakpointAction = new ToggleBreakpointAction(this, getDocumentProvider().getDocument(getEditorInput()), getVerticalRuler()); setAction("ToggleBreakpoint", action); enableDisableBreakpointAction = new RulerEnableDisableBreakpointAction(this, getVerticalRuler()); setAction("ToggleBreakpoint", action); action = new TextOperationAction(bundle, "Format.", this, CeylonSourceViewer.FORMAT); action.setActionDefinitionId(FORMAT); setAction("Format", action); markAsStateDependentAction("Format", true); markAsSelectionDependentAction("Format", true); getWorkbench().getHelpSystem().setHelp(action, IJavaHelpContextIds.FORMAT_ACTION); action = new TextOperationAction(bundle, "ShowOutline.", this, CeylonSourceViewer.SHOW_OUTLINE, true /* runsOnReadOnly */); action.setActionDefinitionId(SHOW_OUTLINE); setAction(SHOW_OUTLINE, action); getWorkbench().getHelpSystem().setHelp(action, IJavaHelpContextIds.SHOW_OUTLINE_ACTION); action = new TextOperationAction(bundle, "ToggleComment.", this, CeylonSourceViewer.TOGGLE_COMMENT); action.setActionDefinitionId(TOGGLE_COMMENT); setAction(TOGGLE_COMMENT, action); getWorkbench().getHelpSystem().setHelp(action, IJavaHelpContextIds.TOGGLE_COMMENT_ACTION); action = new TextOperationAction(bundle, "CorrectIndentation.", this, CeylonSourceViewer.CORRECT_INDENTATION); action.setActionDefinitionId(CORRECT_INDENTATION); setAction(CORRECT_INDENTATION, action); action = new GotoMatchingFenceAction(this); action.setActionDefinitionId(GOTO_MATCHING_FENCE); setAction(GOTO_MATCHING_FENCE, action); action = new GotoPreviousTargetAction(this); action.setActionDefinitionId(GOTO_PREVIOUS_TARGET); setAction(GOTO_PREVIOUS_TARGET, action); action = new GotoNextTargetAction(this); action.setActionDefinitionId(GOTO_NEXT_TARGET); setAction(GOTO_NEXT_TARGET, action); action = new SelectEnclosingAction(this); action.setActionDefinitionId(SELECT_ENCLOSING); setAction(SELECT_ENCLOSING, action); action = new RestorePreviousSelectionAction(this); action.setActionDefinitionId(RESTORE_PREVIOUS); setAction(RESTORE_PREVIOUS, action); action = new TextOperationAction(bundle, "ShowHierarchy.", this, CeylonSourceViewer.SHOW_HIERARCHY, true); action.setActionDefinitionId(EditorActionIds.SHOW_CEYLON_HIERARCHY); setAction(EditorActionIds.SHOW_CEYLON_HIERARCHY, action); action = new TextOperationAction(bundle, "ShowCode.", this, CeylonSourceViewer.SHOW_CODE, true); action.setActionDefinitionId(EditorActionIds.SHOW_CEYLON_CODE); setAction(EditorActionIds.SHOW_CEYLON_CODE, action); action = new TerminateStatementAction(this); action.setActionDefinitionId(EditorActionIds.TERMINATE_STATEMENT); setAction(EditorActionIds.TERMINATE_STATEMENT, action); foldingActionGroup = new FoldingActionGroup(this, this.getSourceViewer()); getAction(ITextEditorActionConstants.SHIFT_LEFT).setImageDescriptor( CeylonPlugin.getInstance().getImageRegistry().getDescriptor(CeylonResources.SHIFT_LEFT)); getAction(ITextEditorActionConstants.SHIFT_RIGHT).setImageDescriptor( CeylonPlugin.getInstance().getImageRegistry().getDescriptor(CeylonResources.SHIFT_RIGHT)); IAction qaa = getAction(ITextEditorActionConstants.QUICK_ASSIST); qaa.setImageDescriptor( CeylonPlugin.getInstance().getImageRegistry().getDescriptor(CeylonResources.QUICK_ASSIST)); qaa.setText("Quick Fix/Assist"); } @Override protected void createNavigationActions() { super.createNavigationActions(); final StyledText textWidget = getSourceViewer().getTextWidget(); /*IAction action= new SmartLineStartAction(textWidget, false); action.setActionDefinitionId(ITextEditorActionDefinitionIds.LINE_START); editor.setAction(ITextEditorActionDefinitionIds.LINE_START, action); action= new SmartLineStartAction(textWidget, true); action.setActionDefinitionId(ITextEditorActionDefinitionIds.SELECT_LINE_START); editor.setAction(ITextEditorActionDefinitionIds.SELECT_LINE_START, action);*/ setAction(WORD_PREVIOUS, new NavigatePreviousSubWordAction()); textWidget.setKeyBinding(SWT.CTRL | SWT.ARROW_LEFT, SWT.NULL); setAction(WORD_NEXT, new NavigateNextSubWordAction()); textWidget.setKeyBinding(SWT.CTRL | SWT.ARROW_RIGHT, SWT.NULL); setAction(SELECT_WORD_PREVIOUS, new SelectPreviousSubWordAction()); textWidget.setKeyBinding(SWT.CTRL | SWT.SHIFT | SWT.ARROW_LEFT, SWT.NULL); setAction(SELECT_WORD_NEXT, new SelectNextSubWordAction()); textWidget.setKeyBinding(SWT.CTRL | SWT.SHIFT | SWT.ARROW_RIGHT, SWT.NULL); setAction(DELETE_PREVIOUS_WORD, new DeletePreviousSubWordAction()); textWidget.setKeyBinding(SWT.CTRL | SWT.BS, SWT.NULL); markAsStateDependentAction(DELETE_PREVIOUS_WORD, true); setAction(DELETE_NEXT_WORD, new DeleteNextSubWordAction()); textWidget.setKeyBinding(SWT.CTRL | SWT.DEL, SWT.NULL); markAsStateDependentAction(DELETE_NEXT_WORD, true); } protected class NavigateNextSubWordAction extends NextSubWordAction { public NavigateNextSubWordAction() { super(ST.WORD_NEXT); setActionDefinitionId(WORD_NEXT); } @Override protected void setCaretPosition(final int position) { getTextWidget().setCaretOffset(modelOffset2WidgetOffset(getSourceViewer(), position)); } } protected class NavigatePreviousSubWordAction extends PreviousSubWordAction { public NavigatePreviousSubWordAction() { super(ST.WORD_PREVIOUS); setActionDefinitionId(WORD_PREVIOUS); } @Override protected void setCaretPosition(final int position) { getTextWidget().setCaretOffset(modelOffset2WidgetOffset(getSourceViewer(), position)); } } protected abstract class NextSubWordAction extends TextNavigationAction { protected CeylonWordIterator fIterator = new CeylonWordIterator(); /** * Creates a new next sub-word action. * * @param code Action code for the default operation. Must be an action code from @see org.eclipse.swt.custom.ST. */ protected NextSubWordAction(int code) { super(getSourceViewer().getTextWidget(), code); } @Override public void run() { // Check whether we are in a java code partition and the preference is enabled final ISourceViewer viewer = getSourceViewer(); final IDocument document = viewer.getDocument(); try { fIterator.setText((CharacterIterator) new DocumentCharacterIterator(document)); int position = widgetOffset2ModelOffset(viewer, viewer.getTextWidget().getCaretOffset()); if (position == -1) return; int next = findNextPosition(position); if (next != BreakIterator.DONE) { setCaretPosition(next); getTextWidget().showSelection(); fireSelectionChanged(); } } catch (BadLocationException x) { // ignore } } /** * Finds the next position after the given position. * * @param position the current position * @return the next position */ protected int findNextPosition(int position) { ISourceViewer viewer = getSourceViewer(); int widget = -1; int next = position; while (next != BreakIterator.DONE && widget == -1) { // XXX: optimize next = fIterator.following(next); if (next != BreakIterator.DONE) widget = modelOffset2WidgetOffset(viewer, next); } IDocument document = viewer.getDocument(); LinkedModeModel model = LinkedModeModel.getModel(document, position); if (model != null) { LinkedPosition linkedPosition = model.findPosition(new LinkedPosition(document, position, 0)); if (linkedPosition != null) { int linkedPositionEnd = linkedPosition.getOffset() + linkedPosition.getLength(); if (position != linkedPositionEnd && linkedPositionEnd < next) next = linkedPositionEnd; } else { LinkedPosition nextLinkedPosition = model.findPosition(new LinkedPosition(document, next, 0)); if (nextLinkedPosition != null) { int nextLinkedPositionOffset = nextLinkedPosition.getOffset(); if (position != nextLinkedPositionOffset && nextLinkedPositionOffset < next) next = nextLinkedPositionOffset; } } } return next; } /** * Sets the caret position to the sub-word boundary given with * <code>position</code>. * * @param position Position where the action should move the caret */ protected abstract void setCaretPosition(int position); } protected abstract class PreviousSubWordAction extends TextNavigationAction { protected CeylonWordIterator fIterator = new CeylonWordIterator(); /** * Creates a new previous sub-word action. * * @param code Action code for the default operation. Must be an * action code from @see org.eclipse.swt.custom.ST. */ protected PreviousSubWordAction(final int code) { super(getSourceViewer().getTextWidget(), code); } @Override public void run() { // Check whether we are in a java code partition and the preference is enabled final ISourceViewer viewer = getSourceViewer(); final IDocument document = viewer.getDocument(); try { fIterator.setText((CharacterIterator) new DocumentCharacterIterator(document)); int position = widgetOffset2ModelOffset(viewer, viewer.getTextWidget().getCaretOffset()); if (position == -1) return; int previous = findPreviousPosition(position); if (previous != BreakIterator.DONE) { setCaretPosition(previous); getTextWidget().showSelection(); fireSelectionChanged(); } } catch (BadLocationException x) { // ignore - getLineOfOffset failed } } /** * Finds the previous position before the given position. * * @param position the current position * @return the previous position */ protected int findPreviousPosition(int position) { ISourceViewer viewer = getSourceViewer(); int widget = -1; int previous = position; while (previous != BreakIterator.DONE && widget == -1) { // XXX: optimize previous = fIterator.preceding(previous); if (previous != BreakIterator.DONE) widget = modelOffset2WidgetOffset(viewer, previous); } IDocument document = viewer.getDocument(); LinkedModeModel model = LinkedModeModel.getModel(document, position); if (model != null) { LinkedPosition linkedPosition = model.findPosition(new LinkedPosition(document, position, 0)); if (linkedPosition != null) { int linkedPositionOffset = linkedPosition.getOffset(); if (position != linkedPositionOffset && previous < linkedPositionOffset) previous = linkedPositionOffset; } else { LinkedPosition previousLinkedPosition = model .findPosition(new LinkedPosition(document, previous, 0)); if (previousLinkedPosition != null) { int previousLinkedPositionEnd = previousLinkedPosition.getOffset() + previousLinkedPosition.getLength(); if (position != previousLinkedPositionEnd && previous < previousLinkedPositionEnd) previous = previousLinkedPositionEnd; } } } return previous; } /** * Sets the caret position to the sub-word boundary given with <code>position</code>. * * @param position Position where the action should move the caret */ protected abstract void setCaretPosition(int position); } protected class SelectNextSubWordAction extends NextSubWordAction { public SelectNextSubWordAction() { super(ST.SELECT_WORD_NEXT); setActionDefinitionId(SELECT_WORD_NEXT); } @Override protected void setCaretPosition(final int position) { final ISourceViewer viewer = getSourceViewer(); final StyledText text = viewer.getTextWidget(); if (text != null && !text.isDisposed()) { final Point selection = text.getSelection(); final int caret = text.getCaretOffset(); final int offset = modelOffset2WidgetOffset(viewer, position); if (caret == selection.x) text.setSelectionRange(selection.y, offset - selection.y); else text.setSelectionRange(selection.x, offset - selection.x); } } } protected class SelectPreviousSubWordAction extends PreviousSubWordAction { public SelectPreviousSubWordAction() { super(ST.SELECT_WORD_PREVIOUS); setActionDefinitionId(SELECT_WORD_PREVIOUS); } @Override protected void setCaretPosition(final int position) { final ISourceViewer viewer = getSourceViewer(); final StyledText text = viewer.getTextWidget(); if (text != null && !text.isDisposed()) { final Point selection = text.getSelection(); final int caret = text.getCaretOffset(); final int offset = modelOffset2WidgetOffset(viewer, position); if (caret == selection.x) text.setSelectionRange(selection.y, offset - selection.y); else text.setSelectionRange(selection.x, offset - selection.x); } } } protected class DeleteNextSubWordAction extends NextSubWordAction implements IUpdate { public DeleteNextSubWordAction() { super(ST.DELETE_WORD_NEXT); setActionDefinitionId(DELETE_NEXT_WORD); } @Override protected void setCaretPosition(final int position) { if (!validateEditorInputState()) return; final ISourceViewer viewer = getSourceViewer(); StyledText text = viewer.getTextWidget(); Point selection = viewer.getSelectedRange(); final int caret, length; if (selection.y != 0) { caret = selection.x; length = selection.y; } else { caret = widgetOffset2ModelOffset(viewer, text.getCaretOffset()); length = position - caret; } try { viewer.getDocument().replace(caret, length, ""); } catch (BadLocationException exception) { // Should not happen } } public void update() { setEnabled(isEditorInputModifiable()); } } protected class DeletePreviousSubWordAction extends PreviousSubWordAction implements IUpdate { public DeletePreviousSubWordAction() { super(ST.DELETE_WORD_PREVIOUS); setActionDefinitionId(DELETE_PREVIOUS_WORD); } @Override protected void setCaretPosition(int position) { if (!validateEditorInputState()) return; final int length; final ISourceViewer viewer = getSourceViewer(); StyledText text = viewer.getTextWidget(); Point selection = viewer.getSelectedRange(); if (selection.y != 0) { position = selection.x; length = selection.y; } else { length = widgetOffset2ModelOffset(viewer, text.getCaretOffset()) - position; } try { viewer.getDocument().replace(position, length, ""); } catch (BadLocationException exception) { // Should not happen } } public void update() { setEnabled(isEditorInputModifiable()); } } protected void initializeKeyBindingScopes() { setKeyBindingScopes(new String[] { PLUGIN_ID + ".context" }); } //private QuickMenuAction fQuickAccessAction; //private IHandlerActivation fQuickAccessHandlerActivation; //private IHandlerService fHandlerService; //private static final String QUICK_MENU_ID= "org.eclipse.imp.runtime.editor.refactor.quickMenu"; /*private class RefactorQuickAccessAction extends QuickMenuAction { public RefactorQuickAccessAction() { super(QUICK_MENU_ID); } protected void fillMenu(IMenuManager menu) { //contributeRefactoringActions(menu); } }*/ /*private void installQuickAccessAction() { fHandlerService= (IHandlerService) getSite().getService(IHandlerService.class); if (fHandlerService != null) { fQuickAccessAction= new RefactorQuickAccessAction(); fQuickAccessHandlerActivation= fHandlerService.activateHandler(fQuickAccessAction.getActionDefinitionId(), new ActionHandler(fQuickAccessAction)); } }*/ protected boolean isOverviewRulerVisible() { return true; } protected void rulerContextMenuAboutToShow(IMenuManager menu) { addDebugActions(menu); super.rulerContextMenuAboutToShow(menu); //IMenuManager foldingMenu= new MenuManager("Folding", "projection"); //menu.appendToGroup(GROUP_RULERS, foldingMenu); menu.appendToGroup(GROUP_RULERS, new Separator()); IAction action; // action= getAction("FoldingToggle"); // foldingMenu.add(action); action = getAction("FoldingExpandAll"); menu.appendToGroup(GROUP_RULERS, action); //foldingMenu.add(action); action = getAction("FoldingCollapseAll"); //foldingMenu.add(action); menu.appendToGroup(GROUP_RULERS, action); /*action= getAction("FoldingRestore"); foldingMenu.add(action); action= getAction("FoldingCollapseMembers"); foldingMenu.add(action); action= getAction("FoldingCollapseComments"); foldingMenu.add(action);*/ } private void addDebugActions(IMenuManager menu) { menu.add(toggleBreakpointAction); menu.add(enableDisableBreakpointAction); } /** * Sets the given message as error message to this editor's status line. * * @param msg message to be set */ protected void setStatusLineErrorMessage(String msg) { IEditorStatusLine statusLine = (IEditorStatusLine) getAdapter(IEditorStatusLine.class); if (statusLine != null) statusLine.setMessage(true, msg, null); } /** * Sets the given message as message to this editor's status line. * * @param msg message to be set * @since 3.0 */ protected void setStatusLineMessage(String msg) { IEditorStatusLine statusLine = (IEditorStatusLine) getAdapter(IEditorStatusLine.class); if (statusLine != null) statusLine.setMessage(false, msg, null); } public ProblemMarkerManager getProblemMarkerManager() { return problemMarkerManager; } @Override protected void setTitleImage(Image titleImage) { super.setTitleImage(titleImage); } public IDocumentProvider getDocumentProvider() { if (isSrcArchive(getEditorInput())) { //Note: I would prefer to register the //document provider in plugin.xml but //I don't know how to uniquely identity //that a IURIEditorInput is a source //archive there if (sourceArchiveDocumentProvider == null) { sourceArchiveDocumentProvider = new SourceArchiveDocumentProvider(); } return sourceArchiveDocumentProvider; } else { return super.getDocumentProvider(); } } public CeylonSourceViewer getCeylonSourceViewer() { return (CeylonSourceViewer) super.getSourceViewer(); } public void createPartControl(Composite parent) { // Initialize the parse controller first, since the // initialization of other things (like the context // help support) might depend on it. initializeParseController(); // Not sure why the "run the spell checker" pref would // get set, but it does seem to, which gives lots of // annoying squigglies all over the place... getPreferenceStore().setValue(PREFERENCE_SPELLING_ENABLED, false); super.createPartControl(parent); initiateServiceControllers(); setTitleImageFromLanguageIcon(); setSourceFontFromPreference(); setupBracketCloser(); /*((IContextService) getSite().getService(IContextService.class)) .activateContext(PLUGIN_ID + ".context");*/ //CeylonPlugin.getInstance().getPreferenceStore().addPropertyChangeListener(colorChangeListener); currentTheme.getColorRegistry().addListener(colorChangeListener); updateFontAndCaret(); currentTheme.getFontRegistry().addListener(fontChangeListener); } private void watchForSourceBuild() { getWorkspace().addResourceChangeListener(buildListener = new IResourceChangeListener() { public void resourceChanged(IResourceChangeEvent event) { if (event.getType() == POST_BUILD && event.getBuildKind() != CLEAN_BUILD) { scheduleParsing(); } } }, IResourceChangeEvent.POST_BUILD); } public synchronized void scheduleParsing() { if (parserScheduler != null && !backgroundParsingPaused) { parserScheduler.cancel(); parserScheduler.schedule(REPARSE_SCHEDULE_DELAY); } } private void initializeParseController() { IEditorInput editorInput = getEditorInput(); IFile file = getFile(editorInput); IPath filePath = getPath(editorInput); parseController = new CeylonParseController(); IProject project = file != null && file.exists() ? file.getProject() : null; parseController.initialize(filePath, project, annotationCreator); } private void watchDocument() { getSourceViewer().getDocument().addDocumentListener(documentListener = new IDocumentListener() { public void documentAboutToBeChanged(DocumentEvent event) { } public void documentChanged(DocumentEvent event) { synchronized (CeylonEditor.this) { if (parserScheduler != null && !backgroundParsingPaused) { parserScheduler.cancel(); parserScheduler.schedule(REPARSE_SCHEDULE_DELAY); } } } }); } /*private class BracketInserter implements VerifyKeyListener { private final Map<String,String> fFencePairs= new HashMap<String, String>(); private final String fOpenFences; private final Map<Character,Boolean> fCloseFenceMap= new HashMap<Character, Boolean>(); // private final String CATEGORY= toString(); // private IPositionUpdater fUpdater= new ExclusivePositionUpdater(CATEGORY); public BracketInserter() { String[][] pairs= fLanguageServiceManager.getParseController().getSyntaxProperties().getFences(); StringBuilder sb= new StringBuilder(); for(int i= 0; i < pairs.length; i++) { sb.append(pairs[i][0]); fFencePairs.put(pairs[i][0], pairs[i][1]); } fOpenFences= sb.toString(); } public void setCloseFenceEnabled(char openingFence, boolean enabled) { fCloseFenceMap.put(openingFence, enabled); } public void setCloseFencesEnabled(boolean enabled) { for(int i= 0; i < fOpenFences.length(); i++) { fCloseFenceMap.put(fOpenFences.charAt(i), enabled); } } public void verifyKey(VerifyEvent event) { // early pruning to slow down normal typing as little as possible if (!event.doit || getInsertMode() != SMART_INSERT) return; if (fOpenFences.indexOf(event.character) < 0) { return; } final ISourceViewer sourceViewer= getSourceViewer(); IDocument document= sourceViewer.getDocument(); final Point selection= sourceViewer.getSelectedRange(); final int offset= selection.x; final int length= selection.y; try { // IRegion startLine= document.getLineInformationOfOffset(offset); // IRegion endLine= document.getLineInformationOfOffset(offset + length); // TODO Ask the parser/scanner whether the close fence is valid here // (i.e. whether it would recover by inserting the matching close fence character) // Right now, naively insert the closing fence regardless. ITypedRegion partition= TextUtilities.getPartition(document, getSourceViewerConfiguration().getConfiguredDocumentPartitioning(sourceViewer), offset, true); if (!IDocument.DEFAULT_CONTENT_TYPE.equals(partition.getType())) return; if (!validateEditorInputState()) return; final String inputStr= new String(new char[] { event.character }); final String closingFence= fFencePairs.get(inputStr); final StringBuffer buffer= new StringBuffer(); buffer.append(inputStr); buffer.append(closingFence); document.replace(offset, length, buffer.toString()); sourceViewer.setSelectedRange(offset + inputStr.length(), 0); event.doit= false; } catch (BadLocationException e) { e.printStackTrace(); } } }*/ //private BracketInserter fBracketInserter; //private final String CLOSE_FENCES= PreferenceConstants.EDITOR_CLOSE_FENCES; private void setupBracketCloser() { // Bug #536: Disable for now, until we can be more intelligent //about when to overwrite an existing (subsequent) close-fence char. /*IParseController parseController = fLanguageServiceManager.getParseController(); if (parseController == null || parseController.getSyntaxProperties() == null || parseController.getSyntaxProperties().getFences() == null) { return; } //Preference key for automatically closing brackets and parenthesis boolean closeFences= fLangSpecificPrefs.getBooleanPreference(CLOSE_FENCES); // false if no lang-specific setting fBracketInserter= new BracketInserter(); fBracketInserter.setCloseFencesEnabled(closeFences); ISourceViewer sourceViewer= getSourceViewer(); if (sourceViewer instanceof ITextViewerExtension) ((ITextViewerExtension) sourceViewer).prependVerifyKeyListener(fBracketInserter);*/ } /** * The following listener is intended to detect when the document associated * with this editor changes its identity, which happens when, e.g., the * underlying resource gets moved or renamed. */ private IPropertyListener fEditorInputPropertyListener = new IPropertyListener() { public void propertyChanged(Object source, int propId) { if (source == CeylonEditor.this && propId == IEditorPart.PROP_INPUT) { IDocument oldDoc = getParseController().getDocument(); IDocument curDoc = getDocumentProvider().getDocument(getEditorInput()); if (curDoc != oldDoc) { // Need to unwatch the old document and watch the new document oldDoc.removeDocumentListener(documentListener); curDoc.addDocumentListener(documentListener); } } } }; private void watchForSourceMove() { // We need to see when the editor input changes, so we can watch the new document addPropertyListener(fEditorInputPropertyListener); getWorkspace().addResourceChangeListener(moveListener = new IResourceChangeListener() { public void resourceChanged(IResourceChangeEvent event) { if (event.getType() == IResourceChangeEvent.POST_CHANGE) { IProject project = parseController.getProject(); if (project != null) { //things extrenal to the workspace don't move IPath oldWSRelPath = project.getFullPath().append(parseController.getPath()); IResourceDelta rd = event.getDelta().findMember(oldWSRelPath); if (rd != null) { if ((rd.getFlags() & IResourceDelta.MOVED_TO) == IResourceDelta.MOVED_TO) { // The net effect of the following is to re-initialize() the IParseController with the new path IPath newPath = rd.getMovedToPath(); IPath newProjRelPath = newPath.removeFirstSegments(1); String newProjName = newPath.segment(0); IProject proj = project.getName().equals(newProjName) ? project : project.getWorkspace().getRoot().getProject(newProjName); // Tell the IParseController about the move - it caches the path // fParserScheduler.cancel(); // avoid a race condition if ParserScheduler was starting/in the middle of a run parseController.initialize(newProjRelPath, proj, annotationCreator); } } } } } }); } private void setSourceFontFromPreference() { String fontName = WorkbenchPlugin.getDefault().getPreferenceStore().getString(JFaceResources.TEXT_FONT); FontRegistry fontRegistry = CeylonPlugin.getInstance().getFontRegistry(); if (!fontRegistry.hasValueFor(fontName)) { fontRegistry.put(fontName, PreferenceConverter.readFontData(fontName)); } Font sourceFont = fontRegistry.get(fontName); if (sourceFont != null) { getSourceViewer().getTextWidget().setFont(sourceFont); } } private void initiateServiceControllers() { try { final CeylonSourceViewer sourceViewer = (CeylonSourceViewer) getSourceViewer(); annotationUpdater = new IProblemChangedListener() { public void problemsChanged(IResource[] changedResources, boolean isMarkerChange) { // Remove annotations that were resolved by changes to // other resources. // TODO: It would be better to match the markers to the // annotations, and decide which annotations to remove. scheduleParsing(); } }; problemMarkerManager.addListener(annotationUpdater); editorIconUpdater = new EditorIconUpdater(this); problemMarkerManager.addListener(editorIconUpdater); parserScheduler = new CeylonParserScheduler(parseController, this, annotationCreator); addModelListener(new AdditionalAnnotationCreator(this)); // The source viewer configuration has already been asked for its ITextHover, // but before we actually instantiated the relevant controller class. So update // the source viewer, now that we actually have the hover provider. //HoverHelpController hover = new HoverHelpController(this); //sourceViewer.setTextHover(hover, DEFAULT_CONTENT_TYPE); //addModelListener(hover); new ProjectionSupport(sourceViewer, getAnnotationAccess(), getSharedColors()).install(); sourceViewer.doOperation(ProjectionViewer.TOGGLE); ProjectionAnnotationModel projectionAnnotationModel = sourceViewer.getProjectionAnnotationModel(); if (projectionAnnotationModel != null) { addModelListener(new FoldingController(projectionAnnotationModel)); } if (isEditable()) { addModelListener(new MarkerAnnotationUpdater()); } watchDocument(); watchForSourceMove(); watchForSourceBuild(); parserScheduler.schedule(); } catch (Exception e) { e.printStackTrace(); } } private void setTitleImageFromLanguageIcon() { IEditorInput editorInput = getEditorInput(); Object fileOrPath = getFile(editorInput); if (fileOrPath == null) { fileOrPath = getParseController().getPath(); } try { setTitleImage(new CeylonLabelProvider().getImage(fileOrPath)); } catch (Exception e) { e.printStackTrace(); } } /** * Makes sure that menu items and status bar items disappear as the editor * is out of focus, and reappear when it gets focus again. This does * not work for toolbar items for unknown reasons, they stay visible. * */ /*private void registerEditorContributionsActivator() { fRefreshContributions = new DefaultPartListener() { private UniversalEditor editor = UniversalEditor.this; @Override public void partActivated(IWorkbenchPart part) { if (part == editor) { editor.fActionBars.activate(); editor.fActionBars.updateActionBars(); } if (part instanceof UniversalEditor) { part.getSite().getPage().showActionSet(IMP_CODING_ACTION_SET); part.getSite().getPage().showActionSet(IMP_OPEN_ACTION_SET); } else { part.getSite().getPage().hideActionSet(IMP_CODING_ACTION_SET); part.getSite().getPage().hideActionSet(IMP_OPEN_ACTION_SET); } } @Override public void partDeactivated(IWorkbenchPart part) { if (part == editor) { editor.fActionBars.deactivate(); editor.fActionBars.updateActionBars(); } } }; getSite().getPage().addPartListener(fRefreshContributions); }*/ public void dispose() { if (editorIconUpdater != null) { editorIconUpdater.dispose(); editorIconUpdater = null; } if (annotationUpdater != null) { problemMarkerManager.removeListener(annotationUpdater); annotationUpdater = null; } /*if (fActionBars!=null) { fActionBars.dispose(); fActionBars = null; }*/ /*if (documentListener!=null) { getSourceViewer().getDocument() .removeDocumentListener(documentListener); }*/ if (buildListener != null) { getWorkspace().removeResourceChangeListener(buildListener); buildListener = null; } if (moveListener != null) { getWorkspace().removeResourceChangeListener(moveListener); moveListener = null; } toggleBreakpointAction.dispose(); // this holds onto the IDocument foldingActionGroup.dispose(); if (parserScheduler != null) { parserScheduler.cancel(); // avoid unnecessary work after the editor is asked to close down } parserScheduler = null; parseController = null; super.dispose(); /*if (fResourceListener != null) { ResourcesPlugin.getWorkspace().removeResourceChangeListener(fResourceListener); }*/ //CeylonPlugin.getInstance().getPreferenceStore().removePropertyChangeListener(colorChangeListener); currentTheme.getColorRegistry().removeListener(colorChangeListener); currentTheme.getFontRegistry().removeListener(fontChangeListener); } private static final String TEXT_FONT_PREFERENCE = PLUGIN_ID + ".editorFont"; private void updateFontAndCaret() { Font font = currentTheme.getFontRegistry().get(TEXT_FONT_PREFERENCE); getSourceViewer().getTextWidget().setFont(font); try { Method updateCaretMethod = AbstractTextEditor.class.getDeclaredMethod("updateCaret"); updateCaretMethod.setAccessible(true); updateCaretMethod.invoke(this); } catch (Exception e) { e.printStackTrace(); } } private IPropertyChangeListener colorChangeListener = new IPropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent event) { if (event.getProperty().startsWith(PLUGIN_ID + ".theme.color.")) { getSourceViewer().invalidateTextPresentation(); } } }; IPropertyChangeListener fontChangeListener = new IPropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent event) { if (event.getProperty().equals(TEXT_FONT_PREFERENCE)) { updateFontAndCaret(); } } }; private final ITheme currentTheme = PlatformUI.getWorkbench().getThemeManager().getCurrentTheme(); /** * Override creation of the normal source viewer with one that supports source folding. */ protected ISourceViewer createSourceViewer(Composite parent, IVerticalRuler ruler, int styles) { // if (fFoldingUpdater == null) // return super.createSourceViewer(parent, ruler, styles); fAnnotationAccess = createAnnotationAccess(); fOverviewRuler = createOverviewRuler(getSharedColors()); ISourceViewer viewer = new CeylonSourceViewer(parent, ruler, getOverviewRuler(), isOverviewRulerVisible(), styles); // ensure decoration support has been created and configured. getSourceViewerDecorationSupport(viewer); /*if (fLanguageServiceManager != null && fLanguageServiceManager.getParseController() != null) { IMPHelp.setHelp(fLanguageServiceManager, this, viewer.getTextWidget(), IMP_EDITOR_CONTEXT); }*/ return viewer; } protected void configureSourceViewerDecorationSupport(SourceViewerDecorationSupport support) { IPreferenceStore store = getPreferenceStore(); store.setDefault(MATCHING_BRACKETS, true); Color color = currentTheme.getColorRegistry().get(PLUGIN_ID + ".theme.matchingBracketsColor"); store.setDefault(MATCHING_BRACKETS_COLOR, color.getRed() + "," + color.getGreen() + "," + color.getBlue()); store.setValue(MATCHING_BRACKETS, true); String[][] fences = getFences(); if (fences != null) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < fences.length; i++) { sb.append(fences[i][0]); sb.append(fences[i][1]); } bracketMatcher = new DefaultCharacterPairMatcher(sb.toString().toCharArray()); support.setCharacterPairMatcher(bracketMatcher); support.setMatchingCharacterPainterPreferenceKeys(MATCHING_BRACKETS, MATCHING_BRACKETS_COLOR); } super.configureSourceViewerDecorationSupport(support); } /** * Jumps to the matching bracket. */ public void gotoMatchingFence() { ISourceViewer sourceViewer = getSourceViewer(); IDocument document = sourceViewer.getDocument(); if (document == null) return; IRegion selection = getSignedSelection(sourceViewer); int selectionLength = Math.abs(selection.getLength()); if (selectionLength > 1) { setStatusLineErrorMessage("Invalid selection"); sourceViewer.getTextWidget().getDisplay().beep(); return; } // #26314 int sourceCaretOffset = selection.getOffset() + selection.getLength(); if (isSurroundedByBrackets(document, sourceCaretOffset)) sourceCaretOffset -= selection.getLength(); IRegion region = bracketMatcher.match(document, sourceCaretOffset); if (region == null) { setStatusLineErrorMessage("No matching fence!"); sourceViewer.getTextWidget().getDisplay().beep(); return; } int offset = region.getOffset(); int length = region.getLength(); if (length < 1) return; int anchor = bracketMatcher.getAnchor(); // http://dev.eclipse.org/bugs/show_bug.cgi?id=34195 int targetOffset = (ICharacterPairMatcher.RIGHT == anchor) ? offset + 1 : offset + length; boolean visible = false; if (sourceViewer instanceof ITextViewerExtension5) { ITextViewerExtension5 extension = (ITextViewerExtension5) sourceViewer; visible = (extension.modelOffset2WidgetOffset(targetOffset) > -1); } else { IRegion visibleRegion = sourceViewer.getVisibleRegion(); // http://dev.eclipse.org/bugs/show_bug.cgi?id=34195 visible = (targetOffset >= visibleRegion.getOffset() && targetOffset <= visibleRegion.getOffset() + visibleRegion.getLength()); } if (!visible) { setStatusLineErrorMessage("Matching fence is outside the currently selected element."); sourceViewer.getTextWidget().getDisplay().beep(); return; } if (selection.getLength() < 0) targetOffset -= selection.getLength(); sourceViewer.setSelectedRange(targetOffset, selection.getLength()); sourceViewer.revealRange(targetOffset, selection.getLength()); } private boolean isBracket(char character) { String[][] fences = getFences(); for (int i = 0; i != fences.length; ++i) { if (fences[i][0].indexOf(character) >= 0) return true; if (fences[i][1].indexOf(character) >= 0) return true; } return false; } private boolean isSurroundedByBrackets(IDocument document, int offset) { if (offset == 0 || offset == document.getLength()) return false; try { return isBracket(document.getChar(offset - 1)) && isBracket(document.getChar(offset)); } catch (BadLocationException e) { return false; } } /** * Returns the signed current selection. * The length will be negative if the resulting selection * is right-to-left (RtoL). * <p> * The selection offset is model based. * </p> * * @param sourceViewer the source viewer * @return a region denoting the current signed selection, for a resulting RtoL selections length is < 0 */ public IRegion getSignedSelection(ISourceViewer sourceViewer) { StyledText text = sourceViewer.getTextWidget(); Point selection = text.getSelectionRange(); if (text.getCaretOffset() == selection.x) { selection.x = selection.x + selection.y; selection.y = -selection.y; } selection.x = widgetOffset2ModelOffset(sourceViewer, selection.x); return new Region(selection.x, selection.y); } private Map<IMarker, Annotation> markerParseAnnotations = new HashMap<IMarker, Annotation>(); private Map<IMarker, MarkerAnnotation> markerMarkerAnnotations = new HashMap<IMarker, MarkerAnnotation>(); /** * Refresh the marker annotations on the input document by removing any * that do not map to current parse annotations. Do this for problem * markers, specifically; ignore other types of markers. * * SMS 25 Apr 2007 */ public void refreshMarkerAnnotations(String problemMarkerType) { // Get current marker annotations IAnnotationModel model = getDocumentProvider().getAnnotationModel(getEditorInput()); List<MarkerAnnotation> markerAnnotations = new ArrayList<MarkerAnnotation>(); for (Iterator iter = model.getAnnotationIterator(); iter.hasNext();) { Object ann = iter.next(); if (ann instanceof MarkerAnnotation) { markerAnnotations.add((MarkerAnnotation) ann); } } // For the current marker annotations, if any lacks a corresponding // parse annotation, delete the marker annotation from the document's // annotation model (but leave the marker on the underlying resource, // which presumably hasn't been changed, despite changes to the document) for (int i = 0; i < markerAnnotations.size(); i++) { MarkerAnnotation markerAnnotation = markerAnnotations.get(i); IMarker marker = markerAnnotation.getMarker(); try { if (marker.getType().equals(problemMarkerType)) { if (markerParseAnnotations.get(marker) == null) { model.removeAnnotation(markerAnnotation); } } } catch (CoreException e) { // If we get a core exception here, probably something is wrong with the // marker, and we probably don't want to keep any annotation that may be // associated with it (I don't think) model.removeAnnotation(markerAnnotation); continue; } } } /** * This is a type of listener whose purpose is to monitor changes to a document * annotation model and to maintain at a mapping from markers on the underlying * resource to parse annotations on the document. * * The association of markers to annotations is determined by a subroutine that * may be more or less sophisticated in how it identifies associations. The * accuracy of the map depends on the implementation of this routine. (The * current implementation of the method simply compares text ranges of annotations * and markers.) * * The motivating purpose of the mapping is to enable the identification of marker * annotations that are (or are not) associated with a current parse annotation. * Then, for instance, marker annotations that are not associated with current parse * annotations might be removed from the document. * * No assumptions are made here about the type (or types) of marker annotation of * interest; all types of marker annotation are considered. * * SMS 25 Apr 2007 */ protected class InputAnnotationModelListener implements IAnnotationModelListener { public void modelChanged(IAnnotationModel model) { List<Annotation> currentParseAnnotations = new ArrayList<Annotation>(); List<IMarker> currentMarkers = new ArrayList<IMarker>(); markerParseAnnotations = new HashMap<IMarker, Annotation>(); markerMarkerAnnotations = new HashMap<IMarker, MarkerAnnotation>(); // Collect the current set of markers and parse annotations; // also maintain a map of markers to marker annotations (as // there doesn't seem to be a way to get from a marker to the // annotations that may represent it) for (Iterator iter = model.getAnnotationIterator(); iter.hasNext();) { Object ann = iter.next(); if (ann instanceof MarkerAnnotation) { IMarker marker = ((MarkerAnnotation) ann).getMarker(); if (marker.exists()) { currentMarkers.add(marker); } markerMarkerAnnotations.put(marker, (MarkerAnnotation) ann); } else if (ann instanceof Annotation) { Annotation annotation = (Annotation) ann; if (isParseAnnotation(annotation)) { currentParseAnnotations.add(annotation); } } } // Create a mapping between current markers and parse annotations for (int i = 0; i < currentMarkers.size(); i++) { IMarker marker = currentMarkers.get(i); Annotation annotation = findParseAnnotationForMarker(model, marker, currentParseAnnotations); if (annotation != null) { markerParseAnnotations.put(marker, annotation); } } } public Annotation findParseAnnotationForMarker(IAnnotationModel model, IMarker marker, List<Annotation> parseAnnotations) { Integer markerStartAttr = null; Integer markerEndAttr = null; try { // SMS 22 May 2007: With markers created through the editor the CHAR_START // and CHAR_END attributes are null, giving rise to NPEs here. Not sure // why this happens, but it seems to help down the line to trap the NPE. markerStartAttr = ((Integer) marker.getAttribute(IMarker.CHAR_START)); markerEndAttr = ((Integer) marker.getAttribute(IMarker.CHAR_END)); if (markerStartAttr == null || markerEndAttr == null) { return null; } } catch (Exception e) { e.printStackTrace(); return null; } int markerStart = markerStartAttr.intValue(); int markerEnd = markerEndAttr.intValue(); int markerLength = markerEnd - markerStart; for (int j = 0; j < parseAnnotations.size(); j++) { Annotation parseAnnotation = parseAnnotations.get(j); Position pos = model.getPosition(parseAnnotation); if (pos != null) { int annotationStart = pos.offset; int annotationLength = pos.length; if (markerStart == annotationStart && markerLength == annotationLength) { return parseAnnotation; } } } return null; } } public static boolean isParseAnnotation(Annotation a) { return a.getType().startsWith(PARSE_ANNOTATION_TYPE); } protected void doSetInput(IEditorInput input) throws CoreException { // Catch CoreExceptions here, since it's possible that things like IOExceptions occur // while retrieving the input's contents, e.g., if the given input doesn't exist. try { super.doSetInput(input); } catch (CoreException e) { if (e.getCause() instanceof IOException) { throw new CoreException(new Status(IStatus.ERROR, CeylonPlugin.PLUGIN_ID, 0, "Unable to read source text", e.getStatus().getException())); } } setInsertMode(SMART_INSERT); // SMS 25 Apr 2007 // Added for maintenance of associations between marker annotations // and parse annotations IAnnotationModel annotationModel = getDocumentProvider().getAnnotationModel(input); // RMF 6 Jun 2007 - Not sure why annotationModel is null for files outside the // workspace, but they are, so make sure we don't cause an NPE here. if (annotationModel != null) annotationModel.addAnnotationModelListener(new InputAnnotationModelListener()); } /** * Add a Model listener to this editor. Any time the underlying AST is recomputed, the listener is notified. * * @param listener the listener to notify of Model changes */ public void addModelListener(TreeLifecycleListener listener) { parserScheduler.addModelListener(listener); } /** * Remove a Model listener from this editor. * * @param listener the listener to remove */ public void removeModelListener(TreeLifecycleListener listener) { parserScheduler.removeModelListener(listener); } private AnnotationCreator annotationCreator = new AnnotationCreator(this); private EditorIconUpdater editorIconUpdater; private IProblemChangedListener annotationUpdater; private class MarkerAnnotationUpdater implements TreeLifecycleListener { public Stage getStage() { return TYPE_ANALYSIS; } public void update(CeylonParseController parseController, IProgressMonitor monitor) { // SMS 25 Apr 2007 // Since parsing has finished, check whether the marker annotations // are up-to-date with the most recent parse annotations. // Assuming that's often enough--i.e., don't refresh the marker // annotations after every update to the document annotation model // since there will be many of these, including possibly many that // don't relate to problem markers. refreshMarkerAnnotations(PROBLEM_MARKER_ID); refreshMarkerAnnotations(TASK_MARKER_ID); } } public String getSelectionText() { IRegion sel = getSelection(); IFileEditorInput fileEditorInput = (IFileEditorInput) getEditorInput(); IDocument document = getDocumentProvider().getDocument(fileEditorInput); try { return document.get(sel.getOffset(), sel.getLength()); } catch (BadLocationException e) { e.printStackTrace(); return ""; } } public IRegion getSelection() { ITextSelection ts = (ITextSelection) getSelectionProvider().getSelection(); return new Region(ts.getOffset(), ts.getLength()); } public boolean canPerformFind() { return true; } public CeylonParseController getParseController() { return parseController; } public String toString() { return "Ceylon Editor for " + getEditorInput().getName(); } } /*class GotoAnnotationAction extends TextEditorAction { public static final String PREFIX= RuntimePlugin.IMP_RUNTIME + '.'; private static final String nextAnnotationContextID= PREFIX + "goto_next_error_action"; private static final String prevAnnotationContextID= PREFIX + "goto_previous_error_action"; private boolean fForward; public GotoAnnotationAction(String prefix, boolean forward) { super(CeylonEditor.fgBundleForConstructedKeys, prefix, null); fForward= forward; if (forward) PlatformUI.getWorkbench().getHelpSystem().setHelp(this, nextAnnotationContextID); else PlatformUI.getWorkbench().getHelpSystem().setHelp(this, prevAnnotationContextID); } public void run() { CeylonEditor e= (CeylonEditor) getTextEditor(); e.gotoAnnotation(fForward); } public void setEditor(ITextEditor editor) { if (editor instanceof CeylonEditor) super.setEditor(editor); update(); } public void update() { setEnabled(getTextEditor() instanceof CeylonEditor); } }*/