From 98861a3e6568f384dcc601b51ed625cbbc196aa5 Mon Sep 17 00:00:00 2001 From: Christian Schneider Date: Thu, 8 Oct 2015 13:39:30 +0200 Subject: [PATCH] [xtend generator] Migration of 'ContentAssistFragment' Signed-off-by: Christian Schneider --- .../META-INF/MANIFEST.MF | 1 + .../ContentAssistFragment2.xtend | 330 ++++++++++++++++++ .../wizard/RuntimeProjectDescriptor.xtend | 4 +- .../xtext/example/mydsl/GenerateMyDsl.mwe2 | 4 +- .../xtext/example/mydsl/GenerateMyDsl.mwe2 | 4 +- .../xtext/example/mydsl/GenerateMyDsl.mwe2 | 4 +- 6 files changed, 335 insertions(+), 12 deletions(-) create mode 100644 plugins/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/ui/contentAssist/ContentAssistFragment2.xtend diff --git a/plugins/org.eclipse.xtext.xtext.generator/META-INF/MANIFEST.MF b/plugins/org.eclipse.xtext.xtext.generator/META-INF/MANIFEST.MF index f140da1ed..d2f62635a 100644 --- a/plugins/org.eclipse.xtext.xtext.generator/META-INF/MANIFEST.MF +++ b/plugins/org.eclipse.xtext.xtext.generator/META-INF/MANIFEST.MF @@ -43,6 +43,7 @@ Export-Package: org.eclipse.xtext.xtext.generator, org.eclipse.xtext.xtext.generator.parser.antlr.splitting.simpleExpressions.util;x-internal:=true, org.eclipse.xtext.xtext.generator.scoping, org.eclipse.xtext.xtext.generator.types, + org.eclipse.xtext.xtext.generator.ui.contentAssist, org.eclipse.xtext.xtext.generator.ui.outline, org.eclipse.xtext.xtext.generator.util, org.eclipse.xtext.xtext.generator.validation, diff --git a/plugins/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/ui/contentAssist/ContentAssistFragment2.xtend b/plugins/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/ui/contentAssist/ContentAssistFragment2.xtend new file mode 100644 index 000000000..9c81bb021 --- /dev/null +++ b/plugins/org.eclipse.xtext.xtext.generator/src/org/eclipse/xtext/xtext/generator/ui/contentAssist/ContentAssistFragment2.xtend @@ -0,0 +1,330 @@ +/******************************************************************************* + * Copyright (c) 2015 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.xtext.generator.ui.contentAssist + +import com.google.common.collect.Sets +import com.google.inject.Inject +import java.util.Set +import org.eclipse.emf.ecore.EObject +import org.eclipse.xtend.lib.annotations.Accessors +import org.eclipse.xtend2.lib.StringConcatenationClient +import org.eclipse.xtext.AbstractElement +import org.eclipse.xtext.AbstractRule +import org.eclipse.xtext.Alternatives +import org.eclipse.xtext.Assignment +import org.eclipse.xtext.CrossReference +import org.eclipse.xtext.Grammar +import org.eclipse.xtext.RuleCall +import org.eclipse.xtext.xtext.generator.AbstractGeneratorFragment2 +import org.eclipse.xtext.xtext.generator.CodeConfig +import org.eclipse.xtext.xtext.generator.XtextGeneratorNaming +import org.eclipse.xtext.xtext.generator.model.FileAccessFactory +import org.eclipse.xtext.xtext.generator.model.GuiceModuleAccess +import org.eclipse.xtext.xtext.generator.model.TypeReference + +import static extension org.eclipse.xtext.GrammarUtil.* +import static extension org.eclipse.xtext.xtext.generator.util.GrammarUtil2.* + +/** + * Contributes the 'Abstract...ProposalProvider' and '...ProposalProvider' stub, + * the latter either in Xtend or Java language. + * + * @author Christian Schneider - Initial contribution and API + */ +class ContentAssistFragment2 extends AbstractGeneratorFragment2 { + + @Inject + extension XtextGeneratorNaming + + @Inject + extension CodeConfig + + @Inject + FileAccessFactory fileAccessFactory + + @Accessors + boolean generateStub = true; + + @Accessors + boolean inheritImplementation = true + + def protected TypeReference getProposalProviderClass(Grammar g) { + return new TypeReference( + g.eclipsePluginBasePackage + ".contentassist." + g.simpleName + "ProposalProvider" + ) + } + + def protected TypeReference getGenProposalProviderClass(Grammar g) { + return new TypeReference( + g.eclipsePluginBasePackage + ".contentassist.Abstract" + g.simpleName + "ProposalProvider" + ) + } + + def protected TypeReference getGenProposalProviderSuperClass(Grammar g) { + val superGrammar = g.nonTerminalsSuperGrammar + if(inheritImplementation && superGrammar != null) + superGrammar.proposalProviderClass + else new TypeReference( + "org.eclipse.xtext.ui.editor.contentassist.AbstractJavaBasedContentProposalProvider" + ) + } + + /** + * Extra getter facilitates customization by overriding. + */ + def protected TypeReference getDefaultEObjectLabelProviderSuperClass() { + new TypeReference("org.eclipse.xtext.ui.label.DefaultEObjectLabelProvider") + } + + + override generate() { + val chosenClass = + if (generateStub) grammar.getProposalProviderClass else grammar.getGenProposalProviderClass; + + if (projectConfig.eclipsePluginManifest != null) { + projectConfig.eclipsePluginManifest.requiredBundles += "org.eclipse.xtext.ui" + } + + new GuiceModuleAccess.BindingFactory() + .addTypeToType( + new TypeReference("org.eclipse.xtext.ui.editor.contentassist.IContentProposalProvider"), + chosenClass + ).contributeTo(language.eclipsePluginGenModule); + + var generatedCode = false + + if (projectConfig.eclipsePluginSrcGen !== null) { + // generate the 'Abstract...ProposalProvider' + generateGenJavaProposalProvider + generatedCode = true + } + + if (generateStub && projectConfig.eclipsePluginSrc != null) { + if (preferXtendStubs) { + generateXtendProposalProviderStub + + if (projectConfig.eclipsePluginManifest != null) { + projectConfig.eclipsePluginManifest.requiredBundles += "org.eclipse.xtext.xbase.lib" + } + } else { + generateJavaProposalProviderStub + } + + generatedCode = true + } + + if (generatedCode && projectConfig.eclipsePluginManifest != null) { + projectConfig.eclipsePluginManifest.exportedPackages += grammar.proposalProviderClass.packageName + } + } + + // generation of the '...ProposalProvider' stub + + def generateXtendProposalProviderStub() { + fileAccessFactory.createXtendFile(grammar.proposalProviderClass, ''' + /** + * See https://www.eclipse.org/Xtext/documentation/304_ide_concepts.html#content-assist + * on how to customize the content assistant. + */ + class «grammar.proposalProviderClass.simpleName» extends «grammar.genProposalProviderClass» { + } + ''').writeTo(projectConfig.eclipsePluginSrc) + } + + def generateJavaProposalProviderStub() { + fileAccessFactory.createJavaFile(grammar.proposalProviderClass, ''' + /** + * See https://www.eclipse.org/Xtext/documentation/304_ide_concepts.html#content-assist + * on how to customize the content assistant. + */ + public class «grammar.proposalProviderClass.simpleName» extends «grammar.genProposalProviderClass» { + } + ''').writeTo(projectConfig.eclipsePluginSrc) + } + + // generation of the 'Abstract...ProposalProvider' + + + def generateGenJavaProposalProvider() { + // excluded features are those that stem from inherited grammars, + // they are handled by the super grammars' proposal provider + val excludedFqnFeatureNames = grammar.getFQFeatureNamesToExclude + val processedNames = newHashSet() + + // determine all assignments within the grammar that are not excluded + // and not handled yet, (iterable is evaluated lazily!) + val assignments = grammar.containedAssignments().fold(newArrayList()) [ candidates, assignment | + val fqFeatureName = assignment.FQFeatureName + if (!processedNames.contains(fqFeatureName) && !excludedFqnFeatureNames.contains(fqFeatureName)) { + processedNames += fqFeatureName; + candidates += assignment; + } + candidates + ] + + // determine the remaining rules that are not excluded + // and not handled yet, (iterable is evaluated lazily!) + val remainingRules = grammar.rules.fold(newArrayList()) [candidates, rule | + val fqnFeatureName = rule.FQFeatureName + if (!processedNames.contains(fqnFeatureName) && !excludedFqnFeatureNames.contains(fqnFeatureName)) { + processedNames += fqnFeatureName + candidates += rule + } + candidates + ] + + val superClass = grammar.getGenProposalProviderSuperClass + fileAccessFactory.createJavaFile(grammar.getGenProposalProviderClass, ''' + /** + * Represents a generated, default implementation of superclass {@link «superClass»}. + * Methods are dynamically dispatched on the first parameter, i.e., you can override them + * with a more concrete subtype. + */ + @SuppressWarnings("all") + public class «grammar.getGenProposalProviderClass.simpleName» extends «superClass» { + + «FOR assignment : assignments» + «assignment.handleAssignment» + «ENDFOR» + + «FOR rule : remainingRules» + public void complete«rule.FQFeatureName»(«EObject» model, «RuleCall» ruleCall, « + contentAssistContextClass» context, «ICompletionProposalAcceptorClass» acceptor) { + // subclasses may override + } + «ENDFOR» + } + ''').writeTo(projectConfig.eclipsePluginSrcGen) + + } + + private def StringConcatenationClient handleAssignment(Assignment assignment) { + // determine all assignment within 'assignment's containing parser rule + // assigning the same feature, obtain their expected terminals, ... + val terminals = assignment.containingParserRule.containedAssignments().filter[ + it.feature == assignment.feature + ].map[ + terminal + ].toList + + // ... and determine the types of those terminals + val terminalTypes = terminals.map[ eClass ].toSet; + + ''' + public void complete«assignment.FQFeatureName»(«EObject» model, «Assignment» assignment, « + contentAssistContextClass» context, «ICompletionProposalAcceptorClass» acceptor) { + «IF terminalTypes.size > 1» + «terminals.handleAssignmentOptions» + «ELSE» + «assignment.terminal.assignmentTerminal("assignment.getTerminal()")» + «ENDIF» + } + ''' + } + + private def StringConcatenationClient handleAssignmentOptions(Iterable terminals) { + val processedTerminals = newHashSet(); + + // for each type of terminal occurring in 'terminals' (evaluated lazily) ... + val candidates = terminals.fold(newHashSet) [ candidates, terminal | + if (!processedTerminals.contains(terminal.eClass)) { + processedTerminals += terminal.eClass + candidates += terminal + } + candidates + ] + + // ... generate an 'instanceof' clause + ''' + «FOR terminal : candidates» + if (assignment.getTerminal() instanceof «terminal.eClass.instanceClass») { + «terminal.assignmentTerminal("assignment.getTerminal()")» + } + «ENDFOR» + ''' + } + + private def dispatch StringConcatenationClient assignmentTerminal(AbstractElement element, String accessor) { + ''' + // subclasses may override + ''' + } + + private def dispatch StringConcatenationClient assignmentTerminal(CrossReference element, String accessor) { + ''' + lookupCrossReference(((«CrossReference»)«accessor»), context, acceptor); + ''' + } + + private def dispatch StringConcatenationClient assignmentTerminal(RuleCall element, String accessor) { + ''' + completeRuleCall(((«RuleCall»)«accessor»), context, acceptor); + ''' + } + + private def dispatch StringConcatenationClient assignmentTerminal(Alternatives alternatives, String accessor) { + val list = alternatives.elements + ''' + «FOR it : list» + «it.assignmentTerminal("((Alternatives)" + accessor + ").getElements.get("+ list.indexOf(it) +")")» + «ENDFOR» + ''' + } + + // helper methods + + private def getContentAssistContextClass() { + new TypeReference("org.eclipse.xtext.ui.editor.contentassist.ContentAssistContext") + } + + private def getICompletionProposalAcceptorClass() { + new TypeReference("org.eclipse.xtext.ui.editor.contentassist.ICompletionProposalAcceptor") + } + + def private getFQFeatureName(AbstractRule r) { + "_" + r.name; + } + + def private getFQFeatureName(Assignment a) { + a.containingParserRule().name.toFirstUpper() + "_" + a.feature.toFirstUpper(); + } + + def private computeFQFeatureNames(Grammar g) { + g.containedAssignments.map[ FQFeatureName ] + g.rules.map[ FQFeatureName ] + } + + def getFQFeatureNamesToExclude(Grammar g) { + val superGrammar = g.nonTerminalsSuperGrammar + if (superGrammar != null) { + val superGrammarsFqFeatureNames = computeFQFeatureNamesFromSuperGrammars(g).toSet + val thisGrammarFqFeatureNames = g.computeFQFeatureNames.toSet + + // return all elements that are already defined in some inherited grammars + Sets.intersection(thisGrammarFqFeatureNames, superGrammarsFqFeatureNames) + } else { + newHashSet() + } + } + + def private computeFQFeatureNamesFromSuperGrammars(Grammar g) { + val superGrammars = newHashSet() + computeAllSuperGrammars(g, superGrammars) + superGrammars.map[ + computeFQFeatureNames + ].flatten + } + + def private void computeAllSuperGrammars(Grammar current, Set visitedGrammars) { + for (s : current.usedGrammars) { + if (!visitedGrammars.contains(s)) { + visitedGrammars.add(s) + computeAllSuperGrammars(s, visitedGrammars) + } + } + } +} \ No newline at end of file diff --git a/plugins/org.eclipse.xtext.xtext.wizard/src/org/eclipse/xtext/xtext/wizard/RuntimeProjectDescriptor.xtend b/plugins/org.eclipse.xtext.xtext.wizard/src/org/eclipse/xtext/xtext/wizard/RuntimeProjectDescriptor.xtend index e6863afca..86b335984 100644 --- a/plugins/org.eclipse.xtext.xtext.wizard/src/org/eclipse/xtext/xtext/wizard/RuntimeProjectDescriptor.xtend +++ b/plugins/org.eclipse.xtext.xtext.wizard/src/org/eclipse/xtext/xtext/wizard/RuntimeProjectDescriptor.xtend @@ -283,9 +283,7 @@ class RuntimeProjectDescriptor extends TestedProjectDescriptor { } // content assist API - fragment = adapter.FragmentAdapter { - fragment = contentAssist.ContentAssistFragment auto-inject {} - } + fragment = ui.contentAssist.ContentAssistFragment2 auto-inject {} // provides a preference page for template proposals fragment = adapter.FragmentAdapter { diff --git a/tests/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.eclipsePlugin/org.xtext.example.eclipsePlugin/src/org/xtext/example/mydsl/GenerateMyDsl.mwe2 b/tests/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.eclipsePlugin/org.xtext.example.eclipsePlugin/src/org/xtext/example/mydsl/GenerateMyDsl.mwe2 index 61af7519f..7394dfa82 100644 --- a/tests/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.eclipsePlugin/org.xtext.example.eclipsePlugin/src/org/xtext/example/mydsl/GenerateMyDsl.mwe2 +++ b/tests/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.eclipsePlugin/org.xtext.example.eclipsePlugin/src/org/xtext/example/mydsl/GenerateMyDsl.mwe2 @@ -94,9 +94,7 @@ Workflow { } // content assist API - fragment = adapter.FragmentAdapter { - fragment = contentAssist.ContentAssistFragment auto-inject {} - } + fragment = ui.contentAssist.ContentAssistFragment2 auto-inject {} // provides a preference page for template proposals fragment = adapter.FragmentAdapter { diff --git a/tests/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.full/org.xtext.example.full.parent/org.xtext.example.full/src/org/xtext/example/mydsl/GenerateMyDsl.mwe2 b/tests/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.full/org.xtext.example.full.parent/org.xtext.example.full/src/org/xtext/example/mydsl/GenerateMyDsl.mwe2 index 7a41b70b6..1af25ab03 100644 --- a/tests/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.full/org.xtext.example.full.parent/org.xtext.example.full/src/org/xtext/example/mydsl/GenerateMyDsl.mwe2 +++ b/tests/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.full/org.xtext.example.full.parent/org.xtext.example.full/src/org/xtext/example/mydsl/GenerateMyDsl.mwe2 @@ -96,9 +96,7 @@ Workflow { } // content assist API - fragment = adapter.FragmentAdapter { - fragment = contentAssist.ContentAssistFragment auto-inject {} - } + fragment = ui.contentAssist.ContentAssistFragment2 auto-inject {} // provides a preference page for template proposals fragment = adapter.FragmentAdapter { diff --git a/tests/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.mavenTycho/org.xtext.example.mavenTycho.parent/org.xtext.example.mavenTycho/src/org/xtext/example/mydsl/GenerateMyDsl.mwe2 b/tests/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.mavenTycho/org.xtext.example.mavenTycho.parent/org.xtext.example.mavenTycho/src/org/xtext/example/mydsl/GenerateMyDsl.mwe2 index 3fe97d41c..c19316c2e 100644 --- a/tests/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.mavenTycho/org.xtext.example.mavenTycho.parent/org.xtext.example.mavenTycho/src/org/xtext/example/mydsl/GenerateMyDsl.mwe2 +++ b/tests/org.eclipse.xtext.tests/testdata/wizard-expectations/org.xtext.example.mavenTycho/org.xtext.example.mavenTycho.parent/org.xtext.example.mavenTycho/src/org/xtext/example/mydsl/GenerateMyDsl.mwe2 @@ -95,9 +95,7 @@ Workflow { } // content assist API - fragment = adapter.FragmentAdapter { - fragment = contentAssist.ContentAssistFragment auto-inject {} - } + fragment = ui.contentAssist.ContentAssistFragment2 auto-inject {} // provides a preference page for template proposals fragment = adapter.FragmentAdapter {