[xtend generator] Migration of 'ContentAssistFragment'

Signed-off-by: Christian Schneider <christian.schneider@itemis.de>
This commit is contained in:
Christian Schneider 2015-10-08 13:39:30 +02:00
parent 513f1462ca
commit 98861a3e65
6 changed files with 335 additions and 12 deletions

View file

@ -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,

View file

@ -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(<Assignment>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(<AbstractRule>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<AbstractElement> terminals) {
val processedTerminals = newHashSet();
// for each type of terminal occurring in 'terminals' (evaluated lazily) ...
val candidates = terminals.fold(<AbstractElement>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<Grammar> visitedGrammars) {
for (s : current.usedGrammars) {
if (!visitedGrammars.contains(s)) {
visitedGrammars.add(s)
computeAllSuperGrammars(s, visitedGrammars)
}
}
}
}

View file

@ -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 {

View file

@ -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 {

View file

@ -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 {

View file

@ -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 {