Skip to content

Commit a23cf9c

Browse files
authored
Merge pull request #79 from NixOS/symbols
Variable resolution via experimental Symbols API
2 parents cdd4ca8 + b6c291b commit a23cf9c

33 files changed

+2848
-12
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@
44

55
### Added
66

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

913
### Deprecated

src/main/java/org/nixos/idea/lang/builtins/NixBuiltin.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -200,7 +200,11 @@ private NixBuiltin(@NotNull String name,
200200
this.global = GLOBAL_SCOPE.contains(name);
201201
}
202202

203-
public HighlightingType highlightingType() {
203+
public @NotNull String name() {
204+
return name;
205+
}
206+
207+
public @NotNull HighlightingType highlightingType() {
204208
return highlightingType;
205209
}
206210

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package org.nixos.idea.lang.references;
2+
3+
import com.intellij.model.Pointer;
4+
import com.intellij.openapi.util.TextRange;
5+
import com.intellij.platform.backend.navigation.NavigationRequest;
6+
import com.intellij.platform.backend.navigation.NavigationTarget;
7+
import com.intellij.platform.backend.presentation.TargetPresentation;
8+
import com.intellij.psi.SmartPointerManager;
9+
import org.jetbrains.annotations.NotNull;
10+
import org.jetbrains.annotations.Nullable;
11+
import org.jetbrains.annotations.TestOnly;
12+
import org.nixos.idea.psi.NixPsiElement;
13+
14+
@SuppressWarnings("UnstableApiUsage")
15+
public final class NixNavigationTarget implements NavigationTarget {
16+
17+
private final @NotNull NixPsiElement myIdentifier;
18+
private final @NotNull TargetPresentation myTargetPresentation;
19+
private @Nullable Pointer<NavigationTarget> myPointer;
20+
21+
public NixNavigationTarget(@NotNull NixPsiElement identifier, @NotNull TargetPresentation targetPresentation) {
22+
myIdentifier = identifier;
23+
myTargetPresentation = targetPresentation;
24+
}
25+
26+
private NixNavigationTarget(@NotNull Pointer<NavigationTarget> pointer,
27+
@NotNull NixPsiElement identifier,
28+
@NotNull TargetPresentation targetPresentation) {
29+
myIdentifier = identifier;
30+
myTargetPresentation = targetPresentation;
31+
myPointer = pointer;
32+
}
33+
34+
@TestOnly
35+
TextRange getRangeInFile() {
36+
return myIdentifier.getTextRange();
37+
}
38+
39+
@Override
40+
public @NotNull Pointer<NavigationTarget> createPointer() {
41+
if (myPointer == null) {
42+
TargetPresentation targetPresentation = myTargetPresentation;
43+
myPointer = Pointer.uroborosPointer(
44+
SmartPointerManager.createPointer(myIdentifier),
45+
(identifier, pointer) -> new NixNavigationTarget(pointer, identifier, targetPresentation));
46+
}
47+
return myPointer;
48+
}
49+
50+
@Override
51+
public @NotNull TargetPresentation computePresentation() {
52+
return myTargetPresentation;
53+
}
54+
55+
@Override
56+
public @Nullable NavigationRequest navigationRequest() {
57+
return NavigationRequest.sourceNavigationRequest(myIdentifier.getContainingFile(), myIdentifier.getTextRange());
58+
}
59+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package org.nixos.idea.lang.references;
2+
3+
import org.jetbrains.annotations.NotNull;
4+
import org.nixos.idea.lang.references.symbol.NixSymbol;
5+
import org.nixos.idea.psi.NixPsiElement;
6+
7+
import java.util.Collection;
8+
9+
@SuppressWarnings("UnstableApiUsage")
10+
public final class NixScopeReference extends NixSymbolReference {
11+
12+
public NixScopeReference(@NotNull NixPsiElement element, @NotNull NixPsiElement identifier, @NotNull String variableName) {
13+
super(element, identifier, variableName);
14+
}
15+
16+
@Override
17+
public @NotNull Collection<NixSymbol> resolveReference() {
18+
return myElement.getScope().resolveVariable(myName);
19+
}
20+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
package org.nixos.idea.lang.references;
2+
3+
import com.intellij.model.psi.PsiSymbolDeclaration;
4+
import com.intellij.openapi.util.TextRange;
5+
import com.intellij.platform.backend.navigation.NavigationTarget;
6+
import com.intellij.platform.backend.presentation.TargetPresentation;
7+
import org.jetbrains.annotations.NotNull;
8+
import org.jetbrains.annotations.Nullable;
9+
import org.nixos.idea.lang.references.symbol.NixUserSymbol;
10+
import org.nixos.idea.psi.NixPsiElement;
11+
import org.nixos.idea.util.TextRangeFactory;
12+
13+
@SuppressWarnings("UnstableApiUsage")
14+
public final class NixSymbolDeclaration implements PsiSymbolDeclaration {
15+
16+
private final @NotNull NixPsiElement myDeclarationElement;
17+
private final @NotNull NixPsiElement myIdentifier;
18+
private final @NotNull NixUserSymbol mySymbol;
19+
private final @NotNull String myDeclarationElementName;
20+
private final @Nullable String myDeclarationElementType;
21+
22+
public NixSymbolDeclaration(@NotNull NixPsiElement declarationElement, @NotNull NixPsiElement identifier,
23+
@NotNull NixUserSymbol symbol,
24+
@NotNull String declarationElementName, @Nullable String declarationElementType) {
25+
myDeclarationElement = declarationElement;
26+
myIdentifier = identifier;
27+
mySymbol = symbol;
28+
myDeclarationElementName = declarationElementName;
29+
myDeclarationElementType = declarationElementType;
30+
}
31+
32+
public @NotNull NixPsiElement getIdentifier() {
33+
return myIdentifier;
34+
}
35+
36+
public @NotNull NavigationTarget navigationTarget() {
37+
return new NixNavigationTarget(myIdentifier, TargetPresentation.builder(mySymbol.presentation())
38+
.presentableText(myDeclarationElementName)
39+
.containerText(myDeclarationElementType)
40+
.presentation());
41+
}
42+
43+
@Override
44+
public @NotNull NixPsiElement getDeclaringElement() {
45+
return myDeclarationElement;
46+
}
47+
48+
@Override
49+
public @NotNull TextRange getRangeInDeclaringElement() {
50+
return TextRangeFactory.relative(myIdentifier, myDeclarationElement);
51+
}
52+
53+
@Override
54+
public @NotNull NixUserSymbol getSymbol() {
55+
return mySymbol;
56+
}
57+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package org.nixos.idea.lang.references;
2+
3+
import com.intellij.model.Symbol;
4+
import com.intellij.model.psi.PsiSymbolReference;
5+
import com.intellij.openapi.util.TextRange;
6+
import com.intellij.psi.PsiElement;
7+
import org.jetbrains.annotations.NotNull;
8+
import org.nixos.idea.lang.references.symbol.NixSymbol;
9+
import org.nixos.idea.psi.NixPsiElement;
10+
import org.nixos.idea.util.TextRangeFactory;
11+
12+
@SuppressWarnings("UnstableApiUsage")
13+
public abstract class NixSymbolReference implements PsiSymbolReference {
14+
15+
protected final @NotNull NixPsiElement myElement;
16+
protected final @NotNull NixPsiElement myIdentifier;
17+
protected final @NotNull String myName;
18+
19+
protected NixSymbolReference(@NotNull NixPsiElement element, @NotNull NixPsiElement identifier, @NotNull String name) {
20+
myElement = element;
21+
myIdentifier = identifier;
22+
myName = name;
23+
}
24+
25+
public @NotNull NixPsiElement getIdentifier() {
26+
return myIdentifier;
27+
}
28+
29+
@Override
30+
public @NotNull PsiElement getElement() {
31+
return myElement;
32+
}
33+
34+
@Override
35+
public @NotNull TextRange getRangeInElement() {
36+
return TextRangeFactory.relative(myIdentifier, myElement);
37+
}
38+
39+
@Override
40+
public boolean resolvesTo(@NotNull Symbol target) {
41+
// Check name as a shortcut to avoid resolving the reference when it cannot match anyway.
42+
return target instanceof NixSymbol t &&
43+
myName.equals(t.getName()) &&
44+
PsiSymbolReference.super.resolvesTo(target);
45+
}
46+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package org.nixos.idea.lang.references;
2+
3+
import com.intellij.find.usages.api.PsiUsage;
4+
import com.intellij.find.usages.api.ReadWriteUsage;
5+
import com.intellij.find.usages.api.UsageAccess;
6+
import com.intellij.model.Pointer;
7+
import com.intellij.openapi.util.TextRange;
8+
import com.intellij.psi.PsiFile;
9+
import com.intellij.psi.SmartPointerManager;
10+
import org.jetbrains.annotations.NotNull;
11+
import org.jetbrains.annotations.Nullable;
12+
import org.nixos.idea.psi.NixPsiElement;
13+
import org.nixos.idea.settings.NixSymbolSettings;
14+
15+
@SuppressWarnings("UnstableApiUsage")
16+
final class NixUsage implements PsiUsage, ReadWriteUsage {
17+
18+
private final @NotNull NixPsiElement myIdentifier;
19+
private final boolean myIsDeclaration;
20+
private @Nullable Pointer<NixUsage> myPointer;
21+
22+
NixUsage(@NotNull NixSymbolDeclaration declaration) {
23+
myIdentifier = declaration.getIdentifier();
24+
myIsDeclaration = true;
25+
}
26+
27+
NixUsage(@NotNull NixSymbolReference reference) {
28+
myIdentifier = reference.getIdentifier();
29+
myIsDeclaration = false;
30+
}
31+
32+
private NixUsage(@NotNull Pointer<NixUsage> pointer, @NotNull NixPsiElement identifier, boolean isDeclaration) {
33+
myIdentifier = identifier;
34+
myIsDeclaration = isDeclaration;
35+
myPointer = pointer;
36+
}
37+
38+
@Override
39+
public @NotNull Pointer<NixUsage> createPointer() {
40+
if (myPointer == null) {
41+
boolean isDeclaration = myIsDeclaration;
42+
myPointer = Pointer.uroborosPointer(
43+
SmartPointerManager.createPointer(myIdentifier),
44+
(identifier, pointer) -> new NixUsage(pointer, identifier, isDeclaration));
45+
}
46+
return myPointer;
47+
}
48+
49+
@Override
50+
public @NotNull PsiFile getFile() {
51+
return myIdentifier.getContainingFile();
52+
}
53+
54+
@Override
55+
public @NotNull TextRange getRange() {
56+
return myIdentifier.getTextRange();
57+
}
58+
59+
@Override
60+
public boolean getDeclaration() {
61+
// IDEA removes all instances which return true from the result of the usage search
62+
return !NixSymbolSettings.getInstance().getShowDeclarationsAsUsages() && myIsDeclaration;
63+
}
64+
65+
@Override
66+
public @Nullable UsageAccess computeAccess() {
67+
return myIsDeclaration ? UsageAccess.Write : UsageAccess.Read;
68+
}
69+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package org.nixos.idea.lang.references;
2+
3+
import com.intellij.find.usages.api.Usage;
4+
import com.intellij.find.usages.api.UsageSearchParameters;
5+
import com.intellij.find.usages.api.UsageSearcher;
6+
import com.intellij.model.search.LeafOccurrence;
7+
import com.intellij.model.search.LeafOccurrenceMapper;
8+
import com.intellij.model.search.SearchContext;
9+
import com.intellij.model.search.SearchService;
10+
import com.intellij.psi.PsiElement;
11+
import com.intellij.util.Query;
12+
import org.jetbrains.annotations.NotNull;
13+
import org.jetbrains.annotations.Nullable;
14+
import org.nixos.idea.lang.NixLanguage;
15+
import org.nixos.idea.lang.references.symbol.NixSymbol;
16+
import org.nixos.idea.lang.references.symbol.NixUserSymbol;
17+
import org.nixos.idea.psi.NixPsiElement;
18+
import org.nixos.idea.settings.NixSymbolSettings;
19+
20+
import java.util.Collection;
21+
import java.util.List;
22+
23+
@SuppressWarnings("UnstableApiUsage")
24+
public final class NixUsageSearcher implements UsageSearcher, LeafOccurrenceMapper.Parameterized<NixSymbol, Usage> {
25+
26+
@Override
27+
public @NotNull Collection<? extends Usage> collectImmediateResults(@NotNull UsageSearchParameters parameters) {
28+
if (!NixSymbolSettings.getInstance().getEnabled()) {
29+
return List.of();
30+
} else if (parameters.getTarget() instanceof NixUserSymbol symbol) {
31+
return symbol.getDeclarations().stream().map(NixUsage::new).toList();
32+
} else {
33+
return List.of();
34+
}
35+
}
36+
37+
@Override
38+
public @Nullable Query<? extends Usage> collectSearchRequest(@NotNull UsageSearchParameters parameters) {
39+
if (!NixSymbolSettings.getInstance().getEnabled()) {
40+
return null;
41+
} else if (parameters.getTarget() instanceof NixSymbol symbol) {
42+
String name = symbol.getName();
43+
return SearchService.getInstance()
44+
.searchWord(parameters.getProject(), name)
45+
.inContexts(SearchContext.IN_CODE_HOSTS, SearchContext.IN_CODE)
46+
.inScope(parameters.getSearchScope())
47+
.inFilesWithLanguage(NixLanguage.INSTANCE)
48+
.buildQuery(LeafOccurrenceMapper.withPointer(symbol.createPointer(), this));
49+
} else {
50+
return null;
51+
}
52+
}
53+
54+
@Override
55+
public @NotNull Collection<? extends Usage> mapOccurrence(@NotNull NixSymbol symbol, @NotNull LeafOccurrence occurrence) {
56+
for (PsiElement element = occurrence.getStart(); element != null && element != occurrence.getScope(); element = element.getParent()) {
57+
if (element instanceof NixPsiElement nixElement) {
58+
List<NixUsage> usages = nixElement.getOwnReferences().stream()
59+
.filter(reference -> reference.resolvesTo(symbol))
60+
.map(NixUsage::new)
61+
.toList();
62+
if (!usages.isEmpty()) {
63+
return usages;
64+
}
65+
}
66+
}
67+
return List.of();
68+
}
69+
}

0 commit comments

Comments
 (0)