| /*=============================================================================# |
| # Copyright (c) 2007, 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 static org.eclipse.statet.ecommons.ui.actions.UIActions.ADDITIONS_GROUP_ID; |
| |
| import org.eclipse.core.runtime.IAdaptable; |
| import org.eclipse.core.runtime.content.IContentType; |
| import org.eclipse.jface.action.Action; |
| import org.eclipse.jface.action.IMenuManager; |
| import org.eclipse.jface.action.Separator; |
| import org.eclipse.jface.dialogs.IDialogSettings; |
| import org.eclipse.jface.viewers.IPostSelectionProvider; |
| import org.eclipse.jface.viewers.ISelection; |
| import org.eclipse.jface.viewers.IStructuredSelection; |
| import org.eclipse.jface.viewers.StructuredSelection; |
| import org.eclipse.jface.viewers.TreeViewer; |
| import org.eclipse.swt.custom.BusyIndicator; |
| import org.eclipse.swt.widgets.Composite; |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.ui.IActionBars; |
| import org.eclipse.ui.IPageLayout; |
| import org.eclipse.ui.contexts.IContextService; |
| import org.eclipse.ui.editors.text.IEncodingSupport; |
| import org.eclipse.ui.part.IPageSite; |
| import org.eclipse.ui.part.IShowInSource; |
| import org.eclipse.ui.part.IShowInTarget; |
| import org.eclipse.ui.part.IShowInTargetList; |
| import org.eclipse.ui.part.ShowInContext; |
| import org.eclipse.ui.services.IServiceLocator; |
| import org.eclipse.ui.texteditor.ITextEditorActionConstants; |
| import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds; |
| import org.eclipse.ui.views.contentoutline.IContentOutlinePage; |
| |
| import org.eclipse.statet.jcommons.lang.NonNullByDefault; |
| import org.eclipse.statet.jcommons.lang.Nullable; |
| |
| import org.eclipse.statet.ecommons.ui.SharedUIResources; |
| import org.eclipse.statet.ecommons.ui.actions.HandlerCollection; |
| import org.eclipse.statet.ecommons.ui.util.UIAccess; |
| import org.eclipse.statet.ecommons.ui.workbench.BasicEditorOutlinePage; |
| import org.eclipse.statet.ecommons.ui.workbench.ContextHandlers; |
| |
| import org.eclipse.statet.internal.ltk.ui.EditingMessages; |
| import org.eclipse.statet.ltk.ast.core.AstInfo; |
| import org.eclipse.statet.ltk.core.SourceModelStamp; |
| import org.eclipse.statet.ltk.model.core.element.LtkModelElement; |
| import org.eclipse.statet.ltk.model.core.element.LtkModelElementDelta; |
| import org.eclipse.statet.ltk.model.core.element.LtkModelElementFilter; |
| 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.ISelectionWithElementInfoListener; |
| import org.eclipse.statet.ltk.ui.LTKInputData; |
| import org.eclipse.statet.ltk.ui.ModelElementInputListener; |
| |
| |
| /** |
| * Abstract content outline page for a {@link SourceEditor1} with model info. |
| */ |
| @NonNullByDefault |
| public abstract class SourceEditor1OutlinePage extends BasicEditorOutlinePage |
| implements IContentOutlinePage, IAdaptable, ISourceEditorAssociated, |
| IShowInSource, IShowInTargetList, IShowInTarget, |
| IPostSelectionProvider, ModelElementInputListener<LtkModelElement<?>> { |
| |
| |
| protected class PageOutlineContent implements OutlineContentProvider.OutlineContent { |
| |
| |
| public PageOutlineContent() { |
| } |
| |
| |
| @Override |
| public @Nullable SourceUnitModelInfo getModelInfo(final Object input) { |
| return SourceEditor1OutlinePage.this.getModelInfo(input); |
| } |
| |
| @Override |
| public @Nullable LtkModelElementFilter<? super SourceStructElement<?, ?>> getContentFilter() { |
| return SourceEditor1OutlinePage.this.getContentFilter(); |
| } |
| |
| } |
| |
| public class AstContentProvider extends OutlineContentProvider { |
| |
| |
| public AstContentProvider() { |
| super(new PageOutlineContent()); |
| } |
| |
| |
| @Override |
| public @Nullable SourceModelStamp getStamp(final Object inputElement) { |
| if (inputElement instanceof SourceUnit) { |
| final AstInfo ast= ((SourceUnit)inputElement).getAstInfo(SourceEditor1OutlinePage.this.mainType, false, null); |
| if (ast != null) { |
| return ast.getStamp(); |
| } |
| } |
| return null; |
| } |
| |
| @Override |
| public Object[] getElements(final Object inputElement) { |
| if (inputElement instanceof SourceUnit) { |
| final AstInfo ast= ((SourceUnit)inputElement).getAstInfo(SourceEditor1OutlinePage.this.mainType, false, null); |
| if (ast != null) { |
| SourceEditor1OutlinePage.this.currentModelStamp= ast.getStamp(); |
| return new Object[] { ast.getRoot() }; |
| } |
| } |
| return new Object[0]; |
| } |
| |
| } |
| |
| |
| /** |
| * @deprecated use {@link AbstractToggleHandler} |
| */ |
| @Deprecated |
| protected abstract class ToggleAction extends Action { |
| |
| private final String settingsKey; |
| private final int time; |
| |
| public ToggleAction(final String checkSettingsKey, final boolean checkSettingsDefault, |
| final int expensive) { |
| assert (checkSettingsKey != null); |
| |
| this.settingsKey= checkSettingsKey; |
| this.time= expensive; |
| |
| final IDialogSettings settings= getDialogSettings(); |
| final boolean on= (settings.get(this.settingsKey) == null) ? |
| checkSettingsDefault : getDialogSettings().getBoolean(this.settingsKey); |
| setChecked(on); |
| configure(on); |
| } |
| |
| protected void init() { |
| } |
| |
| @Override |
| public void run() { |
| final Runnable runnable= new Runnable() { |
| @Override |
| public void run() { |
| final boolean on= isChecked(); |
| configure(on); |
| getDialogSettings().put(ToggleAction.this.settingsKey, on); |
| } |
| }; |
| if (this.time == 0) { |
| runnable.run(); |
| } |
| else { |
| BusyIndicator.showWhile(Display.getCurrent(), runnable); |
| } |
| } |
| |
| protected abstract void configure(boolean on); |
| |
| } |
| |
| private class SyncWithEditorAction extends ToggleAction implements ISelectionWithElementInfoListener { |
| |
| public SyncWithEditorAction() { |
| super("sync.editor", true, 0); //$NON-NLS-1$ |
| setText(EditingMessages.SyncWithEditor_label); |
| setImageDescriptor(SharedUIResources.getImages().getDescriptor(SharedUIResources.LOCTOOL_SYNCHRONIZED_IMAGE_ID)); |
| } |
| |
| @Override |
| protected void configure(final boolean on) { |
| if (on) { |
| SourceEditor1OutlinePage.this.editor.addPostSelectionWithElementInfoListener(this); |
| } |
| else { |
| SourceEditor1OutlinePage.this.editor.removePostSelectionWithElementInfoListener(this); |
| } |
| } |
| |
| @Override |
| public void inputChanged() { |
| } |
| |
| @Override |
| public void stateChanged(final LTKInputData state) { |
| if (!state.isStillValid()) { |
| return; |
| } |
| if (!isUpToDate(state.getInputInfo().getStamp())) { |
| elementUpdatedInfo(state.getInputElement(), null); |
| } |
| UIAccess.getDisplay().syncExec(new Runnable() { |
| @Override |
| public void run() { |
| if (state.isStillValid() && isChecked()) { |
| select(state.getModelSelection()); |
| } |
| } |
| }); |
| } |
| |
| } |
| |
| |
| private final SourceEditor1 editor; |
| private final String mainType; |
| private OutlineContentProvider contentProvider; |
| |
| private @Nullable SourceModelStamp currentModelStamp; |
| |
| private @Nullable LtkModelElement<?> inputUnit; |
| |
| private SyncWithEditorAction syncWithEditorAction; |
| |
| |
| public SourceEditor1OutlinePage(final SourceEditor1 editor, final String mainType, final String contextMenuId) { |
| super(contextMenuId); |
| this.editor= nonNullAssert(editor); |
| this.mainType= nonNullAssert(mainType); |
| } |
| |
| |
| public SourceEditor1 getEditor() { |
| return this.editor; |
| } |
| |
| |
| @Override |
| public void init(final IPageSite pageSite) { |
| super.init(pageSite); |
| pageSite.setSelectionProvider(this); |
| } |
| |
| protected boolean isUpToDate(final SourceModelStamp stamp) { |
| final SourceModelStamp current= this.currentModelStamp; |
| return (current != null && current.equals(stamp)); |
| } |
| |
| protected @Nullable LtkModelElementFilter<? super SourceStructElement<?, ?>> getContentFilter() { |
| return null; |
| } |
| |
| |
| @Override |
| protected @Nullable TreeViewer getViewer() { |
| return (TreeViewer)super.getViewer(); |
| } |
| |
| @Override |
| public void createControl(final Composite parent) { |
| super.createControl(parent); |
| |
| this.editor.getModelInputProvider().addListener(this); |
| getViewer().setInput(this.inputUnit); |
| } |
| |
| protected OutlineContentProvider createContentProvider() { |
| return new OutlineContentProvider(new PageOutlineContent()); |
| } |
| |
| @Override |
| protected void configureViewer(final TreeViewer viewer) { |
| this.contentProvider= createContentProvider(); |
| viewer.setContentProvider(this.contentProvider); |
| } |
| |
| |
| @Override |
| protected void initActions(final IServiceLocator serviceLocator, |
| final ContextHandlers handlers) { |
| super.initActions(serviceLocator, handlers); |
| |
| this.syncWithEditorAction= new SyncWithEditorAction(); |
| } |
| |
| @Override |
| protected void contributeToActionBars(final IServiceLocator serviceLocator, |
| final IActionBars actionBars, final HandlerCollection handlers) { |
| super.contributeToActionBars(serviceLocator, actionBars, handlers); |
| |
| actionBars.setGlobalActionHandler(ITextEditorActionConstants.UNDO, this.editor.getAction(ITextEditorActionConstants.UNDO)); |
| actionBars.setGlobalActionHandler(ITextEditorActionConstants.REDO, this.editor.getAction(ITextEditorActionConstants.REDO)); |
| |
| // actionBars.setGlobalActionHandler(ITextEditorActionConstants.NEXT, this.editor.getAction(ITextEditorActionConstants.NEXT)); |
| actionBars.setGlobalActionHandler(ITextEditorActionDefinitionIds.GOTO_NEXT_ANNOTATION, this.editor.getAction(ITextEditorActionConstants.NEXT)); |
| // actionBars.setGlobalActionHandler(ITextEditorActionConstants.PREVIOUS, this.editor.getAction(ITextEditorActionConstants.PREVIOUS)); |
| actionBars.setGlobalActionHandler(ITextEditorActionDefinitionIds.GOTO_PREVIOUS_ANNOTATION, this.editor.getAction(ITextEditorActionConstants.PREVIOUS)); |
| |
| final IMenuManager menuManager= actionBars.getMenuManager(); |
| |
| menuManager.add(this.syncWithEditorAction); |
| |
| final IContextService service= serviceLocator.getService(IContextService.class); |
| service.activateContext("org.eclipse.statet.ltk.contexts.EditSource1MenuSet"); //$NON-NLS-1$ |
| } |
| |
| @Override |
| protected void contextMenuAboutToShow(final IMenuManager m) { |
| final Separator additions= new Separator(ADDITIONS_GROUP_ID); |
| m.add(additions); |
| } |
| |
| |
| @Override |
| public void elementChanged(final @Nullable LtkModelElement<?> element) { |
| this.inputUnit= element; |
| this.currentModelStamp= null; |
| final TreeViewer viewer= getViewer(); |
| if (viewer != null && UIAccess.isOkToUse(viewer.getControl())) { |
| viewer.setInput(this.inputUnit); |
| } |
| } |
| |
| @Override |
| public void elementInitialInfo(final @Nullable LtkModelElement<?> element) { |
| elementUpdatedInfo(element, null); |
| } |
| |
| @Override |
| public void elementUpdatedInfo(final @Nullable LtkModelElement<?> element, final @Nullable LtkModelElementDelta delta) { |
| if (element != this.inputUnit || (element == null && this.inputUnit == null)) { |
| return; |
| } |
| final Display display= UIAccess.getDisplay(); |
| display.syncExec(new Runnable() { |
| @Override |
| public void run() { |
| final TreeViewer viewer= getViewer(); |
| |
| if (element != SourceEditor1OutlinePage.this.inputUnit |
| || viewer == null || !UIAccess.isOkToUse(viewer.getControl()) |
| || isUpToDate(SourceEditor1OutlinePage.this.contentProvider.getStamp(element)) ) { |
| return; |
| } |
| beginIgnoreSelection(); |
| try { |
| viewer.refresh(true); |
| } |
| finally { |
| endIgnoreSelection(false); |
| } |
| } |
| }); |
| } |
| |
| protected @Nullable SourceUnitModelInfo getModelInfo(final Object input) { |
| if (input instanceof SourceUnit) { |
| return ((SourceUnit)input).getModelInfo(this.mainType, 0, null); |
| } |
| return null; |
| } |
| |
| |
| @Override |
| public void dispose() { |
| this.editor.getModelInputProvider().removeListener(this); |
| this.editor.handleOutlinePageClosed(); |
| |
| super.dispose(); |
| } |
| |
| |
| @Override |
| protected void selectInEditor(final ISelection selection) { |
| this.editor.setSelection(selection, this.syncWithEditorAction); |
| } |
| |
| protected void select(@Nullable SourceStructElement<?, ?> element) { |
| final TreeViewer viewer= getViewer(); |
| if (viewer != null && UIAccess.isOkToUse(viewer.getControl())) { |
| beginIgnoreSelection(); |
| try { |
| final LtkModelElementFilter<? super SourceStructElement<?, ?>> filter= getContentFilter(); |
| Object selectedElement= null; |
| final IStructuredSelection currentSelection= ((IStructuredSelection)viewer.getSelection()); |
| if (currentSelection.size() == 1) { |
| selectedElement= currentSelection.getFirstElement(); |
| } |
| while (element != null |
| && (element.getElementType() & LtkModelElement.MASK_C2) != LtkModelElement.C2_SOURCE_FILE) { |
| if (selectedElement != null && element.equals(selectedElement)) { |
| return; |
| } |
| if (filter == null || filter.include(element)) { |
| selectedElement= null; |
| viewer.setSelection(new StructuredSelection(element), true); |
| if (!viewer.getSelection().isEmpty()) { |
| return; |
| } |
| } |
| final LtkModelElement<?> parent= element.getSourceParent(); |
| if (parent instanceof SourceStructElement) { |
| element= (SourceStructElement<?, ?>)parent; |
| continue; |
| } |
| else { |
| break; |
| } |
| } |
| if (!viewer.getSelection().isEmpty()) { |
| viewer.setSelection(StructuredSelection.EMPTY); |
| } |
| } |
| finally { |
| endIgnoreSelection(true); |
| } |
| } |
| } |
| |
| |
| @Override |
| public ShowInContext getShowInContext() { |
| return new ShowInContext(this.editor.getEditorInput(), null); |
| } |
| |
| @Override |
| public String[] getShowInTargetIds() { |
| return new String[] { IPageLayout.ID_PROJECT_EXPLORER }; |
| } |
| |
| @Override |
| public boolean show(final ShowInContext context) { |
| final LtkModelElement<?> inputUnit= this.inputUnit; |
| final ISelection selection= context.getSelection(); |
| if (selection instanceof LTKInputData) { |
| final LTKInputData data= (LTKInputData) selection; |
| data.update(); |
| if (inputUnit.equals(data.getInputElement())) { |
| select(data.getModelSelection()); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| |
| @Override |
| public ISourceEditor getSourceEditor() { |
| return this.editor; |
| } |
| |
| @Override |
| @SuppressWarnings("unchecked") |
| public <T> @Nullable T getAdapter(final Class<T> adapterType) { |
| if (adapterType == ISourceEditorAssociated.class) { |
| return (T)this; |
| } |
| if (adapterType == IEncodingSupport.class) { |
| return (T)this.editor.getAdapter(IEncodingSupport.class); |
| } |
| if (adapterType == IContentType.class) { |
| return (T)this.editor.getContentType(); |
| } |
| return null; |
| } |
| |
| } |