blob: 3fb9e88ba5bac2901d7cd0f63028e5feaa6d2a69 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.php.editor.completion;
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.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.ImageIcon;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.JTextComponent;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.editor.document.LineDocumentUtils;
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.lexer.TokenId;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.api.lexer.TokenUtilities;
import org.netbeans.editor.BaseDocument;
import org.netbeans.modules.csl.api.CodeCompletionContext;
import org.netbeans.modules.csl.api.CodeCompletionHandler.QueryType;
import org.netbeans.modules.csl.api.CodeCompletionHandler2;
import org.netbeans.modules.csl.api.CodeCompletionResult;
import org.netbeans.modules.csl.api.CompletionProposal;
import org.netbeans.modules.csl.api.Documentation;
import org.netbeans.modules.csl.api.ElementHandle;
import org.netbeans.modules.csl.api.ParameterInfo;
import org.netbeans.modules.csl.spi.ParserResult;
import org.netbeans.modules.csl.spi.support.CancelSupport;
import org.netbeans.modules.parsing.spi.indexing.support.QuerySupport;
import org.netbeans.modules.parsing.spi.indexing.support.QuerySupport.Kind;
import org.netbeans.modules.php.api.PhpVersion;
import org.netbeans.modules.php.editor.CodeUtils;
import org.netbeans.modules.php.editor.NavUtils;
import org.netbeans.modules.php.editor.PredefinedSymbols;
import org.netbeans.modules.php.editor.api.AliasedName;
import org.netbeans.modules.php.editor.api.ElementQueryFactory;
import org.netbeans.modules.php.editor.api.NameKind;
import org.netbeans.modules.php.editor.api.NameKind.CaseInsensitivePrefix;
import org.netbeans.modules.php.editor.api.PhpElementKind;
import org.netbeans.modules.php.editor.api.QualifiedName;
import org.netbeans.modules.php.editor.api.QualifiedNameKind;
import org.netbeans.modules.php.editor.api.elements.AliasedElement.Trait;
import org.netbeans.modules.php.editor.api.elements.ClassElement;
import org.netbeans.modules.php.editor.api.elements.ConstantElement;
import org.netbeans.modules.php.editor.api.elements.ElementFilter;
import org.netbeans.modules.php.editor.api.elements.FieldElement;
import org.netbeans.modules.php.editor.api.elements.FunctionElement;
import org.netbeans.modules.php.editor.api.elements.InterfaceElement;
import org.netbeans.modules.php.editor.api.elements.MethodElement;
import org.netbeans.modules.php.editor.api.elements.NamespaceElement;
import org.netbeans.modules.php.editor.api.elements.ParameterElement;
import org.netbeans.modules.php.editor.api.elements.PhpElement;
import org.netbeans.modules.php.editor.api.elements.TraitElement;
import org.netbeans.modules.php.editor.api.elements.TypeConstantElement;
import org.netbeans.modules.php.editor.api.elements.TypeElement;
import org.netbeans.modules.php.editor.api.elements.TypeMemberElement;
import org.netbeans.modules.php.editor.api.elements.VariableElement;
import org.netbeans.modules.php.editor.completion.CompletionContextFinder.CompletionContext;
import org.netbeans.modules.php.editor.completion.CompletionContextFinder.KeywordCompletionType;
import static org.netbeans.modules.php.editor.completion.CompletionContextFinder.lexerToASTOffset;
import org.netbeans.modules.php.editor.completion.PHPCompletionItem.CompletionRequest;
import org.netbeans.modules.php.editor.completion.PHPCompletionItem.FieldItem;
import org.netbeans.modules.php.editor.completion.PHPCompletionItem.MethodElementItem;
import org.netbeans.modules.php.editor.completion.PHPCompletionItem.TypeConstantItem;
import org.netbeans.modules.php.editor.elements.TypeResolverImpl;
import org.netbeans.modules.php.editor.elements.VariableElementImpl;
import org.netbeans.modules.php.editor.indent.CodeStyle;
import org.netbeans.modules.php.editor.lexer.LexUtilities;
import org.netbeans.modules.php.editor.lexer.PHPTokenId;
import org.netbeans.modules.php.editor.model.ArrowFunctionScope;
import org.netbeans.modules.php.editor.model.FunctionScope;
import org.netbeans.modules.php.editor.model.Model;
import org.netbeans.modules.php.editor.model.ModelElement;
import org.netbeans.modules.php.editor.model.ModelUtils;
import org.netbeans.modules.php.editor.model.NamespaceScope;
import org.netbeans.modules.php.editor.model.ParameterInfoSupport;
import org.netbeans.modules.php.editor.model.Scope;
import org.netbeans.modules.php.editor.model.TypeScope;
import org.netbeans.modules.php.editor.model.VariableName;
import org.netbeans.modules.php.editor.model.VariableScope;
import org.netbeans.modules.php.editor.model.impl.Type;
import org.netbeans.modules.php.editor.model.impl.VariousUtils;
import org.netbeans.modules.php.editor.options.CodeCompletionPanel.VariablesScope;
import org.netbeans.modules.php.editor.options.OptionsUtils;
import org.netbeans.modules.php.editor.parser.PHPParseResult;
import org.netbeans.modules.php.editor.parser.astnodes.ASTNode;
import org.netbeans.modules.php.editor.parser.astnodes.Block;
import org.netbeans.modules.php.editor.parser.astnodes.ClassDeclaration;
import org.netbeans.modules.php.editor.parser.astnodes.ClassInstanceCreation;
import org.netbeans.modules.php.editor.parser.astnodes.Expression;
import org.netbeans.modules.php.editor.parser.astnodes.TraitDeclaration;
import org.netbeans.modules.php.editor.parser.astnodes.TypeDeclaration;
import org.openide.filesystems.FileObject;
import org.openide.util.Pair;
/**
*
* @author Tomasz.Slota@Sun.COM
*/
public class PHPCodeCompletion implements CodeCompletionHandler2 {
// for unit tests
static volatile PhpVersion PHP_VERSION = null;
private static final Logger LOGGER = Logger.getLogger(PHPCodeCompletion.class.getName());
private static enum UseType {
TYPE,
CONST,
FUNCTION,
};
static final Map<String, KeywordCompletionType> PHP_KEYWORDS = new HashMap<>();
static {
PHP_KEYWORDS.put("use", KeywordCompletionType.SIMPLE); //NOI18N
PHP_KEYWORDS.put("namespace", KeywordCompletionType.SIMPLE); //NOI18N
PHP_KEYWORDS.put("class", KeywordCompletionType.ENDS_WITH_SPACE); //NOI18N
PHP_KEYWORDS.put("const", KeywordCompletionType.ENDS_WITH_SPACE); //NOI18N
PHP_KEYWORDS.put("continue", KeywordCompletionType.ENDS_WITH_SEMICOLON); //NOI18N
PHP_KEYWORDS.put("function", KeywordCompletionType.ENDS_WITH_SPACE); //NOI18N
PHP_KEYWORDS.put("fn", KeywordCompletionType.SIMPLE); // NOI18N PHP 7.4
PHP_KEYWORDS.put("new", KeywordCompletionType.SIMPLE); //NOI18N
PHP_KEYWORDS.put("static", KeywordCompletionType.ENDS_WITH_SPACE); //NOI18N
PHP_KEYWORDS.put("var", KeywordCompletionType.ENDS_WITH_SPACE); //NOI18N
PHP_KEYWORDS.put("final", KeywordCompletionType.ENDS_WITH_SPACE); //NOI18N
PHP_KEYWORDS.put("interface", KeywordCompletionType.ENDS_WITH_SPACE); //NOI18N
PHP_KEYWORDS.put("instanceof", KeywordCompletionType.ENDS_WITH_SPACE); //NOI18N
PHP_KEYWORDS.put("implements", KeywordCompletionType.SIMPLE); //NOI18N
PHP_KEYWORDS.put("extends", KeywordCompletionType.SIMPLE); //NOI18N
PHP_KEYWORDS.put("public", KeywordCompletionType.ENDS_WITH_SPACE); //NOI18N
PHP_KEYWORDS.put("private", KeywordCompletionType.ENDS_WITH_SPACE); //NOI18N
PHP_KEYWORDS.put("protected", KeywordCompletionType.ENDS_WITH_SPACE); //NOI18N
PHP_KEYWORDS.put("abstract", KeywordCompletionType.ENDS_WITH_SPACE); //NOI18N
PHP_KEYWORDS.put("readonly", KeywordCompletionType.ENDS_WITH_SPACE); //NOI18N
PHP_KEYWORDS.put("clone", KeywordCompletionType.ENDS_WITH_SPACE); //NOI18N
PHP_KEYWORDS.put("global", KeywordCompletionType.ENDS_WITH_SPACE); //NOI18N
PHP_KEYWORDS.put("goto", KeywordCompletionType.ENDS_WITH_SPACE); //NOI18N
PHP_KEYWORDS.put("throw", KeywordCompletionType.ENDS_WITH_SPACE); //NOI18N
PHP_KEYWORDS.put("if", KeywordCompletionType.CURSOR_INSIDE_BRACKETS); //NOI18N
PHP_KEYWORDS.put("switch", KeywordCompletionType.CURSOR_INSIDE_BRACKETS); //NOI18N
PHP_KEYWORDS.put("match", KeywordCompletionType.CURSOR_INSIDE_BRACKETS); //NOI18N
PHP_KEYWORDS.put("for", KeywordCompletionType.CURSOR_INSIDE_BRACKETS); //NOI18N
PHP_KEYWORDS.put("array", KeywordCompletionType.CURSOR_INSIDE_BRACKETS); //NOI18N
PHP_KEYWORDS.put("foreach", KeywordCompletionType.CURSOR_INSIDE_BRACKETS); //NOI18N
PHP_KEYWORDS.put("while", KeywordCompletionType.CURSOR_INSIDE_BRACKETS); //NOI18N
PHP_KEYWORDS.put("catch", KeywordCompletionType.CURSOR_INSIDE_BRACKETS); //NOI18N
PHP_KEYWORDS.put("try", KeywordCompletionType.ENDS_WITH_CURLY_BRACKETS); //NOI18N
PHP_KEYWORDS.put("default", KeywordCompletionType.ENDS_WITH_COLON); //NOI18N
PHP_KEYWORDS.put("default =>", KeywordCompletionType.ENDS_WITH_SPACE); //NOI18N PHP 8.0 match expression
PHP_KEYWORDS.put("break", KeywordCompletionType.ENDS_WITH_SEMICOLON); //NOI18N
PHP_KEYWORDS.put("endif", KeywordCompletionType.ENDS_WITH_SEMICOLON); //NOI18N
PHP_KEYWORDS.put("endfor", KeywordCompletionType.ENDS_WITH_SEMICOLON); //NOI18N
PHP_KEYWORDS.put("endforeach", KeywordCompletionType.ENDS_WITH_SEMICOLON); //NOI18N
PHP_KEYWORDS.put("endwhile", KeywordCompletionType.ENDS_WITH_SEMICOLON); //NOI18N
PHP_KEYWORDS.put("endswitch", KeywordCompletionType.ENDS_WITH_SEMICOLON); //NOI18N
PHP_KEYWORDS.put("case", KeywordCompletionType.ENDS_WITH_COLON); //NOI18N
PHP_KEYWORDS.put("and", KeywordCompletionType.ENDS_WITH_SPACE); //NOI18N
PHP_KEYWORDS.put("as", KeywordCompletionType.ENDS_WITH_SPACE); //NOI18N
PHP_KEYWORDS.put("declare", KeywordCompletionType.CURSOR_INSIDE_BRACKETS); //NOI18N
PHP_KEYWORDS.put("do", KeywordCompletionType.ENDS_WITH_CURLY_BRACKETS); //NOI18N
PHP_KEYWORDS.put("else", KeywordCompletionType.ENDS_WITH_CURLY_BRACKETS); //NOI18N
PHP_KEYWORDS.put("elseif", KeywordCompletionType.ENDS_WITH_BRACKETS_AND_CURLY_BRACKETS); //NOI18N
PHP_KEYWORDS.put("enddeclare", KeywordCompletionType.ENDS_WITH_SEMICOLON); //NOI18N
PHP_KEYWORDS.put("or", KeywordCompletionType.ENDS_WITH_SPACE); //NOI18N
PHP_KEYWORDS.put("xor", KeywordCompletionType.ENDS_WITH_SPACE); //NOI18N
PHP_KEYWORDS.put("finally", KeywordCompletionType.ENDS_WITH_CURLY_BRACKETS); //NOI18N
PHP_KEYWORDS.put("yield", KeywordCompletionType.CURSOR_BEFORE_ENDING_SEMICOLON); //NOI18N
PHP_KEYWORDS.put("yield from", KeywordCompletionType.ENDS_WITH_SPACE); //NOI18N
}
private static final String[] PHP_LANGUAGE_CONSTRUCTS_WITH_QUOTES = {
"echo", "include", "include_once", "require", "require_once", "print" // NOI18N
};
private static final String[] PHP_LANGUAGE_CONSTRUCTS_WITH_PARENTHESES = {
"die", "eval", "exit", "empty", "isset", "list", "unset" // NOI18N
};
private static final String[] PHP_LANGUAGE_CONSTRUCTS_WITH_SEMICOLON = {
"return" // NOI18N
};
static final String PHP_CLASS_KEYWORD_THIS = "$this->"; //NOI18N
static final String[] PHP_CLASS_KEYWORDS = {
PHP_CLASS_KEYWORD_THIS, "self::", "parent::", "static::" //NOI18N
};
static final String[] PHP_STATIC_CLASS_KEYWORDS = {
"self::", "parent::", "static::" //NOI18N
};
static final List<String> PHP_GLOBAL_CONST_KEYWORDS = Arrays.asList(
"array", // NOI18N
"new" // NOI18N
);
static final List<String> PHP_CLASS_CONST_KEYWORDS = Arrays.asList(
"array", // NOI18N
"self::", // NOI18N
"parent::" // NOI18N
);
private static final List<String> PHP_MATCH_EXPRESSION_KEYWORDS = Arrays.asList(
"function", // NOI18N
"fn", // NOI18N
"new", // NOI18N
"static", // NOI18N
"instanceof", // NOI18N
"clone", // NOI18N
"throw", // NOI18N
"match", // NOI18N
"array", // NOI18N
"default =>", // NOI18N
"and", // NOI18N
"or", // NOI18N
"xor" // NOI18N
);
private static final List<String> PHP_VISIBILITY_KEYWORDS = Arrays.asList(
"public", // NOI18N
"protected", // NOI18N
"private" // NOI18N
);
private static final Collection<Character> AUTOPOPUP_STOP_CHARS = new TreeSet<>(
Arrays.asList('=', ';', '+', '-', '*', '/',
'%', '(', ')', '[', ']', '{', '}', '?'));
private static final Collection<PHPTokenId> TOKENS_TRIGGERING_AUTOPUP_TYPES_WS =
Arrays.asList(PHPTokenId.PHP_NEW, PHPTokenId.PHP_EXTENDS, PHPTokenId.PHP_IMPLEMENTS, PHPTokenId.PHP_INSTANCEOF);
private static final List<String> INVALID_PROPOSALS_FOR_CLS_MEMBERS =
Arrays.asList(new String[]{"__construct", "__destruct", "__call", "__callStatic",
"__clone", "__get", "__invoke", "__isset", "__set", "__set_state",
"__sleep", "__toString", "__unset", "__wakeup"}); //NOI18N
private static final List<String> CLASS_CONTEXT_KEYWORD_PROPOSAL =
Arrays.asList(new String[]{"abstract", "const", "function", "private", "final",
"protected", "public", "static", "var", "readonly"}); //NOI18N
private static final List<String> INTERFACE_CONTEXT_KEYWORD_PROPOSAL =
Arrays.asList(new String[]{"const", "function", "public", "static"}); //NOI18N
private static final List<String> INHERITANCE_KEYWORDS =
Arrays.asList(new String[]{"extends", "implements"}); //NOI18N
private static final String EXCEPTION_CLASS_NAME = "\\Exception"; // NOI18N
private static final List<PHPTokenId> VALID_UNION_TYPE_TOKENS = Arrays.asList(
PHPTokenId.WHITESPACE, PHPTokenId.PHP_STRING, PHPTokenId.PHP_NS_SEPARATOR,
PHPTokenId.PHP_TYPE_BOOL, PHPTokenId.PHP_TYPE_FLOAT, PHPTokenId.PHP_TYPE_INT, PHPTokenId.PHP_TYPE_STRING, PHPTokenId.PHP_TYPE_VOID,
PHPTokenId.PHP_TYPE_OBJECT, PHPTokenId.PHP_TYPE_MIXED, PHPTokenId.PHP_SELF, PHPTokenId.PHP_PARENT, PHPTokenId.PHP_STATIC,
PHPTokenId.PHP_NULL, PHPTokenId.PHP_FALSE, PHPTokenId.PHP_ARRAY, PHPTokenId.PHP_ITERABLE, PHPTokenId.PHP_CALLABLE,
PHPTokenId.PHPDOC_COMMENT_START, PHPTokenId.PHPDOC_COMMENT, PHPTokenId.PHPDOC_COMMENT_END,
PHPTokenId.PHP_COMMENT_START, PHPTokenId.PHP_COMMENT, PHPTokenId.PHP_COMMENT_END
);
private boolean caseSensitive;
private QuerySupport.Kind nameKind;
@Override
public CodeCompletionResult complete(CodeCompletionContext completionContext) {
long startTime = 0;
if (LOGGER.isLoggable(Level.FINE)) {
startTime = System.currentTimeMillis();
}
BaseDocument doc = (BaseDocument) completionContext.getParserResult().getSnapshot().getSource().getDocument(false);
if (doc == null) {
return CodeCompletionResult.NONE;
}
if (CancelSupport.getDefault().isCancelled()) {
return CodeCompletionResult.NONE;
}
// TODO: separate the code that uses informatiom from lexer
// and avoid running the index/ast analysis under read lock
// in order to improve responsiveness
// doc.readLock(); //TODO: use token hierarchy from snapshot and not use read lock in CC #171702
final PHPCompletionResult completionResult = new PHPCompletionResult(completionContext);
ParserResult info = completionContext.getParserResult();
final int caretOffset = completionContext.getCaretOffset();
this.caseSensitive = completionContext.isCaseSensitive();
this.nameKind = caseSensitive ? QuerySupport.Kind.PREFIX : QuerySupport.Kind.CASE_INSENSITIVE_PREFIX;
PHPParseResult result = (PHPParseResult) info;
if (result.getProgram() == null) {
return CodeCompletionResult.NONE;
}
final FileObject fileObject = result.getSnapshot().getSource().getFileObject();
if (fileObject == null) {
return CodeCompletionResult.NONE;
}
if (CancelSupport.getDefault().isCancelled()) {
return CodeCompletionResult.NONE;
}
CompletionContext context = CompletionContextFinder.findCompletionContext(info, caretOffset);
LOGGER.log(Level.FINE, "CC context: {0}", context);
if (context == CompletionContext.NONE) {
return CodeCompletionResult.NONE;
}
PHPCompletionItem.CompletionRequest request = new PHPCompletionItem.CompletionRequest();
request.context = context;
String prefix = getPrefix(info, caretOffset, true, PrefixBreaker.WITH_NS_PARTS);
if (prefix == null) {
return CodeCompletionResult.NONE;
}
prefix = prefix.trim().isEmpty() ? completionContext.getPrefix() : prefix;
// prefix for index search (used for group use, equals to the base NS (before curly open))
String searchPrefix;
switch (context) {
case GROUP_USE_KEYWORD:
case GROUP_USE_CONST_KEYWORD:
case GROUP_USE_FUNCTION_KEYWORD:
searchPrefix = getPrefix(info, findBaseNamespaceEnd(info, caretOffset), true, PrefixBreaker.WITH_NS_PARTS);
break;
case EXPRESSION: // no break
if (prefix.startsWith("@")) { // NOI18N
prefix = prefix.substring(1);
}
default:
searchPrefix = null;
break;
}
request.extraPrefix = searchPrefix;
request.anchor = caretOffset
// can't just use 'prefix.getLength()' here cos it might have been calculated with
// the 'upToOffset' flag set to false
- prefix.length();
request.result = result;
request.info = info;
request.prefix = prefix;
request.index = ElementQueryFactory.getIndexQuery(info);
request.currentlyEditedFileURL = fileObject.toURL().toString();
if (CancelSupport.getDefault().isCancelled()) {
return CodeCompletionResult.NONE;
}
CodeStyle codeStyle;
switch (context) {
case DEFAULT_PARAMETER_VALUE:
final CaseInsensitivePrefix nameKindPrefix = NameKind.caseInsensitivePrefix(request.prefix);
autoCompleteKeywords(completionResult, request, Arrays.asList("array", "new")); //NOI18N
autoCompleteNamespaces(completionResult, request);
autoCompleteTypeNames(completionResult, request, null, true);
if (CancelSupport.getDefault().isCancelled()) {
return CodeCompletionResult.NONE;
}
final ElementFilter forName = ElementFilter.forName(nameKindPrefix);
final Model model = request.result.getModel();
final Set<AliasedName> aliasedNames = ModelUtils.getAliasedNames(model, request.anchor);
Set<ConstantElement> constants = request.index.getConstants(nameKindPrefix, aliasedNames, Trait.ALIAS);
for (ConstantElement constant : forName.filter(constants)) {
if (CancelSupport.getDefault().isCancelled()) {
return CodeCompletionResult.NONE;
}
completionResult.add(new PHPCompletionItem.ConstantItem(constant, request));
}
final EnclosingClass enclosingClass = findEnclosingClass(request.info, lexerToASTOffset(request.result, request.anchor));
if (enclosingClass != null) {
String clsName = enclosingClass.getClassName();
for (String classKeyword : PHP_STATIC_CLASS_KEYWORDS) {
if (classKeyword.toLowerCase().startsWith(request.prefix)) { //NOI18N
completionResult.add(new PHPCompletionItem.ClassScopeKeywordItem(clsName, classKeyword, request));
}
}
}
break;
case NAMESPACE_KEYWORD:
if (CancelSupport.getDefault().isCancelled()) {
return CodeCompletionResult.NONE;
}
Set<NamespaceElement> namespaces = request.index.getNamespaces(
NameKind.caseInsensitivePrefix(QualifiedName.create(request.prefix).toNotFullyQualified()));
for (NamespaceElement namespace : namespaces) {
if (CancelSupport.getDefault().isCancelled()) {
return CodeCompletionResult.NONE;
}
completionResult.add(new PHPCompletionItem.NamespaceItem(namespace, request, QualifiedNameKind.QUALIFIED));
}
break;
case GLOBAL:
autoCompleteGlobals(completionResult, request);
break;
case MATCH_EXPRESSION:
autoCompleteNamespaces(completionResult, request);
autoCompleteExpression(completionResult, request, PHP_MATCH_EXPRESSION_KEYWORDS);
break;
case EXPRESSION:
autoCompleteExpression(completionResult, request);
break;
case CLASS_MEMBER_PARAMETER_NAME:
autoCompleteExpression(completionResult, request);
autoCompleteClassMethodParameterName(completionResult, request, false);
break;
case STATIC_CLASS_MEMBER_PARAMETER_NAME:
autoCompleteExpression(completionResult, request);
autoCompleteClassMethodParameterName(completionResult, request, true);
break;
case FUNCTION_PARAMETER_NAME:
autoCompleteExpression(completionResult, request);
autoCompleteFunctionParameterName(completionResult, request);
break;
case GLOBAL_CONST_EXPRESSION:
autoCompleteNamespaces(completionResult, request);
autoCompleteTypeNames(completionResult, request, null, true);
autoCompleteConstants(completionResult, request);
autoCompleteKeywords(completionResult, request, PHP_GLOBAL_CONST_KEYWORDS);
break;
case CLASS_CONST_EXPRESSION:
autoCompleteNamespaces(completionResult, request);
autoCompleteTypeNames(completionResult, request, null, true);
autoCompleteConstants(completionResult, request);
autoCompleteKeywords(completionResult, request, PHP_CLASS_CONST_KEYWORDS);
// NETBEANS-1855
if (!request.prefix.contains("\\")) { // NOI18N
// e.g. const CONSTANT = \^Foo\Bar::CONSTANT;
autoCompleteClassConstants(completionResult, request);
}
break;
case HTML:
case OPEN_TAG:
completionResult.add(new PHPCompletionItem.TagItem("<?php", 1, request)); //NOI18N
completionResult.add(new PHPCompletionItem.TagItem("<?=", 2, request)); //NOI18N
break;
case NEW_CLASS:
autoCompleteKeywords(completionResult, request, Arrays.asList("class")); //NOI18N
autoCompleteNamespaces(completionResult, request);
autoCompleteNewClass(completionResult, request);
break;
case CLASS_NAME:
autoCompleteNamespaces(completionResult, request);
autoCompleteClassNames(completionResult, request, false);
break;
case INTERFACE_NAME:
autoCompleteNamespaces(completionResult, request);
autoCompleteInterfaceNames(completionResult, request);
break;
case GROUP_USE_KEYWORD:
autoCompleteGroupUse(UseType.TYPE, completionResult, request);
List<String> keywords = Arrays.asList("const", "function"); // NOI18N
for (String keyword : keywords) {
if (startsWith(keyword, request.prefix)) {
completionResult.add(new PHPCompletionItem.KeywordItem(keyword, request));
}
}
break;
case GROUP_USE_CONST_KEYWORD:
autoCompleteGroupUse(UseType.CONST, completionResult, request);
break;
case GROUP_USE_FUNCTION_KEYWORD:
autoCompleteGroupUse(UseType.FUNCTION, completionResult, request);
break;
case USE_KEYWORD:
codeStyle = CodeStyle.get(request.result.getSnapshot().getSource().getDocument(caseSensitive));
autoCompleteAfterUse(
UseType.TYPE,
completionResult,
request,
codeStyle.startUseWithNamespaceSeparator() ? QualifiedNameKind.FULLYQUALIFIED : QualifiedNameKind.QUALIFIED);
break;
case USE_CONST_KEYWORD:
codeStyle = CodeStyle.get(request.result.getSnapshot().getSource().getDocument(caseSensitive));
autoCompleteAfterUse(
UseType.CONST,
completionResult,
request,
codeStyle.startUseWithNamespaceSeparator() ? QualifiedNameKind.FULLYQUALIFIED : QualifiedNameKind.QUALIFIED);
break;
case USE_FUNCTION_KEYWORD:
codeStyle = CodeStyle.get(request.result.getSnapshot().getSource().getDocument(caseSensitive));
autoCompleteAfterUse(
UseType.FUNCTION,
completionResult,
request,
codeStyle.startUseWithNamespaceSeparator() ? QualifiedNameKind.FULLYQUALIFIED : QualifiedNameKind.QUALIFIED);
break;
case USE_TRAITS:
codeStyle = CodeStyle.get(request.result.getSnapshot().getSource().getDocument(caseSensitive));
autoCompleteAfterUseTrait(
completionResult,
request,
codeStyle.startUseWithNamespaceSeparator() ? QualifiedNameKind.FULLYQUALIFIED : QualifiedNameKind.QUALIFIED);
break;
case VISIBILITY_MODIFIER_OR_TYPE_NAME: // no break
autoCompleteKeywords(completionResult, request, PHP_VISIBILITY_KEYWORDS);
autoCompleteKeywords(completionResult, request, Arrays.asList("readonly")); // NOI18N
case TYPE_NAME:
autoCompleteNamespaces(completionResult, request);
autoCompleteTypeNames(completionResult, request);
final ArrayList<String> typesForTypeName = new ArrayList<>(Type.getTypesForEditor());
if (isInType(request)) {
// add self and parent
typesForTypeName.addAll(Type.getSpecialTypesForType());
}
if (isNullableType(info, caretOffset)) {
typesForTypeName.remove(Type.FALSE);
typesForTypeName.remove(Type.NULL);
}
if (isUnionType(info, caretOffset)) {
typesForTypeName.remove(Type.MIXED);
}
autoCompleteKeywords(completionResult, request, typesForTypeName);
break;
case RETURN_UNION_TYPE_NAME: // no break
case RETURN_TYPE_NAME:
autoCompleteNamespaces(completionResult, request);
autoCompleteTypeNames(completionResult, request);
final ArrayList<String> typesForReturnTypeName = new ArrayList<>(Type.getTypesForReturnType());
if (isInType(request)) {
// add self and parent
typesForReturnTypeName.addAll(Type.getSpecialTypesForType());
typesForReturnTypeName.add(Type.STATIC);
}
if (isNullableType(info, caretOffset)) {
typesForReturnTypeName.remove(Type.FALSE);
typesForReturnTypeName.remove(Type.NULL);
typesForReturnTypeName.remove(Type.VOID);
typesForReturnTypeName.remove(Type.NEVER);
} else if (context == CompletionContext.RETURN_UNION_TYPE_NAME) {
typesForReturnTypeName.remove(Type.VOID);
typesForReturnTypeName.remove(Type.NEVER);
typesForReturnTypeName.remove(Type.MIXED);
}
autoCompleteKeywords(completionResult, request, typesForReturnTypeName);
break;
case FIELD_TYPE_NAME:
autoCompleteFieldType(info, caretOffset, completionResult, request, false);
break;
case STRING:
// LOCAL VARIABLES
completionResult.addAll(getVariableProposals(request, null));
// are we in class?
if (request.prefix.length() == 0 || startsWith(PHP_CLASS_KEYWORD_THIS, request.prefix)) {
final EnclosingClass enclosingCls = findEnclosingClass(info, caretOffset);
if (enclosingCls != null) {
final String className = enclosingCls.extractClassName();
if (className != null) {
completionResult.add(new PHPCompletionItem.ClassScopeKeywordItem(className, PHP_CLASS_KEYWORD_THIS, request));
}
}
}
break;
case CLASS_MEMBER:
autoCompleteClassMembers(completionResult, request, false);
break;
case STATIC_CLASS_MEMBER:
autoCompleteClassMembers(completionResult, request, true);
break;
case PHPDOC:
PHPDOCCodeCompletion.complete(completionResult, request);
if (PHPDOCCodeCompletion.isTypeCtx(request)) {
autoCompleteTypeNames(completionResult, request);
autoCompleteNamespaces(completionResult, request);
autoCompleteKeywordsInPHPDoc(completionResult, request);
}
break;
case CLASS_CONTEXT_KEYWORDS:
autoCompleteInClassContext(info, caretOffset, completionResult, request);
break;
case INTERFACE_CONTEXT_KEYWORDS:
autoCompleteInInterfaceContext(completionResult, request);
break;
case METHOD_NAME:
autoCompleteMethodName(info, caretOffset, completionResult, request);
break;
case IMPLEMENTS:
autoCompleteKeywords(completionResult, request, Collections.singletonList("implements")); //NOI18N
break;
case EXTENDS:
autoCompleteKeywords(completionResult, request, Collections.singletonList("extends")); //NOI18N
break;
case INHERITANCE:
autoCompleteKeywords(completionResult, request, INHERITANCE_KEYWORDS);
break;
case THROW_NEW:
autoCompleteNamespaces(completionResult, request);
autoCompleteExceptions(completionResult, request, true);
break;
case THROW:
autoCompleteKeywords(completionResult, request, Collections.singletonList("new")); // NOI18N
autoCompleteNamespaces(completionResult, request);
// XXX allow all class names for static factory methods? e.g. ExceptionFactory::create("Something");
// currently, restrict to classes extending the Exception class
autoCompleteExceptions(completionResult, request, false);
break;
case CATCH:
autoCompleteNamespaces(completionResult, request);
autoCompleteExceptions(completionResult, request, false);
break;
case CLASS_MEMBER_IN_STRING:
autoCompleteClassFields(completionResult, request);
break;
case SERVER_ENTRY_CONSTANTS:
//TODO: probably better PHPCompletionItem instance should be used
//autoCompleteMagicItems(proposals, request, PredefinedSymbols.SERVER_ENTRY_CONSTANTS);
for (String keyword : PredefinedSymbols.SERVER_ENTRY_CONSTANTS) {
if (keyword.startsWith(request.prefix)) {
completionResult.add(new PHPCompletionItem.KeywordItem(keyword, request) {
@Override
public ImageIcon getIcon() {
return null;
}
});
}
}
break;
default:
assert false : context;
}
if (CancelSupport.getDefault().isCancelled()) {
return CodeCompletionResult.NONE;
}
if (LOGGER.isLoggable(Level.FINE)) {
long time = System.currentTimeMillis() - startTime;
LOGGER.fine(String.format("complete() took %d ms, result contains %d items", time, completionResult.getItems().size()));
}
return completionResult;
}
private List<ElementFilter> createTypeFilter(final EnclosingClass enclosingClass) {
List<ElementFilter> superTypeIndices = new ArrayList<>();
Expression superClass = enclosingClass.getSuperClass();
if (superClass != null) {
String superClsName = enclosingClass.extractUnqualifiedSuperClassName();
superTypeIndices.add(ElementFilter.forSuperClassName(QualifiedName.create(superClsName)));
}
List<Expression> interfaces = enclosingClass.getInterfaces();
Set<QualifiedName> superIfaceNames = new HashSet<>();
for (Expression identifier : interfaces) {
String ifaceName = CodeUtils.extractUnqualifiedName(identifier);
if (ifaceName != null) {
superIfaceNames.add(QualifiedName.create(ifaceName));
}
}
if (!superIfaceNames.isEmpty()) {
superTypeIndices.add(ElementFilter.forSuperInterfaceNames(superIfaceNames));
}
return superTypeIndices;
}
private void autoCompleteMethodName(ParserResult info, int caretOffset, final PHPCompletionResult completionResult,
PHPCompletionItem.CompletionRequest request) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
EnclosingClass enclosingClass = findEnclosingClass(info, lexerToASTOffset(info, caretOffset));
if (enclosingClass != null) {
List<ElementFilter> superTypeIndices = createTypeFilter(enclosingClass);
String clsName = enclosingClass.getClassName();
NamespaceScope namespaceScope = ModelUtils.getNamespaceScope(request.result.getModel().getFileScope(), request.anchor);
String fullyQualifiedClassName = VariousUtils.qualifyTypeNames(clsName, request.anchor, namespaceScope);
if (fullyQualifiedClassName != null) {
final FileObject fileObject = request.result.getSnapshot().getSource().getFileObject();
final ElementFilter classFilter = ElementFilter.allOf(
ElementFilter.forFiles(fileObject), ElementFilter.allOf(superTypeIndices));
Set<ClassElement> classes = classFilter.filter(request.index.getClasses(NameKind.exact(fullyQualifiedClassName)));
for (ClassElement classElement : classes) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
ElementFilter methodFilter = ElementFilter.allOf(
ElementFilter.forExcludedNames(toNames(request.index.getDeclaredMethods(classElement)), PhpElementKind.METHOD),
ElementFilter.forName(NameKind.caseInsensitivePrefix(QualifiedName.create(request.prefix))));
Set<MethodElement> accessibleMethods = methodFilter.filter(request.index.getAccessibleMethods(classElement, classElement));
for (MethodElement method : accessibleMethods) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
if (!method.isFinal()) {
completionResult.add(PHPCompletionItem.MethodDeclarationItem.forMethodName(method, request));
}
}
Set<MethodElement> magicMethods = methodFilter.filter(request.index.getAccessibleMagicMethods(classElement));
for (MethodElement magicMethod : magicMethods) {
if (magicMethod != null) {
completionResult.add(PHPCompletionItem.MethodDeclarationItem.forMethodName(magicMethod, request));
}
}
break;
}
}
}
}
/**
* Finding item after new keyword.
*
* @param completionResult
* @param request
*/
private void autoCompleteNewClass(final PHPCompletionResult completionResult, PHPCompletionItem.CompletionRequest request) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
// At first find all classes that match the prefix
final boolean isCamelCase = isCamelCaseForTypeNames(request.prefix);
final QualifiedName prefix = QualifiedName.create(request.prefix).toNotFullyQualified();
final NameKind nameQuery = NameKind.create(request.prefix,
isCamelCase ? Kind.CAMEL_CASE : Kind.CASE_INSENSITIVE_PREFIX);
Model model = request.result.getModel();
Set<ClassElement> classes = request.index.getClasses(nameQuery, ModelUtils.getAliasedNames(model, request.anchor), Trait.ALIAS);
if (!classes.isEmpty()) {
completionResult.setFilterable(false);
}
boolean addedExact = false;
final NameKind query;
if (classes.size() == 1) {
ClassElement clazz = (ClassElement) classes.toArray()[0];
if (!clazz.isAbstract()
&& !clazz.isAnonymous()) {
// if there is only once class find constructors for it
query = isCamelCase ? NameKind.create(prefix.toString(), QuerySupport.Kind.CAMEL_CASE) : NameKind.caseInsensitivePrefix(prefix);
autoCompleteConstructors(completionResult, request, model, query);
}
} else {
for (ClassElement clazz : classes) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
if (!clazz.isAbstract()
&& !clazz.isAnonymous()) {
// check whether the prefix is exactly the class
NamespaceScope namespaceScope = ModelUtils.getNamespaceScope(request.result.getModel().getFileScope(), request.anchor);
String fqPrefixName = VariousUtils.qualifyTypeNames(request.prefix, request.anchor, namespaceScope);
if (clazz.getFullyQualifiedName().toString().equals(fqPrefixName)) {
// find constructor of the class
if (!addedExact) { // add the constructors only once
autoCompleteConstructors(completionResult, request, model, NameKind.exact(fqPrefixName));
addedExact = true;
}
} else {
// put to the cc just the class
completionResult.add(new PHPCompletionItem.ClassItem(clazz, request, false, null));
}
}
}
}
}
private void autoCompleteConstructors(final PHPCompletionResult completionResult, final PHPCompletionItem.CompletionRequest request, final Model model, final NameKind query) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
Set<AliasedName> aliasedNames = ModelUtils.getAliasedNames(model, request.anchor);
Set<MethodElement> constructors = request.index.getConstructors(query, aliasedNames, Trait.ALIAS);
for (MethodElement constructor : constructors) {
for (final PHPCompletionItem.NewClassItem newClassItem : PHPCompletionItem.NewClassItem.getNewClassItems(constructor, request)) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
completionResult.add(newClassItem);
}
}
}
private void autoCompleteExceptions(final PHPCompletionResult completionResult, PHPCompletionItem.CompletionRequest request, boolean withConstructors) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
final boolean isCamelCase = isCamelCaseForTypeNames(request.prefix);
final NameKind nameQuery = NameKind.create(request.prefix, isCamelCase ? Kind.CAMEL_CASE : Kind.CASE_INSENSITIVE_PREFIX);
final Set<ClassElement> classes = request.index.getClasses(nameQuery);
final Model model = request.result.getModel();
final Set<QualifiedName> constructorClassNames = new HashSet<>();
for (ClassElement classElement : classes) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
if (isExceptionClass(classElement)) {
completionResult.add(new PHPCompletionItem.ClassItem(classElement, request, false, null));
if (withConstructors) {
constructorClassNames.add(classElement.getFullyQualifiedName());
}
continue;
}
if (classElement.getSuperClassName() != null) {
Set<ClassElement> inheritedClasses = request.index.getInheritedClasses(classElement);
for (ClassElement inheritedClass : inheritedClasses) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
if (isExceptionClass(inheritedClass)) {
completionResult.add(new PHPCompletionItem.ClassItem(classElement, request, false, null));
if (withConstructors) {
constructorClassNames.add(classElement.getFullyQualifiedName());
}
break;
}
}
}
}
for (QualifiedName qualifiedName : constructorClassNames) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
autoCompleteConstructors(completionResult, request, model, NameKind.exact(qualifiedName));
}
}
private boolean isExceptionClass(ClassElement classElement) {
return classElement.getFullyQualifiedName().toString().equals(EXCEPTION_CLASS_NAME);
}
private void autoCompleteClassNames(final PHPCompletionResult completionResult,
PHPCompletionItem.CompletionRequest request, boolean endWithDoubleColon) {
autoCompleteClassNames(completionResult, request, endWithDoubleColon, null);
}
private void autoCompleteClassNames(final PHPCompletionResult completionResult,
PHPCompletionItem.CompletionRequest request, boolean endWithDoubleColon, QualifiedNameKind kind) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
final boolean isCamelCase = isCamelCaseForTypeNames(request.prefix);
final NameKind nameQuery = NameKind.create(request.prefix,
isCamelCase ? Kind.CAMEL_CASE : Kind.CASE_INSENSITIVE_PREFIX);
Model model = request.result.getModel();
Set<ClassElement> classes = request.index.getClasses(nameQuery, ModelUtils.getAliasedNames(model, request.anchor), Trait.ALIAS);
if (!classes.isEmpty()) {
completionResult.setFilterable(false);
}
for (ClassElement clazz : classes) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
if (!clazz.isAnonymous()) {
completionResult.add(new PHPCompletionItem.ClassItem(clazz, request, endWithDoubleColon, kind));
}
}
}
private void autoCompleteInterfaceNames(final PHPCompletionResult completionResult, PHPCompletionItem.CompletionRequest request) {
autoCompleteInterfaceNames(completionResult, request, null);
}
private void autoCompleteInterfaceNames(final PHPCompletionResult completionResult, PHPCompletionItem.CompletionRequest request, QualifiedNameKind kind) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
final boolean isCamelCase = isCamelCaseForTypeNames(request.prefix);
final NameKind nameQuery = NameKind.create(request.prefix,
isCamelCase ? Kind.CAMEL_CASE : Kind.CASE_INSENSITIVE_PREFIX);
Model model = request.result.getModel();
Set<InterfaceElement> interfaces = request.index.getInterfaces(nameQuery, ModelUtils.getAliasedNames(model, request.anchor), Trait.ALIAS);
if (!interfaces.isEmpty()) {
completionResult.setFilterable(false);
}
for (InterfaceElement iface : interfaces) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
completionResult.add(new PHPCompletionItem.InterfaceItem(iface, request, kind, false));
}
}
private void autoCompleteTypeNames(final PHPCompletionResult completionResult, PHPCompletionItem.CompletionRequest request) {
autoCompleteTypeNames(completionResult, request, null, false);
}
private void autoCompleteAfterUseTrait(final PHPCompletionResult completionResult, final PHPCompletionItem.CompletionRequest request, final QualifiedNameKind kind) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
Set<NamespaceElement> namespaces = request.index.getNamespaces(
NameKind.caseInsensitivePrefix(QualifiedName.create(request.prefix).toNotFullyQualified()));
for (NamespaceElement namespace : namespaces) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
completionResult.add(new PHPCompletionItem.NamespaceItem(namespace, request, QualifiedNameKind.FULLYQUALIFIED));
}
final NameKind nameQuery = NameKind.caseInsensitivePrefix(request.prefix);
for (TraitElement trait : request.index.getTraits(nameQuery)) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
completionResult.add(new PHPCompletionItem.TraitItem(trait, request));
}
}
private void autoCompleteGroupUse(UseType useType, PHPCompletionResult completionResult, CompletionRequest request) {
assert request.extraPrefix != null;
if (CancelSupport.getDefault().isCancelled()) {
return;
}
request.insertOnlyMethodsName = true;
// we will "complete" FQN so handle search prefix as well
if (!request.extraPrefix.startsWith("\\")) { // NOI18N
request.extraPrefix = "\\" + request.extraPrefix; // NOI18N
}
final String prefix = request.extraPrefix + request.prefix;
Set<NamespaceElement> namespaces = request.index.getNamespaces(
NameKind.caseInsensitivePrefix(QualifiedName.create(prefix).toNotFullyQualified()));
for (NamespaceElement namespace : namespaces) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
completionResult.add(new PHPCompletionItem.NamespaceItem(namespace, request, QualifiedNameKind.FULLYQUALIFIED));
}
final NameKind nameQuery = NameKind.caseInsensitivePrefix(prefix);
switch (useType) {
case TYPE:
for (ClassElement clazz : request.index.getClasses(nameQuery)) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
completionResult.add(new PHPCompletionItem.ClassItem(clazz, request, false, QualifiedNameKind.FULLYQUALIFIED));
}
for (InterfaceElement iface : request.index.getInterfaces(nameQuery)) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
completionResult.add(new PHPCompletionItem.InterfaceItem(iface, request, QualifiedNameKind.FULLYQUALIFIED, false));
}
// NETBEANS-4650
for (TraitElement trait : request.index.getTraits(nameQuery)) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
completionResult.add(new PHPCompletionItem.TraitItem(trait, request));
}
break;
case CONST:
for (ConstantElement constant : request.index.getConstants(nameQuery)) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
completionResult.add(new PHPCompletionItem.ConstantItem(constant, request, QualifiedNameKind.FULLYQUALIFIED));
}
break;
case FUNCTION:
for (FunctionElement function : request.index.getFunctions(nameQuery)) {
for (PHPCompletionItem.FunctionElementItem item : PHPCompletionItem.FunctionElementItem.getItems(function, request, QualifiedNameKind.FULLYQUALIFIED)) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
completionResult.add(item);
}
}
break;
default:
assert false : "Unknown use type: " + useType;
}
}
private void autoCompleteAfterUse(
UseType useType,
final PHPCompletionResult completionResult,
PHPCompletionItem.CompletionRequest request,
QualifiedNameKind kind) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
Set<NamespaceElement> namespaces = request.index.getNamespaces(
NameKind.caseInsensitivePrefix(QualifiedName.create(request.prefix).toNotFullyQualified()));
for (NamespaceElement namespace : namespaces) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
completionResult.add(new PHPCompletionItem.NamespaceItem(namespace, request, kind));
}
final NameKind nameQuery = NameKind.caseInsensitivePrefix(request.prefix);
switch (useType) {
case TYPE:
for (ClassElement clazz : request.index.getClasses(nameQuery)) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
completionResult.add(new PHPCompletionItem.ClassItem(clazz, request, false, kind));
}
for (InterfaceElement iface : request.index.getInterfaces(nameQuery)) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
completionResult.add(new PHPCompletionItem.InterfaceItem(iface, request, kind, false));
}
// NETBEANS-4650
for (TraitElement trait : request.index.getTraits(nameQuery)) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
completionResult.add(new PHPCompletionItem.TraitItem(trait, request));
}
break;
case CONST:
for (ConstantElement constant : request.index.getConstants(nameQuery)) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
completionResult.add(new PHPCompletionItem.ConstantItem(constant, request));
}
break;
case FUNCTION:
for (FunctionElement function : request.index.getFunctions(nameQuery)) {
List<PHPCompletionItem.FunctionElementItem> items = PHPCompletionItem.FunctionElementItem.getItems(function, request);
for (PHPCompletionItem.FunctionElementItem item : items) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
completionResult.add(item);
}
}
break;
default:
assert false : "Unknown use type: " + useType;
}
}
private void autoCompleteTypeNames(
final PHPCompletionResult completionResult,
PHPCompletionItem.CompletionRequest request,
QualifiedNameKind kind,
boolean endWithDoubleColon) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
if (request.prefix.trim().length() > 0) {
autoCompleteClassNames(completionResult, request, endWithDoubleColon, kind);
autoCompleteInterfaceNames(completionResult, request, kind);
} else {
Model model = request.result.getModel();
Set<AliasedName> aliasedNames = ModelUtils.getAliasedNames(model, request.anchor);
Collection<PhpElement> allTopLevel = request.index.getTopLevelElements(NameKind.empty(), aliasedNames, Trait.ALIAS);
for (PhpElement element : allTopLevel) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
if (element instanceof ClassElement) {
ClassElement classElement = (ClassElement) element;
if (!classElement.isAnonymous()) {
completionResult.add(new PHPCompletionItem.ClassItem(classElement, request, endWithDoubleColon, kind));
}
} else if (element instanceof InterfaceElement) {
completionResult.add(new PHPCompletionItem.InterfaceItem((InterfaceElement) element, request, kind, endWithDoubleColon));
}
}
}
}
private void autoCompleteKeywords(final PHPCompletionResult completionResult,
PHPCompletionItem.CompletionRequest request, List<String> keywordList) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
for (String keyword : keywordList) {
if (keyword.startsWith(request.prefix)) {
completionResult.add(new PHPCompletionItem.KeywordItem(keyword, request));
}
}
}
private void autoCompleteKeywordsInPHPDoc(final PHPCompletionResult completionResult,
PHPCompletionItem.CompletionRequest request) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
BaseDocument doc = (BaseDocument) request.info.getSnapshot().getSource().getDocument(false);
if (doc == null) {
return;
}
try {
int start = request.anchor - 1;
if (start >= 0) {
String prefix = doc.getText(start, 1);
if (CodeUtils.NULLABLE_TYPE_PREFIX.equals(prefix)) {
List<String> keywords = new ArrayList<>(Type.getTypesForEditor());
keywords.remove(Type.FALSE);
keywords.remove(Type.NULL);
autoCompleteKeywords(completionResult, request, keywords);
} else {
autoCompleteKeywords(completionResult, request, Type.getTypesForPhpDoc());
}
}
} catch (BadLocationException ex) {
LOGGER.log(Level.WARNING, "Incorrect offset for the nullable type prefix: {0}", ex.offsetRequested()); // NOI18N
}
}
private void autoCompleteNamespaces(final PHPCompletionResult completionResult,
PHPCompletionItem.CompletionRequest request) {
autoCompleteNamespaces(completionResult, request, null);
}
private void autoCompleteNamespaces(final PHPCompletionResult completionResult,
PHPCompletionItem.CompletionRequest request, QualifiedNameKind kind) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
final QualifiedName prefix = QualifiedName.create(request.prefix).toNotFullyQualified();
Model model = request.result.getModel();
Set<NamespaceElement> namespaces = request.index.getNamespaces(NameKind.caseInsensitivePrefix(prefix),
ModelUtils.getAliasedNames(model, request.anchor), Trait.ALIAS);
for (NamespaceElement namespace : namespaces) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
completionResult.add(new PHPCompletionItem.NamespaceItem(namespace, request, kind));
}
}
private void autoCompleteInInterfaceContext(final PHPCompletionResult completionResult, final PHPCompletionItem.CompletionRequest request) {
autoCompleteKeywords(completionResult, request, INTERFACE_CONTEXT_KEYWORD_PROPOSAL);
}
private void autoCompleteInClassContext(
ParserResult info,
int caretOffset,
final PHPCompletionResult completionResult,
PHPCompletionItem.CompletionRequest request) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
TokenHierarchy<?> th = info.getSnapshot().getTokenHierarchy();
TokenSequence<PHPTokenId> tokenSequence = th.tokenSequence(PHPTokenId.language());
assert tokenSequence != null;
autoCompleteKeywords(completionResult, request, CLASS_CONTEXT_KEYWORD_PROPOSAL);
if (offerMagicAndInherited(tokenSequence, caretOffset, th)) {
EnclosingClass enclosingClass = findEnclosingClass(info, lexerToASTOffset(info, caretOffset));
if (enclosingClass != null) {
List<ElementFilter> superTypeIndices = createTypeFilter(enclosingClass);
String clsName = enclosingClass.getClassName();
NamespaceScope namespaceScope = ModelUtils.getNamespaceScope(request.result.getModel().getFileScope(), request.anchor);
String fullyQualifiedClassName = VariousUtils.qualifyTypeNames(clsName, request.anchor, namespaceScope);
if (fullyQualifiedClassName != null) {
final FileObject fileObject = request.result.getSnapshot().getSource().getFileObject();
final ElementFilter classFilter = ElementFilter.allOf(
ElementFilter.forFiles(fileObject), ElementFilter.allOf(superTypeIndices));
Set<ClassElement> classes = classFilter.filter(request.index.getClasses(NameKind.exact(fullyQualifiedClassName)));
for (ClassElement classElement : classes) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
ElementFilter methodFilter = ElementFilter.allOf(
ElementFilter.forExcludedNames(toNames(request.index.getDeclaredMethods(classElement)), PhpElementKind.METHOD),
ElementFilter.forName(NameKind.caseInsensitivePrefix(QualifiedName.create(request.prefix))));
Set<MethodElement> accessibleMethods = methodFilter.filter(request.index.getAccessibleMethods(classElement, classElement));
for (MethodElement method : accessibleMethods) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
if (!method.isFinal()) {
completionResult.add(PHPCompletionItem.MethodDeclarationItem.getDeclarationItem(method, request));
}
}
Set<MethodElement> magicMethods = methodFilter.filter(request.index.getAccessibleMagicMethods(classElement));
for (MethodElement magicMethod : magicMethods) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
if (magicMethod != null) {
completionResult.add(PHPCompletionItem.MethodDeclarationItem.getDeclarationItem(magicMethod, request));
}
}
break;
}
}
}
} else if (completeFieldTypes(tokenSequence, caretOffset, th, info.getSnapshot().getSource().getFileObject())){
autoCompleteFieldType(info, caretOffset, completionResult, request, true);
}
}
private void autoCompleteFieldType(ParserResult info, int caretOffset, final PHPCompletionResult completionResult, CompletionRequest request, boolean isInClassContext) {
// PHP 7.4 Typed Properties 2.0
// https://wiki.php.net/rfc/typed_properties_v2
autoCompleteNamespaces(completionResult, request);
autoCompleteTypeNames(completionResult, request);
List<String> keywords = new ArrayList<>(Type.getTypesForFieldType());
boolean isNullableType = isNullableType(info, caretOffset);
if (!isInClassContext && !isNullableType) {
// e.g. private stat^
TokenHierarchy<?> th = info.getSnapshot().getTokenHierarchy();
TokenSequence<PHPTokenId> tokenSequence = th.tokenSequence(PHPTokenId.language());
assert tokenSequence != null;
tokenSequence.move(caretOffset);
boolean addStaticKeyword = false;
boolean addReadonlyKeyword = false;
boolean addVisibilityKeyword = false;
if (!(!tokenSequence.moveNext() && !tokenSequence.movePrevious())) {
Token<PHPTokenId> token = tokenSequence.token();
int tokenIdOffset = tokenSequence.token().offset(th);
addStaticKeyword = !CompletionContextFinder.lineContainsAny(token, caretOffset - tokenIdOffset, tokenSequence, Arrays.asList(
PHPTokenId.PHP_STATIC,
PHPTokenId.PHP_READONLY,
PHPTokenId.PHP_OPERATOR // "|"
));
addReadonlyKeyword = !CompletionContextFinder.lineContainsAny(token, caretOffset - tokenIdOffset, tokenSequence, Arrays.asList(
PHPTokenId.PHP_READONLY,
PHPTokenId.PHP_OPERATOR // "|"
));
addVisibilityKeyword = !CompletionContextFinder.lineContainsAny(token, caretOffset - tokenIdOffset, tokenSequence, Arrays.asList(
PHPTokenId.PHP_PUBLIC,
PHPTokenId.PHP_PRIVATE,
PHPTokenId.PHP_PROTECTED,
PHPTokenId.PHP_OPERATOR // "|"
));
}
if (addStaticKeyword) {
keywords.add("static"); // NOI18N
}
if (addReadonlyKeyword) {
keywords.add("readonly"); // NOI18N
}
if (addVisibilityKeyword) {
keywords.addAll(PHP_VISIBILITY_KEYWORDS);
}
}
if (isNullableType) {
keywords.remove(Type.FALSE);
keywords.remove(Type.NULL);
}
if (isUnionType(info, caretOffset)) {
keywords.remove(Type.MIXED);
}
autoCompleteKeywords(completionResult, request, keywords);
}
private boolean offerMagicAndInherited(TokenSequence<PHPTokenId> tokenSequence, int caretOffset, TokenHierarchy<?> th) {
boolean offerMagicAndInherited = true;
tokenSequence.move(caretOffset);
if (!(!tokenSequence.moveNext() && !tokenSequence.movePrevious())) {
Token<PHPTokenId> token = tokenSequence.token();
int tokenIdOffset = tokenSequence.token().offset(th);
offerMagicAndInherited = !CompletionContextFinder.lineContainsAny(token, caretOffset - tokenIdOffset, tokenSequence, Arrays.asList(
PHPTokenId.PHP_PRIVATE,
PHPTokenId.PHP_PUBLIC,
PHPTokenId.PHP_PROTECTED,
PHPTokenId.PHP_ABSTRACT,
PHPTokenId.PHP_VAR,
PHPTokenId.PHP_STATIC,
PHPTokenId.PHP_CONST,
PHPTokenId.PHP_READONLY
));
}
return offerMagicAndInherited;
}
private boolean completeFieldTypes(TokenSequence<PHPTokenId> tokenSequence, int caretOffset, TokenHierarchy<?> th, FileObject fileObject) {
if (!isPhp74OrNewer(fileObject)) {
return false;
}
// e.g. private static s^tring|int $field; private bool ^$bool;
boolean completeTypes = false;
tokenSequence.move(caretOffset);
if (!(!tokenSequence.moveNext() && !tokenSequence.movePrevious())) {
Token<PHPTokenId> token = tokenSequence.token();
int tokenIdOffset = tokenSequence.token().offset(th);
completeTypes = !CompletionContextFinder.lineContainsAny(token, caretOffset - tokenIdOffset, tokenSequence, Arrays.asList(
PHPTokenId.PHP_TYPE_BOOL,
PHPTokenId.PHP_TYPE_INT,
PHPTokenId.PHP_TYPE_FLOAT,
PHPTokenId.PHP_TYPE_STRING,
PHPTokenId.PHP_ARRAY,
PHPTokenId.PHP_TYPE_OBJECT,
PHPTokenId.PHP_ITERABLE,
PHPTokenId.PHP_SELF,
PHPTokenId.PHP_PARENT,
PHPTokenId.PHP_FALSE,
PHPTokenId.PHP_NULL,
PHPTokenId.PHP_STRING,
PHPTokenId.PHP_CONST
));
}
return completeTypes;
}
private static Set<String> toNames(Set<? extends PhpElement> elements) {
Set<String> names = new HashSet<>();
for (PhpElement elem : elements) {
names.add(elem.getName());
}
return names;
}
private void autoCompleteClassMembers(
final PHPCompletionResult completionResult,
PHPCompletionItem.CompletionRequest request,
boolean staticContext) {
autoCompleteClassMembers(completionResult, request, staticContext, false);
}
private void autoCompleteClassMembers(
final PHPCompletionResult completionResult,
PHPCompletionItem.CompletionRequest request,
boolean staticContext,
boolean completeAccessPrefix) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
// TODO: remove duplicate/redundant code from here
TokenHierarchy<?> th = request.info.getSnapshot().getTokenHierarchy();
TokenSequence<PHPTokenId> tokenSequence = LexUtilities.getPHPTokenSequence(th, request.anchor);
if (tokenSequence == null) {
return;
}
tokenSequence.move(request.anchor);
if (tokenSequence.movePrevious()) {
boolean instanceContext = !staticContext;
if (tokenSequence.token().id() != PHPTokenId.PHP_PAAMAYIM_NEKUDOTAYIM
&& tokenSequence.token().id() != PHPTokenId.PHP_OBJECT_OPERATOR
&& tokenSequence.token().id() != PHPTokenId.PHP_NULLSAFE_OBJECT_OPERATOR) {
tokenSequence.movePrevious();
}
tokenSequence.movePrevious();
if (tokenSequence.token().id() == PHPTokenId.WHITESPACE) {
tokenSequence.movePrevious();
}
final CharSequence varName = tokenSequence.token().text();
tokenSequence.moveNext();
List<String> invalidProposalsForClsMembers = INVALID_PROPOSALS_FOR_CLS_MEMBERS;
Model model = request.result.getModel();
boolean parentContext = false;
boolean selfContext = false;
boolean staticLateBindingContext = false;
boolean specialVariable = false;
if (TokenUtilities.textEquals(varName, "$this")) { // NOI18N
specialVariable = true;
} else if (TokenUtilities.textEquals(varName, "self")) { // NOI18N
staticContext = true;
selfContext = true;
specialVariable = true;
} else if (TokenUtilities.textEquals(varName, "parent")) { // NOI18N
invalidProposalsForClsMembers = Collections.emptyList();
staticContext = true;
instanceContext = true;
specialVariable = true;
parentContext = true;
} else if (TokenUtilities.textEquals(varName, "static")) { // NOI18N
staticContext = true;
instanceContext = false;
staticLateBindingContext = true;
specialVariable = true;
}
Collection<? extends TypeScope> types = ModelUtils.resolveTypeAfterReferenceToken(model, tokenSequence, request.anchor, specialVariable);
if (types != null) {
TypeElement enclosingType = getEnclosingType(request, types);
if (completeAccessPrefix) {
// NETBEANS-1855
types = ModelUtils.resolveType(model, request.anchor);
}
Set<PhpElement> duplicateElementCheck = new HashSet<>();
for (TypeScope typeScope : types) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
final ElementFilter staticFlagFilter = !completeAccessPrefix
? new StaticOrInstanceMembersFilter(staticContext, instanceContext, selfContext, staticLateBindingContext, parentContext)
: new ElementFilter() { // NETBEANS-1855
@Override
public boolean isAccepted(PhpElement element) {
return true;
}
};
final ElementFilter methodsFilter = ElementFilter.allOf(
ElementFilter.forKind(PhpElementKind.METHOD),
ElementFilter.forName(NameKind.caseInsensitivePrefix(request.prefix)),
staticFlagFilter,
ElementFilter.forExcludedNames(invalidProposalsForClsMembers, PhpElementKind.METHOD),
ElementFilter.forInstanceOf(MethodElement.class));
final ElementFilter fieldsFilter = ElementFilter.allOf(
ElementFilter.forKind(PhpElementKind.FIELD),
ElementFilter.forName(NameKind.caseInsensitivePrefix(request.prefix)),
staticFlagFilter,
ElementFilter.forInstanceOf(FieldElement.class));
final ElementFilter constantsFilter = ElementFilter.allOf(
ElementFilter.forKind(PhpElementKind.TYPE_CONSTANT),
ElementFilter.forName(NameKind.caseInsensitivePrefix(request.prefix)),
ElementFilter.forInstanceOf(TypeConstantElement.class));
HashSet<TypeMemberElement> accessibleTypeMembers = new HashSet<>();
accessibleTypeMembers.addAll(request.index.getAccessibleTypeMembers(typeScope, enclosingType));
// for @mixin tag #241740
if (typeScope instanceof ClassElement) {
ClassElement classElement = (ClassElement) typeScope;
if (!classElement.getFQMixinClassNames().isEmpty()) {
// XXX currently, only when mixins are used directly in the class. should support all cases?
accessibleTypeMembers.addAll(request.index.getAccessibleMixinTypeMembers(typeScope, enclosingType));
}
}
for (final PhpElement phpElement : accessibleTypeMembers) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
if (duplicateElementCheck.add(phpElement)) {
if (methodsFilter.isAccepted(phpElement)) {
MethodElement method = (MethodElement) phpElement;
List<MethodElementItem> items = PHPCompletionItem.MethodElementItem.getItems(method, request, completeAccessPrefix);
for (MethodElementItem methodItem : items) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
completionResult.add(methodItem);
}
} else if (fieldsFilter.isAccepted(phpElement)) {
FieldElement field = (FieldElement) phpElement;
FieldItem fieldItem = PHPCompletionItem.FieldItem.getItem(field, request, false, completeAccessPrefix);
completionResult.add(fieldItem);
} else if ((staticContext || completeAccessPrefix) && constantsFilter.isAccepted(phpElement)) {
TypeConstantElement constant = (TypeConstantElement) phpElement;
TypeConstantItem constantItem = PHPCompletionItem.TypeConstantItem.getItem(constant, request, completeAccessPrefix);
completionResult.add(constantItem);
}
}
}
if (staticContext) {
Set<TypeConstantElement> magicConstants = constantsFilter.filter(request.index.getAccessibleMagicConstants(typeScope));
for (TypeConstantElement magicConstant : magicConstants) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
if (magicConstant != null) {
// NETBEANS-4443
// PHP 8.0 allows ::class on objects (e.g. $instance::class, create()::class)
// so don't restrict dynamic access any more
// https://wiki.php.net/rfc/class_name_literal_on_object
completionResult.add(PHPCompletionItem.TypeConstantItem.getItem(magicConstant, request));
}
}
}
}
}
}
}
private void autoCompleteClassMethodParameterName(
final PHPCompletionResult completionResult,
PHPCompletionItem.CompletionRequest request,
boolean staticContext
) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
TokenHierarchy<?> th = request.info.getSnapshot().getTokenHierarchy();
TokenSequence<PHPTokenId> tokenSequence = LexUtilities.getPHPTokenSequence(th, request.anchor);
if (tokenSequence == null) {
return;
}
Token<? extends PHPTokenId> functionName = CompletionContextFinder.findFunctionInvocationName(tokenSequence, request.anchor);
if (functionName != null) {
int originalAnchor = request.anchor;
try {
request.anchor = tokenSequence.offset();
boolean isInstanceContext = !staticContext;
boolean isStaticContext = staticContext;
if (tokenSequence.token().id() != PHPTokenId.PHP_PAAMAYIM_NEKUDOTAYIM
&& tokenSequence.token().id() != PHPTokenId.PHP_OBJECT_OPERATOR
&& tokenSequence.token().id() != PHPTokenId.PHP_NULLSAFE_OBJECT_OPERATOR) {
tokenSequence.movePrevious();
}
tokenSequence.movePrevious();
if (tokenSequence.token().id() == PHPTokenId.WHITESPACE) {
tokenSequence.movePrevious();
}
final CharSequence varName = tokenSequence.token().text();
tokenSequence.moveNext();
List<String> invalidProposalsForClsMembers = INVALID_PROPOSALS_FOR_CLS_MEMBERS;
Model model = request.result.getModel();
boolean parentContext = false;
boolean selfContext = false;
boolean staticLateBindingContext = false;
boolean specialVariable = false;
if (TokenUtilities.textEquals(varName, "$this")) { // NOI18N
specialVariable = true;
} else if (TokenUtilities.textEquals(varName, "self")) { // NOI18N
isStaticContext = true;
selfContext = true;
specialVariable = true;
} else if (TokenUtilities.textEquals(varName, "parent")) { // NOI18N
invalidProposalsForClsMembers = Collections.emptyList();
isStaticContext = true;
isInstanceContext = true;
specialVariable = true;
parentContext = true;
} else if (TokenUtilities.textEquals(varName, "static")) { // NOI18N
isStaticContext = true;
isInstanceContext = false;
staticLateBindingContext = true;
specialVariable = true;
}
Collection<? extends TypeScope> types = ModelUtils.resolveTypeAfterReferenceToken(model, tokenSequence, request.anchor, specialVariable);
TypeElement enclosingType = getEnclosingType(request, types);
Set<PhpElement> duplicateElementCheck = new HashSet<>();
for (TypeScope typeScope : types) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
final ElementFilter staticFlagFilter = new StaticOrInstanceMembersFilter(isStaticContext, isInstanceContext, selfContext, staticLateBindingContext, true);
final ElementFilter methodsFilter = ElementFilter.allOf(
ElementFilter.forKind(PhpElementKind.METHOD),
ElementFilter.forName(NameKind.exact(functionName.text().toString())),
staticFlagFilter,
ElementFilter.forExcludedNames(invalidProposalsForClsMembers, PhpElementKind.METHOD),
ElementFilter.forInstanceOf(MethodElement.class));
HashSet<TypeMemberElement> accessibleTypeMembers = new HashSet<>();
accessibleTypeMembers.addAll(request.index.getAccessibleTypeMembers(typeScope, enclosingType));
if (typeScope instanceof ClassElement) {
ClassElement classElement = (ClassElement) typeScope;
if (!classElement.getFQMixinClassNames().isEmpty()) {
accessibleTypeMembers.addAll(request.index.getAccessibleMixinTypeMembers(typeScope, enclosingType));
}
}
for (final PhpElement phpElement : accessibleTypeMembers) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
if (duplicateElementCheck.add(phpElement)) {
if (methodsFilter.isAccepted(phpElement)) {
MethodElement method = (MethodElement) phpElement;
for (ParameterElement parameter : method.getParameters()) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
String name = parameter.getName();
if (name != null) {
name = name.substring(1);
}
if (name != null
&& name.startsWith(request.prefix)) {
completionResult.add(new PHPCompletionItem.ParameterNameItem(parameter, request));
}
}
}
}
}
}
} finally {
request.anchor = originalAnchor;
}
}
}
private void autoCompleteFunctionParameterName(final PHPCompletionResult completionResult, PHPCompletionItem.CompletionRequest request) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
TokenHierarchy<?> th = request.info.getSnapshot().getTokenHierarchy();
TokenSequence<PHPTokenId> tokenSequence = LexUtilities.getPHPTokenSequence(th, request.anchor);
if (tokenSequence == null) {
return;
}
Token<? extends PHPTokenId> functionName = CompletionContextFinder.findFunctionInvocationName(tokenSequence, request.anchor);
if (functionName != null) {
Set<PhpElement> elements = request.index.getTopLevelElements(NameKind.exact(functionName.text().toString()));
// usually, php doesn't have the same name functions
// but just check duplicate name
Set<String> duplicateCheck = new HashSet<>();
for (PhpElement element : elements) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
if (element instanceof FunctionElement) {
FunctionElement functionElement = (FunctionElement) element;
if (functionElement.isAnonymous()) {
continue;
}
for (ParameterElement parameter : functionElement.getParameters()) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
String name = parameter.getName();
if (name != null) {
name = name.substring(1);
}
if (name != null
&& name.startsWith(request.prefix)
&& duplicateCheck.add(name)) {
completionResult.add(new PHPCompletionItem.ParameterNameItem(parameter, request));
}
}
}
}
}
}
private void autoCompleteClassConstants(final PHPCompletionResult completionResult, final PHPCompletionItem.CompletionRequest request) {
// NETBANS-1855
// complete access prefix i.e. add "self::" to the top of constant names
if (CancelSupport.getDefault().isCancelled()) {
return;
}
final ElementFilter constantsFilter = ElementFilter.allOf(
ElementFilter.forKind(PhpElementKind.TYPE_CONSTANT),
ElementFilter.forName(NameKind.caseInsensitivePrefix(request.prefix)),
ElementFilter.forInstanceOf(TypeConstantElement.class)
);
Model model = request.result.getModel();
Collection<? extends TypeScope> types = ModelUtils.resolveType(model, request.anchor);
TypeElement enclosingType = getEnclosingType(request, types);
for (TypeScope typeScope : types) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
for (final PhpElement phpElement : request.index.getAccessibleTypeMembers(typeScope, enclosingType)) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
if (constantsFilter.isAccepted(phpElement)) {
TypeConstantElement constant = (TypeConstantElement) phpElement;
TypeConstantItem constantItem = PHPCompletionItem.TypeConstantItem.getItem(constant, request, true);
completionResult.add(constantItem);
}
}
}
}
private void autoCompleteClassFields(final PHPCompletionResult completionResult, final PHPCompletionItem.CompletionRequest request) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
TokenHierarchy<?> th = request.info.getSnapshot().getTokenHierarchy();
TokenSequence<PHPTokenId> tokenSequence = LexUtilities.getPHPTokenSequence(th, request.anchor);
Model model = request.result.getModel();
Collection<? extends TypeScope> types = ModelUtils.resolveTypeAfterReferenceToken(model, tokenSequence, request.anchor, false);
final ElementFilter fieldsFilter = ElementFilter.allOf(
ElementFilter.forKind(PhpElementKind.FIELD),
ElementFilter.forName(NameKind.caseInsensitivePrefix(request.prefix)),
ElementFilter.forInstanceOf(FieldElement.class));
if (types != null) {
TypeElement enclosingType = getEnclosingType(request, types);
for (TypeScope typeScope : types) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
for (final PhpElement phpElement : request.index.getAccessibleTypeMembers(typeScope, enclosingType)) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
if (fieldsFilter.isAccepted(phpElement)) {
FieldElement field = (FieldElement) phpElement;
FieldItem fieldItem = PHPCompletionItem.FieldItem.getItem(field, request);
completionResult.add(fieldItem);
}
}
}
}
}
@CheckForNull
private TypeElement getEnclosingType(CompletionRequest request, Collection<? extends TypeScope> types) {
final EnclosingType enclosingType = findEnclosingType(request.info, lexerToASTOffset(request.result, request.anchor));
final String enclosingTypeName = enclosingType != null ? enclosingType.extractTypeName() : null;
NamespaceScope namespaceScope = ModelUtils.getNamespaceScope(request.result.getModel().getFileScope(), request.anchor);
final String enclosingFQTypeName = VariousUtils.qualifyTypeNames(enclosingTypeName, request.anchor, namespaceScope);
final NameKind enclosingTypeNameKind = (enclosingFQTypeName != null && !enclosingFQTypeName.trim().isEmpty()) ? NameKind.exact(enclosingFQTypeName) : null;
Set<FileObject> preferedFileObjects = new HashSet<>();
Set<TypeElement> enclosingTypes = null;
FileObject currentFile = request.result.getSnapshot().getSource().getFileObject();
if (currentFile != null) {
preferedFileObjects.add(currentFile);
}
for (TypeScope typeScope : types) {
final FileObject fileObject = typeScope.getFileObject();
if (fileObject != null) {
preferedFileObjects.add(fileObject);
}
if (enclosingTypeNameKind != null && enclosingTypes == null) {
if (enclosingTypeNameKind.matchesName(typeScope)) {
enclosingTypes = Collections.<TypeElement>singleton((TypeElement) typeScope);
}
}
}
if (enclosingTypeNameKind != null && enclosingTypes == null) {
final ElementFilter forFiles = ElementFilter.forFiles(preferedFileObjects.toArray(new FileObject[preferedFileObjects.size()]));
Set<TypeElement> indexTypes = forFiles.prefer(request.index.getTypes(enclosingTypeNameKind));
if (!indexTypes.isEmpty()) {
enclosingTypes = new HashSet<>(indexTypes);
}
}
return (enclosingTypes == null || enclosingTypes.isEmpty()) ? null : enclosingTypes.iterator().next();
}
private static boolean isNullableType(ParserResult info, int caretOffset) {
TokenHierarchy<?> th = info.getSnapshot().getTokenHierarchy();
TokenSequence<PHPTokenId> tokenSequence = th.tokenSequence(PHPTokenId.language());
assert tokenSequence != null;
tokenSequence.move(caretOffset);
if (tokenSequence.movePrevious()) {
Token<? extends PHPTokenId> previousToken = LexUtilities.findPrevious(tokenSequence, Arrays.asList(PHPTokenId.WHITESPACE, PHPTokenId.PHP_STRING));
if (previousToken.id() == PHPTokenId.PHP_TOKEN && TokenUtilities.textEquals(previousToken.text(), "?")) { // NOI18N
return true;
}
}
return false;
}
private static boolean isUnionType(ParserResult info, int caretOffset) {
TokenHierarchy<?> th = info.getSnapshot().getTokenHierarchy();
TokenSequence<PHPTokenId> tokenSequence = th.tokenSequence(PHPTokenId.language());
assert tokenSequence != null;
tokenSequence.move(caretOffset);
if (tokenSequence.movePrevious()) {
Token<? extends PHPTokenId> previousToken = LexUtilities.findPrevious(tokenSequence, VALID_UNION_TYPE_TOKENS);
if (previousToken.id() == PHPTokenId.PHP_OPERATOR && TokenUtilities.textEquals(previousToken.text(), Type.SEPARATOR)) {
return true;
}
}
return false;
}
private static boolean isInType(CompletionRequest request) {
return findEnclosingType(request.info, lexerToASTOffset(request.result, request.anchor)) != null;
}
@CheckForNull
private static EnclosingType findEnclosingType(ParserResult info, int offset) {
List<ASTNode> nodes = NavUtils.underCaret(info, offset);
for (int i = nodes.size() - 1; i >= 0; i--) {
ASTNode node = nodes.get(i);
if (node instanceof TypeDeclaration
&& node.getEndOffset() > offset) {
return EnclosingType.forTypeDeclaration((TypeDeclaration) node);
}
if (node instanceof ClassInstanceCreation
&& node.getEndOffset() > offset) {
ClassInstanceCreation classInstanceCreation = (ClassInstanceCreation) node;
if (classInstanceCreation.isAnonymous()) {
Block body = classInstanceCreation.getBody();
if (body != null
&& body.getStartOffset() <= offset
&& body.getEndOffset() >= offset) {
return EnclosingType.forClassInstanceCreation(classInstanceCreation);
}
}
}
}
return null;
}
@CheckForNull
private static EnclosingClass findEnclosingClass(ParserResult info, int offset) {
List<ASTNode> nodes = NavUtils.underCaret(info, offset);
for (int i = nodes.size() - 1; i >= 0; i--) {
ASTNode node = nodes.get(i);
if (node instanceof ClassDeclaration
&& node.getEndOffset() > offset) {
return EnclosingClass.forClassDeclaration((ClassDeclaration) node);
}
if (node instanceof ClassInstanceCreation
&& node.getEndOffset() > offset) {
ClassInstanceCreation classInstanceCreation = (ClassInstanceCreation) node;
if (classInstanceCreation.isAnonymous()) {
Block body = classInstanceCreation.getBody();
if (body != null
&& body.getStartOffset() <= offset
&& body.getEndOffset() >= offset) {
return EnclosingClass.forClassInstanceCreation((ClassInstanceCreation) node);
}
}
}
}
return null;
}
private void autoCompleteExpression(final PHPCompletionResult completionResult, PHPCompletionItem.CompletionRequest request) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
autoCompleteNamespaces(completionResult, request);
List<String> defaultKeywords = new ArrayList<>(PHP_KEYWORDS.keySet());
defaultKeywords.remove("default =>"); // NOI18N
autoCompleteExpression(completionResult, request, defaultKeywords);
}
private void autoCompleteExpression(final PHPCompletionResult completionResult, PHPCompletionItem.CompletionRequest request, List<String> keywords) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
// KEYWORDS
for (String keyword : keywords) {
if (startsWith(keyword, request.prefix)) {
completionResult.add(new PHPCompletionItem.KeywordItem(keyword, request));
}
}
for (String keyword : PHP_LANGUAGE_CONSTRUCTS_WITH_QUOTES) {
if (startsWith(keyword, request.prefix)) {
completionResult.add(new PHPCompletionItem.LanguageConstructWithQuotesItem(keyword, request));
}
}
for (String construct : PHP_LANGUAGE_CONSTRUCTS_WITH_PARENTHESES) {
if (startsWith(construct, request.prefix)) {
completionResult.add(new PHPCompletionItem.LanguageConstructWithParenthesesItem(construct, request));
}
}
for (String construct : PHP_LANGUAGE_CONSTRUCTS_WITH_SEMICOLON) {
if (startsWith(construct, request.prefix)) {
completionResult.add(new PHPCompletionItem.LanguageConstructWithSemicolonItem(construct, request));
}
}
final boolean offerGlobalVariables = OptionsUtils.codeCompletionVariablesScope().equals(VariablesScope.ALL);
final boolean isCamelCase = isCamelCaseForTypeNames(request.prefix);
final NameKind prefix = NameKind.create(request.prefix,
isCamelCase ? Kind.CAMEL_CASE : Kind.CASE_INSENSITIVE_PREFIX);
final Set<VariableElement> globalVariables = new HashSet<>();
Model model = request.result.getModel();
Set<AliasedName> aliasedNames = ModelUtils.getAliasedNames(model, request.anchor);
for (final PhpElement element : request.index.getTopLevelElements(prefix, aliasedNames, Trait.ALIAS)) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
if (element instanceof FunctionElement) {
FunctionElement functionElement = (FunctionElement) element;
if (!functionElement.isAnonymous()) {
for (final PHPCompletionItem.FunctionElementItem functionItem
: PHPCompletionItem.FunctionElementItem.getItems(functionElement, request)) {
completionResult.add(functionItem);
}
}
} else if (element instanceof ClassElement) {
ClassElement classElement = (ClassElement) element;
if (!classElement.isAnonymous()) {
completionResult.add(new PHPCompletionItem.ClassItem(classElement, request, true, null));
}
} else if (element instanceof InterfaceElement) {
completionResult.add(new PHPCompletionItem.InterfaceItem((InterfaceElement) element, request, true));
} else if (offerGlobalVariables && element instanceof VariableElement) {
globalVariables.add((VariableElement) element);
} else if (element instanceof ConstantElement) {
completionResult.add(new PHPCompletionItem.ConstantItem((ConstantElement) element, request));
}
}
FileObject fileObject = request.result.getSnapshot().getSource().getFileObject();
final ElementFilter forCurrentFile = ElementFilter.forFiles(fileObject);
completionResult.addAll(getVariableProposals(request, forCurrentFile.reverseFilter(globalVariables)));
// Special keywords applicable only inside a class or trait
final EnclosingType enclosingType = findEnclosingType(request.info, lexerToASTOffset(request.result, request.anchor));
if (enclosingType != null
&& (enclosingType.isClassDeclaration() || enclosingType.isTraitDeclaration())) {
final String typeName = enclosingType.extractTypeName();
if (typeName != null) {
for (final String keyword : PHP_CLASS_KEYWORDS) {
if (startsWith(keyword, request.prefix)) {
completionResult.add(new PHPCompletionItem.ClassScopeKeywordItem(typeName, keyword, request));
}
}
// NETBEANS-1855
autoCompleteClassMembers(completionResult, request, false, true);
}
}
}
private void autoCompleteGlobals(final PHPCompletionResult completionResult, PHPCompletionItem.CompletionRequest request) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
if (OptionsUtils.codeCompletionVariablesScope().equals(VariablesScope.ALL)) {
final CaseInsensitivePrefix prefix = NameKind.caseInsensitivePrefix(QualifiedName.create(request.prefix));
for (VariableElement variableElement : request.index.getTopLevelVariables(prefix)) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
completionResult.add(new PHPCompletionItem.VariableItem(variableElement, request));
}
}
}
private void autoCompleteConstants(final PHPCompletionResult completionResult, PHPCompletionItem.CompletionRequest request) {
final boolean isCamelCase = isCamelCaseForTypeNames(request.prefix);
final NameKind prefix = NameKind.create(request.prefix,
isCamelCase ? Kind.CAMEL_CASE : Kind.CASE_INSENSITIVE_PREFIX);
Model model = request.result.getModel();
Set<AliasedName> aliasedNames = ModelUtils.getAliasedNames(model, request.anchor);
for (final ConstantElement element : request.index.getConstants(prefix, aliasedNames, Trait.ALIAS)) {
if (CancelSupport.getDefault().isCancelled()) {
return;
}
completionResult.add(new PHPCompletionItem.ConstantItem((ConstantElement) element, request));
}
}
/**
* @param globalVariables (can be bull) if null then will be looked up in
* index
*/
private Collection<CompletionProposal> getVariableProposals(final CompletionRequest request, Set<VariableElement> globalVariables) {
if (CancelSupport.getDefault().isCancelled()) {
return Collections.emptyList();
}
final Map<String, CompletionProposal> proposals = new LinkedHashMap<>();
Model model = request.result.getModel();
VariableScope variableScope = model.getVariableScope(request.anchor);
if (variableScope != null) {
if (variableScope instanceof NamespaceScope
|| variableScope instanceof ArrowFunctionScope) {
if (globalVariables == null) {
FileObject fileObject = request.result.getSnapshot().getSource().getFileObject();
final ElementFilter forCurrentFile = ElementFilter.forFiles(fileObject);
globalVariables = forCurrentFile.reverseFilter(request.index.getTopLevelVariables(NameKind.caseInsensitivePrefix(QualifiedName.create(request.prefix))));
}
for (final VariableElement globalVariable : globalVariables) {
if (CancelSupport.getDefault().isCancelled()) {
return Collections.emptyList();
}
proposals.put(globalVariable.getName(), new PHPCompletionItem.VariableItem(globalVariable, request));
}
}
List<VariableName> allDeclaredVariables = new ArrayList<>(variableScope.getDeclaredVariables());
// for nested arrow functions
if (variableScope instanceof ArrowFunctionScope) {
Scope inScope = variableScope.getInScope();
while (inScope instanceof FunctionScope || inScope instanceof NamespaceScope) {
allDeclaredVariables.addAll(((VariableScope) inScope).getDeclaredVariables());
if (inScope instanceof FunctionScope
&& !(inScope instanceof ArrowFunctionScope)) {
break;
}
inScope = inScope.getInScope();
}
}
Collection<? extends VariableName> declaredVariables = ModelUtils.filter(allDeclaredVariables, nameKind, request.prefix);
final int caretOffset = request.anchor + request.prefix.length();
for (VariableName varName : declaredVariables) {
if (CancelSupport.getDefault().isCancelled()) {
return Collections.emptyList();
}
final FileObject realFileObject = varName.getRealFileObject();
if (realFileObject != null || varName.getNameRange().getEnd() < caretOffset) {
final String name = varName.getName();
String notDollaredName = name.startsWith("$") ? name.substring(1) : name;
if (PredefinedSymbols.SUPERGLOBALS.contains(notDollaredName)) {
continue;
}
if (varName.representsThis()) {
continue;
}
final Collection<? extends String> typeNames = varName.getTypeNames(request.anchor);
String typeName = typeNames.size() > 1 ? Type.MIXED : ModelUtils.getFirst(typeNames);
final Set<Pair<QualifiedName, Boolean>> qualifiedNames = typeName != null
? Collections.singleton(Pair.of(QualifiedName.create(typeName), false))
: Collections.<Pair<QualifiedName, Boolean>>emptySet();
if (realFileObject != null) {
//#183928 - Extend model to allow CTRL + click for 'view/action' variables
proposals.put(name, new PHPCompletionItem.VariableItem(
VariableElementImpl.create(name, 0, realFileObject,
varName.getElementQuery(), TypeResolverImpl.forNames(qualifiedNames), varName.isDeprecated()), request) {
@Override
public boolean isSmart() {
return true;
}
});
} else {
proposals.put(name, new PHPCompletionItem.VariableItem(
VariableElementImpl.create(name, 0, request.currentlyEditedFileURL,
varName.getElementQuery(), TypeResolverImpl.forNames(qualifiedNames), varName.isDeprecated()), request));
}
}
}
for (final String name : PredefinedSymbols.SUPERGLOBALS) {
if (isPrefix("$" + name, request.prefix)) { //NOI18N
proposals.put(name, new PHPCompletionItem.SuperGlobalItem(request, name));
}
}
}
return proposals.values();
}
private boolean isPrefix(String name, String prefix) {
return name != null && (name.startsWith(prefix)
|| nameKind == QuerySupport.Kind.CASE_INSENSITIVE_PREFIX && name.toLowerCase().startsWith(prefix.toLowerCase()));
}
@Override
public Documentation documentElement(ParserResult info, ElementHandle element, Callable<Boolean> cancel) {
Documentation result;
if (element instanceof ModelElement) {
ModelElement mElem = (ModelElement) element;
ModelElement parentElem = mElem.getInScope();
FileObject fileObject = mElem.getFileObject();
String fName = fileObject == null ? "?" : fileObject.getNameExt(); //NOI18N
String tooltip;
if (parentElem instanceof TypeScope) {
tooltip = mElem.getPhpElementKind() + ": " + parentElem.getName() + "<b> " + mElem.getName() + " </b>" + "(" + fName + ")"; //NOI18N
} else {
tooltip = mElem.getPhpElementKind() + ":<b> " + mElem.getName() + " </b>" + "(" + fName + ")"; //NOI18N
}
result = Documentation.create(String.format("<div align=\"right\"><font size=-1>%s</font></div>", tooltip)); //NOI18N
} else {
result = ((element instanceof MethodElement) && ((MethodElement) element).isMagic()) ? null : DocRenderer.document(info, element);
}
return result;
}
@Override
public String document(ParserResult info, ElementHandle element) {
return null;
}
@Override
public ElementHandle resolveLink(String link, ElementHandle originalHandle) {
return null;
}
private static boolean isPHPIdentifierPart(char c) {
return Character.isJavaIdentifierPart(c) || c == '@';
}
@org.netbeans.api.annotations.common.SuppressWarnings(value = "INT_BAD_COMPARISON_WITH_NONNEGATIVE_VALUE", justification = "Not sure about FB analysis correctness")
private String getPrefix(ParserResult info, int caretOffset, boolean upToOffset, PrefixBreaker prefixBreaker) {
try {
BaseDocument doc = (BaseDocument) info.getSnapshot().getSource().getDocument(false);
if (doc == null) {
return null;
}
int lineBegin = LineDocumentUtils.getLineStart(doc, caretOffset);
if (lineBegin != -1) {
int lineEnd = LineDocumentUtils.getLineEnd(doc, caretOffset);
String line = doc.getText(lineBegin, lineEnd - lineBegin);
int lineOffset = caretOffset - lineBegin;
int start = lineOffset;
if (lineOffset > 0) {
char c = 0;
for (int i = lineOffset - 1; i >= 0; i--) {
assert i >= 0 && i <= line.length() - 1 : "line:" + line + " | i:" + i + " | line.length():" + line.length() + " | lineBegin:" + lineBegin + " | lineEnd:" + lineEnd + " | caretOffset:" + caretOffset;
if (i >= 0 && i <= line.length() - 1) {
c = line.charAt(i);
if (!isPHPIdentifierPart(c) && c != '\\') {
break;
} else {
start = i;
}
}
}
if (start == lineOffset && c == '?'
&& lineOffset - 2 >= 0 && line.charAt(lineOffset - 2) == '<') {
start -= 2;
}
}
// Find identifier end
String prefix;
if (upToOffset) {
prefix = line.substring(start, lineOffset);
int lastIndexOfDollar = prefix.lastIndexOf('$'); //NOI18N
if (lastIndexOfDollar > 0) {
prefix = prefix.substring(lastIndexOfDollar);
}
} else {
if (lineOffset == line.length()) {
prefix = line.substring(start);
} else {
int n = line.length();
int end = lineOffset;
for (int j = lineOffset; j < n; j++) {
char d = line.charAt(j);
// Try to accept Foo::Bar as well
if (!isPHPIdentifierPart(d)) {
break;
} else {
end = j + 1;
}
}
prefix = line.substring(start, end);
}
}
if (prefix.length() > 0) {
if (prefix.endsWith("::")) {
return "";
}
if (prefix.endsWith(":") && prefix.length() > 1) {
return null;
}
// Strip out LHS if it's a qualified method, e.g. Benchmark::measure -> measure
int q = prefix.lastIndexOf("::");
if (q != -1) {
prefix = prefix.substring(q + 2);
}
// The identifier chars identified by JsLanguage are a bit too permissive;
// they include things like "=", "!" and even "&" such that double-clicks will
// pick up the whole "token" the user is after. But "=" is only allowed at the
// end of identifiers for example.
if (prefix.length() == 1) {
char c = prefix.charAt(0);
if (prefixBreaker.isBreaker(c)) {
return null;
}
} else if (!"<?".equals(prefix)) { //NOI18N
for (int i = prefix.length() - 1; i >= 0; i--) { // -2: the last position (-1) can legally be =, ! or ?
char c = prefix.charAt(i);
if (i == 0 && c == ':') {
// : is okay at the begining of prefixes
} else if (prefixBreaker.isBreaker(c)) {
prefix = prefix.substring(i + 1);
break;
}
}
}
}
if (prefix != null && prefix.startsWith("@")) { //NOI18N
final TokenHierarchy<?> tokenHierarchy = info.getSnapshot().getTokenHierarchy();
TokenSequence<PHPTokenId> tokenSequence = tokenHierarchy != null ? LexUtilities.getPHPTokenSequence(tokenHierarchy, caretOffset) : null;
if (tokenSequence != null) {
tokenSequence.move(caretOffset);
if (tokenSequence.moveNext() && tokenSequence.movePrevious()) {
Token<PHPTokenId> token = tokenSequence.token();
PHPTokenId id = token.id();
if (id.equals(PHPTokenId.PHP_STRING) || id.equals(PHPTokenId.PHP_TOKEN)) {
prefix = prefix.substring(1);
}
}
}
}
return prefix;
}
// Else: normal identifier: just return null and let the machinery do the rest
} catch (BadLocationException ble) {
//Exceptions.printStackTrace(ble);
}
return null;
}
@Override
public String getPrefix(ParserResult info, int caretOffset, boolean upToOffset) {
return getPrefix(info, caretOffset, upToOffset, PrefixBreaker.COMMON);
}
@Override
public QueryType getAutoQuery(JTextComponent component, String typedText) {
if (typedText.length() == 0) {
return QueryType.NONE;
}
char lastChar = typedText.charAt(typedText.length() - 1);
Document document = component.getDocument();
//TokenHierarchy th = TokenHierarchy.get(document);
int offset = component.getCaretPosition();
TokenSequence<PHPTokenId> ts = LexUtilities.getPHPTokenSequence(document, offset);
if (ts == null) {
return QueryType.STOP;
}
int diff = ts.move(offset);
if (diff > 0 && ts.moveNext() || ts.movePrevious()) {
Token t = ts.token();
if (t != null) {
if (t.id() == PHPTokenId.T_INLINE_HTML) {
return QueryType.NONE;
} else {
if (AUTOPOPUP_STOP_CHARS.contains(Character.valueOf(lastChar))) {
return QueryType.STOP;
}
if (OptionsUtils.autoCompletionTypes()) {
if (lastChar == ' ' || lastChar == '\t') {
if (ts.movePrevious()
&& TOKENS_TRIGGERING_AUTOPUP_TYPES_WS.contains(ts.token().id())) {
return QueryType.ALL_COMPLETION;
} else {
return QueryType.STOP;
}
}
if (t.id() == PHPTokenId.PHP_OBJECT_OPERATOR
|| t.id() == PHPTokenId.PHP_NULLSAFE_OBJECT_OPERATOR
|| t.id() == PHPTokenId.PHP_PAAMAYIM_NEKUDOTAYIM) {
return QueryType.ALL_COMPLETION;
}
}
if (OptionsUtils.autoCompletionVariables()) {
if ((t.id() == PHPTokenId.PHP_TOKEN && lastChar == '$')
|| (t.id() == PHPTokenId.PHP_CONSTANT_ENCAPSED_STRING && lastChar == '$')) {
return QueryType.ALL_COMPLETION;
}
}
if (OptionsUtils.autoCompletionNamespaces()) {
if (t.id() == PHPTokenId.PHP_NS_SEPARATOR) {
return isPhp53OrNewer(document) ? QueryType.ALL_COMPLETION : QueryType.NONE;
}
}
if (t.id() == PHPTokenId.PHPDOC_COMMENT && lastChar == '@') {
return QueryType.ALL_COMPLETION;
}
if (OptionsUtils.autoCompletionFull()) {
TokenId id = t.id();
if ((id.equals(PHPTokenId.PHP_STRING) || id.equals(PHPTokenId.PHP_VARIABLE)) && t.length() > 0) {
return QueryType.ALL_COMPLETION;
}
}
}
}
}
return QueryType.NONE;
}
public static boolean isPhp53OrNewer(Document document) {
final FileObject fileObject = CodeUtils.getFileObject(document);
assert fileObject != null;
return CodeUtils.isPhpVersionGreaterThan(fileObject, PhpVersion.PHP_5);
}
private static boolean isPhp74OrNewer(FileObject fileObject) {
if (PHP_VERSION != null) {
return PHP_VERSION.compareTo(PhpVersion.PHP_74) >= 0;
}
assert fileObject != null;
return CodeUtils.isPhpVersionGreaterThan(fileObject, PhpVersion.PHP_73);
}
@Override
public String resolveTemplateVariable(String variable, ParserResult info, int caretOffset, String name, Map parameters) {
return null;
}
@Override
public Set<String> getApplicableTemplates(Document doc, int selectionBegin, int selectionEnd) {
return null;
}
@Override
public ParameterInfo parameters(final ParserResult info, final int caretOffset, CompletionProposal proposal) {
final org.netbeans.modules.php.editor.model.Model model = ((PHPParseResult) info).getModel();
ParameterInfoSupport infoSupport = model.getParameterInfoSupport(caretOffset);
ParameterInfo parameterInfo = infoSupport.getParameterInfo();
return parameterInfo == null ? ParameterInfo.NONE : parameterInfo;
}
private boolean startsWith(String theString, String prefix) {
if (prefix.length() == 0) {
return true;
}
return caseSensitive ? theString.startsWith(prefix)
: theString.toLowerCase().startsWith(prefix.toLowerCase());
}
private int findBaseNamespaceEnd(ParserResult info, int caretOffset) {
TokenHierarchy<?> th = info.getSnapshot().getTokenHierarchy();
assert th != null;
TokenSequence<PHPTokenId> tokenSequence = LexUtilities.getPHPTokenSequence(th, caretOffset);
assert tokenSequence != null;
tokenSequence.move(caretOffset);
final boolean moveNextSucces = tokenSequence.moveNext();
if (!moveNextSucces && !tokenSequence.movePrevious()) {
assert false;
return caretOffset;
}
boolean hasCurly = false;
while (tokenSequence.movePrevious()) {
if (!hasCurly) {
if (tokenSequence.token().id() == PHPTokenId.PHP_CURLY_OPEN) {
hasCurly = true;
}
} else {
// possibly some whitespace before curly open?
if (tokenSequence.token().id() != PHPTokenId.WHITESPACE) {
tokenSequence.moveNext();
break;
}
}
}
if (hasCurly) {
return tokenSequence.offset();
}
assert false;
return caretOffset;
}
private static class StaticOrInstanceMembersFilter extends ElementFilter {
private final boolean forStaticContext;
private final boolean forInstanceContext;
private final boolean forSelfContext;
private final boolean staticAllowed;
private final boolean nonstaticAllowed;
private final boolean forStaticLateBinding;
private final boolean forParentContext;
public StaticOrInstanceMembersFilter(final boolean forStaticContext, final boolean forInstanceContext,
final boolean forSelfContext, final boolean forStaticLateBinding, final boolean forParentContext) {
this.forStaticContext = forStaticContext;
this.forInstanceContext = forInstanceContext;
this.forSelfContext = forSelfContext;
this.forStaticLateBinding = forStaticLateBinding;
this.staticAllowed = OptionsUtils.codeCompletionStaticMethods();
this.nonstaticAllowed = OptionsUtils.codeCompletionNonStaticMethods();
this.forParentContext = forParentContext;
}
@Override
public boolean isAccepted(final PhpElement element) {
if (forSelfContext && isAcceptedForSelfContext(element)) {
return true;
}
if (forStaticContext && isAcceptedForStaticContext(element)) {
return true;
}
if (forInstanceContext && isAcceptedForNotStaticContext(element)) {
return true;
}
return false;
}
private boolean isAcceptedForNotStaticContext(final PhpElement element) {
final boolean isStatic = element.getPhpModifiers().isStatic();
if (forParentContext
&& !isStatic
&& element.getPhpElementKind().equals(PhpElementKind.FIELD)) {
// parent::fieldName is invalid
// this is constant
return false;
}
return !isStatic || (staticAllowed && element.getPhpElementKind().equals(PhpElementKind.METHOD));
}
private boolean isAcceptedForStaticContext(final PhpElement element) {
final boolean isStatic = element.getPhpModifiers().isStatic();
return isStatic || (nonstaticAllowed && !forStaticLateBinding && element.getPhpElementKind().equals(PhpElementKind.METHOD));
}
private boolean isAcceptedForSelfContext(final PhpElement element) {
return forSelfContext && nonstaticAllowed && !element.getPhpElementKind().equals(PhpElementKind.FIELD);
}
}
private interface PrefixBreaker {
PrefixBreaker COMMON = new PrefixBreaker() {
@Override
public boolean isBreaker(char c) {
return !(isPHPIdentifierPart(c) || c == ':');
}
};
PrefixBreaker WITH_NS_PARTS = new PrefixBreaker() {
@Override
public boolean isBreaker(char c) {
return !(isPHPIdentifierPart(c) || c == '\\' || c == ':');
}
};
boolean isBreaker(char c);
}
private static boolean isCamelCaseForTypeNames(final String query) {
return false;
}
private interface EnclosingType {
boolean isClassDeclaration();
boolean isTraitDeclaration();
String extractTypeName();
//~ Factories
static EnclosingType forTypeDeclaration(final TypeDeclaration typeDeclaration) {
return new EnclosingType() {
@Override
public boolean isClassDeclaration() {
return typeDeclaration instanceof ClassDeclaration;
}
@Override
public boolean isTraitDeclaration() {
return typeDeclaration instanceof TraitDeclaration;
}
@Override
public String extractTypeName() {
return CodeUtils.extractTypeName(typeDeclaration);
}
};
}
static EnclosingType forClassInstanceCreation(final ClassInstanceCreation classInstanceCreation) {
assert classInstanceCreation.isAnonymous() : classInstanceCreation;
return new EnclosingType() {
@Override
public boolean isClassDeclaration() {
return true;
}
@Override
public boolean isTraitDeclaration() {
return false;
}
@Override
public String extractTypeName() {
return CodeUtils.extractClassName(classInstanceCreation);
}
};
}
}
private interface EnclosingClass {
String getClassName();
Expression getSuperClass();
List<Expression> getInterfaces();
String extractClassName();
String extractUnqualifiedSuperClassName();
//~ Factories
static EnclosingClass forClassDeclaration(final ClassDeclaration classDeclaration) {
return new EnclosingClass() {
@Override
public String getClassName() {
return classDeclaration.getName().getName();
}
@Override
public Expression getSuperClass() {
return classDeclaration.getSuperClass();
}
@Override
public List<Expression> getInterfaces() {
return classDeclaration.getInterfaes();
}
@Override
public String extractClassName() {
return CodeUtils.extractClassName(classDeclaration);
}
@Override
public String extractUnqualifiedSuperClassName() {
return CodeUtils.extractUnqualifiedSuperClassName(classDeclaration);
}
};
}
static EnclosingClass forClassInstanceCreation(final ClassInstanceCreation classInstanceCreation) {
assert classInstanceCreation.isAnonymous() : classInstanceCreation;
return new EnclosingClass() {
@Override
public String getClassName() {
return CodeUtils.extractClassName(classInstanceCreation);
}
@Override
public Expression getSuperClass() {
return classInstanceCreation.getSuperClass();
}
@Override
public List<Expression> getInterfaces() {
return classInstanceCreation.getInterfaces();
}
@Override
public String extractClassName() {
return CodeUtils.extractClassName(classInstanceCreation);
}
@Override
public String extractUnqualifiedSuperClassName() {
return CodeUtils.extractUnqualifiedSuperClassName(classInstanceCreation);
}
};
}
}
}