package com.redhat.ceylon.eclipse.code.correct;
import static com.redhat.ceylon.eclipse.ui.CeylonResources.MINOR_CHANGE;
import static com.redhat.ceylon.eclipse.util.Nodes.findStatement;
import java.util.List;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.ICompletionProposalExtension6;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.jface.text.link.LinkedPosition;
import org.eclipse.jface.viewers.StyledString;
import org.eclipse.ltk.core.refactoring.DocumentChange;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import com.redhat.ceylon.compiler.typechecker.tree.Node;
import com.redhat.ceylon.compiler.typechecker.tree.Tree;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.InvocationExpression;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.TypedDeclaration;
import com.redhat.ceylon.eclipse.code.editor.CeylonEditor;
import com.redhat.ceylon.eclipse.code.parse.CeylonParseController;
import com.redhat.ceylon.eclipse.code.refactor.AbstractLinkedMode;
import com.redhat.ceylon.eclipse.code.refactor.RefactorInformationPopup;
import com.redhat.ceylon.eclipse.util.EditorUtil;
import com.redhat.ceylon.eclipse.util.Highlights;
import com.redhat.ceylon.eclipse.util.Nodes;
import com.redhat.ceylon.model.typechecker.model.Declaration;
import com.redhat.ceylon.model.typechecker.model.Type;
import com.redhat.ceylon.model.typechecker.model.Unit;
public abstract class LocalProposal extends AbstractLinkedMode
implements ICompletionProposal, ICompletionProposalExtension6 {
protected int offset;
protected int exitPos;
protected Type type;
protected String initialName;
protected String[] nameProposals;
protected final int currentOffset;
@Override
protected String getHintTemplate() {
return "Enter type and name for new local {0}";
}
@Override
protected final void updatePopupLocation() {
LinkedPosition currentLinkedPosition =
getCurrentLinkedPosition();
RefactorInformationPopup popup = getInfoPopup();
if (currentLinkedPosition==null) {
popup.setHintTemplate(getHintTemplate());
}
else if (currentLinkedPosition.getSequenceNumber()==1) {
popup.setHintTemplate("Enter type for new local {0}");
}
else {
popup.setHintTemplate("Enter name for new local {0}");
}
}
private void performInitialChange(IDocument document) {
Tree.Statement st = findStatement(rootNode, node);
Node expression;
Node expanse;
Type resultType;
Unit unit = node.getUnit();
if (st instanceof Tree.ExpressionStatement) {
Tree.ExpressionStatement es =
(Tree.ExpressionStatement) st;
Tree.Expression e = es.getExpression();
expression = e;
expanse = st;
resultType = e.getTypeModel();
Tree.Term term = e.getTerm();
if (term instanceof Tree.InvocationExpression) {
Tree.InvocationExpression ie =
(Tree.InvocationExpression) term;
Tree.Primary primary = ie.getPrimary();
if (primary instanceof Tree.QualifiedMemberExpression) {
Tree.QualifiedMemberExpression prim =
(Tree.QualifiedMemberExpression)
primary;
if (prim.getMemberOperator().getToken()==null) {
//an expression followed by two annotations
//can look like a named operator expression
//even though that is disallowed as an
//expression statement
Tree.Primary p = prim.getPrimary();
expression = p;
expanse = expression;
resultType = p.getTypeModel();
}
}
}
}
else if (st instanceof Tree.Declaration) {
Tree.Declaration dec = (Tree.Declaration) st;
Declaration d = dec.getDeclarationModel();
if (d==null || d.isToplevel()) {
return;
}
//some expressions get interpreted as annotations
List<Tree.Annotation> annotations =
dec.getAnnotationList()
.getAnnotations();
Tree.AnonymousAnnotation aa =
dec.getAnnotationList()
.getAnonymousAnnotation();
if (aa!=null && currentOffset<=aa.getEndIndex()) {
expression = aa;
expanse = expression;
resultType = unit.getStringType();
}
else if (!annotations.isEmpty() &&
currentOffset<=dec.getAnnotationList().getEndIndex()) {
Tree.Annotation a = annotations.get(0);
expression = a;
expanse = expression;
resultType = a.getTypeModel();
}
else if (st instanceof Tree.TypedDeclaration) {
//some expressions look like a type declaration
//when they appear right in front of an annotation
//or function invocations
TypedDeclaration td =
(Tree.TypedDeclaration) st;
Tree.Type type = td.getType();
Type t = type.getTypeModel();
if (type instanceof Tree.SimpleType) {
expression = type;
expanse = expression;
resultType = t;
}
else if (type instanceof Tree.FunctionType) {
expression = type;
expanse = expression;
resultType = unit.getCallableReturnType(t);
}
else {
return;
}
}
else {
return;
}
}
else {
return;
}
int startIndex = expanse.getStartIndex();
int endIndex = expanse.getEndIndex();
if (currentOffset<startIndex ||
currentOffset>endIndex) {
return;
}
nameProposals = computeNameProposals(expression);
initialName = nameProposals[0];
offset = startIndex;
type = resultType==null ? null :
unit.denotableType(resultType);
DocumentChange change =
createChange(document, expanse, endIndex);
EditorUtil.performChange(change);
}
String[] computeNameProposals(Node expression) {
return Nodes.nameProposals(expression);
}
protected abstract DocumentChange createChange(
IDocument document, Node expanse, int endIndex);
protected final Node node;
protected final Tree.CompilationUnit rootNode;
@Override
public void apply(IDocument document) {
try {
performInitialChange(document);
CeylonParseController cpc =
editor.getParseController();
Unit unit = cpc.getLastCompilationUnit().getUnit();
addLinkedPositions(document, unit);
enterLinkedMode(document,
getExitSequenceNumber(),
getExitPosition());
openPopup();
}
catch (BadLocationException e) {
e.printStackTrace();
}
}
int getExitSequenceNumber() {
return 2;
}
protected int getExitPosition() {
return exitPos + initialName.length() + 9;
}
protected abstract void addLinkedPositions(
IDocument document, Unit unit)
throws BadLocationException;
@Override
public StyledString getStyledDisplayString() {
return Highlights.styleProposal(getDisplayString(),
false, true);
}
@Override
public Point getSelection(IDocument document) {
return null;
}
@Override
public String getAdditionalProposalInfo() {
return null;
}
@Override
public Image getImage() {
return MINOR_CHANGE;
}
@Override
public IContextInformation getContextInformation() {
return null;
}
public LocalProposal(CeylonEditor ceylonEditor,
Tree.CompilationUnit cu, Node node,
int currentOffset) {
super(ceylonEditor);
this.rootNode = cu;
this.node = node;
this.currentOffset = currentOffset;
}
boolean isEnabled(Type resultType) {
return true;
}
boolean isEnabled() {
Tree.Statement st = findStatement(rootNode, node);
if (st instanceof Tree.ExpressionStatement) {
Tree.ExpressionStatement es =
(Tree.ExpressionStatement) st;
Tree.Expression e = es.getExpression();
Type resultType = e.getTypeModel();
Tree.Term term = e.getTerm();
if (term instanceof Tree.InvocationExpression) {
InvocationExpression ie =
(Tree.InvocationExpression) term;
Tree.Primary primary = ie.getPrimary();
if (primary instanceof Tree.QualifiedMemberExpression) {
Tree.QualifiedMemberExpression prim =
(Tree.QualifiedMemberExpression)
primary;
if (prim.getMemberOperator().getToken()==null) {
//an expression followed by two annotations
//can look like a named operator expression
//even though that is disallowed as an
//expression statement
Tree.Primary p = prim.getPrimary();
resultType = p.getTypeModel();
}
}
}
return isEnabled(resultType);
}
else if (st instanceof Tree.Declaration) {
Unit unit = node.getUnit();
Tree.Declaration dec = (Tree.Declaration) st;
Tree.Identifier id = dec.getIdentifier();
if (id==null) {
return false;
}
int line = id.getToken().getLine();
Declaration d = dec.getDeclarationModel();
if (d==null || d.isToplevel()) {
return false;
}
//some expressions get interpreted as annotations
List<Tree.Annotation> annotations =
dec.getAnnotationList()
.getAnnotations();
Tree.AnonymousAnnotation aa =
dec.getAnnotationList()
.getAnonymousAnnotation();
Type resultType;
if (aa!=null && currentOffset<=aa.getEndIndex()) {
if (aa.getEndToken().getLine()==line) {
return false;
}
resultType = unit.getStringType();
}
else if (!annotations.isEmpty() &&
currentOffset<=dec.getAnnotationList().getEndIndex()) {
Tree.Annotation a = annotations.get(0);
if (a.getEndToken().getLine()==line) {
return false;
}
resultType = a.getTypeModel();
}
else if (st instanceof Tree.TypedDeclaration &&
!(st instanceof Tree.ObjectDefinition)) {
//some expressions look like a type declaration
//when they appear right in front of an annotation
//or function invocations
TypedDeclaration td =
(Tree.TypedDeclaration) st;
Tree.Type type = td.getType();
if (currentOffset<=type.getEndIndex() &&
currentOffset>=type.getStartIndex() &&
type.getEndToken().getLine()!=line) {
resultType = type.getTypeModel();
if (type instanceof Tree.SimpleType) {
//just use that type
}
else if (type instanceof Tree.FunctionType) {
//instantiation expressions look like a
//function type declaration
resultType =
unit.getCallableReturnType(
resultType);
}
else {
return false;
}
}
else {
return false;
}
}
else {
return false;
}
return isEnabled(resultType);
}
return false;
}
}