package org.jnario.ui.contentassist;
import static java.util.Collections.emptyList;
import java.util.List;
import org.eclipse.emf.ecore.resource.Resource;
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.TextUtilities;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.xtend.core.xtend.XtendClass;
import org.eclipse.xtend.core.xtend.XtendFile;
import org.eclipse.xtext.common.types.xtext.ui.JdtTypesProposalProvider;
import org.eclipse.xtext.conversion.IValueConverter;
import org.eclipse.xtext.naming.IQualifiedNameConverter;
import org.eclipse.xtext.naming.QualifiedName;
import org.eclipse.xtext.nodemodel.ICompositeNode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.resource.IEObjectDescription;
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.xbase.conversion.XbaseQualifiedNameValueConverter;
import org.eclipse.xtext.xtype.XImportDeclaration;
import com.google.inject.Inject;
/**
* @author Sebastian Zarnekow - Initial contribution and API
*/
public class ImportingTypesProposalProvider extends JdtTypesProposalProvider {
@Inject
private XbaseQualifiedNameValueConverter qualifiedNameValueConverter;
@Override
protected IReplacementTextApplier createTextApplier(ContentAssistContext context, IScope typeScope,
IQualifiedNameConverter qualifiedNameConverter, IValueConverter<String> valueConverter) {
if (context.getCurrentModel() instanceof XImportDeclaration)
return super.createTextApplier(context, typeScope, qualifiedNameConverter, valueConverter);
return new FQNImporter(context.getResource(), context.getViewer(), typeScope, qualifiedNameConverter, valueConverter, qualifiedNameValueConverter);
}
public static class FQNImporter extends FQNShortener {
private final ITextViewer viewer;
private final XbaseQualifiedNameValueConverter importConverter;
public FQNImporter(Resource context, ITextViewer viewer, IScope scope, IQualifiedNameConverter qualifiedNameConverter,
IValueConverter<String> valueConverter, XbaseQualifiedNameValueConverter importConverter) {
super(context, scope, qualifiedNameConverter, valueConverter);
this.viewer = viewer;
this.importConverter = importConverter;
}
@Override
public void apply(IDocument document, ConfigurableCompletionProposal proposal) throws BadLocationException {
String proposalReplacementString = proposal.getReplacementString();
String typeName = proposalReplacementString;
if (valueConverter != null)
typeName = valueConverter.toValue(proposalReplacementString, null);
String replacementString = getActualReplacementString(proposal);
// there is an import statement - apply computed replacementString
if (!proposalReplacementString.equals(replacementString)) {
String shortTypeName = replacementString;
if (valueConverter != null)
shortTypeName = valueConverter.toValue(replacementString, null);
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
XtendFile file = (XtendFile) context.getContents().get(0);
XtendClass clazz = (XtendClass) (file.getXtendTypes().isEmpty() ? null : file.getXtendTypes().get(0));
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;
}
IEObjectDescription description = scope.getSingleElement(qualifiedName.skipFirst(qualifiedName.getSegmentCount() - 1));
if (description != null) {
// there exists a conflict - insert fully qualified name
proposal.setCursorPosition(proposalReplacementString.length());
document.replace(proposal.getReplacementOffset(), proposal.getReplacementLength(), proposalReplacementString);
return;
}
// Import does not introduce ambiguities - add import and insert short name
String shortName = qualifiedName.getLastSegment();
int topPixel = -1;
// store the pixel coordinates to prevent the ui from flickering
StyledText widget = viewer.getTextWidget();
if (widget != null)
topPixel = widget.getTopPixel();
ITextViewerExtension viewerExtension = null;
if (viewer instanceof ITextViewerExtension) {
viewerExtension = (ITextViewerExtension) viewer;
viewerExtension.setRedraw(false);
}
DocumentRewriteSession rewriteSession = null;
String lineSeparator = TextUtilities.getDefaultLineDelimiter(document);
try {
if (document instanceof IDocumentExtension4) {
rewriteSession = ((IDocumentExtension4) document).startRewriteSession(DocumentRewriteSessionType.UNRESTRICTED_SMALL);
}
// compute import statement's offset
int offset = 0;
boolean startWithLineBreak = true;
boolean endWithLineBreak = false;
List<XImportDeclaration> imports = emptyList();
if(file.getImportSection() != null){
imports =file.getImportSection().getImportDeclarations();
}
if (imports.isEmpty()) {
startWithLineBreak = false;
if (clazz == null) {
offset = document.getLength();
} else {
ICompositeNode node = NodeModelUtils.getNode(clazz);
if (node == null) {
throw new IllegalStateException("node may not be null");
}
offset = node.getOffset();
endWithLineBreak = true;
}
} else {
ICompositeNode node = NodeModelUtils.getNode(imports.get(imports.size() - 1));
if (node == null) {
throw new IllegalStateException("node may not be null");
}
offset = node.getOffset() + node.getLength();
}
offset = Math.min(proposal.getReplacementOffset(), offset);
// apply short proposal
String escapedShortname = shortName;
if (valueConverter != null) {
escapedShortname = valueConverter.toString(shortName);
}
proposal.setCursorPosition(escapedShortname.length());
document.replace(proposal.getReplacementOffset(), proposal.getReplacementLength(), escapedShortname);
// add import statement
String importStatement = importConverter.toString(typeName);
for (XImportDeclaration importDeclaration : imports) {
if(importDeclaration == null || importStatement.equals(importDeclaration.getImportedNamespace())){
return;
}
}
importStatement = (startWithLineBreak ? lineSeparator + "import " : "import ") + importStatement;
if (endWithLineBreak) {
importStatement += lineSeparator;
importStatement += lineSeparator;
}
document.replace(offset, 0, importStatement);
proposal.setCursorPosition(proposal.getCursorPosition() + importStatement.length());
// set the pixel coordinates
if (widget != null) {
int additionalTopPixel = 0;
if (startWithLineBreak)
additionalTopPixel += widget.getLineHeight();
if (endWithLineBreak)
additionalTopPixel += 2 * widget.getLineHeight();
widget.setTopPixel(topPixel + additionalTopPixel);
}
} finally {
if (rewriteSession != null) {
((IDocumentExtension4) document).stopRewriteSession(rewriteSession);
}
if (viewerExtension != null)
viewerExtension.setRedraw(true);
}
}
}
}