blob: efc635d9ccdf5a94361dfe9fe5ae75aba0460ba2 [file] [log] [blame]
/*
* generated by Xtext
*/
package org.eclipse.osbp.xtext.oxtype.ui.contentassist
import com.google.common.base.Function
import com.google.common.base.Predicate
import com.google.inject.Inject
import java.util.List
import org.apache.log4j.Logger
import org.eclipse.core.runtime.Assert
import org.eclipse.emf.ecore.EObject
import org.eclipse.emf.ecore.EReference
import org.eclipse.emf.ecore.resource.Resource
import org.eclipse.emf.ecore.util.EcoreUtil
import org.eclipse.jface.text.BadLocationException
import org.eclipse.jface.text.DocumentRewriteSession
import org.eclipse.jface.text.DocumentRewriteSessionType
import org.eclipse.jface.text.IDocument
import org.eclipse.jface.text.IDocumentExtension4
import org.eclipse.jface.text.ITextViewer
import org.eclipse.jface.text.ITextViewerExtension
import org.eclipse.jface.text.contentassist.ICompletionProposal
import org.eclipse.osbp.xtext.oxtype.imports.IShouldImportProvider
import org.eclipse.swt.custom.StyledText
import org.eclipse.text.edits.MultiTextEdit
import org.eclipse.text.edits.ReplaceEdit
import org.eclipse.text.edits.TextEdit
import org.eclipse.xtend.lib.annotations.Data
import org.eclipse.xtext.CrossReference
import org.eclipse.xtext.EcoreUtil2
import org.eclipse.xtext.GrammarUtil
import org.eclipse.xtext.ParserRule
import org.eclipse.xtext.RuleCall
import org.eclipse.xtext.common.types.JvmDeclaredType
import org.eclipse.xtext.common.types.TypesPackage
import org.eclipse.xtext.conversion.IValueConverter
import org.eclipse.xtext.naming.IQualifiedNameConverter
import org.eclipse.xtext.naming.QualifiedName
import org.eclipse.xtext.resource.IEObjectDescription
import org.eclipse.xtext.resource.XtextResource
import org.eclipse.xtext.resource.impl.AliasedEObjectDescription
import org.eclipse.xtext.scoping.IScope
import org.eclipse.xtext.ui.editor.contentassist.ConfigurableCompletionProposal
import org.eclipse.xtext.ui.editor.contentassist.ConfigurableCompletionProposal.IReplacementTextApplier
import org.eclipse.xtext.ui.editor.contentassist.ContentAssistContext
import org.eclipse.xtext.ui.editor.contentassist.ICompletionProposalAcceptor
import org.eclipse.xtext.ui.editor.contentassist.ReplacementTextApplier
import org.eclipse.xtext.util.ReplaceRegion
import org.eclipse.xtext.xbase.XbasePackage
import org.eclipse.xtext.xbase.conversion.XbaseQualifiedNameValueConverter
import org.eclipse.xtext.xbase.imports.RewritableImportSection
import org.eclipse.xtext.xbase.ui.contentassist.XbaseReferenceProposalCreator
import org.eclipse.xtext.xbase.ui.imports.ReplaceConverter
import org.eclipse.xtext.xtype.XImportSection
import static org.eclipse.xtext.util.Strings.notNull
import org.eclipse.osbp.xtext.oxtype.imports.OXTypeRewritableImportSection
@SuppressWarnings("restriction")
class OXtypeProposalProvider extends AbstractOXtypeProposalProvider {
@Inject IShouldImportProvider importProvider
override void lookupCrossReference(CrossReference crossReference, EReference reference,
ContentAssistContext contentAssistContext, ICompletionProposalAcceptor acceptor,
Predicate<IEObjectDescription> filter) {
var String ruleName = null;
if (crossReference.getTerminal() instanceof RuleCall) {
ruleName = (crossReference.getTerminal() as RuleCall).getRule().getName();
}
if (importProvider.shouldAutoImport(contentAssistContext.getCurrentModel(), reference)) {
// if the type should be imported, pass a custom acceptor to pass additional information to the wrapped proposal factory
val customAcceptor = new ImportAwareCompletionProposalAcceptor(acceptor, contentAssistContext);
lookupCrossReference(contentAssistContext.getCurrentModel(), reference, customAcceptor, filter,
getProposalFactory(ruleName, contentAssistContext));
} else {
lookupCrossReference(contentAssistContext.getCurrentModel(), reference, acceptor, filter,
getProposalFactory(ruleName, contentAssistContext));
}
}
override void lookupCrossReference(CrossReference crossReference, ContentAssistContext contentAssistContext,
ICompletionProposalAcceptor acceptor, Predicate<IEObjectDescription> filter,
Function<IEObjectDescription, ICompletionProposal> proposalFactory) {
val ParserRule containingParserRule = GrammarUtil.containingParserRule(crossReference);
if (!GrammarUtil.isDatatypeRule(containingParserRule)) {
val EReference ref = GrammarUtil.getReference(crossReference);
if (importProvider.shouldAutoImport(contentAssistContext.getCurrentModel(), ref)) {
// if the type should be imported, pass a custom acceptor to pass additional information to the wrapped proposal factory
val customAcceptor = new ImportAwareCompletionProposalAcceptor(acceptor, contentAssistContext);
lookupCrossReference(contentAssistContext.getCurrentModel(), ref, customAcceptor, filter,
proposalFactory);
} else {
lookupCrossReference(contentAssistContext.getCurrentModel(), ref, acceptor, filter,
proposalFactory);
}
}
}
/**
* Is responsible to create the completion proposals
*/
static class CustomReferenceProposalCreator extends XbaseReferenceProposalCreator {
@Inject IShouldImportProvider importProvider
@Inject IQualifiedNameConverter nameConverter
@Inject
private RewritableImportSection.Factory importSectionFactory;
@Inject
private ReplaceConverter replaceConverter;
@Inject
XbaseQualifiedNameValueConverter valueConverter;
override void lookupCrossReference(IScope scope, EObject model, EReference reference,
ICompletionProposalAcceptor acceptor, Predicate<IEObjectDescription> filter,
Function<IEObjectDescription, ICompletionProposal> proposalFactory) {
if (TypesPackage.Literals.JVM_TYPE.isSuperTypeOf(getEReferenceType(model, reference)) ||
reference === XbasePackage.Literals.XABSTRACT_FEATURE_CALL__FEATURE) {
super.lookupCrossReference(scope, model, reference, acceptor, filter, proposalFactory)
return
}
val Function<IEObjectDescription, ICompletionProposal> wrappedFactory = getWrappedFactory(scope, model,
reference, proposalFactory, acceptor);
if (importProvider.shouldProposeAllElements(model, reference)) {
for (IEObjectDescription candidate : scope.allElements) {
if (!acceptor.canAcceptMoreProposals())
return;
var transformed = candidate
val name = candidate.name;
if (name.segmentCount > 1) {
transformed = new AliasedEObjectDescription(QualifiedName.create(name.lastSegment),
candidate)
}
if (filter.apply(transformed)) {
acceptor.accept(wrappedFactory.apply(transformed));
}
}
} else {
val Iterable<IEObjectDescription> candidates = queryScope(scope, model, reference, filter);
for (IEObjectDescription candidate : candidates) {
if (!acceptor.canAcceptMoreProposals())
return;
if (filter.apply(candidate)) {
acceptor.accept(wrappedFactory.apply(candidate));
}
}
}
}
def Function<IEObjectDescription, ICompletionProposal> getWrappedFactory(IScope scope, EObject model,
EReference reference, Function<IEObjectDescription, ICompletionProposal> proposalFactory,
ICompletionProposalAcceptor acceptor) {
if (!TypesPackage.Literals.JVM_TYPE.isSuperTypeOf(getEReferenceType(model, reference)) &&
acceptor instanceof ImportAwareCompletionProposalAcceptor) {
// create the default factory
val factory = super.getWrappedFactory(model, reference, proposalFactory)
// wrap the factory and initialize the text applier for import stuff
val customAcceptor = acceptor as ImportAwareCompletionProposalAcceptor
val complContext = customAcceptor.context
return new Function<IEObjectDescription, ICompletionProposal>() {
override ICompletionProposal apply(IEObjectDescription from) {
val ICompletionProposal result = factory.apply(from);
if (result instanceof ConfigurableCompletionProposal) {
result.textApplier = createTextApplier(complContext, scope, nameConverter,
valueConverter)
result.setAdditionalData(IEObjectDescription.name, from)
}
return result;
}
};
} else {
return super.getWrappedFactory(model, reference, proposalFactory);
}
}
def IReplacementTextApplier createTextApplier(ContentAssistContext context, IScope typeScope,
IQualifiedNameConverter qualifiedNameConverter, IValueConverter<String> valueConverter) {
if (EcoreUtil2.getContainerOfType(context.getCurrentModel(), XImportSection) !== null)
return null;
return new FQNImporter(context.getResource(), context.getViewer(), typeScope, qualifiedNameConverter,
valueConverter, importSectionFactory, replaceConverter);
}
}
static class FQNImporter extends ReplacementTextApplier {
private static final Logger LOG = Logger.getLogger(FQNImporter);
private final ITextViewer viewer;
RewritableImportSection.Factory importSectionFactory;
ReplaceConverter replaceConverter;
IValueConverter<String> valueConverter
IQualifiedNameConverter qualifiedNameConverter
IScope scope
Resource context
new(Resource context, ITextViewer viewer, IScope scope, IQualifiedNameConverter qualifiedNameConverter,
IValueConverter<String> valueConverter, RewritableImportSection.Factory importSectionFactory,
ReplaceConverter replaceConverter) {
this.viewer = viewer;
this.context = context
this.qualifiedNameConverter = qualifiedNameConverter;
this.scope = scope;
this.qualifiedNameConverter = qualifiedNameConverter;
this.valueConverter = valueConverter
this.importSectionFactory = importSectionFactory;
this.replaceConverter = replaceConverter;
}
override void apply(IDocument document,
ConfigurableCompletionProposal proposal) throws BadLocationException {
val IEObjectDescription desc = proposal.getAdditionalData(IEObjectDescription.name) as IEObjectDescription
val toImportQualifiedName = desc.qualifiedName
val String proposalReplacementString = proposal.getReplacementString();
var String typeName = proposalReplacementString;
if (valueConverter !== null)
typeName = valueConverter.toValue(toImportQualifiedName.toString, null);
val String replacementString = getActualReplacementString(proposal);
// there is an import statement - apply computed replacementString
if (!proposalReplacementString.equals(replacementString)) {
var String shortTypeName = replacementString;
if (valueConverter !== null)
shortTypeName = valueConverter.toValue(replacementString, null);
val QualifiedName shortQualifiedName = qualifiedNameConverter.toQualifiedName(shortTypeName);
if (shortQualifiedName.getSegmentCount() === 1) {
proposal.setCursorPosition(replacementString.length());
document.replace(proposal.getReplacementOffset(), proposal.getReplacementLength(),
replacementString);
return;
}
}
// we could create an import statement if there is no conflict
val QualifiedName qualifiedName = qualifiedNameConverter.toQualifiedName(typeName);
if (qualifiedName.getSegmentCount() === 1) {
// type resides in default package - no need to hassle with imports
proposal.setCursorPosition(proposalReplacementString.length());
document.replace(proposal.getReplacementOffset(), proposal.getReplacementLength(),
proposalReplacementString);
return;
}
val IEObjectDescription description = scope.getSingleElement(
qualifiedName.skipFirst(qualifiedName.getSegmentCount() - 1));
if (description !== null) {
// already imported
proposal.setCursorPosition(proposalReplacementString.length());
document.replace(proposal.getReplacementOffset(), proposal.getReplacementLength(),
proposalReplacementString);
return;
}
// Import does not introduce ambiguities - add import and insert short name
val String shortName = qualifiedName.getLastSegment();
var int topPixel = -1;
// store the pixel coordinates to prevent the ui from flickering
val StyledText widget = viewer.getTextWidget();
if (widget !== null)
topPixel = widget.getTopPixel();
var ITextViewerExtension viewerExtension = null;
if (viewer instanceof ITextViewerExtension) {
viewerExtension = viewer as ITextViewerExtension;
viewerExtension.setRedraw(false);
}
val OXTypeRewritableImportSection importSection = importSectionFactory.parse(context as XtextResource) as OXTypeRewritableImportSection;
val IEObjectDescription typeToImport = scope.getSingleElement(qualifiedName);
if (typeToImport === null) {
LOG.error("Could not find unique type named '" + notNull(qualifiedName) + "' in scope");
if (viewerExtension !== null)
viewerExtension.setRedraw(true);
return;
}
val EObject resolved = EcoreUtil.resolve(typeToImport.getEObjectOrProxy(), context);
Assert.isTrue(!resolved.eIsProxy());
importSection.addFQNImport(typeName);
var DocumentRewriteSession rewriteSession = null;
try {
if (document instanceof IDocumentExtension4) {
rewriteSession = (document as IDocumentExtension4).startRewriteSession(
DocumentRewriteSessionType.UNRESTRICTED_SMALL);
}
// apply short proposal
var String escapedShortname = shortName;
if (valueConverter !== null) {
escapedShortname = valueConverter.toString(shortName);
}
proposal.setCursorPosition(escapedShortname.length());
val int initialCursorLine = document.getLineOfOffset(proposal.getReplacementOffset());
val ReplaceEdit replaceEdit = new ReplaceEdit(proposal.getReplacementOffset(),
proposal.getReplacementLength(), escapedShortname);
// add import statement
val List<ReplaceRegion> importChanges = importSection.rewrite();
var TextEdit textEdit = replaceConverter.convertToTextEdit(importChanges);
if (textEdit !== null) {
val MultiTextEdit compound = new MultiTextEdit();
compound.addChild(textEdit);
compound.addChild(replaceEdit);
textEdit = compound;
} else {
textEdit = replaceEdit;
}
textEdit.apply(document);
val int cursorPosition = proposal.getCursorPosition() +
replaceConverter.getReplaceLengthDelta(importChanges, proposal.getReplacementOffset());
proposal.setCursorPosition(cursorPosition);
val int newCursorLine = document.getLineOfOffset(cursorPosition);
// set the pixel coordinates
if (widget !== null) {
val int additionalTopPixel = (newCursorLine - initialCursorLine) * widget.getLineHeight();
widget.setTopPixel(topPixel + additionalTopPixel);
}
} finally {
if (rewriteSession !== null) {
(document as IDocumentExtension4).stopRewriteSession(rewriteSession);
}
if (viewerExtension !== null)
viewerExtension.setRedraw(true);
}
}
override getActualReplacementString(ConfigurableCompletionProposal proposal) {
var String replacementString = proposal.getReplacementString();
if (scope !== null) {
var String qualifiedNameAsString = replacementString;
if (valueConverter !== null) {
qualifiedNameAsString = valueConverter.toValue(qualifiedNameAsString, null);
}
val IEObjectDescription element = scope.getSingleElement(
qualifiedNameConverter.toQualifiedName(qualifiedNameAsString));
if (element !== null) {
val EObject resolved = EcoreUtil.resolve(element.getEObjectOrProxy(), context);
if (!resolved.eIsProxy()) {
val IEObjectDescription shortendElement = scope.getSingleElement(resolved);
if (shortendElement !== null)
replacementString = applyValueConverter(shortendElement.getName());
}
}
}
return replacementString;
}
def protected String applyValueConverter(QualifiedName qualifiedName) {
var String result = qualifiedNameConverter.toString(qualifiedName);
if(valueConverter !== null) result = valueConverter.toString(result);
return result;
}
}
@Data
static class ImportAwareCompletionProposalAcceptor extends ICompletionProposalAcceptor.Delegate {
final ContentAssistContext context
new(ICompletionProposalAcceptor delegate, ContentAssistContext context) {
super(delegate)
this.context = context
}
}
}