package com.redhat.ceylon.eclipse.code.complete; import static com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer.AIDENTIFIER; import static com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer.ASTRING_LITERAL; import static com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer.AVERBATIM_STRING; import static com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer.CASE_TYPES; import static com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer.CHAR_LITERAL; import static com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer.COMMA; import static com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer.EOF; import static com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer.FLOAT_LITERAL; import static com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer.IS_OP; import static com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer.LARGER_OP; import static com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer.LBRACE; import static com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer.LIDENTIFIER; import static com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer.LINE_COMMENT; import static com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer.LPAREN; import static com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer.MEMBER_OP; import static com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer.MULTI_COMMENT; import static com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer.NATURAL_LITERAL; import static com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer.PIDENTIFIER; import static com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer.RBRACE; import static com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer.SAFE_MEMBER_OP; import static com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer.SEMICOLON; import static com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer.SPREAD_OP; import static com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer.STRING_END; import static com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer.STRING_LITERAL; import static com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer.STRING_MID; import static com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer.STRING_START; import static com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer.UIDENTIFIER; import static com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer.VERBATIM_STRING; import static com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer.WS; import static com.redhat.ceylon.eclipse.code.complete.AnonFunctionProposal.addAnonFunctionProposal; import static com.redhat.ceylon.eclipse.code.complete.BasicCompletionProposal.addDocLinkProposal; import static com.redhat.ceylon.eclipse.code.complete.BasicCompletionProposal.addImportProposal; import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.getLine; import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.isEmptyModuleDescriptor; import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.isEmptyPackageDescriptor; import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.isModuleDescriptor; import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.isPackageDescriptor; import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.nextTokenType; import static com.redhat.ceylon.eclipse.code.complete.CompletionUtil.overloads; import static com.redhat.ceylon.eclipse.code.complete.ControlStructureCompletionProposal.addAssertExistsProposal; import static com.redhat.ceylon.eclipse.code.complete.ControlStructureCompletionProposal.addAssertNonemptyProposal; import static com.redhat.ceylon.eclipse.code.complete.ControlStructureCompletionProposal.addForProposal; import static com.redhat.ceylon.eclipse.code.complete.ControlStructureCompletionProposal.addIfExistsProposal; import static com.redhat.ceylon.eclipse.code.complete.ControlStructureCompletionProposal.addIfNonemptyProposal; import static com.redhat.ceylon.eclipse.code.complete.ControlStructureCompletionProposal.addSwitchProposal; import static com.redhat.ceylon.eclipse.code.complete.ControlStructureCompletionProposal.addTryProposal; import static com.redhat.ceylon.eclipse.code.complete.FunctionCompletionProposal.addFunctionProposal; import static com.redhat.ceylon.eclipse.code.complete.InvocationCompletionProposal.addFakeShowParametersCompletion; import static com.redhat.ceylon.eclipse.code.complete.InvocationCompletionProposal.addInvocationProposals; import static com.redhat.ceylon.eclipse.code.complete.InvocationCompletionProposal.addProgramElementReferenceProposal; import static com.redhat.ceylon.eclipse.code.complete.InvocationCompletionProposal.addReferenceProposal; import static com.redhat.ceylon.eclipse.code.complete.InvocationCompletionProposal.addSecondLevelProposal; import static com.redhat.ceylon.eclipse.code.complete.KeywordCompletionProposal.addKeywordProposals; import static com.redhat.ceylon.eclipse.code.complete.MemberNameCompletions.addMemberNameProposal; import static com.redhat.ceylon.eclipse.code.complete.MemberNameCompletions.addMemberNameProposals; import static com.redhat.ceylon.eclipse.code.complete.ModuleCompletions.addModuleCompletions; import static com.redhat.ceylon.eclipse.code.complete.ModuleCompletions.addModuleDescriptorCompletion; import static com.redhat.ceylon.eclipse.code.complete.PackageCompletions.addCurrentPackageNameCompletion; import static com.redhat.ceylon.eclipse.code.complete.PackageCompletions.addPackageCompletions; import static com.redhat.ceylon.eclipse.code.complete.PackageCompletions.addPackageDescriptorCompletion; import static com.redhat.ceylon.eclipse.code.complete.ParametersCompletionProposal.addParametersProposal; import static com.redhat.ceylon.eclipse.code.complete.RefinementCompletionProposal.addInlineFunctionProposal; import static com.redhat.ceylon.eclipse.code.complete.RefinementCompletionProposal.addNamedArgumentProposal; import static com.redhat.ceylon.eclipse.code.complete.RefinementCompletionProposal.addRefinementProposal; import static com.redhat.ceylon.eclipse.code.complete.RefinementCompletionProposal.getRefinedProducedReference; import static com.redhat.ceylon.eclipse.code.complete.TypeArgumentListCompletions.addTypeArgumentListProposal; import static com.redhat.ceylon.eclipse.code.outline.CeylonLabelProvider.getDecoratedImage; import static com.redhat.ceylon.eclipse.code.preferences.CeylonPreferenceInitializer.AUTO_ACTIVATION_CHARS; import static com.redhat.ceylon.eclipse.code.preferences.CeylonPreferenceInitializer.COMPLETION_FILTERS; import static com.redhat.ceylon.eclipse.code.preferences.CeylonPreferenceInitializer.ENABLE_COMPLETION_FILTERS; import static com.redhat.ceylon.eclipse.code.preferences.CeylonPreferenceInitializer.FILTERS; 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.eclipse.util.Nodes.getReferencedNodeInUnit; import static com.redhat.ceylon.eclipse.util.Nodes.getTokenIndexAtCharacter; import static com.redhat.ceylon.eclipse.util.Types.getRequiredType; import static com.redhat.ceylon.eclipse.util.Types.getResultType; import static com.redhat.ceylon.ide.common.util.OccurrenceLocation.ALIAS_REF; import static com.redhat.ceylon.ide.common.util.OccurrenceLocation.CASE; import static com.redhat.ceylon.ide.common.util.OccurrenceLocation.CATCH; import static com.redhat.ceylon.ide.common.util.OccurrenceLocation.CLASS_ALIAS; import static com.redhat.ceylon.ide.common.util.OccurrenceLocation.DOCLINK; import static com.redhat.ceylon.ide.common.util.OccurrenceLocation.EXISTS; import static com.redhat.ceylon.ide.common.util.OccurrenceLocation.EXPRESSION; import static com.redhat.ceylon.ide.common.util.OccurrenceLocation.EXTENDS; import static com.redhat.ceylon.ide.common.util.OccurrenceLocation.FUNCTION_REF; import static com.redhat.ceylon.ide.common.util.OccurrenceLocation.IMPORT; import static com.redhat.ceylon.ide.common.util.OccurrenceLocation.IS; import static com.redhat.ceylon.ide.common.util.OccurrenceLocation.META; import static com.redhat.ceylon.ide.common.util.OccurrenceLocation.NONEMPTY; import static com.redhat.ceylon.ide.common.util.OccurrenceLocation.OF; import static com.redhat.ceylon.ide.common.util.OccurrenceLocation.PARAMETER_LIST; import static com.redhat.ceylon.ide.common.util.OccurrenceLocation.SATISFIES; import static com.redhat.ceylon.ide.common.util.OccurrenceLocation.TYPE_ALIAS; import static com.redhat.ceylon.ide.common.util.OccurrenceLocation.TYPE_ARGUMENT_LIST; import static com.redhat.ceylon.ide.common.util.OccurrenceLocation.TYPE_PARAMETER_LIST; import static com.redhat.ceylon.ide.common.util.OccurrenceLocation.TYPE_PARAMETER_REF; import static com.redhat.ceylon.ide.common.util.OccurrenceLocation.UPPER_BOUND; import static com.redhat.ceylon.ide.common.util.OccurrenceLocation.VALUE_REF; import static com.redhat.ceylon.model.typechecker.model.ModelUtil.isAbstraction; import static com.redhat.ceylon.model.typechecker.model.ModelUtil.isConstructor; import static com.redhat.ceylon.model.typechecker.model.ModelUtil.isTypeUnknown; import static java.lang.Character.isDigit; import static java.lang.Character.isLetter; import static java.util.Collections.emptyMap; import static org.antlr.runtime.Token.HIDDEN_CHANNEL; import static org.eclipse.ui.PlatformUI.getWorkbench; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import java.util.regex.Pattern; import org.antlr.runtime.CommonToken; import org.antlr.runtime.Token; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jface.operation.IRunnableWithProgress; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.jface.text.contentassist.IContentAssistProcessor; import org.eclipse.jface.text.contentassist.IContextInformation; import org.eclipse.jface.text.contentassist.IContextInformationValidator; import org.eclipse.swt.graphics.Image; import com.redhat.ceylon.compiler.typechecker.context.PhasedUnit; import com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer; import com.redhat.ceylon.compiler.typechecker.tree.Node; import com.redhat.ceylon.compiler.typechecker.tree.Tree; import com.redhat.ceylon.compiler.typechecker.tree.Visitor; import com.redhat.ceylon.eclipse.code.complete.InvocationCompletionProposal.ParameterContextInformation; import com.redhat.ceylon.eclipse.code.editor.CeylonEditor; import com.redhat.ceylon.eclipse.code.parse.CeylonParseController; import com.redhat.ceylon.eclipse.ui.CeylonPlugin; import com.redhat.ceylon.eclipse.ui.CeylonResources; import com.redhat.ceylon.eclipse.util.Types; import com.redhat.ceylon.ide.common.completion.FindScopeVisitor; import com.redhat.ceylon.ide.common.util.OccurrenceLocation; import com.redhat.ceylon.ide.common.util.escaping_; import com.redhat.ceylon.model.typechecker.model.Cancellable; 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.FunctionOrValue; import com.redhat.ceylon.model.typechecker.model.Functional; import com.redhat.ceylon.model.typechecker.model.ImportList; import com.redhat.ceylon.model.typechecker.model.Interface; import com.redhat.ceylon.model.typechecker.model.Package; import com.redhat.ceylon.model.typechecker.model.Parameter; import com.redhat.ceylon.model.typechecker.model.ParameterList; 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.TypeAlias; import com.redhat.ceylon.model.typechecker.model.TypeDeclaration; import com.redhat.ceylon.model.typechecker.model.TypeParameter; import com.redhat.ceylon.model.typechecker.model.TypedDeclaration; import com.redhat.ceylon.model.typechecker.model.Unit; import com.redhat.ceylon.model.typechecker.model.Value; public class CeylonCompletionProcessor implements IContentAssistProcessor { static final Image LARGE_CORRECTION_IMAGE = getDecoratedImage(CeylonResources.CEYLON_CORRECTION, 0, false); private static final char[] CONTEXT_INFO_ACTIVATION_CHARS = ",(;{".toCharArray(); private static final IContextInformation[] NO_CONTEXTS = new IContextInformation[0]; static ICompletionProposal[] NO_COMPLETIONS = new ICompletionProposal[0]; private ParameterContextValidator validator; private CeylonEditor editor; private boolean secondLevel; private boolean returnedParamInfo; private int lastOffsetAcrossSessions=-1; private int lastOffset=-1; public void sessionStarted() { secondLevel = false; lastOffset=-1; } public CeylonCompletionProcessor(CeylonEditor editor) { this.editor=editor; } public ICompletionProposal[] computeCompletionProposals( final ITextViewer viewer, final int offset) { if (offset!=lastOffsetAcrossSessions) { returnedParamInfo = false; secondLevel = false; } try { if (lastOffset>=0 && offset>0 && offset!=lastOffset && !isIdentifierCharacter(viewer, offset)) { //user typed a whitespace char with an open //completions window, so close the window return NO_COMPLETIONS; } } catch (BadLocationException ble) { ble.printStackTrace(); return NO_COMPLETIONS; } if (offset==lastOffset) { secondLevel = !secondLevel; } lastOffset = offset; lastOffsetAcrossSessions = offset; class Runnable implements IRunnableWithProgress { ICompletionProposal[] contentProposals = NO_COMPLETIONS; @Override public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { monitor.beginTask("Preparing completions...", IProgressMonitor.UNKNOWN); CeylonParseController controller = editor.getParseController(); contentProposals = getContentProposals(controller, offset, viewer, secondLevel, returnedParamInfo, monitor); if (contentProposals!=null && contentProposals.length==1 && contentProposals[0] instanceof InvocationCompletionProposal.ParameterInfo) { returnedParamInfo = true; } monitor.done(); } } Runnable runnable = new Runnable(); try { if (secondLevel) { runnable.run(new NullProgressMonitor()); } else { getWorkbench() .getActiveWorkbenchWindow() .run(true, true, runnable); } } catch (Exception e) { e.printStackTrace(); } return runnable.contentProposals; } private boolean isIdentifierCharacter( ITextViewer viewer, int offset) throws BadLocationException { IDocument doc = viewer.getDocument(); char ch = doc.get(offset-1, 1).charAt(0); return isLetter(ch) || isDigit(ch) || ch=='_' || ch=='.'; } public IContextInformation[] computeContextInformation( final ITextViewer viewer, final int offset) { CeylonParseController controller = editor.getParseController(); PhasedUnit phasedUnit = controller.parseAndTypecheck( viewer.getDocument(), 10, new NullProgressMonitor(), null); if (phasedUnit!=null) { return computeParameterContextInformation(offset, phasedUnit.getCompilationUnit(), viewer) .toArray(NO_CONTEXTS); } else { return NO_CONTEXTS; } } public char[] getCompletionProposalAutoActivationCharacters() { return CeylonPlugin.getPreferences() .getString(AUTO_ACTIVATION_CHARS) .toCharArray(); } public char[] getContextInformationAutoActivationCharacters() { return CONTEXT_INFO_ACTIVATION_CHARS; } public IContextInformationValidator getContextInformationValidator() { if (validator==null) { validator = new ParameterContextValidator(editor); } return validator; } public String getErrorMessage() { return "No completions available"; } public ICompletionProposal[] getContentProposals( CeylonParseController controller, int offset, ITextViewer viewer, boolean secondLevel, boolean returnedParamInfo, final IProgressMonitor monitor) { Cancellable cancellable = new Cancellable() { @Override public boolean isCancelled() { return monitor.isCanceled(); } }; if (controller==null || viewer==null) { return null; } PhasedUnit typecheckedPhasedUnit = controller.parseAndTypecheck( viewer.getDocument(), 10, monitor, null); if (typecheckedPhasedUnit == null) { return null; } editor.getAnnotationCreator().updateAnnotations(); List<CommonToken> tokens = controller.getTokens(); Tree.CompilationUnit typecheckedRootNode = typecheckedPhasedUnit.getCompilationUnit(); //adjust the token to account for unclosed blocks //we search for the first non-whitespace/non-comment //token to the left of the caret int tokenIndex = getTokenIndexAtCharacter(tokens, offset); if (tokenIndex<0) tokenIndex = -tokenIndex; CommonToken adjustedToken = adjust(tokenIndex, offset, tokens); int tt = adjustedToken.getType(); if (offset<=adjustedToken.getStopIndex() && offset>adjustedToken.getStartIndex()) { if (isCommentOrCodeStringLiteral(adjustedToken)) { return null; } } if (isLineComment(adjustedToken) && offset>adjustedToken.getStartIndex() && adjustedToken.getLine()==getLine(offset,viewer)+1) { return null; } //find the node at the token Node node = getTokenNode( adjustedToken.getStartIndex(), adjustedToken.getStopIndex()+1, tt, typecheckedRootNode, offset); //it's useful to know the type of the preceding //token, if any int index = adjustedToken.getTokenIndex(); if (offset<=adjustedToken.getStopIndex()+1 && offset>adjustedToken.getStartIndex()) { index--; } int tokenType = adjustedToken.getType(); int previousTokenType = index>=0 ? adjust(index, offset, tokens).getType() : -1; //find the type that is expected in the current //location so we can prioritize proposals of that //type //TODO: this breaks as soon as the user starts typing // an expression, since RequiredTypeVisitor // doesn't know how to search up the tree for // the containing InvocationExpression Types.Required required = getRequiredType(typecheckedRootNode, node, adjustedToken); String prefix = ""; String fullPrefix = ""; if (isIdentifierOrKeyword(adjustedToken)) { String text = adjustedToken.getText(); //work from the end of the token to //compute the offset, in order to //account for quoted identifiers, where //the \i or \I is not in the token text int offsetInToken = offset-adjustedToken.getStopIndex()-1+text.length(); int realOffsetInToken = offset-adjustedToken.getStartIndex(); if (offsetInToken<=text.length()) { prefix = text.substring(0, offsetInToken); fullPrefix = getRealText(adjustedToken) .substring(0, realOffsetInToken); } } boolean isMemberOp = isMemberOperator(adjustedToken); String qualified = null; // special handling for doc links boolean inDoc = isAnnotationStringLiteral(adjustedToken) && offset>adjustedToken.getStartIndex() && offset<=adjustedToken.getStopIndex(); if (inDoc) { if (node instanceof Tree.DocLink) { Tree.DocLink docLink = (Tree.DocLink) node; int offsetInLink = offset - docLink.getStartIndex(); String text = docLink.getToken().getText(); int bar = text.indexOf('|')+1; if (offsetInLink<bar) { return null; } qualified = text.substring(bar, offsetInLink); int dcolon = qualified.indexOf("::"); String pkg = null; if (dcolon>=0) { pkg = qualified.substring(0, dcolon+2); qualified = qualified.substring(dcolon+2); } int dot = qualified.indexOf('.')+1; isMemberOp = dot>0; prefix = qualified.substring(dot); if (dcolon>=0) { qualified = pkg + qualified; } fullPrefix = prefix; } else { return null; } } FindScopeVisitor fsv = new FindScopeVisitor(node); fsv.visit(typecheckedRootNode); Scope scope = fsv.getScope(); //construct completions when outside ordinary code ICompletionProposal[] completions = constructCompletions(offset, fullPrefix, controller, node, adjustedToken, scope, returnedParamInfo, isMemberOp, viewer.getDocument(), tokenType, monitor); if (completions==null) { //finally, construct and sort proposals Map<String, DeclarationWithProximity> proposals = getProposals(node, scope, prefix, isMemberOp, typecheckedRootNode, cancellable); Map<String, DeclarationWithProximity> functionProposals = getFunctionProposals(node, scope, prefix, isMemberOp, cancellable); filterProposals(proposals); filterProposals(functionProposals); Set<DeclarationWithProximity> sortedProposals = sortProposals(prefix, required, proposals); Set<DeclarationWithProximity> sortedFunctionProposals = sortProposals(prefix, required, functionProposals); completions = constructCompletions(offset, inDoc ? qualified : fullPrefix, sortedProposals, sortedFunctionProposals, controller, scope, node, adjustedToken, isMemberOp, viewer.getDocument(), secondLevel, inDoc, required.getType(), previousTokenType, tokenType); } return completions; } private void filterProposals( Map<String, DeclarationWithProximity> proposals) { List<Pattern> filters = getProposalFilters(); if (!filters.isEmpty()) { Iterator<DeclarationWithProximity> iterator = proposals.values() .iterator(); while (iterator.hasNext()) { DeclarationWithProximity dwp = iterator.next(); String name = dwp.getDeclaration() .getQualifiedNameString(); for (Pattern filter: filters) { if (filter.matcher(name).matches()) { iterator.remove(); continue; } } } } } private List<Pattern> getProposalFilters() { List<Pattern> filters = new ArrayList<Pattern>(); IPreferenceStore preferences = CeylonPlugin.getPreferences(); parseFilters(filters, preferences.getString(FILTERS)); if (preferences.getBoolean(ENABLE_COMPLETION_FILTERS)) { parseFilters(filters, preferences.getString(COMPLETION_FILTERS)); } return filters; } private void parseFilters(List<Pattern> filters, String filtersString) { if (!filtersString.trim().isEmpty()) { String[] regexes = filtersString .replaceAll("\\(\\w+\\)", "") .replace(".", "\\.") .replace("*", ".*") .split(","); for (String regex: regexes) { regex = regex.trim(); if (!regex.isEmpty()) { filters.add(Pattern.compile(regex)); } } } } private String getRealText(CommonToken token) { String text = token.getText(); int type = token.getType(); int len = token.getStopIndex()-token.getStartIndex()+1; if (text.length()<len) { String quote; if (type==LIDENTIFIER) { quote = "\\i"; } else if (type==UIDENTIFIER) { quote = "\\I"; } else { quote = ""; } return quote + text; } else { return text; } } private boolean isLineComment(CommonToken adjustedToken) { return adjustedToken.getType()==LINE_COMMENT; } private boolean isCommentOrCodeStringLiteral( CommonToken adjustedToken) { int tt = adjustedToken.getType(); return tt==MULTI_COMMENT || tt==LINE_COMMENT || tt==STRING_LITERAL || tt==STRING_END || tt==STRING_MID || tt==STRING_START || tt==VERBATIM_STRING || tt==CHAR_LITERAL || tt==FLOAT_LITERAL || tt==NATURAL_LITERAL; } private static boolean isAnnotationStringLiteral( CommonToken token) { int type = token.getType(); return type == ASTRING_LITERAL || type == AVERBATIM_STRING; } private static CommonToken adjust( int tokenIndex, int offset, List<CommonToken> tokens) { CommonToken adjustedToken = tokens.get(tokenIndex); while (--tokenIndex>=0 && (adjustedToken.getType()==WS //ignore whitespace || adjustedToken.getType()==EOF || adjustedToken.getStartIndex()==offset)) { //don't consider the token to the right of the caret adjustedToken = tokens.get(tokenIndex); if (adjustedToken.getType()!=WS && adjustedToken.getType()!=EOF && adjustedToken.getChannel()!=HIDDEN_CHANNEL) { //don't adjust to a ws token break; } } return adjustedToken; } private static Boolean isDirectlyInsideBlock(Node node, CeylonParseController cpc, Scope scope, CommonToken token) { if (scope instanceof Interface || scope instanceof Package) { return false; } else { //TODO: check that it is not the opening/closing // brace of a named argument list! return !(node instanceof Tree.SequenceEnumeration) && occursAfterBraceOrSemicolon(token, cpc.getTokens()); } } private static Boolean occursAfterBraceOrSemicolon( CommonToken token, List<CommonToken> tokens) { if (token.getTokenIndex()==0) { return false; } else { int tokenType = token.getType(); if (tokenType==LBRACE || tokenType==RBRACE || tokenType==SEMICOLON) { return true; } int previousTokenType = adjust(token.getTokenIndex()-1, token.getStartIndex(), tokens) .getType(); return previousTokenType==LBRACE || previousTokenType==RBRACE || previousTokenType==SEMICOLON; } } private static Node getTokenNode( int adjustedStart, int adjustedEnd, int tokenType, Tree.CompilationUnit rootNode, int offset) { Node node = findNode(rootNode, null, adjustedStart, adjustedEnd); if (node instanceof Tree.StringLiteral) { Tree.StringLiteral sl = (Tree.StringLiteral) node; if (!sl.getDocLinks().isEmpty()) { node = findNode(node, null, offset, offset); } } if (tokenType==RBRACE && !(node instanceof Tree.IterableType) || tokenType==SEMICOLON) { //We are to the right of a } or ; //so the returned node is the previous //statement/declaration. Look for the //containing body. class BodyVisitor extends Visitor { Node node, currentBody, result; BodyVisitor(Node node, Node root) { this.node = node; currentBody = root; } @Override public void visitAny(Node that) { if (that==node) { result = currentBody; } else { Node cb = currentBody; if (that instanceof Tree.Body) { currentBody = that; } if (that instanceof Tree.NamedArgumentList) { currentBody = that; } super.visitAny(that); currentBody = cb; } } } BodyVisitor mv = new BodyVisitor(node, rootNode); mv.visit(rootNode); node = mv.result; } if (node==null) node = rootNode; //we're in whitespace at the start of the file return node; } private static boolean isIdentifierOrKeyword(Token token) { int type = token.getType(); return type==LIDENTIFIER || type==UIDENTIFIER || type==AIDENTIFIER || type==PIDENTIFIER || escaping_.get_().isKeyword(token.getText()); } private static ICompletionProposal[] constructCompletions(int offset, String prefix, CeylonParseController cpc, Node node, CommonToken token, Scope scope, boolean returnedParamInfo, boolean memberOp, final IDocument document, int tokenType, IProgressMonitor monitor) { final List<ICompletionProposal> result = new ArrayList<ICompletionProposal>(); if (!returnedParamInfo && atStartOfPositionalArgument(node, token)) { addFakeShowParametersCompletion(node, cpc, result); if (result.isEmpty()) { return null; } } else if (node instanceof Tree.PackageLiteral) { addPackageCompletions(cpc, offset, prefix, null, node, result, false, monitor); } else if (node instanceof Tree.ModuleLiteral) { addModuleCompletions(cpc, offset, prefix, null, node, result, false, monitor); } else if (isDescriptorPackageNameMissing(node)) { addCurrentPackageNameCompletion(cpc, offset, prefix, result); } else if (node instanceof Tree.Import && offset>token.getStopIndex()+1) { addPackageCompletions(cpc, offset, prefix, null, node, result, nextTokenType(cpc, token)!=LBRACE, monitor); } else if (node instanceof Tree.ImportModule && offset>token.getStopIndex()+1) { addModuleCompletions(cpc, offset, prefix, null, node, result, nextTokenType(cpc, token)!=STRING_LITERAL, monitor); } else if (node instanceof Tree.ImportPath) { Tree.CompilationUnit upToDateAndTypechecked = cpc.getTypecheckedRootNode(); if (upToDateAndTypechecked != null) { new ImportVisitor(prefix, token, offset, node, cpc, result, monitor) .visit(upToDateAndTypechecked); } } else if (isEmptyModuleDescriptor(cpc)) { addModuleDescriptorCompletion(cpc, offset, prefix, result); addKeywordProposals(cpc, offset, prefix, result, node, null, false, tokenType); } else if (isEmptyPackageDescriptor(cpc)) { addPackageDescriptorCompletion(cpc, offset, prefix, result); addKeywordProposals(cpc, offset, prefix, result, node, null, false, tokenType); } else if (node instanceof Tree.TypeArgumentList && token.getType()==LARGER_OP) { if (offset==token.getStopIndex()+1) { addTypeArgumentListProposal(offset, cpc, node, scope, document, result); } else if (isMemberNameProposable(offset, node, memberOp)) { addMemberNameProposals(offset, cpc, node, result); } else { return null; } } else { return null; } return result.toArray(NO_COMPLETIONS); } private static boolean atStartOfPositionalArgument( Node node, CommonToken token) { if (node instanceof Tree.PositionalArgumentList) { int type = token.getType(); return type==LPAREN || type==COMMA; } else if (node instanceof Tree.NamedArgumentList) { int type = token.getType(); return type==LBRACE || type==SEMICOLON; } else { return false; } } private static boolean isDescriptorPackageNameMissing(Node node) { Tree.ImportPath path; if (node instanceof Tree.ModuleDescriptor) { Tree.ModuleDescriptor md = (Tree.ModuleDescriptor) node; path = md.getImportPath(); } else if (node instanceof Tree.PackageDescriptor) { Tree.PackageDescriptor pd = (Tree.PackageDescriptor) node; path = pd.getImportPath(); } else { return false; } return path==null || path.getIdentifiers().isEmpty(); } private static boolean isMemberNameProposable(int offset, Node node, boolean memberOp) { CommonToken token = (CommonToken) node.getEndToken(); return !memberOp && token!=null && token.getStopIndex()>=offset-2; } private ICompletionProposal[] constructCompletions(int offset, String prefix, Set<DeclarationWithProximity> sortedProposals, Set<DeclarationWithProximity> sortedFunctionProposals, CeylonParseController controller, Scope scope, final Node node, CommonToken token, boolean memberOp, IDocument doc, boolean secondLevel, boolean inDoc, Type requiredType, int previousTokenType, int tokenType) { List<ICompletionProposal> result = new ArrayList<ICompletionProposal>(); Tree.CompilationUnit rootNode = controller.getTypecheckedRootNode(); if (rootNode == null) { return result.toArray(NO_COMPLETIONS); } OccurrenceLocation ol = getOccurrenceLocation(rootNode, node, offset); Unit unit = node.getUnit(); if (node instanceof Tree.Term) { addParametersProposal(offset, node, result); } else if (node instanceof Tree.ArgumentList) { class FindInvocationVisitor extends Visitor { Tree.InvocationExpression result; @Override public void visit(Tree.InvocationExpression that) { if (that.getNamedArgumentList()==node || that.getPositionalArgumentList()==node) { result = that; } super.visit(that); } } FindInvocationVisitor fiv = new FindInvocationVisitor(); fiv.visit(rootNode); Tree.InvocationExpression ie = fiv.result; if (ie!=null) { addParametersProposal(offset, ie, result); } } if (node instanceof Tree.TypeConstraint) { for (DeclarationWithProximity dwp: sortedProposals) { Declaration dec = dwp.getDeclaration(); if (isTypeParameterOfCurrentDeclaration(node, dec)) { addReferenceProposal(offset, prefix, controller, result, dwp, scope, false, null, ol); } } } else if (prefix.isEmpty() && ol!=IS && isMemberNameProposable(offset, node, memberOp) && (node instanceof Tree.Type || node instanceof Tree.BaseTypeExpression || node instanceof Tree.QualifiedTypeExpression)) { //member names we can refine Type t=null; if (node instanceof Tree.Type) { Tree.Type type = (Tree.Type) node; t = type.getTypeModel(); } else if (node instanceof Tree.BaseTypeExpression) { Tree.BaseTypeExpression bte = (Tree.BaseTypeExpression) node; Reference target = bte.getTarget(); if (target!=null) { t = target.getType(); } } else if (node instanceof Tree.QualifiedTypeExpression) { Tree.QualifiedTypeExpression qte = (Tree.QualifiedTypeExpression) node; Reference target = qte.getTarget(); if (target!=null) { t = target.getType(); } } if (t!=null) { addRefinementProposals(offset, sortedProposals, controller, scope, node, doc, secondLevel, result, ol, t, false); } //otherwise guess something from the type addMemberNameProposal(offset, prefix, node, result, rootNode); } else if (node instanceof Tree.TypedDeclaration && !(node instanceof Tree.Variable && ((Tree.Variable) node).getType() instanceof Tree.SyntheticVariable) && !(node instanceof Tree.InitializerParameter) && isMemberNameProposable(offset, node, memberOp)) { //member names we can refine Tree.TypedDeclaration td = (Tree.TypedDeclaration) node; Tree.Type dnt = td.getType(); if (dnt!=null && dnt.getTypeModel()!=null) { Type t = dnt.getTypeModel(); addRefinementProposals(offset, sortedProposals, controller, scope, node, doc, secondLevel, result, ol, t, true); } //otherwise guess something from the type addMemberNameProposal(offset, prefix, node, result, rootNode); } else { boolean isMember = node instanceof Tree.QualifiedMemberOrTypeExpression || node instanceof Tree.QualifiedType || node instanceof Tree.MemberLiteral && ((Tree.MemberLiteral) node).getType()!=null; if (!secondLevel && !inDoc && !memberOp) { addKeywordProposals(controller, offset, prefix, result, node, ol, isMember, tokenType); //addTemplateProposal(offset, prefix, result); } if (!secondLevel && !inDoc && !isMember) { if (prefix.isEmpty() && !isTypeUnknown(requiredType) && unit.isCallableType(requiredType)) { addAnonFunctionProposal(offset, requiredType, result, unit); } } boolean isPackageOrModuleDescriptor = isModuleDescriptor(controller) || isPackageDescriptor(controller); for (DeclarationWithProximity dwp: sortedProposals) { Declaration dec = dwp.getDeclaration(); try { if (!dec.isToplevel() && !dec.isClassOrInterfaceMember() && dec.getUnit().equals(unit)) { Node decNode = getReferencedNodeInUnit(dec, rootNode); if (decNode!=null && offset<getIdentifyingNode(decNode) .getStartIndex()) { continue; } } if (isPackageOrModuleDescriptor && !inDoc && ol!=META && (ol==null || !ol.reference) && (!dec.isAnnotation() || !(dec instanceof Function))) { continue; } if (!secondLevel && isParameterOfNamedArgInvocation(scope, dwp) && isDirectlyInsideNamedArgumentList(controller, node, token)) { addNamedArgumentProposal(offset, prefix, controller, result, dec, scope); addInlineFunctionProposal(offset, dec, scope, node, prefix, controller, doc, result); } CommonToken nextToken = getNextToken(controller, token); boolean noParamsFollow = noParametersFollow(nextToken); if (!secondLevel && !inDoc && noParamsFollow && isInvocationProposable(dwp, ol, previousTokenType) && (!isQualifiedType(node) || isConstructor(dec) || dec.isStaticallyImportable()) && (!(scope instanceof Constructor) || ol!=EXTENDS || isDelegatableConstructor(scope, dec))) { for (Declaration d: overloads(dec)) { Reference pr = isMember ? getQualifiedProducedReference(node, d) : getRefinedProducedReference(scope, d); addInvocationProposals( offset, prefix, controller, result, dwp, d, pr, scope, ol, null, isMember); } } if (isProposable(dwp, ol, scope, unit, requiredType, previousTokenType) && isProposable(node, ol, dec) && (definitelyRequiresType(ol) || noParamsFollow || dec instanceof Functional) && (!(scope instanceof Constructor) || ol!=EXTENDS || isDelegatableConstructor(scope, dec))) { if (ol==DOCLINK) { addDocLinkProposal(offset, prefix, controller, result, dec, scope); } else if (ol==IMPORT) { addImportProposal( offset, prefix, controller, result, dec, scope); } else if (ol!=null && ol.reference) { if (isReferenceProposable(ol, dec)) { addProgramElementReferenceProposal( offset, prefix, controller, result, dec, scope, isMember); } } else { Reference pr = isMember ? getQualifiedProducedReference(node, dec) : getRefinedProducedReference(scope, dec); if (secondLevel) { addSecondLevelProposal( offset, prefix, controller, result, dec, scope, false, pr, requiredType, ol); } else { if (!(dec instanceof Function) || !isAbstraction(dec) || !noParamsFollow) { addReferenceProposal( offset, prefix, controller, result, dwp, scope, isMember, pr, ol); } } } } if (!memberOp && !secondLevel && isProposable(dwp, ol, scope, unit, requiredType, previousTokenType) && ol!=IMPORT && ol!=CASE && ol!=CATCH && isDirectlyInsideBlock(node, controller, scope, token)) { addForProposal(offset, prefix, controller, result, dwp, dec); addIfExistsProposal(offset, prefix, controller, result, dwp, dec); addAssertExistsProposal(offset, prefix, controller, result, dwp, dec); addIfNonemptyProposal(offset, prefix, controller, result, dwp, dec); addAssertNonemptyProposal(offset, prefix, controller, result, dwp, dec); addTryProposal(offset, prefix, controller, result, dwp, dec); addSwitchProposal(offset, prefix, controller, result, dwp, dec, node, doc); } if (!memberOp && !isMember && !secondLevel) { for (Declaration d: overloads(dec)) { if (isRefinementProposable(d, ol, scope)) { addRefinementProposal(offset, d, (ClassOrInterface) scope, node, scope, prefix, controller, doc, result, true); } } } } catch (Exception e) { e.printStackTrace(); } } if (node instanceof Tree.QualifiedMemberExpression || memberOp && node instanceof Tree.QualifiedTypeExpression) { for (DeclarationWithProximity dwp: sortedFunctionProposals) { Tree.QualifiedMemberOrTypeExpression qmte = (Tree.QualifiedMemberOrTypeExpression) node; Tree.Primary primary = qmte.getPrimary(); addFunctionProposal(offset, controller, primary, result, dwp.getDeclaration(), doc); } } } if (previousTokenType==CeylonLexer.OBJECT_DEFINITION) { addKeywordProposals(controller, offset, prefix, result, node, ol, false, tokenType); } return result.toArray(NO_COMPLETIONS); } private static boolean isDelegatableConstructor( Scope scope, Declaration dec) { if (isConstructor(dec)) { Scope container = dec.getContainer(); Scope outerScope = scope.getContainer(); if (container==null || outerScope==null) { return false; } else if (outerScope.equals(container)) { return !scope.equals(dec); //local constructor } else { TypeDeclaration id = scope.getInheritingDeclaration(dec); return id!=null && id.equals(outerScope); //inherited constructor } } else if (dec instanceof Class) { Scope outerScope = scope.getContainer(); if (outerScope instanceof Class) { Class c = (Class) outerScope; Type sup = c.getExtendedType(); return sup!=null && sup.getDeclaration().equals(dec); } else { return false; } } else { return false; } } private static boolean isProposable(Node node, OccurrenceLocation ol, Declaration dec) { if (ol!=EXISTS && ol!=NONEMPTY && ol!=IS) { return true; } else if (dec instanceof Value) { Value val = (Value) dec; Type type = val.getType(); if (val.isVariable() || val.isTransient() || val.isDefault() || val.isFormal() || isTypeUnknown(type)) { return false; } else { Unit unit = node.getUnit(); switch (ol) { case EXISTS: return unit.isOptionalType(type); case NONEMPTY: return unit.isPossiblyEmptyType(type); case IS: return true; default: return false; } } } else { return false; } } private static boolean isReferenceProposable(OccurrenceLocation ol, Declaration dec) { return (ol==VALUE_REF || !(dec instanceof Value) || ((Value)dec).getTypeDeclaration().isAnonymous()) && (ol==FUNCTION_REF || !(dec instanceof Function)) && (ol==ALIAS_REF || !(dec instanceof TypeAlias)) && (ol==TYPE_PARAMETER_REF || !(dec instanceof TypeParameter)) && //note: classes and interfaces are almost always proposable // because they are legal qualifiers for other refs (ol!=TYPE_PARAMETER_REF || dec instanceof TypeParameter); } private static void addRefinementProposals(int offset, Set<DeclarationWithProximity> set, CeylonParseController cpc, Scope scope, Node node, IDocument doc, boolean filter, final List<ICompletionProposal> result, OccurrenceLocation ol, Type t, boolean preamble) { for (DeclarationWithProximity dwp: set) { Declaration dec = dwp.getDeclaration(); if (!filter && dec instanceof FunctionOrValue) { FunctionOrValue m = (FunctionOrValue) dec; for (Declaration d: overloads(dec)) { if (isRefinementProposable(d, ol, scope) && isReturnType(t, m, node)) { try { int start = node.getStartIndex(); String pfx = doc.get(start, offset-start); addRefinementProposal(offset, d, (ClassOrInterface) scope, node, scope, pfx, cpc, doc, result, preamble); } catch (BadLocationException e) { e.printStackTrace(); } } } } } } private static final List<Type> NO_TYPES = Collections.<Type>emptyList(); private static boolean isReturnType(Type t, FunctionOrValue m, Node node) { if (t.isSubtypeOf(m.getType())) { return true; } if (node instanceof Tree.TypedDeclaration) { Tree.TypedDeclaration td = (Tree.TypedDeclaration) node; Scope container = td.getDeclarationModel().getContainer(); if (container instanceof ClassOrInterface) { ClassOrInterface ci = (ClassOrInterface) container; Type type = ci.getType() .getTypedMember(m, NO_TYPES) .getType(); if (t.isSubtypeOf(type)) { return true; } } } return false; } private static boolean isQualifiedType(Node node) { if (node instanceof Tree.QualifiedType) { return true; } else if (node instanceof Tree.QualifiedMemberOrTypeExpression) { Tree.QualifiedMemberOrTypeExpression qmte = (Tree.QualifiedMemberOrTypeExpression) node; return qmte.getStaticMethodReference(); } else { return false; } } private static boolean noParametersFollow(CommonToken nextToken) { return nextToken==null || //should we disable this, since a statement //can in fact begin with an LPAREN?? nextToken.getType()!=LPAREN //disabled now because a declaration can //begin with an LBRACE (an Iterable type) /*&& nextToken.getType()!=CeylonLexer.LBRACE*/; } private static boolean definitelyRequiresType( OccurrenceLocation ol) { return ol==SATISFIES || ol==OF || ol==UPPER_BOUND || ol==TYPE_ALIAS; } private static CommonToken getNextToken( CeylonParseController cpc, CommonToken token) { int i = token.getTokenIndex(); CommonToken nextToken=null; List<CommonToken> tokens = cpc.getTokens(); do { if (++i<tokens.size()) { nextToken = tokens.get(i); } else { break; } } while (nextToken.getChannel()==HIDDEN_CHANNEL); return nextToken; } private static boolean isDirectlyInsideNamedArgumentList( CeylonParseController cpc, Node node, CommonToken token) { return node instanceof Tree.NamedArgumentList || (!(node instanceof Tree.SequenceEnumeration) && occursAfterBraceOrSemicolon(token, cpc.getTokens())); } private static boolean isMemberOperator(Token token) { int type = token.getType(); return type==MEMBER_OP || type==SPREAD_OP || type==SAFE_MEMBER_OP; } private static boolean isRefinementProposable( Declaration dec, OccurrenceLocation ol, Scope scope) { return ol==null && (dec.isDefault() || dec.isFormal()) && (dec instanceof FunctionOrValue || dec instanceof Class) && scope instanceof ClassOrInterface && ((ClassOrInterface) scope).isInheritedFromSupertype(dec); } private static boolean isInvocationProposable( DeclarationWithProximity dwp, OccurrenceLocation ol, int previousTokenType) { Declaration dec = dwp.getDeclaration(); return dec instanceof Functional && previousTokenType!=IS_OP && (previousTokenType!=CASE_TYPES||ol==OF) && (ol==null || ol==EXPRESSION && (!(dec instanceof Class) || !((Class) dec).isAbstract()) || ol==EXTENDS && dec instanceof Class && !((Class) dec).isFinal() && ((Class) dec).getTypeParameters().isEmpty() || ol==EXTENDS && isConstructor(dec) && !((Class) dec.getContainer()).isFinal() && ((Class) dec.getContainer()).getTypeParameters().isEmpty() || ol==CLASS_ALIAS && dec instanceof Class || ol==PARAMETER_LIST && dec instanceof Function && dec.isAnnotation()) && dwp.getNamedArgumentList()==null && (!dec.isAnnotation() || !(dec instanceof Function) || !((Function) dec).getParameterLists().isEmpty() && !((Function) dec).getParameterLists().get(0).getParameters().isEmpty()); } private static boolean isProposable( DeclarationWithProximity dwp, OccurrenceLocation ol, Scope scope, Unit unit, Type requiredType, int previousTokenType) { Declaration dec = dwp.getDeclaration(); return (ol!=EXTENDS || dec instanceof Class && !((Class) dec).isFinal() || isConstructor(dec) && !((Class) dec.getContainer()).isFinal()) && (ol!=CLASS_ALIAS || dec instanceof Class) && (ol!=SATISFIES || dec instanceof Interface) && (ol!=OF || dec instanceof Class || isAnonymousClassValue(dec)) && ((ol!=TYPE_ARGUMENT_LIST && ol!=UPPER_BOUND && ol!=TYPE_ALIAS && ol!=CATCH) || dec instanceof TypeDeclaration) && (ol!=CATCH || isExceptionType(unit, dec)) && (ol!=PARAMETER_LIST || dec instanceof TypeDeclaration || dec instanceof Function && dec.isAnnotation() || //i.e. an annotation dec instanceof Value && dec.getContainer().equals(scope)) && //a parameter ref (ol!=IMPORT || !dwp.isUnimported()) && (ol!=CASE || isCaseOfSwitch(requiredType, dec, previousTokenType)) && (previousTokenType!=IS_OP && (previousTokenType!=CASE_TYPES||ol==OF) || dec instanceof TypeDeclaration) && ol!=TYPE_PARAMETER_LIST && dwp.getNamedArgumentList()==null; } private static boolean isCaseOfSwitch(Type requiredType, Declaration dec, int previousTokenType) { return previousTokenType==IS_OP && isTypeCaseOfSwitch(requiredType, dec) || previousTokenType!=IS_OP && isValueCaseOfSwitch(requiredType, dec); } private static boolean isValueCaseOfSwitch(Type requiredType, Declaration dec) { if (requiredType!=null && requiredType.isUnion()) { for (Type td: requiredType.getCaseTypes()) { if (isValueCaseOfSwitch(td, dec)) { return true; } } return false; } else { if (isAnonymousClassValue(dec)) { if (requiredType==null) return true; TypedDeclaration d = (TypedDeclaration) dec; TypeDeclaration td = d.getTypeDeclaration(); TypeDeclaration rtd = requiredType.getDeclaration(); return td.inherits(rtd); } else { return false; } } } private static boolean isTypeCaseOfSwitch(Type requiredType, Declaration dec) { if (requiredType!=null && requiredType.isUnion()) { for (Type td: requiredType.getCaseTypes()) { if (isTypeCaseOfSwitch(td, dec)) { return true; } } return false; } else { if (dec instanceof TypeDeclaration) { if (requiredType==null) return true; TypeDeclaration td = (TypeDeclaration) dec; TypeDeclaration rtd = requiredType.getDeclaration(); return td.inherits(rtd); } else { return false; } } } private static boolean isExceptionType(Unit unit, Declaration dec) { if (dec instanceof TypeDeclaration) { TypeDeclaration td = (TypeDeclaration) dec; return td.inherits(unit.getExceptionDeclaration()); } else { return false; } } private static boolean isAnonymousClassValue(Declaration dec) { if (dec instanceof Value) { Value value = (Value) dec; TypeDeclaration vtd = value.getTypeDeclaration(); return vtd!=null && vtd.isAnonymous(); } else { return false; } } private static boolean isTypeParameterOfCurrentDeclaration( Node node, Declaration d) { //TODO: this is a total mess and totally error-prone // - figure out something better! if (d instanceof TypeParameter) { TypeParameter tp = (TypeParameter) d; Scope tpc = tp.getContainer(); if (tpc==node.getScope()) { return true; } else { Tree.TypeConstraint constraint = (Tree.TypeConstraint) node; TypeParameter tcp = constraint.getDeclarationModel(); return tcp!=null && tpc==tcp.getContainer(); } } else { return false; } } private static boolean isParameterOfNamedArgInvocation( Scope scope, DeclarationWithProximity d) { return scope==d.getNamedArgumentList(); } private static Reference getQualifiedProducedReference( Node node, Declaration d) { Type pt; if (node instanceof Tree.QualifiedMemberOrTypeExpression) { Tree.QualifiedMemberOrTypeExpression qmte = (Tree.QualifiedMemberOrTypeExpression) node; pt = qmte.getPrimary().getTypeModel(); } else if (node instanceof Tree.QualifiedType) { Tree.QualifiedType qt = (Tree.QualifiedType) node; pt = qt.getOuterType().getTypeModel(); } else { return null; } if (pt!=null && d.isClassOrInterfaceMember()) { TypeDeclaration container = (TypeDeclaration) d.getContainer(); pt = pt.getSupertype(container); } return d.appliedReference(pt, Collections.<Type>emptyList()); } private static Set<DeclarationWithProximity> sortProposals(String prefix, Types.Required required, Map<String, DeclarationWithProximity> proposals) { Set<DeclarationWithProximity> set = new TreeSet<DeclarationWithProximity> (new ProposalComparator(prefix, required)); set.addAll(proposals.values()); return set; } public static Map<String, DeclarationWithProximity> getProposals(Node node, Scope scope, Tree.CompilationUnit rootNode, Cancellable cancellable) { return getProposals(node, scope, "", false, rootNode, cancellable); } private static Map<String, DeclarationWithProximity> getFunctionProposals(Node node, Scope scope, String prefix, boolean memberOp, Cancellable cancellable) { Unit unit = node.getUnit(); if (node instanceof Tree.QualifiedMemberOrTypeExpression) { Tree.QualifiedMemberOrTypeExpression qmte = (Tree.QualifiedMemberOrTypeExpression) node; Type type = getPrimaryType(qmte); if (!qmte.getStaticMethodReference() && !isTypeUnknown(type)) { return collectUnaryFunctions(type, scope.getMatchingDeclarations( unit, prefix, 0, cancellable)); } } else if (memberOp && node instanceof Tree.Term) { Type type = null; if (node instanceof Tree.Term) { Tree.Term term = (Tree.Term) node; type = term.getTypeModel(); } if (type!=null) { return collectUnaryFunctions(type, scope.getMatchingDeclarations( unit, prefix, 0, cancellable)); } else { return emptyMap(); } } return emptyMap(); } public static Map<String, DeclarationWithProximity> collectUnaryFunctions(Type type, Map<String, DeclarationWithProximity> candidates) { Map<String,DeclarationWithProximity> matches = new HashMap<String, DeclarationWithProximity>(); for (Map.Entry<String,DeclarationWithProximity> e: candidates.entrySet()) { Declaration declaration = e.getValue().getDeclaration(); if (declaration instanceof Function && !declaration.isAnnotation()) { Function m = (Function) declaration; List<ParameterList> pls = m.getParameterLists(); if (!pls.isEmpty()) { ParameterList pl = pls.get(0); List<Parameter> params = pl.getParameters(); if (!params.isEmpty()) { boolean unary=true; for (int i=1; i<params.size(); i++) { if (!params.get(i).isDefaulted()) { unary = false; } } Type t = params.get(0).getType(); if (unary && !isTypeUnknown(t) && type.isSubtypeOf(t)) { matches.put(e.getKey(), e.getValue()); } } } } } return matches; } private static Map<String, DeclarationWithProximity> getProposals(Node node, Scope scope, String prefix, boolean memberOp, Tree.CompilationUnit rootNode, Cancellable cancellable) { Unit unit = node.getUnit(); if (node instanceof Tree.MemberLiteral) { Tree.MemberLiteral ml = (Tree.MemberLiteral) node; Tree.StaticType mlt = ml.getType(); if (mlt!=null) { Type type = mlt.getTypeModel(); if (type!=null) { return type.resolveAliases() .getDeclaration() .getMatchingMemberDeclarations( unit, scope, prefix, 0); } else { return emptyMap(); } } } else if (node instanceof Tree.TypeLiteral) { Tree.TypeLiteral tl = (Tree.TypeLiteral) node; Tree.StaticType tlt = tl.getType(); if (tlt instanceof Tree.BaseType) { Tree.BaseType bt = (Tree.BaseType) tlt; if (bt.getPackageQualified()) { return unit.getPackage() .getMatchingDirectDeclarations( prefix, 0); } } if (tlt!=null) { Type type = tlt.getTypeModel(); if (type!=null) { return type.resolveAliases() .getDeclaration() .getMatchingMemberDeclarations( unit, scope, prefix, 0); } else { return emptyMap(); } } } if (node instanceof Tree.QualifiedMemberOrTypeExpression) { Tree.QualifiedMemberOrTypeExpression qmte = (Tree.QualifiedMemberOrTypeExpression) node; Type type = getPrimaryType(qmte); if (qmte.getStaticMethodReference()) { type = unit.getCallableReturnType(type); } if (type!=null && !type.isUnknown()) { return type.resolveAliases() .getDeclaration() .getMatchingMemberDeclarations( unit, scope, prefix, 0); } else { Tree.Primary primary = qmte.getPrimary(); if (primary instanceof Tree.MemberOrTypeExpression) { //it might be a qualified type or even a static method reference Tree.MemberOrTypeExpression pmte = (Tree.MemberOrTypeExpression) primary; Declaration d = pmte.getDeclaration(); if (d instanceof TypeDeclaration) { TypeDeclaration td = (TypeDeclaration) d; type = td.getType(); if (type!=null) { return type.resolveAliases() .getDeclaration() .getMatchingMemberDeclarations( unit, scope, prefix, 0); } } } else if (primary instanceof Tree.Package) { return unit.getPackage() .getMatchingDirectDeclarations( prefix, 0); } } return emptyMap(); } else if (node instanceof Tree.QualifiedType) { Tree.QualifiedType type = (Tree.QualifiedType) node; Type t = type.getOuterType().getTypeModel(); if (t!=null) { return t.resolveAliases() .getDeclaration() .getMatchingMemberDeclarations( unit, scope, prefix, 0); } else { return emptyMap(); } } else if (node instanceof Tree.BaseType) { Tree.BaseType type = (Tree.BaseType) node; if (type.getPackageQualified()) { return unit.getPackage() .getMatchingDirectDeclarations( prefix, 0); } else if (scope!=null) { return scope.getMatchingDeclarations( unit, prefix, 0, cancellable); } else { return emptyMap(); } } else if (memberOp && (node instanceof Tree.Term || node instanceof Tree.DocLink)) { Type type = null; if (node instanceof Tree.DocLink) { Tree.DocLink docLink = (Tree.DocLink) node; Declaration d = docLink.getBase(); if (d != null) { type = getResultType(d); if (type == null) { type = d.getReference().getFullType(); } } } // else if (node instanceof Tree.StringLiteral) { // type = null; // } else if (node instanceof Tree.Term) { Tree.Term term = (Tree.Term) node; type = term.getTypeModel(); } if (type!=null) { return type.resolveAliases() .getDeclaration() .getMatchingMemberDeclarations( unit, scope, prefix, 0); } else { return scope.getMatchingDeclarations( unit, prefix, 0, cancellable); } } else { if (scope instanceof ImportList) { ImportList IL = (ImportList) scope; return IL.getMatchingDeclarations( unit, prefix, 0, cancellable); } else { return scope==null ? //a null scope occurs when we have not finished parsing the file getUnparsedProposals(rootNode, prefix, cancellable) : scope.getMatchingDeclarations( unit, prefix, 0, cancellable); } } } private static Type getPrimaryType( Tree.QualifiedMemberOrTypeExpression qme) { Type type = qme.getPrimary().getTypeModel(); if (type==null) { return null; } else { Tree.MemberOperator mo = qme.getMemberOperator(); Unit unit = qme.getUnit(); if (mo instanceof Tree.SafeMemberOp) { return unit.getDefiniteType(type); } else if (mo instanceof Tree.SpreadOp) { return unit.getIteratedType(type); } else { return type; } } } private static Map<String, DeclarationWithProximity> getUnparsedProposals(Node node, String prefix, Cancellable cancellable) { if (node == null) { return newEmptyProposals(); } Unit unit = node.getUnit(); if (unit == null) { return newEmptyProposals(); } Package pkg = unit.getPackage(); if (pkg == null) { return newEmptyProposals(); } return pkg.getModule() .getAvailableDeclarations(prefix, 0, cancellable); } private static TreeMap<String, DeclarationWithProximity> newEmptyProposals() { return new TreeMap<String,DeclarationWithProximity>(); } static List<IContextInformation> computeParameterContextInformation( final int offset, final Tree.CompilationUnit rootNode, final ITextViewer viewer) { final List<IContextInformation> infos = new ArrayList<IContextInformation>(); rootNode.visit(new Visitor() { @Override public void visit(Tree.InvocationExpression that) { Tree.ArgumentList al = that.getPositionalArgumentList(); if (al==null) { al = that.getNamedArgumentList(); } if (al!=null) { //TODO: should reuse logic for adjusting tokens // from CeylonContentProposer!! Integer start = al.getStartIndex(); Integer stop = al.getEndIndex(); if (start!=null && stop!=null && offset>start) { String string = ""; if (offset>stop) { try { string = viewer.getDocument() .get(stop, offset-stop); } catch (BadLocationException e) {} } if (string.trim().isEmpty()) { Unit unit = rootNode.getUnit(); Tree.Term primary = that.getPrimary(); Declaration declaration; Reference target; if (primary instanceof Tree.MemberOrTypeExpression) { Tree.MemberOrTypeExpression mte = (Tree.MemberOrTypeExpression) primary; declaration = mte.getDeclaration(); target = mte.getTarget(); } else { declaration = null; target = null; } if (declaration instanceof Functional) { Functional fd = (Functional) declaration; List<ParameterList> pls = fd.getParameterLists(); if (!pls.isEmpty()) { //Note: This line suppresses the little menu // that gives me a choice of context infos. // Delete it to get a choice of all surrounding // argument lists. infos.clear(); infos.add(new ParameterContextInformation( declaration, target, unit, pls.get(0), start, true, al instanceof Tree.NamedArgumentList)); } } else { Type type = primary.getTypeModel(); if (unit.isCallableType(type)) { List<Type> argTypes = unit.getCallableArgumentTypes(type); if (!argTypes.isEmpty()) { infos.clear(); infos.add(new ParametersCompletionProposal.ParameterContextInformation( argTypes, start, unit)); } } } } } } super.visit(that); } }); return infos; } }