diff --git a/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/JavaFormatter.java b/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/JavaFormatter.java new file mode 100644 index 000000000..8ea829704 --- /dev/null +++ b/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/JavaFormatter.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2020 itemis AG (http://www.itemis.eu) 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 + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.xtext.formatting2.internal; + +import org.eclipse.xtext.formatting2.AbstractJavaFormatter; +import org.eclipse.xtext.formatting2.IFormattableDocument; +import org.eclipse.xtext.formatting2.internal.formattertestlanguage.IDList; +import org.eclipse.xtext.formatting2.internal.services.FormatterTestLanguageGrammarAccess; + +import com.google.inject.Inject; + +/** + * @author Arne Deutsch - Initial contribution and API + */ +public class JavaFormatter extends AbstractJavaFormatter { + + @Inject + FormatterTestLanguageGrammarAccess grammar; + + protected void format(IDList list, IFormattableDocument doc) { + doc.prepend(regionFor(list).keyword("idlist"), it -> it.noSpace()); + regionFor(list).ruleCallsTo(grammar.getIDRule()).forEach(region -> doc.prepend(region, it -> it.oneSpace())); + } + +} diff --git a/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/JavaFormatterTest.java b/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/JavaFormatterTest.java new file mode 100644 index 000000000..cd472fdca --- /dev/null +++ b/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/JavaFormatterTest.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2020 itemis AG (http://www.itemis.com) 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 + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.xtext.formatting2.internal; + +import org.eclipse.xtext.formatting2.internal.tests.FormatterTestLanguageInjectorProvider; +import org.eclipse.xtext.testing.InjectWith; +import org.eclipse.xtext.testing.XtextRunner; +import org.junit.Test; +import org.junit.runner.RunWith; + +import com.google.inject.Inject; + +/** + * @author Arne Deutsch - Initial contribution and API + */ +@RunWith(XtextRunner.class) +@InjectWith(FormatterTestLanguageInjectorProvider.class) +public class JavaFormatterTest { + + @Inject + private JavaFormatterTestHelper helper; + + @Test + public void javaFormatterWorks() { + helper.assertFormatted(" idlist a b c", "idlist a b c"); + } + +} diff --git a/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/JavaFormatterTestHelper.java b/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/JavaFormatterTestHelper.java new file mode 100644 index 000000000..4083b5416 --- /dev/null +++ b/org.eclipse.xtext.tests/src/org/eclipse/xtext/formatting2/internal/JavaFormatterTestHelper.java @@ -0,0 +1,34 @@ +/******************************************************************************* + * Copyright (c) 2020 itemis AG (http://www.itemis.eu) 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 + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.xtext.formatting2.internal; + +import org.eclipse.xtext.formatting2.IFormatter2; +import org.eclipse.xtext.testing.formatter.FormatterTestHelper; +import org.eclipse.xtext.testing.formatter.FormatterTestRequest; + +import com.google.inject.Inject; + +/** + * @author Arne Deutsch - Initial contribution and API + */ +public class JavaFormatterTestHelper extends FormatterTestHelper { + + @Inject + private JavaFormatter formatter; + + public void assertFormatted(String original, String expectation) { + assertFormatted(r -> r.setToBeFormatted(original).setExpectation(expectation)); + } + + @Override + protected IFormatter2 createFormatter(FormatterTestRequest request) { + return formatter; + } + +} diff --git a/org.eclipse.xtext/META-INF/MANIFEST.MF b/org.eclipse.xtext/META-INF/MANIFEST.MF index 1c3e412d7..e10c099fd 100644 --- a/org.eclipse.xtext/META-INF/MANIFEST.MF +++ b/org.eclipse.xtext/META-INF/MANIFEST.MF @@ -32,20 +32,7 @@ Export-Package: org.eclipse.xtext, org.eclipse.xtend.core", org.eclipse.xtext.formatting, org.eclipse.xtext.formatting.impl, - org.eclipse.xtext.formatting2; - x-friends:="org.eclipse.xtext.generator, - org.eclipse.xtext.ide, - org.eclipse.xtext.ide.tests, - org.eclipse.xtext.purexbase, - org.eclipse.xtext.testing, - org.eclipse.xtext.testlanguages, - org.eclipse.xtext.tests, - org.eclipse.xtext.xbase, - org.eclipse.xtend.core, - org.eclipse.xtend.ide, - org.eclipse.xtext.xtext.generator, - org.eclipse.xtext.junit4, - org.eclipse.xtext.ui", + org.eclipse.xtext.formatting2, org.eclipse.xtext.formatting2.debug;x-friends:="org.eclipse.xtext.ide, org.eclipse.xtext.eclipse.tests, org.eclipse.xtext.ide.tests, @@ -54,17 +41,7 @@ Export-Package: org.eclipse.xtext, org.eclipse.xtext.tests", org.eclipse.xtext.formatting2.internal;x-friends:="org.eclipse.xtext.tests, org.eclipse.xtend.core", - org.eclipse.xtext.formatting2.regionaccess;x-friends:="org.eclipse.xtext.ide, - org.eclipse.xtext.ide.tests, - org.eclipse.xtext.purexbase, - org.eclipse.xtext.xbase, - org.eclipse.xtend.core, - org.eclipse.xtend.ide, - org.eclipse.xtend.core, - org.eclipse.xtext.junit4, - org.eclipse.xtext.testing, - org.eclipse.xtext.tests, - org.eclipse.xtext.ui", + org.eclipse.xtext.formatting2.regionaccess, org.eclipse.xtext.formatting2.regionaccess.internal;x-friends:="org.eclipse.xtext.ide, org.eclipse.xtend.core, org.eclipse.xtend.ide, diff --git a/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/AbstractJavaFormatter.java b/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/AbstractJavaFormatter.java new file mode 100644 index 000000000..38405b3a0 --- /dev/null +++ b/org.eclipse.xtext/src/org/eclipse/xtext/formatting2/AbstractJavaFormatter.java @@ -0,0 +1,156 @@ +/******************************************************************************* + * Copyright (c) 2020 itemis AG (http://www.itemis.eu) 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 + * http://www.eclipse.org/legal/epl-2.0. + * + * SPDX-License-Identifier: EPL-2.0 + *******************************************************************************/ +package org.eclipse.xtext.formatting2; + +import org.eclipse.emf.ecore.EObject; +import org.eclipse.xtext.formatting2.regionaccess.IEObjectRegion; +import org.eclipse.xtext.formatting2.regionaccess.IHiddenRegion; +import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegion; +import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegionFinder; +import org.eclipse.xtext.formatting2.regionaccess.ISemanticRegionsFinder; +import org.eclipse.xtext.resource.XtextResource; +import org.eclipse.xtext.util.PolymorphicDispatcher; + +import com.google.common.annotations.Beta; + +/** + *

+ * This is an abstract base class for language-specific formatters, intended to be extended with a java class. + *

+ *

+ * It is working very much like {@link AbstractFormatter2} but does support additional methods to simplify java + * formatter code. + *

+ *

+ * the "_format" methods are called by reflection. The name is important as well as there are exactly two arguments + * whereas the first one is of the element type you like to format and the second one of type + * {@link IFormattableDocument}. The methods should be protected. + *

+ *

+ * Example code for a java formatter: + * + *

+ * import static org.xtext.example.mydsl.myDsl.MyDslPackage.Literals.*;
+ * 
+ * public class MyDslFormatter2 extends AbstractJavaFormatter {
+ * 	protected void _format(Model model, IFormattableDocument doc) {
+ * 		for (Parent parent : model.getParents())
+ * 			doc.format(parent);
+ * 	}
+ * 
+ * 	protected void _format(Parent parent, IFormattableDocument doc) {
+ * 		doc.prepend(regionFor(parent).keyword("parent"), it -> it.noSpace());
+ * 		doc.append(regionFor(parent).keyword("parent"), it -> it.oneSpace());
+ * 		doc.append(regionFor(parent).feature(PARENT__NAME), it -> it.oneSpace());
+ * 		doc.prepend(regionFor(parent).keyword("{"), it -> it.oneSpace());
+ * 		doc.append(regionFor(parent).keyword("{"), it -> it.newLine());
+ * 		doc.interior(regionFor(parent).keyword("{"), regionFor(parent).keyword("}"), it -> it.indent());
+ * 		doc.append(regionFor(parent).keyword("}"), it -> it.setNewLines(1, 1, 2));
+ * 		for (Child child : parent.getChildren())
+ * 			doc.format(child);
+ * 	}
+ * 
+ * 	protected void _format(Child child, IFormattableDocument doc) {
+ * 		doc.append(regionFor(child).keyword("child"), it -> it.oneSpace());
+ * 		doc.append(regionFor(child).feature(CHILD__NAME), it -> it.newLine());
+ * 	}
+ * }
+ * 
+ *

+ *

+ * This is suitable for the following grammar: + * + *

+ * grammar org.xtext.example.mydsl.MyDsl with org.eclipse.xtext.common.Terminals
+ * 
+ * generate myDsl "http://www.xtext.org/example/mydsl/MyDsl"
+ * 
+ * Model: parents+=Parent*;
+ * Parent: 'parent' name=ID '!' ('{' children+=Child* '}')?;
+ * Child: 'child' name=ID;
+ * 
+ *

+ */ +@Beta +public abstract class AbstractJavaFormatter extends AbstractFormatter2 { + + private PolymorphicDispatcher dispatcher = createPolymorhicDispatcher(); + + // reflective method that calls "_format" methods found in the implementing class. + @Override + public void format(Object child, IFormattableDocument document) { + if (child instanceof XtextResource) { + _format((XtextResource) child, document); + return; + } else if (child == null) { + _format((Void) null, document); + return; + } + try { + dispatcher.invoke(child, document); + } catch (SecurityException | IllegalArgumentException e) { + throw new RuntimeException(e); + } + } + + /** + * Override if you like to specify formatting methods in different way then default (using annotations or similar). + */ + protected PolymorphicDispatcher createPolymorhicDispatcher() { + return PolymorphicDispatcher.createForSingleTarget(m -> "format".equals(m.getName()) + && m.getParameterCount() == 2 && m.getParameterTypes()[1] == IFormattableDocument.class, this); + } + + // implementations that forward the methods of ITextRegionExtensions to simplify formatter code. + + protected ISemanticRegionsFinder allRegionsFor(EObject semanticElement) { + return textRegionExtensions.allRegionsFor(semanticElement); + } + + protected Iterable allSemanticRegions(EObject semanticElement) { + return textRegionExtensions.allSemanticRegions(semanticElement); + } + + protected EObject grammarElement(EObject semanticElement) { + return textRegionExtensions.grammarElement(semanticElement); + } + + protected ISemanticRegionFinder immediatelyFollowing(EObject semanticElement) { + return textRegionExtensions.immediatelyFollowing(semanticElement); + } + + protected ISemanticRegionFinder immediatelyPreceding(EObject semanticElement) { + return textRegionExtensions.immediatelyPreceding(semanticElement); + } + + protected boolean isMultiline(EObject semanticElement) { + return textRegionExtensions.isMultiline(semanticElement); + } + + protected IHiddenRegion nextHiddenRegion(EObject semanticElement) { + return textRegionExtensions.nextHiddenRegion(semanticElement); + } + + protected IHiddenRegion previousHiddenRegion(EObject semanticElement) { + return textRegionExtensions.previousHiddenRegion(semanticElement); + } + + protected ISemanticRegionsFinder regionFor(EObject semanticElement) { + return textRegionExtensions.regionFor(semanticElement); + } + + protected IEObjectRegion regionForEObject(EObject semanticElement) { + return textRegionExtensions.regionForEObject(semanticElement); + } + + protected Iterable semanticRegions(EObject semanticElement) { + return textRegionExtensions.semanticRegions(semanticElement); + } + +}