Seemingly random behavior for Find Usages
My custom language plugin implements References and a FindUsagesProvider for numeric and string variables, where the latter all end in a '$' character.
For unknown reasons, I experience some very weird behavior when finding usages for such string variables: Seemingly at random, finding usages works for some string variables, but not for others:
For all variables, however, IDEA highlights all variable instances when placing the cursor on any instance.
I do not understand how find usages can work for some variables, but not for others. I've ruled out that the position in the Psi tree matters: For example, renaming (manually, without using inline renaming) a working variable can result in a non-working variable.
Also, I've never found an instance where find usages didn't work for numeric variables, so I think the '$' character does play a role.
This is my FindUsagesProvider
public class Xbas99FindUsagesProvider implements FindUsagesProvider {
@Override
@Nullable
public WordsScanner getWordsScanner() {
return new DefaultWordsScanner(new Xbas99LexerAdapter(),
TokenSet.create(Xbas99Types.IDENT, Xbas99Types.SIDENT), // identifiers
TokenSet.create(Xbas99Types.COMMENT), // comments
TokenSet.create(Xbas99Types.QSTRING, Xbas99Types.NUMBER, Xbas99Types.FLOAT)); // literals
}
@Override
public boolean canFindUsagesFor(@NotNull PsiElement psiElement) {
return psiElement instanceof Xbas99NamedElement;
}
@Override
@Nullable
public String getHelpId(@NotNull PsiElement psiElement) {
return null;
}
@Override
@NotNull
public String getType(@NotNull PsiElement element) {
if (element instanceof Xbas99Linedef || element instanceof Xbas99Lino) {
return "BASIC line number";
}
if (element instanceof Xbas99NvarW || element instanceof Xbas99NvarR) {
return "BASIC numerical variable";
}
if (element instanceof Xbas99SvarW || element instanceof Xbas99SvarR) {
return "BASIC string variable";
}
return "";
}
@Override
@NotNull
public String getDescriptiveName(@NotNull PsiElement element) {
return getNodeText(element, true);
}
@Override
@NotNull
public String getNodeText(@NotNull PsiElement element, boolean useFullName) {
if (element instanceof Xbas99Linedef) {
return ((Xbas99Linedef) element).getName();
}
if (element instanceof Xbas99Lino) {
return ((Xbas99Lino) element).getName();
}
if (element instanceof Xbas99NvarW) {
return ((Xbas99NvarW) element).getName();
}
if (element instanceof Xbas99NvarR) {
return ((Xbas99NvarR) element).getName();
}
if (element instanceof Xbas99SvarW) {
return ((Xbas99SvarW) element).getName();
}
if (element instanceof Xbas99SvarR) {
return ((Xbas99SvarR) element).getName();
}
return "";
}
}
and my Reference.
public class Xbas99VarReference extends PsiReferenceBase<PsiElement> implements PsiPolyVariantReference {
private final String dummy = CompletionUtilCore.DUMMY_IDENTIFIER_TRIMMED.toUpperCase();
private final String varName;
private final boolean isStringVar;
public Xbas99VarReference(@NotNull PsiElement element, TextRange textRange, boolean forString) {
super(element, textRange);
varName = element.getText().substring(textRange.getStartOffset(), textRange.getEndOffset()).toUpperCase();
isStringVar = forString;
}
@Override
public ResolveResult @NotNull [] multiResolve(boolean incompleteCode) {
List<ResolveResult> results = new ArrayList<>();
final List<Xbas99NamedElement> vars = isStringVar ?
Xbas99Util.findSvardef(myElement, varName, false) :
Xbas99Util.findNvardef(myElement, varName, false);
for (Xbas99NamedElement var : vars) {
results.add(new PsiElementResolveResult(var));
}
return results.toArray(new ResolveResult[results.size()]);
}
@Override
@Nullable
public PsiElement resolve() {
ResolveResult[] resolveResults = multiResolve(false);
return resolveResults.length == 1 ? resolveResults[0].getElement() : null;
}
@Override
public Object @NotNull [] getVariants() {
boolean queryForString = false;
String varText = varName;
if (varText.endsWith("$")) {
do {
varText = varText.substring(0, varText.length() - 1); // remove last char
} while (varText.endsWith(" "));
queryForString = true;
}
if (varText.endsWith(dummy)) {
varText = varText.substring(0, varText.length() - dummy.length());
}
List<Xbas99NamedElement> vars = isStringVar || queryForString ?
Xbas99Util.findSvardef(myElement, varText, true) :
Xbas99Util.findNvardef(myElement, varText, true);
List<LookupElement> variants = new ArrayList<LookupElement>();
for (final Xbas99NamedElement var : vars) {
if (!var.getName().isEmpty() && var.getName().length() > 0) {
variants.add(LookupElementBuilder.create(var).
withIcon(Xbas99Icons.FILE).
withTypeText(var.getContainingFile().getName())
);
}
}
return variants.toArray();
}
@Override
public PsiElement handleElementRename(@NotNull String newElementName) throws IncorrectOperationException {
Xbas99PsiImplUtil.rename(myElement, newElementName);
return myElement;
}
}
The entire project together with some examples are on GitHub: https://github.com/endlos99/plugin-wip.git
The code in question is in directories xbas99 and xbas99l (two variants of the same language, both are affected).
Please sign in to leave a comment.
Hi Ralph,
I've tested your project and actually, it couldn't find references for some variables. I used your previous example project I had on my disk. After some time I decided to clear indexes (File / Invalidate Caches...) and it seems it resolved the problem. Finding usages works fine every time I use it, so the issue cause was probably wrongly indexed files (maybe during work in progress).
Could you please check it on your side and confirm if it solved the issue?
Thanks, Karol, I can confirm that invalidating caches solved the problem for me.
Still, it's slightly surprising, because I first noticed this behavior in a newly created project. I'd assume that for a new project, there cannot really be any corrupted index?! Also, why only for this element type?
But anyway, it's working now, and I know how to fix it, so thanks again!
Hi Ralph,
That's a bit disturbing if it didn't work for a newly created project. Are you sure you didn't include the same file as before in the new project? Virtual File System and indexes are application-level concepts, so maybe the same index was used for the new project as well.
Anyway, if it happens again, at least you have a pointer to look into and debug indexing logic.
I think I remembered incorrectly. When I created a new project yesterday, everything was working fine without invalidating caches, so I think that issue was merely an artifact of the sandbox.