Skip to content

Commit

Permalink
Merge 5001592 into f2b1857
Browse files Browse the repository at this point in the history
  • Loading branch information
LorenzoBettini committed Jul 25, 2020
2 parents f2b1857 + 5001592 commit ffb9de5
Show file tree
Hide file tree
Showing 8 changed files with 228 additions and 77 deletions.

This file was deleted.

@@ -0,0 +1,137 @@
package edelta.ui.tests;

import static org.eclipse.xtext.ui.testing.util.IResourcesSetupUtil.createFile;
import static org.eclipse.xtext.ui.testing.util.IResourcesSetupUtil.waitForBuild;

import org.assertj.core.api.Assertions;
import org.eclipse.jface.viewers.TreeSelection;
import org.eclipse.swt.widgets.Display;
import org.eclipse.xtext.testing.InjectWith;
import org.eclipse.xtext.testing.XtextRunner;
import org.eclipse.xtext.ui.editor.XtextEditor;
import org.eclipse.xtext.ui.editor.outline.impl.EObjectNode;
import org.eclipse.xtext.ui.editor.outline.impl.OutlinePage;
import org.eclipse.xtext.ui.testing.AbstractEditorTest;
import org.junit.Test;
import org.junit.runner.RunWith;

import com.google.inject.Inject;

import edelta.ui.internal.EdeltaActivator;
import edelta.ui.tests.utils.EdeltaPluginProjectHelper;

@RunWith(XtextRunner.class)
@InjectWith(EdeltaUiInjectorProvider.class)
public class EdeltaOutlineWithEditorLinkerTest extends AbstractEditorTest {

@Inject
private EdeltaPluginProjectHelper edeltaProjectHelper;

private static final String TEST_PROJECT = "mytestproject";

private String program =
"package foo\n" +
"\n" +
"metamodel \"mypackage\"\n" +
"\n" +
"def anOp() {\n" +
"}\n" +
"\n" +
"modifyEcore aTest epackage mypackage {\n" +
" addNewEClass(\"MyNewClass\") [\n" +
" addNewEAttribute(\"MyNewAttribute\", null)\n" +
" ]\n" +
" ecoreref(MyClass)\n" +
" ecoreref(MyDerivedClass) => [\n" +
" addNewEAttribute(\"MyNewDerivedClassAttribute\", null)\n" +
" ]\n" +
"}";

private XtextEditor editor;

private OutlinePage outlinePage;

@Override
protected String getEditorId() {
return EdeltaActivator.EDELTA_EDELTA;
}

@Override
public void setUp() throws Exception {
super.setUp();
edeltaProjectHelper.createEdeltaPluginProject(TEST_PROJECT);
var file = createFile(
TEST_PROJECT + "/src/Test.edelta",
program
);
// we need to wait for build twice when we run all the UI tests
waitForBuild();
editor = openEditor(file);
outlinePage = editor.getAdapter(OutlinePage.class);
Assertions.assertThat(outlinePage).isNotNull();
editor.setFocus();
}

@Test
public void testSelectOperation() throws Exception {
whenEditorTextIsSelectedThenOutlineNodeIsSelected
("anOp", "anOp() : Object");
}

@Test
public void testSelectModifyEcore() throws Exception {
whenEditorTextIsSelectedThenOutlineNodeIsSelected
("modifyEcore", "aTest(EPackage) : void");
}

@Test
public void testSelectNonResponsibleExpressionInModifyEcore() throws Exception {
whenEditorTextIsSelectedThenOutlineNodeIsSelected
("ecoreref(MyClass)", "aTest(EPackage) : void");
}

@Test
public void testSelectExpressionThatCreatesEClass() throws Exception {
whenEditorTextIsSelectedThenOutlineNodeIsSelected
("addNewEClass", "MyNewClass");
}

@Test
public void testSelectExpressionThatCreatesEAttributeInCreatedEClass() throws Exception {
whenEditorTextIsSelectedThenOutlineNodeIsSelected
("addNewEAttribute(\"MyNewAttribute", "MyNewAttribute");
}

@Test
public void testSelectExpressionThatCreatesEAttributeInExistingEClass() throws Exception {
whenEditorTextIsSelectedThenOutlineNodeIsSelected
("addNewEAttribute(\"MyNewDerivedClassAttribute", "MyNewDerivedClassAttribute");
}

private void whenEditorTextIsSelectedThenOutlineNodeIsSelected(String textToSelect, String expectedNode) throws InterruptedException {
editor.getInternalSourceViewer()
.setSelectedRange(program.indexOf(textToSelect), 0);
var selection = waitForSelection();
assertEquals(expectedNode,
((EObjectNode) selection.getFirstElement()).getText().toString());
}

@SuppressWarnings("all")
private TreeSelection waitForSelection() throws InterruptedException {
int attempts = 30;
for (int i = 0; i < attempts; ++i) {
Thread.sleep(100);
executeAsyncDisplayJobs();
var selection = (TreeSelection) outlinePage.getTreeViewer().getSelection();
if (!selection.isEmpty())
return selection;
}
fail("No node is selected in the outline");
return null;
}

private void executeAsyncDisplayJobs() {
while(Display.getCurrent().readAndDispatch()) {
}
}
}
7 changes: 6 additions & 1 deletion edelta.parent/edelta.ui/src/edelta/ui/EdeltaUiModule.xtend
@@ -1,7 +1,9 @@
package edelta.ui

import org.eclipse.xtend.lib.annotations.FinalFieldsConstructor
import edelta.ui.navigation.EdeltaHyperlinkinHelper
import org.eclipse.xtend.lib.annotations.FinalFieldsConstructor
import org.eclipse.xtext.ui.editor.outline.actions.OutlineWithEditorLinker
import edelta.ui.outline.actions.EdeltaOutlineWithEditorLinker

/**
* Use this class to register components to be used within the Eclipse IDE.
Expand All @@ -13,4 +15,7 @@ class EdeltaUiModule extends AbstractEdeltaUiModule {
EdeltaHyperlinkinHelper
}

def Class<? extends OutlineWithEditorLinker> bindOutlineWithEditorLinker() {
EdeltaOutlineWithEditorLinker
}
}
Expand Up @@ -3,8 +3,13 @@
*/
package edelta.ui.outline;

import org.eclipse.emf.ecore.ENamedElement;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.swt.graphics.Image;
import org.eclipse.xtext.ui.editor.outline.IOutlineNode;
import org.eclipse.xtext.ui.editor.outline.impl.DefaultOutlineTreeProvider;
import org.eclipse.xtext.ui.editor.outline.impl.EObjectNode;
import org.eclipse.xtext.xbase.XExpression;

import com.google.inject.Inject;

Expand Down Expand Up @@ -48,4 +53,19 @@ public boolean _isLeaf(final EdeltaOperation m) {
public boolean _isLeaf(final EdeltaModifyEcoreOperation m) {
return true;
}

@Override
protected EObjectNode createEObjectNode(IOutlineNode parentNode, EObject modelElement, Image image, Object text,
boolean isLeaf) {
final var eObjectNode = super.createEObjectNode(parentNode, modelElement, image, text, isLeaf);
if (modelElement instanceof ENamedElement) {
// try to associate the node to the responsible XExpression
XExpression expression = derivedStateHelper.
getLastResponsibleExpression((ENamedElement) modelElement);
if (expression != null)
eObjectNode.setShortTextRegion(
locationInFileProvider.getSignificantTextRegion(expression));
}
return eObjectNode;
}
}
@@ -0,0 +1,65 @@
/**
*
*/
package edelta.ui.outline.actions;

import static edelta.edelta.EdeltaPackage.Literals.EDELTA_MODIFY_ECORE_OPERATION;
import static org.eclipse.emf.ecore.EcorePackage.Literals.EPACKAGE;

import java.util.Objects;

import org.eclipse.emf.ecore.ENamedElement;
import org.eclipse.xtext.ui.editor.outline.IOutlineNode;
import org.eclipse.xtext.ui.editor.outline.actions.OutlineWithEditorLinker;
import org.eclipse.xtext.ui.editor.outline.impl.EObjectNode;
import org.eclipse.xtext.util.ITextRegion;
import org.eclipse.xtext.xbase.XExpression;

/**
* From an {@link XExpression} responsible of the modification of an {@link ENamedElement}
* in the editor, navigate to the corresponding element in the outline.
*
* @author Lorenzo Bettini
*
*/
public class EdeltaOutlineWithEditorLinker extends OutlineWithEditorLinker {

@Override
protected IOutlineNode findBestNode(IOutlineNode input, ITextRegion selectedTextRegion) {
final var findBestNode = super.findBestNode(input, selectedTextRegion);
if (findBestNode instanceof EObjectNode) {
EObjectNode eObjectNode = (EObjectNode) findBestNode;
if (eObjectNode.getEClass() == EDELTA_MODIFY_ECORE_OPERATION) {
/* since XExpressions are not shown in the outline, when we select
* such an expression, by default the containing modifyEcore node is
* selected. We try and find a node representing an Ecore element
* that is modified by the selected XExpressionv*/
return eObjectNode.getParent().getChildren().stream()
.filter(node -> EPACKAGE == ((EObjectNode) node).getEClass())
.map(node -> findENamedElementNode(node, selectedTextRegion))
.filter(Objects::nonNull)
.findFirst()
.orElse(findBestNode);
}
}
return findBestNode;
}

private IOutlineNode findENamedElementNode(IOutlineNode node, ITextRegion selectedTextRegion) {
for (var child : node.getChildren()) {
// at this point we are sure it's an EObjectNode
EObjectNode eObjectNode = (EObjectNode) child;
// our Outline nodes for Ecore elements are already associated with the
// text region of the corresponding responsible XExpression
final var recursiveFind = findENamedElementNode(child, selectedTextRegion);
// first check whether there's a child that matches, since,
// as said before, an outer expression region might match an inner one
if (recursiveFind != null) {
return recursiveFind;
} else if (eObjectNode.getSignificantTextRegion().contains(selectedTextRegion)) {
return eObjectNode;
}
}
return null;
}
}
5 changes: 0 additions & 5 deletions edelta.parent/edelta/src/edelta/EdeltaRuntimeModule.xtend
Expand Up @@ -9,7 +9,6 @@ import edelta.compiler.EdeltaXbaseCompiler
import edelta.interpreter.EdeltaInterpreter
import edelta.interpreter.EdeltaSafeInterpreter
import edelta.resource.EdeltaDerivedStateComputer
import edelta.resource.EdeltaLocationInFileProvider
import edelta.resource.EdeltaResourceDescriptionStrategy
import edelta.scoping.EdeltaQualifiedNameProvider
import edelta.typesystem.EdeltaTypeComputer
Expand Down Expand Up @@ -37,10 +36,6 @@ class EdeltaRuntimeModule extends AbstractEdeltaRuntimeModule {
EdeltaDerivedStateComputer
}

override bindILocationInFileProvider() {
EdeltaLocationInFileProvider
}

override bindIDefaultResourceDescriptionStrategy() {
EdeltaResourceDescriptionStrategy
}
Expand Down

0 comments on commit ffb9de5

Please sign in to comment.