mirror of
https://github.com/sigmasternchen/xtext-core
synced 2025-03-16 08:48:55 +00:00
[xtend generator] Migration of 'ContentAssistFragment'
Signed-off-by: Christian Schneider <christian.schneider@itemis.de>
This commit is contained in:
parent
513f1462ca
commit
98861a3e65
6 changed files with 335 additions and 12 deletions
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue