/******************************************************************************* * 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.CeylonSourceViewerConfiguration.configCompletionPopup; import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.ADD_BLOCK_COMMENT; import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.CORRECT_INDENTATION; import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.GOTO_MATCHING_FENCE; import static com.redhat.ceylon.eclipse.code.editor.EditorActionIds.REMOVE_BLOCK_COMMENT; 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.outline.CeylonLabelProvider.getImageForFile; import static com.redhat.ceylon.eclipse.code.preferences.CeylonPreferenceInitializer.CLEAN_IMPORTS; import static com.redhat.ceylon.eclipse.code.preferences.CeylonPreferenceInitializer.FORMAT; import static com.redhat.ceylon.eclipse.code.preferences.CeylonPreferenceInitializer.NORMALIZE_NL; import static com.redhat.ceylon.eclipse.code.preferences.CeylonPreferenceInitializer.NORMALIZE_WS; import static com.redhat.ceylon.eclipse.code.preferences.CeylonPreferenceInitializer.STRIP_TRAILING_WS; import static com.redhat.ceylon.eclipse.code.preferences.CeylonPreferenceInitializer.SUB_WORD_NAVIGATION; import static com.redhat.ceylon.eclipse.java2ceylon.Java2CeylonProxies.editorJ2C; import static com.redhat.ceylon.eclipse.java2ceylon.Java2CeylonProxies.importsJ2C; import static com.redhat.ceylon.eclipse.java2ceylon.Java2CeylonProxies.modelJ2C; import static com.redhat.ceylon.eclipse.java2ceylon.Java2CeylonProxies.utilJ2C; import static com.redhat.ceylon.eclipse.ui.CeylonPlugin.PLUGIN_ID; import static com.redhat.ceylon.eclipse.util.EditorUtil.getCurrentTheme; import static com.redhat.ceylon.eclipse.util.Nodes.findNode; import static java.util.ResourceBundle.getBundle; import static org.eclipse.core.resources.IncrementalProjectBuilder.CLEAN_BUILD; import static org.eclipse.core.resources.ResourcesPlugin.getWorkspace; import static org.eclipse.jdt.ui.PreferenceConstants.EDITOR_FOLDING_ENABLED; import static org.eclipse.jface.text.DocumentRewriteSessionType.SEQUENTIAL; import static org.eclipse.ui.PlatformUI.getWorkbench; import static org.eclipse.ui.texteditor.AbstractDecoratedTextEditorPreferenceConstants.EDITOR_SPACES_FOR_TABS; 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 java.lang.reflect.Method; import java.text.BreakIterator; import java.text.CharacterIterator; import java.util.Iterator; import java.util.List; import java.util.ResourceBundle; import java.util.regex.Pattern; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; 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.Status; import org.eclipse.debug.ui.actions.IRunToLineTarget; import org.eclipse.debug.ui.actions.IToggleBreakpointsTarget; import org.eclipse.debug.ui.actions.ToggleBreakpointAction; import org.eclipse.jdt.internal.debug.ui.actions.JavaBreakpointPropertiesRulerAction; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IContributionItem; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.Separator; import org.eclipse.jface.commands.ActionHandler; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.DocumentRewriteSession; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentExtension4; import org.eclipse.jface.text.IDocumentListener; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.contentassist.ContentAssistant; import org.eclipse.jface.text.link.LinkedModeModel; import org.eclipse.jface.text.link.LinkedPosition; import org.eclipse.jface.text.source.CompositeRuler; 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.IVerticalRulerInfo; import org.eclipse.jface.text.source.SourceViewer; 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.jface.viewers.IPostSelectionProvider; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.CaretEvent; import org.eclipse.swt.custom.CaretListener; import org.eclipse.swt.custom.ST; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.custom.StyledTextContent; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Shell; import org.eclipse.text.edits.ReplaceEdit; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IFileEditorInput; import org.eclipse.ui.IPropertyListener; import org.eclipse.ui.IWorkbenchPartSite; import org.eclipse.ui.editors.text.EditorsUI; import org.eclipse.ui.editors.text.TextEditor; import org.eclipse.ui.handlers.IHandlerActivation; import org.eclipse.ui.handlers.IHandlerService; import org.eclipse.ui.ide.FileStoreEditorInput; import org.eclipse.ui.texteditor.AbstractTextEditor; import org.eclipse.ui.texteditor.AnnotationPreference; 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.MarkerAnnotationPreferences; 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.themes.IThemeManager; import org.eclipse.ui.views.contentoutline.IContentOutlinePage; import com.redhat.ceylon.compiler.typechecker.tree.Node; import com.redhat.ceylon.eclipse.code.outline.CeylonOutlinePage; import com.redhat.ceylon.eclipse.code.outline.NavigateMenuItems; 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.code.preferences.CeylonCompletionPreferencePage; import com.redhat.ceylon.eclipse.code.preferences.CeylonEditorPreferencePage; import com.redhat.ceylon.eclipse.code.preferences.CeylonFiltersPreferencePage; import com.redhat.ceylon.eclipse.code.preferences.CeylonOpenDialogsPreferencePage; import com.redhat.ceylon.eclipse.code.preferences.CeylonOutlinesPreferencePage; import com.redhat.ceylon.eclipse.code.preferences.CeylonRefactoringPreferencePage; import com.redhat.ceylon.eclipse.code.preferences.CeylonSaveActionsPreferencePage; import com.redhat.ceylon.eclipse.code.refactor.RefactorMenuItems; import com.redhat.ceylon.eclipse.code.search.FindMenuItems; import com.redhat.ceylon.eclipse.ui.CeylonPlugin; import com.redhat.ceylon.eclipse.util.EditorUtil; import com.redhat.ceylon.eclipse.util.Highlights; import com.redhat.ceylon.ide.common.model.CeylonProject; import com.redhat.ceylon.ide.common.model.CeylonProjectBuild; import com.redhat.ceylon.ide.common.model.ModelListener; import com.redhat.ceylon.ide.common.typechecker.ExternalPhasedUnit; import com.redhat.ceylon.ide.common.typechecker.ProjectPhasedUnit; import com.redhat.ceylon.ide.common.vfs.FileVirtualFile; import ceylon.language.Iterable; /** * An editor for Ceylon source code. * * @author Gavin King * @author Chris Laffra * @author Robert M. Fuhrer */ public class CeylonEditor extends TextEditor implements ModelListener<IProject, IResource, IFolder, IFile> { private static final Pattern TRAILING_WS = Pattern.compile("[ \\t]+$", Pattern.MULTILINE); public static final String MESSAGE_BUNDLE = "com.redhat.ceylon.eclipse.code.editor.EditorActionMessages"; private static final int REPARSE_SCHEDULE_DELAY = 200; private CeylonParserScheduler parserScheduler; private ProblemMarkerManager problemMarkerManager; private ICharacterPairMatcher bracketMatcher; private ToggleBreakpointAction toggleBreakpointAction; private IAction enableDisableBreakpointAction; private IAction breakpointPropertiesAction; private FoldingActionGroup foldingActionGroup; private SourceArchiveDocumentProvider sourceArchiveDocumentProvider; private ToggleBreakpointAdapter toggleBreakpointTarget; private CeylonOutlinePage outlinePage; private boolean backgroundParsingPaused; private CeylonParseController parseController; private ProjectionSupport projectionSupport; private LinkedModeModel linkedMode; private Object linkedModeOwner; private MarkerAnnotationUpdater markerAnnotationUpdater = new MarkerAnnotationUpdater(this); private ProjectionAnnotationManager projectionAnnotationManager = new ProjectionAnnotationManager(this); private AnnotationCreator annotationCreator = new AnnotationCreator(this); private RefinementAnnotationCreator refinementAnnotationCreator = new RefinementAnnotationCreator(this); private RangeAnnotationCreator rangeAnnotationCreator = new RangeAnnotationCreator(this); ToggleFoldingRunner fFoldingRunner; public CeylonEditor() { setSourceViewerConfiguration(createSourceViewerConfiguration()); setRangeIndicator(new CeylonRangeIndicator()); configureInsertMode(SMART_INSERT, true); setInsertMode(SMART_INSERT); problemMarkerManager = new ProblemMarkerManager(); parseController = new CeylonParseController(); } static String[][] getFences() { return new String[][] { { "(", ")" }, { "[", "]" }, { "{", "}" } }; } public synchronized void pauseBackgroundParsing() { backgroundParsingPaused = true; } public synchronized void unpauseBackgroundParsing() { backgroundParsingPaused = false; if (shouldForceBackgroundParsing) { scheduleParsing(true); } } public synchronized boolean isBackgroundParsingPaused() { return backgroundParsingPaused; } public boolean isInLinkedMode() { return linkedMode!=null; } public void setLinkedMode(LinkedModeModel linkedMode, Object linkedModeOwner) { this.linkedMode = linkedMode; this.linkedModeOwner = linkedModeOwner; } public void clearLinkedMode() { this.linkedMode = null; this.linkedModeOwner = null; } public LinkedModeModel getLinkedMode() { return linkedMode; } public Object getLinkedModeOwner() { return linkedModeOwner; } public AnnotationCreator getAnnotationCreator() { return annotationCreator; } /** * 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(this); } public Object getAdapter( @SuppressWarnings("rawtypes") Class required) { if (IContentOutlinePage.class.equals(required)) { return getOutlinePage(); } if (IToggleBreakpointsTarget.class.equals(required)) { return getToggleBreakpointAdapter(); } if (IRunToLineTarget.class.equals(required)) { return getToggleBreakpointAdapter(); } return super.getAdapter(required); } public Object getToggleBreakpointAdapter() { if (toggleBreakpointTarget == null) { toggleBreakpointTarget = new ToggleBreakpointAdapter(); } return toggleBreakpointTarget; } public CeylonOutlinePage getOutlinePage() { if (outlinePage == null || outlinePage.isDisposed()) { outlinePage = new CeylonOutlinePage( getParseController(), getCeylonSourceViewer()); parserScheduler.addModelListener(outlinePage); } return outlinePage; } @Override protected void editorContextMenuAboutToShow(IMenuManager menu) { super.editorContextMenuAboutToShow(menu); menu.remove(ITextEditorActionConstants.SAVE); menu.remove(ITextEditorActionConstants.REVERT); menu.remove(ITextEditorActionConstants.SHIFT_LEFT); menu.remove(ITextEditorActionConstants.SHIFT_RIGHT); menu.remove(ITextEditorActionConstants.QUICK_ASSIST); menu.remove(ITextEditorActionConstants.CUT); menu.remove(ITextEditorActionConstants.COPY); menu.remove(ITextEditorActionConstants.PASTE); menu.remove(ITextEditorActionConstants.UNDO); } 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); IVerticalRuler verticalRuler = getVerticalRuler(); if (verticalRuler!=null) { IDocument document = getDocumentProvider() .getDocument(getEditorInput()); toggleBreakpointAction = new ToggleBreakpointAction(this, document, verticalRuler); setAction("ToggleBreakpoint", action); enableDisableBreakpointAction= new RulerEnableDisableBreakpointAction( this, verticalRuler); setAction("ToggleBreakpoint", action); breakpointPropertiesAction = new JavaBreakpointPropertiesRulerAction( this, verticalRuler); } // 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, "AddBlockComment.", this, CeylonSourceViewer.ADD_BLOCK_COMMENT); action.setActionDefinitionId(ADD_BLOCK_COMMENT); setAction(ADD_BLOCK_COMMENT, action); markAsStateDependentAction(ADD_BLOCK_COMMENT, true); markAsSelectionDependentAction(ADD_BLOCK_COMMENT, true); //PlatformUI.getWorkbench().getHelpSystem().setHelp(action, IJavaHelpContextIds.ADD_BLOCK_COMMENT_ACTION); action = new TextOperationAction(bundle, "RemoveBlockComment.", this, CeylonSourceViewer.REMOVE_BLOCK_COMMENT); action.setActionDefinitionId(REMOVE_BLOCK_COMMENT); setAction(REMOVE_BLOCK_COMMENT, action); markAsStateDependentAction(REMOVE_BLOCK_COMMENT, true); markAsSelectionDependentAction(REMOVE_BLOCK_COMMENT, true); //PlatformUI.getWorkbench().getHelpSystem().setHelp(action, IJavaHelpContextIds.REMOVE_BLOCK_COMMENT_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 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, "ShowInHierarchyView.", this, CeylonSourceViewer.SHOW_IN_HIERARCHY_VIEW, true); action.setActionDefinitionId(EditorActionIds.SHOW_IN_CEYLON_HIERARCHY_VIEW); setAction(EditorActionIds.SHOW_IN_CEYLON_HIERARCHY_VIEW, action); action = new TextOperationAction(bundle, "ShowReferences.", this, CeylonSourceViewer.SHOW_REFERENCES, true); action.setActionDefinitionId(EditorActionIds.SHOW_CEYLON_REFERENCES); setAction(EditorActionIds.SHOW_CEYLON_REFERENCES, action); action = new TextOperationAction(bundle, "ShowCode.", this, CeylonSourceViewer.SHOW_DEFINITION, true); action.setActionDefinitionId(EditorActionIds.SHOW_CEYLON_CODE); setAction(EditorActionIds.SHOW_CEYLON_CODE, action); action = editorJ2C().newEclipseTerminateStatementAction(this); action.setActionDefinitionId(EditorActionIds.TERMINATE_STATEMENT); setAction(EditorActionIds.TERMINATE_STATEMENT, action); action = new FormatBlockAction(this); action.setActionDefinitionId(EditorActionIds.FORMAT_BLOCK); setAction(EditorActionIds.FORMAT_BLOCK, action); action = new FormatAction(this); action.setActionDefinitionId(EditorActionIds.FORMAT); setAction(EditorActionIds.FORMAT, action); foldingActionGroup = new FoldingActionGroup(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"); installQuickAccessAction(); } @Override protected String[] collectContextMenuPreferencePages() { String[] pages = super.collectContextMenuPreferencePages(); String[] result = new String[pages.length+7]; System.arraycopy(pages, 0, result, 7, pages.length); result[0] = CeylonEditorPreferencePage.ID; result[1] = CeylonCompletionPreferencePage.ID; result[2] = CeylonRefactoringPreferencePage.ID; result[3] = CeylonSaveActionsPreferencePage.ID; result[4] = CeylonOutlinesPreferencePage.ID; result[5] = CeylonOpenDialogsPreferencePage.ID; result[6] = CeylonFiltersPreferencePage.ID; return result; } @Override protected void createNavigationActions() { super.createNavigationActions(); 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);*/ IAction action = new NavigatePreviousSubWordAction(); action.setActionDefinitionId(WORD_PREVIOUS); setAction(WORD_PREVIOUS, action); textWidget.setKeyBinding(SWT.CTRL | SWT.ARROW_LEFT, SWT.NULL); action = new NavigateNextSubWordAction(); action.setActionDefinitionId(WORD_NEXT); setAction(WORD_NEXT, action); textWidget.setKeyBinding(SWT.CTRL | SWT.ARROW_RIGHT, SWT.NULL); action = new SelectPreviousSubWordAction(); action.setActionDefinitionId(SELECT_WORD_PREVIOUS); setAction(SELECT_WORD_PREVIOUS, action); textWidget.setKeyBinding(SWT.CTRL | SWT.SHIFT | SWT.ARROW_LEFT, SWT.NULL); action = new SelectNextSubWordAction(); action.setActionDefinitionId(SELECT_WORD_NEXT); setAction(SELECT_WORD_NEXT, action); textWidget.setKeyBinding(SWT.CTRL | SWT.SHIFT | SWT.ARROW_RIGHT, SWT.NULL); action = new DeletePreviousSubWordAction(); action.setActionDefinitionId(DELETE_PREVIOUS_WORD); setAction(DELETE_PREVIOUS_WORD, action); textWidget.setKeyBinding(SWT.CTRL | SWT.BS, SWT.NULL); markAsStateDependentAction(DELETE_PREVIOUS_WORD, true); action = new DeleteNextSubWordAction(); action.setActionDefinitionId(DELETE_NEXT_WORD); setAction(DELETE_NEXT_WORD, action); textWidget.setKeyBinding(SWT.CTRL | SWT.DEL, SWT.NULL); markAsStateDependentAction(DELETE_NEXT_WORD, true); } /** * Text navigation action to navigate to the next sub-word. * * @since 3.0 */ 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 if (!CeylonPlugin.getPreferences() .getBoolean(SUB_WORD_NAVIGATION)) { super.run(); return; } ISourceViewer viewer = getSourceViewer(); IDocument document = viewer.getDocument(); try { CharacterIterator iter = (CharacterIterator) new DocumentCharacterIterator( document); fIterator.setText(iter); int caretOffset = viewer.getTextWidget() .getCaretOffset(); int position = widgetOffset2ModelOffset(viewer, caretOffset); if (position == -1) return; int next= findNextPosition(position); if (isBlockSelectionModeEnabled() && document.getLineOfOffset(next) != document.getLineOfOffset(position)) { super.run(); // may navigate into virtual white space } else 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 && next != BreakIterator.DONE) { LinkedPosition pos = new LinkedPosition(document, position, 0); LinkedPosition linkedPosition = model.findPosition(pos); if (linkedPosition != null) { int linkedPositionEnd = linkedPosition.getOffset() + linkedPosition.getLength(); if (position != linkedPositionEnd && linkedPositionEnd < next) next= linkedPositionEnd; } else { LinkedPosition nxt = new LinkedPosition(document, next, 0); LinkedPosition nextLinkedPosition= model.findPosition(nxt); 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); } /** * Text navigation action to navigate to the next sub-word. * * @since 3.0 */ protected class NavigateNextSubWordAction extends NextSubWordAction { /** * Creates a new navigate next sub-word action. */ public NavigateNextSubWordAction() { super(ST.WORD_NEXT); } @Override protected void setCaretPosition(final int position) { ISourceViewer viewer = getSourceViewer(); int offset = modelOffset2WidgetOffset(viewer, position); getTextWidget().setCaretOffset(offset); } } /** * Text operation action to delete the next sub-word. * * @since 3.0 */ protected class DeleteNextSubWordAction extends NextSubWordAction implements IUpdate { /** * Creates a new delete next sub-word action. */ public DeleteNextSubWordAction() { super(ST.DELETE_WORD_NEXT); } @Override protected void setCaretPosition(final int position) { if (!validateEditorInputState()) { return; } final ISourceViewer viewer = getSourceViewer(); StyledText text = viewer.getTextWidget(); Point widgetSelection = text.getSelection(); int caretOffset = text.getCaretOffset(); if (isBlockSelectionModeEnabled() && widgetSelection.y != widgetSelection.x) { int offset = modelOffset2WidgetOffset(viewer, position); if (caretOffset == widgetSelection.x) { text.setSelectionRange(widgetSelection.y, offset - widgetSelection.y); } else { text.setSelectionRange(widgetSelection.x, offset - widgetSelection.x); } text.invokeAction(ST.DELETE_NEXT); } else { Point selection = viewer.getSelectedRange(); final int caret, length; if (selection.y != 0) { caret = selection.x; length = selection.y; } else { caret = widgetOffset2ModelOffset(viewer, caretOffset); length = position - caret; } try { viewer.getDocument() .replace(caret, length, ""); } catch (BadLocationException exception) { // Should not happen } } } public void update() { setEnabled(isEditorInputModifiable()); } } /** * Text operation action to select the next sub-word. * * @since 3.0 */ protected class SelectNextSubWordAction extends NextSubWordAction { /** * Creates a new select next sub-word action. */ public SelectNextSubWordAction() { super(ST.SELECT_WORD_NEXT); } @Override protected void setCaretPosition(final int position) { ISourceViewer viewer = getSourceViewer(); StyledText text = viewer.getTextWidget(); if (text != null && !text.isDisposed()) { Point selection = text.getSelection(); int caret = text.getCaretOffset(); int offset = modelOffset2WidgetOffset(viewer, position); if (caret == selection.x) { text.setSelectionRange(selection.y, offset - selection.y); } else { text.setSelectionRange(selection.x, offset - selection.x); } } } } /** * Text navigation action to navigate to the previous sub-word. * * @since 3.0 */ 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 if (!CeylonPlugin.getPreferences() .getBoolean(SUB_WORD_NAVIGATION)) { super.run(); return; } ISourceViewer viewer = getSourceViewer(); IDocument document = viewer.getDocument(); try { CharacterIterator iter = (CharacterIterator) new DocumentCharacterIterator( document); fIterator.setText(iter); int caretOffset = viewer.getTextWidget() .getCaretOffset(); int position = widgetOffset2ModelOffset(viewer, caretOffset); if (position == -1) { return; } int previous = findPreviousPosition(position); if (isBlockSelectionModeEnabled() && document.getLineOfOffset(previous) != document.getLineOfOffset(position)) { super.run(); // may navigate into virtual white space } else 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 && previous != BreakIterator.DONE) { LinkedPosition pos = new LinkedPosition(document, position, 0); LinkedPosition linkedPosition = model.findPosition(pos); if (linkedPosition != null) { int linkedPositionOffset = linkedPosition.getOffset(); if (position != linkedPositionOffset && previous < linkedPositionOffset) previous= linkedPositionOffset; } else { LinkedPosition prev = new LinkedPosition(document, previous, 0); LinkedPosition previousLinkedPosition = model.findPosition(prev); 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); } /** * Text navigation action to navigate to the previous sub-word. * * @since 3.0 */ protected class NavigatePreviousSubWordAction extends PreviousSubWordAction { /** * Creates a new navigate previous sub-word action. */ public NavigatePreviousSubWordAction() { super(ST.WORD_PREVIOUS); } @Override protected void setCaretPosition(final int position) { ISourceViewer viewer = getSourceViewer(); int offset = modelOffset2WidgetOffset(viewer, position); getTextWidget().setCaretOffset(offset); } } /** * Text operation action to delete the previous sub-word. * * @since 3.0 */ protected class DeletePreviousSubWordAction extends PreviousSubWordAction implements IUpdate { /** * Creates a new delete previous sub-word action. */ public DeletePreviousSubWordAction() { super(ST.DELETE_WORD_PREVIOUS); } @Override protected void setCaretPosition(int position) { if (!validateEditorInputState()) { return; } final int length; ISourceViewer viewer = getSourceViewer(); StyledText text = viewer.getTextWidget(); Point widgetSelection = text.getSelection(); if (isBlockSelectionModeEnabled() && widgetSelection.y != widgetSelection.x) { int caret = text.getCaretOffset(); int offset = modelOffset2WidgetOffset(viewer, position); if (caret == widgetSelection.x) { text.setSelectionRange(widgetSelection.y, offset - widgetSelection.y); } else { text.setSelectionRange(widgetSelection.x, offset - widgetSelection.x); } text.invokeAction(ST.DELETE_PREVIOUS); } else { 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()); } } /** * Text operation action to select the previous sub-word. * * @since 3.0 */ protected class SelectPreviousSubWordAction extends PreviousSubWordAction { /** * Creates a new select previous sub-word action. */ public SelectPreviousSubWordAction() { super(ST.SELECT_WORD_PREVIOUS); } @Override protected void setCaretPosition(final int position) { final ISourceViewer viewer = getSourceViewer(); final StyledText text = viewer.getTextWidget(); if (text != null && !text.isDisposed()) { Point selection = text.getSelection(); int caret = text.getCaretOffset(); 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 void initializeKeyBindingScopes() { setKeyBindingScopes(new String[] { PLUGIN_ID + ".context", PLUGIN_ID + ".wizardContext" }); } private IHandlerActivation fSourceQuickAccessHandlerActivation; private IHandlerActivation fFindQuickAccessHandlerActivation; private IHandlerActivation fRefactorQuickAccessHandlerActivation; private IHandlerActivation fNavigateQuickAccessHandlerActivation; private IHandlerService fHandlerService; public static final String REFACTOR_MENU_ID = CeylonPlugin.PLUGIN_ID + ".menu.refactorQuickMenu"; public static final String NAVIGATE_MENU_ID = CeylonPlugin.PLUGIN_ID + ".menu.navigateQuickMenu"; public static final String FIND_MENU_ID = CeylonPlugin.PLUGIN_ID + ".menu.findQuickMenu"; public static final String SOURCE_MENU_ID = CeylonPlugin.PLUGIN_ID + ".menu.sourceQuickMenu"; private class NavigateQuickAccessAction extends QuickMenuAction { public NavigateQuickAccessAction() { super(NAVIGATE_MENU_ID); } protected void fillMenu(IMenuManager menu) { IContributionItem[] cis = new NavigateMenuItems() .getContributionItems(); for (IContributionItem ci: cis) { menu.add(ci); } } } private class RefactorQuickAccessAction extends QuickMenuAction { public RefactorQuickAccessAction() { super(REFACTOR_MENU_ID); } protected void fillMenu(IMenuManager menu) { IContributionItem[] cis = new RefactorMenuItems() .getContributionItems(); for (IContributionItem ci: cis) { menu.add(ci); } } } private class FindQuickAccessAction extends QuickMenuAction { public FindQuickAccessAction() { super(FIND_MENU_ID); } protected void fillMenu(IMenuManager menu) { IContributionItem[] cis = new FindMenuItems() .getContributionItems(); for (IContributionItem ci: cis) { menu.add(ci); } } } private class SourceQuickAccessAction extends QuickMenuAction { public SourceQuickAccessAction() { super(SOURCE_MENU_ID); } protected void fillMenu(IMenuManager menu) { IContributionItem[] cis = new SourceMenuItems() .getContributionItems(); for (IContributionItem ci: cis) { menu.add(ci); } } } private void installQuickAccessAction() { fHandlerService = (IHandlerService) getSite().getService(IHandlerService.class); if (fHandlerService != null) { QuickMenuAction navigateQuickAccessAction = new NavigateQuickAccessAction(); fNavigateQuickAccessHandlerActivation = fHandlerService.activateHandler( navigateQuickAccessAction.getActionDefinitionId(), new ActionHandler(navigateQuickAccessAction)); QuickMenuAction refactorQuickAccessAction = new RefactorQuickAccessAction(); fRefactorQuickAccessHandlerActivation = fHandlerService.activateHandler( refactorQuickAccessAction.getActionDefinitionId(), new ActionHandler(refactorQuickAccessAction)); QuickMenuAction findQuickAccessAction = new FindQuickAccessAction(); fFindQuickAccessHandlerActivation = fHandlerService.activateHandler( findQuickAccessAction.getActionDefinitionId(), new ActionHandler(findQuickAccessAction)); QuickMenuAction sourceQuickAccessAction = new SourceQuickAccessAction(); fSourceQuickAccessHandlerActivation = fHandlerService.activateHandler( sourceQuickAccessAction.getActionDefinitionId(), new ActionHandler(sourceQuickAccessAction)); } } protected void uninstallQuickAccessAction() { if (fHandlerService != null) { fHandlerService.deactivateHandler( fNavigateQuickAccessHandlerActivation); fHandlerService.deactivateHandler( fRefactorQuickAccessHandlerActivation); fHandlerService.deactivateHandler( fFindQuickAccessHandlerActivation); fHandlerService.deactivateHandler( fSourceQuickAccessHandlerActivation); } } protected boolean isOverviewRulerVisible() { return true; } protected void rulerContextMenuAboutToShow(IMenuManager menu) { addDebugActions(menu); super.rulerContextMenuAboutToShow(menu); menu.appendToGroup(GROUP_RULERS, new Separator()); menu.appendToGroup(GROUP_RULERS, getAction("FoldingToggle")); menu.appendToGroup(GROUP_RULERS, getAction("FoldingExpandAll")); menu.appendToGroup(GROUP_RULERS, getAction("FoldingCollapseAll")); menu.appendToGroup(GROUP_RULERS, getAction("FoldingCollapseImports")); menu.appendToGroup(GROUP_RULERS, getAction("FoldingCollapseComments")); } private void addDebugActions(IMenuManager menu) { menu.add(toggleBreakpointAction); menu.add(enableDisableBreakpointAction); menu.add(breakpointPropertiesAction); } /** * 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(); super.createPartControl(parent); initiateServiceControllers(); updateTitleImage(); //setSourceFontFromPreference(); /*((IContextService) getSite().getService(IContextService.class)) .activateContext(PLUGIN_ID + ".context");*/ IThemeManager themeManager = getWorkbench().getThemeManager(); CeylonPlugin.log(Status.WARNING, "theme " + themeManager.getCurrentTheme().getId()); themeManager.addPropertyChangeListener(colorChangeListener); updateFontAndCaret(); themeManager.addPropertyChangeListener(fontChangeListener); } private boolean shouldForceBackgroundParsing = false; public synchronized void scheduleParsing(boolean force) { if (parserScheduler!=null && !backgroundParsingPaused) { parserScheduler.cancel(); if (force) parseController.dirty(); parserScheduler.schedule(REPARSE_SCHEDULE_DELAY); } else { if (backgroundParsingPaused) { shouldForceBackgroundParsing = true; } if (force) parseController.dirty(); } } private void initializeParseController() { IEditorInput editorInput = getEditorInput(); IFile file = getFile(editorInput); IPath filePath = getPath(editorInput); IProject project = file!=null && file.exists() ? file.getProject() : null; parseController.initialize(filePath, project, annotationCreator); } private IProblemChangedListener editorIconUpdater = new IProblemChangedListener() { @Override public void problemsChanged( IResource[] changedResources, boolean isMarkerChange) { if (isMarkerChange) { IEditorInput input= getEditorInput(); if (input instanceof IFileEditorInput) { // The editor might be looking at something outside the workspace (e.g. system include files). IFileEditorInput fileInput = (IFileEditorInput) input; IFile file = fileInput.getFile(); if (file != null) { for (int i=0; i<changedResources.length; i++) { if (changedResources[i].equals(file)) { Shell shell = getEditorSite().getShell(); if (shell!=null && !shell.isDisposed()) { shell.getDisplay() .syncExec(new Runnable() { @Override public void run() { updateTitleImage(); } }); } } } } } } } }; private IDocumentListener documentListener = new IDocumentListener() { public void documentAboutToBeChanged(DocumentEvent event) { if (parseController!=null) { parseController.resetStage(); } } public void documentChanged(DocumentEvent event) { synchronized (CeylonEditor.this) { scheduleParsing(true); } } }; private IResourceChangeListener buildListener = new IResourceChangeListener() { public void resourceChanged(IResourceChangeEvent event) { if (event.getBuildKind()!=CLEAN_BUILD) { scheduleParsing(true); } } }; /** * 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. We need to see when * the editor input changes, so we can watch the new * document. */ private IPropertyListener editorInputPropertyListener = 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 if (oldDoc!=null) { oldDoc.removeDocumentListener( documentListener); } curDoc.addDocumentListener( documentListener); } initializeParseController(); scheduleParsing(true); } } }; private IResourceChangeListener moveListener = new IResourceChangeListener() { public void resourceChanged(IResourceChangeEvent event) { if (parseController==null) { return; } IProject project = parseController.getProject(); if (project!=null) { //things external to the workspace don't move IPath path = parseController.getPath(); IPath oldWSRelPath = project.getFullPath().append(path); IResourceDelta rd = event.getDelta() .findMember(oldWSRelPath); if (rd != null) { if ((rd.getFlags() & IResourceDelta.MOVED_TO) != 0) { // The net effect of the following is to re-initialize() the parse controller 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 parse controller about the move - it caches the path // parserScheduler.cancel(); // avoid a race condition if ParserScheduler was starting/in the middle of a run parseController.initialize( newProjRelPath, proj, annotationCreator); } } } } }; private IProblemChangedListener 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 exactly which // annotations to remove. scheduleParsing(true); } }; private void initiateServiceControllers() { problemMarkerManager.addListener(annotationUpdater); problemMarkerManager.addListener(editorIconUpdater); parserScheduler = new CeylonParserScheduler(parseController, this); addModelListener(annotationCreator); addModelListener(refinementAnnotationCreator); installProjectionSupport(); updateProjectionAnnotationManager(); if (isEditable()) { addModelListener(markerAnnotationUpdater); } getSelectionProvider() .addPostSelectionChangedListener(rangeAnnotationCreator); IDocument document = getSourceViewer().getDocument(); if (document!=null) { document.addDocumentListener(documentListener); } addPropertyListener(editorInputPropertyListener); getWorkspace() .addResourceChangeListener(moveListener, IResourceChangeEvent.POST_CHANGE); getWorkspace() .addResourceChangeListener(buildListener, IResourceChangeEvent.POST_BUILD); modelJ2C().ceylonModel().addModelListener(this); parserScheduler.schedule(); } @Override public IPostSelectionProvider getSelectionProvider() { return (IPostSelectionProvider) super.getSelectionProvider(); } private void installProjectionSupport() { CeylonSourceViewer sourceViewer = getCeylonSourceViewer(); projectionSupport = new ProjectionSupport(sourceViewer, getAnnotationAccess(), getSharedColors()); MarkerAnnotationPreferences markerAnnotationPreferences = (MarkerAnnotationPreferences) getAdapter(MarkerAnnotationPreferences.class); if (markerAnnotationPreferences != null) { @SuppressWarnings("unchecked") List<AnnotationPreference> annPrefs = markerAnnotationPreferences.getAnnotationPreferences(); for (Iterator<AnnotationPreference> e = annPrefs.iterator(); e.hasNext();) { Object annotationType = e.next().getAnnotationType(); if (annotationType instanceof String) { projectionSupport.addSummarizableAnnotationType( (String) annotationType); } } } /*else { projectionSupport.addSummarizableAnnotationType(PARSE_ANNOTATION_TYPE_ERROR); projectionSupport.addSummarizableAnnotationType(PARSE_ANNOTATION_TYPE_WARNING); projectionSupport.addSummarizableAnnotationType("org.eclipse.ui.workbench.texteditor.error"); projectionSupport.addSummarizableAnnotationType("org.eclipse.ui.workbench.texteditor.warning"); }*/ projectionSupport.install(); IPreferenceStore store = CeylonPlugin.getPreferences(); store.setDefault(EDITOR_FOLDING_ENABLED, true); if (store.getBoolean(EDITOR_FOLDING_ENABLED)) { sourceViewer.doOperation(ProjectionViewer.TOGGLE); } sourceViewer.addProjectionListener( projectionAnnotationManager); } private void updateProjectionAnnotationManager() { CeylonSourceViewer sourceViewer = getCeylonSourceViewer(); if (sourceViewer!=null) { if (sourceViewer.isProjectionMode()) { addModelListener( projectionAnnotationManager); } else if (projectionAnnotationManager!=null) { removeModelListener( projectionAnnotationManager); } } } @Override protected void handlePreferenceStoreChanged( PropertyChangeEvent event) { super.handlePreferenceStoreChanged(event); if (EDITOR_FOLDING_ENABLED.equals(event.getProperty())) { updateProjectionAnnotationManager(); new ToggleFoldingRunner(this) .runWhenNextVisible(); } CeylonSourceViewer sourceViewer = getCeylonSourceViewer(); if (sourceViewer!=null) { ContentAssistant contentAssistant = sourceViewer.getContentAssistant(); configCompletionPopup(contentAssistant); } } private IPropertyChangeListener propertyChangeListener; @Override protected void initializeEditor() { propertyChangeListener = new IPropertyChangeListener() { @Override public void propertyChange( PropertyChangeEvent event) { handlePreferenceStoreChanged(event); } }; CeylonPlugin.getPreferences() .addPropertyChangeListener( propertyChangeListener); super.initializeEditor(); } public void updateTitleImage() { IFile file = getFile(getEditorInput()); if (file!=null) { setTitleImage(getImageForFile(file)); } } public void dispose() { if (editorIconUpdater!=null) { problemMarkerManager.removeListener( editorIconUpdater); editorIconUpdater = null; } if (annotationUpdater!=null) { problemMarkerManager.removeListener( annotationUpdater); annotationUpdater = null; } if (propertyChangeListener!=null) { CeylonPlugin.getPreferences() .removePropertyChangeListener( propertyChangeListener); propertyChangeListener = null; } outlinePage = null; if (buildListener!=null) { getWorkspace() .removeResourceChangeListener(buildListener); buildListener = null; } if (moveListener!=null) { getWorkspace() .removeResourceChangeListener(moveListener); moveListener = null; } modelJ2C().ceylonModel().removeModelListener(this); IDocument document = getParseController().getDocument(); if (document!=null) { document.removeDocumentListener( documentListener); } removePropertyListener(editorInputPropertyListener); if (toggleBreakpointAction!=null) { toggleBreakpointAction.dispose(); // this holds onto the IDocument } if (foldingActionGroup!=null) { foldingActionGroup.dispose(); } if (projectionSupport!=null) { projectionSupport.dispose(); projectionSupport = null; } if (parserScheduler!=null) { parserScheduler.cancel(); // avoid unnecessary work after the editor is asked to close down parserScheduler.dispose(); parserScheduler = null; } parseController = null; uninstallQuickAccessAction(); super.dispose(); /*if (fResourceListener != null) { ResourcesPlugin.getWorkspace().removeResourceChangeListener(fResourceListener); }*/ IThemeManager themeManager = getWorkbench().getThemeManager(); themeManager.removePropertyChangeListener(colorChangeListener); themeManager.removePropertyChangeListener(fontChangeListener); } private IPropertyChangeListener colorChangeListener = new IPropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent event) { if (Highlights.isColorChange(event)) { CeylonPlugin.log(Status.WARNING, event.getProperty() + "=" + event.getNewValue()); Highlights.refreshColors(); getSourceViewer() .invalidateTextPresentation(); } } }; IPropertyChangeListener fontChangeListener = new IPropertyChangeListener() { @Override public void propertyChange(PropertyChangeEvent event) { if (event.getProperty() .equals(CeylonPlugin.EDITOR_FONT_PREFERENCE)) { updateFontAndCaret(); } } }; private void updateFontAndCaret() { getSourceViewer() .getTextWidget() .setFont(CeylonPlugin.getEditorFont()); try { Method updateCaretMethod = AbstractTextEditor.class .getDeclaredMethod("updateCaret"); updateCaretMethod.setAccessible(true); updateCaretMethod.invoke(this); } catch (Exception e) { e.printStackTrace(); } } protected final SourceViewer createSourceViewer( Composite parent, IVerticalRuler ruler, int styles) { fAnnotationAccess = getAnnotationAccess(); fOverviewRuler = createOverviewRuler(getSharedColors()); SourceViewer viewer = new CeylonSourceViewer(this, parent, ruler, getOverviewRuler(), isOverviewRulerVisible(), styles); // ensure decoration support has been created and configured. getSourceViewerDecorationSupport(viewer); viewer.getTextWidget() .addCaretListener(new CaretListener() { @Override public void caretMoved(CaretEvent event) { Object adapter = getAdapter(IVerticalRulerInfo.class); if (adapter instanceof CompositeRuler) { // redraw initializer annotations according to cursor position CompositeRuler compositeRuler = (CompositeRuler) adapter; compositeRuler.update(); } } }); return viewer; } protected void configureSourceViewerDecorationSupport( SourceViewerDecorationSupport support) { installBracketMatcher(support); super.configureSourceViewerDecorationSupport(support); } //These preferences can't be stored in the CeylonPlugin preferences public final static String MATCHING_BRACKET = "matchingBrackets"; public final static String MATCHING_BRACKETS_COLOR = "matchingBracketsColor"; public final static String SELECTED_BRACKET = "highlightBracketAtCaretLocation"; public final static String ENCLOSING_BRACKETS = "enclosingBrackets"; private void installBracketMatcher( SourceViewerDecorationSupport support) { initializeBrackMatcherPreferences(); bracketMatcher = new CeylonCharacterPairMatcher(); support.setCharacterPairMatcher(bracketMatcher); support.setMatchingCharacterPainterPreferenceKeys( MATCHING_BRACKET, MATCHING_BRACKETS_COLOR, SELECTED_BRACKET, ENCLOSING_BRACKETS); } private final static String MATCHING_BRACKETS_PREF = PLUGIN_ID + ".theme.matchingBracketsColor"; public static void initializeBrackMatcherPreferences() { ITheme currentTheme = getCurrentTheme(); Color color = currentTheme.getColorRegistry() .get(MATCHING_BRACKETS_PREF); String colorString; if (color==null) { //because I can't trust the ThemeManager :-( colorString = "0,120,255"; } else { colorString = color.getRed() +"," + color.getGreen() + "," + color.getBlue(); } IPreferenceStore store = //can't be stored in the CeylonPlugin preferences! EditorsUI.getPreferenceStore(); store.setDefault(MATCHING_BRACKET, true); store.setDefault(ENCLOSING_BRACKETS, false); store.setDefault(SELECTED_BRACKET, false); store.setDefault(MATCHING_BRACKETS_COLOR, colorString); store.setDefault(EDITOR_FOLDING_ENABLED, true); } public ICharacterPairMatcher getBracketMatcher() { return bracketMatcher; } private void doSaveInternal(IProgressMonitor progressMonitor) { super.doSave(progressMonitor); } public void saveWithoutActions() { doSaveInternal(getProgressMonitor()); } @Override public void doSave(IProgressMonitor progressMonitor) { CeylonSourceViewer viewer = getCeylonSourceViewer(); IDocument doc = viewer.getDocument(); IPreferenceStore prefs = CeylonPlugin.getPreferences(); boolean normalizeWs = prefs.getBoolean(NORMALIZE_WS) && EditorsUI.getPreferenceStore() .getBoolean(EDITOR_SPACES_FOR_TABS); boolean normalizeNl = prefs.getBoolean(NORMALIZE_NL); boolean stripTrailingWs = prefs.getBoolean(STRIP_TRAILING_WS); boolean cleanImports = prefs.getBoolean(CLEAN_IMPORTS); boolean format = prefs.getBoolean(FORMAT); if (cleanImports) { try { importsJ2C().cleanImports( parseController, doc); } catch (Exception e) { e.printStackTrace(); } } if (format) { try { FormatAction.format(parseController, viewer.getDocument(), EditorUtil.getSelection(this), false, getSelectionProvider()); } catch (Exception e) { e.printStackTrace(); } } else if (normalizeWs || normalizeNl || stripTrailingWs) { normalize(viewer, doc, normalizeWs, normalizeNl, stripTrailingWs); } doSaveInternal(progressMonitor); } private static void normalize(CeylonSourceViewer viewer, IDocument doc, boolean normalizeWs, boolean normalizeNl, boolean stripTrailingWs) { DocumentRewriteSession rewriteSession = null; if (doc instanceof IDocumentExtension4) { rewriteSession = ((IDocumentExtension4) doc) .startRewriteSession(SEQUENTIAL); } Point range = viewer.getSelectedRange(); int modelOffset = range.x; int modelLength = range.y; try { String text = doc.get(); String normalized = normalize(text, doc, normalizeWs, normalizeNl, stripTrailingWs); if (!normalized.equals(text)) { StyledText widget = viewer.getTextWidget(); Point selection = widget.getSelectionRange(); StyledTextContent content = widget.getContent(); String textBeforeRange = content.getTextRange( 0, selection.x); int offset = normalize(textBeforeRange, doc, normalizeWs, normalizeNl, stripTrailingWs) .length(); String textInRange = content.getTextRange( selection.x, selection.y); int length = normalize(textInRange, doc, normalizeWs, normalizeNl, stripTrailingWs) .length(); modelOffset = viewer.widgetOffset2ModelOffset(offset); modelLength = viewer.widgetOffset2ModelOffset(offset+length) - modelOffset; new ReplaceEdit(0, text.length(), normalized) .apply(doc); } } catch (Exception e) { e.printStackTrace(); } finally { if (doc instanceof IDocumentExtension4) { ((IDocumentExtension4) doc) .stopRewriteSession(rewriteSession); } viewer.setSelectedRange(modelOffset, modelLength); } } private static String normalize(String text, IDocument doc, boolean normalizeWs, boolean normalizeNl, boolean stripTrailingWs) { if (stripTrailingWs) { text = TRAILING_WS.matcher(text).replaceAll(""); } if (normalizeWs) { text = text.replace("\t", utilJ2C().indents().getDefaultIndent()); } if (normalizeNl) { String delim = utilJ2C().indents().getDefaultLineDelimiter(doc); for (String s: doc.getLegalLineDelimiters()) { text = text.replace(s, delim); } } return text; } // 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); // } @Override protected void doSetInput(IEditorInput input) throws CoreException { input = EditorUtil.adjustEditorInput(input); if (input != null) { //the following crazy stuff seems to be needed in //order to get syntax highlighting in structured //compare viewer CeylonSourceViewer sourceViewer = getCeylonSourceViewer(); if (sourceViewer!=null) { // uninstall & unregister preference store listener getSourceViewerDecorationSupport(sourceViewer) .uninstall(); sourceViewer.unconfigure(); //setPreferenceStore(createCombinedPreferenceStore(input)); // install & register preference store listener sourceViewer.configure(getSourceViewerConfiguration()); getSourceViewerDecorationSupport(sourceViewer) .install(getPreferenceStore()); } } super.doSetInput(input); if (input != null) { //have to do this or we get a funny-looking caret setInsertMode(SMART_INSERT); } } /** * 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); } public String getSelectionText() { IRegion sel = getSelection(); IDocument document = getDocumentProvider() .getDocument(getEditorInput()); if (document==null) { return ""; } 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()); } /** * Returns the signed current selection. * The length will be negative if the resulting selection * is right-to-left (RtoL). * The selection offset is model based. */ public IRegion getSignedSelection() { ISourceViewer sourceViewer = getSourceViewer(); 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); } public boolean canPerformFind() { return true; } public CeylonParseController getParseController() { return parseController; } public String toString() { return "Ceylon Editor for " + getEditorInput().getName(); } boolean isFoldingEnabled() { return getPreferenceStore() .getBoolean(EDITOR_FOLDING_ENABLED); } public ITextSelection getSelectionFromThread() { final class GetSelection implements Runnable { ITextSelection selection; @Override public void run() { ISelectionProvider sp = getSelectionProvider(); selection = sp==null ? null : (ITextSelection) sp.getSelection(); } ITextSelection getSelection() { Display.getDefault().syncExec(this); return selection; } } return new GetSelection().getSelection(); } public Node getSelectedNode() { CeylonParseController cpc = getParseController(); if (cpc==null || cpc.getLastCompilationUnit()==null) { return null; } else { return findNode(cpc.getLastCompilationUnit(), cpc.getTokens(), EditorUtil.getSelection(this)); } } @Override public Object ceylonModelParsed(CeylonProject<IProject, IResource, IFolder, IFile> project) { IEditorInput input = getEditorInput(); if (input instanceof FileStoreEditorInput) { FileStoreEditorInput fsei = (FileStoreEditorInput) input; final IEditorInput newInput = EditorUtil.fixSourceArchiveInput(fsei); if (newInput != null) { IWorkbenchPartSite site = getSite(); if (site != null) { site.getPage() .getWorkbenchWindow() .getWorkbench() .getDisplay() .asyncExec(new Runnable() { @Override public void run() { setInput(newInput); } }); } } } return null; } @Override public Object ceylonProjectAdded( CeylonProject<IProject, IResource, IFolder, IFile> project) { return null; } @Override public Object ceylonProjectRemoved( CeylonProject<IProject, IResource, IFolder, IFile> project) { return null; } @Override public Object buildMessagesChanged( CeylonProject<IProject,IResource,IFolder,IFile> project, ceylon.language.Iterable<? extends CeylonProjectBuild<IProject,IResource,IFolder,IFile>.SourceFileMessage,? extends Object> frontendMessages, ceylon.language.Iterable<? extends CeylonProjectBuild<IProject,IResource,IFolder,IFile>.SourceFileMessage,? extends Object> backMessages, ceylon.language.Iterable<? extends CeylonProjectBuild<IProject,IResource,IFolder,IFile>.ProjectMessage,? extends Object> projectMessages) { return null; } @Override public Object modelFilesUpdated( Iterable<? extends FileVirtualFile<IProject, IResource, IFolder, IFile>, ? extends Object> arg0) { return null; } @Override public Object modelPhasedUnitsTypechecked( Iterable<? extends ProjectPhasedUnit<IProject, IResource, IFolder, IFile>, ? extends Object> arg0) { return null; } @Override public Object externalPhasedUnitsTypechecked( Iterable<? extends ExternalPhasedUnit, ? extends Object> externalPhasedUnits, boolean fullyTypechecked) { return null; } }