package com.redhat.ceylon.eclipse.code.correct; import static com.redhat.ceylon.eclipse.code.correct.ImportProposals.importProposals; import static com.redhat.ceylon.eclipse.java2ceylon.Java2CeylonProxies.completionJ2C; import static com.redhat.ceylon.eclipse.ui.CeylonResources.MINOR_CHANGE; import static com.redhat.ceylon.eclipse.util.EditorUtil.getDocument; import static com.redhat.ceylon.eclipse.util.Nodes.findNode; import static com.redhat.ceylon.eclipse.util.Nodes.getIdentifyingNode; import static com.redhat.ceylon.eclipse.util.Nodes.getOccurrenceLocation; import static com.redhat.ceylon.ide.common.util.OccurrenceLocation.IMPORT; import static java.lang.Character.isUpperCase; import static java.util.Collections.singleton; import java.util.Collection; import java.util.List; import org.eclipse.core.resources.IFile; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.jface.viewers.StyledString; import org.eclipse.ltk.core.refactoring.TextChange; import org.eclipse.ltk.core.refactoring.TextFileChange; import org.eclipse.text.edits.InsertEdit; import org.eclipse.text.edits.MultiTextEdit; import org.eclipse.text.edits.ReplaceEdit; import com.redhat.ceylon.compiler.typechecker.tree.Node; import com.redhat.ceylon.compiler.typechecker.tree.Tree; import com.redhat.ceylon.compiler.typechecker.util.NormalizedLevenshtein; import com.redhat.ceylon.eclipse.util.Highlights; import com.redhat.ceylon.ide.common.util.OccurrenceLocation; import com.redhat.ceylon.model.typechecker.model.Declaration; import com.redhat.ceylon.model.typechecker.model.DeclarationWithProximity; import com.redhat.ceylon.model.typechecker.model.Module; import com.redhat.ceylon.model.typechecker.model.NamedArgumentList; import com.redhat.ceylon.model.typechecker.model.Parameter; import com.redhat.ceylon.model.typechecker.model.ParameterList; import com.redhat.ceylon.model.typechecker.model.Scope; class ChangeReferenceProposal extends CorrectionProposal { @Deprecated private ChangeReferenceProposal(ProblemLocation problem, String name, String pkg, TextFileChange change) { super("Change reference to '" + name + "'" + pkg, change, new Region(problem.getOffset(), name.length()), MINOR_CHANGE); } ChangeReferenceProposal(String desc, TextChange change, Region selection) { super(desc, change, selection, MINOR_CHANGE); } @Deprecated static void addChangeReferenceProposal( ProblemLocation problem, Collection<ICompletionProposal> proposals, IFile file, String brokenName, Declaration dec, Tree.CompilationUnit rootNode) { TextFileChange change = new TextFileChange("Change Reference", file); change.setEdit(new MultiTextEdit()); IDocument doc = getDocument(change); String pkg = ""; int problemOffset = problem.getOffset(); if (dec.isToplevel() && !importProposals().isImported(dec, rootNode) && isInPackage(rootNode, dec)) { String pn = dec.getContainer() .getQualifiedNameString(); pkg = " in '" + pn + "'"; if (!pn.isEmpty() && !pn.equals(Module.LANGUAGE_MODULE_NAME)) { OccurrenceLocation ol = getOccurrenceLocation(rootNode, findNode(rootNode, problemOffset), problemOffset); if (ol!=IMPORT) { List<InsertEdit> ies = importProposals().importEdits(rootNode, singleton(dec), null, null, doc); for (InsertEdit ie: ies) { change.addEdit(ie); } } } } change.addEdit(new ReplaceEdit(problemOffset, brokenName.length(), dec.getName())); //Note: don't use problem.getLength() because it's wrong from the problem list proposals.add(new ChangeReferenceProposal(problem, dec.getName(), pkg, change)); } @Deprecated protected static boolean isInPackage( Tree.CompilationUnit cu, Declaration dec) { return !dec.getUnit().getPackage() .equals(cu.getUnit().getPackage()); } static void addChangeReferenceProposals( Tree.CompilationUnit rootNode, Node node, ProblemLocation problem, Collection<ICompletionProposal> proposals, IFile file) { Node id = getIdentifyingNode(node); if (id!=null) { String brokenName = id.getText(); if (brokenName!=null && !brokenName.isEmpty()) { Scope scope = node.getScope(); Collection<DeclarationWithProximity> dwps = completionJ2C().getProposals(node, scope, rootNode) .values(); for (DeclarationWithProximity dwp: dwps) { processProposal(rootNode, problem, proposals, file, brokenName, dwp.getDeclaration()); } } } } @Deprecated static void addChangeArgumentReferenceProposals( Tree.CompilationUnit rootNode, Node node, ProblemLocation problem, Collection<ICompletionProposal> proposals, IFile file) { String brokenName = getIdentifyingNode(node) .getText(); if (brokenName!=null && !brokenName.isEmpty()) { if (node instanceof Tree.NamedArgument) { Scope scope = node.getScope(); if (!(scope instanceof NamedArgumentList)) { scope = scope.getScope(); //for declaration-style named args } NamedArgumentList namedArgumentList = (NamedArgumentList) scope; ParameterList parameterList = namedArgumentList.getParameterList(); if (parameterList!=null) { for (Parameter parameter: parameterList.getParameters()) { Declaration declaration = parameter.getModel(); if (declaration!=null) { processProposal(rootNode, problem, proposals, file, brokenName, declaration); } } } } } } @Deprecated private static void processProposal( Tree.CompilationUnit rootNode, ProblemLocation problem, Collection<ICompletionProposal> proposals, IFile file, String brokenName, Declaration declaration) { String name = declaration.getName(); if (!brokenName.equals(name)) { boolean nuc = isUpperCase(name.codePointAt(0)); boolean bnuc = isUpperCase(brokenName.codePointAt(0)); if (nuc==bnuc) { double similarity = distance.similarity(brokenName, name); //TODO: would it be better to just sort by distance, // and then select the 3 closest possibilities? if (similarity > 0.6) { addChangeReferenceProposal(problem, proposals, file, brokenName, declaration, rootNode); } } } } @Override public StyledString getStyledDisplayString() { return Highlights.styleProposal(getDisplayString(), true); } static final NormalizedLevenshtein distance = new NormalizedLevenshtein(); }