Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull

Collection<LookupElement> lookupElements = new ArrayList<>();

// Check if we're before a public method (using shared logic from PhpAttributeCompletionPopupHandlerCompletionConfidence)
Method method = PhpAttributeCompletionPopupHandlerCompletionConfidence.getMethod(position);
// Check if we're before a public method (using shared scope validator)
Method method = PhpAttributeScopeValidator.getMethod(position);
if (method != null) {
// Method-level attribute completions
PhpClass containingClass = method.getContainingClass();
Expand All @@ -101,7 +101,7 @@ protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull
}
} else {
// Check if we're before a property/field
Field field = PhpAttributeCompletionPopupHandlerCompletionConfidence.getField(position);
Field field = PhpAttributeScopeValidator.getField(position);
if (field != null) {
// Property-level attribute completions
PhpClass containingClass = field.getContainingClass();
Expand All @@ -110,7 +110,7 @@ protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull
}
} else {
// Check if we're before a class
PhpClass phpClass = PhpAttributeCompletionPopupHandlerCompletionConfidence.getPhpClass(position);
PhpClass phpClass = PhpAttributeScopeValidator.getPhpClass(position);
if (phpClass != null) {
// Class-level attribute completions
if (AddRouteAttributeIntention.isControllerClass(phpClass)) {
Expand All @@ -124,11 +124,8 @@ protected void addCompletions(@NotNull CompletionParameters parameters, @NotNull
}
}

// Stop here - don't show other completions when typing "#" for attributes
if (!lookupElements.isEmpty()) {
result.addAllElements(lookupElements);
result.stopHere();
}
result.addAllElements(lookupElements);
result.stopHere();
}

/**
Expand Down Expand Up @@ -504,20 +501,20 @@ public void handleInsert(@NotNull InsertionContext context, @NotNull LookupEleme
return;
}

// Determine the target context (method, field, or class) dynamically
// Determine the target context (method, field, or class) dynamically using shared scope validator
PhpClass phpClass;
Method targetMethod = PhpAttributeCompletionPopupHandlerCompletionConfidence.getMethod(originalElement);
Method targetMethod = PhpAttributeScopeValidator.getMethod(originalElement);
if (targetMethod != null) {
// We're in a method context
phpClass = targetMethod.getContainingClass();
} else {
// Try field context
Field targetField = PhpAttributeCompletionPopupHandlerCompletionConfidence.getField(originalElement);
Field targetField = PhpAttributeScopeValidator.getField(originalElement);
if (targetField != null) {
phpClass = targetField.getContainingClass();
} else {
// Try class context
phpClass = PhpAttributeCompletionPopupHandlerCompletionConfidence.getPhpClass(originalElement);
phpClass = PhpAttributeScopeValidator.getPhpClass(originalElement);
if (phpClass == null) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,9 @@
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.util.ThreeState;
import com.jetbrains.php.lang.parser.PhpElementTypes;
import com.jetbrains.php.lang.psi.PhpFile;
import com.jetbrains.php.lang.psi.PhpPsiUtil;
import com.jetbrains.php.lang.psi.elements.*;
import fr.adrienbrault.idea.symfony2plugin.Symfony2ProjectComponent;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public class PhpAttributeCompletionPopupHandlerCompletionConfidence {
/**
Expand All @@ -28,19 +24,23 @@ public static class PhpAttributeCompletionConfidence extends CompletionConfidenc
@NotNull
@Override
public ThreeState shouldSkipAutopopup(@NotNull Editor editor, @NotNull PsiElement contextElement, @NotNull PsiFile psiFile, int offset) {
if (offset <= 0 || !(psiFile instanceof PhpFile) || !Symfony2ProjectComponent.isEnabled(editor.getProject())) {
if (offset <= 0 || !(psiFile instanceof PhpFile)) {
return ThreeState.UNSURE;
}

// Check if we're before a method, class, or field
if (getMethod(contextElement) == null && getPhpClass(contextElement) == null && getField(contextElement) == null) {
Project project = editor.getProject();
if (!Symfony2ProjectComponent.isEnabled(project)) {
return ThreeState.UNSURE;
}

// Check if there's a "#" before the cursor in the document
CharSequence documentText = editor.getDocument().getCharsSequence();
if (documentText.charAt(offset - 1) == '#' && psiFile.findElementAt(offset - 2) instanceof PsiWhiteSpace) {
return ThreeState.NO;
// Check if we should provide attribute completions for this context
// (controller class, twig component, twig extension, etc.)
if (PhpAttributeScopeValidator.shouldProvideAttributeCompletions(contextElement, project)) {
return ThreeState.NO;
}
}

return ThreeState.UNSURE;
Expand Down Expand Up @@ -71,97 +71,14 @@ public static class PhpAttributeAutoPopupHandler extends TypedHandlerDelegate {
return Result.CONTINUE;
}

// Check if we're before a method, class, or field
if (getMethod(element) == null && getField(element) == null && getPhpClass(element) == null) {
// Check if we should provide attribute completions for this context
// (controller class, twig component, twig extension, etc.)
if (!PhpAttributeScopeValidator.shouldProvideAttributeCompletions(element, project)) {
return Result.CONTINUE;
}

AutoPopupController.getInstance(project).scheduleAutoPopup(editor);
return Result.STOP;
}
}

/**
* Finds a public method associated with the given element.
* Returns the method if the element is a child of a method or if the next sibling is a method.
*
* @param element The PSI element to check
* @return The public method if found, null otherwise
*/
public static @Nullable Method getMethod(@NotNull PsiElement element) {
Method foundMethod = null;

if (element.getParent() instanceof Method method) {
foundMethod = method;
} else if (PhpPsiUtil.getNextSiblingIgnoreWhitespace(element, true) instanceof Method method) {
foundMethod = method;
}

return foundMethod != null && foundMethod.getAccess().isPublic()
? foundMethod
: null;
}

/**
* Finds a PhpClass associated with the given element.
* Returns the class if the element is a child of a class or if the next sibling is a class.
* Also handles cases where we're in the middle of an attribute list.
*
* @param element The PSI element to check
* @return The PhpClass if found, null otherwise
*/
public static @Nullable PhpClass getPhpClass(@NotNull PsiElement element) {
if (element.getParent() instanceof PhpClass phpClass) {
return phpClass;
}

// with use statement given
PsiElement nextSiblingIgnoreWhitespace = PhpPsiUtil.getNextSiblingIgnoreWhitespace(element, true);
if (nextSiblingIgnoreWhitespace instanceof PhpClass phpClass) {
return phpClass;
}

// no use statements
if (nextSiblingIgnoreWhitespace != null && nextSiblingIgnoreWhitespace.getNode().getElementType() == PhpElementTypes.NON_LAZY_GROUP_STATEMENT) {
if (nextSiblingIgnoreWhitespace.getFirstChild() instanceof PhpClass phpClass) {
return phpClass;
}
}

return null;
}

/**
* Finds a Field (property) associated with the given element.
* Returns the field if the element is a child of a field or if the next sibling is a field.
*
* @param element The PSI element to check
* @return The Field if found, null otherwise
*/
public static @Nullable Field getField(@NotNull PsiElement element) {
PsiElement nextSiblingIgnoreWhitespace = PhpPsiUtil.getNextSiblingIgnoreWhitespace(element, true);
if (nextSiblingIgnoreWhitespace instanceof PhpModifierList phpModifierList && phpModifierList.hasPublic()) {
if (phpModifierList.getNextPsiSibling() instanceof Field field) {
return field;
}
}

if (nextSiblingIgnoreWhitespace instanceof PhpPsiElement phpPsiElement) {
PhpPsiElement firstPsiChild = phpPsiElement.getFirstPsiChild();
if (firstPsiChild instanceof PhpModifierList phpModifierList && phpModifierList.hasPublic()) {
PhpPsiElement nextPsiSibling = phpModifierList.getNextPsiSibling();

if (nextPsiSibling instanceof Field field) {
return field;
} else if(nextPsiSibling instanceof PhpFieldType phpFieldType) {
PhpPsiElement nextPsiSibling1 = phpFieldType.getNextPsiSibling();
if (nextPsiSibling1 instanceof Field field1) {
return field1;
}
}
}
}

return null;
}
}
Loading