| /*=============================================================================# |
| # Copyright (c) 2005, 2021 Stephan Wahlbrink and others. |
| # |
| # This program and the accompanying materials are made available under the |
| # terms of the Eclipse Public License 2.0 which is available at |
| # https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0 |
| # which is available at https://www.apache.org/licenses/LICENSE-2.0. |
| # |
| # SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 |
| # |
| # Contributors: |
| # Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation |
| #=============================================================================*/ |
| |
| package org.eclipse.statet.ltk.ui.sourceediting; |
| |
| import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.eclipse.core.commands.IHandler2; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.content.IContentType; |
| import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener; |
| import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent; |
| import org.eclipse.jface.action.IMenuManager; |
| import org.eclipse.jface.action.MenuManager; |
| import org.eclipse.jface.resource.ImageDescriptor; |
| import org.eclipse.jface.resource.JFaceResources; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.IInformationControl; |
| import org.eclipse.jface.text.IInformationControlCreator; |
| import org.eclipse.jface.text.IRegion; |
| import org.eclipse.jface.text.ISynchronizable; |
| import org.eclipse.jface.text.ITextSelection; |
| import org.eclipse.jface.text.ITextViewerExtension5; |
| import org.eclipse.jface.text.link.ILinkedModeListener; |
| import org.eclipse.jface.text.link.LinkedModeModel; |
| import org.eclipse.jface.text.source.IAnnotationModel; |
| import org.eclipse.jface.text.source.ISourceViewer; |
| import org.eclipse.jface.text.source.IVerticalRuler; |
| import org.eclipse.jface.text.source.SourceViewer; |
| import org.eclipse.jface.text.source.projection.IProjectionListener; |
| import org.eclipse.jface.text.source.projection.ProjectionSupport; |
| import org.eclipse.jface.text.source.projection.ProjectionViewer; |
| import org.eclipse.jface.viewers.IPostSelectionProvider; |
| import org.eclipse.jface.viewers.ISelection; |
| import org.eclipse.jface.viewers.IStructuredSelection; |
| import org.eclipse.swt.custom.StyledText; |
| 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.IEditorInput; |
| import org.eclipse.ui.IPageLayout; |
| import org.eclipse.ui.IWorkbenchPart; |
| import org.eclipse.ui.contexts.IContextService; |
| import org.eclipse.ui.editors.text.TextEditor; |
| import org.eclipse.ui.handlers.IHandlerService; |
| import org.eclipse.ui.part.IShowInSource; |
| import org.eclipse.ui.part.IShowInTargetList; |
| import org.eclipse.ui.part.ShowInContext; |
| import org.eclipse.ui.services.IServiceLocator; |
| import org.eclipse.ui.texteditor.IDocumentProvider; |
| import org.eclipse.ui.texteditor.ITextEditorActionConstants; |
| import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds; |
| import org.eclipse.ui.texteditor.IUpdate; |
| import org.eclipse.ui.texteditor.SourceViewerDecorationSupport; |
| import org.eclipse.ui.texteditor.templates.ITemplatesPage; |
| import org.eclipse.ui.views.contentoutline.IContentOutlinePage; |
| |
| import org.eclipse.statet.jcommons.collections.CopyOnWriteIdentityListSet; |
| import org.eclipse.statet.jcommons.collections.ImCollections; |
| import org.eclipse.statet.jcommons.collections.ImList; |
| import org.eclipse.statet.jcommons.lang.NonNull; |
| import org.eclipse.statet.jcommons.lang.NonNullByDefault; |
| import org.eclipse.statet.jcommons.lang.Nullable; |
| import org.eclipse.statet.jcommons.text.core.TextRegion; |
| |
| import org.eclipse.statet.ecommons.preferences.PreferencesUtil; |
| import org.eclipse.statet.ecommons.preferences.SettingsChangeNotifier; |
| import org.eclipse.statet.ecommons.preferences.core.Preference; |
| import org.eclipse.statet.ecommons.preferences.core.util.PreferenceUtils; |
| import org.eclipse.statet.ecommons.text.ICharPairMatcher; |
| import org.eclipse.statet.ecommons.text.core.JFaceTextRegion; |
| import org.eclipse.statet.ecommons.text.core.sections.DocContentSections; |
| import org.eclipse.statet.ecommons.text.ui.TextHandlerUtil; |
| import org.eclipse.statet.ecommons.ui.ISettingsChangedHandler; |
| import org.eclipse.statet.ecommons.ui.util.UIAccess; |
| |
| import org.eclipse.statet.internal.ltk.ui.EditingMessages; |
| import org.eclipse.statet.ltk.ast.core.AstNode; |
| import org.eclipse.statet.ltk.core.Ltk; |
| import org.eclipse.statet.ltk.model.core.DocumentModelProvider; |
| import org.eclipse.statet.ltk.model.core.ModelManager; |
| import org.eclipse.statet.ltk.model.core.ModelTypeDescriptor; |
| import org.eclipse.statet.ltk.model.core.element.LtkModelElement; |
| import org.eclipse.statet.ltk.model.core.element.SourceStructElement; |
| import org.eclipse.statet.ltk.model.core.element.SourceUnit; |
| import org.eclipse.statet.ltk.model.core.element.SourceUnitModelInfo; |
| import org.eclipse.statet.ltk.ui.ElementInfoController; |
| import org.eclipse.statet.ltk.ui.LTKInputData; |
| import org.eclipse.statet.ltk.ui.LtkActions; |
| import org.eclipse.statet.ltk.ui.ModelElementInputProvider; |
| import org.eclipse.statet.ltk.ui.PostSelectionCancelExtension; |
| import org.eclipse.statet.ltk.ui.PostSelectionWithElementInfoController; |
| import org.eclipse.statet.ltk.ui.PostSelectionWithElementInfoController.IgnoreActivation; |
| import org.eclipse.statet.ltk.ui.SelectionWithElementInfoListener; |
| import org.eclipse.statet.ltk.ui.sourceediting.actions.DeleteNextWordHandler; |
| import org.eclipse.statet.ltk.ui.sourceediting.actions.DeletePreviousWordHandler; |
| import org.eclipse.statet.ltk.ui.sourceediting.actions.GotoMatchingBracketHandler; |
| import org.eclipse.statet.ltk.ui.sourceediting.actions.GotoNextWordHandler; |
| import org.eclipse.statet.ltk.ui.sourceediting.actions.GotoPreviousWordHandler; |
| import org.eclipse.statet.ltk.ui.sourceediting.actions.SelectNextWordHandler; |
| import org.eclipse.statet.ltk.ui.sourceediting.actions.SelectPreviousWordHandler; |
| import org.eclipse.statet.ltk.ui.sourceediting.actions.SpecificContentAssistHandler; |
| import org.eclipse.statet.ltk.ui.sourceediting.actions.ToggleCommentHandler; |
| import org.eclipse.statet.ltk.ui.util.LTKSelectionUtils; |
| |
| |
| /** |
| * Abstract LTK based source editor. |
| */ |
| @NonNullByDefault |
| public abstract class SourceEditor1 extends TextEditor implements SourceEditor, |
| SettingsChangeNotifier.ChangeListener, IPreferenceChangeListener, |
| IShowInSource, IShowInTargetList { |
| |
| |
| protected static final ImList<String> ACTION_SET_CONTEXT_IDS= ImCollections.newIdentityList( |
| "org.eclipse.statet.ltk.contexts.EditSource1MenuSet", //$NON-NLS-1$ |
| "org.eclipse.ui.edit.text.actionSet.presentation" ); //$NON-NLS-1$ |
| |
| private static final ImList<String> CONTEXT_IDS= ImCollections.addElement( |
| ACTION_SET_CONTEXT_IDS, |
| "org.eclipse.statet.workbench.contexts.TextEditor" ); //$NON-NLS-1$ |
| |
| |
| /*- Static utility methods --------------------------------------------------*/ |
| |
| /** |
| * Returns the lock object for the given annotation model. |
| * |
| * @param annotationModel the annotation model |
| * @return the annotation model's lock object |
| */ |
| protected static final Object getLockObject(final IAnnotationModel annotationModel) { |
| if (annotationModel instanceof ISynchronizable) { |
| final Object lock= ((ISynchronizable)annotationModel).getLockObject(); |
| if (lock != null) { |
| return lock; |
| } |
| } |
| return annotationModel; |
| } |
| |
| private TextRegion expand(final TextRegion region, final @Nullable TextRegion regionOpt) { |
| return (regionOpt != null) ? region.expansion(regionOpt) : region; |
| } |
| |
| |
| /*- Inner classes -----------------------------------------------------------*/ |
| |
| protected class PostSelectionEditorCancel extends PostSelectionCancelExtension { |
| |
| public PostSelectionEditorCancel() { |
| } |
| |
| @Override |
| public void init() { |
| final ISourceViewer viewer= getSourceViewer(); |
| if (viewer != null) { |
| viewer.addTextInputListener(this); |
| viewer.getDocument().addDocumentListener(this); |
| } |
| } |
| |
| @Override |
| public void dispose() { |
| final ISourceViewer viewer= getSourceViewer(); |
| if (viewer != null) { |
| viewer.removeTextInputListener(this); |
| final IDocument document= viewer.getDocument(); |
| if (document != null) { |
| document.removeDocumentListener(this); |
| } |
| } |
| } |
| } |
| |
| private class EffectSynchonizer implements TextEditToolSynchronizer, ILinkedModeListener { |
| |
| private EffectSynchonizer() { |
| } |
| |
| @Override |
| public void install(final LinkedModeModel model) { |
| SourceEditor1.this.effectSynchonizerCounter++; |
| |
| final var markOccurrencesProvider= SourceEditor1.this.markOccurrencesProvider; |
| if (markOccurrencesProvider != null) { |
| markOccurrencesProvider.uninstall(); |
| } |
| |
| model.addLinkingListener(this); |
| } |
| |
| @Override |
| public void left(final LinkedModeModel model, final int flags) { |
| SourceEditor1.this.effectSynchonizerCounter--; |
| |
| updateMarkOccurrencesEnablement(); |
| } |
| |
| @Override |
| public void resume(final LinkedModeModel model, final int flags) { |
| } |
| |
| @Override |
| public void suspend(final LinkedModeModel model) { |
| } |
| |
| } |
| |
| |
| /*- Fields -----------------------------------------------------------------*/ |
| |
| private final IContentType contentType; |
| private final ModelTypeDescriptor modelType; |
| |
| private SourceEditorViewerConfigurator configurator; |
| private boolean lazySetup; |
| private @Nullable SourceUnit sourceUnit; |
| private ElementInfoController modelProvider; |
| private @Nullable PostSelectionWithElementInfoController modelPostSelection; |
| protected volatile Point currentSelection; |
| |
| /** The outline page of this editor */ |
| private @Nullable SourceEditor1OutlinePage outlinePage; |
| |
| /** The templates page of this editor */ |
| private @Nullable ITemplatesPage templatesPage; |
| |
| private StructureSelectionHistory selectionHistory; |
| private @Nullable Preference<Boolean> foldingEnablement; |
| private ProjectionSupport foldingSupport; |
| private @Nullable SourceEditorAddon foldingProvider; |
| private @Nullable FoldingActionGroup foldingActionGroup; |
| private @Nullable Preference<Boolean> markOccurrencesEnablement; |
| private @Nullable SourceEditorAddon markOccurrencesProvider; |
| |
| private @Nullable EffectSynchonizer effectSynchronizer; |
| private int effectSynchonizerCounter; |
| |
| private final CopyOnWriteIdentityListSet<IUpdate> contentUpdateables= new CopyOnWriteIdentityListSet<>(); |
| private final CopyOnWriteIdentityListSet<IHandler2> stateUpdateables= new CopyOnWriteIdentityListSet<>(); |
| |
| private boolean inputChange; |
| private int inputUpdate= Integer.MAX_VALUE; |
| |
| private @Nullable ImageDescriptor imageDescriptor; |
| |
| |
| /*- Contructors ------------------------------------------------------------*/ |
| |
| public SourceEditor1(final IContentType contentType) { |
| super(); |
| this.contentType= contentType; |
| this.modelType= Ltk.getExtContentTypeManager().getModelTypeForContentType(contentType.getId()); |
| } |
| |
| |
| /*- Methods ----------------------------------------------------------------*/ |
| |
| @Override |
| public IContentType getContentType() { |
| return this.contentType; |
| } |
| |
| /** |
| * Returns the model type of source units of the editor. |
| * The value must not change for an editor instance and all source units |
| * in the editor must be of the same type. |
| * |
| * @return id of the model type |
| */ |
| public String getModelTypeId() { |
| return this.modelType.getId(); |
| } |
| |
| @Override |
| protected void initializeEditor() { |
| this.configurator= createConfiguration(); |
| super.initializeEditor(); |
| setCompatibilityMode(false); |
| final SourceEditorViewerConfiguration configuration= this.configurator.getSourceViewerConfiguration(); |
| setPreferenceStore(configuration.getPreferences()); |
| setSourceViewerConfiguration(configuration); |
| if (configuration.isSmartInsertSupported()) { |
| configureInsertMode(SMART_INSERT, true); |
| } |
| |
| PreferencesUtil.getSettingsChangeNotifier().addChangeListener(this); |
| } |
| |
| protected abstract SourceEditorViewerConfigurator createConfiguration(); |
| |
| protected @Nullable SourceEditorViewerConfigurator createInfoConfigurator() { |
| return null; |
| } |
| |
| |
| protected void enableStructuralFeatures(final ModelManager modelManager, |
| final @Nullable Preference<Boolean> codeFoldingEnablement, |
| final @Nullable Preference<Boolean> markOccurrencesEnablement) { |
| this.modelProvider= new ElementInfoController(modelManager, Ltk.EDITOR_CONTEXT); |
| this.foldingEnablement= codeFoldingEnablement; |
| this.markOccurrencesEnablement= markOccurrencesEnablement; |
| } |
| |
| /** |
| * Overwrites the default title image (editor icon) during the initialization of the editor |
| * input. |
| * |
| * The image is created and disposed automatically. |
| * |
| * For example, it can be used to overwrite the default image using the image descriptor of |
| * the editor input in {@link #setDocumentProvider(IEditorInput)} |
| * |
| * @param descriptor the image description of the icon or <code>null</code> |
| */ |
| protected void overwriteTitleImage(final @Nullable ImageDescriptor descriptor) { |
| final var oldDescriptor= this.imageDescriptor; |
| if (oldDescriptor == descriptor) { |
| return; |
| } |
| if (oldDescriptor != null) { |
| JFaceResources.getResources().destroyImage(oldDescriptor); |
| } |
| this.imageDescriptor= descriptor; |
| if (descriptor != null) { |
| super.setTitleImage(JFaceResources.getResources().createImage(descriptor)); |
| } |
| } |
| |
| @Override |
| protected void setTitleImage(final @Nullable Image titleImage) { |
| if (this.imageDescriptor == null) { |
| super.setTitleImage(titleImage); |
| } |
| } |
| |
| protected Collection<String> getContextIds() { |
| return CONTEXT_IDS; |
| } |
| |
| @Override |
| protected void initializeKeyBindingScopes() { |
| final Collection<String> ids= getContextIds(); |
| setKeyBindingScopes(ids.toArray(new String[ids.size()])); |
| } |
| |
| @Override |
| protected @NonNull String[] collectContextMenuPreferencePages() { |
| final List<String> list= new ArrayList<>(); |
| collectContextMenuPreferencePages(list); |
| list.addAll(ImCollections.newList(super.collectContextMenuPreferencePages())); |
| return list.toArray(new @NonNull String[list.size()]); |
| } |
| |
| protected void collectContextMenuPreferencePages(final List<String> pageIds) { |
| } |
| |
| |
| @Override |
| protected void doSetInput(final @Nullable IEditorInput input) throws CoreException { |
| if (this.modelProvider != null && this.sourceUnit != null) { |
| this.modelProvider.setInput(null); |
| } |
| |
| // project has changed |
| final ISourceViewer sourceViewer= getSourceViewer(); |
| if (sourceViewer != null) { |
| this.configurator.unconfigureTarget(); |
| } |
| else { |
| this.lazySetup= true; |
| } |
| |
| this.inputChange= true; |
| this.inputUpdate= 1; |
| super.doSetInput(input); |
| // setup in |
| // 1) setDocumentProvider -> setupConfiguration(..., input) |
| // 2) handleInsertModeChanged -> setupConfiguration(..., input, SourceViewer) |
| this.inputChange= false; |
| this.inputUpdate= Integer.MAX_VALUE; |
| |
| initSmartInsert(); |
| |
| if (input != null) { |
| final var outlinePage= this.outlinePage; |
| if (outlinePage != null) { |
| updateOutlinePageInput(outlinePage); |
| } |
| } |
| } |
| |
| private void initSmartInsert() { |
| final SourceEditorViewerConfiguration config= this.configurator.getSourceViewerConfiguration(); |
| if (config.isSmartInsertSupported()) { |
| if (config.isSmartInsertByDefault()) { |
| setInsertMode(SMART_INSERT); |
| } |
| else { |
| setInsertMode(INSERT); |
| } |
| } |
| } |
| |
| @Override |
| protected void setPartName(final String partName) { |
| super.setPartName(partName); |
| |
| // see doSetInput |
| if (this.inputChange) { |
| if (this.inputUpdate != 1) { |
| return; |
| } |
| this.inputUpdate= 2; |
| final IEditorInput input= getEditorInput(); |
| setupConfiguration(input); |
| } |
| } |
| |
| @Override |
| protected void handleInsertModeChanged() { |
| // see doSetInput |
| if (this.inputChange && !this.lazySetup) { |
| if (this.inputUpdate != 2) { |
| return; |
| } |
| this.inputUpdate= 3; |
| final IEditorInput input= getEditorInput(); |
| final ISourceViewer sourceViewer= getSourceViewer(); |
| if (input != null && sourceViewer != null) { |
| setupConfiguration(input, sourceViewer); |
| this.configurator.configureTarget(); |
| } |
| this.inputChange= false; |
| } |
| |
| super.handleInsertModeChanged(); |
| } |
| |
| /** |
| * Subclasses should setup the SourceViewerConfiguration. |
| */ |
| protected void setupConfiguration(final @Nullable IEditorInput newInput) { |
| final IDocumentProvider documentProvider= getDocumentProvider(); |
| if (documentProvider instanceof DocumentModelProvider) { |
| this.sourceUnit= (newInput != null) ? |
| ((DocumentModelProvider)documentProvider).getWorkingCopy(newInput) : |
| null; |
| if (this.modelProvider != null) { |
| this.modelProvider.setInput(this.sourceUnit); |
| } |
| } |
| } |
| |
| /** |
| * Subclasses should setup the SourceViewerConfiguration. |
| */ |
| protected void setupConfiguration(final IEditorInput newInput, final ISourceViewer sourceViewer) { |
| updateStateDependentActions(); |
| } |
| |
| @Override |
| public @Nullable SourceUnit getSourceUnit() { |
| return this.sourceUnit; |
| } |
| |
| |
| @Override |
| public SourceViewer getViewer() { |
| return (SourceViewer)super.getSourceViewer(); |
| } |
| |
| @Override |
| public DocContentSections getDocumentContentInfo() { |
| return this.configurator.getDocumentContentInfo(); |
| } |
| |
| @Override |
| public IWorkbenchPart getWorkbenchPart() { |
| return this; |
| } |
| |
| @Override |
| public IServiceLocator getServiceLocator() { |
| return getSite(); |
| } |
| |
| @Override |
| public boolean isEditable(final boolean validate) { |
| if (validate) { |
| return SourceEditor1.this.validateEditorInputState(); |
| } |
| return SourceEditor1.this.isEditorInputModifiable(); |
| } |
| |
| public ModelElementInputProvider getModelInputProvider() { |
| return this.modelProvider; |
| } |
| |
| public void addPostSelectionWithElementInfoListener(final SelectionWithElementInfoListener listener) { |
| final var modelPostSelection= this.modelPostSelection; |
| if (modelPostSelection != null) { |
| modelPostSelection.addListener(listener); |
| } |
| } |
| |
| public void removePostSelectionWithElementInfoListener(final SelectionWithElementInfoListener listener) { |
| final var modelPostSelection= this.modelPostSelection; |
| if (modelPostSelection != null) { |
| modelPostSelection.removeListener(listener); |
| } |
| } |
| |
| |
| @Override |
| public void createPartControl(final Composite parent) { |
| super.createPartControl(parent); |
| |
| if (this.modelProvider != null) { |
| final var modelPostSelection= new PostSelectionWithElementInfoController( |
| this.modelProvider, (IPostSelectionProvider)getSelectionProvider(), |
| new PostSelectionEditorCancel() ); |
| modelPostSelection.addListener(new SelectionWithElementInfoListener() { |
| @Override |
| public void inputChanged() { |
| } |
| @Override |
| public void stateChanged(final LTKInputData state) { |
| final TextRegion toHighlight= getRangeToHighlight(state); |
| if (toHighlight != null) { |
| setHighlightRange(toHighlight.getStartOffset(), toHighlight.getLength(), false); |
| } |
| else { |
| resetHighlightRange(); |
| } |
| } |
| }); |
| this.modelPostSelection= modelPostSelection; |
| } |
| if (this.foldingEnablement != null) { |
| final ProjectionViewer viewer= (ProjectionViewer)getSourceViewer(); |
| |
| this.foldingSupport= new ProjectionSupport(viewer, getAnnotationAccess(), getSharedColors()); |
| final SourceEditorViewerConfigurator config= createInfoConfigurator(); |
| if (config != null) { |
| final IInformationControlCreator presentationCreator= new IInformationControlCreator() { |
| @Override |
| public IInformationControl createInformationControl(final Shell parent) { |
| return new SourceViewerInformationControl(parent, |
| createInfoConfigurator(), getOrientation() ); |
| } |
| }; |
| this.foldingSupport.setHoverControlCreator(new IInformationControlCreator() { |
| @Override |
| public IInformationControl createInformationControl(final Shell parent) { |
| return new SourceViewerInformationControl(parent, |
| createInfoConfigurator(), getOrientation(), presentationCreator ); |
| } |
| }); |
| this.foldingSupport.setInformationPresenterControlCreator(presentationCreator); |
| } |
| this.foldingSupport.install(); |
| viewer.addProjectionListener(new IProjectionListener() { |
| @Override |
| public void projectionEnabled() { |
| installFoldingProvider(); |
| } |
| @Override |
| public void projectionDisabled() { |
| uninstallFoldingProvider(); |
| } |
| }); |
| PreferenceUtils.getInstancePrefs().addPreferenceNodeListener( |
| this.foldingEnablement.getQualifier(), this); |
| updateFoldingEnablement(); |
| } |
| if (this.markOccurrencesEnablement != null) { |
| PreferenceUtils.getInstancePrefs().addPreferenceNodeListener( |
| this.markOccurrencesEnablement.getQualifier(), this); |
| updateMarkOccurrencesEnablement(); |
| } |
| |
| if (this.lazySetup) { |
| this.lazySetup= false; |
| setupConfiguration(getEditorInput(), getSourceViewer()); |
| this.configurator.setTarget(this); |
| } |
| |
| { final IContextService contextService= nonNullAssert( |
| getServiceLocator().getService(IContextService.class) ); |
| for (final String id : getContextIds()) { |
| contextService.activateContext(id); |
| } |
| } |
| } |
| |
| @Override |
| protected ISourceViewer createSourceViewer(final Composite parent, final IVerticalRuler ruler, final int styles) { |
| this.fAnnotationAccess= getAnnotationAccess(); |
| this.fOverviewRuler= createOverviewRuler(getSharedColors()); |
| |
| final ISourceViewer viewer= new SourceEditorViewer(parent, |
| ruler, getOverviewRuler(), isOverviewRulerVisible(), styles, |
| getSourceViewerFlags() ); |
| // ensure decoration support has been created and configured. |
| getSourceViewerDecorationSupport(viewer); |
| |
| return viewer; |
| } |
| |
| protected int getSourceViewerFlags() { |
| return 0; |
| } |
| |
| protected @Nullable IRegion getRangeToReveal(final SourceUnitModelInfo modelInfo, |
| final SourceStructElement<?, ?> element) { |
| return null; |
| } |
| |
| protected @Nullable TextRegion getRangeToHighlight(final LTKInputData state) { |
| final SourceUnitModelInfo info= state.getInputInfo(); |
| if (info == null) { |
| return null; |
| } |
| |
| final TextRegion region= getRangeToHighlight(info, state.getModelSelection()); |
| if (region != null) { |
| return region; |
| } |
| |
| final AstNode root= info.getAst().getRoot(); |
| TRY_AST: if (root != null) { |
| final ITextSelection selection= (ITextSelection)state.getSelection(); |
| final int n= root.getChildCount(); |
| for (int i= 0; i < n; i++) { |
| final AstNode child= root.getChild(i); |
| if (selection.getOffset() >= child.getStartOffset()) { |
| if (selection.getOffset()+selection.getLength() <= child.getEndOffset()) { |
| return child; |
| } |
| } |
| else { |
| break TRY_AST; |
| } |
| } |
| } |
| return null; |
| } |
| |
| protected @Nullable TextRegion getRangeToHighlight(final SourceUnitModelInfo info, |
| @Nullable SourceStructElement element) { |
| while (element != null) { |
| switch (element.getElementType() & LtkModelElement.MASK_C1) { |
| case LtkModelElement.C1_CLASS: |
| case LtkModelElement.C1_METHOD: |
| return expand(element.getSourceRange(), element.getDocumentationRange()); |
| case LtkModelElement.C1_SOURCE: |
| if ((element.getElementType() & LtkModelElement.MASK_C2) == LtkModelElement.C2_SOURCE_CHUNK) { |
| return expand(element.getSourceRange(), element.getDocumentationRange()); |
| } |
| return null; |
| case LtkModelElement.C1_VARIABLE: |
| if ((element.getSourceParent().getElementType() & LtkModelElement.MASK_C2) == LtkModelElement.C2_SOURCE_FILE) { |
| return expand(element.getSourceRange(), element.getDocumentationRange()); |
| } |
| //$FALL-THROUGH$ |
| default: |
| element= element.getSourceParent(); |
| continue; |
| } |
| } |
| return null; |
| } |
| |
| |
| protected @Nullable SourceEditorAddon createCodeFoldingProvider() { |
| return null; |
| } |
| |
| private void installFoldingProvider() { |
| uninstallFoldingProvider(); |
| final var foldingProvider= createCodeFoldingProvider(); |
| if (foldingProvider != null) { |
| foldingProvider.install(this); |
| this.foldingProvider= foldingProvider; |
| } |
| } |
| |
| private void uninstallFoldingProvider() { |
| final var foldingProvider= this.foldingProvider; |
| if (foldingProvider != null) { |
| this.foldingProvider= null; |
| foldingProvider.uninstall(); |
| } |
| } |
| |
| private void updateFoldingEnablement() { |
| if (this.foldingEnablement != null) { |
| UIAccess.getDisplay().asyncExec(new Runnable() { |
| @Override |
| public void run() { |
| final Boolean enable= PreferenceUtils.getInstancePrefs().getPreferenceValue( |
| SourceEditor1.this.foldingEnablement ); |
| final ProjectionViewer viewer= (ProjectionViewer)getSourceViewer(); |
| if (enable != null && UIAccess.isOkToUse(viewer)) { |
| if (enable != viewer.isProjectionMode()) { |
| viewer.doOperation(ProjectionViewer.TOGGLE); |
| } |
| } |
| } |
| }); |
| } |
| } |
| |
| |
| protected @Nullable SourceEditorAddon createMarkOccurrencesProvider() { |
| return null; |
| } |
| |
| private void uninstallMarkOccurrencesProvider() { |
| final var markOccurrencesProvider= this.markOccurrencesProvider; |
| if (markOccurrencesProvider != null) { |
| this.markOccurrencesProvider= null; |
| markOccurrencesProvider.uninstall(); |
| } |
| } |
| |
| private void updateMarkOccurrencesEnablement() { |
| if (this.markOccurrencesEnablement != null) { |
| UIAccess.getDisplay().asyncExec(new Runnable() { |
| @Override |
| public void run() { |
| final Boolean enable= PreferenceUtils.getInstancePrefs().getPreferenceValue( |
| SourceEditor1.this.markOccurrencesEnablement ); |
| if (enable) { |
| var provider= SourceEditor1.this.markOccurrencesProvider; |
| if (provider == null) { |
| provider= createMarkOccurrencesProvider(); |
| SourceEditor1.this.markOccurrencesProvider= provider; |
| } |
| if (provider != null && SourceEditor1.this.effectSynchonizerCounter == 0) { |
| provider.install(SourceEditor1.this); |
| } |
| } |
| else { |
| uninstallMarkOccurrencesProvider(); |
| } |
| } |
| }); |
| } |
| } |
| |
| |
| @Override |
| protected void configureSourceViewerDecorationSupport(final SourceViewerDecorationSupport support) { |
| super.configureSourceViewerDecorationSupport(support); |
| this.configurator.configureSourceViewerDecorationSupport(support); |
| } |
| |
| @Override |
| protected void createActions() { |
| super.createActions(); |
| final IHandlerService handlerService= nonNullAssert( |
| getServiceLocator().getService(IHandlerService.class) ); |
| final StyledText textWidget= getViewer().getTextWidget(); |
| |
| { final IHandler2 handler= new GotoNextWordHandler(this); |
| setAction(ITextEditorActionDefinitionIds.WORD_NEXT, null); |
| TextHandlerUtil.disable(textWidget, ITextEditorActionDefinitionIds.WORD_NEXT); |
| handlerService.activateHandler(ITextEditorActionDefinitionIds.WORD_NEXT, handler); |
| } |
| { final IHandler2 handler= new GotoPreviousWordHandler(this); |
| setAction(ITextEditorActionDefinitionIds.WORD_PREVIOUS, null); |
| TextHandlerUtil.disable(textWidget, ITextEditorActionDefinitionIds.WORD_NEXT); |
| handlerService.activateHandler(ITextEditorActionDefinitionIds.WORD_PREVIOUS, handler); |
| } |
| { final IHandler2 handler= new SelectNextWordHandler(this); |
| setAction(ITextEditorActionDefinitionIds.SELECT_WORD_NEXT, null); |
| TextHandlerUtil.disable(textWidget, ITextEditorActionDefinitionIds.SELECT_WORD_NEXT); |
| handlerService.activateHandler(ITextEditorActionDefinitionIds.SELECT_WORD_NEXT, handler); |
| } |
| { final IHandler2 handler= new SelectPreviousWordHandler(this); |
| setAction(ITextEditorActionDefinitionIds.SELECT_WORD_PREVIOUS, null); |
| TextHandlerUtil.disable(textWidget, ITextEditorActionDefinitionIds.SELECT_WORD_PREVIOUS); |
| handlerService.activateHandler(ITextEditorActionDefinitionIds.SELECT_WORD_PREVIOUS, handler); |
| } |
| { final IHandler2 handler= new DeleteNextWordHandler(this); |
| setAction(ITextEditorActionDefinitionIds.DELETE_NEXT_WORD, null); |
| TextHandlerUtil.disable(textWidget, ITextEditorActionDefinitionIds.DELETE_NEXT_WORD); |
| handlerService.activateHandler(ITextEditorActionDefinitionIds.DELETE_NEXT_WORD, handler); |
| markAsStateDependentHandler(handler, true); |
| } |
| { final IHandler2 handler= new DeletePreviousWordHandler(this); |
| setAction(ITextEditorActionDefinitionIds.DELETE_PREVIOUS_WORD, null); |
| TextHandlerUtil.disable(textWidget, ITextEditorActionDefinitionIds.DELETE_PREVIOUS_WORD); |
| handlerService.activateHandler(ITextEditorActionDefinitionIds.DELETE_PREVIOUS_WORD, handler); |
| markAsStateDependentHandler(handler, true); |
| } |
| |
| final ICharPairMatcher matcher= this.configurator.getSourceViewerConfiguration().getPairMatcher(); |
| if (matcher != null) { |
| handlerService.activateHandler(LtkActions.GOTO_MATCHING_BRACKET_COMMAND_ID, |
| new GotoMatchingBracketHandler(matcher, this)); |
| } |
| { final IHandler2 handler= new SpecificContentAssistHandler(this, |
| this.configurator.getSourceViewerConfiguration().getContentAssist() ); |
| handlerService.activateHandler(LtkActions.SPECIFIC_CONTENT_ASSIST_COMMAND_ID, handler); |
| } |
| |
| { final IHandler2 handler= createToggleCommentHandler(); |
| if (handler != null) { |
| handlerService.activateHandler(LtkActions.TOGGLE_COMMENT, handler); |
| } |
| } |
| { final IHandler2 handler= createCorrectIndentHandler(); |
| if (handler != null) { |
| handlerService.activateHandler(LtkActions.CORRECT_INDENT_COMMAND_ID, handler); |
| } |
| } |
| |
| if (this.foldingEnablement != null) { |
| this.foldingActionGroup= createFoldingActionGroup(); |
| } |
| if (this.modelProvider != null) { |
| this.selectionHistory= new StructureSelectionHistory(this); |
| handlerService.activateHandler(LtkActions.SELECT_ENCLOSING_COMMAND_ID, |
| new StructureSelectHandler.Enclosing(this, this.selectionHistory)); |
| handlerService.activateHandler(LtkActions.SELECT_PREVIOUS_COMMAND_ID, |
| new StructureSelectHandler.Previous(this, this.selectionHistory)); |
| handlerService.activateHandler(LtkActions.SELECT_NEXT_COMMAND_ID, |
| new StructureSelectHandler.Next(this, this.selectionHistory)); |
| final StructureSelectionHistoryBackHandler backHandler= new StructureSelectionHistoryBackHandler(this, this.selectionHistory); |
| handlerService.activateHandler(LtkActions.SELECT_LAST_COMMAND_ID, backHandler); |
| this.selectionHistory.addUpdateListener(backHandler); |
| } |
| |
| //WorkbenchHelp.setHelp(action, IJavaHelpContextIds.TOGGLE_COMMENT_ACTION); |
| } |
| |
| protected FoldingActionGroup createFoldingActionGroup() { |
| return new FoldingActionGroup(this, (ProjectionViewer)getSourceViewer()); |
| } |
| |
| protected @Nullable IHandler2 createToggleCommentHandler() { |
| final IHandler2 commentHandler= new ToggleCommentHandler(this); |
| markAsStateDependentHandler(commentHandler, true); |
| return commentHandler; |
| } |
| |
| protected @Nullable IHandler2 createCorrectIndentHandler() { |
| return null; |
| } |
| |
| protected void markAsContentDependentHandler(final IUpdate handler, final boolean mark) { |
| if (mark) { |
| this.contentUpdateables.add(handler); |
| } |
| else { |
| this.contentUpdateables.remove(handler); |
| } |
| } |
| |
| protected void markAsStateDependentHandler(final IHandler2 handler, final boolean mark) { |
| if (mark) { |
| this.stateUpdateables.add(handler); |
| } |
| else { |
| this.stateUpdateables.remove(handler); |
| } |
| } |
| |
| @Override |
| protected void updateContentDependentActions() { |
| super.updateContentDependentActions(); |
| for (final IUpdate handler : this.contentUpdateables) { |
| handler.update(); |
| } |
| } |
| |
| @Override |
| protected void updateStateDependentActions() { |
| super.updateStateDependentActions(); |
| for (final IHandler2 handler : this.stateUpdateables) { |
| handler.setEnabled(this); |
| } |
| } |
| |
| @Override |
| protected void rulerContextMenuAboutToShow(final IMenuManager menu) { |
| super.rulerContextMenuAboutToShow(menu); |
| |
| final var foldingActionGroup= this.foldingActionGroup; |
| if (foldingActionGroup != null) { |
| final IMenuManager foldingMenu= new MenuManager(EditingMessages.CodeFolding_label, "projection"); //$NON-NLS-1$ |
| menu.appendToGroup(ITextEditorActionConstants.GROUP_RULERS, foldingMenu); |
| foldingActionGroup.fillMenu(foldingMenu); |
| } |
| } |
| |
| |
| @Override |
| public TextEditToolSynchronizer getTextEditToolSynchronizer() { |
| var effectSynchronizer= this.effectSynchronizer; |
| if (effectSynchronizer == null) { |
| effectSynchronizer= new EffectSynchonizer(); |
| this.effectSynchronizer= effectSynchronizer; |
| } |
| return effectSynchronizer; |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public <T> @Nullable T getAdapter(final Class<T> adapterType) { |
| if (adapterType == SourceEditor.class) { |
| return (T)this; |
| } |
| if (adapterType == ISourceViewer.class) { |
| return (T)getSourceViewer(); |
| } |
| |
| if (adapterType == IContentType.class) { |
| return (T)this.contentType; |
| } |
| |
| if (adapterType == IContentOutlinePage.class) { |
| var outlinePage= this.outlinePage; |
| if (outlinePage == null) { |
| outlinePage= createOutlinePage(); |
| if (outlinePage != null) { |
| updateOutlinePageInput(outlinePage); |
| } |
| this.outlinePage= outlinePage; |
| } |
| return (T)outlinePage; |
| } |
| if (adapterType == ITemplatesPage.class) { |
| var templatesPage= this.templatesPage; |
| if (templatesPage == null) { |
| templatesPage= createTemplatesPage(); |
| this.templatesPage= templatesPage; |
| } |
| return (T)templatesPage; |
| } |
| if (this.foldingSupport != null) { |
| final Object adapter= this.foldingSupport.getAdapter(getSourceViewer(), adapterType); |
| if (adapter != null) { |
| return (T)adapter; |
| } |
| } |
| |
| return super.getAdapter(adapterType); |
| } |
| |
| |
| @Override |
| public void settingsChanged(final Set<String> groupIds) { |
| final Map<String, Object> options= new HashMap<>(); |
| UIAccess.getDisplay().syncExec(new Runnable() { |
| @Override |
| public void run() { |
| handleSettingsChanged(groupIds, options); |
| } |
| }); |
| } |
| |
| /** |
| * @see ISettingsChangedHandler#handleSettingsChanged(Set, Map) |
| */ |
| protected void handleSettingsChanged(final Set<String> groupIds, final Map<String, Object> options) { |
| if (this.configurator != null) { |
| this.configurator.handleSettingsChanged(groupIds, options); |
| } |
| } |
| |
| @Override |
| public void preferenceChange(final PreferenceChangeEvent event) { |
| if (this.foldingEnablement != null && event.getKey().equals(this.foldingEnablement.getKey())) { |
| updateFoldingEnablement(); |
| } |
| if (this.markOccurrencesEnablement != null && event.getKey().equals(this.markOccurrencesEnablement.getKey())) { |
| updateMarkOccurrencesEnablement(); |
| } |
| } |
| |
| protected void updateIndentSettings() { |
| updateIndentPrefixes(); |
| } |
| |
| |
| @Override |
| protected void handleCursorPositionChanged() { |
| this.currentSelection= getSourceViewer().getSelectedRange(); |
| super.handleCursorPositionChanged(); |
| } |
| |
| |
| protected @Nullable SourceEditor1OutlinePage createOutlinePage() { |
| return null; |
| } |
| |
| protected void updateOutlinePageInput(final SourceEditor1OutlinePage page) { |
| } |
| |
| void handleOutlinePageClosed() { |
| final var outlinePage= this.outlinePage; |
| if (outlinePage != null) { |
| this.outlinePage= null; |
| resetHighlightRange(); |
| } |
| } |
| |
| protected @Nullable ITemplatesPage createTemplatesPage() { |
| return null; |
| } |
| |
| |
| // inject annotation painter workaround |
| @Override |
| protected SourceViewerDecorationSupport getSourceViewerDecorationSupport(final ISourceViewer viewer) { |
| var decorationSupport= this.fSourceViewerDecorationSupport; |
| if (decorationSupport == null) { |
| decorationSupport= new org.eclipse.statet.ltk.ui.sourceediting.SourceViewerDecorationSupport( |
| viewer, getOverviewRuler(), getAnnotationAccess(), getSharedColors() ); |
| configureSourceViewerDecorationSupport(decorationSupport); |
| this.fSourceViewerDecorationSupport= decorationSupport; |
| } |
| return decorationSupport; |
| } |
| |
| @Override |
| public void selectAndReveal(final int start, final int length) { |
| selectAndReveal(start, length, start, length); |
| } |
| |
| @Override |
| protected void selectAndReveal(final int selectionStart, final int selectionLength, |
| final int revealStart, final int revealLength) { |
| final var modelPostSelection= this.modelPostSelection; |
| if (modelPostSelection != null) { |
| modelPostSelection.setUpdateOnSelection(true); |
| try { |
| super.selectAndReveal(selectionStart, selectionLength, revealStart, revealLength); |
| } |
| finally { |
| modelPostSelection.setUpdateOnSelection(false); |
| } |
| } |
| else { |
| super.selectAndReveal(selectionStart, selectionLength, revealStart, revealLength); |
| } |
| } |
| |
| public void setSelection(final ISelection selection, final SelectionWithElementInfoListener listener) { |
| final var modelPostSelection= this.modelPostSelection; |
| if (modelPostSelection != null && listener != null) { |
| final IgnoreActivation activation= modelPostSelection.ignoreNext(listener); |
| doSetSelection(selection); |
| activation.deleteNext(); |
| } |
| else { |
| doSetSelection(selection); |
| } |
| } |
| |
| @Override |
| protected void doSetSelection(final ISelection selection) { |
| if (selection instanceof IStructuredSelection) { |
| final IStructuredSelection structured= (IStructuredSelection)selection; |
| if (!structured.isEmpty()) { |
| final Object first= structured.getFirstElement(); |
| TextRegion region= null; |
| if (first instanceof SourceStructElement) { |
| final var sourceElement= (SourceStructElement<?, ?>)first; |
| region= LTKSelectionUtils.getRegionToSelect(sourceElement); |
| |
| final var sourceUnit= sourceElement.getSourceUnit(); |
| final var modelInfo= sourceUnit.getModelInfo(getModelTypeId(), 0, null); |
| if (modelInfo != null) { |
| final IRegion toReveal= getRangeToReveal(modelInfo, sourceElement); |
| if (toReveal != null) { |
| final SourceViewer viewer= getViewer(); |
| if (viewer instanceof ITextViewerExtension5) { |
| ((ITextViewerExtension5)viewer).exposeModelRange(toReveal); |
| } |
| getViewer().revealRange(toReveal.getOffset(), toReveal.getLength()); |
| } |
| final TextRegion toHighlight= getRangeToHighlight(modelInfo, sourceElement); |
| if (toHighlight != null) { |
| setHighlightRange(toHighlight.getStartOffset(), toHighlight.getLength(), true); |
| } |
| } |
| } |
| if (region == null && first instanceof TextRegion) { |
| region= (TextRegion)first; |
| } |
| else if (region == null && first instanceof IRegion) { |
| region= JFaceTextRegion.toTextRegion((IRegion)first); |
| } |
| |
| if (region != null) { |
| selectAndReveal(region.getStartOffset(), region.getLength()); |
| return; |
| } |
| } |
| } |
| super.doSetSelection(selection); |
| } |
| |
| |
| @Override |
| public void dispose() { |
| if (this.modelProvider != null) { |
| this.modelProvider.setInput(null); |
| this.modelProvider.dispose(); |
| } |
| |
| PreferencesUtil.getSettingsChangeNotifier().removeChangeListener(this); |
| { final var modelPostSelection= this.modelPostSelection; |
| if (modelPostSelection != null) { |
| this.modelPostSelection= null; |
| modelPostSelection.dispose(); |
| } |
| } |
| { final var foldingEnablement= this.foldingEnablement; |
| if (foldingEnablement != null) { |
| PreferenceUtils.getInstancePrefs().removePreferenceNodeListener( |
| foldingEnablement.getQualifier(), this ); |
| uninstallFoldingProvider(); |
| } |
| } |
| { final var markOccurrencesEnablement= this.markOccurrencesEnablement; |
| if (markOccurrencesEnablement != null) { |
| PreferenceUtils.getInstancePrefs().removePreferenceNodeListener( |
| markOccurrencesEnablement.getQualifier(), this ); |
| uninstallMarkOccurrencesProvider(); |
| } |
| } |
| |
| super.dispose(); |
| |
| if (this.imageDescriptor != null) { |
| JFaceResources.getResources().destroyImage(this.imageDescriptor); |
| this.imageDescriptor= null; |
| } |
| |
| this.sourceUnit= null; |
| this.modelProvider= null; |
| } |
| |
| @Override |
| public ShowInContext getShowInContext() { |
| final Point selectionPoint= this.currentSelection; |
| final ISourceViewer sourceViewer= getSourceViewer(); |
| final SourceUnit unit= getSourceUnit(); |
| ISelection selection= null; |
| if (selectionPoint != null && unit != null && sourceViewer != null) { |
| selection= new LTKInputData(unit, getSelectionProvider()); |
| } |
| return new ShowInContext(getEditorInput(), selection); |
| } |
| |
| @Override |
| public @NonNull String[] getShowInTargetIds() { |
| return new @NonNull String[] { |
| IPageLayout.ID_PROJECT_EXPLORER }; |
| } |
| |
| } |