package com.redhat.ceylon.eclipse.code.complete; import static com.redhat.ceylon.eclipse.code.complete.CodeCompletions.appendPositionalArgs; import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.getAssignableLiterals; import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.getCurrentSpecifierRegion; import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.getProposedName; import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.getSortedProposedValues; import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.isIgnoredLanguageModuleClass; import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.isIgnoredLanguageModuleMethod; import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.isIgnoredLanguageModuleValue; import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.withinBounds; import static com.redhat.ceylon.eclipse.code.complete.EclipseCompletionProcessor.NO_COMPLETIONS; import static com.redhat.ceylon.eclipse.code.correct.ImportProposals.importProposals; import static com.redhat.ceylon.eclipse.code.hover.DocumentationHover.getDocumentationFor; import static com.redhat.ceylon.eclipse.code.outline.CeylonLabelProvider.getDecoratedImage; import static com.redhat.ceylon.eclipse.code.outline.CeylonLabelProvider.getImageForDeclaration; import static com.redhat.ceylon.eclipse.code.outline.CeylonLabelProvider.getRefinementIcon; import static com.redhat.ceylon.eclipse.code.preferences.CeylonPreferenceInitializer.LINKED_MODE_ARGUMENTS; import static com.redhat.ceylon.eclipse.ui.CeylonPlugin.getCompletionFont; import static com.redhat.ceylon.eclipse.ui.CeylonPlugin.getPreferences; import static com.redhat.ceylon.eclipse.ui.CeylonPlugin.imageRegistry; import static com.redhat.ceylon.eclipse.ui.CeylonResources.CEYLON_DEFAULT_REFINEMENT; import static com.redhat.ceylon.eclipse.ui.CeylonResources.CEYLON_FORMAL_REFINEMENT; import static com.redhat.ceylon.eclipse.ui.CeylonResources.CEYLON_LITERAL; import static com.redhat.ceylon.eclipse.util.EditorUtil.getCurrentEditor; import static com.redhat.ceylon.eclipse.util.EditorUtil.performChange; import static com.redhat.ceylon.model.typechecker.model.ModelUtil.getContainingClassOrInterface; import static com.redhat.ceylon.model.typechecker.model.ModelUtil.isNameMatching; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.jface.text.contentassist.ICompletionProposalExtension2; import org.eclipse.jface.text.contentassist.ICompletionProposalExtension6; import org.eclipse.jface.text.contentassist.IContextInformation; import org.eclipse.jface.text.link.LinkedModeModel; import org.eclipse.jface.text.link.ProposalPosition; import org.eclipse.jface.viewers.StyledString; import org.eclipse.ltk.core.refactoring.DocumentChange; import org.eclipse.swt.graphics.Font; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.text.edits.MultiTextEdit; import com.redhat.ceylon.compiler.typechecker.tree.Tree; import com.redhat.ceylon.eclipse.code.editor.CeylonEditor; import com.redhat.ceylon.eclipse.code.parse.CeylonParseController; import com.redhat.ceylon.eclipse.platform.platformJ2C; import com.redhat.ceylon.eclipse.util.Highlights; import com.redhat.ceylon.eclipse.util.LinkedMode; import com.redhat.ceylon.ide.common.platform.TextChange; import com.redhat.ceylon.model.typechecker.model.Class; import com.redhat.ceylon.model.typechecker.model.ClassOrInterface; import com.redhat.ceylon.model.typechecker.model.Constructor; import com.redhat.ceylon.model.typechecker.model.Declaration; import com.redhat.ceylon.model.typechecker.model.DeclarationWithProximity; import com.redhat.ceylon.model.typechecker.model.Function; import com.redhat.ceylon.model.typechecker.model.Functional; import com.redhat.ceylon.model.typechecker.model.ModelUtil; import com.redhat.ceylon.model.typechecker.model.Module; import com.redhat.ceylon.model.typechecker.model.NothingType; import com.redhat.ceylon.model.typechecker.model.Reference; import com.redhat.ceylon.model.typechecker.model.Scope; import com.redhat.ceylon.model.typechecker.model.Type; import com.redhat.ceylon.model.typechecker.model.TypedDeclaration; import com.redhat.ceylon.model.typechecker.model.Unit; import com.redhat.ceylon.model.typechecker.model.Value; import ceylon.interop.java.CeylonMutableSet; public final class RefinementCompletionProposal extends CompletionProposal { final class ReturnValueContextInfo implements IContextInformation { @Override public String getInformationDisplayString() { if (declaration instanceof TypedDeclaration) { return getType().asString(getUnit()); } else { return null; } } @Override public Image getImage() { if (declaration instanceof TypedDeclaration) { return getImageForDeclaration(getType().getDeclaration()); } else { return null; } } @Override public String getContextDisplayString() { return "Return value of '" + declaration.getName() + "'"; } } public static Image DEFAULT_REFINEMENT = imageRegistry().get(CEYLON_DEFAULT_REFINEMENT); public static Image FORMAL_REFINEMENT = imageRegistry().get(CEYLON_FORMAL_REFINEMENT); // @Deprecated // static void addRefinementProposal(int offset, // Declaration dec, ClassOrInterface ci, // Node node, Scope scope, String prefix, // CeylonParseController cpc, IDocument doc, // List<ICompletionProposal> result, // boolean preamble) { // boolean isInterface = scope instanceof Interface; // Reference pr = getRefinedProducedReference(scope, dec); // Unit unit = node.getUnit(); // result.add(new RefinementCompletionProposal(offset, // prefix, pr, // getRefinementDescriptionFor(dec, pr, unit), // getRefinementTextFor(dec, pr, unit, // isInterface, ci, // utilJ2C().indents().getDefaultLineDelimiter(doc) // + utilJ2C().indents().getIndent(node, doc), // true, preamble), // cpc, dec, scope, false, true)); // } // // @Deprecated // static void addNamedArgumentProposal(int offset, // String prefix, // CeylonParseController cpc, // List<ICompletionProposal> result, // Declaration dec, Scope scope) { // //TODO: type argument substitution using the // // Reference of the primary node // Unit unit = cpc.getLastCompilationUnit().getUnit(); // result.add(new RefinementCompletionProposal(offset, // prefix, // dec.getReference(), //TODO: this needs to do type arg substitution // getDescriptionFor(dec, unit), // getTextFor(dec, unit) + " = nothing;", // cpc, dec, scope, true, false)); // } // // @Deprecated // static void addInlineFunctionProposal(int offset, // Declaration dec, Scope scope, Node node, // String prefix, CeylonParseController cpc, // IDocument doc, // List<ICompletionProposal> result) { // //TODO: type argument substitution using the // // Reference of the primary node // if (dec.isParameter()) { // FunctionOrValue fov = (FunctionOrValue) dec; // Parameter p = fov.getInitializerParameter(); // Unit unit = node.getUnit(); // result.add(new RefinementCompletionProposal( // offset, prefix, // dec.getReference(), //TODO: this needs to do type arg substitution // getInlineFunctionDescriptionFor(p, null, unit), // getInlineFunctionTextFor(p, null, unit, // utilJ2C().indents().getDefaultLineDelimiter(doc) + // utilJ2C().indents().getIndent(node, doc)), // cpc, dec, scope, false, false)); // } // } // // @Deprecated // public static Reference getRefinedProducedReference( // Scope scope, Declaration d) { // return refinedProducedReference( // scope.getDeclaringType(d), d); // } // // @Deprecated // public static Reference getRefinedProducedReference( // Type superType, Declaration d) { // if (superType.isIntersection()) { // for (Type pt: superType.getSatisfiedTypes()) { // Reference result = // getRefinedProducedReference(pt, d); // if (result!=null) return result; // } // return null; //never happens? // } // else { // Type declaringType = // superType.getDeclaration() // .getDeclaringType(d); // if (declaringType==null) return null; // Type outerType = // superType.getSupertype( // declaringType.getDeclaration()); // return refinedProducedReference(outerType, d); // } // } // // @Deprecated // private static Reference refinedProducedReference( // Type outerType, Declaration d) { // List<Type> params = new ArrayList<Type>(); // if (d instanceof Generic) { // Generic g = (Generic) d; // for (TypeParameter tp: g.getTypeParameters()) { // params.add(tp.getType()); // } // } // return d.appliedReference(outerType, params); // } private final CeylonParseController cpc; private final Declaration declaration; private final Reference pr; private final boolean fullType; private final Scope scope; private boolean explicitReturnType; @Deprecated public RefinementCompletionProposal(int offset, String prefix, Reference pr, String desc, String text, CeylonParseController cpc, final Declaration dec, Scope scope, boolean fullType, boolean explicitReturnType) { super( offset, prefix, new ImageRetriever() { @Override public Image getImage() { return getRefinementIcon(dec); } }, desc, text); this.cpc = cpc; this.declaration = dec; this.pr = pr; this.fullType = fullType; this.scope = scope; this.explicitReturnType = explicitReturnType; } private Unit getUnit() { return cpc.getLastCompilationUnit().getUnit(); } @Override public StyledString getStyledDisplayString() { StyledString result = new StyledString(); String string = getDisplayString(); Font font = getCompletionFont(); if (string.startsWith("shared actual ")) { result.append(string.substring(0,14), new Highlights.FontStyler(font, Highlights.ANN_STYLER)); string = string.substring(14); } int loc = string.indexOf(' '); if (loc>=0) { Highlights.styleFragment(result, string.substring(0, loc), false, null, font); Highlights.styleFragment(result, string.substring(loc), false, currentPrefix, font); } else { Highlights.styleFragment(result, string, false, null, font); } return result; } private Type getType() { return fullType ? pr.getFullType() : pr.getType(); } @Override public Point getSelection(IDocument document) { int loc = text.indexOf("nothing;"); int length; int start; if (loc<0) { start = offset + text.length()-prefix.length(); if (text.endsWith("{}")) start--; length = 0; } else { start = offset + loc-prefix.length(); length = 7; } return new Point(start, length); } @Override public void apply(IDocument document) { try { performChange(createChange(document));; } catch (BadLocationException e) { e.printStackTrace(); } if (getPreferences().getBoolean(LINKED_MODE_ARGUMENTS)) { enterLinkedMode(document); } } private DocumentChange createChange(IDocument document) throws BadLocationException { DocumentChange change = new DocumentChange("Complete Refinement", document); TextChange commonChange = new platformJ2C() .newChange("Complete Refinement", change); change.setEdit(new MultiTextEdit()); HashSet<Declaration> decs = new HashSet<Declaration>(); Tree.CompilationUnit cu = cpc.getLastCompilationUnit(); if (explicitReturnType) { importProposals() .importSignatureTypes(declaration, cu, new CeylonMutableSet<>(null, decs)); } else { importProposals() .importParameterTypes(declaration, cu, new CeylonMutableSet<>(null, decs)); } int il = (int) importProposals() .applyImports(commonChange, new CeylonMutableSet<>(null, decs), cu, commonChange.getDocument()); change.addEdit(createEdit(document)); offset+=il; return change; } @Override public String getAdditionalProposalInfo() { return getAdditionalProposalInfo(null); } @Override public String getAdditionalProposalInfo(IProgressMonitor monitor) { return getDocumentationFor(cpc, declaration, monitor); } public void enterLinkedMode(IDocument document) { try { final int loc = offset-prefix.length(); int pos = text.indexOf("nothing"); if (pos>0) { final LinkedModeModel linkedModeModel = new LinkedModeModel(); List<ICompletionProposal> props = new ArrayList<ICompletionProposal>(); addProposals(loc+pos, prefix, props); ProposalPosition linkedPosition = new ProposalPosition(document, loc+pos, 7, 0, props.toArray(NO_COMPLETIONS)); LinkedMode.addLinkedPosition(linkedModeModel, linkedPosition); CeylonEditor editor = (CeylonEditor) getCurrentEditor(); LinkedMode.installLinkedMode(editor, document, linkedModeModel, this, new LinkedMode.NullExitPolicy(), 1, loc+text.length()); } } catch (Exception e) { e.printStackTrace(); } } @Override public IContextInformation getContextInformation() { return new ReturnValueContextInfo(); } @Override public boolean validate(IDocument document, int offset, DocumentEvent event) { if (offset<this.offset) { return false; } currentPrefix = getCurrentPrefix(document, offset); return currentPrefix==null ? false : isNameMatching(currentPrefix, declaration.getName()); } private void addProposals(int loc, String prefix, List<ICompletionProposal> props) { Type type = getType(); if (type==null) return; Unit unit = getUnit(); //nothing: props.add(new NestedCompletionProposal( unit.getLanguageModuleDeclaration("nothing"), loc, unit)); //this: ClassOrInterface ci = getContainingClassOrInterface(scope); if (ci!=null) { if (ci.getType().isSubtypeOf(type)) { props.add(new NestedLiteralCompletionProposal( "this", loc)); } } //literals: for (String value: getAssignableLiterals(type, unit)) { props.add(new NestedLiteralCompletionProposal( value, loc)); } //declarations for (DeclarationWithProximity dwp: getSortedProposedValues(scope, unit, null)) { if (dwp.isUnimported()) { //don't propose unimported stuff b/c adding //imports drops us out of linked mode and //because it results in a pause continue; } Declaration d = dwp.getDeclaration(); if (d instanceof NothingType) { return; } String name = d.getName(); String[] split = prefix.split("\\s+"); if (split.length>0 && name.equals(split[split.length-1])) { continue; } String pname = d.getUnit().getPackage() .getNameAsString(); boolean inLanguageModule = pname.equals(Module.LANGUAGE_MODULE_NAME); if (d instanceof Value && !d.equals(declaration)) { Value value = (Value) d; if (inLanguageModule) { if (isIgnoredLanguageModuleValue(value)) { continue; } } Type vt = value.getType(); if (vt!=null && !vt.isNothing() && withinBounds(type, vt, scope)) { props.add(new NestedCompletionProposal(d, loc, getUnit())); } } if (d instanceof Function && !d.equals(declaration) && !d.isAnnotation()) { Function method = (Function) d; if (inLanguageModule && isIgnoredLanguageModuleMethod(method)) { continue; } Type mt = method.getType(); if (mt!=null && !mt.isNothing() && withinBounds(type, mt, scope)) { props.add(new NestedCompletionProposal(d, loc, getUnit())); } } if (d instanceof Class) { Class clazz = (Class) d; if (!clazz.isAbstract() && !d.isAnnotation()) { if (inLanguageModule && isIgnoredLanguageModuleClass(clazz)) { continue; } Type ct = clazz.getType(); if (ct!=null && !ct.isNothing() && (withinBounds(type, ct, scope) || ct.getDeclaration() .equals(type.getDeclaration()))) { if (clazz.getParameterList()!=null) { props.add(new NestedCompletionProposal(d, loc, getUnit())); } for (Declaration m: clazz.getMembers()) { if (m instanceof Constructor && m.isShared() && m.getName()!=null) { props.add(new NestedCompletionProposal(m, loc, getUnit())); } } } } } } } //TODO: this class is a big copy/paste of // InitializerProposal.NestedCompletionProposal static final class NestedCompletionProposal implements ICompletionProposal, ICompletionProposalExtension2, ICompletionProposalExtension6 { private final Declaration dec; private final int offset; private Unit unit; public NestedCompletionProposal(Declaration dec, int offset, Unit unit) { this.dec = dec; this.offset = offset; this.unit = unit; } @Override public void apply(IDocument document) { try { IRegion region = getCurrentSpecifierRegion(document, offset); document.replace(region.getOffset(), region.getLength(), getText(false)); } catch (BadLocationException e) { e.printStackTrace(); } } @Override public Point getSelection(IDocument document) { return null; } @Override public String getAdditionalProposalInfo() { return null; } @Override public String getDisplayString() { return getText(true); } @Override public StyledString getStyledDisplayString() { StyledString result = new StyledString(); Highlights.styleFragment(result, getDisplayString(), false, null, getCompletionFont()); return result; } @Override public Image getImage() { return getImageForDeclaration(dec); } @Override public IContextInformation getContextInformation() { return null; } private String getText(boolean description) { StringBuilder sb = new StringBuilder(); sb.append(getProposedName(null, dec, unit)); if (dec instanceof Functional) { appendPositionalArgs(dec, unit, sb, false, description); } return sb.toString(); } @Override public void apply(ITextViewer viewer, char trigger, int stateMask, int offset) { apply(viewer.getDocument()); } @Override public void selected(ITextViewer viewer, boolean smartToggle) {} @Override public void unselected(ITextViewer viewer) {} @Override public boolean validate(IDocument document, int currentOffset, DocumentEvent event) { if (event==null) { return true; } else { try { IRegion region = getCurrentSpecifierRegion(document, offset); String content = document.get(region.getOffset(), currentOffset-region.getOffset()); return isContentValid(content); } catch (BadLocationException e) { // ignore concurrently modified document } return false; } } private boolean isContentValid(String content) { String filter = content.trim().toLowerCase(); return ModelUtil.isNameMatching(content, dec) || getProposedName(null, dec, unit) .toLowerCase() .startsWith(filter); } } static final class NestedLiteralCompletionProposal implements ICompletionProposal, ICompletionProposalExtension2, ICompletionProposalExtension6 { private final String value; private final int offset; public NestedLiteralCompletionProposal(String value, int offset) { this.offset = offset; this.value = value; } @Override public Point getSelection(IDocument document) { return null; } @Override public void apply(IDocument document) { try { IRegion region = getCurrentSpecifierRegion(document, offset); document.replace(region.getOffset(), region.getLength(), value); } catch (BadLocationException e) { e.printStackTrace(); } } @Override public String getDisplayString() { return value; } @Override public StyledString getStyledDisplayString() { StyledString result = new StyledString(); Highlights.styleFragment(result, getDisplayString(), false, null, getCompletionFont()); return result; } @Override public Image getImage() { return getDecoratedImage(CEYLON_LITERAL, 0, false); } @Override public String getAdditionalProposalInfo() { return null; } @Override public IContextInformation getContextInformation() { return null; } @Override public void apply(ITextViewer viewer, char trigger, int stateMask, int offset) { apply(viewer.getDocument()); } @Override public void selected(ITextViewer viewer, boolean smartToggle) {} @Override public void unselected(ITextViewer viewer) {} @Override public boolean validate(IDocument document, int currentOffset, DocumentEvent event) { if (event==null) { return true; } else { try { IRegion region = getCurrentSpecifierRegion(document, offset); String content = document.get(region.getOffset(), currentOffset-region.getOffset()); String filter = content.trim().toLowerCase(); if (value.toLowerCase().startsWith(filter)) { return true; } } catch (BadLocationException e) { // ignore concurrently modified document } return false; } } } }