diff --git a/src/main/java/org/nixos/idea/lang/references/NixUsage.java b/src/main/java/org/nixos/idea/lang/references/NixUsage.java index 111ca62d..dd8db499 100644 --- a/src/main/java/org/nixos/idea/lang/references/NixUsage.java +++ b/src/main/java/org/nixos/idea/lang/references/NixUsage.java @@ -10,6 +10,7 @@ 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 { @@ -57,7 +58,8 @@ private NixUsage(@NotNull Pointer pointer, @NotNull NixPsiElement iden @Override public boolean getDeclaration() { - return myIsDeclaration; + // IDEA removes all instances which return true from the result of the usage search + return !NixSymbolSettings.getInstance().getShowDeclarationsAsUsages() && myIsDeclaration; } @Override diff --git a/src/main/java/org/nixos/idea/lang/references/NixUsageSearcher.java b/src/main/java/org/nixos/idea/lang/references/NixUsageSearcher.java index 945745da..f4618ff0 100644 --- a/src/main/java/org/nixos/idea/lang/references/NixUsageSearcher.java +++ b/src/main/java/org/nixos/idea/lang/references/NixUsageSearcher.java @@ -1,6 +1,5 @@ package org.nixos.idea.lang.references; -import com.intellij.find.usages.api.SearchTarget; import com.intellij.find.usages.api.Usage; import com.intellij.find.usages.api.UsageSearchParameters; import com.intellij.find.usages.api.UsageSearcher; @@ -16,6 +15,7 @@ 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; @@ -25,7 +25,9 @@ public final class NixUsageSearcher implements UsageSearcher, LeafOccurrenceMapp @Override public @NotNull Collection collectImmediateResults(@NotNull UsageSearchParameters parameters) { - if (parameters.getTarget() instanceof NixUserSymbol symbol) { + 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(); @@ -34,8 +36,9 @@ public final class NixUsageSearcher implements UsageSearcher, LeafOccurrenceMapp @Override public @Nullable Query collectSearchRequest(@NotNull UsageSearchParameters parameters) { - SearchTarget target = parameters.getTarget(); - if (target instanceof NixSymbol symbol) { + if (!NixSymbolSettings.getInstance().getEnabled()) { + return null; + } else if (parameters.getTarget() instanceof NixSymbol symbol) { String name = symbol.getName(); return SearchService.getInstance() .searchWord(parameters.getProject(), name) diff --git a/src/main/java/org/nixos/idea/lang/references/symbol/NixUserSymbol.java b/src/main/java/org/nixos/idea/lang/references/symbol/NixUserSymbol.java index beb144bc..4de980f3 100644 --- a/src/main/java/org/nixos/idea/lang/references/symbol/NixUserSymbol.java +++ b/src/main/java/org/nixos/idea/lang/references/symbol/NixUserSymbol.java @@ -16,6 +16,7 @@ import org.nixos.idea.lang.highlighter.NixTextAttributes; import org.nixos.idea.lang.references.NixSymbolDeclaration; import org.nixos.idea.psi.NixDeclarationHost; +import org.nixos.idea.settings.NixSymbolSettings; import javax.swing.Icon; import java.lang.invoke.MethodHandles; @@ -23,6 +24,7 @@ import java.util.Collection; import java.util.List; import java.util.Objects; +import java.util.stream.Stream; @SuppressWarnings("UnstableApiUsage") public final class NixUserSymbol extends NixSymbol @@ -93,7 +95,12 @@ public NixUserSymbol(@NotNull NixDeclarationHost host, @NotNull List pat @Override public @NotNull Collection getNavigationTargets(@NotNull Project project) { assert myHost.getProject().equals(project); - return myHost.getDeclarations(myPath).stream().map(NixSymbolDeclaration::navigationTarget).toList(); + Stream targets = myHost.getDeclarations(myPath).stream().map(NixSymbolDeclaration::navigationTarget); + if (NixSymbolSettings.getInstance().getJumpToFirstDeclaration()) { + return targets.findFirst().map(List::of).orElse(List.of()); + } else { + return targets.toList(); + } } @Override diff --git a/src/main/java/org/nixos/idea/psi/impl/AbstractNixDeclarationHost.java b/src/main/java/org/nixos/idea/psi/impl/AbstractNixDeclarationHost.java index c9390638..cc1d06be 100644 --- a/src/main/java/org/nixos/idea/psi/impl/AbstractNixDeclarationHost.java +++ b/src/main/java/org/nixos/idea/psi/impl/AbstractNixDeclarationHost.java @@ -18,6 +18,7 @@ import org.nixos.idea.psi.NixParameter; import org.nixos.idea.psi.NixPsiElement; import org.nixos.idea.psi.NixPsiUtil; +import org.nixos.idea.settings.NixSymbolSettings; import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; @@ -84,16 +85,20 @@ public final boolean isDeclaringVariables() { } private @NotNull Symbols getSymbols() { - if (mySymbols == null) { - MY_SYMBOLS.compareAndSet(this, null, initSymbols()); + NixSymbolSettings settings = NixSymbolSettings.getInstance(); + Symbols symbols = mySymbols; + if (symbols == null || symbols.isOutdated(settings)) { + MY_SYMBOLS.compareAndSet(this, symbols, initSymbols(settings)); Objects.requireNonNull(mySymbols, "initSymbols() must not return null"); } return mySymbols; } - private @NotNull Symbols initSymbols() { - Symbols symbols = new Symbols(); - if (this instanceof NixExprLet let) { + private @NotNull Symbols initSymbols(@NotNull NixSymbolSettings settings) { + Symbols symbols = new Symbols(settings); + if (!NixSymbolSettings.getInstance().getEnabled()) { + return symbols; + } else if (this instanceof NixExprLet let) { collectBindDeclarations(symbols, let.getBindList(), true); } else if (this instanceof NixExprAttrs attrs) { collectBindDeclarations(symbols, attrs.getBindList(), NixPsiUtil.isLegacyLet(attrs)); @@ -140,6 +145,15 @@ private final class Symbols { private final @NotNull Map, List> myDeclarationsBySymbol = new HashMap<>(); private final @NotNull Map> myDeclarationsByElement = new HashMap<>(); private final @NotNull Set myVariables = new HashSet<>(); + private final long mySettingsModificationCount; + + private Symbols(@NotNull NixSymbolSettings settings) { + mySettingsModificationCount = settings.getStateModificationCount(); + } + + private boolean isOutdated(@NotNull NixSymbolSettings settings) { + return mySettingsModificationCount != settings.getStateModificationCount(); + } private void addBindAttr(@NotNull NixPsiElement element, @NotNull NixAttrPath attrPath, @NotNull NixUserSymbol.Type type) { if (!checkDeclarationHost(element)) { diff --git a/src/main/java/org/nixos/idea/psi/impl/AbstractNixPsiElement.java b/src/main/java/org/nixos/idea/psi/impl/AbstractNixPsiElement.java index 45b9a9c3..dc49ef1b 100644 --- a/src/main/java/org/nixos/idea/psi/impl/AbstractNixPsiElement.java +++ b/src/main/java/org/nixos/idea/psi/impl/AbstractNixPsiElement.java @@ -18,6 +18,7 @@ import org.nixos.idea.psi.NixExprVar; import org.nixos.idea.psi.NixPsiElement; import org.nixos.idea.psi.NixPsiUtil; +import org.nixos.idea.settings.NixSymbolSettings; import java.util.Collection; import java.util.List; @@ -59,7 +60,9 @@ abstract class AbstractNixPsiElement extends ASTWrapperPsiElement implements Nix @Override @SuppressWarnings("UnstableApiUsage") public final @NotNull Collection getOwnReferences() { - if (this instanceof NixExprVar) { + if (!NixSymbolSettings.getInstance().getEnabled()) { + return List.of(); + } else if (this instanceof NixExprVar) { return List.of(new NixScopeReference(this, this, getText())); } else if (this instanceof NixExprSelect) { // TODO: Attribute reference support diff --git a/src/main/java/org/nixos/idea/settings/NixSymbolConfigurable.kt b/src/main/java/org/nixos/idea/settings/NixSymbolConfigurable.kt new file mode 100644 index 00000000..9c4a79bb --- /dev/null +++ b/src/main/java/org/nixos/idea/settings/NixSymbolConfigurable.kt @@ -0,0 +1,67 @@ +package org.nixos.idea.settings + +import com.intellij.openapi.options.BoundSearchableConfigurable +import com.intellij.openapi.options.Configurable +import com.intellij.openapi.ui.DialogPanel +import com.intellij.ui.components.JBCheckBox +import com.intellij.ui.dsl.builder.Cell +import com.intellij.ui.dsl.builder.bind +import com.intellij.ui.dsl.builder.bindSelected +import com.intellij.ui.dsl.builder.panel +import com.intellij.ui.dsl.builder.selected + +class NixSymbolConfigurable : + BoundSearchableConfigurable("Nix Symbols", "org.nixos.idea.settings.NixSymbolConfigurable"), + Configurable.Beta { + + override fun createPanel(): DialogPanel { + val settings = NixSymbolSettings.getInstance() + lateinit var enabledCheckBox: Cell + return panel { + row { + enabledCheckBox = checkBox("Use Symbol API to resolve references and find usages") + .bindSelected(settings::enabled) + } + rowsRange { + groupRowsRange("Go To Declaration") { + buttonsGroup { + row { + radioButton("Go to first declaration", true) + radioButton("Ask when symbol has multiple declarations", false) + }.rowComment( + """ + Attribute sets and let-expressions may contain + multiple indirect declarations of the same symbol. + """.trimIndent() + ).contextHelp( + """ + The following code block contains three + declarations of “common”: +
+                            let
+                              zero = 0;
+                              common.a = 1;
+                              common.b = 2;
+                              common.c = 3;
+                            in
+                              common
+                            
+ If you run Go To Declaration on the last line, + this setting defines whether + the action jumps directly to common.a + (the first declaration), + or opens a popup asking which declaration you want to see. + """.trimIndent() + ) + }.bind(settings::jumpToFirstDeclaration) + } + groupRowsRange("Find Usages") { + row { + checkBox("Show declarations as part of the results") + .bindSelected(settings::showDeclarationsAsUsages) + } + } + }.enabledIf(enabledCheckBox.selected) + } + } +} diff --git a/src/main/java/org/nixos/idea/settings/NixSymbolSettings.kt b/src/main/java/org/nixos/idea/settings/NixSymbolSettings.kt new file mode 100644 index 00000000..d4a12f1c --- /dev/null +++ b/src/main/java/org/nixos/idea/settings/NixSymbolSettings.kt @@ -0,0 +1,30 @@ +package org.nixos.idea.settings + +import com.intellij.openapi.application.ApplicationManager +import com.intellij.openapi.components.BaseState +import com.intellij.openapi.components.SimplePersistentStateComponent +import com.intellij.openapi.components.State +import com.intellij.openapi.components.Storage +import org.nixos.idea.settings.SimplePersistentStateComponentHelper.delegate + +@State(name = "NixSymbolSettings", storages = [Storage("nix-idea.xml")]) +class NixSymbolSettings : SimplePersistentStateComponent(State()) { + + class State : BaseState() { + var enabled by property(true) + var jumpToFirstDeclaration by property(true) + var showDeclarationsAsUsages by property(false) + } + + companion object { + @JvmStatic + fun getInstance(): NixSymbolSettings { + return ApplicationManager.getApplication().getService(NixSymbolSettings::class.java) + } + } + + var enabled: Boolean by delegate(State::enabled) + var jumpToFirstDeclaration by delegate(State::jumpToFirstDeclaration) + var showDeclarationsAsUsages: Boolean by delegate(State::showDeclarationsAsUsages) + +} diff --git a/src/main/java/org/nixos/idea/settings/SimplePersistentStateComponentHelper.kt b/src/main/java/org/nixos/idea/settings/SimplePersistentStateComponentHelper.kt new file mode 100644 index 00000000..715ff279 --- /dev/null +++ b/src/main/java/org/nixos/idea/settings/SimplePersistentStateComponentHelper.kt @@ -0,0 +1,38 @@ +package org.nixos.idea.settings + +import com.intellij.openapi.components.BaseState +import com.intellij.openapi.components.SimplePersistentStateComponent +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KMutableProperty1 +import kotlin.reflect.KProperty + +internal object SimplePersistentStateComponentHelper { + + /** + * Creates property which delegates every access to the given property of the state. + * + * ```kotlin + * @State(name = "SomeSettings", storages = [Storage(...)]) + * class SomeSettings : SimplePersistentStateComponent(State()) { + * class State : BaseState() { + * // The internal storage of the configured values + * var enabled by property(true) + * } + * + * // Makes the property publicly accessible + * var enabled: Boolean by delegate(State::enabled) + * } + * ``` + */ + fun delegate(prop: KMutableProperty1): ReadWriteProperty, V> { + return object : ReadWriteProperty, V> { + override fun getValue(thisRef: SimplePersistentStateComponent, property: KProperty<*>): V { + return prop.get(thisRef.state) + } + + override fun setValue(thisRef: SimplePersistentStateComponent, property: KProperty<*>, value: V) { + prop.set(thisRef.state, value) + } + } + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 7b16b6be..74f21076 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -48,6 +48,15 @@ id="org.nixos.idea.settings.NixIDEASettings" instance="org.nixos.idea.settings.NixIDEASettings" /> + + + + diff --git a/src/test/java/org/nixos/idea/lang/references/SymbolNavigationTest.java b/src/test/java/org/nixos/idea/lang/references/SymbolNavigationTest.java index 302da4fb..6f25cfeb 100644 --- a/src/test/java/org/nixos/idea/lang/references/SymbolNavigationTest.java +++ b/src/test/java/org/nixos/idea/lang/references/SymbolNavigationTest.java @@ -19,6 +19,7 @@ import com.intellij.testFramework.fixtures.CodeInsightTestFixture; import org.intellij.lang.annotations.Language; import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DynamicNode; import org.junit.jupiter.api.TestFactory; @@ -29,6 +30,7 @@ import org.nixos.idea.lang.references.symbol.NixSymbol; import org.nixos.idea.lang.references.symbol.NixUserSymbol; import org.nixos.idea.psi.NixDeclarationHost; +import org.nixos.idea.settings.NixSymbolSettings; import java.util.ArrayList; import java.util.Collection; @@ -54,6 +56,11 @@ final class SymbolNavigationTest { myFixture = fixture; } + @BeforeEach + void setUp() { + NixSymbolSettings.getInstance().setJumpToFirstDeclaration(false); + } + @TestFactory Stream simple_assignment() { return createTests("""