package org.rubypeople.rdt.internal.ui.rubyeditor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Iterator; import java.util.ResourceBundle; import java.util.Stack; import org.eclipse.core.resources.IMarker; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExtension; import org.eclipse.core.runtime.IExtensionPoint; import org.eclipse.core.runtime.IExtensionRegistry; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.ListenerList; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Preferences; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.jobs.Job; import org.eclipse.jface.action.Action; import org.eclipse.jface.action.GroupMarker; 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.text.AbstractInformationControlManager; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.BadPositionCategoryException; import org.eclipse.jface.text.DefaultInformationControl; import org.eclipse.jface.text.DocumentCommand; import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IDocumentExtension; import org.eclipse.jface.text.IDocumentListener; import org.eclipse.jface.text.IInformationControl; import org.eclipse.jface.text.IInformationControlCreator; import org.eclipse.jface.text.ILineTracker; import org.eclipse.jface.text.IPositionUpdater; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextHover; import org.eclipse.jface.text.ITextOperationTarget; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.ITextViewerExtension; import org.eclipse.jface.text.ITextViewerExtension2; import org.eclipse.jface.text.ITextViewerExtension4; import org.eclipse.jface.text.ITextViewerExtension5; import org.eclipse.jface.text.ITypedRegion; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.TextUtilities; import org.eclipse.jface.text.information.IInformationProvider; import org.eclipse.jface.text.information.IInformationProviderExtension; import org.eclipse.jface.text.information.IInformationProviderExtension2; import org.eclipse.jface.text.information.InformationPresenter; import org.eclipse.jface.text.link.ILinkedModeListener; import org.eclipse.jface.text.link.LinkedModeModel; import org.eclipse.jface.text.link.LinkedModeUI; import org.eclipse.jface.text.link.LinkedPosition; import org.eclipse.jface.text.link.LinkedPositionGroup; import org.eclipse.jface.text.link.LinkedModeUI.ExitFlags; import org.eclipse.jface.text.link.LinkedModeUI.IExitPolicy; import org.eclipse.jface.text.source.Annotation; import org.eclipse.jface.text.source.IAnnotationHover; import org.eclipse.jface.text.source.IAnnotationHoverExtension; import org.eclipse.jface.text.source.IAnnotationModel; import org.eclipse.jface.text.source.ICharacterPairMatcher; import org.eclipse.jface.text.source.ILineRange; import org.eclipse.jface.text.source.IOverviewRuler; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.jface.text.source.ISourceViewerExtension3; import org.eclipse.jface.text.source.IVerticalRuler; import org.eclipse.jface.text.source.IVerticalRulerInfo; 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.ISelection; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.search.ui.IContextMenuConstants; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.custom.VerifyKeyListener; import org.eclipse.swt.events.VerifyEvent; import org.eclipse.swt.graphics.Point; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Shell; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IPageLayout; import org.eclipse.ui.IPartListener2; import org.eclipse.ui.IViewPart; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.IWorkbenchPartReference; import org.eclipse.ui.IWorkbenchPartSite; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.SelectionEnabler; import org.eclipse.ui.actions.ActionContext; import org.eclipse.ui.actions.ActionGroup; import org.eclipse.ui.editors.text.EditorsUI; import org.eclipse.ui.editors.text.IEncodingSupport; import org.eclipse.ui.help.WorkbenchHelp; import org.eclipse.ui.part.IShowInSource; import org.eclipse.ui.part.IShowInTargetList; import org.eclipse.ui.part.ShowInContext; 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.MarkerAnnotation; import org.eclipse.ui.texteditor.ResourceAction; import org.eclipse.ui.texteditor.TextEditorAction; import org.eclipse.ui.texteditor.TextOperationAction; import org.eclipse.ui.texteditor.link.EditorLinkedModeUI; import org.jruby.ast.RootNode; import org.rubypeople.rdt.core.IRubyElement; import org.rubypeople.rdt.core.IRubyProject; import org.rubypeople.rdt.core.IRubyScript; import org.rubypeople.rdt.core.RubyCore; import org.rubypeople.rdt.core.RubyModelException; import org.rubypeople.rdt.core.formatter.DefaultCodeFormatterConstants; import org.rubypeople.rdt.internal.corext.util.CodeFormatterUtil; import org.rubypeople.rdt.internal.corext.util.RubyModelUtil; import org.rubypeople.rdt.internal.ui.IRubyHelpContextIds; import org.rubypeople.rdt.internal.ui.RubyPlugin; import org.rubypeople.rdt.internal.ui.actions.CompositeActionGroup; import org.rubypeople.rdt.internal.ui.actions.FoldingActionGroup; import org.rubypeople.rdt.internal.ui.actions.SelectionConverter; import org.rubypeople.rdt.internal.ui.text.HTMLTextPresenter; import org.rubypeople.rdt.internal.ui.text.IRubyPartitions; import org.rubypeople.rdt.internal.ui.text.RubyHeuristicScanner; import org.rubypeople.rdt.internal.ui.text.Symbols; import org.rubypeople.rdt.internal.ui.text.ruby.IRubyReconcilingListener; import org.rubypeople.rdt.internal.ui.text.ruby.hover.SourceViewerInformationControl; import org.rubypeople.rdt.ui.IWorkingCopyManager; import org.rubypeople.rdt.ui.PreferenceConstants; import org.rubypeople.rdt.ui.RubyUI; import org.rubypeople.rdt.ui.actions.FormatAction; import org.rubypeople.rdt.ui.actions.IRubyEditorActionDefinitionIds; import org.rubypeople.rdt.ui.actions.OpenEditorActionGroup; import org.rubypeople.rdt.ui.actions.OpenViewActionGroup; import org.rubypeople.rdt.ui.actions.RubyActionGroup; import org.rubypeople.rdt.ui.actions.RubySearchActionGroup; import org.rubypeople.rdt.ui.actions.ShowInRubyExplorerViewAction; import org.rubypeople.rdt.ui.actions.SurroundWithBeginRescueAction; import org.rubypeople.rdt.ui.text.folding.IRubyFoldingStructureProvider; import org.rubypeople.rdt.ui.text.folding.IRubyFoldingStructureProviderExtension; public class RubyEditor extends RubyAbstractEditor implements IRubyReconcilingListener { private ProjectionSupport fProjectionSupport; /** The editor's tab converter */ private TabConverter fTabConverter; /** Preference key for automatically closing strings */ private final static String CLOSE_STRINGS = PreferenceConstants.EDITOR_CLOSE_STRINGS; /** Preference key for automatically closing brackets and parenthesis */ private final static String CLOSE_BRACKETS = PreferenceConstants.EDITOR_CLOSE_BRACKETS; /** Preference key for automatically closing braces */ private final static String CLOSE_BRACES = PreferenceConstants.EDITOR_CLOSE_BRACES; /** Preference key for code formatter tab size */ private final static String CODE_FORMATTER_TAB_SIZE = DefaultCodeFormatterConstants.FORMATTER_TAB_SIZE; /** Preference key for inserting spaces rather than tabs */ private final static String SPACES_FOR_TABS = DefaultCodeFormatterConstants.FORMATTER_TAB_CHAR; /** * Mutex for the reconciler. See https://bugs.eclipse.org/bugs/show_bug.cgi?id=63898 for a description of the * problem. * <p> * TODO remove once the underlying problem is solved. * </p> */ private final Object fReconcilerLock = new Object(); /** * This editor's projection model updater * * @since 3.0 */ private IRubyFoldingStructureProvider fProjectionModelUpdater; /** * The override and implements indicator manager for this editor. * * @since 3.0 */ protected OverrideIndicatorManager fOverrideIndicatorManager; /** * Indicates whether this editor is about to update any annotation views. * * @since 3.0 */ private boolean fIsUpdatingAnnotationViews = false; /** * The marker that served as last target for a goto marker request. * * @since 3.0 */ private IMarker fLastMarkerTarget = null; /** * The folding runner. * * @since 0.9.0 */ private ToggleFoldingRunner fFoldingRunner; /** * The action group for folding. * * @since 0.9.0 */ private FoldingActionGroup fFoldingGroup; /** * Reconciling listeners. * * @since 3.0 */ private ListenerList fReconcilingListeners = new ListenerList(ListenerList.IDENTITY); private BracketInserter fBracketInserter = new BracketInserter(); private CompositeActionGroup fActionGroups; private CompositeActionGroup fContextMenuGroup; private RubyActionGroup fGenerateActionGroup; private InformationPresenter fInformationPresenter; private StringSubstitutionConverter fStringConverter; public RubyEditor() { super(); setDocumentProvider(RubyPlugin.getDefault().getRubyDocumentProvider()); this.setRulerContextMenuId(RubyUI.ID_RULER_CONTEXT_MENU); //$NON-NLS-1$ this.setEditorContextMenuId(RubyUI.ID_EDITOR_CONTEXT_MENU); //$NON-NLS-1$ setKeyBindingScopes(new String[] { "org.rubypeople.rdt.ui.rubyEditorScope" }); //$NON-NLS-1$ setOutlinerContextMenuId("#RubyScriptOutlinerContext"); //$NON-NLS-1$ } /** * Returns the standard action group of this editor. * * @return returns this editor's standard action group */ protected ActionGroup getActionGroup() { return fActionGroups; } protected void createActions() { super.createActions(); ActionGroup oeg, ovg, rsg; fActionGroups = new CompositeActionGroup(new ActionGroup[] { oeg = new OpenEditorActionGroup(this), ovg = new OpenViewActionGroup(this), rsg = new RubySearchActionGroup(this) }); fGenerateActionGroup = new RubyActionGroup(this, ITextEditorActionConstants.GROUP_EDIT); fContextMenuGroup = new CompositeActionGroup(new ActionGroup[] { oeg, ovg, rsg, fGenerateActionGroup }); fFoldingGroup = new FoldingActionGroup(this, getViewer()); ISelectionProvider provider = getSite().getSelectionProvider(); ISelection selection = provider.getSelection(); ResourceAction resAction = new TextOperationAction(RubyEditorMessages.getBundleForConstructedKeys(), "ShowRDoc.", this, ISourceViewer.INFORMATION, true); //$NON-NLS-1$ resAction = new InformationDispatchAction(RubyEditorMessages.getBundleForConstructedKeys(), "ShowRDoc.", (TextOperationAction) resAction); //$NON-NLS-1$ resAction.setActionDefinitionId(IRubyEditorActionDefinitionIds.SHOW_RDOC); setAction("ShowRDoc", resAction); //$NON-NLS-1$ PlatformUI.getWorkbench().getHelpSystem().setHelp(resAction, IRubyHelpContextIds.SHOW_JAVADOC_ACTION); SurroundWithBeginRescueAction beginRescueAction = new SurroundWithBeginRescueAction(this); beginRescueAction.setActionDefinitionId(IRubyEditorActionDefinitionIds.SURROUND_WITH_BEGIN_RESCUE); beginRescueAction.update(selection); provider.addSelectionChangedListener(beginRescueAction); setAction(SurroundWithBeginRescueAction.SURROUND_WTH_BEGIN_RESCUE, beginRescueAction); Action action = new ContentAssistAction(RubyEditorMessages.getBundleForConstructedKeys(), "ContentAssistProposal.", this); action.setActionDefinitionId(IRubyEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS); setAction("ContentAssistProposal", action); action = new TextOperationAction(RubyEditorMessages.getBundleForConstructedKeys(), "Comment.", this, ITextOperationTarget.PREFIX); action.setActionDefinitionId(IRubyEditorActionDefinitionIds.COMMENT); setAction("Comment", action); action = new TextOperationAction(RubyEditorMessages.getBundleForConstructedKeys(), "Uncomment.", this, ITextOperationTarget.STRIP_PREFIX); action.setActionDefinitionId(IRubyEditorActionDefinitionIds.UNCOMMENT); setAction("Uncomment", action); action = new ToggleCommentAction(RubyEditorMessages.getBundleForConstructedKeys(), "ToggleComment.", this); //$NON-NLS-1$ action.setActionDefinitionId(IRubyEditorActionDefinitionIds.TOGGLE_COMMENT); setAction("ToggleComment", action); //$NON-NLS-1$ markAsStateDependentAction("ToggleComment", true); //$NON-NLS-1$ WorkbenchHelp.setHelp(action, IRubyHelpContextIds.TOGGLE_COMMENT_ACTION); configureToggleCommentAction(); // add annotation actions for roll-over expand hover action = new RubySelectMarkerRulerAction2(RubyEditorMessages.getBundleForConstructedKeys(), "Editor.RulerAnnotationSelection.", this); //$NON-NLS-1$ setAction("AnnotationAction", action); //$NON-NLS-1$ action = new ShowInRubyExplorerViewAction(this); action.setActionDefinitionId(IRubyEditorActionDefinitionIds.SHOW_IN_RUBY_RESOURCES_VIEW); setAction("ShowInPackageView", action); //$NON-NLS-1$ action = new GotoMatchingBracketAction(this); action.setActionDefinitionId(IRubyEditorActionDefinitionIds.GOTO_MATCHING_BRACKET); setAction(GotoMatchingBracketAction.GOTO_MATCHING_BRACKET, action); action = new TextOperationAction(RubyEditorMessages.getBundleForConstructedKeys(), "ShowOutline.", this, RubySourceViewer.SHOW_OUTLINE, true); //$NON-NLS-1$ action.setActionDefinitionId(IRubyEditorActionDefinitionIds.SHOW_OUTLINE); setAction(IRubyEditorActionDefinitionIds.SHOW_OUTLINE, action); PlatformUI.getWorkbench().getHelpSystem().setHelp(action, IRubyHelpContextIds.SHOW_OUTLINE_ACTION); action = new TextOperationAction(RubyEditorMessages.getBundleForConstructedKeys(), "OpenStructure.", this, RubySourceViewer.OPEN_STRUCTURE, true); //$NON-NLS-1$ action.setActionDefinitionId(IRubyEditorActionDefinitionIds.OPEN_STRUCTURE); setAction(IRubyEditorActionDefinitionIds.OPEN_STRUCTURE, action); PlatformUI.getWorkbench().getHelpSystem().setHelp(action, IRubyHelpContextIds.OPEN_STRUCTURE_ACTION); action = new TextOperationAction(RubyEditorMessages.getBundleForConstructedKeys(), "OpenHierarchy.", this, RubySourceViewer.SHOW_HIERARCHY, true); //$NON-NLS-1$ action.setActionDefinitionId(IRubyEditorActionDefinitionIds.OPEN_HIERARCHY); setAction(IRubyEditorActionDefinitionIds.OPEN_HIERARCHY, action); PlatformUI.getWorkbench().getHelpSystem().setHelp(action, IRubyHelpContextIds.OPEN_HIERARCHY_ACTION); action = new FormatAction(RubyEditorMessages.getBundleForConstructedKeys(), "Format.", this); action.setActionDefinitionId(IRubyEditorActionDefinitionIds.FORMAT); setAction("Format", action); } /** * Configures the toggle comment action * * @since 3.0 */ private void configureToggleCommentAction() { IAction action = getAction("ToggleComment"); //$NON-NLS-1$ if (action instanceof ToggleCommentAction) { ISourceViewer sourceViewer = getSourceViewer(); SourceViewerConfiguration configuration = getSourceViewerConfiguration(); ((ToggleCommentAction) action).configure(sourceViewer, configuration); } } /* * (non-Javadoc) * @see org.eclipse.ui.texteditor.AbstractDecoratedTextEditor#createPartControl(org.eclipse.swt.widgets.Composite) */ public void createPartControl(Composite parent) { super.createPartControl(parent); IInformationControlCreator informationControlCreator = new IInformationControlCreator() { public IInformationControl createInformationControl(Shell shell) { boolean cutDown = false; int style = cutDown ? SWT.NONE : (SWT.V_SCROLL | SWT.H_SCROLL); return new DefaultInformationControl(shell, SWT.RESIZE | SWT.TOOL, style, new HTMLTextPresenter(cutDown)); } }; fInformationPresenter = new InformationPresenter(informationControlCreator); fInformationPresenter.setSizeConstraints(60, 10, true, true); fInformationPresenter.install(getSourceViewer()); fInformationPresenter.setDocumentPartitioning(IRubyPartitions.RUBY_PARTITIONING); if (isTabConversionEnabled()) startTabConversion(); startStringSubstitutionConverter(); ISourceViewer sourceViewer = getSourceViewer(); if (sourceViewer instanceof ITextViewerExtension) { IPreferenceStore preferenceStore = getPreferenceStore(); boolean closeBrackets = preferenceStore.getBoolean(CLOSE_BRACKETS); boolean closeBraces = preferenceStore.getBoolean(CLOSE_BRACES); boolean closeStrings = preferenceStore.getBoolean(CLOSE_STRINGS); fBracketInserter.setCloseBracketsEnabled(closeBrackets); fBracketInserter.setCloseBracesEnabled(closeBraces); fBracketInserter.setCloseStringsEnabled(closeStrings); ((ITextViewerExtension) sourceViewer).prependVerifyKeyListener(fBracketInserter); } if (sourceViewer instanceof ProjectionViewer) { if (isFoldingEnabled()) { ProjectionViewer pv = (ProjectionViewer) sourceViewer; pv.doOperation(ProjectionViewer.TOGGLE); } } } /** * Resets the foldings structure according to the folding preferences. * * @since 0.9.0 */ public void resetProjection() { if (fProjectionModelUpdater != null) { fProjectionModelUpdater.initialize(); } } /* * @see * org.eclipse.ui.texteditor.AbstractTextEditor#rulerContextMenuAboutToShow(org.eclipse.jface.action.IMenuManager) */ protected void rulerContextMenuAboutToShow(IMenuManager menu) { super.rulerContextMenuAboutToShow(menu); IMenuManager foldingMenu = new MenuManager(RubyEditorMessages.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); action = getAction("FoldingCollapseMembers"); //$NON-NLS-1$ foldingMenu.add(action); action = getAction("FoldingCollapseComments"); //$NON-NLS-1$ foldingMenu.add(action); } /** * Returns the annotation overlapping with the given range or <code>null</code>. * * @param offset * the region offset * @param length * the region length * @return the found annotation or <code>null</code> * @since 3.0 */ private Annotation getAnnotation(int offset, int length) { IAnnotationModel model = getDocumentProvider().getAnnotationModel(getEditorInput()); Iterator e = new RubyAnnotationIterator(model, true, true); while (e.hasNext()) { Annotation a = (Annotation) e.next(); if (!isNavigationTarget(a)) continue; Position p = model.getPosition(a); if (p != null && p.overlapsWith(offset, length)) return a; } return null; } /** * Returns the annotation closest to the given range respecting the given direction. If an annotation is found, the * annotations current position is copied into the provided annotation position. * * @param offset * the region offset * @param length * the region length * @param forward * <code>true</code> for forwards, <code>false</code> for backward * @param annotationPosition * the position of the found annotation * @return the found annotation */ private Annotation getNextAnnotation(final int offset, final int length, boolean forward, Position annotationPosition) { Annotation nextAnnotation = null; Position nextAnnotationPosition = null; Annotation containingAnnotation = null; Position containingAnnotationPosition = null; boolean currentAnnotation = false; IDocument document = getDocumentProvider().getDocument(getEditorInput()); int endOfDocument = document.getLength(); int distance = Integer.MAX_VALUE; IAnnotationModel model = getDocumentProvider().getAnnotationModel(getEditorInput()); Iterator e = new RubyAnnotationIterator(model, true, true); while (e.hasNext()) { Annotation a = (Annotation) e.next(); if ((a instanceof IRubyAnnotation) && ((IRubyAnnotation) a).hasOverlay() || !isNavigationTarget(a)) continue; Position p = model.getPosition(a); if (p == null) continue; if (forward && p.offset == offset || !forward && p.offset + p.getLength() == offset + length) {// || p.includes(offset)) { if (containingAnnotation == null || (forward && p.length >= containingAnnotationPosition.length || !forward && p.length >= containingAnnotationPosition.length)) { containingAnnotation = a; containingAnnotationPosition = p; currentAnnotation = p.length == length; } } else { int currentDistance = 0; if (forward) { currentDistance = p.getOffset() - offset; if (currentDistance < 0) currentDistance = endOfDocument + currentDistance; if (currentDistance < distance || currentDistance == distance && p.length < nextAnnotationPosition.length) { distance = currentDistance; nextAnnotation = a; nextAnnotationPosition = p; } } else { currentDistance = offset + length - (p.getOffset() + p.length); if (currentDistance < 0) currentDistance = endOfDocument + currentDistance; if (currentDistance < distance || currentDistance == distance && p.length < nextAnnotationPosition.length) { distance = currentDistance; nextAnnotation = a; nextAnnotationPosition = p; } } } } if (containingAnnotationPosition != null && (!currentAnnotation || nextAnnotation == null)) { annotationPosition.setOffset(containingAnnotationPosition.getOffset()); annotationPosition.setLength(containingAnnotationPosition.getLength()); return containingAnnotation; } if (nextAnnotationPosition != null) { annotationPosition.setOffset(nextAnnotationPosition.getOffset()); annotationPosition.setLength(nextAnnotationPosition.getLength()); } return nextAnnotation; } /** * Returns whether the given annotation is configured as a target for the "Go to Next/Previous Annotation" actions * CHANGED TO WORK WITH 3.2 (Non-breaking in 3.1) Method couldn't be restricted to private, changed to protected * * @param annotation * the annotation * @return <code>true</code> if this is a target, <code>false</code> otherwise * @since 3.2 */ protected boolean isNavigationTarget(Annotation annotation) { Preferences preferences = EditorsUI.getPluginPreferences(); AnnotationPreference preference = getAnnotationPreferenceLookup().getAnnotationPreference(annotation); // See bug 41689 // String key= forward ? preference.getIsGoToNextNavigationTargetKey() : // preference.getIsGoToPreviousNavigationTargetKey(); String key = preference == null ? null : preference.getIsGoToNextNavigationTargetKey(); return (key != null && preferences.getBoolean(key)); } /** * Jumps to the next enabled annotation according to the given direction. An annotation type is enabled if it is * configured to be in the Next/Previous tool bar drop down menu and if it is checked. CHANGED TO WORK WITH 3.2 * (Non-breaking in 3.1) Annotation type must be returned * * @param forward * <code>true</code> if search direction is forward, <code>false</code> if backward * @since 3.2 */ public Annotation gotoAnnotation(boolean forward) { Annotation annotation = null; ITextSelection selection = (ITextSelection) getSelectionProvider().getSelection(); Position position = new Position(0, 0); if (false /* delayed - see bug 18316 */) { getNextAnnotation(selection.getOffset(), selection.getLength(), forward, position); selectAndReveal(position.getOffset(), position.getLength()); } else /* no delay - see bug 18316 */{ annotation = getNextAnnotation(selection.getOffset(), selection.getLength(), forward, position); setStatusLineErrorMessage(null); setStatusLineMessage(null); if (annotation != null) { updateAnnotationViews(annotation); selectAndReveal(position.getOffset(), position.getLength()); setStatusLineMessage(annotation.getText()); } } return annotation; } /** * Updates the annotation views that show the given annotation. * * @param annotation * the annotation */ private void updateAnnotationViews(Annotation annotation) { IMarker marker = null; if (annotation instanceof MarkerAnnotation) marker = ((MarkerAnnotation) annotation).getMarker(); else if (annotation instanceof IRubyAnnotation) { Iterator e = ((IRubyAnnotation) annotation).getOverlaidIterator(); if (e != null) { while (e.hasNext()) { Object o = e.next(); if (o instanceof MarkerAnnotation) { marker = ((MarkerAnnotation) o).getMarker(); break; } } } } if (marker != null && !marker.equals(fLastMarkerTarget)) { try { boolean isProblem = marker.isSubtypeOf(IMarker.PROBLEM); IWorkbenchPage page = getSite().getPage(); IViewPart view = page.findView(isProblem ? IPageLayout.ID_PROBLEM_VIEW : IPageLayout.ID_TASK_LIST); //$NON-NLS-1$ //$NON-NLS-2$ if (view != null) { Method method = view.getClass().getMethod( "setSelection", new Class[] { IStructuredSelection.class, boolean.class }); //$NON-NLS-1$ method.invoke(view, new Object[] { new StructuredSelection(marker), Boolean.TRUE }); } } catch (CoreException x) { } catch (NoSuchMethodException x) { } catch (IllegalAccessException x) { } catch (InvocationTargetException x) { } // ignore exceptions, don't update any of the lists, just set status // line } } /* * @see org.eclipse.ui.texteditor.AbstractTextEditor#gotoMarker(org.eclipse.core.resources.IMarker) */ public void gotoMarker(IMarker marker) { fLastMarkerTarget = marker; if (!fIsUpdatingAnnotationViews) { super.gotoMarker(marker); } } protected void updateStatusLine() { ITextSelection selection = (ITextSelection) getSelectionProvider().getSelection(); Annotation annotation = getAnnotation(selection.getOffset(), selection.getLength()); setStatusLineErrorMessage(null); setStatusLineMessage(null); if (annotation != null) { try { fIsUpdatingAnnotationViews = true; updateAnnotationViews(annotation); } finally { fIsUpdatingAnnotationViews = false; } if (annotation instanceof IRubyAnnotation && ((IRubyAnnotation) annotation).isProblem()) setStatusLineMessage(annotation.getText()); } } /** * 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); } boolean isFoldingEnabled() { return RubyPlugin.getDefault().getPreferenceStore().getBoolean(PreferenceConstants.EDITOR_FOLDING_ENABLED); } /* * (non-Javadoc) * @see org.rubypeople.rdt.internal.ui.rubyeditor.RubyAbstractEditor#dispose() */ public void dispose() { ISourceViewer sourceViewer = getSourceViewer(); if (sourceViewer instanceof ITextViewerExtension) { ((ITextViewerExtension) sourceViewer).removeVerifyKeyListener(fBracketInserter); } if (fProjectionModelUpdater != null) { fProjectionModelUpdater.uninstall(); fProjectionModelUpdater = null; } if (fProjectionSupport != null) { fProjectionSupport.dispose(); fProjectionSupport = null; } if (fActionGroups != null) { fActionGroups.dispose(); fActionGroups = null; } super.dispose(); } /* * (non-Javadoc) * @see org.eclipse.ui.texteditor.AbstractTextEditor#performRevert() */ protected void performRevert() { ProjectionViewer projectionViewer = (ProjectionViewer) getSourceViewer(); projectionViewer.setRedraw(false); try { boolean projectionMode = projectionViewer.isProjectionMode(); if (projectionMode) { projectionViewer.disableProjection(); if (fProjectionModelUpdater != null) fProjectionModelUpdater.uninstall(); } super.performRevert(); if (projectionMode) { if (fProjectionModelUpdater != null) fProjectionModelUpdater.install(this, projectionViewer); projectionViewer.enableProjection(); } } finally { projectionViewer.setRedraw(true); } } /* * (non-Javadoc) * @see org.rubypeople.rdt.internal.ui.rubyeditor.RubyAbstractEditor#getAdapter(java.lang.Class) */ public Object getAdapter(Class required) { if (IEncodingSupport.class.equals(required)) return fEncodingSupport; if (required == IShowInTargetList.class) { return new IShowInTargetList() { public String[] getShowInTargetIds() { return new String[] { RubyUI.ID_RUBY_EXPLORER, IPageLayout.ID_OUTLINE, IPageLayout.ID_RES_NAV }; } }; } if (required == IShowInSource.class) { return new IShowInSource() { public ShowInContext getShowInContext() { return new ShowInContext(getEditorInput(), null) { /* * @see org.eclipse.ui.part.ShowInContext#getSelection() * @since 3.3 */ public ISelection getSelection() { IRubyElement re = null; try { re = SelectionConverter.getElementAtOffset(RubyEditor.this); if (re == null) return null; return new StructuredSelection(re); } catch (RubyModelException ex) { return null; } } }; } }; } if (required == IRubyFoldingStructureProvider.class) return fProjectionModelUpdater; if (fProjectionSupport != null) { Object adapter = fProjectionSupport.getAdapter(getSourceViewer(), required); if (adapter != null) return adapter; } // if (required == IContextProvider.class) // return RubyUIHelp.getHelpContextProvider(this, IRubyHelpContextIds.JAVA_EDITOR); return super.getAdapter(required); } /* * (non-Javadoc) * @see org.rubypeople.rdt.internal.ui.rubyeditor.RubyAbstractEditor#doSetInput(org.eclipse.ui.IEditorInput) */ protected void doSetInput(IEditorInput input) throws CoreException { super.doSetInput(input); configureTabConverter(); if (fProjectionModelUpdater != null) fProjectionModelUpdater.initialize(); if (isShowingOverrideIndicators()) installOverrideIndicator(false); } protected void editorContextMenuAboutToShow(IMenuManager menu) { super.editorContextMenuAboutToShow(menu); menu.insertAfter(IContextMenuConstants.GROUP_OPEN, new GroupMarker(IContextMenuConstants.GROUP_SHOW)); ActionContext context = new ActionContext(getSelectionProvider().getSelection()); fContextMenuGroup.setContext(context); fContextMenuGroup.fillContextMenu(menu); fContextMenuGroup.setContext(null); // Quick views IAction action = getAction(IRubyEditorActionDefinitionIds.SHOW_OUTLINE); menu.appendToGroup(IContextMenuConstants.GROUP_OPEN, action); action = getAction(IRubyEditorActionDefinitionIds.OPEN_HIERARCHY); menu.appendToGroup(IContextMenuConstants.GROUP_OPEN, action); addExtensionMenuItems(menu); } private void addExtensionMenuItems(IMenuManager menu) { IExtensionRegistry registry = Platform.getExtensionRegistry(); IExtensionPoint extensionPoint = registry.getExtensionPoint("org.rubypeople.rdt.ui.editorPopupExtender"); IExtension[] extensions = extensionPoint.getExtensions(); for (int i = 0; i < extensions.length; i++) { IConfigurationElement[] elements = extensions[i].getConfigurationElements(); for (int j = 0; j < elements.length; j++) { IConfigurationElement element = elements[j]; SelectionEnabler selectionEnabler = new SelectionEnabler(element); if (selectionEnabler.isEnabledForSelection(this.getSelectionProvider().getSelection())) { try { Object menuExtender = element.createExecutableExtension("class"); if (!(menuExtender instanceof ActionGroup)) { String message = "The editorPopupExtender" + element.getName() + " is of type " + menuExtender.getClass().getName() + " , but should be of type ActionGroup"; RubyPlugin.log(IStatus.ERROR, message, null); continue; } ActionGroup menuExtenderActionGroup = (ActionGroup) menuExtender; menuExtenderActionGroup .setContext(new ActionContext(this.getSelectionProvider().getSelection())); menuExtenderActionGroup.fillContextMenu(menu); } catch (CoreException e) { RubyPlugin.log(e); } } } } } protected void handlePreferenceStoreChanged(PropertyChangeEvent event) { super.handlePreferenceStoreChanged(event); String property = event.getProperty(); if (CLOSE_BRACKETS.equals(property)) { fBracketInserter.setCloseBracketsEnabled(getPreferenceStore().getBoolean(property)); return; } if (CLOSE_BRACES.equals(property)) { fBracketInserter.setCloseBracesEnabled(getPreferenceStore().getBoolean(property)); return; } if (CLOSE_STRINGS.equals(property)) { fBracketInserter.setCloseStringsEnabled(getPreferenceStore().getBoolean(property)); return; } AdaptedSourceViewer sourceViewer = (AdaptedSourceViewer) getSourceViewer(); if (sourceViewer == null) return; if (SPACES_FOR_TABS.equals(property)) { if (isTabConversionEnabled()) startTabConversion(); else stopTabConversion(); return; } if (CODE_FORMATTER_TAB_SIZE.equals(property)) { sourceViewer.updateIndentationPrefixes(); if (fTabConverter != null) fTabConverter.setNumberOfSpacesPerTab(getTabSize()); } if (PreferenceConstants.EDITOR_FOLDING_PROVIDER.equals(property)) { if (sourceViewer instanceof ProjectionViewer) { ProjectionViewer projectionViewer = (ProjectionViewer) sourceViewer; if (fProjectionModelUpdater != null) fProjectionModelUpdater.uninstall(); // either freshly enabled or provider changed fProjectionModelUpdater = RubyPlugin.getDefault().getFoldingStructureProviderRegistry() .getCurrentFoldingProvider(); if (fProjectionModelUpdater != null) { fProjectionModelUpdater.install(this, projectionViewer); } } return; } if (PreferenceConstants.EDITOR_FOLDING_ENABLED.equals(property)) { if (sourceViewer instanceof ProjectionViewer) { new ToggleFoldingRunner().runWhenNextVisible(); } return; } } protected ISourceViewer createRubySourceViewer(Composite parent, IVerticalRuler verticalRuler, IOverviewRuler overviewRuler, boolean isOverviewRulerVisible, int styles, IPreferenceStore store) { ISourceViewer viewer = new AdaptedSourceViewer(parent, verticalRuler, overviewRuler, isOverviewRulerVisible, styles, store); RubySourceViewer rubySourceViewer = null; if (viewer instanceof RubySourceViewer) rubySourceViewer = (RubySourceViewer) viewer; /* * This is a performance optimization to reduce the computation of the text presentation triggered by {@link * #setVisibleDocument(IDocument)} */ // if (rubySourceViewer != null && isFoldingEnabled() && (store == null || // !store.getBoolean(PreferenceConstants.EDITOR_SHOW_SEGMENTS))) // rubySourceViewer.prepareDelayedProjection(); ProjectionViewer projectionViewer = (ProjectionViewer) viewer; fProjectionSupport = new ProjectionSupport(projectionViewer, getAnnotationAccess(), getSharedColors()); fProjectionSupport.addSummarizableAnnotationType("org.eclipse.ui.workbench.texteditor.error"); //$NON-NLS-1$ fProjectionSupport.addSummarizableAnnotationType("org.eclipse.ui.workbench.texteditor.warning"); //$NON-NLS-1$ fProjectionSupport.setHoverControlCreator(new IInformationControlCreator() { public IInformationControl createInformationControl(Shell shell) { return new SourceViewerInformationControl(shell, SWT.TOOL | SWT.NO_TRIM | getOrientation(), SWT.NONE); } }); // fProjectionSupport.setInformationPresenterControlCreator(new IInformationControlCreator() { // public IInformationControl createInformationControl(Shell shell) { // int shellStyle= SWT.RESIZE | SWT.TOOL | getOrientation(); // int style= SWT.V_SCROLL | SWT.H_SCROLL; // return new SourceViewerInformationControl(shell, shellStyle, style); // } // }); fProjectionSupport.install(); fProjectionModelUpdater = RubyPlugin.getDefault().getFoldingStructureProviderRegistry() .getCurrentFoldingProvider(); if (fProjectionModelUpdater != null) fProjectionModelUpdater.install(this, projectionViewer); if (isFoldingEnabled()) projectionViewer.doOperation(ProjectionViewer.TOGGLE); return viewer; } /** * Returns the mutex for the reconciler. See https://bugs.eclipse.org/bugs/show_bug.cgi?id=63898 for a description * of the problem. * <p> * TODO remove once the underlying problem is solved. * </p> * * @return the lock reconcilers may use to synchronize on */ public Object getReconcilerLock() { return fReconcilerLock; } private static char getEscapeCharacter(char character) { switch (character) { case '"': case '\'': return '\\'; default: return 0; } } private static char getPeerCharacter(char character) { switch (character) { case '(': return ')'; case ')': return '('; case '{': return '}'; case '}': return '{'; case '[': return ']'; case ']': return '['; case '"': return character; case '\'': return character; case '`': return character; default: throw new IllegalArgumentException(); } } public void setCaretPosition(CaretPosition pos) { try { int lineOffset = this.getSourceViewer().getDocument().getLineOffset(pos.line); this.selectAndReveal(lineOffset + pos.column, 0); } catch (BadLocationException e) { } } public class CaretPosition { public CaretPosition(int line, int column) { this.line = line; this.column = column; } public CaretPosition(int line, int column, int offset) { this(line, column); this.offset = offset; } public int getColumn() { return column; } public int getLine() { return line; } public int getOffset() { return offset; } private int line; private int column; private int offset; } private class ExitPolicy implements IExitPolicy { final char fExitCharacter; final char fEscapeCharacter; final Stack fStack; final int fSize; public ExitPolicy(char exitCharacter, char escapeCharacter, Stack stack) { fExitCharacter = exitCharacter; fEscapeCharacter = escapeCharacter; fStack = stack; fSize = fStack.size(); } /* * @see * org.eclipse.jdt.internal.ui.text.link.LinkedPositionUI.ExitPolicy#doExit(org.eclipse.jdt.internal.ui.text * .link.LinkedPositionManager, org.eclipse.swt.events.VerifyEvent, int, int) */ public ExitFlags doExit(LinkedModeModel model, VerifyEvent event, int offset, int length) { if (event.character == fExitCharacter) { if (fSize == fStack.size() && !isMasked(offset)) { BracketLevel level = (BracketLevel) fStack.peek(); if (level.fFirstPosition.offset > offset || level.fSecondPosition.offset < offset) return null; if (level.fSecondPosition.offset == offset && length == 0) // don't enter the character if if its the closing peer return new ExitFlags(ILinkedModeListener.UPDATE_CARET, false); } } return null; } private boolean isMasked(int offset) { IDocument document = getSourceViewer().getDocument(); try { return fEscapeCharacter == document.getChar(offset - 1); } catch (BadLocationException e) { } return false; } } private static class BracketLevel { int fOffset; int fLength; LinkedModeUI fUI; Position fFirstPosition; Position fSecondPosition; } /** * Position updater that takes any changes at the borders of a position to not belong to the position. * * @since 3.0 */ private static class ExclusivePositionUpdater implements IPositionUpdater { /** The position category. */ private final String fCategory; /** * Creates a new updater for the given <code>category</code>. * * @param category * the new category. */ public ExclusivePositionUpdater(String category) { fCategory = category; } /* * @see org.eclipse.jface.text.IPositionUpdater#update(org.eclipse.jface.text.DocumentEvent) */ public void update(DocumentEvent event) { int eventOffset = event.getOffset(); int eventOldLength = event.getLength(); int eventNewLength = event.getText() == null ? 0 : event.getText().length(); int deltaLength = eventNewLength - eventOldLength; try { Position[] positions = event.getDocument().getPositions(fCategory); for (int i = 0; i != positions.length; i++) { Position position = positions[i]; if (position.isDeleted()) continue; int offset = position.getOffset(); int length = position.getLength(); int end = offset + length; if (offset >= eventOffset + eventOldLength) // position comes // after change - shift position.setOffset(offset + deltaLength); else if (end <= eventOffset) { // position comes way before change - // leave alone } else if (offset <= eventOffset && end >= eventOffset + eventOldLength) { // event completely internal to the position - adjust // length position.setLength(length + deltaLength); } else if (offset < eventOffset) { // event extends over end of position - adjust length int newEnd = eventOffset; position.setLength(newEnd - offset); } else if (end > eventOffset + eventOldLength) { // event extends from before position into it - adjust // offset // and length // offset becomes end of event, length ajusted // acordingly int newOffset = eventOffset + eventNewLength; position.setOffset(newOffset); position.setLength(end - newOffset); } else { // event consumes the position - delete it position.delete(); } } } catch (BadPositionCategoryException e) { // ignore and return } } /** * Returns the position category. * * @return the position category */ public String getCategory() { return fCategory; } } private class BracketInserter implements VerifyKeyListener, ILinkedModeListener { private boolean fCloseBrackets = true; private boolean fCloseStrings = true; private boolean fCloseBraces = true; private final String CATEGORY = toString(); private IPositionUpdater fUpdater = new ExclusivePositionUpdater(CATEGORY); private Stack fBracketLevelStack = new Stack(); public void setCloseBracketsEnabled(boolean enabled) { fCloseBrackets = enabled; } public void setCloseStringsEnabled(boolean enabled) { fCloseStrings = enabled; } public void setCloseBracesEnabled(boolean enabled) { fCloseBraces = enabled; } /* * @see org.eclipse.swt.custom.VerifyKeyListener#verifyKey(org.eclipse.swt.events.VerifyEvent) */ public void verifyKey(VerifyEvent event) { // FIXME Why aren't we normally in SMART_INSERT mode like JDT? // early pruning to slow down normal typing as little as possible if (!event.doit /* || getInsertMode() != SMART_INSERT */) return; switch (event.character) { case '(': case '{': case '[': case '\'': case '\"': case '`': break; default: return; } final ISourceViewer sourceViewer = getSourceViewer(); IDocument document = sourceViewer.getDocument(); final Point selection = sourceViewer.getSelectedRange(); final int offset = selection.x; final int length = selection.y; try { String selected = document.get(offset, length); if (selected != null && selected.length() > 0 && (event.character == '"' || event.character == '\'')) { return; } IRegion startLine = document.getLineInformationOfOffset(offset); IRegion endLine = document.getLineInformationOfOffset(offset + length); RubyHeuristicScanner scanner = new RubyHeuristicScanner(document); int nextToken = scanner.nextToken(offset + length, endLine.getOffset() + endLine.getLength()); String next = nextToken == Symbols.TokenEOF ? null : document.get(offset, scanner.getPosition() - offset).trim(); int prevToken = scanner.previousToken(offset - 1, startLine.getOffset()); int prevTokenOffset = scanner.getPosition() + 1; String previous = prevToken == Symbols.TokenEOF ? null : document.get(prevTokenOffset, offset - prevTokenOffset).trim(); switch (event.character) { case '(': if (!fCloseBrackets || nextToken == Symbols.TokenLPAREN || nextToken == Symbols.TokenIDENT || next != null && next.length() > 1) return; break; case '{': if (!fCloseBraces || nextToken == Symbols.TokenLBRACE || nextToken == Symbols.TokenIDENT || next != null && next.length() > 1) return; break; case '[': if (!fCloseBrackets || nextToken == Symbols.TokenIDENT || next != null && next.length() > 1) return; break; case '\'': case '"': case '`': if (!fCloseStrings || nextToken == Symbols.TokenIDENT /* || prevToken == Symbols.TokenIDENT */|| next != null && next.length() > 1 /* || previous != null && previous.length() > 1 */) return; break; default: return; } ITypedRegion partition = TextUtilities.getPartition(document, IRubyPartitions.RUBY_PARTITIONING, offset - 1, true); if (event.character == '{' && previous != null && previous.endsWith("#") && partition != null && partition.getType() != null) { if (!IDocument.DEFAULT_CONTENT_TYPE.equals(partition.getType()) && !IRubyPartitions.RUBY_STRING.equals(partition.getType())) return; } else { if (!IDocument.DEFAULT_CONTENT_TYPE.equals(partition.getType())) return; } if (!validateEditorInputState()) return; final char character = event.character; final char closingCharacter = getPeerCharacter(character); final StringBuffer buffer = new StringBuffer(); buffer.append(character); buffer.append(closingCharacter); document.replace(offset, length, buffer.toString()); BracketLevel level = new BracketLevel(); fBracketLevelStack.push(level); LinkedPositionGroup group = new LinkedPositionGroup(); group.addPosition(new LinkedPosition(document, offset + 1, 0, LinkedPositionGroup.NO_STOP)); LinkedModeModel model = new LinkedModeModel(); model.addLinkingListener(this); model.addGroup(group); model.forceInstall(); level.fOffset = offset; level.fLength = 2; // set up position tracking for our magic peers if (fBracketLevelStack.size() == 1) { document.addPositionCategory(CATEGORY); document.addPositionUpdater(fUpdater); } level.fFirstPosition = new Position(offset, 1); level.fSecondPosition = new Position(offset + 1, 1); document.addPosition(CATEGORY, level.fFirstPosition); document.addPosition(CATEGORY, level.fSecondPosition); level.fUI = new EditorLinkedModeUI(model, sourceViewer); level.fUI.setSimpleMode(true); level.fUI.setExitPolicy(new ExitPolicy(closingCharacter, getEscapeCharacter(closingCharacter), fBracketLevelStack)); level.fUI.setExitPosition(sourceViewer, offset + 2, 0, Integer.MAX_VALUE); level.fUI.setCyclingMode(LinkedModeUI.CYCLE_NEVER); level.fUI.enter(); IRegion newSelection = level.fUI.getSelectedRegion(); sourceViewer.setSelectedRange(newSelection.getOffset(), newSelection.getLength()); event.doit = false; } catch (BadLocationException e) { RubyPlugin.log(e); } catch (BadPositionCategoryException e) { RubyPlugin.log(e); } } /* * @see org.eclipse.jface.text.link.ILinkedModeListener#left(org.eclipse.jface.text.link.LinkedModeModel, int) */ public void left(LinkedModeModel environment, int flags) { final BracketLevel level = (BracketLevel) fBracketLevelStack.pop(); if (flags != ILinkedModeListener.EXTERNAL_MODIFICATION) return; // remove brackets final ISourceViewer sourceViewer = getSourceViewer(); final IDocument document = sourceViewer.getDocument(); if (document instanceof IDocumentExtension) { IDocumentExtension extension = (IDocumentExtension) document; extension.registerPostNotificationReplace(null, new IDocumentExtension.IReplace() { public void perform(IDocument d, IDocumentListener owner) { if ((level.fFirstPosition.isDeleted || level.fFirstPosition.length == 0) && !level.fSecondPosition.isDeleted && level.fSecondPosition.offset == level.fFirstPosition.offset) { try { document.replace(level.fSecondPosition.offset, level.fSecondPosition.length, null); } catch (BadLocationException e) { RubyPlugin.log(e); } } if (fBracketLevelStack.size() == 0) { document.removePositionUpdater(fUpdater); try { document.removePositionCategory(CATEGORY); } catch (BadPositionCategoryException e) { RubyPlugin.log(e); } } } }); } } /* * @see org.eclipse.jface.text.link.ILinkedModeListener#suspend(org.eclipse.jface.text.link.LinkedModeModel) */ public void suspend(LinkedModeModel environment) { } /* * @see org.eclipse.jface.text.link.ILinkedModeListener#resume(org.eclipse.jface.text.link.LinkedModeModel, int) */ public void resume(LinkedModeModel environment, int flags) { } } public CaretPosition getCaretPosition() { // needed for positioning the cursor after formatting without selection StyledText styledText = this.getSourceViewer().getTextWidget(); int caret = widgetOffset2ModelOffset(getSourceViewer(), styledText.getCaretOffset()); IDocument document = getSourceViewer().getDocument(); try { int line = document.getLineOfOffset(caret); int lineOffset = document.getLineOffset(line); return new CaretPosition(line, caret - lineOffset, caret); } catch (BadLocationException e) { return new CaretPosition(0, 0); } } /** * Returns the most narrow element including the given offset. If <code>reconcile</code> is <code>true</code> the * editor's input element is reconciled in advance. If it is <code>false</code> this method only returns a result if * the editor's input element does not need to be reconciled. * * @param offset * the offset included by the retrieved element * @param reconcile * <code>true</code> if working copy should be reconciled * @return the most narrow element which includes the given offset */ protected IRubyElement getElementAt(int offset, boolean reconcile) { IWorkingCopyManager manager = RubyPlugin.getDefault().getWorkingCopyManager(); IRubyScript unit = manager.getWorkingCopy(getEditorInput()); if (unit != null) { try { if (reconcile) { RubyModelUtil.reconcile(unit); return unit.getElementAt(offset); } else if (unit.isConsistent()) return unit.getElementAt(offset); } catch (RubyModelException x) { if (!x.isDoesNotExist()) RubyPlugin.log(x.getStatus()); // nothing found, be tolerant and go on } } return null; } /* * @see RubyEditor#getElementAt(int) */ protected IRubyElement getElementAt(int offset) { return getElementAt(offset, true); } /** * Jumps to the matching bracket. */ public void gotoMatchingBracket() { ISourceViewer sourceViewer = getSourceViewer(); IDocument document = sourceViewer.getDocument(); if (document == null) return; IRegion selection = getSignedSelection(sourceViewer); int selectionLength = Math.abs(selection.getLength()); if (selectionLength > 1) { setStatusLineErrorMessage(RubyEditorMessages.GotoMatchingBracket_error_invalidSelection); sourceViewer.getTextWidget().getDisplay().beep(); return; } // #26314 int sourceCaretOffset = selection.getOffset() + selection.getLength(); if (isSurroundedByBrackets(document, sourceCaretOffset)) sourceCaretOffset -= selection.getLength(); IRegion region = fBracketMatcher.match(document, sourceCaretOffset); if (region == null) { setStatusLineErrorMessage(RubyEditorMessages.GotoMatchingBracket_error_noMatchingBracket); sourceViewer.getTextWidget().getDisplay().beep(); return; } int offset = region.getOffset(); int length = region.getLength(); if (length < 1) return; int anchor = fBracketMatcher.getAnchor(); // http://dev.eclipse.org/bugs/show_bug.cgi?id=34195 int targetOffset = (ICharacterPairMatcher.RIGHT == anchor) ? offset + 1 : offset + length; boolean visible = false; if (sourceViewer instanceof ITextViewerExtension5) { ITextViewerExtension5 extension = (ITextViewerExtension5) sourceViewer; visible = (extension.modelOffset2WidgetOffset(targetOffset) > -1); } else { IRegion visibleRegion = sourceViewer.getVisibleRegion(); // http://dev.eclipse.org/bugs/show_bug.cgi?id=34195 visible = (targetOffset >= visibleRegion.getOffset() && targetOffset <= visibleRegion.getOffset() + visibleRegion.getLength()); } if (!visible) { setStatusLineErrorMessage(RubyEditorMessages.GotoMatchingBracket_error_bracketOutsideSelectedElement); sourceViewer.getTextWidget().getDisplay().beep(); return; } if (selection.getLength() < 0) targetOffset -= selection.getLength(); sourceViewer.setSelectedRange(targetOffset, selection.getLength()); sourceViewer.revealRange(targetOffset, selection.getLength()); } /** * Returns the signed current selection. The length will be negative if the resulting selection is right-to-left * (RtoL). * <p> * The selection offset is model based. * </p> * * @param sourceViewer * the source viewer * @return a region denoting the current signed selection, for a resulting RtoL selections length is < 0 */ protected IRegion getSignedSelection(ISourceViewer sourceViewer) { StyledText text = sourceViewer.getTextWidget(); Point selection = text.getSelectionRange(); if (text.getCaretOffset() == selection.x) { selection.x = selection.x + selection.y; selection.y = -selection.y; } selection.x = widgetOffset2ModelOffset(sourceViewer, selection.x); return new Region(selection.x, selection.y); } private static boolean isSurroundedByBrackets(IDocument document, int offset) { if (offset == 0 || offset == document.getLength()) return false; try { return isBracket(document.getChar(offset - 1)) && isBracket(document.getChar(offset)); } catch (BadLocationException e) { return false; } } private static boolean isBracket(char character) { for (int i = 0; i != BRACKETS.length; ++i) if (character == BRACKETS[i]) return true; return false; } /** * Runner that will toggle folding either instantly (if the editor is visible) or the next time it becomes visible. * If a runner is started when there is already one registered, the registered one is canceled as toggling folding * twice is a no-op. * <p> * The access to the fFoldingRunner field is not thread-safe, it is assumed that <code>runWhenNextVisible</code> is * only called from the UI thread. * </p> * * @since 0.9.0 */ private final class ToggleFoldingRunner implements IPartListener2 { /** * The workbench page we registered the part listener with, or <code>null</code>. */ private IWorkbenchPage fPage; /** * Does the actual toggling of projection. */ private void toggleFolding() { ISourceViewer sourceViewer = getSourceViewer(); if (sourceViewer instanceof ProjectionViewer) { ProjectionViewer pv = (ProjectionViewer) sourceViewer; if (pv.isProjectionMode() != isFoldingEnabled()) { if (pv.canDoOperation(ProjectionViewer.TOGGLE)) pv.doOperation(ProjectionViewer.TOGGLE); } } } /** * Makes sure that the editor's folding state is correct the next time it becomes visible. If it already is * visible, it toggles the folding state. If not, it either registers a part listener to toggle folding when the * editor becomes visible, or cancels an already registered runner. */ public void runWhenNextVisible() { // if there is one already: toggling twice is the identity if (fFoldingRunner != null) { fFoldingRunner.cancel(); return; } IWorkbenchPartSite site = getSite(); if (site != null) { IWorkbenchPage page = site.getPage(); if (!page.isPartVisible(RubyEditor.this)) { // if we're not visible - defer until visible fPage = page; fFoldingRunner = this; page.addPartListener(this); return; } } // we're visible - run now toggleFolding(); } /** * Remove the listener and clear the field. */ private void cancel() { if (fPage != null) { fPage.removePartListener(this); fPage = null; } if (fFoldingRunner == this) fFoldingRunner = null; } /* * @see org.eclipse.ui.IPartListener2#partVisible(org.eclipse.ui.IWorkbenchPartReference) */ public void partVisible(IWorkbenchPartReference partRef) { if (RubyEditor.this.equals(partRef.getPart(false))) { cancel(); toggleFolding(); } } /* * @see org.eclipse.ui.IPartListener2#partClosed(org.eclipse.ui.IWorkbenchPartReference) */ public void partClosed(IWorkbenchPartReference partRef) { if (RubyEditor.this.equals(partRef.getPart(false))) { cancel(); } } public void partActivated(IWorkbenchPartReference partRef) { } public void partBroughtToTop(IWorkbenchPartReference partRef) { } public void partDeactivated(IWorkbenchPartReference partRef) { } public void partOpened(IWorkbenchPartReference partRef) { } public void partHidden(IWorkbenchPartReference partRef) { } public void partInputChanged(IWorkbenchPartReference partRef) { } } interface ITextConverter { void customizeDocumentCommand(IDocument document, DocumentCommand command); } public static class TabConverter implements ITextConverter { private int fTabRatio; private ILineTracker fLineTracker; public TabConverter() { } public void setNumberOfSpacesPerTab(int ratio) { fTabRatio = ratio; } public void setLineTracker(ILineTracker lineTracker) { fLineTracker = lineTracker; } private int insertTabString(StringBuffer buffer, int offsetInLine) { if (fTabRatio == 0) return 0; int remainder = offsetInLine % fTabRatio; remainder = fTabRatio - remainder; for (int i = 0; i < remainder; i++) buffer.append(' '); return remainder; } public void customizeDocumentCommand(IDocument document, DocumentCommand command) { String text = command.text; if (text == null) return; int index = text.indexOf('\t'); if (index > -1) { StringBuffer buffer = new StringBuffer(); fLineTracker.set(command.text); int lines = fLineTracker.getNumberOfLines(); try { for (int i = 0; i < lines; i++) { int offset = fLineTracker.getLineOffset(i); int endOffset = offset + fLineTracker.getLineLength(i); String line = text.substring(offset, endOffset); int position = 0; if (i == 0) { IRegion firstLine = document.getLineInformationOfOffset(command.offset); position = command.offset - firstLine.getOffset(); } int length = line.length(); for (int j = 0; j < length; j++) { char c = line.charAt(j); if (c == '\t') { position += insertTabString(buffer, position); } else { buffer.append(c); ++position; } } } command.text = buffer.toString(); } catch (BadLocationException x) { } } } } /** * Collapses all foldable members if supported by the folding structure provider. * * @since 0.9.0 */ public void collapseMembers() { if (fProjectionModelUpdater instanceof IRubyFoldingStructureProviderExtension) { IRubyFoldingStructureProviderExtension extension = (IRubyFoldingStructureProviderExtension) fProjectionModelUpdater; extension.collapseMembers(); } } /** * Collapses all foldable comments if supported by the folding structure provider. * * @since 0.9.0 */ public void collapseComments() { if (fProjectionModelUpdater instanceof IRubyFoldingStructureProviderExtension) { IRubyFoldingStructureProviderExtension extension = (IRubyFoldingStructureProviderExtension) fProjectionModelUpdater; extension.collapseComments(); } } public FoldingActionGroup getFoldingActionGroup() { return fFoldingGroup; } private int getTabSize() { IRubyElement element = getInputRubyElement(); IRubyProject project = element == null ? null : element.getRubyProject(); return CodeFormatterUtil.getTabWidth(project); } private void startTabConversion() { if (fTabConverter == null) { fTabConverter = new TabConverter(); configureTabConverter(); fTabConverter.setNumberOfSpacesPerTab(getTabSize()); AdaptedSourceViewer asv = (AdaptedSourceViewer) getSourceViewer(); asv.addTextConverter(fTabConverter); // http://dev.eclipse.org/bugs/show_bug.cgi?id=19270 asv.updateIndentationPrefixes(); } } private void startStringSubstitutionConverter() { if (fStringConverter == null) { fStringConverter = new StringSubstitutionConverter(this); IDocumentProvider provider = getDocumentProvider(); if (provider instanceof IRubyScriptDocumentProvider) { IRubyScriptDocumentProvider cup = (IRubyScriptDocumentProvider) provider; fStringConverter.setLineTracker(cup.createLineTracker(getEditorInput())); } AdaptedSourceViewer asv = (AdaptedSourceViewer) getSourceViewer(); asv.addTextConverter(fStringConverter); } } private void configureTabConverter() { if (fTabConverter != null) { IDocumentProvider provider = getDocumentProvider(); if (provider instanceof IRubyScriptDocumentProvider) { IRubyScriptDocumentProvider cup = (IRubyScriptDocumentProvider) provider; fTabConverter.setLineTracker(cup.createLineTracker(getEditorInput())); } } } private void stopTabConversion() { if (fTabConverter != null) { AdaptedSourceViewer asv = (AdaptedSourceViewer) getSourceViewer(); asv.removeTextConverter(fTabConverter); // http://dev.eclipse.org/bugs/show_bug.cgi?id=19270 asv.updateIndentationPrefixes(); fTabConverter = null; } } private boolean isTabConversionEnabled() { IRubyElement element = getInputRubyElement(); IRubyProject project = element == null ? null : element.getRubyProject(); String option; if (project == null) option = RubyCore.getOption(SPACES_FOR_TABS); else option = project.getOption(SPACES_FOR_TABS, true); return RubyCore.SPACE.equals(option); } /** * Tells whether override indicators are shown. * * @return <code>true</code> if the override indicators are shown * @since 3.0 */ protected boolean isShowingOverrideIndicators() { AnnotationPreference preference = getAnnotationPreferenceLookup().getAnnotationPreference( OverrideIndicatorManager.ANNOTATION_TYPE); IPreferenceStore store = getPreferenceStore(); return getBoolean(store, preference.getHighlightPreferenceKey()) || getBoolean(store, preference.getVerticalRulerPreferenceKey()) || getBoolean(store, preference.getOverviewRulerPreferenceKey()) || getBoolean(store, preference.getTextPreferenceKey()); } /** * Returns the boolean preference for the given key. * * @param store * the preference store * @param key * the preference key * @return <code>true</code> if the key exists in the store and its value is <code>true</code> * @since 3.0 */ private boolean getBoolean(IPreferenceStore store, String key) { return key != null && store.getBoolean(key); } protected void installOverrideIndicator(boolean provideAST) { uninstallOverrideIndicator(); IAnnotationModel model = getDocumentProvider().getAnnotationModel(getEditorInput()); final IRubyElement inputElement = getInputRubyElement(); if (model == null || inputElement == null) return; fOverrideIndicatorManager = new OverrideIndicatorManager(model, inputElement, null); if (provideAST) { Job job = new Job("Installing override indicators...") { /* * @see org.eclipse.core.runtime.jobs.Job#run(org.eclipse.core.runtime.IProgressMonitor) * @since 3.0 */ protected IStatus run(IProgressMonitor monitor) { if (fOverrideIndicatorManager != null) // editor might have been closed in the meanwhile fOverrideIndicatorManager.reconciled((IRubyScript) inputElement, null, true, monitor); return Status.OK_STATUS; } }; job.setPriority(Job.DECORATE); job.setSystem(true); job.schedule(); } if (fOverrideIndicatorManager == null) return; addReconcileListener(fOverrideIndicatorManager); } /** * Adds the given listener. Has no effect if an identical listener was not already registered. * * @param listener * The reconcile listener to be added * @since 3.0 */ final void addReconcileListener(IRubyReconcilingListener listener) { synchronized (fReconcilingListeners) { fReconcilingListeners.add(listener); } } protected void uninstallOverrideIndicator() { if (fOverrideIndicatorManager != null) { fOverrideIndicatorManager.removeAnnotations(); fOverrideIndicatorManager = null; } } /** * Determines whether the preference change encoded by the given event changes the override indication. * * @param event * the event to be investigated * @return <code>true</code> if event causes a change * @since 3.0 */ protected boolean affectsOverrideIndicatorAnnotations(PropertyChangeEvent event) { String key = event.getProperty(); AnnotationPreference preference = getAnnotationPreferenceLookup().getAnnotationPreference( OverrideIndicatorManager.ANNOTATION_TYPE); if (key == null || preference == null) return false; return key.equals(preference.getHighlightPreferenceKey()) || key.equals(preference.getVerticalRulerPreferenceKey()) || key.equals(preference.getOverviewRulerPreferenceKey()) || key.equals(preference.getTextPreferenceKey()); } /* * @see org.rubypeople.rdt.internal.ui.text.ruby.IRubyReconcilingListener#aboutToBeReconciled() * @since 1.0 */ public void aboutToBeReconciled() { // Notify AST provider RubyPlugin.getDefault().getASTProvider().aboutToBeReconciled(getInputRubyElement()); // Notify listeners Object[] listeners = fReconcilingListeners.getListeners(); for (int i = 0, length = listeners.length; i < length; ++i) ((IRubyReconcilingListener) listeners[i]).aboutToBeReconciled(); } /* * @see org.rubypeople.rdt.internal.ui.text.ruby.IRubyReconcilingListener#reconciled(RootNode, boolean, * IProgressMonitor) * @since 1.0 */ public void reconciled(IRubyScript script, RootNode ast, boolean forced, IProgressMonitor progressMonitor) { // see: https://bugs.eclipse.org/bugs/show_bug.cgi?id=58245 RubyPlugin javaPlugin = RubyPlugin.getDefault(); if (javaPlugin == null) return; // Always notify AST provider javaPlugin.getASTProvider().reconciled(ast, getInputRubyElement(), progressMonitor); // Notify listeners Object[] listeners = fReconcilingListeners.getListeners(); for (int i = 0, length = listeners.length; i < length; ++i) ((IRubyReconcilingListener) listeners[i]).reconciled(script, ast, forced, progressMonitor); // Update Ruby Outline page selection if (!forced && !progressMonitor.isCanceled()) { Shell shell = getSite().getShell(); if (shell != null && !shell.isDisposed()) { shell.getDisplay().asyncExec(new Runnable() { public void run() { selectionChanged(); } }); } } } /** * React to changed selection. * * @since 3.0 */ protected void selectionChanged() { if (getSelectionProvider() == null) return; // FIXME Uncomment // ISourceReference element= computeHighlightRangeSourceReference(); // if (getPreferenceStore().getBoolean(PreferenceConstants.EDITOR_SYNC_OUTLINE_ON_CURSOR_MOVE)) // synchronizeOutlinePage(element); // setSelection(element, false); // if (!fSelectionChangedViaGotoAnnotation) // updateStatusLine(); // fSelectionChangedViaGotoAnnotation= false; } /** * This action behaves in two different ways: If there is no current text hover, the javadoc is displayed using * information presenter. If there is a current text hover, it is converted into a information presenter in order to * make it sticky. */ class InformationDispatchAction extends TextEditorAction { /** The wrapped text operation action. */ private final TextOperationAction fTextOperationAction; /** * Creates a dispatch action. * * @param resourceBundle * the resource bundle * @param prefix * the prefix * @param textOperationAction * the text operation action */ public InformationDispatchAction(ResourceBundle resourceBundle, String prefix, final TextOperationAction textOperationAction) { super(resourceBundle, prefix, RubyEditor.this); if (textOperationAction == null) throw new IllegalArgumentException(); fTextOperationAction = textOperationAction; } /* * @see org.eclipse.jface.action.IAction#run() */ public void run() { ISourceViewer sourceViewer = getSourceViewer(); if (sourceViewer == null) { fTextOperationAction.run(); return; } if (sourceViewer instanceof ITextViewerExtension4) { ITextViewerExtension4 extension4 = (ITextViewerExtension4) sourceViewer; if (extension4.moveFocusToWidgetToken()) return; } if (sourceViewer instanceof ITextViewerExtension2) { // does a text hover exist? ITextHover textHover = ((ITextViewerExtension2) sourceViewer).getCurrentTextHover(); if (textHover != null && makeTextHoverFocusable(sourceViewer, textHover)) return; } if (sourceViewer instanceof ISourceViewerExtension3) { // does an annotation hover exist? IAnnotationHover annotationHover = ((ISourceViewerExtension3) sourceViewer).getCurrentAnnotationHover(); if (annotationHover != null && makeAnnotationHoverFocusable(sourceViewer, annotationHover)) return; } // otherwise, just run the action fTextOperationAction.run(); } /** * Tries to make a text hover focusable (or "sticky"). * * @param sourceViewer * the source viewer to display the hover over * @param textHover * the hover to make focusable * @return <code>true</code> if successful, <code>false</code> otherwise * @since 3.2 */ private boolean makeTextHoverFocusable(ISourceViewer sourceViewer, ITextHover textHover) { Point hoverEventLocation = ((ITextViewerExtension2) sourceViewer).getHoverEventLocation(); int offset = computeOffsetAtLocation(sourceViewer, hoverEventLocation.x, hoverEventLocation.y); if (offset == -1) return false; try { IRegion hoverRegion = textHover.getHoverRegion(sourceViewer, offset); if (hoverRegion == null) return false; String hoverInfo = textHover.getHoverInfo(sourceViewer, hoverRegion); IInformationControlCreator controlCreator = null; if (textHover instanceof IInformationProviderExtension2) controlCreator = ((IInformationProviderExtension2) textHover) .getInformationPresenterControlCreator(); IInformationProvider informationProvider = new InformationProvider(hoverRegion, hoverInfo, controlCreator); fInformationPresenter.setOffset(offset); fInformationPresenter.setAnchor(AbstractInformationControlManager.ANCHOR_BOTTOM); fInformationPresenter.setMargins(6, 6); // default values from AbstractInformationControlManager String contentType = TextUtilities.getContentType(sourceViewer.getDocument(), IRubyPartitions.RUBY_PARTITIONING, offset, true); fInformationPresenter.setInformationProvider(informationProvider, contentType); fInformationPresenter.showInformation(); return true; } catch (BadLocationException e) { return false; } } /** * Tries to make an annotation hover focusable (or "sticky"). * * @param sourceViewer * the source viewer to display the hover over * @param annotationHover * the hover to make focusable * @return <code>true</code> if successful, <code>false</code> otherwise * @since 3.2 */ private boolean makeAnnotationHoverFocusable(ISourceViewer sourceViewer, IAnnotationHover annotationHover) { IVerticalRulerInfo info = getVerticalRuler(); int line = info.getLineOfLastMouseButtonActivity(); if (line == -1) return false; try { // compute the hover information Object hoverInfo; if (annotationHover instanceof IAnnotationHoverExtension) { IAnnotationHoverExtension extension = (IAnnotationHoverExtension) annotationHover; ILineRange hoverLineRange = extension.getHoverLineRange(sourceViewer, line); if (hoverLineRange == null) return false; final int maxVisibleLines = Integer.MAX_VALUE; // allow any number of lines being displayed, as we // support scrolling hoverInfo = extension.getHoverInfo(sourceViewer, hoverLineRange, maxVisibleLines); } else { hoverInfo = annotationHover.getHoverInfo(sourceViewer, line); } // hover region: the beginning of the concerned line to place the control right over the line IDocument document = sourceViewer.getDocument(); int offset = document.getLineOffset(line); String contentType = TextUtilities.getContentType(document, IRubyPartitions.RUBY_PARTITIONING, offset, true); IInformationControlCreator controlCreator = null; /* * XXX: This is a hack to avoid API changes at the end of 3.2, and should be fixed for 3.3, see: * https://bugs.eclipse.org/bugs/show_bug.cgi?id=137967 */ if ("org.eclipse.jface.text.source.projection.ProjectionAnnotationHover".equals(annotationHover.getClass().getName())) { //$NON-NLS-1$ controlCreator = new IInformationControlCreator() { public IInformationControl createInformationControl(Shell shell) { int shellStyle = SWT.RESIZE | SWT.TOOL | getOrientation(); int style = SWT.V_SCROLL | SWT.H_SCROLL; return new SourceViewerInformationControl(shell, shellStyle, style); } }; } else { if (annotationHover instanceof IInformationProviderExtension2) controlCreator = ((IInformationProviderExtension2) annotationHover) .getInformationPresenterControlCreator(); else if (annotationHover instanceof IAnnotationHoverExtension) controlCreator = ((IAnnotationHoverExtension) annotationHover).getHoverControlCreator(); } IInformationProvider informationProvider = new InformationProvider(new Region(offset, 0), hoverInfo, controlCreator); fInformationPresenter.setOffset(offset); fInformationPresenter.setAnchor(AbstractInformationControlManager.ANCHOR_RIGHT); fInformationPresenter.setMargins(4, 0); // AnnotationBarHoverManager sets (5,0), minus // SourceViewer.GAP_SIZE_1 fInformationPresenter.setInformationProvider(informationProvider, contentType); fInformationPresenter.showInformation(); return true; } catch (BadLocationException e) { return false; } } // modified version from TextViewer private int computeOffsetAtLocation(ITextViewer textViewer, int x, int y) { StyledText styledText = textViewer.getTextWidget(); IDocument document = textViewer.getDocument(); if (document == null) return -1; try { int widgetOffset = styledText.getOffsetAtLocation(new Point(x, y)); Point p = styledText.getLocationAtOffset(widgetOffset); if (p.x > x) widgetOffset--; if (textViewer instanceof ITextViewerExtension5) { ITextViewerExtension5 extension = (ITextViewerExtension5) textViewer; return extension.widgetOffset2ModelOffset(widgetOffset); } else { IRegion visibleRegion = textViewer.getVisibleRegion(); return widgetOffset + visibleRegion.getOffset(); } } catch (IllegalArgumentException e) { return -1; } } } /** * Information provider used to present focusable information shells. * * @since 3.2 */ private static final class InformationProvider implements IInformationProvider, IInformationProviderExtension, IInformationProviderExtension2 { private IRegion fHoverRegion; private Object fHoverInfo; private IInformationControlCreator fControlCreator; InformationProvider(IRegion hoverRegion, Object hoverInfo, IInformationControlCreator controlCreator) { fHoverRegion = hoverRegion; fHoverInfo = hoverInfo; fControlCreator = controlCreator; } /* * @see org.eclipse.jface.text.information.IInformationProvider#getSubject(org.eclipse.jface.text.ITextViewer, * int) */ public IRegion getSubject(ITextViewer textViewer, int invocationOffset) { return fHoverRegion; } /* * @see * org.eclipse.jface.text.information.IInformationProvider#getInformation(org.eclipse.jface.text.ITextViewer, * org.eclipse.jface.text.IRegion) */ public String getInformation(ITextViewer textViewer, IRegion subject) { return fHoverInfo.toString(); } /* * @seeorg.eclipse.jface.text.information.IInformationProviderExtension#getInformation2(org.eclipse.jface.text. * ITextViewer, org.eclipse.jface.text.IRegion) * @since 3.2 */ public Object getInformation2(ITextViewer textViewer, IRegion subject) { return fHoverInfo; } /* * @see * org.eclipse.jface.text.information.IInformationProviderExtension2#getInformationPresenterControlCreator() */ public IInformationControlCreator getInformationPresenterControlCreator() { return fControlCreator; } } }