aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorkwannheden2009-08-21 03:42:30 (EDT)
committersefftinge2009-08-21 03:42:30 (EDT)
commit3a690efefcd739c1fbff5b8562f17c4c4e2d2601 (patch)
tree320b06454dec316e756e07cc926f472e676831a7
parent9815f1e445ecaa147ed76e8afd9728abbf17a14f (diff)
downloadorg.eclipse.xtext-3a690efefcd739c1fbff5b8562f17c4c4e2d2601.zip
org.eclipse.xtext-3a690efefcd739c1fbff5b8562f17c4c4e2d2601.tar.gz
org.eclipse.xtext-3a690efefcd739c1fbff5b8562f17c4c4e2d2601.tar.bz2
Feature: propagate model changes to editor - https://bugs.eclipse.org/bugs/show_bug.cgi?id=265097
-rw-r--r--plugins/org.eclipse.xtext.ui.core/META-INF/MANIFEST.MF1
-rw-r--r--plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/XtextDocument.java21
-rw-r--r--plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/edit/DefaultDocumentEditor.java58
-rw-r--r--plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/edit/DefaultTextEditComposer.java163
-rw-r--r--plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/edit/IDocumentEditor.java24
-rw-r--r--plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/edit/ITextEditComposer.java27
-rw-r--r--tests/org.eclipse.xtext.ui.core.tests/META-INF/MANIFEST.MF3
-rw-r--r--tests/org.eclipse.xtext.ui.core.tests/src/org/eclipse/xtext/ui/core/editor/model/edit/DefaultDocumentEditorTest.java65
-rw-r--r--tests/org.eclipse.xtext.ui.core.tests/src/org/eclipse/xtext/ui/core/editor/model/edit/DefaultTextEditComposerTest.java157
9 files changed, 515 insertions, 4 deletions
diff --git a/plugins/org.eclipse.xtext.ui.core/META-INF/MANIFEST.MF b/plugins/org.eclipse.xtext.ui.core/META-INF/MANIFEST.MF
index fe42442..70ad1e3 100644
--- a/plugins/org.eclipse.xtext.ui.core/META-INF/MANIFEST.MF
+++ b/plugins/org.eclipse.xtext.ui.core/META-INF/MANIFEST.MF
@@ -26,6 +26,7 @@ Export-Package: org.eclipse.xtext.ui.core,
org.eclipse.xtext.ui.core.editor.formatting,
org.eclipse.xtext.ui.core.editor.handler,
org.eclipse.xtext.ui.core.editor.model,
+ org.eclipse.xtext.ui.core.editor.model.edit,
org.eclipse.xtext.ui.core.editor.preferences,
org.eclipse.xtext.ui.core.editor.preferences.fields,
org.eclipse.xtext.ui.core.editor.reconciler,
diff --git a/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/XtextDocument.java b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/XtextDocument.java
index f1e36ef..c7bb986 100644
--- a/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/XtextDocument.java
+++ b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/XtextDocument.java
@@ -8,6 +8,7 @@
*******************************************************************************/
package org.eclipse.xtext.ui.core.editor.model;
+import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
@@ -206,7 +207,7 @@ public class XtextDocument extends Document implements IXtextDocument {
}
@Override
- protected void beforeReadOnly(XtextResource res, org.eclipse.xtext.concurrent.IUnitOfWork<?, XtextResource> work) {
+ protected void beforeReadOnly(XtextResource res, IUnitOfWork<?, XtextResource> work) {
if (log.isDebugEnabled())
log.debug("read - " + Thread.currentThread().getName());
updateContentBeforeRead();
@@ -219,16 +220,30 @@ public class XtextDocument extends Document implements IXtextDocument {
}
@Override
- protected void afterReadOnly(XtextResource res, Object result, org.eclipse.xtext.concurrent.IUnitOfWork<?, XtextResource> work) {
+ protected void afterReadOnly(XtextResource res, Object result, IUnitOfWork<?, XtextResource> work) {
ensureThatStateIsNotReturned(result, work);
}
@Override
- protected void afterModify(XtextResource res, Object result, org.eclipse.xtext.concurrent.IUnitOfWork<?, XtextResource> work) {
+ protected void afterModify(XtextResource res, Object result, IUnitOfWork<?, XtextResource> work) {
ensureThatStateIsNotReturned(result, work);
notifyModelListeners(resource);
}
+ @Override
+ public <T> T modify(IUnitOfWork<T, XtextResource> work) {
+ try {
+ return super.modify(work);
+ } catch (RuntimeException e) {
+ try {
+ getState().reparse(get());
+ }
+ catch (IOException ioe) {
+ }
+ throw e;
+ }
+ }
+
public <T> T process(IUnitOfWork<T, XtextResource> transaction) {
if (transaction != null) {
readLock.unlock();
diff --git a/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/edit/DefaultDocumentEditor.java b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/edit/DefaultDocumentEditor.java
new file mode 100644
index 0000000..2b12bf5
--- /dev/null
+++ b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/edit/DefaultDocumentEditor.java
@@ -0,0 +1,58 @@
+/*******************************************************************************
+ * Copyright (c) 2009 itemis AG (http://www.itemis.eu) 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
+ *******************************************************************************/
+package org.eclipse.xtext.ui.core.editor.model.edit;
+
+import org.eclipse.text.edits.TextEdit;
+import org.eclipse.xtext.concurrent.IUnitOfWork;
+import org.eclipse.xtext.resource.XtextResource;
+import org.eclipse.xtext.ui.core.editor.model.IXtextDocument;
+
+import com.google.inject.Inject;
+
+/**
+ * @author Knut Wannheden - Initial contribution and API
+ */
+public class DefaultDocumentEditor implements IDocumentEditor {
+
+ @Inject
+ private ITextEditComposer composer;
+
+ private final class ReconcilingUnitOfWork<T> implements IUnitOfWork<T, XtextResource> {
+
+ private final IUnitOfWork<T, XtextResource> work;
+ private final IXtextDocument document;
+
+ private ReconcilingUnitOfWork(IUnitOfWork<T, XtextResource> work, IXtextDocument document) {
+ this.work = work;
+ this.document = document;
+ }
+
+ public T exec(XtextResource state) throws Exception {
+ composer.beginRecording(state);
+ T result = work.exec(state);
+ final TextEdit edit = composer.endRecording();
+ if (edit != null) {
+ String original = document.get();
+ try {
+ edit.apply(document);
+ }
+ catch (Exception e) {
+ document.set(original);
+ throw new RuntimeException(e);
+ }
+ }
+ return result;
+ }
+ }
+
+ public <T> T process(final IUnitOfWork<T, XtextResource> work, final IXtextDocument document) {
+ IUnitOfWork<T, XtextResource> reconcilingUnitOfWork = new ReconcilingUnitOfWork<T>(work, document);
+ return document.modify(reconcilingUnitOfWork);
+ }
+
+}
diff --git a/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/edit/DefaultTextEditComposer.java b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/edit/DefaultTextEditComposer.java
new file mode 100644
index 0000000..c436074
--- /dev/null
+++ b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/edit/DefaultTextEditComposer.java
@@ -0,0 +1,163 @@
+/*******************************************************************************
+ * Copyright (c) 2009 itemis AG (http://www.itemis.eu) 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
+ *******************************************************************************/
+package org.eclipse.xtext.ui.core.editor.model.edit;
+
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.List;
+
+import org.eclipse.emf.common.notify.Notification;
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.emf.ecore.util.EContentAdapter;
+import org.eclipse.emf.ecore.util.EcoreUtil;
+import org.eclipse.text.edits.MultiTextEdit;
+import org.eclipse.text.edits.ReplaceEdit;
+import org.eclipse.text.edits.TextEdit;
+import org.eclipse.xtext.parsetree.CompositeNode;
+import org.eclipse.xtext.parsetree.NodeAdapter;
+import org.eclipse.xtext.parsetree.NodeUtil;
+import org.eclipse.xtext.parsetree.reconstr.SerializerUtil;
+
+import com.google.common.collect.Lists;
+import com.google.inject.Inject;
+
+/**
+ * @author Knut Wannheden - Initial contribution and API
+ */
+public class DefaultTextEditComposer extends EContentAdapter implements ITextEditComposer {
+
+ @Inject
+ private SerializerUtil serializerUtil;
+
+ private Resource resource;
+ private int resourceSize;
+ private boolean resourceChanged;
+
+ private Collection<EObject> modifiedObjects = new LinkedHashSet<EObject>();
+
+ private boolean recording = false;
+
+ @Override
+ public void notifyChanged(Notification notification) {
+ super.notifyChanged(notification);
+
+ if (!doRecord(notification))
+ return;
+
+ if (notification.getNotifier() instanceof EObject) {
+ recordObjectModification((EObject) notification.getNotifier());
+ }
+ else if (notification.getNotifier() instanceof Resource) {
+ recordResourceModification((Resource) notification.getNotifier());
+ }
+ }
+
+ protected void recordObjectModification(EObject obj) {
+ if (obj.eResource() == null || obj.eResource() != resource)
+ getModifiedObjects().remove(obj);
+ else
+ getModifiedObjects().add(obj);
+ }
+
+ protected void recordResourceModification(Resource notifier) {
+ resourceChanged = true;
+ }
+
+ protected Collection<EObject> getModifiedObjects() {
+ return modifiedObjects;
+ }
+
+ protected boolean doRecord(Notification notification) {
+ if (!recording || notification.isTouch())
+ return false;
+
+ switch (notification.getEventType()) {
+ case Notification.ADD:
+ case Notification.ADD_MANY:
+ case Notification.MOVE:
+ case Notification.REMOVE:
+ case Notification.REMOVE_MANY:
+ case Notification.SET:
+ case Notification.UNSET:
+ return true;
+ default:
+ return false;
+ }
+ }
+
+ public void beginRecording(Resource newResource) {
+ if (newResource != resource) {
+ if (resource != null)
+ resource.eAdapters().remove(this);
+ newResource.eAdapters().add(this);
+ resource = newResource;
+ }
+ if (resource.getContents().isEmpty()) {
+ resourceSize = 0;
+ }
+ else {
+ final EObject root = resource.getContents().get(0);
+ resourceSize = NodeUtil.getNodeAdapter(root).getParserNode().getTotalLength();
+ }
+ recording = true;
+ }
+
+ public TextEdit endRecording() {
+ recording = false;
+ TextEdit textEdit = getTextEdit();
+
+ getModifiedObjects().clear();
+ resourceChanged = false;
+
+ return textEdit;
+ }
+
+ public TextEdit getTextEdit() {
+ TextEdit result = null;
+
+ if (resourceChanged) {
+ String text = resource.getContents().isEmpty() ? "" : serializerUtil.serialize(
+ resource.getContents().get(0)).trim();
+ result = new ReplaceEdit(0, resourceSize, text);
+ }
+ else {
+ final Collection<EObject> modifiedObjects = getModifiedObjects();
+ if (!modifiedObjects.isEmpty()) {
+ List<TextEdit> edits = getObjectEdits();
+ if (edits.size() == 1)
+ result = edits.get(0);
+ else {
+ result = new MultiTextEdit();
+ for (TextEdit edit : edits) {
+ result.addChild(edit);
+ }
+ }
+ }
+ }
+
+ return result;
+ }
+
+ private List<TextEdit> getObjectEdits() {
+ final Collection<EObject> modifiedObjects = getModifiedObjects();
+ Collection<EObject> topLevelObjects = EcoreUtil.filterDescendants(modifiedObjects);
+ List<TextEdit> edits = Lists.newArrayList();
+
+ for (EObject eObject : topLevelObjects) {
+ NodeAdapter nodeAdapter = NodeUtil.getNodeAdapter(eObject);
+ CompositeNode node = nodeAdapter.getParserNode();
+
+ String text = serializerUtil.serialize(eObject, false);
+ TextEdit edit = new ReplaceEdit(node.getOffset(), node.getLength(), text);
+ edits.add(edit);
+ }
+ return edits;
+ }
+
+} \ No newline at end of file
diff --git a/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/edit/IDocumentEditor.java b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/edit/IDocumentEditor.java
new file mode 100644
index 0000000..cf8b240
--- /dev/null
+++ b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/edit/IDocumentEditor.java
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * Copyright (c) 2009 itemis AG (http://www.itemis.eu) 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
+ *******************************************************************************/
+package org.eclipse.xtext.ui.core.editor.model.edit;
+
+import org.eclipse.xtext.concurrent.IUnitOfWork;
+import org.eclipse.xtext.resource.XtextResource;
+import org.eclipse.xtext.ui.core.editor.model.IXtextDocument;
+
+import com.google.inject.ImplementedBy;
+
+/**
+ * @author Knut Wannheden - Initial contribution and API
+ */
+@ImplementedBy(DefaultDocumentEditor.class)
+public interface IDocumentEditor {
+
+ <T> T process(final IUnitOfWork<T, XtextResource> work, final IXtextDocument document);
+
+}
diff --git a/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/edit/ITextEditComposer.java b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/edit/ITextEditComposer.java
new file mode 100644
index 0000000..aba3130
--- /dev/null
+++ b/plugins/org.eclipse.xtext.ui.core/src/org/eclipse/xtext/ui/core/editor/model/edit/ITextEditComposer.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2009 itemis AG (http://www.itemis.eu) 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
+ *******************************************************************************/
+package org.eclipse.xtext.ui.core.editor.model.edit;
+
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.text.edits.TextEdit;
+
+import com.google.inject.ImplementedBy;
+
+/**
+ * @author Knut Wannheden - Initial contribution and API
+ */
+@ImplementedBy(DefaultTextEditComposer.class)
+public interface ITextEditComposer {
+
+ void beginRecording(Resource resource);
+
+ TextEdit endRecording();
+
+ TextEdit getTextEdit();
+
+}
diff --git a/tests/org.eclipse.xtext.ui.core.tests/META-INF/MANIFEST.MF b/tests/org.eclipse.xtext.ui.core.tests/META-INF/MANIFEST.MF
index 41705d1..f048db4 100644
--- a/tests/org.eclipse.xtext.ui.core.tests/META-INF/MANIFEST.MF
+++ b/tests/org.eclipse.xtext.ui.core.tests/META-INF/MANIFEST.MF
@@ -12,7 +12,8 @@ Require-Bundle: org.eclipse.xtext.ui.core;bundle-version="0.8.0",
org.eclipse.xtext.generator;bundle-version="0.8.0",
org.eclipse.jdt.core;bundle-version="3.4.0",
org.eclipse.jdt.launching;bundle-version="3.4.0",
- org.eclipse.xtext
+ org.eclipse.xtext,
+ org.eclipse.xtext.junit;bundle-version="0.8.0"
Bundle-Activator: org.eclipse.xtext.ui.core.internal.XtextUICoreTestsPlugin
Bundle-ActivationPolicy: lazy
Bundle-Vendor: Eclipse.org
diff --git a/tests/org.eclipse.xtext.ui.core.tests/src/org/eclipse/xtext/ui/core/editor/model/edit/DefaultDocumentEditorTest.java b/tests/org.eclipse.xtext.ui.core.tests/src/org/eclipse/xtext/ui/core/editor/model/edit/DefaultDocumentEditorTest.java
new file mode 100644
index 0000000..d252e45
--- /dev/null
+++ b/tests/org.eclipse.xtext.ui.core.tests/src/org/eclipse/xtext/ui/core/editor/model/edit/DefaultDocumentEditorTest.java
@@ -0,0 +1,65 @@
+/*******************************************************************************
+ * Copyright (c) 2009 itemis AG (http://www.itemis.eu) 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
+ *******************************************************************************/
+package org.eclipse.xtext.ui.core.editor.model.edit;
+
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.xtext.Grammar;
+import org.eclipse.xtext.XtextStandaloneSetup;
+import org.eclipse.xtext.concurrent.IUnitOfWork;
+import org.eclipse.xtext.junit.AbstractXtextTests;
+import org.eclipse.xtext.resource.XtextResource;
+import org.eclipse.xtext.ui.core.editor.model.IXtextDocument;
+import org.eclipse.xtext.ui.core.editor.model.XtextDocument;
+import org.eclipse.xtext.util.StringInputStream;
+
+/**
+ * @author Knut Wannheden - Initial contribution and API
+ */
+public class DefaultDocumentEditorTest extends AbstractXtextTests {
+
+ private IDocumentEditor editor;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ with(XtextStandaloneSetup.class);
+ editor = get(IDocumentEditor.class);
+ }
+
+ public void testProcess() throws Exception {
+ String grammar = "grammar foo.Foo " + "generate foo \"foo://foo/42\" " + "Foo: \"foo\" | \"bar\" | \"baz\"; "
+ + "Bar: foo=Foo; ";
+ final Resource res = getResource(new StringInputStream(grammar));
+ final Object expected = res.getContents().get(0);
+ final IXtextDocument document = new XtextDocument() {
+ @Override
+ public <T> T modify(IUnitOfWork<T, XtextResource> work) {
+ try {
+ return work.exec((XtextResource) res);
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ };
+ document.set(grammar);
+
+ Object result = editor.process(new IUnitOfWork<Object, XtextResource>() {
+ public Object exec(XtextResource state) throws Exception {
+ assertEquals(res, state);
+ Grammar grammar = (Grammar) state.getContents().get(0);
+ grammar.setName("foo.Bar");
+ return grammar;
+ }
+ }, document);
+
+ assertEquals(expected, result);
+ assertEquals(grammar.replaceFirst("foo\\.Foo", "foo.Bar"), document.get());
+ }
+
+}
diff --git a/tests/org.eclipse.xtext.ui.core.tests/src/org/eclipse/xtext/ui/core/editor/model/edit/DefaultTextEditComposerTest.java b/tests/org.eclipse.xtext.ui.core.tests/src/org/eclipse/xtext/ui/core/editor/model/edit/DefaultTextEditComposerTest.java
new file mode 100644
index 0000000..7bb4659
--- /dev/null
+++ b/tests/org.eclipse.xtext.ui.core.tests/src/org/eclipse/xtext/ui/core/editor/model/edit/DefaultTextEditComposerTest.java
@@ -0,0 +1,157 @@
+/*******************************************************************************
+ * Copyright (c) 2009 itemis AG (http://www.itemis.eu) 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
+ *******************************************************************************/
+package org.eclipse.xtext.ui.core.editor.model.edit;
+
+import java.io.InputStream;
+
+import org.eclipse.emf.ecore.EObject;
+import org.eclipse.emf.ecore.resource.Resource;
+import org.eclipse.text.edits.MultiTextEdit;
+import org.eclipse.text.edits.ReplaceEdit;
+import org.eclipse.text.edits.TextEdit;
+import org.eclipse.xtext.AbstractRule;
+import org.eclipse.xtext.Alternatives;
+import org.eclipse.xtext.Grammar;
+import org.eclipse.xtext.Keyword;
+import org.eclipse.xtext.ParserRule;
+import org.eclipse.xtext.XtextFactory;
+import org.eclipse.xtext.XtextStandaloneSetup;
+import org.eclipse.xtext.junit.AbstractXtextTests;
+import org.eclipse.xtext.parsetree.AbstractNode;
+import org.eclipse.xtext.parsetree.NodeUtil;
+import org.eclipse.xtext.parsetree.impl.ParsetreeUtil;
+import org.eclipse.xtext.util.StringInputStream;
+
+/**
+ * @author Knut Wannheden - Initial contribution and API
+ */
+public class DefaultTextEditComposerTest extends AbstractXtextTests {
+
+ private ITextEditComposer composer;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ with(new XtextStandaloneSetup());
+ composer = get(ITextEditComposer.class);
+ }
+
+ private InputStream newTestGrammar() {
+ return new StringInputStream("grammar foo.Foo " + "generate foo 'foo://foo/42' "
+ + "Foo: 'foo' | 'bar' | 'baz'; " + "Bar: foo=Foo; ");
+ }
+
+ public void testProtocol() throws Exception {
+ Resource res = getResource(newTestGrammar());
+ assertNull(composer.endRecording());
+ composer.beginRecording(res);
+ assertNull(composer.endRecording());
+ assertNull(composer.endRecording());
+ composer.beginRecording(res);
+ Grammar grammar = (Grammar) res.getContents().get(0);
+ ParserRule rule = (ParserRule) grammar.getRules().get(0);
+ rule.setName("Bar");
+ assertNotNull(composer.endRecording());
+ assertNull(composer.endRecording());
+ }
+
+ public void testRemoveRootObject() throws Exception {
+ Resource res = getResource(newTestGrammar());
+
+ composer.beginRecording(res);
+ res.getContents().clear();
+ TextEdit edit = composer.endRecording();
+
+ assertEquals("", ((ReplaceEdit) edit).getText());
+ }
+
+ public void testReplaceRootObject() throws Exception {
+ Resource res = getResource(newTestGrammar());
+
+ composer.beginRecording(res);
+ Grammar grammar = (Grammar) getResource(
+ new StringInputStream("grammar bar.Bar " + "generate bar 'bar://bar/43' " + "Bar: 'bar'; "))
+ .getContents().get(0);
+ res.getContents().set(0, grammar);
+ TextEdit edit = composer.endRecording();
+
+ assertMatches(grammar, edit);
+ }
+
+ public void testObjectAddition() throws Exception {
+ Resource res = getResource(newTestGrammar());
+
+ composer.beginRecording(res);
+ Grammar grammar = (Grammar) res.getContents().get(0);
+ ParserRule rule = (ParserRule) grammar.getRules().get(0);
+ Alternatives alternatives = (Alternatives) rule.getAlternatives();
+ Keyword keyword = XtextFactory.eINSTANCE.createKeyword();
+ keyword.setValue("qux");
+ alternatives.getGroups().add(keyword);
+ TextEdit edit = composer.endRecording();
+
+ assertMatches(alternatives, edit);
+ }
+
+ public void testObjectRemoval() throws Exception {
+ Resource res = getResource(newTestGrammar());
+
+ composer.beginRecording(res);
+ Grammar grammar = (Grammar) res.getContents().get(0);
+ AbstractRule rule = grammar.getRules().get(0);
+ Alternatives alternatives = (Alternatives) rule.getAlternatives();
+ alternatives.getGroups().remove(2);
+ TextEdit edit = composer.endRecording();
+
+ assertMatches(alternatives, edit);
+ }
+
+ public void testObjectReplacement() throws Exception {
+ Resource res = getResource(newTestGrammar());
+
+ composer.beginRecording(res);
+ Grammar grammar = (Grammar) res.getContents().get(0);
+ ParserRule rule = (ParserRule) grammar.getRules().get(0);
+ Keyword keyword = XtextFactory.eINSTANCE.createKeyword();
+ keyword.setValue("baz");
+ rule.setAlternatives(keyword);
+ TextEdit edit = composer.endRecording();
+
+ assertMatches(rule, edit);
+ }
+
+ public void testMultiEdit() throws Exception {
+ Resource res = getResource(newTestGrammar());
+
+ composer.beginRecording(res);
+ Grammar grammar = (Grammar) res.getContents().get(0);
+ ParserRule fooRule = (ParserRule) grammar.getRules().get(0);
+ ParserRule barRule = (ParserRule) grammar.getRules().get(1);
+ Alternatives fooAlternatives = (Alternatives) fooRule.getAlternatives();
+ barRule.setAlternatives(fooAlternatives.getGroups().remove(0));
+ TextEdit edit = composer.endRecording();
+
+ assertTrue(edit instanceof MultiTextEdit);
+ TextEdit[] children = ((MultiTextEdit) edit).getChildren();
+ assertEquals(2, children.length);
+ assertMatches(fooAlternatives, children[0]);
+ assertMatches(barRule, children[1]);
+ }
+
+ private void assertMatches(EObject obj, TextEdit edit) {
+ assertTrue(edit instanceof ReplaceEdit);
+ AbstractNode node = NodeUtil.getNodeAdapter(obj).getParserNode();
+ assertEquals(ParsetreeUtil.getOffset(node), ((ReplaceEdit) edit).getOffset());
+ assertEqualsIgnoringWhitespace(getSerializer().serialize(obj, false), ((ReplaceEdit) edit).getText());
+ }
+
+ private void assertEqualsIgnoringWhitespace(String expected, String actual) {
+ assertEquals(expected.replaceAll("\\s+", " "), actual.replaceAll("\\s+", " "));
+ }
+
+}