/**
* Aptana Studio
* Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved.
* Licensed under the terms of the GNU Public License (GPL) v3 (with exceptions).
* Please see the license.html included with this distribution for details.
* Any modifications to this file must keep this entire header intact.
*/
package com.aptana.editor.php.internal.contentAssist;
import static com.aptana.editor.php.internal.contentAssist.PHPContextCalculator.EXTENDS_PROPOSAL_CONTEXT_TYPE;
import static com.aptana.editor.php.internal.contentAssist.PHPContextCalculator.IMPLEMENTS_PROPOSAL_CONTEXT_TYPE;
import static com.aptana.editor.php.internal.contentAssist.PHPContextCalculator.NAMESPACE_PROPOSAL_CONTEXT_TYPE;
import static com.aptana.editor.php.internal.contentAssist.PHPContextCalculator.NEW_PROPOSAL_CONTEXT_TYPE;
import static com.aptana.editor.php.internal.contentAssist.PHPContextCalculator.TRAIT_USE_BLOCK_PROPOSAL_CONTEXT_TYPE;
import static com.aptana.editor.php.internal.contentAssist.PHPContextCalculator.TRAIT_USE_PROPOSAL_CONTEXT_TYPE;
import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
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.ITypedRegion;
import org.eclipse.jface.text.Position;
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 org2.eclipse.php.core.compiler.PHPFlags;
import org2.eclipse.php.internal.core.PHPVersion;
import org2.eclipse.php.internal.core.documentModel.parser.AbstractPhpLexer;
import org2.eclipse.php.internal.core.documentModel.parser.PhpLexerFactory;
import org2.eclipse.php.internal.core.documentModel.parser.regions.PHPRegionTypes;
import com.aptana.core.logging.IdeLog;
import com.aptana.core.util.CollectionsUtil;
import com.aptana.core.util.ResourceUtil;
import com.aptana.core.util.StringUtil;
import com.aptana.editor.common.AbstractThemeableEditor;
import com.aptana.editor.common.CommonContentAssistProcessor;
import com.aptana.editor.common.contentassist.ICommonCompletionProposal;
import com.aptana.editor.common.contentassist.ILexemeProvider;
import com.aptana.editor.common.text.rules.CompositePartitionScanner;
import com.aptana.editor.php.PHPEditorPlugin;
import com.aptana.editor.php.core.PHPVersionProvider;
import com.aptana.editor.php.indexer.IElementEntry;
import com.aptana.editor.php.indexer.IElementsIndex;
import com.aptana.editor.php.indexer.IIndexReporter;
import com.aptana.editor.php.indexer.IPHPIndexConstants;
import com.aptana.editor.php.indexer.IReportable;
import com.aptana.editor.php.indexer.PHPGlobalIndexer;
import com.aptana.editor.php.internal.builder.LocalModule;
import com.aptana.editor.php.internal.contentAssist.preferences.IContentAssistPreferencesConstants;
import com.aptana.editor.php.internal.core.IPHPConstants;
import com.aptana.editor.php.internal.core.builder.IModule;
import com.aptana.editor.php.internal.indexer.AbstractPHPEntryValue;
import com.aptana.editor.php.internal.indexer.AccessModifierEntryFilter;
import com.aptana.editor.php.internal.indexer.ClassPHPEntryValue;
import com.aptana.editor.php.internal.indexer.ElementsIndexingUtils;
import com.aptana.editor.php.internal.indexer.FunctionPHPEntryValue;
import com.aptana.editor.php.internal.indexer.IEntryFilter;
import com.aptana.editor.php.internal.indexer.ModuleSubstitutionIndex;
import com.aptana.editor.php.internal.indexer.NamespacePHPEntryValue;
import com.aptana.editor.php.internal.indexer.PDTPHPModuleIndexer;
import com.aptana.editor.php.internal.indexer.PHPTypeProcessor;
import com.aptana.editor.php.internal.indexer.PublicsOnlyEntryFilter;
import com.aptana.editor.php.internal.indexer.TraitPHPEntryValue;
import com.aptana.editor.php.internal.indexer.UnpackedElementIndex;
import com.aptana.editor.php.internal.indexer.UnpackedEntry;
import com.aptana.editor.php.internal.indexer.VariablePHPEntryValue;
import com.aptana.editor.php.internal.indexer.language.PHPBuiltins;
import com.aptana.editor.php.internal.model.utils.TypeHierarchyUtils;
import com.aptana.editor.php.internal.parser.nodes.IPHPParseNode;
import com.aptana.editor.php.internal.parser.nodes.PHPBaseParseNode;
import com.aptana.editor.php.internal.parser.nodes.PHPClassParseNode;
import com.aptana.editor.php.internal.parser.nodes.PHPFunctionParseNode;
import com.aptana.editor.php.internal.parser.nodes.PHPVariableParseNode;
import com.aptana.editor.php.internal.parser.nodes.Parameter;
import com.aptana.editor.php.internal.ui.editor.PHPSourceEditor;
import com.aptana.editor.php.internal.ui.editor.PHPVersionDocumentManager;
import com.aptana.editor.php.internal.ui.editor.contentassist.PHPContextInformationValidator;
import com.aptana.editor.php.internal.ui.editor.outline.PHPDecoratingLabelProvider;
import com.aptana.editor.php.internal.ui.editor.outline.PHPOutlineItem;
import com.aptana.parsing.lexer.IRange;
import com.aptana.parsing.lexer.Range;
/**
* Content assist processor for PHP.
*
* @author Shalom Gibly <sgibly@aptana.com>
*/
public class PHPContentAssistProcessor extends CommonContentAssistProcessor implements IContentAssistProcessor
{
/**
* Max look-behind chars when computing the lexemes for the context-info detection.
*/
private static final int CONTEXT_INFO_MAX_LOOK_BEHIND = 100;
private static final IContextInformation[] EMPTY_CONTEXT_INFO = new IContextInformation[0];
private static final int EXTERNAL_CLASS_PROPOSAL_RELEVANCE = ICommonCompletionProposal.RELEVANCE_MEDIUM - 10;
private static final int EXTERNAL_FUNCTION_PROPOSAL_RELEVANCE = ICommonCompletionProposal.RELEVANCE_MEDIUM - 15;
private static final int EXTERNAL_CONSTANT_PROPOSAL_RELEVANCE = ICommonCompletionProposal.RELEVANCE_MEDIUM - 20;
private static final int EXTERNAL_DEFAULT_PROPOSAL_RELEVANCE = ICommonCompletionProposal.RELEVANCE_MEDIUM - 25;
private static final ICompletionProposal[] EMPTY_PROPOSAL = new ICompletionProposal[0];
protected static final String DOLLAR_SIGN = "$"; //$NON-NLS-1$
/**
* The global namespace char, which is also used as a namespace separator.
*/
public static final String GLOBAL_NAMESPACE = "\\"; //$NON-NLS-1$
/**
* Dereference operator.
*/
public static final String DEREFERENCE_OP = "->"; //$NON-NLS-1$
/**
* Static dereference operator.
*/
public static final String STATIC_DEREFERENCE_OP = "::"; //$NON-NLS-1$
/**
* Possible dereference operators.
*/
public static final String[] OPS = new String[] { DEREFERENCE_OP, STATIC_DEREFERENCE_OP };
/**
* "$this" activation sequence.
*/
private static final String THIS_ACTIVATION_SEQUENCE = "$this"; //$NON-NLS-1$
/**
* "self" activation sequence.
*/
private static final String SELF_ACTIVATION_SEQUENCE = "self"; //$NON-NLS-1$
/**
* "static" activation sequence.
*/
private static final String STATIC_ACTIVATION_SEQUENCE = "static"; //$NON-NLS-1$
/**
* "parent" activation sequence.
*/
private static final String PARENT_ACTIVATION_SEQUENCE = "parent"; //$NON-NLS-1$
/**
* Content assist suggestions that we should only allow under classes
*/
@SuppressWarnings("nls")
private static final Set<String> ALLOW_ONLY_UNDER_CLASS = new HashSet<String>(Arrays.asList("$this", "self",
"parent", "public", "private", "protected")); //$NON-NLS-4$
private static final IRange EMPTY_RANGE = new Range(0, 0);
private static String fIcon54 = ResourceUtil.resourcePathToString(PHPEditorPlugin.getDefault().getBundle().getEntry("icons/full/obj16/php_icon_v54.png")); //$NON-NLS-1$
private static String fIcon53 = ResourceUtil.resourcePathToString(PHPEditorPlugin.getDefault().getBundle().getEntry("icons/full/obj16/php_icon_v53.png")); //$NON-NLS-1$
private static String fIcon5 = ResourceUtil.resourcePathToString(PHPEditorPlugin.getDefault().getBundle().getEntry("icons/full/obj16/php_icon_v5.png")); //$NON-NLS-1$
private static String fIcon4 = ResourceUtil.resourcePathToString(PHPEditorPlugin.getDefault().getBundle().getEntry("icons/full/obj16/php_icon_v4.png")); //$NON-NLS-1$
private static String fIcon54off = ResourceUtil.resourcePathToString(PHPEditorPlugin.getDefault().getBundle().getEntry("icons/full/obj16/php_icon_v54_off.png")); //$NON-NLS-1$
private static String fIcon53off = ResourceUtil.resourcePathToString(PHPEditorPlugin.getDefault().getBundle().getEntry("icons/full/obj16/php_icon_v53_off.png")); //$NON-NLS-1$
private static String fIcon5off = ResourceUtil.resourcePathToString(PHPEditorPlugin.getDefault().getBundle().getEntry("icons/full/obj16/php_icon_v5_off.png")); //$NON-NLS-1$
private static String fIcon4off = ResourceUtil.resourcePathToString(PHPEditorPlugin.getDefault().getBundle().getEntry("icons/full/obj16/php_icon_v4_off.png")); //$NON-NLS-1$
// Auto-activation chars. Note that we omit the $ sign, as we have to verify its context. The $ is handled as any
// other char in the isValidAutoActivationLocation()
private static char[] autoactivationCharacters = "@:>&$abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ".toCharArray();
private static final char[] contextInformationActivationChars = { '(', ',' };
// When the current context scope is one of these types, we allow identifiers with empty name (see
// simpleIdentifierCompletion());
private static final Set<String> TYPES_ACCEPTING_EMPTY_IDENTIFIERS = CollectionsUtil.newSet(
EXTENDS_PROPOSAL_CONTEXT_TYPE, IMPLEMENTS_PROPOSAL_CONTEXT_TYPE, NEW_PROPOSAL_CONTEXT_TYPE,
TRAIT_USE_BLOCK_PROPOSAL_CONTEXT_TYPE, TRAIT_USE_PROPOSAL_CONTEXT_TYPE);
private static PHPDecoratingLabelProvider labelProvider = new PHPDecoratingLabelProvider(false);
private ITextViewer viewer;
private int offset;
private String content;
/**
* Whether reported scope is global.
*/
private boolean reportedScopeIsGlobal = true;
/**
* Whether reported scope is under a class definition.
*/
private boolean reportedScopeIsUnderClass = false;
/**
* Reported imports.
*/
private Set<String> globalImports;
private Map<String, String> aliases;
private String namespace;
private IModule module;
/**
* Current proposal context. By default this instance accepts all and everything.
*/
private ProposalContext currentContext;
private PHPContextCalculator contextCalculator;
private IPreferenceStore preferenceStore;
private boolean isOutOfWorkspace;
private IDocument document;
/**
* Constructs a new PHP content assist processor.
*
* @param editor
*/
public PHPContentAssistProcessor(AbstractThemeableEditor editor)
{
super(editor);
if (editor == null)
{
throw new IllegalArgumentException("Expected PHPSourceEditor, but got null"); //$NON-NLS-1$
}
if (!(editor instanceof PHPSourceEditor))
{
throw new IllegalArgumentException("Expected PHPSourceEditor, but got " + editor.getClass().getName()); //$NON-NLS-1$
}
preferenceStore = PHPEditorPlugin.getDefault().getPreferenceStore();
currentContext = new ProposalContext(new AcceptAllContextFilter(), true, true, null);
contextCalculator = new PHPContextCalculator();
}
/*
* (non-Javadoc)
* @see com.aptana.editor.common.CommonContentAssistProcessor#isValidAutoActivationLocation(char, int,
* org.eclipse.jface.text.IDocument, int)
*/
@Override
public boolean isValidAutoActivationLocation(char c, int keyCode, IDocument document, int offset)
{
// make sure we don't popup a proposal when typing the <?php
try
{
String openPhp = document.get(Math.max(0, offset - 4),
Math.min(Math.min(offset, 4), document.getLength() - 1));
if (openPhp.endsWith("<?") || openPhp.endsWith("<?p") || openPhp.endsWith("<?ph")) //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
{
return false;
}
}
catch (Exception e) // $codepro.audit.disable emptyCatchClause
{
// ignore it and just use the lexemeProvider
}
if(Character.isDigit(c)){
return false;
}
ILexemeProvider<PHPTokenType> lexemeProvider = ParsingUtils.createLexemeProvider(document, offset);
currentContext = contextCalculator.calculateCompletionContext(lexemeProvider, offset, c,
reportedScopeIsUnderClass);
if (!currentContext.acceptModelsElements() && !currentContext.isAutoActivateCAAfterApply())
{
return false;
}
return true;
}
/*
* (non-Javadoc)
* @see com.aptana.editor.common.CommonContentAssistProcessor#isValidIdentifier(char, int)
*/
public boolean isValidIdentifier(char c, int keyCode)
{
return Character.isJavaIdentifierStart(c) || Character.isJavaIdentifierPart(c);
}
/**
* Calls the SnippetsCompletionProcessor to contribute any relevant snippets for the offset, in case the current
* context allows snippet proposals.
*
* @see com.aptana.editor.common.CommonContentAssistProcessor#addSnippetProposals(org.eclipse.jface.text.ITextViewer,
* int)
*/
@Override
protected Collection<ICompletionProposal> addSnippetProposals(ITextViewer viewer, int offset, boolean autoActivated)
{
if (currentContext != null && !currentContext.acceptExternalProposals())
{
return Collections.emptyList();
}
return super.addSnippetProposals(viewer, offset, autoActivated);
}
/**
* (Experimental) This hooks our Ruble scripting up to Content Assist, allowing them to contribute possible
* proposals, in case the current context allows snippet proposals. (non-Javadoc)
*
* @see com.aptana.editor.common.CommonContentAssistProcessor#addRubleProposals(org.eclipse.jface.text.ITextViewer,
* int)
*/
@Override
protected List<ICompletionProposal> addRubleProposals(ITextViewer viewer, int offset)
{
if (currentContext != null && !currentContext.acceptExternalProposals())
{
return Collections.emptyList();
}
return super.addRubleProposals(viewer, offset);
}
@Override
public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset)
{
this.viewer = viewer;
this.document = viewer.getDocument();
return computeCompletionProposals(document, offset);
}
/**
* Compute the proposals using an offset and a document. Note: this is a convenient way of separating the UI (the
* ITextViewer) from the actual computation, so it can be tested easily.
*
* @param viewer
* @param offset
* @param document
* @return
*/
public ICompletionProposal[] computeCompletionProposals(IDocument document, int offset)
{
// First, check if we are in a PHP partition
ITypedRegion partition;
try
{
// Make sure that if the offset is at the end of the document, we test for the offset-1. This
// is done due to a bug in the Studio that returns the default partition and not the php-default
// partition.
int length = document.getLength();
if (length == offset && length > 0)
{
partition = document.getPartition(offset - 1);
}
else
{
partition = document.getPartition(offset);
}
if (!partition.getType().startsWith(IPHPConstants.PREFIX))
{
// The partition does not start with our __php_ prefix, so do not return any proposals.
return null;
}
}
catch (BadLocationException e1)
{
IdeLog.logError(PHPEditorPlugin.getDefault(), "Error computing PHP completion proposals", e1); //$NON-NLS-1$
return null;
}
PHPVersion phpVersion = PHPVersionDocumentManager.getPHPVersion(document);
if (phpVersion == null)
{
phpVersion = PHPVersionProvider.getDefaultPHPVersion();
}
ILexemeProvider<PHPTokenType> lexemeProvider = ParsingUtils.createLexemeProvider(document, offset);
// Calculates and sets completion context
// Call to get the index. This will update the reported scope.
getIndex(content, offset);
currentContext = contextCalculator
.calculateCompletionContext(lexemeProvider, offset, reportedScopeIsUnderClass);
String content = document.get();
AbstractPhpLexer lexer = PhpLexerFactory.createLexer(new StringReader(content), phpVersion); // $codepro.audit.disable
// closeWhereCreated
int state = -1;
try
{
// set initial lexer state - we use reflection here since we don't
// know the constant value of
// of this state in specific PHP version lexer
state = lexer.getClass().getField("ST_PHP_IN_SCRIPTING").getInt(lexer); //$NON-NLS-1$
}
catch (Exception e)
{
IdeLog.logError(PHPEditorPlugin.getDefault(), "Error grabbing a field from the PHP lexer", e); //$NON-NLS-1$
return null;
}
lexer.initialize(state);
lexer.setPatterns(null);
lexer.setAspTags(true);
String prev = null;
String prev2 = null;
String lastN = null;
StringBuilder contentS = null;
try
{
while (true)
{
String next_token = lexer.getNextToken();
int left = lexer.getTokenStart();
int right = left + lexer.yylength();
String value = lexer.yytext();
if (next_token == PHPRegionTypes.PHP_NS_SEPARATOR || next_token == PHPRegionTypes.PHP_USE)
{
lastN = next_token;
contentS = new StringBuilder();
}
else if (lastN != null)
{
if (next_token == PHPRegionTypes.PHP_STRING || next_token == PHPRegionTypes.PHP_NS_SEPARATOR)
{
contentS.append((value == null) ? '\\' : value);
}
else
{
lastN = null;
}
}
// if (lastN != null)
// {
// if (left <= offset && right >= offset)
// {
// return getNamespaceCompletionProposals(content, contentS.toString(), offset,
// contentS.length(), 1,
// viewer);
// }
// }
if (left < offset && right > offset)
{
if (next_token == PHPRegionTypes.PHP_CONSTANT_ENCAPSED_STRING)
{
if (prev2 != null || prev != null)
{
if (checkInclude(prev2) || checkInclude(prev))
{
// String substring = value.substring(1, offset - left);
// FIXME: Shalom - Implement getFilePathCompletionProposals
// return getFilePathCompletionProposals(substring, left + 1, substring.length(), 1,
// viewer);
return EMPTY_PROPOSAL;
}
}
return EMPTY_PROPOSAL;
}
// System.out.println(next_token);
}
if (next_token == null
|| (PHPRegionTypes.PHP_CLOSETAG.equals(next_token) && PHPRegionTypes.PHP_CLOSETAG.equals(prev)))
{
break;
}
prev2 = prev;
prev = next_token;
}
}
catch (IOException e)
{
IdeLog.logError(PHPEditorPlugin.getDefault(), "Error computing PHP completion proposals", e); //$NON-NLS-1$
}
int startOffset = (offset < content.length()) ? offset : offset - 1;
for (int a = startOffset; a >= 0; a--)
{
char c = content.charAt(a);
if (c < ' ')
{
break;
}
if (c == '/')
{
if (a > 0)
{
if (content.charAt(a - 1) == '/')
{
return EMPTY_PROPOSAL;
}
}
}
}
// if (activationChar == '@' && autoActivated)
// {
// return new ICompletionProposal[0];
// }
boolean forceActivation = false;
// The only reason why we test for a null viewer here is to allow testing without any ITextViewer attachment.
Boolean fa = (viewer != null && viewer.getTextWidget() != null) ? (Boolean) viewer.getTextWidget().getData(
"ASSIST_FORCE_ACTIVATION") : null; //$NON-NLS-1$
if (fa != null)
{
forceActivation = fa;
}
int replaceLengthIncrease = countReplaceLengthIncrease(content, offset);
ICompletionProposal[] computeCompletionProposalInternal = computeCompletionProposalInternal(partition, offset,
content, true, forceActivation);
if (computeCompletionProposalInternal.length > 0)
{
if (replaceLengthIncrease > 0)
{
computeCompletionProposalInternal = batchIncreaseReplaceLength(computeCompletionProposalInternal,
replaceLengthIncrease);
}
}
// resetting the force activation flag.
if (viewer != null && viewer.getTextWidget() != null)
{
viewer.getTextWidget().setData("ASSIST_FORCE_ACTIVATION", false);//$NON-NLS-1$
}
return computeCompletionProposalInternal;
}
/**
* Computes proposals.
*
* @param partition
* @param offset
* - offset.
* @param content
* - content.
* @param proposeBuiltins
* - whether to propose built-ins.
* @param forceActivation
* - whether force activation occured.
* @return proposals
*/
public ICompletionProposal[] computeCompletionProposalInternal(ITypedRegion partition, final int offset,
String content, boolean proposeBuiltins, boolean forceActivation)
{
final int start = (offset == 0) ? 0 : offset - 1;
this.offset = offset;
this.content = content;
List<String> callPath = ParsingUtils.parseCallPath(partition, content, start, OPS, false, document);
if (callPath == null || callPath.isEmpty())
{
return EMPTY_PROPOSAL;
}
if (callPath.size() > 1)
{
if (hasStaticDereferenceOperatorAfterTheFirst(callPath))
{
return EMPTY_PROPOSAL;
}
// Foo::hello()->goodbye()...
if (content.charAt(start) == '(')
{
return EMPTY_PROPOSAL;
}
if (DEREFERENCE_OP.equals(callPath.get(1)))
{
return dereferencingCompletion(getIndex(content, start), callPath, start, getModule());
}
else if (callPath.size() > 3 && DEREFERENCE_OP.equals(callPath.get(3)))
{
// We have a case like A::$B-> so we treat $B as a simple identifier
return dereferencingCompletion(getIndex(content, start), callPath, start, getModule());
}
else
{
return dereferencingStaticCompletion(getIndex(content, start), callPath, start, getModule());
}
}
else if (callPath.size() == 1)
{
// if content assistant is not auto-activated and we should not
// auto-activate on
// identifiers, skipping the proposals computation.
if (forceActivation
&& !preferenceStore.getBoolean(IContentAssistPreferencesConstants.AUTO_ACTIVATE_ON_IDENTIFIERS))
{
return EMPTY_PROPOSAL;
}
String identifier = callPath.get(0);
// TODO refactor new instance completion to use the new completion
// context system
// if (isNewInstanceCompletion(start, identifier, content))
// {
// return newInstanceCompletion(start, content, identifier,
// getModule());
// }
// Check if we are dealing with a namespace completion
if (identifier.startsWith(GLOBAL_NAMESPACE))
{
return computeNamespaceCompletion(start, content, identifier, globalImports, getModule(),
proposeBuiltins, true);
}
return simpleIdentifierCompletion(start, content, identifier, globalImports, getModule(), proposeBuiltins,
true, false);
}
return EMPTY_PROPOSAL;
}
private ICompletionProposal[] computeNamespaceCompletion(int offset, String content, String identifier,
Set<String> globalImports, IModule module, boolean proposeBuiltins, boolean filter)
{
// We need to trim out our namespace separator at the beginning of the identifier.
identifier = identifier.substring(1);
return simpleIdentifierCompletion(offset, content, identifier, globalImports, module, proposeBuiltins, filter,
true);
}
// /**
// * Returns a namespace-only completion proposals.
// * @param index
// *
// * @param content
// * @param identifier
// * @param offset
// * @param collectItemsFromScope
// * @param proposals
// * @param length
// * @return
// * @return
// */
// private Set<ICompletionProposal> computeNamespaceCompletionProposals(IElementsIndex index, String content, String
// identifier, int offset, boolean collectItemsFromScope)
// {
// Set<ICompletionProposal> nsProposals = new TreeSet<ICompletionProposal>();
// List<IElementEntry> entries = index.getEntriesStartingWith(IPHPIndexConstants.NAMESPACE_CATEGORY,
// StringUtil.EMPTY);
// HashSet<String> paths = new HashSet<String>();
// for (IElementEntry e : entries)
// {
// String entryPath = e.getEntryPath();
// if (!entryPath.startsWith(identifier))
// {
// continue;
// }
// if (!paths.contains(entryPath))
// {
// paths.add(entryPath);
// PHPCompletionProposal proposal = createProposal(e, offset, identifier, entryPath, e.getModule(), false,
// index, false);
// if (proposal != null)
// {
// nsProposals.add(proposal);
// }
// if (collectItemsFromScope)
// {
// // We should continue to collect any relevant proposal from this namespace scope
// ICompletionProposal[] identifiersCompletion = simpleIdentifierCompletion(offset, content, identifier, false,
// null, module, false, true, true);
// for (ICompletionProposal p : identifiersCompletion)
// {
// nsProposals.add(p);
// }
// }
// }
// }
// return nsProposals;
// }
/**
* Performs dereferencing completion.
*
* @param index
* - index to use.
* @param callPath
* - call path
* @param module
* - current module.
* @return completion proposals
*/
private ICompletionProposal[] dereferencingCompletion(IElementsIndex index, List<String> callPath, int offset,
IModule module)
{
Set<IElementEntry> result = computeDereferenceEntries(index, callPath, (offset == 0) ? 0 : offset - 1, module,
false, aliases, namespace);
if (result == null || result.isEmpty())
{
return new ICompletionProposal[] {};
}
List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
Set<String> usedName = new LinkedHashSet<String>();
for (IElementEntry currentEntry : result)
{
String lastName = ElementsIndexingUtils.getLastNameInPath(currentEntry.getEntryPath());
if (!usedName.contains(lastName))
{
ICompletionProposal proposal = createProposal(currentEntry, offset, callPath.get(callPath.size() - 1),
lastName, module, false, index, false);
if (proposal != null)
{
proposals.add(proposal);
}
usedName.add(lastName);
}
}
ICompletionProposal[] toReturn = new ICompletionProposal[proposals.size()];
return proposals.toArray(toReturn);
}
/**
* Computes dereference entries.
*
* @param index
* - index to use.
* @param callPath
* - call path.
* @param offset
* - offset.
* @param module
* - module.
* @param exactMatch
* - whether to perform the exact match of the last entry.
* @param namespace
* - current namespace
* @return dereference entries or null.
*/
@SuppressWarnings("unchecked")
public static Set<IElementEntry> computeDereferenceEntries(IElementsIndex index, List<String> callPath, int offset,
IModule module, boolean exactMatch, Map<String, String> aliases, String namespace)
{
String entryName = callPath.get(0);
Set<IElementEntry> leftDereferenceEntries = computeDereferenceLeftEntries(index, pathEntryName(entryName),
offset, module, aliases, namespace);
if (leftDereferenceEntries == null)
{
return null;
}
if (leftDereferenceEntries.isEmpty() && ParsingUtils.isFunctionCall(callPath.get(0)))
{
// In this case, we have a function call right at the beginning on the call-path. So we compute
// a simple identifier for that call and add it to our list, so the code assist will suggest completion
// for the return type of that call.
leftDereferenceEntries.addAll(computeSimpleIdentifierEntries(true, Collections.EMPTY_SET,
pathEntryName(entryName), false, index, false, module, false, StringUtil.EMPTY,
Collections.EMPTY_MAP));
}
boolean innerCompletion = false;
if (THIS_ACTIVATION_SEQUENCE.equals(callPath.get(0)))
{
innerCompletion = true;
}
Set<IElementEntry> result = null;
for (int i = 2; i < callPath.size(); i += 2)
{
boolean currentExactMatchFlag = true;
boolean applyAccessRestriction = false;
if (i == callPath.size() - 1)
{
currentExactMatchFlag = exactMatch;
applyAccessRestriction = true;
}
String callPathEntry = callPath.get(i);
result = computeDereferenceRightEntries(leftDereferenceEntries, index, pathEntryName(callPathEntry),
offset, module, currentExactMatchFlag, applyAccessRestriction, innerCompletion, aliases, namespace);
if (result == null || result.isEmpty())
{
return null;
}
leftDereferenceEntries = result;
}
return result;
}
/**
* Performs static dereferencing completion.
*
* @param index
* - index to use.
* @param callPath
* - call path.
* @param module
* - current module.
* @return completion proposals
*/
private ICompletionProposal[] dereferencingStaticCompletion(IElementsIndex index, List<String> callPath,
int offset, IModule module)
{
Set<IElementEntry> result = computeStaticDereferenceEntries(index, callPath, (offset == 0) ? 0 : offset - 1,
module, false, aliases, namespace);
if (result == null || result.isEmpty())
{
return new ICompletionProposal[] {};
}
List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
Set<String> usedName = new LinkedHashSet<String>();
for (IElementEntry currentEntry : result)
{
String lastName = ElementsIndexingUtils.getLastNameInPath(currentEntry.getEntryPath());
if (isVariableEntry(currentEntry) && !isConstVariable(currentEntry))
{
lastName = "$" + lastName; //$NON-NLS-1$ // $codepro.audit.disable stringConcatenationInLoop
}
if (!usedName.contains(lastName))
{
ICompletionProposal proposal = createProposal(currentEntry, offset, callPath.get(callPath.size() - 1),
lastName, module, !isConstVariable(currentEntry), index, false);
if (proposal != null)
{
proposals.add(proposal);
usedName.add(lastName);
}
}
}
ICompletionProposal[] toReturn = new ICompletionProposal[proposals.size()];
return proposals.toArray(toReturn);
}
/**
* Computes static dereference entries.
*
* @param index
* - index to use.
* @param callPath
* - call path.
* @param offset
* - offset.
* @param module
* - module.
* @param exactMatch
* - whether to perform an exact match for the last entry.
* @param namespace
* @return set of entries or null.
*/
public static Set<IElementEntry> computeStaticDereferenceEntries(IElementsIndex index, List<String> callPath,
int offset, IModule module, boolean exactMatch, Map<String, String> aliases, String namespace)
{
Set<IElementEntry> leftDereferenceEntries = computeStaticDereferenceLeftEntries(index,
pathEntryName(callPath.get(0)), offset, module, aliases, namespace);
if (leftDereferenceEntries == null || leftDereferenceEntries.isEmpty())
{
return null;
}
boolean selfCompletion = SELF_ACTIVATION_SEQUENCE.equals(callPath.get(0));
boolean staticCompletion = STATIC_ACTIVATION_SEQUENCE.equals(callPath.get(0));
boolean parentCompletion = PARENT_ACTIVATION_SEQUENCE.equals(callPath.get(0));
boolean currentExactMatch = true;
if (callPath.size() == 3)
{
currentExactMatch = exactMatch;
}
if (leftDereferenceEntries.size() == 1)
{
if (parentCompletion)
{
// Make sure we grab the right namespace scope from the entry itself
Object entryValue = leftDereferenceEntries.iterator().next().getValue();
if (entryValue instanceof ClassPHPEntryValue)
{
namespace = ((ClassPHPEntryValue) entryValue).getNameSpace();
}
}
else
{
// In case the left side is a trait, we would like to treat it as a 'parent' completion.
// For example: B::smallTalk insteadof A;
parentCompletion = leftDereferenceEntries.iterator().next().getValue() instanceof TraitPHPEntryValue;
}
}
Set<IElementEntry> result = computeStaticDereferenceRightEntries(index, leftDereferenceEntries,
pathEntryName(callPath.get(2)), offset, module, currentExactMatch, true, selfCompletion
|| staticCompletion, parentCompletion, aliases, namespace);
if (result == null || result.isEmpty())
{
return null;
}
// if call path has more then one reference, we need to count other
// references
// through the usual dereferencing algorithm
if (callPath.size() > 3)
{
leftDereferenceEntries = result;
for (int i = 4; i < callPath.size(); i += 2)
{
currentExactMatch = true;
boolean applyAccessRestriction = false;
if (i == callPath.size() - 1)
{
currentExactMatch = exactMatch;
applyAccessRestriction = true;
}
result = computeDereferenceRightEntries(leftDereferenceEntries, index, pathEntryName(callPath.get(i)),
offset, module, currentExactMatch, applyAccessRestriction, selfCompletion, aliases, namespace);
if (result == null || result.isEmpty())
{
return null;
}
leftDereferenceEntries = result;
}
}
return result;
}
/**
* Computes right entries for the static dereferencing completion.
*
* @param index
* - index to use.
* @param leftEntries
* - left dereferencing entries.
* @param right
* - right side of the dereferencing.
* @param offset
* - offset.
* @param module
* - module.
* @param exactMatch
* - whether to check for exact match of the right side.
* @param applyAccessModifiers
* - whether to filter entries by access modifiers.
* @param innerEntries
* - whether inner entries are being computed. Like $this->... or self::...
* @param parentEntries
* @return set of entries or null.
*/
private static Set<IElementEntry> computeStaticDereferenceRightEntries(IElementsIndex index,
Set<IElementEntry> leftEntries, String right, int offset, IModule module, boolean exactMatch,
boolean applyAccessModifiers, boolean innerEntries, boolean parentEntries, Map<String, String> aliases,
String namespace)
{
if (leftEntries == null || leftEntries.isEmpty())
{
return null;
}
Set<Object> leftTypes = getEntriesTypes(leftEntries);
if (leftTypes == null || leftTypes.isEmpty())
{
return null;
}
// here we have all the types, left part might be of
// now resolving it to the simple custom types
Set<String> resolvedLeftTypes = PHPTypeProcessor.processTypes(leftTypes, index);
if (resolvedLeftTypes == null || resolvedLeftTypes.isEmpty())
{
return null;
}
Set<String> customLeftTypes = PHPTypeProcessor.getCustomTypes(resolvedLeftTypes);
if (customLeftTypes == null || customLeftTypes.isEmpty())
{
return null;
}
IEntryFilter filter = null;
if (applyAccessModifiers)
{
if (innerEntries)
{
filter = new AccessModifierEntryFilter(customLeftTypes, false);
}
else if (parentEntries)
{
filter = new AccessModifierEntryFilter(customLeftTypes, true);
}
else
{
filter = new PublicsOnlyEntryFilter();
}
}
Set<String> typesWithAncestors = TypeHierarchyUtils.addAllAncestors(customLeftTypes, index, namespace, aliases);
Set<IElementEntry> result = new LinkedHashSet<IElementEntry>();
// now searching for the possible right parts
if (right.length() == 0)
{
Set<IElementEntry> varResults = ContentAssistCollectors.collectVariableEntries(index, DOLLAR_SIGN,
typesWithAncestors, exactMatch, aliases, namespace);
if (varResults != null)
{
result.addAll(varResults);
}
Set<IElementEntry> funcResults = ContentAssistCollectors.collectFunctionEntries(index, right,
typesWithAncestors, exactMatch, aliases, namespace);
if (funcResults != null)
{
result.addAll(funcResults);
}
Set<IElementEntry> constResults = ContentAssistCollectors.collectConstEntries(index, right,
typesWithAncestors, exactMatch, aliases, namespace);
if (constResults != null)
{
result.addAll(constResults);
}
}
else if (right.startsWith(DOLLAR_SIGN))
{
result.addAll(ContentAssistCollectors.collectVariableEntries(index, right, typesWithAncestors, exactMatch,
aliases, namespace));
}
else
{
result.addAll(ContentAssistCollectors.collectConstEntries(index, right, typesWithAncestors, exactMatch,
aliases, namespace));
result.addAll(ContentAssistCollectors.collectFunctionEntries(index, right, typesWithAncestors, exactMatch,
aliases, namespace));
}
if (result == null || result.isEmpty())
{
return null;
}
if (!parentEntries)
{
result = ContentAssistFilters.filterStaticEntries(result);
}
if (filter != null)
{
result = filter.filter(result);
}
return ContentAssistFilters.filterByModule(result, module, index);
}
/**
* Gets the types for the number of entries.
*
* @param entries
* - entries.
* @return types
*/
private static Set<Object> getEntriesTypes(Collection<IElementEntry> entries)
{
Set<Object> result = new LinkedHashSet<Object>();
for (IElementEntry entry : entries)
{
Set<Object> entryTypes = getEntryTypes(entry);
Set<Object> splittedTypes = new HashSet<Object>(entryTypes.size() + 2);
for (Object type : entryTypes)
{
if (type instanceof String)
{
String t = type.toString();
String[] types = t.split("\\|"); //$NON-NLS-1$
for (String splittedType : types)
{
String resolvedType = splittedType.trim();
if (resolvedType.startsWith(GLOBAL_NAMESPACE))
{
resolvedType = resolvedType.substring(1);
}
splittedTypes.add(resolvedType);
}
}
else
{
splittedTypes.add(type);
}
}
result.addAll(splittedTypes);
}
return result;
}
/**
* Gets entry types.
*
* @param entry
* - entry, which types to get.
* @return set of entry types.
*/
private static Set<Object> getEntryTypes(IElementEntry entry)
{
if (isVariableEntry(entry))
{
return ((VariablePHPEntryValue) entry.getValue()).getTypes();
}
else if (isFunctionEntry(entry))
{
return ((FunctionPHPEntryValue) entry.getValue()).getReturnTypes();
}
else if (isClassEntry(entry))
{
Set<Object> result = new HashSet<Object>(1);
result.add(ElementsIndexingUtils.getFirstNameInPath(entry.getEntryPath()));
return result;
}
return Collections.emptySet();
}
/**
* Computes left static dereference entries.
*
* @param index
* - index to use.
* @param left
* - left name.
* @param offset
* - offset.
* @param module
* - current module.
* @param namespace
* @return set of entries or null.
*/
private static Set<IElementEntry> computeStaticDereferenceLeftEntries(IElementsIndex index, String left,
int offset, IModule module, Map<String, String> aliases, String namespace)
{
if (left.startsWith("$")) //$NON-NLS-1$
{
// static dereferencing is allowed on PHP 5.3 variables
IProject project = getProject(module);
if (project != null && !PHPVersionProvider.isPHP53(project))
{
return null;
}
else
{
return computeDereferenceLeftEntries(index, left, offset, module, aliases, namespace);
}
}
if (SELF_ACTIVATION_SEQUENCE.equals(left) || STATIC_ACTIVATION_SEQUENCE.equals(left))
{
IElementEntry currentClass = getCurrentClass(index, module, offset);
if (currentClass == null)
{
return null;
}
Set<IElementEntry> result = new LinkedHashSet<IElementEntry>();
result.add(currentClass);
return result;
}
else if (PARENT_ACTIVATION_SEQUENCE.equals(left))
{
IElementEntry currentClass = getCurrentClass(index, module, offset);
if (currentClass == null)
{
return null;
}
Set<IElementEntry> result = new LinkedHashSet<IElementEntry>();
Object clazz = currentClass.getValue();
if (clazz instanceof ClassPHPEntryValue)
{
ClassPHPEntryValue classPHPEntryValue = (ClassPHPEntryValue) clazz;
String superClassname = classPHPEntryValue.getSuperClassname();
if (superClassname != null)
{
String resolvedSuperclassNamespace = resolveNamespace(superClassname);
if (StringUtil.isEmpty(resolvedSuperclassNamespace))
{
resolvedSuperclassNamespace = namespace;
}
Set<IElementEntry> superClassEntry = getClassEntries(index, superClassname, module, aliases,
resolvedSuperclassNamespace, true);
if (superClassEntry != null && superClassEntry.size() == 1)
{
result.addAll(superClassEntry);
}
}
}
return result;
}
else
{
return getClassEntries(index, left, module, aliases, namespace, true);
}
}
private static String resolveNamespace(String path)
{
if (StringUtil.isEmpty(path))
{
return path;
}
try
{
IPath p = Path.fromOSString(path.replaceAll("\\\\", "/")); //$NON-NLS-1$ //$NON-NLS-2$
if (p.segmentCount() > 1)
{
String resolvedNamespace = p.removeLastSegments(1).toString();
if (resolvedNamespace.startsWith(GLOBAL_NAMESPACE) || resolvedNamespace.startsWith("/")) //$NON-NLS-1$
{
resolvedNamespace = resolvedNamespace.substring(1);
}
return resolvedNamespace.replace("/", "\\"); //$NON-NLS-1$ //$NON-NLS-2$
}
else
{
return StringUtil.EMPTY;
}
}
catch (Exception e)
{
// ignore
}
return StringUtil.EMPTY;
}
/**
* Resolve the {@link IProject} from the {@link IModule}
*
* @param module
* @return
*/
private static IProject getProject(IModule module)
{
if (module == null || !(module instanceof LocalModule))
{
return null;
}
LocalModule lm = (LocalModule) module;
IFile file = lm.getFile();
if (file != null)
{
return file.getProject();
}
return null;
}
/**
* Gets the class, offset is in.
*
* @param index
* - index to use.
* @param module
* - module.
* @param offset
* - offset.
* @return class entry or null.
*/
private static IElementEntry getCurrentClass(IElementsIndex index, IModule module, int offset)
{
List<IElementEntry> entries = index.getModuleEntries(module);
for (IElementEntry entry : entries)
{
if (entry.getCategory() == IPHPIndexConstants.CLASS_CATEGORY
&& entry.getValue() instanceof ClassPHPEntryValue)
{
ClassPHPEntryValue val = (ClassPHPEntryValue) entry.getValue();
if (val.getStartOffset() < offset && val.getEndOffset() > offset)
{
return entry;
}
}
}
return null;
}
/**
* Gets class entries.
*
* @param index
* - index to use.
* @param clazz
* - class name.
* @param namespace
* @param isStaticDereferencing
* @return variable entries.
*/
private static Set<IElementEntry> getClassEntries(IElementsIndex index, String clazz, IModule module,
Map<String, String> aliases, String namespace, boolean isStaticDereferencing)
{
if (clazz != null && clazz.startsWith(GLOBAL_NAMESPACE))
{
clazz = clazz.substring(1);
}
List<IElementEntry> namespaceEntries = getNamespaceEntries(clazz, module, aliases);
List<IElementEntry> leftEntries = index.getEntries(IPHPIndexConstants.CLASS_CATEGORY, clazz);
if (CollectionsUtil.isEmpty(leftEntries))
{
clazz = getNameByAlias(clazz, index, namespace, aliases, namespaceEntries);
leftEntries = index.getEntries(IPHPIndexConstants.CLASS_CATEGORY, clazz);
}
if (leftEntries == null)
{
leftEntries = new ArrayList<IElementEntry>();
}
if (leftEntries.isEmpty())
{
Set<String> classMap = new HashSet<String>(1);
classMap.add(clazz);
Set<IElementEntry> entries = ContentAssistCollectors.collectBuiltinTypeEntries(classMap, true);
leftEntries.addAll(entries);
}
Set<IElementEntry> result = new LinkedHashSet<IElementEntry>();
for (IElementEntry entry : leftEntries)
{
if (isStaticDereferencing || module == null || module.equals(entry.getModule()))
{
ClassPHPEntryValue value = (ClassPHPEntryValue) entry.getValue();
if (!ContentAssistUtils.isFilterByNamespace() || TypeHierarchyUtils.isInNamespace(value, namespace))
{
result.add(entry);
}
}
}
return result;
}
/**
* Gets path entry name.
*
* @param entry
* - entry.
* @return path entry name.
*/
private static String pathEntryName(String entry)
{
if (ParsingUtils.isFunctionCall(entry))
{
return ParsingUtils.getFunctionNameFromCall(entry);
}
else
{
return entry;
}
}
/**
* Computes entries for the dereferencing completion.
*
* @param index
* - index to use.
* @param leftEntries
* - left dereferencing entries.
* @param right
* - right side of the dereferencing.
* @param offset
* - offset.
* @param module
* - module.
* @param exactMatch
* - whether to check for exact match of the right side.
* @param applyAccessModifiers
* - whether to filter entries by access modifiers.
* @param innerEntries
* - whether inner entries are being computed. Like $this->... or self::...
* @param aliases
* @param namespace
* @return set of entries or null.
*/
private static Set<IElementEntry> computeDereferenceRightEntries(Set<IElementEntry> leftEntries,
IElementsIndex index, String right, int offset, IModule module, boolean exactMatch,
boolean applyAccessModifiers, boolean innerEntries, Map<String, String> aliases, String namespace)
{
if (leftEntries == null || leftEntries.isEmpty())
{
return null;
}
Set<Object> leftTypes = getEntriesTypes(leftEntries);
if (leftTypes == null || leftTypes.isEmpty())
{
return null;
}
// here we have all the types, left part might be of
// now resolving it to the simple custom types
Set<String> resolvedLeftTypes = PHPTypeProcessor.processTypes(leftTypes, index);
if (resolvedLeftTypes == null || resolvedLeftTypes.isEmpty())
{
return null;
}
Set<String> customLeftTypes = PHPTypeProcessor.getCustomTypes(resolvedLeftTypes);
if (customLeftTypes == null || customLeftTypes.isEmpty())
{
return null;
}
IEntryFilter filter = null;
if (applyAccessModifiers)
{
if (innerEntries)
{
filter = new AccessModifierEntryFilter(customLeftTypes, false);
}
else
{
filter = new PublicsOnlyEntryFilter();
}
}
// FIXME - Shalom (???) - Have the ancestors look at the current module and remove any other ancestor from a
// different module and a similar name
// Determine the type and its ancestors. Use the namespace and the aliases (the 'use' statements within this
// namespace) when detecting the ancestors.
Set<String> typesWithAncestors = TypeHierarchyUtils.addAllAncestors(customLeftTypes, index, namespace, aliases);
Set<IElementEntry> result = new LinkedHashSet<IElementEntry>();
// now searching for the possible right parts
boolean filterNonStatic = true;
if (right.length() == 0)
{
Set<IElementEntry> varResults = ContentAssistCollectors.collectVariableEntries(index, DOLLAR_SIGN,
typesWithAncestors, exactMatch, aliases, namespace);
if (varResults != null)
{
result.addAll(varResults);
}
Set<IElementEntry> funcResults = ContentAssistCollectors.collectFunctionEntries(index, right,
typesWithAncestors, exactMatch, aliases, namespace);
if (funcResults != null)
{
result.addAll(funcResults);
}
}
else
{
String var;
if (right.startsWith(DOLLAR_SIGN))
{
var = right;
filterNonStatic = false;
}
else
{
var = DOLLAR_SIGN + right;
}
result.addAll(ContentAssistCollectors.collectVariableEntries(index, var, typesWithAncestors, exactMatch,
aliases, namespace));
result.addAll(ContentAssistCollectors.collectFunctionEntries(index, right, typesWithAncestors, exactMatch,
aliases, namespace));
}
if (result == null || result.isEmpty())
{
return null;
}
if (filterNonStatic)
{
result = ContentAssistFilters.filterNonStaticVariables(result);
}
if (filter != null)
{
result = filter.filter(result);
}
return ContentAssistFilters.filterByModule(result, module, index);
}
/**
* Computes left dereference entries.
*
* @param index
* - index to use.
* @param left
* - left name.
* @param offset
* - offset.
* @param module
* - current module.
* @param namespace
* - current namespace
* @return set of entries or null.
*/
private static Set<IElementEntry> computeDereferenceLeftEntries(IElementsIndex index, String left, int offset,
IModule module, Map<String, String> aliases, String namespace)
{
if (left.startsWith("$")) //$NON-NLS-1$
{
if (THIS_ACTIVATION_SEQUENCE.equals(left))
{
IElementEntry currentClass = getCurrentClass(index, module, offset);
if (currentClass == null)
{
return null;
}
Set<IElementEntry> result = new LinkedHashSet<IElementEntry>(1);
result.add(currentClass);
return result;
}
else
{
return getVariableEntries(index, left, namespace);
}
}
else
{
if (ParsingUtils.isFunctionCall(left))
{
return getFunctionEntriesByCall(index, left);
}
else
{
return getClassEntries(index, left, module, aliases, namespace, false);
}
}
}
/**
* Gets all possible function return types.
*
* @param index
* - index to use.
* @param callString
* - function call (not including brackets and parameters ).
* @return possible types or null.
*/
private static Set<IElementEntry> getFunctionEntriesByCall(IElementsIndex index, String callString)
{
List<IElementEntry> leftEntries = index.getEntries(IPHPIndexConstants.FUNCTION_CATEGORY, callString);
if (leftEntries == null)
{
return null;
}
Set<IElementEntry> result = new LinkedHashSet<IElementEntry>(leftEntries.size());
result.addAll(leftEntries);
return result;
}
/**
* Gets variable entries.
*
* @param index
* - index to use.
* @param var
* - variable name.
* @param namespace
* - namespace
* @return variable entries.
*/
private static Set<IElementEntry> getVariableEntries(IElementsIndex index, String var, String namespace)
{
String varName = var.substring(1);
List<IElementEntry> leftEntries = index.getEntries(IPHPIndexConstants.VAR_CATEGORY, varName);
if (leftEntries == null)
{
return null;
}
Set<IElementEntry> result = new LinkedHashSet<IElementEntry>();
for (IElementEntry entry : leftEntries)
{
VariablePHPEntryValue value = (VariablePHPEntryValue) entry.getValue();
if (!ContentAssistUtils.isFilterByNamespace() || TypeHierarchyUtils.isInNamespace(value, namespace))
{
result.add(entry);
}
}
return result;
}
/**
* Performs a completion for a simple identifier.
*
* @param offset
* - offset.
* @param content
* - content.
* @param identifier
* - identifier to complete.
* @param reportedStackIsGlobal
* - whether imported stack is global.
* @param globalImports
* - global imports set.
* @param module
* - module.
* @param proposeBuiltins
* - whether to propose built-ins.
* @param ignorIndexNamespace
* @return completion proposals
*/
private ICompletionProposal[] simpleIdentifierCompletion(final int offset, String content, String identifier,
Set<String> globalImports, IModule module, boolean proposeBuiltins, boolean filter,
boolean ignorIndexNamespace)
{
String name = identifier;
if (name == null)
{
return new ICompletionProposal[] {};
}
// only allowing empty names for "extends", "implements" and traits proposals types
if (name.length() == 0)
{
if (currentContext == null)
{
return new ICompletionProposal[] {};
}
if (!TYPES_ACCEPTING_EMPTY_IDENTIFIERS.contains(currentContext.getType()))
{
return new ICompletionProposal[] {};
}
}
boolean variableCompletion = false;
if (name.startsWith(DOLLAR_SIGN))
{
name = name.substring(1);
variableCompletion = true;
}
List<Object> items = new ArrayList<Object>();
IElementsIndex index;
if (variableCompletion)
{
index = getIndex(content, offset);
}
else
{
index = getIndexOptimized(content, offset);
}
String namespaceToUse = ignorIndexNamespace ? StringUtil.EMPTY : namespace;
List<IElementEntry> entries = computeSimpleIdentifierEntries(reportedScopeIsGlobal, globalImports, name,
variableCompletion, index, false, module, filter, currentContext, namespaceToUse, aliases);
items.addAll(entries);
if (proposeBuiltins)
{
String completionStart = name;
if (completionStart.startsWith(GLOBAL_NAMESPACE))
{
completionStart = completionStart.substring(1);
}
List<Object> modelItems = variableCompletion ? ContentAssistUtils.selectModelElements(DOLLAR_SIGN
+ completionStart, false) : ContentAssistUtils.selectModelElements(completionStart, false);
if (modelItems != null)
{
for (Object modelItem : modelItems)
{
if (variableCompletion)
{
if (modelItem instanceof PHPVariableParseNode)
{
items.add(modelItem);
}
else if (modelItem instanceof IPHPParseNode && !(modelItem instanceof PHPFunctionParseNode))
{
IPHPParseNode pn = (IPHPParseNode) modelItem;
String nodeName = pn.getNodeName();
if (nodeName.startsWith(DOLLAR_SIGN))
{
if (!reportedScopeIsUnderClass && ALLOW_ONLY_UNDER_CLASS.contains(nodeName))
{
continue;
}
items.add(pn);
}
}
}
else
{
if (!(modelItem instanceof PHPVariableParseNode))
{
String nodeName = ((PHPBaseParseNode) modelItem).getNodeName();
// make sure we don't offer items that should only appear under a class/interface.
if (!reportedScopeIsUnderClass && ALLOW_ONLY_UNDER_CLASS.contains(nodeName))
{
continue;
}
items.add(modelItem);
}
}
}
}
}
List<ICompletionProposal> result = createProposals(offset, name, items, module, true, index, false);
ICompletionProposal[] proposals = new ICompletionProposal[result.size()];
return result.toArray(proposals);
}
/**
* Gets elements index for a module taking into account perfomance preferences.
*
* @param content
* - module content.
* @return elements index
*/
private IElementsIndex getIndexOptimized(String content, int offset)
{
if (isOutOfWorkspace
|| preferenceStore
.getBoolean(IContentAssistPreferencesConstants.PARSE_UNSAVED_MODULE_ON_IDENTIFIERS_COMPLETION))
{
return getIndex(content, offset);
}
return PHPGlobalIndexer.getInstance().getIndex();
}
/**
* Computes entries for simple identifier completion.
*
* @param reportedStackIsGlobal
* - whether reported stack is global.
* @param globalImports
* - global imports set.
* @param name
* - name to complete.
* @param variableCompletion
* - whether completing variable or not.
* @param index
* - index to use.
* @param exactMatch
* - whether to check for exact match of the names.
* @param module
* - module.
* @return list of entries or null.
*/
public static List<IElementEntry> computeSimpleIdentifierEntries(boolean reportedStackIsGlobal,
Set<String> globalImports, String name, boolean variableCompletion, IElementsIndex index,
boolean exactMatch, IModule module, boolean filter, String namespace, Map<String, String> aliases)
{
return computeSimpleIdentifierEntries(reportedStackIsGlobal, globalImports, name, variableCompletion, index,
exactMatch, module, filter, null, namespace, aliases);
}
/**
* Computes entries for simple identifier completion.
*
* @param reportedStackIsGlobal
* - whether reported stack is global.
* @param globalImports
* - global imports set.
* @param name
* - name to complete.
* @param variableCompletion
* - whether completing variable or not.
* @param index
* - index to use.
* @param exactMatch
* - whether to check for exact match of the names.
* @param module
* - module.
* @param proposalContext
* - proposal context.
* @return list of entries or null.
*/
@SuppressWarnings("unused")
private static List<IElementEntry> computeSimpleIdentifierEntries(boolean reportedStackIsGlobal,
Set<String> globalImports, String name, boolean variableCompletion, IElementsIndex index,
boolean exactMatch, IModule module, boolean filter, ProposalContext proposalContext, String namespace,
Map<String, String> aliases)
{
String simpleName = name;
List<IElementEntry> namespaceEntries = getNamespaceEntries(name, module, aliases);
// [http://php.net/manual/en/language.namespaces.faq.php]
// "Names that contain a backslash but do not begin with a backslash like my\name can be resolved in 2 different
// ways. If there is an import statement that aliases another name to my, then the import alias is applied to
// the my in my\name.Otherwise, the current namespace name is prepended to my\name."
if (proposalContext != null && NAMESPACE_PROPOSAL_CONTEXT_TYPE.equals(proposalContext.getType()))
{
// Do nothing
// We keep the name as it is, since we are in a 'use' statement.
}
else if (!name.startsWith(GLOBAL_NAMESPACE))
{
name = getNameByAlias(name, index, namespace, aliases, namespaceEntries);
}
else
{
if (!variableCompletion)
{
name = name.substring(1);
List<IElementEntry> entriesStartingWith = index.getEntriesStartingWith(
IPHPIndexConstants.NAMESPACE_CATEGORY, StringUtil.EMPTY);
for (IElementEntry e : entriesStartingWith)
{
if (e.getEntryPath().startsWith(name))
{
namespaceEntries.add(e);
}
}
}
}
List<IElementEntry> entries;
if (variableCompletion
&& (proposalContext == null || proposalContext.acceptModelElementType(IPHPIndexConstants.VAR_CATEGORY)))
{
if (exactMatch)
{
entries = index.getEntries(IPHPIndexConstants.VAR_CATEGORY, name);
}
else
{
entries = index.getEntriesStartingWith(IPHPIndexConstants.VAR_CATEGORY, name);
}
}
else
{
// searching for methods
if (exactMatch)
{
entries = new ArrayList<IElementEntry>();
if (proposalContext == null || proposalContext.acceptModelElementType(IPHPIndexConstants.VAR_CATEGORY))
{
entries.addAll(index.getEntries(IPHPIndexConstants.CLASS_CATEGORY, name));
}
if (proposalContext == null
|| proposalContext.acceptModelElementType(IPHPIndexConstants.FUNCTION_CATEGORY))
{
entries.addAll(index.getEntries(IPHPIndexConstants.FUNCTION_CATEGORY, name));
}
}
else
{
entries = new ArrayList<IElementEntry>();
if (proposalContext == null
|| proposalContext.acceptModelElementType(IPHPIndexConstants.CLASS_CATEGORY))
{
List<IElementEntry> classTypeEntries = index.getEntriesStartingWith(
IPHPIndexConstants.CLASS_CATEGORY, name);
// Check for traits vs. classes/interfaces here. In case we are in a trait 'use', or trait 'use'
// block, we would like to see only trait types.
if (proposalContext != null
&& (PHPContextCalculator.TRAIT_USE_PROPOSAL_CONTEXT_TYPE.equals(proposalContext.getType()) || PHPContextCalculator.TRAIT_USE_BLOCK_PROPOSAL_CONTEXT_TYPE
.equals(proposalContext.getType())))
{
IContextFilter contextFilter = proposalContext.getContextFilter();
for (IElementEntry elementEntry : classTypeEntries)
{
if (contextFilter.acceptElementEntry(elementEntry))
{
entries.add(elementEntry);
}
}
}
else
{
// TODO - Check if we need to filter out the traits out of the type entries.
entries.addAll(classTypeEntries);
}
}
if (proposalContext == null
|| proposalContext.acceptModelElementType(IPHPIndexConstants.FUNCTION_CATEGORY))
{
//解决有namespace时不提示全局函数的问题
if(!name.equals(simpleName)){
entries.addAll(index.getEntriesStartingWith(IPHPIndexConstants.FUNCTION_CATEGORY, simpleName));
}
entries.addAll(index.getEntriesStartingWith(IPHPIndexConstants.FUNCTION_CATEGORY, name));
}
if (proposalContext == null
|| proposalContext.acceptModelElementType(IPHPIndexConstants.CONST_CATEGORY))
{
entries.addAll(index.getEntriesStartingWith(IPHPIndexConstants.CONST_CATEGORY, name));
}
if (proposalContext == null
|| proposalContext.acceptModelElementType(IPHPIndexConstants.NAMESPACE_CATEGORY))
{
// In case the name start with the current namespace, remove that prefix.
String ns = name;
if (ns.startsWith(namespace + GLOBAL_NAMESPACE))
{
ns = ns.substring(namespace.length() + 1);
}
// tokenize the namespace segments. Collect the namespaces from the longest namespace possible until
// we don't get any more assist.
Set<IElementEntry> collectedNs = new LinkedHashSet<IElementEntry>();
collectedNs.addAll(index.getEntriesStartingWith(IPHPIndexConstants.NAMESPACE_CATEGORY, ns));
int separatorIndex = ns.lastIndexOf(GLOBAL_NAMESPACE);
while (separatorIndex > 0)
{
String nsLookup = ns.substring(0, separatorIndex);
collectedNs.addAll(index
.getEntriesStartingWith(IPHPIndexConstants.NAMESPACE_CATEGORY, nsLookup));
separatorIndex = nsLookup.lastIndexOf(GLOBAL_NAMESPACE);
}
entries.addAll(collectedNs);
if (proposalContext != null && NAMESPACE_PROPOSAL_CONTEXT_TYPE.equals(proposalContext.getType()))
{
// When we are dealing with a 'use' statement, collect and display the Classes that exists under
// the 'used' namespace.
entries.addAll(index.getEntriesStartingWith(IPHPIndexConstants.CLASS_CATEGORY, name));
}
}
}
// searching for defines
Set<IElementEntry> defineEntries = computeDefines(name, index, proposalContext, exactMatch);
if (defineEntries != null)
{
if (entries != null)
{
entries.addAll(defineEntries);
}
else
{
entries = new ArrayList<IElementEntry>(defineEntries);
}
}
}
entries.addAll(namespaceEntries);
if (filter)
{
entries = ContentAssistFilters.filterFieldsAndMembers(entries);
}
if (!reportedStackIsGlobal)
{
entries = ContentAssistFilters.filterGlobalVariables(entries, globalImports);
}
Set<IElementEntry> filterResult = ContentAssistFilters.filterByModule(entries, module, index);
List<IElementEntry> result = new ArrayList<IElementEntry>();
result.addAll(filterResult);
return result;
}
/**
* Returns the 'DEFINE' entries with the given name.
*
* @param name
* The define's name.
* @param index
* @param proposalContext
* @param exactMatch
* @return A {@link List} of define element entries, or <code>null</code>.
*/
public static Set<IElementEntry> computeDefines(String name, IElementsIndex index, ProposalContext proposalContext,
boolean exactMatch)
{
List<IElementEntry> varEntries = null;
if (proposalContext == null || proposalContext.acceptModelElementType(IPHPIndexConstants.CONST_CATEGORY))
{
if (exactMatch)
{
varEntries = index.getEntries(IPHPIndexConstants.CONST_CATEGORY, name);
}
else
{
varEntries = index.getEntriesStartingWith(IPHPIndexConstants.CONST_CATEGORY, name);
}
}
if (varEntries != null)
{
Set<IElementEntry> staticVariableEntries = new LinkedHashSet<IElementEntry>(5);
for (IElementEntry entry : varEntries)
{
if (entry.getValue() instanceof AbstractPHPEntryValue)
{
if (PHPFlags.isNamedConstant(((AbstractPHPEntryValue) entry.getValue()).getModifiers()))
{
staticVariableEntries.add(entry);
}
}
}
return staticVariableEntries;
}
return null;
}
/**
* @param name
* @param module
* @param aliases
* @return
*/
private static List<IElementEntry> getNamespaceEntries(String name, IModule module, Map<String, String> aliases)
{
List<IElementEntry> namespaceEntries = new ArrayList<IElementEntry>();
if (aliases != null)
{
for (String s : aliases.keySet())
{
if (s.toLowerCase().startsWith(name))
{
namespaceEntries.add(new UnpackedEntry(-1, s, new NamespacePHPEntryValue(0, s), module));
}
}
}
return namespaceEntries;
}
/**
* Returns the name of the identifier we complete with regards to the aliases we have in the script.<br>
* This follows the rules defined at http://php.net/manual/en/language.namespaces.faq.php: "Names that contain a
* backslash but do not begin with a backslash like my\name can be resolved in 2 different ways. If there is an
* import statement that aliases another name to my, then the import alias is applied to the my in
* my\name.Otherwise, the current namespace name is prepended to my\name."
*
* @param name
* @param index
* @param namespace
* @param aliases
* @param namespaceEntries
* @return The identifier name with regards to the namespace aliases
*/
private static String getNameByAlias(String name, IElementsIndex index, String namespace,
Map<String, String> aliases, List<IElementEntry> namespaceEntries)
{
boolean foundAlias = false;
String lowerCaseName = (name != null) ? name.toLowerCase() : StringUtil.EMPTY;
if (!CollectionsUtil.isEmpty(aliases))
{
for (String s : aliases.keySet())
{
if (lowerCaseName.startsWith(s.toLowerCase()))
{
name = aliases.get(s) + name.substring(s.length()); // $codepro.audit.disable
foundAlias = true;
break;
}
}
}
if (!foundAlias)
{
if (namespace != null && namespace.length() > 0 && !name.startsWith(namespace + GLOBAL_NAMESPACE))
{
// Calling namespace\ is just like calling the current namespace, so we have to check it here and
// return the current name.
if (name.startsWith("namespace\\")) { //$NON-NLS-1$
name = namespace + GLOBAL_NAMESPACE + name.substring(10);
}
else
{
name = namespace + GLOBAL_NAMESPACE + name;
}
}
}
List<IElementEntry> entriesStartingWith = index.getEntriesStartingWith(IPHPIndexConstants.NAMESPACE_CATEGORY,
StringUtil.EMPTY);
for (IElementEntry e : entriesStartingWith)
{
if (e.getEntryPath().startsWith(name))
{
namespaceEntries.add(e);
}
}
return name;
}
/**
* Checks whether the call path contains static dereference operator after the first one.
*
* @param callPath
* - call path
* @return true if contains, false otherwise.
*/
private boolean hasStaticDereferenceOperatorAfterTheFirst(List<String> callPath)
{
if (callPath.size() < 3)
{
return false;
}
for (int i = 2; i < callPath.size(); i++)
{
if (STATIC_DEREFERENCE_OP.equals(callPath.get(i)))
{
return true;
}
}
return false;
}
/**
* Increases each proposal replace length to the one specified. Only instances of PHPCompletionProposal are
* modified.
*
* @param proposals
* - proposals to modify. The proposals in the list ARE modified by the method. null-safe.
* @param replaceLength
* - new replace length.
* @return the same proposals list as the one specified in proposals argument.
*/
private static ICompletionProposal[] batchIncreaseReplaceLength(ICompletionProposal[] proposals,
int replaceLengthIncrease)
{
if (proposals == null || proposals.length == 0 || replaceLengthIncrease == 0)
{
return proposals;
}
for (int i = 0; i < proposals.length; i++)
{
ICompletionProposal proposal = proposals[i];
if (proposal instanceof PHPCompletionProposal)
{
PHPCompletionProposal phpProposal = ((PHPCompletionProposal) proposal);
phpProposal.setReplacementLength(phpProposal.getReplacementLength() + replaceLengthIncrease);
}
}
return proposals;
}
/**
* Gets elements index for a module.
*
* @param content
* - module content.
* @return elements index
*/
private IElementsIndex getIndex(String content, int offset)
{
IModule currentModule = getModule();
if (currentModule == null)
{
return PHPGlobalIndexer.getInstance().getIndex();
}
final UnpackedElementIndex index = new UnpackedElementIndex();
PDTPHPModuleIndexer indexer = new PDTPHPModuleIndexer(false, offset);
indexer.indexModule(content, currentModule, new IIndexReporter()
{
public IElementEntry reportEntry(int category, String entryPath, IReportable value, IModule module)
{
return index.addEntry(category, entryPath, value, module);
}
});
reportedScopeIsGlobal = indexer.isReportedScopeGlobal();
reportedScopeIsUnderClass = indexer.isReportedScopeUnderClass();
globalImports = indexer.getGlobalImports();
aliases = indexer.getAliases();
namespace = indexer.getNamespace();
ModuleSubstitutionIndex result = new ModuleSubstitutionIndex(currentModule, index, PHPGlobalIndexer
.getInstance().getIndex());
return result;
}
/**
* Creates completion proposals for the list of entries or parse nodes.
*
* @param offset
* - offset.
* @param name
* - name to complete.
* @param items
* - items to create proposal for.
* @param module
* - local module.
* @param applyDollarSymbol
* - whether to apply dollar symbol
* @param index
* - index to use.
* @param newInstanceCompletion
* - whether the new instance completion is on.
* @return
*/
private List<ICompletionProposal> createProposals(final int offset, String name, List<Object> items,
IModule module, boolean applyDollarSymbol, IElementsIndex index, boolean newInstanceCompletion)
{
String origName = name;
int lastIndexOf = name.lastIndexOf('\\');
if (lastIndexOf != -1)
{
name = name.substring(lastIndexOf + 1);
}
String lowerCase = name.toLowerCase();
List<ICompletionProposal> result = new ArrayList<ICompletionProposal>();
Set<Integer> usedProposalHash = new LinkedHashSet<Integer>();
Map<Object, PHPCompletionProposal> itemsToProposals = new HashMap<Object, PHPCompletionProposal>();
for (Object item : items)
{
PHPCompletionProposal proposal = null;
if (item instanceof IPHPParseNode)
{
IPHPParseNode node = (IPHPParseNode) item;
String firstName = node.getNodeName().toLowerCase();
if (firstName.startsWith(lowerCase)
|| (applyDollarSymbol && firstName.substring(1).startsWith(lowerCase)))
{
int itemHash = firstName.hashCode() + item.hashCode();
if (!usedProposalHash.contains(itemHash))
{
if (node.getNodeName().charAt(0) == '$')
{
proposal = createProposal(node, offset, DOLLAR_SIGN + name, newInstanceCompletion);
}
else
{
proposal = createProposal(node, offset, name, newInstanceCompletion);
}
if (proposal != null)
{
usedProposalHash.add(itemHash);
}
}
}
}
else if (item instanceof IElementEntry)
{
IElementEntry entry = (IElementEntry) item;
String firstName;
if (entry.getCategory() != IPHPIndexConstants.CONST_CATEGORY
&& entry.getCategory() != IPHPIndexConstants.NAMESPACE_CATEGORY)
{
firstName = ElementsIndexingUtils.getLastNameInPath(entry.getEntryPath());
}
else
{
firstName = entry.getEntryPath().replaceAll(String.valueOf(IElementsIndex.DELIMITER),
STATIC_DEREFERENCE_OP);
}
int itemHash = firstName.hashCode() + item.hashCode();
if (entry.getValue() instanceof NamespacePHPEntryValue)
{
name = origName;
lowerCase = origName.toLowerCase();
if (lowerCase.startsWith(GLOBAL_NAMESPACE))
{
firstName = GLOBAL_NAMESPACE + firstName; // $codepro.audit.disable stringConcatenationInLoop
}
else if (!usedProposalHash.contains(firstName))
{
String k = firstName;
int p = k.indexOf(name);
if (p != -1)
{
k = k.substring(p);
}
else
{
if (firstName != null && firstName.toLowerCase().startsWith(lowerCase))
{
if (!usedProposalHash.contains(firstName))
{
proposal = createProposal(entry, offset, name, firstName, module,
applyDollarSymbol, index, newInstanceCompletion);
if (proposal != null)
{
usedProposalHash.add(itemHash);
result.add(proposal);
itemsToProposals.put(item, proposal);
}
}
}
continue;
}
proposal = createProposal(entry, offset, name, k, module, applyDollarSymbol, index,
newInstanceCompletion);
if (proposal != null)
{
usedProposalHash.add(itemHash);
}
}
}
else if (currentContext != null && !usedProposalHash.contains(firstName)
&& NAMESPACE_PROPOSAL_CONTEXT_TYPE.equals(currentContext.getType())
&& entry.getValue() instanceof ClassPHPEntryValue)
{
ClassPHPEntryValue classEntry = (ClassPHPEntryValue) entry.getValue();
String classNamespace = classEntry.getNameSpace();
if (classNamespace != null && classNamespace.length() > 0)
{
int prefixIndex = origName.indexOf(classNamespace);
if (prefixIndex > -1)
{
// we have to make some adjustments here since we compute the 'use' namespace and then the
// Class name.
String completionContent = classNamespace + '\\' + firstName;
proposal = createProposal(entry, offset, name, completionContent, module,
applyDollarSymbol, index, newInstanceCompletion);
if (proposal != null)
{
usedProposalHash.add(itemHash);
result.add(proposal);
itemsToProposals.put(item, proposal);
}
continue;
}
}
}
String lowerCaseFirstName = firstName.toLowerCase();
if (firstName != null
&& (lowerCaseFirstName.startsWith(lowerCase) || entry.getEntryPath().toLowerCase()
.startsWith(lowerCase)))
{
if (!usedProposalHash.contains(firstName))
{
String n = name;
if (!lowerCaseFirstName.startsWith(lowerCase))
{
// In this case set the name to an empty string to force the proposal to insert at the
// current offset
n = ""; //$NON-NLS-1$
}
proposal = createProposal(entry, offset, n, firstName, module, applyDollarSymbol, index,
newInstanceCompletion);
if (proposal != null)
{
usedProposalHash.add(itemHash);
}
}
}
}
if (proposal != null)
{
result.add(proposal);
itemsToProposals.put(item, proposal);
}
}
prioritizeItems(itemsToProposals, module);
return result;
}
/**
* Creates proposal for the index entry.
*
* @param entry
* - index entry.
* @param offset
* - offset.
* @param name
* - current name to complete.
* @param applyDollarSymbol
* - whether to apply Dollar symbol.
* @param proposalContent
* - proposal contents.
* @param localModule
* - local module.
* @param index
* - index to use
* @param newInstanceCompletion
* - whether the new instance completion is on.
* @return proposalContent - proposal content.
*/
private PHPCompletionProposal createProposal(final IElementEntry entry, int offset, String name,
final String proposalContent, IModule localModule, boolean applyDollarSymbol, final IElementsIndex index,
boolean newInstanceCompletion)
{
if (!currentContext.acceptModelsElements())
{
return null;
}
if (!currentContext.getContextFilter().acceptElementEntry(entry))
{
return null;
}
offset = offset + 1;
String replaceString = proposalContent;
ProposalEnhancement enhancement = getEnhancement(entry, proposalContent, newInstanceCompletion);
if (enhancement != null)
{
replaceString = enhancement.replaceString;
}
int replOffset = offset - name.length();
int replLength = name.length();
int cursorPos = proposalContent.length();
if (enhancement != null)
{
cursorPos += enhancement.cursorShift;
}
Image image = null;
final IModule module = entry.getModule();
final Object val = entry.getValue();
IDocumentationResolver resolver = new EntryDocumentationResolver(proposalContent, index, val, entry);
// if (entry.getValue() instanceof NamespacePHPEntryValue)
// {
image = labelProvider.getImage(val);
// }
// TODO - Shalom - Check if this is needed, now that we use a decorated label provider.
// if (entry.getValue() instanceof FunctionPHPEntryValue)
// {
// FunctionPHPEntryValue value = (FunctionPHPEntryValue) val;
// int modifiers = value.getModifiers();
// image = ItemDecoratingUtils.getFunctionImage(labelProvider, value.isMethod(), modifiers);
// }
// else if (entry.getValue() instanceof ClassPHPEntryValue)
// {
// ClassPHPEntryValue value = (ClassPHPEntryValue) val;
// int modifiers = value.getModifiers();
// image = ItemDecoratingUtils.getClassImage(labelProvider, modifiers);
// }
// else if (entry.getValue() instanceof VariablePHPEntryValue)
// {
// VariablePHPEntryValue value = (VariablePHPEntryValue) val;
// int modifiers = value.getModifiers();
// image = ItemDecoratingUtils.getVariableImage(labelProvider, value.isParameter(), value.isLocal(), value
// .isField(), modifiers);
// }
String dispString = proposalContent;
// if we propose variable, we should add $ in the front
if (applyDollarSymbol && isVariableEntry(entry) && !dispString.startsWith(DOLLAR_SIGN))
{
dispString = DOLLAR_SIGN + dispString;
}
IContextInformation contextInformation = null;
int objType = 0;
String fileOloc = StringUtil.EMPTY;
if (!(entry.getValue() instanceof NamespacePHPEntryValue))
{
if (localModule != null && localModule.equals(entry.getModule()))
{
dispString += "-[local]"; //$NON-NLS-1$
}
else
{
IModule m = entry.getModule();
if (m != null)
{
dispString += "-[" + m.getShortName() + "]"; //$NON-NLS-1$ //$NON-NLS-2$
}
else
{
// the module might be null in a case on a built-in module (such
// as the PHPFunctions5 file)
if (dispString.length() == 0)
{
dispString = entry.getEntryPath();
}
}
}
}
else
{
dispString += "-[namespace]"; //$NON-NLS-1$
}
String immediateTypesDisplayString = getImmediateTypesDisplayString(entry);
if (immediateTypesDisplayString != null && immediateTypesDisplayString.length() > 0)
{
dispString += "-(" + immediateTypesDisplayString + ")"; //$NON-NLS-1$ //$NON-NLS-2$
}
PHPCompletionProposal cp = null;
if ((currentContext != null && currentContext.isAutoActivateCAAfterApply()) || autoActivateAfterProposal(entry))
{
cp = new AutoActivateContentAssistProposal(replaceString, replOffset, replLength, cursorPos, image,
dispString, contextInformation, null, objType, fileOloc, null);
cp.setViewer(viewer);
}
else
{
cp = new PHPCompletionProposal(replaceString, replOffset, replLength, cursorPos, image, dispString,
contextInformation, null, objType, fileOloc, null);
}
cp.setViewer(viewer);
if (enhancement != null)
{
if (enhancement.positions != null)
{
increasePositions(enhancement.positions, replOffset);
enhancement.caretExitOffset += replOffset;
cp.setPositions(enhancement.positions, enhancement.caretExitOffset);
}
}
cp.setResolver(resolver);
cp.setTriggerCharacters(getProposalTriggerCharacters());
return cp;
}
/**
* Finds classes recursively and adds to the argument list.
*
* @param node
* - node to search in.
* @param offset
* - offset.
* @param classesToFill
* - classes to fill.
*/
private PHPCompletionProposal createProposal(final IPHPParseNode node, int offset, String name,
boolean newInstanceCompletion)
{
if (!currentContext.acceptBuiltins())
{
return null;
}
if (!currentContext.getContextFilter().acceptBuiltin(node))
{
return null;
}
offset = offset + 1;
final String name2 = node.getNodeName();
// if (node instanceof PHPFunctionParseNode) {
// name2 = name2 + "()";
// }
String replaceString = name2;
ProposalEnhancement enhancement = getEnhancement(node, replaceString, newInstanceCompletion);
if (enhancement != null)
{
replaceString = enhancement.replaceString;
}
int replOffset = offset - name.length();
int replLength = name.length();
int cursorPos = name2.length();
if (enhancement != null)
{
cursorPos += enhancement.cursorShift;
}
// if (node instanceof PHPFunctionParseNode) {
// cursorPos--;
// }
PHPOutlineItem outlineItem = new PHPOutlineItem(EMPTY_RANGE, node);
Image image = labelProvider.getImage(outlineItem);
final String dispString = node.getNodeName();
IContextInformation contextInformation = null;
IDocumentationResolver resolver = new IDocumentationResolver()
{
public String resolveDocumentation()
{
String additionalInfo = ContentAssistUtils.getDocumentation(node, name2);
return additionalInfo;
}
public String getProposalContent()
{
return dispString;
}
};
int objType = 0;
String fileOloc = StringUtil.EMPTY;
Map<String, String> images = new HashMap<String, String>();
if (PHPBuiltins.getInstance().existsIn(PHPVersion.PHP4, node))
{
images.put(fIcon4, "");
}
else
{
images.put(fIcon4off, "");
}
if (PHPBuiltins.getInstance().existsIn(PHPVersion.PHP5, node))
{
images.put(fIcon5, "");
}
else
{
images.put(fIcon5off, "");
}
if (PHPBuiltins.getInstance().existsIn(PHPVersion.PHP5_3, node))
{
images.put(fIcon53, "");
}
else
{
images.put(fIcon53off, "");
}
if (PHPBuiltins.getInstance().existsIn(PHPVersion.PHP5_4, node))
{
images.put(fIcon54, "");
}
else
{
images.put(fIcon54off, "");
}
PHPCompletionProposal cp = null;
if ((currentContext != null && currentContext.isAutoActivateCAAfterApply()) || autoActivateAfterProposal(node))
{
cp = new AutoActivateContentAssistProposal(replaceString, replOffset, replLength, cursorPos, image,
dispString, contextInformation, null, objType, fileOloc, images);
}
else
{
cp = new PHPCompletionProposal(replaceString, replOffset, replLength, cursorPos, image, dispString,
contextInformation, null, objType, fileOloc, images);
}
cp.setViewer(viewer);
if (enhancement != null)
{
if (enhancement.positions != null)
{
increasePositions(enhancement.positions, replOffset);
enhancement.caretExitOffset += replOffset;
cp.setPositions(enhancement.positions, enhancement.caretExitOffset);
}
}
cp.setResolver(resolver);
return cp;
}
/**
* Gets proposal enhancement.
*
* @param entry
* - either a PHPNode or IElementEntry
* @param proposalContent
* - proposal content.
* @param newInstanceCompletion
* - whether the new instance completion is on.
* @return proposal enhancement or null.
*/
private ProposalEnhancement getEnhancement(Object entry, String proposalContent, boolean newInstanceCompletion)
{
boolean insertAllowed = preferenceStore.getString(IContentAssistPreferencesConstants.INSERT_MODE).equals(
IContentAssistPreferencesConstants.INSERT_MODE_INSERT)
|| !checkParenthesesAlreadyExist();
if (!insertAllowed)
{
return null;
}
ProposalEnhancement result = null;
if (newInstanceCompletion)
{
if (preferenceStore.getBoolean(IContentAssistPreferencesConstants.INSERT_PARENTHESES_AFTER_NEW_INSTANCE))
{
if (result == null)
{
result = new ProposalEnhancement();
result.replaceString = proposalContent;
}
result.replaceString = result.replaceString + "()"; //$NON-NLS-1$
result.positions = new ArrayList<Position>();
result.positions.add(new Position(result.replaceString.length() - 1));
result.cursorShift = 1;
}
if (preferenceStore.getBoolean(IContentAssistPreferencesConstants.INSERT_SEMICOLON_AFTER_NEW_INSTANCE))
{
if (result == null)
{
result = new ProposalEnhancement();
result.replaceString = proposalContent;
}
result.replaceString = result.replaceString + ";"; //$NON-NLS-1$
result.cursorShift = 1;
}
if (result != null)
{
result.caretExitOffset = result.replaceString.length();
}
return result;
}
else
{
if (entry instanceof IElementEntry)
{
IElementEntry elementEntry = (IElementEntry) entry;
Object value = elementEntry.getValue();
if (value instanceof FunctionPHPEntryValue)
{
return getFunctionEntryEnhancement(elementEntry, proposalContent);
}
if (value instanceof VariablePHPEntryValue)
{
VariablePHPEntryValue m = (VariablePHPEntryValue) value;
Set<Object> types = m.getTypes();
if (types.size() == 1)
{
Object[] array = types.toArray(new Object[1]);
if (array[0] != null && array[0].toString() != null)
if (array[0].toString().startsWith(IPHPIndexConstants.LAMBDA_TYPE))
{
return getFunctionEntryEnhancement(elementEntry, proposalContent);
}
}
}
}
else if (entry instanceof PHPFunctionParseNode)
{
return getFunctionNodeEnhancement((PHPFunctionParseNode) entry, proposalContent);
}
else if (entry instanceof IPHPParseNode)
{
// adding space after "extends", "implements" and "new" keywords
IPHPParseNode nodeEntry = (IPHPParseNode) entry;
if (nodeEntry.getNodeType() == IPHPParseNode.KEYWORD_NODE
&& ("extends".equals(nodeEntry.getNodeName()) || //$NON-NLS-1$
"implements".equals(nodeEntry.getNodeName()) || //$NON-NLS-1$
"new".equals(nodeEntry.getNodeName()))) //$NON-NLS-1$
{
result = new ProposalEnhancement();
result.replaceString = nodeEntry.getNodeName() + " "; //$NON-NLS-1$
result.caretExitOffset = result.replaceString.length();
result.cursorShift = 1;
result.positions = new ArrayList<Position>();
}
}
return result;
}
}
/**
* Checks if parentheses already exist after the offset we are going to insert to.
*
* @return true if parentheses already exist, false otherwise.
*/
private boolean checkParenthesesAlreadyExist()
{
for (int i = offset; i < content.length(); i++)
{
char ch = content.charAt(i);
if (ch == '(')
{
return true;
}
if (!Character.isWhitespace(ch))
{
return false;
}
}
return false;
}
/**
* Gets enhancement for function entry. Enhancement positions are related to the beginning of the proposal. Caller
* is responsible for mapping positions on the document.
*
* @param elementEntry
* - function element entry.
* @param proposalContent
* - proposal content
* @return enhancement or null
*/
private ProposalEnhancement getFunctionEntryEnhancement(IElementEntry elementEntry, String proposalContent)
{
ProposalEnhancement result = null;
// In case we are in a trait 'use' block, we don't want any special enhancements on the function name (e.g. no
// brackets). We only add a trailing space.
if (PHPContextCalculator.TRAIT_USE_BLOCK_PROPOSAL_CONTEXT_TYPE.equals(currentContext.getType()))
{
result = new ProposalEnhancement();
result.replaceString = proposalContent;
result.caretExitOffset = result.replaceString.length();
result.cursorShift = 0;
result.positions = new ArrayList<Position>();
return result;
}
// if (!(elementEntry.getValue() instanceof FunctionPHPEntryValue)) {
// throw new IllegalArgumentException("Only functions are accepted."); //$NON-NLS-1$
// }
if (preferenceStore.getBoolean(IContentAssistPreferencesConstants.INSERT_PARENTHESES_AFTER_METHOD_CALLS)
|| preferenceStore.getBoolean(IContentAssistPreferencesConstants.INSERT_FUNCTION_PARAMETERS))
{
if (result == null)
{
result = new ProposalEnhancement();
result.replaceString = proposalContent;
}
StringBuilder builder = new StringBuilder();
builder.append('(');
if (preferenceStore.getBoolean(IContentAssistPreferencesConstants.INSERT_FUNCTION_PARAMETERS))
{
if (elementEntry.getValue() instanceof VariablePHPEntryValue)
{
VariablePHPEntryValue vv = (VariablePHPEntryValue) elementEntry.getValue();
String sm = (String) vv.getTypes().iterator().next();
int indexOf = sm.indexOf('(');
int la = sm.indexOf(')');
String pNames = sm.substring(indexOf + 1, la);
String[] parNames = pNames.split(","); //$NON-NLS-1$
if (preferenceStore.getBoolean(IContentAssistPreferencesConstants.PARAMETRS_TAB_JUMP))
{
result.positions = new ArrayList<Position>();
}
updateResult(result, builder, Arrays.asList(parNames));
builder.append(')');
result.replaceString += builder.toString();
result.cursorShift = 1;
return result;
}
FunctionPHPEntryValue entryValue = (FunctionPHPEntryValue) elementEntry.getValue();
if (entryValue.getParameters() != null)
{
if (preferenceStore.getBoolean(IContentAssistPreferencesConstants.PARAMETRS_TAB_JUMP))
{
result.positions = new ArrayList<Position>();
}
List<String> parameterNames = new ArrayList<String>(entryValue.getParameters().size());
if (preferenceStore
.getBoolean(IContentAssistPreferencesConstants.INSERT_OPTIONAL_FUNCTION_PARAMETERS))
{
parameterNames.addAll(entryValue.getParameters().keySet());
}
else
{
Iterator<String> namesIterator = entryValue.getParameters().keySet().iterator();
boolean[] mandatoryParams = entryValue.getMandatoryParams();
for (boolean isMandatory : mandatoryParams)
{
String name = namesIterator.next();
if (isMandatory)
{
parameterNames.add(name);
}
}
}
updateResult(result, builder, parameterNames);
}
}
builder.append(')');
result.replaceString += builder.toString();
result.cursorShift = 1;
}
if (elementEntry.getValue() instanceof FunctionPHPEntryValue
&& preferenceStore.getBoolean(IContentAssistPreferencesConstants.INSERT_SEMICOLON_AFTER_METHOD_CALLS))
{
if (result == null)
{
result = new ProposalEnhancement();
result.replaceString = proposalContent;
}
result.replaceString += ";"; //$NON-NLS-1$
result.cursorShift = 1;
}
if (result != null)
{
result.caretExitOffset = result.replaceString.length();
}
return result;
}
private void updateResult(ProposalEnhancement result, StringBuilder builder, List<String> parameterNames)
{
for (int i = 0; i < parameterNames.size() - 1; i++)
{
String parameterName = parameterNames.get(i);
int start = builder.length() + result.replaceString.length();
builder.append('$');
builder.append(parameterName);
int length = builder.length() + result.replaceString.length() - start;
builder.append(", "); //$NON-NLS-1$
if (result.positions != null)
{
result.positions.add(new Position(start, length));
}
}
if (parameterNames.size() > 0)
{
int start = builder.length() + result.replaceString.length();
builder.append('$');
builder.append(parameterNames.get(parameterNames.size() - 1));
int length = builder.length() + result.replaceString.length() - start;
if (result.positions != null)
{
result.positions.add(new Position(start, length));
}
}
else
{
result.positions.add(new Position(builder.length() + result.replaceString.length(), 0));
}
}
/**
* Gets enhancement for function entry.
*
* @param node
* - function node.
* @param proposalContent
* - proposal content
* @return enhancement or null
*/
private ProposalEnhancement getFunctionNodeEnhancement(PHPFunctionParseNode node, String proposalContent)
{
ProposalEnhancement result = null;
if (preferenceStore.getBoolean(IContentAssistPreferencesConstants.INSERT_PARENTHESES_AFTER_METHOD_CALLS)
|| preferenceStore.getBoolean(IContentAssistPreferencesConstants.INSERT_FUNCTION_PARAMETERS))
{
if (result == null)
{
result = new ProposalEnhancement();
result.replaceString = proposalContent;
}
StringBuilder builder = new StringBuilder();
builder.append('(');
if (preferenceStore.getBoolean(IContentAssistPreferencesConstants.INSERT_FUNCTION_PARAMETERS))
{
Parameter[] parameters = node.getParameters();
if (parameters != null && parameters.length != 0)
{
boolean insertOptionals = preferenceStore
.getBoolean(IContentAssistPreferencesConstants.INSERT_OPTIONAL_FUNCTION_PARAMETERS);
if (preferenceStore.getBoolean(IContentAssistPreferencesConstants.PARAMETRS_TAB_JUMP))
{
result.positions = new ArrayList<Position>();
}
boolean commaAppended = false;
for (int i = 0; i < parameters.length - 1; i++)
{
// If we do not allow the insertion of optional params
// we screen out any parameter with an empty string as a
// default value.
// TODO - It will be better if a non-existing default
// value will be null, and not an empty string (this
// implementation might be incorrect when
// the default value is an empty string)
if (parameters[i].getDefaultValue() != null && !insertOptionals
&& !StringUtil.EMPTY.equals(parameters[i].getDefaultValue()))
{
break; // once we have a default value we can stop
// (since the optional params are always
// right to the mandatory ones)
}
String parameterName = parameters[i].getVariableName();
if (parameterName != null && !parameterName.startsWith(DOLLAR_SIGN))
{
parameterName = DOLLAR_SIGN + parameterName; // $codepro.audit.disable
// stringConcatenationInLoop
int start = builder.length() + result.replaceString.length();
builder.append(parameterName);
int length = builder.length() + result.replaceString.length() - start;
builder.append(", "); //$NON-NLS-1$
commaAppended = true;
if (result.positions != null)
{
result.positions.add(new Position(start, length));
}
}
}
int start = builder.length() + result.replaceString.length();
// make sure that the last param fits the 'optional
// insertion' policy
if (insertOptionals || parameters[parameters.length - 1].getDefaultValue() == null
|| !insertOptionals
&& StringUtil.EMPTY.equals(parameters[parameters.length - 1].getDefaultValue()))
{
String name = parameters[parameters.length - 1].getVariableName();
if (name != null && !name.startsWith(DOLLAR_SIGN))
{
name = DOLLAR_SIGN + name;
builder.append(name);
int length = builder.length() + result.replaceString.length() - start;
if (result.positions != null)
{
result.positions.add(new Position(start, length));
}
commaAppended = false;
}
}
if (commaAppended)
{
builder.delete(builder.length() - 2, builder.length());
}
}
else
{
result.positions = new ArrayList<Position>();
result.positions.add(new Position(builder.length() + result.replaceString.length(), 0));
}
}
builder.append(')');
result.replaceString += builder.toString();
result.cursorShift = 1;
}
if (preferenceStore.getBoolean(IContentAssistPreferencesConstants.INSERT_SEMICOLON_AFTER_METHOD_CALLS))
{
if (result == null)
{
result = new ProposalEnhancement();
result.replaceString = proposalContent;
}
result.replaceString += ";"; //$NON-NLS-1$
result.cursorShift = 1;
}
if (result != null)
{
result.caretExitOffset = result.replaceString.length();
}
return result;
}
/**
* Gets display string for the types of the entry that can be determinate immediately.
*
* @param entry
* - entry.
* @return display string
*/
private String getImmediateTypesDisplayString(IElementEntry entry)
{
Object entryValue = entry.getValue();
if (entryValue instanceof ClassPHPEntryValue)
{
return StringUtil.EMPTY;
}
else if (entryValue instanceof FunctionPHPEntryValue)
{
Set<Object> returnTypes = ((FunctionPHPEntryValue) entryValue).getReturnTypes();
if (CollectionsUtil.isEmpty(returnTypes))
{
return StringUtil.EMPTY;
}
List<String> knownTypes = new ArrayList<String>();
for (Object type : returnTypes)
{
if (type instanceof String)
{
knownTypes.add((String) type);
}
}
if (knownTypes.size() == 0)
{
return "..."; //$NON-NLS-1$
}
String typesString = getTypesDisplayString(knownTypes, entry);
if (knownTypes.size() != returnTypes.size())
{
typesString += ", ..."; //$NON-NLS-1$
}
return typesString;
}
else if (entryValue instanceof VariablePHPEntryValue)
{
Set<Object> variableTypes = ((VariablePHPEntryValue) entryValue).getTypes();
if (variableTypes == null || variableTypes.size() == 0)
{
return StringUtil.EMPTY;
}
List<String> knownTypes = new ArrayList<String>();
for (Object type : variableTypes)
{
if (type instanceof String)
{
knownTypes.add((String) type);
}
}
if (knownTypes.size() == 0)
{
return "..."; //$NON-NLS-1$
}
String typesString = getTypesDisplayString(knownTypes, entry);
if (variableTypes.size() != knownTypes.size())
{
typesString += ", ..."; //$NON-NLS-1$
}
return typesString;
}
else
{
return StringUtil.EMPTY;
}
}
/**
* Gets display string for the list of types.
*
* @param types
* - types.
* @return display string
*/
private static String getTypesDisplayString(List<String> types, IElementEntry entry)
{
List<String> sortedTypes = new ArrayList<String>(types.size());
sortedTypes.addAll(types);
Collections.sort(sortedTypes);
StringBuilder result = new StringBuilder();
for (int i = 0; i < sortedTypes.size() - 1; i++)
{
String type = sortedTypes.get(i);
result.append(type);
result.append(", "); //$NON-NLS-1$
}
result.append(sortedTypes.get(sortedTypes.size() - 1));
return ContentAssistUtils.truncateLineIfNeeded(result.toString());
}
/**
* Gets whether content assist auto-activation is required after current item proposal.
*
* @param item
* - item.
* @return
*/
private boolean autoActivateAfterProposal(Object item)
{
if (item instanceof IPHPParseNode)
{
IPHPParseNode node = (IPHPParseNode) item;
if (node.getNodeType() == IPHPParseNode.KEYWORD_NODE && "new".equals(node.getNodeName())) { //$NON-NLS-1$
return true;
}
}
return false;
}
/**
* Increases each position offset by the offset specified.
*
* @param positions
* - positions, which offsets to increase.
* @param offset
* - offset to increase positions by.
*/
private void increasePositions(List<Position> positions, int offset)
{
for (Position pos : positions)
{
pos.offset += offset;
}
}
/**
* Gets current module.
*
* @return current module.
*/
private IModule getModule()
{
if (module == null)
{
PHPSourceEditor phpSourceEditor = (PHPSourceEditor) editor;
module = phpSourceEditor.getModule();
isOutOfWorkspace = phpSourceEditor.isOutOfWorkspace();
}
return module;
}
/**
* Gets whether entry is variable entry.
*
* @param entry
* - entry.
* @return true if variable entry, false otherwise
*/
private static boolean isVariableEntry(IElementEntry entry)
{
if (entry.getCategory() != IPHPIndexConstants.VAR_CATEGORY)
{
return false;
}
if (entry.getValue() == null)
{
return false;
}
return entry.getValue() instanceof VariablePHPEntryValue;
}
/**
* Checks whether variable is constant (final + static)
*
* @param entry
* - variable entry.
* @return true if constant, false otherwise.
*/
private static boolean isConstVariable(IElementEntry entry)
{
if (!isVariableEntry(entry))
{
return false;
}
int modifier = ((VariablePHPEntryValue) entry.getValue()).getModifiers();
return PHPFlags.isStatic(modifier) && PHPFlags.isFinal(modifier);
}
/**
* Gets whether entry is function entry.
*
* @param entry
* - entry.
* @return true if variable entry, false otherwise
*/
private static boolean isFunctionEntry(IElementEntry entry)
{
if (entry.getCategory() != IPHPIndexConstants.FUNCTION_CATEGORY
&& entry.getCategory() != IPHPIndexConstants.LAMBDA_FUNCTION_CATEGORY)
{
return false;
}
if (entry.getValue() == null)
{
return false;
}
return entry.getValue() instanceof FunctionPHPEntryValue;
}
/**
* Gets whether entry is class entry.
*
* @param entry
* - entry.
* @return true if variable entry, false otherwise
*/
private static boolean isClassEntry(IElementEntry entry)
{
if (entry.getCategory() != IPHPIndexConstants.CLASS_CATEGORY)
{
return false;
}
if (entry.getValue() == null)
{
return false;
}
return entry.getValue() instanceof ClassPHPEntryValue;
}
/**
* Prioritize the items that will be displayed in the CA.
*
* @param itemsToProposals
* A map of elements (php items) to their proposals.
* @param localModule
* - local module.
* @return sorted items.
*/
private void prioritizeItems(Map<Object, PHPCompletionProposal> itemsToProposals, IModule localModule)
{
// extract the external items into this list.
List<Object> externals = new ArrayList<Object>();
for (Object item : itemsToProposals.keySet())
{
if (item instanceof IElementEntry)
{
if (localModule == null || localModule.equals(((IElementEntry) item).getModule()))
{
// local items should have the original order and come first
itemsToProposals.get(item).setRelevance(ICommonCompletionProposal.RELEVANCE_HIGH + 5);
}
else
{
externals.add(item);
}
}
else
{
externals.add(item);
}
}
// Prioritize the external items.
// We set the priority to classes, functions, constants and then all the rest.
for (Object item : externals)
{
if (item instanceof IElementEntry)
{
IElementEntry entry = (IElementEntry) item;
switch (entry.getCategory())
{
case IPHPIndexConstants.CLASS_CATEGORY:
itemsToProposals.get(item).setRelevance(EXTERNAL_CLASS_PROPOSAL_RELEVANCE);
break;
case IPHPIndexConstants.FUNCTION_CATEGORY:
itemsToProposals.get(item).setRelevance(EXTERNAL_FUNCTION_PROPOSAL_RELEVANCE);
break;
case IPHPIndexConstants.CONST_CATEGORY:
itemsToProposals.get(item).setRelevance(EXTERNAL_CONSTANT_PROPOSAL_RELEVANCE);
break;
default:
itemsToProposals.get(item).setRelevance(EXTERNAL_DEFAULT_PROPOSAL_RELEVANCE);
}
}
else if (item instanceof PHPBaseParseNode)
{
PHPBaseParseNode parseNode = (PHPBaseParseNode) item;
switch (parseNode.getNodeType())
{
case IPHPParseNode.KEYWORD_NODE:
// Set high priority for PHP keywords.
itemsToProposals.get(item).setRelevance(ICommonCompletionProposal.RELEVANCE_HIGH - 2);
break;
case IPHPParseNode.CLASS_NODE:
itemsToProposals.get(item).setRelevance(EXTERNAL_CLASS_PROPOSAL_RELEVANCE - 2);
break;
case IPHPParseNode.FUNCTION_NODE:
itemsToProposals.get(item).setRelevance(EXTERNAL_FUNCTION_PROPOSAL_RELEVANCE - 2);
break;
case IPHPParseNode.CONST_NODE:
itemsToProposals.get(item).setRelevance(EXTERNAL_CONSTANT_PROPOSAL_RELEVANCE - 2);
break;
default:
itemsToProposals.get(item).setRelevance(EXTERNAL_DEFAULT_PROPOSAL_RELEVANCE - 2);
}
}
else
{
// All the rest of the PHP built-in API item can
itemsToProposals.get(item).setRelevance(ICommonCompletionProposal.RELEVANCE_LOW + 10);
}
}
}
/**
* Counts replace length increase when override insertion mode is on.
*
* @param content
* - document content.
* @param offset
* - insertion offset.
* @return replace length increase.
*/
private int countReplaceLengthIncrease(String content, int offset)
{
if (!preferenceStore.getString(IContentAssistPreferencesConstants.INSERT_MODE).equals(
IContentAssistPreferencesConstants.INSERT_MODE_OVERWRITE))
{
return 0;
}
for (int i = offset; i < content.length(); i++)
{
char ch = content.charAt(i);
if (!Character.isJavaIdentifierPart(ch))
{
return i - offset;
}
}
return 0;
}
// Returns true if the given token is one of the PHP import tokens (include/require)
private boolean checkInclude(String token)
{
if (token != null)
{
return PHPRegionTypes.PHP_INCLUDE.equals(token) || PHPRegionTypes.PHP_INCLUDE_ONCE.equals(token)
|| PHPRegionTypes.PHP_REQUIRE.equals(token) || PHPRegionTypes.PHP_REQUIRE_ONCE.equals(token);
}
return false;
}
/*
* (non-Javadoc)
* @see
* com.aptana.editor.common.CommonContentAssistProcessor#computeContextInformation(org.eclipse.jface.text.ITextViewer
* , int)
*/
@Override
public IContextInformation[] computeContextInformation(ITextViewer viewer, int offset)
{
IDocument document = viewer.getDocument();
if (document == null)
{
return EMPTY_CONTEXT_INFO;
}
String content = document.get();
// We traverse back the partitions until we are 'far enough' for the context information calculation.
// We need to do that because the ParsingUtils.createLexemeProvider(offset) call only computes the default-PHP
// partition, and stops on PHP-strings partitions etc. (see https://jira.appcelerator.org/browse/APSTUD-3595)
int start = offset;
try
{
ITypedRegion partition = document.getPartition(offset);
while (partition != null && partition.getOffset() > 0
&& !CompositePartitionScanner.START_SWITCH_TAG.equals(partition.getType()))
{
partition = document.getPartition(partition.getOffset() - 1);
if (offset - partition.getOffset() > CONTEXT_INFO_MAX_LOOK_BEHIND)
{
break;
}
}
start = partition.getOffset();
}
catch (Exception e)
{
IdeLog.logError(PHPEditorPlugin.getDefault(), e);
}
ILexemeProvider<PHPTokenType> lexemeProvider = ParsingUtils.createLexemeProvider(document, start, offset);
CallInfo info = PHPContextCalculator.calculateCallInfo(lexemeProvider, offset);
if (info == null)
{
return EMPTY_CONTEXT_INFO;
}
List<?> items = ContentAssistUtils.selectModelElements(info.getName(), true);
// if no built-in items found, trying to find the custom ones
if (items.size() == 0)
{
Set<IElementEntry> entries = null;
IElementsIndex index = getIndex(content, offset);
ITypedRegion partition = viewer.getDocument().getDocumentPartitioner().getPartition(offset);
// trying to get dereference entries
List<String> callPath = ParsingUtils.parseCallPath(partition, content, info.getNameEndPos(), OPS, false,
document);
if (callPath == null || callPath.isEmpty())
{
return EMPTY_CONTEXT_INFO;
}
if (callPath.size() > 1)
{
if (DEREFERENCE_OP.equals(callPath.get(1)))
{
entries = computeDereferenceEntries(index, callPath, info.getNameEndPos(), getModule(), true,
aliases, namespace);
}
else
{
entries = computeStaticDereferenceEntries(index, callPath, info.getNameEndPos(), getModule(), true,
aliases, namespace);
}
}
else
{
List<IElementEntry> res = computeSimpleIdentifierEntries(reportedScopeIsGlobal, globalImports,
info.getName(), false, index, true, getModule(), false, namespace, aliases);
if (res != null)
{
entries = new LinkedHashSet<IElementEntry>();
entries.addAll(res);
}
}
if (entries == null)
{
return EMPTY_CONTEXT_INFO;
}
// FIXME: Shalom - What about class constructors?
entries = ContentAssistFilters.filterAllButFunctions(entries, index);
if (entries.size() == 0)
{
return EMPTY_CONTEXT_INFO;
}
IElementEntry funcEntry = entries.iterator().next();
IContextInformation ci = PHPContextCalculator.computeArgContextInformation(funcEntry, info.getNameEndPos());
if (ci == null || StringUtil.isEmpty(ci.getInformationDisplayString()))
{
return EMPTY_CONTEXT_INFO;
}
IContextInformation[] res = new IContextInformation[1];
res[0] = ci;
return res;
}
else
{
IPHPParseNode pn = (IPHPParseNode) items.get(0);
IContextInformation[] ici = null;
IContextInformation ci = null;
if (pn instanceof PHPFunctionParseNode)
{
ci = PHPContextCalculator.computeArgContextInformation((PHPFunctionParseNode) pn, info.getNameEndPos());
}
else if (pn instanceof PHPClassParseNode)
{
ci = PHPContextCalculator.computeConstructorContextInformation((PHPClassParseNode) pn,
info.getNameEndPos());
}
if (ci != null)
{
ici = new IContextInformation[] { ci };
}
else
{
ici = EMPTY_CONTEXT_INFO;
}
return ici;
}
}
@Override
public char[] getCompletionProposalAutoActivationCharacters()
{
return autoactivationCharacters;
}
@Override
public char[] getContextInformationAutoActivationCharacters()
{
return contextInformationActivationChars;
}
@Override
public IContextInformationValidator getContextInformationValidator()
{
return new PHPContextInformationValidator();
}
/*
* (non-Javadoc)
* @see com.aptana.editor.common.CommonContentAssistProcessor#getPreferenceNodeQualifier()
*/
protected String getPreferenceNodeQualifier()
{
return PHPEditorPlugin.PLUGIN_ID;
}
@Override
public boolean isEnableEmmet(IDocument document, int offset)
{
return false;
}
}