Skip to content

Commit

Permalink
Merge pull request #79 from NixOS/symbols
Browse files Browse the repository at this point in the history
Variable resolution via experimental Symbols API
  • Loading branch information
JojOatXGME authored Oct 20, 2024
2 parents cdd4ca8 + b6c291b commit a23cf9c
Show file tree
Hide file tree
Showing 33 changed files with 2,848 additions and 12 deletions.
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

0 comments on commit a23cf9c

Please sign in to comment.