Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Variable resolution via experimental Symbols API #79

Merged
merged 10 commits into from
Oct 20, 2024
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

### Added

- Experimental support for resolving variables.
The feature is disabled by default since the functionality is rather limited for now.
Feel free to comment your feedback at [issue #87](https://github.com/NixOS/nix-idea/issues/87).

### Changed

### Deprecated
Expand Down
6 changes: 5 additions & 1 deletion src/main/java/org/nixos/idea/lang/builtins/NixBuiltin.java
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,11 @@ private NixBuiltin(@NotNull String name,
this.global = GLOBAL_SCOPE.contains(name);
}

public HighlightingType highlightingType() {
public @NotNull String name() {
return name;
}

public @NotNull HighlightingType highlightingType() {
return highlightingType;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package org.nixos.idea.lang.references;

import com.intellij.model.Pointer;
import com.intellij.openapi.util.TextRange;
import com.intellij.platform.backend.navigation.NavigationRequest;
import com.intellij.platform.backend.navigation.NavigationTarget;
import com.intellij.platform.backend.presentation.TargetPresentation;
import com.intellij.psi.SmartPointerManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import org.nixos.idea.psi.NixPsiElement;

@SuppressWarnings("UnstableApiUsage")
public final class NixNavigationTarget implements NavigationTarget {

private final @NotNull NixPsiElement myIdentifier;
private final @NotNull TargetPresentation myTargetPresentation;
private @Nullable Pointer<NavigationTarget> myPointer;

public NixNavigationTarget(@NotNull NixPsiElement identifier, @NotNull TargetPresentation targetPresentation) {
myIdentifier = identifier;
myTargetPresentation = targetPresentation;
}

private NixNavigationTarget(@NotNull Pointer<NavigationTarget> pointer,
@NotNull NixPsiElement identifier,
@NotNull TargetPresentation targetPresentation) {
myIdentifier = identifier;
myTargetPresentation = targetPresentation;
myPointer = pointer;
}

@TestOnly
TextRange getRangeInFile() {
return myIdentifier.getTextRange();
}

@Override
public @NotNull Pointer<NavigationTarget> createPointer() {
if (myPointer == null) {
TargetPresentation targetPresentation = myTargetPresentation;
myPointer = Pointer.uroborosPointer(
SmartPointerManager.createPointer(myIdentifier),
(identifier, pointer) -> new NixNavigationTarget(pointer, identifier, targetPresentation));
}
return myPointer;
}

@Override
public @NotNull TargetPresentation computePresentation() {
return myTargetPresentation;
}

@Override
public @Nullable NavigationRequest navigationRequest() {
return NavigationRequest.sourceNavigationRequest(myIdentifier.getContainingFile(), myIdentifier.getTextRange());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.nixos.idea.lang.references;

import org.jetbrains.annotations.NotNull;
import org.nixos.idea.lang.references.symbol.NixSymbol;
import org.nixos.idea.psi.NixPsiElement;

import java.util.Collection;

@SuppressWarnings("UnstableApiUsage")
public final class NixScopeReference extends NixSymbolReference {

public NixScopeReference(@NotNull NixPsiElement element, @NotNull NixPsiElement identifier, @NotNull String variableName) {
super(element, identifier, variableName);
}

@Override
public @NotNull Collection<NixSymbol> resolveReference() {
return myElement.getScope().resolveVariable(myName);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.nixos.idea.lang.references;

import com.intellij.model.psi.PsiSymbolDeclaration;
import com.intellij.openapi.util.TextRange;
import com.intellij.platform.backend.navigation.NavigationTarget;
import com.intellij.platform.backend.presentation.TargetPresentation;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.nixos.idea.lang.references.symbol.NixUserSymbol;
import org.nixos.idea.psi.NixPsiElement;
import org.nixos.idea.util.TextRangeFactory;

@SuppressWarnings("UnstableApiUsage")
public final class NixSymbolDeclaration implements PsiSymbolDeclaration {

private final @NotNull NixPsiElement myDeclarationElement;
private final @NotNull NixPsiElement myIdentifier;
private final @NotNull NixUserSymbol mySymbol;
private final @NotNull String myDeclarationElementName;
private final @Nullable String myDeclarationElementType;

public NixSymbolDeclaration(@NotNull NixPsiElement declarationElement, @NotNull NixPsiElement identifier,
@NotNull NixUserSymbol symbol,
@NotNull String declarationElementName, @Nullable String declarationElementType) {
myDeclarationElement = declarationElement;
myIdentifier = identifier;
mySymbol = symbol;
myDeclarationElementName = declarationElementName;
myDeclarationElementType = declarationElementType;
}

public @NotNull NixPsiElement getIdentifier() {
return myIdentifier;
}

public @NotNull NavigationTarget navigationTarget() {
return new NixNavigationTarget(myIdentifier, TargetPresentation.builder(mySymbol.presentation())
.presentableText(myDeclarationElementName)
.containerText(myDeclarationElementType)
.presentation());
}

@Override
public @NotNull NixPsiElement getDeclaringElement() {
return myDeclarationElement;
}

@Override
public @NotNull TextRange getRangeInDeclaringElement() {
return TextRangeFactory.relative(myIdentifier, myDeclarationElement);
}

@Override
public @NotNull NixUserSymbol getSymbol() {
return mySymbol;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package org.nixos.idea.lang.references;

import com.intellij.model.Symbol;
import com.intellij.model.psi.PsiSymbolReference;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.nixos.idea.lang.references.symbol.NixSymbol;
import org.nixos.idea.psi.NixPsiElement;
import org.nixos.idea.util.TextRangeFactory;

@SuppressWarnings("UnstableApiUsage")
public abstract class NixSymbolReference implements PsiSymbolReference {

protected final @NotNull NixPsiElement myElement;
protected final @NotNull NixPsiElement myIdentifier;
protected final @NotNull String myName;

protected NixSymbolReference(@NotNull NixPsiElement element, @NotNull NixPsiElement identifier, @NotNull String name) {
myElement = element;
myIdentifier = identifier;
myName = name;
}

public @NotNull NixPsiElement getIdentifier() {
return myIdentifier;
}

@Override
public @NotNull PsiElement getElement() {
return myElement;
}

@Override
public @NotNull TextRange getRangeInElement() {
return TextRangeFactory.relative(myIdentifier, myElement);
}

@Override
public boolean resolvesTo(@NotNull Symbol target) {
// Check name as a shortcut to avoid resolving the reference when it cannot match anyway.
return target instanceof NixSymbol t &&
myName.equals(t.getName()) &&
PsiSymbolReference.super.resolvesTo(target);
}
}
69 changes: 69 additions & 0 deletions src/main/java/org/nixos/idea/lang/references/NixUsage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.nixos.idea.lang.references;

import com.intellij.find.usages.api.PsiUsage;
import com.intellij.find.usages.api.ReadWriteUsage;
import com.intellij.find.usages.api.UsageAccess;
import com.intellij.model.Pointer;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiFile;
import com.intellij.psi.SmartPointerManager;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.nixos.idea.psi.NixPsiElement;
import org.nixos.idea.settings.NixSymbolSettings;

@SuppressWarnings("UnstableApiUsage")
final class NixUsage implements PsiUsage, ReadWriteUsage {

private final @NotNull NixPsiElement myIdentifier;
private final boolean myIsDeclaration;
private @Nullable Pointer<NixUsage> myPointer;

NixUsage(@NotNull NixSymbolDeclaration declaration) {
myIdentifier = declaration.getIdentifier();
myIsDeclaration = true;
}

NixUsage(@NotNull NixSymbolReference reference) {
myIdentifier = reference.getIdentifier();
myIsDeclaration = false;
}

private NixUsage(@NotNull Pointer<NixUsage> pointer, @NotNull NixPsiElement identifier, boolean isDeclaration) {
myIdentifier = identifier;
myIsDeclaration = isDeclaration;
myPointer = pointer;
}

@Override
public @NotNull Pointer<NixUsage> createPointer() {
if (myPointer == null) {
boolean isDeclaration = myIsDeclaration;
myPointer = Pointer.uroborosPointer(
SmartPointerManager.createPointer(myIdentifier),
(identifier, pointer) -> new NixUsage(pointer, identifier, isDeclaration));
}
return myPointer;
}

@Override
public @NotNull PsiFile getFile() {
return myIdentifier.getContainingFile();
}

@Override
public @NotNull TextRange getRange() {
return myIdentifier.getTextRange();
}

@Override
public boolean getDeclaration() {
// IDEA removes all instances which return true from the result of the usage search
return !NixSymbolSettings.getInstance().getShowDeclarationsAsUsages() && myIsDeclaration;
}

@Override
public @Nullable UsageAccess computeAccess() {
return myIsDeclaration ? UsageAccess.Write : UsageAccess.Read;
}
}
69 changes: 69 additions & 0 deletions src/main/java/org/nixos/idea/lang/references/NixUsageSearcher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package org.nixos.idea.lang.references;

import com.intellij.find.usages.api.Usage;
import com.intellij.find.usages.api.UsageSearchParameters;
import com.intellij.find.usages.api.UsageSearcher;
import com.intellij.model.search.LeafOccurrence;
import com.intellij.model.search.LeafOccurrenceMapper;
import com.intellij.model.search.SearchContext;
import com.intellij.model.search.SearchService;
import com.intellij.psi.PsiElement;
import com.intellij.util.Query;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.nixos.idea.lang.NixLanguage;
import org.nixos.idea.lang.references.symbol.NixSymbol;
import org.nixos.idea.lang.references.symbol.NixUserSymbol;
import org.nixos.idea.psi.NixPsiElement;
import org.nixos.idea.settings.NixSymbolSettings;

import java.util.Collection;
import java.util.List;

@SuppressWarnings("UnstableApiUsage")
public final class NixUsageSearcher implements UsageSearcher, LeafOccurrenceMapper.Parameterized<NixSymbol, Usage> {

@Override
public @NotNull Collection<? extends Usage> collectImmediateResults(@NotNull UsageSearchParameters parameters) {
if (!NixSymbolSettings.getInstance().getEnabled()) {
return List.of();
} else if (parameters.getTarget() instanceof NixUserSymbol symbol) {
return symbol.getDeclarations().stream().map(NixUsage::new).toList();
} else {
return List.of();
}
}

@Override
public @Nullable Query<? extends Usage> collectSearchRequest(@NotNull UsageSearchParameters parameters) {
if (!NixSymbolSettings.getInstance().getEnabled()) {
return null;
} else if (parameters.getTarget() instanceof NixSymbol symbol) {
String name = symbol.getName();
return SearchService.getInstance()
.searchWord(parameters.getProject(), name)
.inContexts(SearchContext.IN_CODE_HOSTS, SearchContext.IN_CODE)
.inScope(parameters.getSearchScope())
.inFilesWithLanguage(NixLanguage.INSTANCE)
.buildQuery(LeafOccurrenceMapper.withPointer(symbol.createPointer(), this));
} else {
return null;
}
}

@Override
public @NotNull Collection<? extends Usage> mapOccurrence(@NotNull NixSymbol symbol, @NotNull LeafOccurrence occurrence) {
for (PsiElement element = occurrence.getStart(); element != null && element != occurrence.getScope(); element = element.getParent()) {
if (element instanceof NixPsiElement nixElement) {
List<NixUsage> usages = nixElement.getOwnReferences().stream()
.filter(reference -> reference.resolvesTo(symbol))
.map(NixUsage::new)
.toList();
if (!usages.isEmpty()) {
return usages;
}
}
}
return List.of();
}
}
Loading