package com.redhat.ceylon.eclipse.code.complete; import static com.redhat.ceylon.eclipse.code.complete.EclipseCompletionProcessor.LARGE_CORRECTION_IMAGE; import static com.redhat.ceylon.eclipse.code.complete.EclipseCompletionProcessor.NO_COMPLETIONS; import static com.redhat.ceylon.eclipse.code.complete.CodeCompletions.appendPositionalArgs; import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.anonFunctionHeader; import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.getAssignableLiterals; import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.getCurrentArgumentRegion; 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.isInBounds; import static com.redhat.ceylon.eclipse.code.complete.ParameterContextValidator.findCharCount; 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.preferences.CeylonPreferenceInitializer.CHAIN_LINKED_MODE_ARGUMENTS; import static com.redhat.ceylon.eclipse.code.preferences.CeylonPreferenceInitializer.LINKED_MODE_ARGUMENTS; import static com.redhat.ceylon.eclipse.code.preferences.CeylonPreferenceInitializer.PARAMETER_TYPES_IN_COMPLETIONS; import static com.redhat.ceylon.eclipse.ui.CeylonPlugin.getPreferences; 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.eclipse.util.LinkedMode.addLinkedPosition; import static com.redhat.ceylon.eclipse.util.LinkedMode.installLinkedMode; import static com.redhat.ceylon.model.typechecker.model.ModelUtil.getContainingClassOrInterface; import java.util.ArrayList; import java.util.Collection; 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.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.text.edits.MultiTextEdit; import com.redhat.ceylon.compiler.typechecker.tree.Node; import com.redhat.ceylon.compiler.typechecker.tree.Tree; import com.redhat.ceylon.eclipse.code.complete.CompletionProposal.FixedImageRetriever; import com.redhat.ceylon.eclipse.code.editor.CeylonEditor; import com.redhat.ceylon.eclipse.ui.CeylonPlugin; import com.redhat.ceylon.eclipse.util.Highlights; import com.redhat.ceylon.eclipse.util.LinkedMode; import com.redhat.ceylon.ide.common.util.escaping_; import com.redhat.ceylon.model.typechecker.model.Class; import com.redhat.ceylon.model.typechecker.model.ClassOrInterface; 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.Interface; 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.Scope; import com.redhat.ceylon.model.typechecker.model.Type; import com.redhat.ceylon.model.typechecker.model.TypeDeclaration; import com.redhat.ceylon.model.typechecker.model.TypeParameter; import com.redhat.ceylon.model.typechecker.model.Unit; import com.redhat.ceylon.model.typechecker.model.Value; public class ParametersCompletionProposal extends CompletionProposal { static final class ParameterContextInformation implements IContextInformation { final String string; final int argumentListOffset; ParameterContextInformation(List<Type> argTypes, int argumentListOffset, Unit unit) { this.argumentListOffset = argumentListOffset; StringBuilder builder = new StringBuilder(); for (Type argType: argTypes) { if (builder.length()>0) { builder.append(", "); } builder.append(argType.asString(unit)); } string = builder.toString(); } @Override public String getInformationDisplayString() { return string; } @Override public Image getImage() { return null; } @Override public String getContextDisplayString() { return "Indirect invocation"; } public int getArgumentListOffset() { return argumentListOffset; } } //TODO: this is a big copy/paste from // InvocationCompletionProposal.NestedCompletionProposal final class NestedCompletionProposal implements ICompletionProposal, ICompletionProposalExtension2, ICompletionProposalExtension6 { private final String op; private final int loc; private final int index; private final boolean basic; private final Declaration dec; private Declaration qualifier; NestedCompletionProposal( Declaration dec, Declaration qualifier, int loc, int index, boolean basic, String op) { this.qualifier = qualifier; this.op = op; this.loc = loc; this.index = index; this.basic = basic; this.dec = dec; } public String getAdditionalProposalInfo() { return null; } @Override public void apply(IDocument document) { //the following awfulness is necessary because the //insertion point may have changed (and even its //text may have changed, since the proposal was //instantiated). try { IRegion region = getCurrentArgumentRegion(document, loc, index, getFirstPosition()); String str = getText(false); int start = region.getOffset(); int len = region.getLength(); int end = start + len; if (document.getChar(end)=='}') { str += " "; } document.replace(start, len, str); } catch (BadLocationException e) { e.printStackTrace(); } //adding imports drops us out of linked mode :( //not needed anyway because we never propose //unimported stuff, so no big deal /*try { DocumentChange tc = new DocumentChange("imports", document); tc.setEdit(new MultiTextEdit()); HashSet<Declaration> decs = new HashSet<Declaration>(); Tree.CompilationUnit cu = cpc.getRootNode(); importDeclaration(decs, dec, cu); if (dec instanceof Functional) { List<ParameterList> pls = ((Functional) dec).getParameterLists(); if (!pls.isEmpty()) { for (Parameter p: pls.get(0).getParameters()) { FunctionOrValue pm = p.getModel(); if (pm instanceof Function) { for (ParameterList ppl: ((Function) pm).getParameterLists()) { for (Parameter pp: ppl.getParameters()) { importSignatureTypes(pp.getModel(), cu, decs); } } } } } } applyImports(tc, decs, cu, document); tc.perform(new NullProgressMonitor()); } catch (Exception e) { e.printStackTrace(); }*/ } private String getText(boolean description) { StringBuilder sb = new StringBuilder(op); Unit unit = getUnit(); sb.append(getProposedName(qualifier, dec, unit)); if (dec instanceof Functional && !basic) { appendPositionalArgs(dec, getUnit(), sb, false, description); } return sb.toString(); } @Override public Point getSelection(IDocument document) { return null; } @Override public String getDisplayString() { return getText(true); } @Override public StyledString getStyledDisplayString() { StyledString result = new StyledString(); Highlights.styleFragment(result, getDisplayString(), false, null, CeylonPlugin.getCompletionFont()); return result; } @Override public Image getImage() { return getImageForDeclaration(dec); } @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 = getCurrentArgumentRegion(document, loc, index, getFirstPosition()); 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) { int fat = content.indexOf("=>"); if (fat>0) { content = content.substring(fat+2); } int eq = content.indexOf("="); if (eq>0) { content = content.substring(eq+1); } if (content.startsWith(op)) { content = content.substring(op.length()); } String filter = content.trim().toLowerCase(); return ModelUtil.isNameMatching(content, dec) || getProposedName(qualifier, dec, getUnit()) .toLowerCase() .startsWith(filter); } } final class NestedLiteralCompletionProposal implements ICompletionProposal, ICompletionProposalExtension2, ICompletionProposalExtension6 { private final int loc; private final int index; private final String value; NestedLiteralCompletionProposal(String value, int loc, int index) { this.value = value; this.loc = loc; this.index = index; } public String getAdditionalProposalInfo() { return null; } @Override public void apply(IDocument document) { //the following awfulness is necessary because the //insertion point may have changed (and even its //text may have changed, since the proposal was //instantiated). try { IRegion region = getCurrentArgumentRegion(document, loc, index, getFirstPosition()); String str = value; int start = region.getOffset(); int len = region.getLength(); int end = start + len; if (document.getChar(end)=='}') { str += " "; } document.replace(start, len, str); } catch (BadLocationException e) { e.printStackTrace(); } } @Override public Point getSelection(IDocument document) { return null; } @Override public String getDisplayString() { return value; } @Override public StyledString getStyledDisplayString() { StyledString result = new StyledString(); Highlights.styleFragment(result, getDisplayString(), false, null, CeylonPlugin.getCompletionFont()); return result; } @Override public Image getImage() { return getDecoratedImage(CEYLON_LITERAL, 0, false); } @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 = getCurrentArgumentRegion(document, loc, index, getFirstPosition()); String content = document.get(region.getOffset(), currentOffset-region.getOffset()); int eq = content.indexOf("="); if (eq>0) { content = content.substring(eq+1); } String filter = content.trim().toLowerCase(); if (value.toLowerCase().startsWith(filter)) { return true; } } catch (BadLocationException e) { // ignore concurrently modified document } return false; } } } private final Unit unit; private final List<Type> argTypes; private final Scope scope; private int argumentListOffset; public ParametersCompletionProposal(int offset, String desc, String text, List<Type> argTypes, Scope scope, Unit unit) { super(offset, "", new FixedImageRetriever(LARGE_CORRECTION_IMAGE), desc, text); this.argumentListOffset = offset + text.indexOf("("); this.unit = unit; this.scope = scope; this.argTypes = argTypes; } private Unit getUnit() { return unit; } private DocumentChange createChange(IDocument document) throws BadLocationException { DocumentChange change = new DocumentChange("Complete Invocation", document); change.setEdit(new MultiTextEdit()); change.addEdit(createEdit(document)); return change; } @Override public void apply(IDocument document) { try { performChange(createChange(document)); } catch (BadLocationException e) { e.printStackTrace(); } if (CeylonPlugin.getPreferences() .getBoolean(LINKED_MODE_ARGUMENTS)) { enterLinkedMode(document); } } @Override public Point getSelection(IDocument document) { int first = getFirstPosition(); if (first<=0) { //no arg list return super.getSelection(document); } int next = getNextPosition(document, first); if (next<=0) { //an empty arg list return super.getSelection(document); } int middle = getCompletionPosition(first, next); int start = offset-prefix.length()+first+middle; int len = next-middle; try { if (document.get(start, len).trim().equals("{}")) { start++; len=0; } } catch (BadLocationException e) {} return new Point(start, len); } protected int getCompletionPosition(int first, int next) { return text.substring(first, first+next-1).lastIndexOf(' ')+1; } protected int getFirstPosition() { return 1; } public int getNextPosition(IDocument document, int lastOffset) { int loc = offset-prefix.length(); int comma = -1; try { int start = loc+lastOffset; int end = loc+text.length()-1; if (text.endsWith(";")) { end--; } comma = findCharCount(1, document, start, end, ",;", "", true) - start; } catch (BadLocationException e) { e.printStackTrace(); } if (comma<0) { int index = text.lastIndexOf(')'); return index - lastOffset; } return comma; } @Override public String getAdditionalProposalInfo() { return getAdditionalProposalInfo(null); } @Override public String getAdditionalProposalInfo(IProgressMonitor monitor) { return null; } public void enterLinkedMode(IDocument document) { int paramCount = argTypes.size(); if (paramCount==0) return; try { final int loc = offset-prefix.length(); int first = getFirstPosition(); if (first<=0) return; //no arg list int next = getNextPosition(document, first); if (next<=0) return; //empty arg list LinkedModeModel linkedModeModel = new LinkedModeModel(); int seq=0, param=0; while (next>0 && param<paramCount) { List<ICompletionProposal> props = new ArrayList<ICompletionProposal>(); addValueArgumentProposals( argTypes.get(seq), loc, first, props, seq); int middle = getCompletionPosition(first, next); int start = loc+first+middle; int len = next-middle; ProposalPosition linkedPosition = new ProposalPosition( document, start, len, seq, props.toArray(NO_COMPLETIONS)); addLinkedPosition(linkedModeModel, linkedPosition); first = first+next+1; next = getNextPosition(document, first); seq++; } param++; if (seq>0) { CeylonEditor editor = (CeylonEditor) getCurrentEditor(); installLinkedMode(editor, document, linkedModeModel, this, new LinkedMode.NullExitPolicy(), seq, loc+text.length()); } } catch (Exception e) { e.printStackTrace(); } } @Override public IContextInformation getContextInformation() { return new ParameterContextInformation(argTypes, argumentListOffset, unit); } private void addValueArgumentProposals( Type type, final int loc, int first, List<ICompletionProposal> props, int index) { if (type==null) { return; } Unit unit = getUnit(); List<DeclarationWithProximity> proposals = getSortedProposedValues(scope, unit, null); for (DeclarationWithProximity dwp: proposals) { if (dwp.getProximity()<=1) { addValueArgumentProposal(loc, props, index, type, unit, dwp, null); } } ClassOrInterface ci = getContainingClassOrInterface(scope); if (ci!=null) { if (ci.getType().isSubtypeOf(type)) { props.add(new NestedLiteralCompletionProposal( "this", loc, index)); } } for (String value: getAssignableLiterals(type, unit)) { props.add(new NestedLiteralCompletionProposal( value, loc, index)); } for (DeclarationWithProximity dwp: proposals) { if (dwp.getProximity()>1) { addValueArgumentProposal(loc, props, index, type, unit, dwp, null); } } } private void addValueArgumentProposal(final int loc, List<ICompletionProposal> props, int index, Type type, Unit unit, DeclarationWithProximity dwp, DeclarationWithProximity qualifier) { if (qualifier==null && dwp.isUnimported()) { return; } TypeDeclaration td = type.getDeclaration(); Declaration d = dwp.getDeclaration(); if (d instanceof NothingType) { return; } String pname = d.getUnit() .getPackage() .getNameAsString(); boolean isInLanguageModule = qualifier==null && pname.equals(Module.LANGUAGE_MODULE_NAME); Declaration qdec = qualifier==null ? null : qualifier.getDeclaration(); if (d instanceof Value) { Value value = (Value) d; if (isInLanguageModule) { if (isIgnoredLanguageModuleValue(value)) { return; } } Type vt = value.getType(); if (vt!=null && !vt.isNothing()) { if (vt.isSubtypeOf(type) || withinBounds(td, vt)) { // boolean isIterArg = namedInvocation && last && // unit.isIterableParameterType(type); // boolean isVarArg = p.isSequenced() && positionalInvocation; props.add(new NestedCompletionProposal( d, qdec, loc, index, false, /*isIterArg || isVarArg ? "*" :*/ "")); } if (qualifier==null && CeylonPlugin.getPreferences() .getBoolean(CHAIN_LINKED_MODE_ARGUMENTS)) { Collection<DeclarationWithProximity> members = value.getTypeDeclaration() .getMatchingMemberDeclarations( unit, scope, "", 0, null) .values(); for (DeclarationWithProximity mwp: members) { addValueArgumentProposal(loc, props, index, type, unit, mwp, dwp); } } } } if (d instanceof Function) { if (!d.isAnnotation()) { Function method = (Function) d; if (isInLanguageModule) { if (isIgnoredLanguageModuleMethod(method)) { return; } } Type mt = method.getType(); if (mt!=null && !mt.isNothing() && (withinBounds(td, mt) || mt.isSubtypeOf(type))) { // boolean isIterArg = namedInvocation && last && // unit.isIterableParameterType(type); // boolean isVarArg = p.isSequenced() && positionalInvocation; props.add(new NestedCompletionProposal( d, qdec, loc, index, false, /*isIterArg || isVarArg ? "*" :*/ "")); } } } if (d instanceof Class) { Class clazz = (Class) d; if (!clazz.isAbstract() && !d.isAnnotation()) { if (isInLanguageModule) { if (isIgnoredLanguageModuleClass(clazz)) { return; } } Type ct = clazz.getType(); if (ct!=null && !ct.isNothing() && (withinBounds(td, ct) || ct.getDeclaration() .equals(type.getDeclaration()) || ct.isSubtypeOf(type))) { // boolean isIterArg = namedInvocation && last && // unit.isIterableParameterType(type); // boolean isVarArg = p.isSequenced() && positionalInvocation; props.add(new NestedCompletionProposal( d, qdec, loc, index, false, /*isIterArg || isVarArg ? "*" :*/ "")); } } } } public boolean withinBounds(TypeDeclaration td, Type t) { if (td instanceof TypeParameter) { TypeParameter tp = (TypeParameter) td; return isInBounds(tp.getSatisfiedTypes(), t); } else { return false; } } boolean isParameterInfo() { return false; } static void addParametersProposal( final int offset, Node node, final List<ICompletionProposal> result) { if (!(node instanceof Tree.StaticMemberOrTypeExpression) || !(((Tree.StaticMemberOrTypeExpression) node) .getDeclaration() instanceof Functional)) { Tree.Term term = (Tree.Term) node; Type type = term.getTypeModel(); Unit unit = node.getUnit(); if (type!=null) { TypeDeclaration td = type.getDeclaration(); Interface cd = unit.getCallableDeclaration(); if (type.isClassOrInterface() && td.equals(cd)) { List<Type> argTypes = unit.getCallableArgumentTypes(type); boolean paramTypes = getPreferences() .getBoolean(PARAMETER_TYPES_IN_COMPLETIONS); final StringBuilder desc = new StringBuilder(); final StringBuilder text = new StringBuilder(); desc.append('('); text.append('('); for (int i=0; i<argTypes.size(); i++) { Type argType = argTypes.get(i); if (desc.length()>1) desc.append(", "); if (text.length()>1) text.append(", "); if (argType.isClassOrInterface() && argType.getDeclaration() .equals(cd)) { String anon = anonFunctionHeader(argType, unit); text.append(anon) .append(" => "); desc.append(anon) .append(" => "); argType = unit.getCallableReturnType(argType); argTypes.set(i, argType); } else if (paramTypes) { desc.append(argType.asString(unit)) .append(' '); } String name; if (argType.isClassOrInterface() || argType.isTypeParameter()) { String n = argType.getDeclaration() .getName(unit); name = escaping_.get_().toInitialLowercase(n); } else { name = "it"; } text.append(name); desc.append(name); } text.append(')'); desc.append(')'); result.add(new ParametersCompletionProposal(offset, desc.toString(), text.toString(), argTypes, node.getScope(), unit)); } } } } }