blob: 3048d2c59f51fbf2dbdc9352fd68f57ae7442ba9 [file] [log] [blame]
/*=============================================================================#
# 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.NonNull;
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.LTKInputData;
import org.eclipse.statet.ltk.ui.ModelElementInputListener;
import org.eclipse.statet.ltk.ui.SelectionWithElementInfoListener;
/**
* Abstract content outline page for a {@link SourceEditor1} with model info.
*/
@NonNullByDefault
public abstract class SourceEditor1OutlinePage extends BasicEditorOutlinePage
implements IContentOutlinePage, IAdaptable, SourceEditorAssociated,
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 @NonNull 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 @NonNull Object[] { ast.getRoot() };
}
}
return new @NonNull 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 SelectionWithElementInfoListener {
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;
}
final SourceUnitModelInfo inputInfo= state.getInputInfo();
if (inputInfo == null || !isUpToDate(inputInfo.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 contextService= nonNullAssert(serviceLocator.getService(IContextService.class));
contextService.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 @NonNull String[] getShowInTargetIds() {
return new @NonNull String[] { IPageLayout.ID_PROJECT_EXPLORER };
}
@Override
public boolean show(final ShowInContext context) {
final LtkModelElement<?> inputUnit= this.inputUnit;
final ISelection selection= context.getSelection();
if (inputUnit != null && selection instanceof LTKInputData) {
final LTKInputData data= (LTKInputData) selection;
data.update();
if (inputUnit.equals(data.getInputElement())) {
select(data.getModelSelection());
return true;
}
}
return false;
}
@Override
public SourceEditor getSourceEditor() {
return this.editor;
}
@Override
@SuppressWarnings("unchecked")
public <T> @Nullable T getAdapter(final Class<T> adapterType) {
if (adapterType == SourceEditorAssociated.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;
}
}