/******************************************************************************* * Copyright (c) 2015, 2015 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Bruno Medeiros - initial API and implementation *******************************************************************************/ package melnorme.lang.ide.ui.editor.structure; import static melnorme.utilbox.core.Assert.AssertNamespace.assertNotNull; import static melnorme.utilbox.core.Assert.AssertNamespace.assertTrue; import static melnorme.utilbox.core.CoreUtil.array; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.TextSelection; import org.eclipse.jface.viewers.ISelection; import org.eclipse.jface.viewers.ISelectionProvider; import org.eclipse.jface.viewers.SelectionChangedEvent; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IPageLayout; import org.eclipse.ui.part.IShowInTargetList; import org.eclipse.ui.texteditor.ITextEditor; import org.eclipse.ui.views.contentoutline.IContentOutlinePage; import melnorme.lang.ide.core.LangCore; import melnorme.lang.ide.core.engine.IStructureModelListener; import melnorme.lang.ide.core.engine.SourceModelManager; import melnorme.lang.ide.core.engine.SourceModelManager.StructureInfo; import melnorme.lang.ide.core.engine.SourceModelManager.StructureModelRegistration; import melnorme.lang.ide.ui.editor.AbstractLangEditor; import melnorme.lang.ide.ui.editor.EditorSourceBuffer; import melnorme.lang.ide.ui.editor.EditorUtils; import melnorme.lang.ide.ui.text.AbstractLangSourceViewerConfiguration; import melnorme.lang.ide.ui.text.LangSourceViewerConfiguration; import melnorme.lang.tooling.LocationKey; import melnorme.lang.tooling.ast.SourceRange; import melnorme.lang.tooling.structure.SourceFileStructure; import melnorme.lang.tooling.structure.StructureElement; import melnorme.utilbox.core.CommonException; import melnorme.utilbox.core.fntypes.Result; import melnorme.utilbox.fields.Field; import melnorme.utilbox.misc.Location; /** * Extension to {@link AbstractLangEditor} with functionality to hande {@link StructureElement}s */ public abstract class AbstractLangStructureEditor extends AbstractLangEditor { protected final SourceModelManager sourceModelMgr = LangCore.getSourceModelManager(); protected final LangOutlinePage outlinePage = addOwned(init_createOutlinePage()); public AbstractLangStructureEditor() { super(); } @Override protected AbstractLangSourceViewerConfiguration createSourceViewerConfiguration() { return new LangSourceViewerConfiguration(getPreferenceStore(), new EditorSourceBuffer(this), this); } /* ----------------- ----------------- */ protected Location editorLocation; protected StructureModelRegistration modelRegistration; { owned.bind(this::disconnectUpdates); } @Override protected void internalDoSetInput(IEditorInput input) { super.internalDoSetInput(input); // Disconnect updates for previous input disconnectUpdates(); LocationKey editorKey = getStructureModelKeyFromEditorInput(input); editorLocation = editorKey.getLocation(); IDocument doc = getDocumentProvider().getDocument(input); modelRegistration = sourceModelMgr.connectStructureUpdates(editorKey, doc, structureInfoListener); // Send initial update handleEditorStructureUpdated(modelRegistration.structureInfo); } protected void disconnectUpdates() { if(modelRegistration != null) { modelRegistration.dispose(); modelRegistration = null; } } public static LocationKey getStructureModelKeyFromEditorInput(IEditorInput input) { try { // Try to adapt as Location Location location = EditorUtils.getLocationFromEditorInput(input); return new LocationKey(location); } catch(CommonException e) { // Is input thread-safe? We assume so since IEditorInput is supposed to be immutable. return new LocationKey(input, input.toString()); } } protected final Field<Result<SourceFileStructure, CommonException>> structureResultField = new Field<>(); public Field<Result<SourceFileStructure, CommonException>> getStructureField() { return structureResultField; } public Result<SourceFileStructure, CommonException> getSourceStructureResult() { return getStructureField().getFieldValue(); } public SourceFileStructure getSourceStructure() throws CommonException { return getSourceStructureResult().get(); } protected final Field<StructureElement> selectedElementField = new Field<>(); public Field<StructureElement> getSelectedElementField() { return selectedElementField; } public StructureElement getSelectedElement() { return selectedElementField.getFieldValue(); } public StructuredSelection getSelectedElementAsStructureSelection() { StructureElement selectedElement = getSelectedElement(); if(selectedElement == null) { return StructuredSelection.EMPTY; } return new StructuredSelection(selectedElement); } protected final IStructureModelListener structureInfoListener = new IStructureModelListener() { @Override public void dataChanged(StructureInfo lockedStructureInfo) { Display.getDefault().asyncExec(new Runnable() { @Override public void run() { // editor input might have changed, so check this update still applies to editor binding if(modelRegistration == null || lockedStructureInfo != modelRegistration.structureInfo) { return; } handleEditorStructureUpdated(modelRegistration.structureInfo); } }); } }; protected void handleEditorStructureUpdated(StructureInfo structureInfo) { assertTrue(Display.getCurrent() != null); structureResultField.setFieldValue(structureInfo.getStoredData()); setSelectedElementField(); } /* ----------------- Selection ----------------- */ protected class EditorSelectionChangedListener extends AbstractSelectionChangedListener { @Override public void selectionChanged(SelectionChangedEvent event) { setSelectedElementField(); } } protected void setSelectedElementField() { ISelectionProvider selectionProvider = getSelectionProvider(); if(selectionProvider == null) { return; // Can happen during dispose } ISelection selection = selectionProvider.getSelection(); if(selection instanceof TextSelection) { TextSelection textSelection = (TextSelection) selection; int caretOffset = textSelection.getOffset(); SourceFileStructure structure; try { structure = getSourceStructure(); } catch(CommonException e) { return; } if(structure != null) { StructureElement selectedElement = structure.getStructureElementAt(caretOffset); selectedElementField.setFieldValue(selectedElement); } } } protected final EditorSelectionChangedListener editorSelectionListener = new EditorSelectionChangedListener(); @Override public void createPartControl(Composite parent) { super.createPartControl(parent); editorSelectionListener.install(assertNotNull(getSourceViewer_())); owned.bind(() -> editorSelectionListener.uninstall(getSourceViewer_())); } /* ----------------- Outline ----------------- */ protected LangOutlinePage init_createOutlinePage() { return new LangOutlinePage(this); } /* ----------------- ----------------- */ @SuppressWarnings("unchecked") @Override public <T> T getAdapter(Class<T> requestedClass) { if (IContentOutlinePage.class.equals(requestedClass)) { IContentOutlinePage outlinePage = this.outlinePage; return (T) outlinePage; } if(requestedClass == IShowInTargetList.class) { return (T) new IShowInTargetList() { @Override public String[] getShowInTargetIds() { return array(IPageLayout.ID_OUTLINE); } }; } return super.getAdapter(requestedClass); } /* ----------------- ----------------- */ public void setElementSelection(StructureElement element) { if(getSelectedElementField().isNotifyingListeners()) { return; // Ignore } setElementSelection(this, element); markInNavigationHistory(); } public static void setElementSelection(ITextEditor editor, StructureElement element) { SourceRange nameSR = element.getNameSourceRange2(); if(nameSR != null) { editor.selectAndReveal(nameSR.getOffset(), nameSR.getLength()); } } }