Java tutorial
/******************************************************************************* * Copyright (c) 2008 itemis AG (http://www.itemis.eu) and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * *******************************************************************************/ package org.eclipse.xtext.ui.editor; import java.text.BreakIterator; import java.text.CharacterIterator; import java.util.Iterator; import org.apache.log4j.Logger; import org.eclipse.core.commands.operations.IOperationApprover; import org.eclipse.core.commands.operations.IOperationHistory; import org.eclipse.core.commands.operations.IUndoContext; import org.eclipse.core.commands.operations.IUndoableOperation; import org.eclipse.core.resources.IMarker; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IAdaptable; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Status; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.IAction; import org.eclipse.jface.action.IMenuManager; import org.eclipse.jface.action.MenuManager; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.resource.ImageDescriptor; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentExtension3; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.TextUtilities; import org.eclipse.jface.text.link.LinkedModeModel; import org.eclipse.jface.text.link.LinkedPosition; import org.eclipse.jface.text.reconciler.IReconciler; import org.eclipse.jface.text.source.Annotation; import org.eclipse.jface.text.source.AnnotationPainter; import org.eclipse.jface.text.source.IAnnotationAccess; import org.eclipse.jface.text.source.IAnnotationAccessExtension; import org.eclipse.jface.text.source.IAnnotationModel; import org.eclipse.jface.text.source.IAnnotationModelExtension2; 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.SourceViewerConfiguration; import org.eclipse.jface.text.source.projection.ProjectionSupport; import org.eclipse.jface.text.source.projection.ProjectionViewer; import org.eclipse.jface.util.PropertyChangeEvent; import org.eclipse.jface.viewers.IPostSelectionProvider; import org.eclipse.jface.viewers.ISelectionChangedListener; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.ST; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.events.VerifyListener; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IEditorDescriptor; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IEditorRegistry; import org.eclipse.ui.IEditorSite; import org.eclipse.ui.IPropertyListener; import org.eclipse.ui.PartInitException; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.editors.text.TextEditor; import org.eclipse.ui.texteditor.DefaultMarkerAnnotationAccess; import org.eclipse.ui.texteditor.ITextEditor; import org.eclipse.ui.texteditor.ITextEditorActionConstants; import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds; import org.eclipse.ui.texteditor.IUpdate; import org.eclipse.ui.texteditor.MarkerAnnotation; import org.eclipse.ui.texteditor.SelectMarkerRulerAction; import org.eclipse.ui.texteditor.SourceViewerDecorationSupport; import org.eclipse.ui.texteditor.TextNavigationAction; import org.eclipse.ui.texteditor.TextOperationAction; import org.eclipse.ui.views.contentoutline.IContentOutlinePage; import org.eclipse.xtext.Constants; import org.eclipse.xtext.ui.IImageHelper; import org.eclipse.xtext.ui.XtextUIMessages; import org.eclipse.xtext.ui.editor.DirtyStateEditorSupport.IDirtyStateEditorSupportClient; import org.eclipse.xtext.ui.editor.DirtyStateEditorSupport.IDirtyStateEditorSupportClientExtension; import org.eclipse.xtext.ui.editor.actions.IActionContributor; import org.eclipse.xtext.ui.editor.bracketmatching.BracketMatchingPreferencesInitializer; import org.eclipse.xtext.ui.editor.folding.IFoldingStructureProvider; import org.eclipse.xtext.ui.editor.model.CommonWordIterator; import org.eclipse.xtext.ui.editor.model.DocumentCharacterIterator; import org.eclipse.xtext.ui.editor.model.ITokenTypeToPartitionTypeMapperExtension; import org.eclipse.xtext.ui.editor.model.IXtextDocument; import org.eclipse.xtext.ui.editor.model.XtextDocumentProvider; import org.eclipse.xtext.ui.editor.model.XtextDocumentUtil; import org.eclipse.xtext.ui.editor.preferences.IPreferenceStoreAccess; import org.eclipse.xtext.ui.editor.preferences.PreferenceConstants; import org.eclipse.xtext.ui.editor.reconciler.XtextReconciler; import org.eclipse.xtext.ui.editor.syntaxcoloring.IHighlightingHelper; import org.eclipse.xtext.ui.editor.syntaxcoloring.TextAttributeProvider; import org.eclipse.xtext.ui.editor.toggleComments.ToggleSLCommentAction; import org.eclipse.xtext.ui.internal.Activator; import com.google.common.collect.ObjectArrays; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.name.Named; /** * @author Dennis Huebner - Initial contribution and API * @author Peter Friese - Initial contribution and API * @author Sven Efftinge * @author Michael Clay * @author Dan Stefanescu - Fix for bug 278279 */ public class XtextEditor extends TextEditor implements IDirtyStateEditorSupportClient, IDirtyStateEditorSupportClientExtension { public static final String ERROR_ANNOTATION_TYPE = "org.eclipse.xtext.ui.editor.error"; public static final String WARNING_ANNOTATION_TYPE = "org.eclipse.xtext.ui.editor.warning"; /** * @since 2.3 */ public static final String INFO_ANNOTATION_TYPE = "org.eclipse.xtext.ui.editor.info"; /** * @since 2.2 */ public static final String KEY_BINDING_SCOPE = "org.eclipse.xtext.ui.editor.XtextEditor.KEY_BINDING_SCOPE"; /** * @since 2.2 */ public static final String DEFAULT_KEY_BINDING_SCOPE = "org.eclipse.xtext.ui.XtextEditorScope"; private static final Logger log = Logger.getLogger(XtextEditor.class); public static final String ID = "org.eclipse.xtext.baseEditor"; //$NON-NLS-1$ @Inject private IFoldingStructureProvider foldingStructureProvider; @Inject(optional = true) private AnnotationPainter.IDrawingStrategy projectionAnnotationDrawingStrategy; @Inject private CompoundXtextEditorCallback callback; @Inject private XtextSourceViewerConfiguration sourceViewerConfiguration; private IContentOutlinePage outlinePage; @Inject(optional = true) private Provider<IContentOutlinePage> outlinePageProvider; @Inject private Provider<XtextDocumentProvider> documentProvider; @Inject private XtextSourceViewer.Factory sourceViewerFactory; @Inject private IHighlightingHelper highlightingHelper; @Inject private IPreferenceStoreAccess preferenceStoreAccess; @Inject private TextAttributeProvider textAttributeProvider; /** * @since 2.4 */ @Inject private ITokenTypeToPartitionTypeMapperExtension tokenTypeToPartitionTypeMapperExtension; @Inject private IImageHelper imageHelper; /** * @since 2.7 */ @Inject private DirtyStateEditorSupport dirtyStateEditorSupport; private String keyBindingScope; private ISelectionChangedListener selectionChangedListener; private IPropertyListener dirtyListener = new IPropertyListener() { @Override public void propertyChanged(Object source, int propId) { if (propId == PROP_DIRTY && !isDirty()) { dirtyStateEditorSupport.markEditorClean(XtextEditor.this); } } }; private String languageName; public XtextEditor() { if (log.isDebugEnabled()) log.debug("Creating Xtext Editor. Instance: [" + this.toString() + "]"); } @Override public IXtextDocument getDocument() { return XtextDocumentUtil.get(getSourceViewer()); } @Inject public void setLanguageName(@Named(Constants.LANGUAGE_NAME) String name) { this.languageName = name; } public String getLanguageName() { return languageName; } /** * Note: Not injected directly into field as {@link #initializeKeyBindingScopes()} is called by constructor. * * @since 2.2 */ @Inject(optional = true) public void setKeyBindingScope(@Named(KEY_BINDING_SCOPE) String scope) { if (scope != null) { this.keyBindingScope = scope; initializeKeyBindingScopes(); } } @Override protected void doSetInput(IEditorInput input) throws CoreException { if (log.isDebugEnabled()) { log.debug("doSetInput:" + input); log.debug("Editor instance is [" + this.toString() + "]"); } removePropertyListener(dirtyListener); callback.beforeSetInput(this); removeDirtyStateSupport(); super.doSetInput(input); initializeDirtyStateSupport(); callback.afterSetInput(this); addPropertyListener(dirtyListener); } /** * @since 2.8 */ protected void removeDirtyStateSupport() { if (getSourceViewer() != null && getEditorInput() != null) dirtyStateEditorSupport.removeDirtyStateSupport(this); } /** * @since 2.8 */ protected void initializeDirtyStateSupport() { if (getSourceViewer() != null && getEditorInput() != null) dirtyStateEditorSupport.initializeDirtyStateSupport(this); } @Override public void init(IEditorSite site, IEditorInput input) throws PartInitException { if (log.isDebugEnabled()) log.debug("init:" + input); // do document provider setup setDocumentProvider(documentProvider.get()); // source viewer setup setSourceViewerConfiguration(sourceViewerConfiguration); sourceViewerConfiguration.setEditor(this); // Bug 464591 all editor presentation settings (font/color) handled here are in the InstanceScope only setPreferenceStore(preferenceStoreAccess.getPreferenceStore()); // NOTE: Outline CANNOT be initialized here, since we do not have access // to the source viewer yet (it will be created later). super.init(site, input); } @Override protected void setPreferenceStore(IPreferenceStore preferenceStore) { super.setPreferenceStore(preferenceStore); if (getSourceViewerConfiguration() instanceof XtextSourceViewerConfiguration) { XtextSourceViewerConfiguration xtextSourceViewerConfiguration = (XtextSourceViewerConfiguration) getSourceViewerConfiguration(); xtextSourceViewerConfiguration.setPreferenceStore(preferenceStore); } } public XtextSourceViewerConfiguration getXtextSourceViewerConfiguration() { return sourceViewerConfiguration; } @Override public void doSaveAs() { super.doSaveAs(); callback.afterSave(this); } @Override public void doSave(IProgressMonitor progressMonitor) { super.doSave(progressMonitor); callback.afterSave(this); } @Override public void doRevertToSaved() { super.doRevertToSaved(); callback.afterSave(this); } /** * Set key binding scope. Required for custom key bindings (e.g. F3). */ @Override protected void initializeKeyBindingScopes() { setKeyBindingScopes(new String[] { keyBindingScope != null ? keyBindingScope : DEFAULT_KEY_BINDING_SCOPE }); } public IResource getResource() { Object adapter = getEditorInput().getAdapter(IResource.class); if (adapter != null) { return (IResource) adapter; } return null; } @SuppressWarnings("rawtypes") @Override public Object getAdapter(Class adapter) { if (IContentOutlinePage.class.isAssignableFrom(adapter)) { return getContentOutlinePage(); } return super.getAdapter(adapter); } private IContentOutlinePage getContentOutlinePage() { // don't create outline page if the editor was already disposed if (outlinePage == null && getSourceViewer() != null) { outlinePage = createOutlinePage(); } return outlinePage; } private IContentOutlinePage createOutlinePage() { IContentOutlinePage page = null; if (outlinePageProvider != null) { // can be null, optional injection page = outlinePageProvider.get(); if (page != null) { if (page instanceof ISourceViewerAware) { ((ISourceViewerAware) page).setSourceViewer(getSourceViewer()); } if (page instanceof IXtextEditorAware) { ((IXtextEditorAware) page).setEditor(this); } } } return page; } /** * Informs the editor that its outline has been closed. */ public void outlinePageClosed() { if (outlinePage != null) { outlinePage = null; resetHighlightRange(); } } @Inject private IActionContributor.CompositeImpl actioncontributor; @Inject private ToggleSLCommentAction.Factory toggleSLCommentActionFactory; @Override protected void createActions() { super.createActions(); if (getSourceViewerConfiguration().getContentFormatter(getSourceViewer()) != null) { Action action = new TextOperationAction(XtextUIMessages.getResourceBundle(), "Format.", this, //$NON-NLS-1$ ISourceViewer.FORMAT); action.setActionDefinitionId(Activator.PLUGIN_ID + ".FormatAction"); setAction("Format", action); //$NON-NLS-1$ markAsStateDependentAction("Format", true); //$NON-NLS-1$ markAsSelectionDependentAction("Format", true); //$NON-NLS-1$ } ToggleSLCommentAction action = toggleSLCommentActionFactory.create(XtextUIMessages.getResourceBundle(), "ToggleComment.", this); //$NON-NLS-1$ action.setActionDefinitionId(Activator.PLUGIN_ID + ".ToggleCommentAction"); setAction("ToggleComment", action); //$NON-NLS-1$ markAsStateDependentAction("ToggleComment", true); //$NON-NLS-1$ markAsSelectionDependentAction("ToggleComment", true); configureToggleCommentAction(action); // Creates an build-in "click an ruler annotation, marks corresponding // text" - action SelectMarkerRulerAction markerAction = new XtextMarkerRulerAction(XtextUIMessages.getResourceBundle(), "XtextSelectAnnotationRulerAction.", this, getVerticalRuler()); //$NON-NLS-1$ setAction(ITextEditorActionConstants.RULER_CLICK, markerAction); actioncontributor.contributeActions(this); } protected void configureToggleCommentAction(ToggleSLCommentAction action) { ISourceViewer sourceViewer = getSourceViewer(); SourceViewerConfiguration configuration = getSourceViewerConfiguration(); action.configure(sourceViewer, configuration); } /** * @since 2.1 */ @Override protected IOperationApprover getUndoRedoOperationApprover(IUndoContext undoContext) { final IOperationApprover result = super.getUndoRedoOperationApprover(undoContext); /* * The undo approver is necessary for some strange cases, e.g. * 1) 2 editors for the same file * 2) modify one of the editors and hit save * 3) the other one will reflect the changes * 4) continue to type in the first editor * 5) use undo in the second to revert the contents */ return new IOperationApprover() { @Override public IStatus proceedRedoing(IUndoableOperation operation, IOperationHistory history, IAdaptable info) { IStatus status = result.proceedRedoing(operation, history, info); return validateEditorInputState(info, status); } @Override public IStatus proceedUndoing(IUndoableOperation operation, IOperationHistory history, IAdaptable info) { IStatus status = result.proceedUndoing(operation, history, info); return validateEditorInputState(info, status); } protected IStatus validateEditorInputState(IAdaptable info, IStatus status) { if (Status.OK_STATUS.equals(status) && info != null && info.getAdapter(ITextEditor.class) == XtextEditor.this) { if (!XtextEditor.this.validateEditorInputState()) { return Status.CANCEL_STATUS; } } return status; } }; } /** * @return true if content assist is available */ public boolean isContentAssistAvailable() { boolean result = getSourceViewer().getTextOperationTarget() .canDoOperation(ISourceViewer.CONTENTASSIST_PROPOSALS); return result; } @Override protected ISourceViewer createSourceViewer(Composite parent, IVerticalRuler ruler, int styles) { if (log.isDebugEnabled()) log.debug("Creating Xtext source viewer."); // overwrite superclass implementation to allow folding fAnnotationAccess = createAnnotationAccess(); fOverviewRuler = createOverviewRuler(getSharedColors()); ISourceViewer projectionViewer = sourceViewerFactory.createSourceViewer(parent, ruler, getOverviewRuler(), isOverviewRulerVisible(), styles); getSourceViewerDecorationSupport(projectionViewer); return projectionViewer; } @Inject private ICharacterPairMatcher characterPairMatcher; @Override protected void configureSourceViewerDecorationSupport(SourceViewerDecorationSupport support) { super.configureSourceViewerDecorationSupport(support); if (characterPairMatcher != null) { support.setCharacterPairMatcher(characterPairMatcher); support.setMatchingCharacterPainterPreferenceKeys(BracketMatchingPreferencesInitializer.IS_ACTIVE_KEY, BracketMatchingPreferencesInitializer.COLOR_KEY); } } private ProjectionSupport projectionSupport; @Override public void createPartControl(Composite parent) { super.createPartControl(parent); ProjectionViewer projectionViewer = (ProjectionViewer) getSourceViewer(); projectionSupport = installProjectionSupport(projectionViewer); installFoldingSupport(projectionViewer); installHighlightingHelper(); installSelectionChangedListener(); initializeDirtyStateSupport(); callback.afterCreatePartControl(this); forceReconcile(); } protected ProjectionSupport installProjectionSupport(ProjectionViewer projectionViewer) { ProjectionSupport projectionSupport = new ProjectionSupport(projectionViewer, getAnnotationAccess(), getSharedColors()); projectionSupport.addSummarizableAnnotationType(INFO_ANNOTATION_TYPE); projectionSupport.addSummarizableAnnotationType(WARNING_ANNOTATION_TYPE); projectionSupport.addSummarizableAnnotationType(ERROR_ANNOTATION_TYPE); projectionSupport.setAnnotationPainterDrawingStrategy(projectionAnnotationDrawingStrategy); projectionSupport.install(); return projectionSupport; } protected void installFoldingSupport(ProjectionViewer projectionViewer) { foldingStructureProvider.install(this, projectionViewer); projectionViewer.enableProjection(); } private void installSelectionChangedListener() { selectionChangedListener = new ISelectionChangedListener() { @Override public void selectionChanged(final SelectionChangedEvent event) { updateStatusLine(); } }; final ISelectionProvider selectionProvider = getSelectionProvider(); if (selectionProvider instanceof IPostSelectionProvider) { final IPostSelectionProvider postSelectionProvider = (IPostSelectionProvider) selectionProvider; postSelectionProvider.addPostSelectionChangedListener(selectionChangedListener); } else { getSelectionProvider().addSelectionChangedListener(selectionChangedListener); } } private void installHighlightingHelper() { if (highlightingHelper != null) highlightingHelper.install(this, (XtextSourceViewer) getSourceViewer()); } private void uninstallHighlightingHelper() { if (highlightingHelper != null) highlightingHelper.uninstall(); } @Override public void dispose() { dirtyStateEditorSupport.removeDirtyStateSupport(this); callback.beforeDispose(this); actioncontributor.editorDisposed(this); super.dispose(); if (projectionSupport != null) { projectionSupport.dispose(); } if (outlinePage != null) { outlinePage = null; } uninstallFoldingSupport(); foldingStructureProvider = null; uninstallHighlightingHelper(); uninstallSelectionChangedListener(); } protected void uninstallFoldingSupport() { if (foldingStructureProvider != null) { foldingStructureProvider.uninstall(); } } private void uninstallSelectionChangedListener() { ISelectionProvider selectionProvider = getSelectionProvider(); if (selectionProvider != null) { if (selectionProvider instanceof IPostSelectionProvider) { final IPostSelectionProvider postSelectionProvider = (IPostSelectionProvider) selectionProvider; postSelectionProvider.removePostSelectionChangedListener(selectionChangedListener); } else { selectionProvider.removeSelectionChangedListener(selectionChangedListener); } } } public ISourceViewer getInternalSourceViewer() { return getSourceViewer(); } @Override protected void handlePreferenceStoreChanged(PropertyChangeEvent event) { ISourceViewer sourceViewer = getSourceViewer(); if (sourceViewer == null || sourceViewer.getTextWidget() == null) return; super.handlePreferenceStoreChanged(event); boolean tokenStyleChanged = event.getProperty().contains(".syntaxColorer.tokenStyles"); if (tokenStyleChanged) { textAttributeProvider.propertyChange(event); initializeViewerColors(sourceViewer); sourceViewer.invalidateTextPresentation(); } } @Override protected void initializeViewerColors(ISourceViewer viewer) { if (viewer == null || viewer.getTextWidget() == null) return; super.initializeViewerColors(viewer); } @Override protected String[] collectContextMenuPreferencePages() { String[] commonPages = super.collectContextMenuPreferencePages(); String[] langSpecificPages = collectLanguageContextMenuPreferencePages(); return ObjectArrays.concat(langSpecificPages, commonPages, String.class); } private String[] collectLanguageContextMenuPreferencePages() { String[] additionalPages = new String[5]; // NOTE: preference page at index 0 will be opened, see // PreferencesUtil.createPreferenceDialogOn additionalPages[0] = getLanguageName(); additionalPages[1] = getLanguageName() + ".editor"; //$NON-NLS-1$ additionalPages[2] = getLanguageName() + ".templates"; //$NON-NLS-1$ additionalPages[3] = getLanguageName() + ".coloring"; //$NON-NLS-1$ additionalPages[4] = getLanguageName() + ".compiler.preferencePage"; //$NON-NLS-1$ return additionalPages; } @Override protected IAnnotationAccess createAnnotationAccess() { return new DefaultMarkerAnnotationAccess() { @Override public int getLayer(Annotation annotation) { if (annotation.isMarkedDeleted()) { return IAnnotationAccessExtension.DEFAULT_LAYER; } return super.getLayer(annotation); } }; } protected void updateStatusLine() { final ITextSelection selection = (ITextSelection) getSelectionProvider().getSelection(); final Annotation annotation = getAnnotation(selection.getOffset(), selection.getLength()); String message = null; if (annotation != null) { updateMarkerViews(annotation); if (isProblemMarkerAnnotation(annotation)) { message = annotation.getText(); } } setStatusLineMessage(message); } @Override public boolean validateEditorInputState() { return dirtyStateEditorSupport.isEditingPossible(this) && callback.onValidateEditorInputState(this) && super.validateEditorInputState(); } public void updatedTitleImage(Image image) { setTitleImage(image); } @Override public Image getDefaultImage() { IEditorRegistry editorRegistry = PlatformUI.getWorkbench().getEditorRegistry(); IEditorDescriptor editorDesc = editorRegistry.findEditor(getSite().getId()); ImageDescriptor imageDesc = editorDesc != null ? editorDesc.getImageDescriptor() : null; return imageDesc != null ? imageHelper.getImage(imageDesc) : super.getDefaultImage(); } /* * @see org.eclipse.ui.texteditor.AbstractTextEditor#rulerContextMenuAboutToShow(org.eclipse.jface.action.IMenuManager) */ @Override protected void rulerContextMenuAboutToShow(IMenuManager menu) { super.rulerContextMenuAboutToShow(menu); IMenuManager foldingMenu = new MenuManager(XtextUIMessages.Editor_FoldingMenu_name, "projection"); //$NON-NLS-1$ menu.appendToGroup(ITextEditorActionConstants.GROUP_RULERS, foldingMenu); IAction action = getAction("FoldingToggle"); //$NON-NLS-1$ foldingMenu.add(action); action = getAction("FoldingExpandAll"); //$NON-NLS-1$ foldingMenu.add(action); action = getAction("FoldingCollapseAll"); //$NON-NLS-1$ foldingMenu.add(action); action = getAction("FoldingRestore"); //$NON-NLS-1$ foldingMenu.add(action); } /** * Resets the foldings structure according to the folding preferences. */ public void resetProjection() { if (foldingStructureProvider != null) { foldingStructureProvider.initialize(); } } @SuppressWarnings("rawtypes") private Annotation getAnnotation(final int offset, final int length) { final IAnnotationModel model = getDocumentProvider().getAnnotationModel(getEditorInput()); if (model == null || length < 0) return null; Iterator iterator; if (model instanceof IAnnotationModelExtension2) { iterator = ((IAnnotationModelExtension2) model).getAnnotationIterator(offset, length, true, true); } else { iterator = model.getAnnotationIterator(); } while (iterator.hasNext()) { final Annotation a = (Annotation) iterator.next(); final Position p = model.getPosition(a); if (p != null && p.overlapsWith(offset, length)) return a; } return null; } private boolean isProblemMarkerAnnotation(final Annotation annotation) { if (!(annotation instanceof MarkerAnnotation)) return false; try { return (((MarkerAnnotation) annotation).getMarker().isSubtypeOf(IMarker.PROBLEM)); } catch (final CoreException e) { return false; } } /** * Externally set the editor callback, e.g. to disable dirty state support for a specific instance. */ public void setXtextEditorCallback(CompoundXtextEditorCallback callback) { this.callback = callback; } /** * @since 2.7 */ public CompoundXtextEditorCallback getXtextEditorCallback() { return callback; } @Override protected boolean isNavigationTarget(Annotation annotation) { boolean result = super.isNavigationTarget(annotation); if (result) { if (annotation.isMarkedDeleted()) { return false; } } return result; } /** * Copied from {@link org.eclipse.ui.texteditor.AbstractTextEditor#selectAndReveal(int, int)} and removed selection * functionality. */ public void reveal(int offset, int length) { if (getSourceViewer() == null) return; StyledText widget = getSourceViewer().getTextWidget(); widget.setRedraw(false); { adjustHighlightRange(offset, length); getSourceViewer().revealRange(offset, length); markInNavigationHistory(); } widget.setRedraw(true); } protected CommonWordIterator createWordIterator() { return new CommonWordIterator(true); } protected DeleteNextSubWordAction createDeleteNextSubWordAction() { return new DeleteNextSubWordAction(); } protected DeletePreviousSubWordAction createDeletePreviousSubWordAction() { return new DeletePreviousSubWordAction(); } protected SelectNextSubWordAction createSelectNextSubWordAction() { return new SelectNextSubWordAction(); } protected SelectPreviousSubWordAction createSelectPreviousSubWordAction() { return new SelectPreviousSubWordAction(); } protected NavigateNextSubWordAction createNavigateNextSubWordAction() { return new NavigateNextSubWordAction(); } protected NavigatePreviousSubWordAction createNavigatePreviousSubWordAction() { return new NavigatePreviousSubWordAction(); } protected SmartLineStartAction createSmartLineStartAction(final StyledText textWidget, boolean doSelect) { return new SmartLineStartAction(textWidget, doSelect); } // CODE BELOW WAS INITIALLY COPIED FROM JavaEditor @Override protected void createNavigationActions() { super.createNavigationActions(); final StyledText textWidget = getSourceViewer().getTextWidget(); IAction action = createSmartLineStartAction(textWidget, false); action.setActionDefinitionId(ITextEditorActionDefinitionIds.LINE_START); setAction(ITextEditorActionDefinitionIds.LINE_START, action); action = createSmartLineStartAction(textWidget, true); action.setActionDefinitionId(ITextEditorActionDefinitionIds.SELECT_LINE_START); setAction(ITextEditorActionDefinitionIds.SELECT_LINE_START, action); action = createNavigatePreviousSubWordAction(); action.setActionDefinitionId(ITextEditorActionDefinitionIds.WORD_PREVIOUS); setAction(ITextEditorActionDefinitionIds.WORD_PREVIOUS, action); textWidget.setKeyBinding(SWT.CTRL | SWT.ARROW_LEFT, SWT.NULL); action = createNavigateNextSubWordAction(); action.setActionDefinitionId(ITextEditorActionDefinitionIds.WORD_NEXT); setAction(ITextEditorActionDefinitionIds.WORD_NEXT, action); textWidget.setKeyBinding(SWT.CTRL | SWT.ARROW_RIGHT, SWT.NULL); action = createSelectPreviousSubWordAction(); action.setActionDefinitionId(ITextEditorActionDefinitionIds.SELECT_WORD_PREVIOUS); setAction(ITextEditorActionDefinitionIds.SELECT_WORD_PREVIOUS, action); textWidget.setKeyBinding(SWT.CTRL | SWT.SHIFT | SWT.ARROW_LEFT, SWT.NULL); action = createSelectNextSubWordAction(); action.setActionDefinitionId(ITextEditorActionDefinitionIds.SELECT_WORD_NEXT); setAction(ITextEditorActionDefinitionIds.SELECT_WORD_NEXT, action); textWidget.setKeyBinding(SWT.CTRL | SWT.SHIFT | SWT.ARROW_RIGHT, SWT.NULL); action = createDeletePreviousSubWordAction(); action.setActionDefinitionId(ITextEditorActionDefinitionIds.DELETE_PREVIOUS_WORD); setAction(ITextEditorActionDefinitionIds.DELETE_PREVIOUS_WORD, action); textWidget.setKeyBinding(SWT.CTRL | SWT.BS, SWT.NULL); markAsStateDependentAction(ITextEditorActionDefinitionIds.DELETE_PREVIOUS_WORD, true); action = createDeleteNextSubWordAction(); action.setActionDefinitionId(ITextEditorActionDefinitionIds.DELETE_NEXT_WORD); setAction(ITextEditorActionDefinitionIds.DELETE_NEXT_WORD, action); textWidget.setKeyBinding(SWT.CTRL | SWT.DEL, SWT.NULL); markAsStateDependentAction(ITextEditorActionDefinitionIds.DELETE_NEXT_WORD, true); } /** * Text navigation action to navigate to the next sub-word. */ protected abstract class NextSubWordAction extends TextNavigationAction { protected CommonWordIterator fIterator = createWordIterator(); /** * 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); } /* * @see org.eclipse.jface.action.IAction#run() */ @Override public void run() { // Check whether we are in a java code partition and the preference is enabled final IPreferenceStore store = getPreferenceStore(); if (!store.getBoolean(PreferenceConstants.EDITOR_SUB_WORD_NAVIGATION)) { super.run(); return; } 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 (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) { 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); } /** * 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); } /* * @see org.eclipse.xtext.ui.editor.XtextEditor.NextSubWordAction#setCaretPosition(int) */ @Override protected void setCaretPosition(final int position) { getTextWidget().setCaretOffset(modelOffset2WidgetOffset(getSourceViewer(), position)); } } /** * 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); } /* * @see org.eclipse.xtext.ui.editor.XtextEditor.NextSubWordAction#setCaretPosition(int) */ @Override protected void setCaretPosition(final int position) { if (!validateEditorInputState()) return; final ISourceViewer viewer = getSourceViewer(); StyledText text = viewer.getTextWidget(); Point widgetSelection = text.getSelection(); if (isBlockSelectionModeEnabled() && widgetSelection.y != widgetSelection.x) { final int caret = text.getCaretOffset(); final 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_NEXT); } else { 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, ""); //$NON-NLS-1$ } catch (BadLocationException exception) { // Should not happen } } } /* * @see org.eclipse.ui.texteditor.IUpdate#update() */ @Override public void update() { setEnabled(isEditorInputModifiable()); } } /** * Text operation action to select the next sub-word. */ protected class SelectNextSubWordAction extends NextSubWordAction { /** * Creates a new select next sub-word action. */ public SelectNextSubWordAction() { super(ST.SELECT_WORD_NEXT); } /* * @see org.eclipse.xtext.ui.editor.XtextEditor.NextSubWordAction#setCaretPosition(int) */ @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); } } } /** * Text navigation action to navigate to the previous sub-word. */ protected abstract class PreviousSubWordAction extends TextNavigationAction { protected CommonWordIterator fIterator = createWordIterator(); /** * 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); } /* * @see org.eclipse.jface.action.IAction#run() */ @Override public void run() { // Check whether we are in a java code partition and the preference is enabled final IPreferenceStore store = getPreferenceStore(); if (!store.getBoolean(PreferenceConstants.EDITOR_SUB_WORD_NAVIGATION)) { super.run(); return; } 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 (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) { 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); } /** * 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); } /* * @see org.eclipse.xtext.ui.editor.XtextEditor.PreviousSubWordAction#setCaretPosition(int) */ @Override protected void setCaretPosition(final int position) { getTextWidget().setCaretOffset(modelOffset2WidgetOffset(getSourceViewer(), position)); } } /** * 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); } /* * @see org.eclipse.xtext.ui.editor.XtextEditor.PreviousSubWordAction#setCaretPosition(int) */ @Override protected void setCaretPosition(int position) { if (!validateEditorInputState()) return; final int length; final ISourceViewer viewer = getSourceViewer(); StyledText text = viewer.getTextWidget(); Point widgetSelection = text.getSelection(); if (isBlockSelectionModeEnabled() && widgetSelection.y != widgetSelection.x) { final int caret = text.getCaretOffset(); final 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, ""); //$NON-NLS-1$ } catch (BadLocationException exception) { // Should not happen } } } /* * @see org.eclipse.ui.texteditor.IUpdate#update() */ @Override public void update() { setEnabled(isEditorInputModifiable()); } } /** * Text operation action to select the previous sub-word. */ protected class SelectPreviousSubWordAction extends PreviousSubWordAction { /** * Creates a new select previous sub-word action. */ public SelectPreviousSubWordAction() { super(ST.SELECT_WORD_PREVIOUS); } /* * @see org.eclipse.xtext.ui.editor.XtextEditor.PreviousSubWordAction#setCaretPosition(int) */ @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); } } } /** * This action implements smart home. * * Instead of going to the start of a line it does the following: * * - if smart home/end is enabled and the caret is after the line's first non-whitespace then the caret is moved * directly before it, taking comments into account. - if the caret is before the line's first non-whitespace the * caret is moved to the beginning of the line - if the caret is at the beginning of the line see first case. */ protected class SmartLineStartAction extends LineStartAction { /** * Creates a new smart line start action * * @param textWidget * the styled text widget * @param doSelect * a boolean flag which tells if the text up to the beginning of the line should be selected */ public SmartLineStartAction(final StyledText textWidget, final boolean doSelect) { super(textWidget, doSelect); } /* * @see org.eclipse.ui.texteditor.AbstractTextEditor.LineStartAction#getLineStartPosition(java.lang.String, int, java.lang.String) */ @Override protected int getLineStartPosition(final IDocument document, final String line, final int length, final int offset) { String type = IDocument.DEFAULT_CONTENT_TYPE; try { type = TextUtilities.getContentType(document, IDocumentExtension3.DEFAULT_PARTITIONING, offset, false); } catch (BadLocationException exception) { // Should not happen } int lineStartPosition = super.getLineStartPosition(document, line, length, offset); if (tokenTypeToPartitionTypeMapperExtension.isMultiLineComment(type) || tokenTypeToPartitionTypeMapperExtension.isSingleLineComment(type)) { try { IRegion lineInformation = document.getLineInformationOfOffset(offset); int offsetInLine = offset - lineInformation.getOffset(); return getCommentLineStartPosition(line, length, offsetInLine, lineStartPosition); } catch (BadLocationException e) { // Should not happen } } if (type.equals(IDocument.DEFAULT_CONTENT_TYPE)) { if (isStartOfSingleLineComment(line, length, lineStartPosition) && !isStartOfMultiLineComment(line, length, lineStartPosition)) { return getTextStartPosition(line, length, lineStartPosition + 1); } } return lineStartPosition; } private int getCommentLineStartPosition(final String line, final int length, int offsetInLine, int lineStartPosition) { if (isMiddleOfMultiLineComment(line, length, lineStartPosition)) { return getTextStartPosition(line, length, lineStartPosition); } if (isStartOfMultiLineComment(line, length, lineStartPosition)) { int textStartPosition = getTextStartPosition(line, length, lineStartPosition + 2); if (offsetInLine <= textStartPosition) { return lineStartPosition; } return lineStartPosition < textStartPosition ? textStartPosition : lineStartPosition; } if (isStartOfSingleLineComment(line, length, lineStartPosition)) { return getTextStartPosition(line, length, lineStartPosition + 1); } return lineStartPosition; } private boolean isStartOfSingleLineComment(final String line, final int length, int index) { return index < length - 1 && line.charAt(index) == '/' && (line.charAt(index + 1) == '/' || line.charAt(index + 1) == '*'); } private boolean isStartOfMultiLineComment(final String line, final int length, int index) { return index < length - 2 && line.charAt(index) == '/' && (line.charAt(index + 1) == '*' && line.charAt(index + 2) == '*'); } private boolean isMiddleOfMultiLineComment(final String line, final int length, int index) { return index < length - 1 && line.charAt(index) == '*' && line.charAt(index + 1) != '/'; } private int getTextStartPosition(final String line, final int length, int index) { int textStartPosition = index; do { ++textStartPosition; } while (textStartPosition < length && Character.isWhitespace(line.charAt(textStartPosition))); return textStartPosition; } } /** * @since 2.7 */ @Override public Shell getShell() { return getSite().getShell(); } /** * @since 2.7 */ @Override public void addVerifyListener(VerifyListener listener) { StyledText textWidget = getSourceViewer().getTextWidget(); if (textWidget != null) textWidget.addVerifyListener(listener); } /** * @since 2.7 */ @Override public void removeVerifyListener(VerifyListener listener) { StyledText textWidget = getSourceViewer().getTextWidget(); if (textWidget != null) textWidget.removeVerifyListener(listener); } /** * @since 2.7 */ @Override public void forceReconcile() { IAdaptable iAdaptable = (IAdaptable) getInternalSourceViewer(); if (iAdaptable == null) { return; } Object reconciler = iAdaptable.getAdapter(IReconciler.class); if (reconciler instanceof XtextReconciler) ((XtextReconciler) reconciler).forceReconcile(); } /** * @since 2.7 */ public DirtyStateEditorSupport getDirtyStateEditorSupport() { return dirtyStateEditorSupport; } @Override protected void editorContextMenuAboutToShow(IMenuManager menu) { super.editorContextMenuAboutToShow(menu); menu.remove(ITextEditorActionConstants.SHIFT_RIGHT); menu.remove(ITextEditorActionConstants.SHIFT_LEFT); } }