diff --git a/CHANGELOG.md b/CHANGELOG.md index 2219f9e..7a61aa3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # KdbInsideBrains Changelog +## [5.12.0] + +### Added + +- Symbol is parsed as a variable if used in any 'set' expression, like: _`myvariable set ..._ or _set[`myvariable;...]_ +- Symbols declared as a variable in any form support cross-links to/from the declaration and can be renamed +- Structure view performance improved + ## [5.11.1] ### Fixed diff --git a/src/main/java/icons/KdbIcons.java b/src/main/java/icons/KdbIcons.java index 06a1b07..5327280 100644 --- a/src/main/java/icons/KdbIcons.java +++ b/src/main/java/icons/KdbIcons.java @@ -3,7 +3,6 @@ import com.intellij.icons.AllIcons; import com.intellij.openapi.util.IconLoader; import com.intellij.ui.IconManager; -import com.intellij.ui.LayeredIcon; import org.jetbrains.annotations.NotNull; import javax.swing.*; @@ -13,11 +12,16 @@ public final class KdbIcons { return IconLoader.getIcon(path, KdbIcons.class); } - private static @NotNull LayeredIcon lineIcons(@NotNull Icon icon1, @NotNull Icon icon2) { - LayeredIcon icon = new LayeredIcon(2); - icon.setIcon(icon1, 0, 0, 0); - icon.setIcon(icon2, 1, icon1.getIconWidth(), 0); - return icon; + private static @NotNull Icon row(@NotNull Icon icon1, @NotNull Icon icon2) { + return IconManager.getInstance().createRowIcon(icon1, icon2); +// LayeredIcon icon = new LayeredIcon(2); +// icon.setIcon(icon1, 0, 0, 0); +// icon.setIcon(icon2, 1, icon1.getIconWidth(), 0); +// return icon; + } + + private static @NotNull Icon layer(@NotNull Icon icon1, @NotNull Icon icon2) { + return IconManager.getInstance().createLayered(icon1, icon2); } public static final class Main { @@ -35,7 +39,7 @@ public static final class Main { public static final class Scope { public static final @NotNull Icon Icon = AllIcons.Ide.LocalScope; public static final @NotNull Icon Local = AllIcons.Ide.LocalScope; - public static final @NotNull Icon Shared = IconManager.getInstance().createLayered(AllIcons.Ide.LocalScope, AllIcons.Nodes.Shared); + public static final @NotNull Icon Shared = layer(AllIcons.Ide.LocalScope, AllIcons.Nodes.Shared); } public static final class Chart { @@ -108,11 +112,14 @@ public static final class Console { } public static final class Node { + public static final @NotNull Icon PublicItem = AllIcons.Nodes.C_public; + public static final @NotNull Icon PrivateItem = AllIcons.Nodes.C_private; + public static final @NotNull Icon Package = AllIcons.Nodes.Folder; public static final @NotNull Icon Instance = load("/org/kdb/inside/brains/icons/instance.svg"); - public static final @NotNull Icon InstanceQueryRunning = lineIcons(Instance, AllIcons.Actions.Execute); - public static final @NotNull Icon InstanceQueryCancelled = lineIcons(Instance, AllIcons.Actions.Suspend); + public static final @NotNull Icon InstanceQueryRunning = row(Instance, AllIcons.Actions.Execute); + public static final @NotNull Icon InstanceQueryCancelled = row(Instance, AllIcons.Actions.Suspend); public static final @NotNull Icon NewPackage = AllIcons.Actions.NewFolder; public static final @NotNull Icon NewInstance = load("/org/kdb/inside/brains/icons/newInstance.svg"); @@ -123,22 +130,25 @@ public static final class Node { public static final @NotNull Icon Context = load("/org/kdb/inside/brains/icons/nodes/context.svg"); public static final @NotNull Icon Symbol = AllIcons.Nodes.Static; public static final @NotNull Icon Lambda = AllIcons.Nodes.Lambda; + public static final @NotNull Icon LambdaPublic = row(Lambda, Node.PublicItem); + public static final @NotNull Icon LambdaPrivate = row(Lambda, Node.PrivateItem); public static final @NotNull Icon Variable = AllIcons.Nodes.Variable; + public static final @NotNull Icon VariablePublic = row(Variable, Node.PublicItem); + public static final @NotNull Icon VariablePrivate = row(Variable, Node.PrivateItem); public static final @NotNull Icon Parameter = AllIcons.Nodes.Parameter; public static final @NotNull Icon Function = AllIcons.Nodes.Function; public static final @NotNull Icon Keyword = AllIcons.Nodes.Constant; public static final @NotNull Icon Namespace = AllIcons.Nodes.Package; public static final @NotNull Icon Table = AllIcons.Nodes.DataTables; + public static final @NotNull Icon TablePublic = row(Table, Node.PublicItem); + public static final @NotNull Icon TablePrivate = row(Table, Node.PrivateItem); public static final @NotNull Icon TableKeyColumn = load("/org/kdb/inside/brains/icons/nodes/keyColumn.svg"); public static final @NotNull Icon TableValueColumn = load("/org/kdb/inside/brains/icons/nodes/valueColumn.svg"); public static final @NotNull Icon ChangeColor = AllIcons.Actions.Colors; public static final @NotNull Icon ShowConnectionFilter = AllIcons.Actions.Show; - public static final @NotNull Icon PublicItem = AllIcons.Nodes.C_public; - public static final @NotNull Icon PrivateItem = AllIcons.Nodes.C_private; - public static final @NotNull Icon SystemNamespaces = AllIcons.Nodes.Private; public static final @NotNull Icon GroupTables = load("/org/kdb/inside/brains/icons/nodes/groupTables.svg"); diff --git a/src/main/java/org/kdb/inside/brains/lang/QChooseByNameContributor.java b/src/main/java/org/kdb/inside/brains/lang/QChooseByNameContributor.java index 6239257..f5ca63d 100644 --- a/src/main/java/org/kdb/inside/brains/lang/QChooseByNameContributor.java +++ b/src/main/java/org/kdb/inside/brains/lang/QChooseByNameContributor.java @@ -6,11 +6,13 @@ import com.intellij.util.ArrayUtilRt; import com.intellij.util.indexing.FindSymbolParameters; import org.jetbrains.annotations.NotNull; +import org.kdb.inside.brains.psi.index.DeclarationRef; import org.kdb.inside.brains.psi.index.QIndexService; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.Objects; public final class QChooseByNameContributor implements ChooseByNameContributor { @Override @@ -26,7 +28,7 @@ public final class QChooseByNameContributor implements ChooseByNameContributor { @Override public NavigationItem @NotNull [] getItemsByName(String name, String pattern, Project project, boolean includeNonProjectItems) { final FindSymbolParameters simple = FindSymbolParameters.simple(project, includeNonProjectItems); - final Collection result = QIndexService.getInstance(project).getDeclarations(name, simple.getSearchScope()); - return result.isEmpty() ? NavigationItem.EMPTY_NAVIGATION_ITEM_ARRAY : result.toArray(NavigationItem.EMPTY_NAVIGATION_ITEM_ARRAY); + final Collection declarations = QIndexService.getInstance(project).getDeclarations(name, simple.getSearchScope()); + return declarations.isEmpty() ? NavigationItem.EMPTY_NAVIGATION_ITEM_ARRAY : declarations.stream().map(DeclarationRef::getNavigationItem).filter(Objects::nonNull).toArray(NavigationItem[]::new); } } diff --git a/src/main/java/org/kdb/inside/brains/psi/QIconProvider.java b/src/main/java/org/kdb/inside/brains/psi/QIconProvider.java index 2a97410..ef086eb 100644 --- a/src/main/java/org/kdb/inside/brains/psi/QIconProvider.java +++ b/src/main/java/org/kdb/inside/brains/psi/QIconProvider.java @@ -3,8 +3,6 @@ import com.intellij.ide.IconProvider; import com.intellij.openapi.project.DumbAware; import com.intellij.psi.PsiElement; -import com.intellij.ui.IconManager; -import com.intellij.ui.icons.RowIcon; import com.intellij.util.BitUtil; import icons.KdbIcons; import org.jetbrains.annotations.NotNull; @@ -21,9 +19,9 @@ public static Icon getColumnIcon(@NotNull QTableColumn column) { @Override public @Nullable Icon getIcon(@NotNull PsiElement element, int flags) { - final boolean visibility = BitUtil.isSet(flags, ICON_FLAG_VISIBILITY); - - if (element instanceof QImport) { + if (element instanceof QFile) { + return KdbIcons.Node.File; + } else if (element instanceof QImport) { return KdbIcons.Node.Import; } else if (element instanceof QCommand) { return KdbIcons.Node.Command; @@ -36,7 +34,7 @@ public static Icon getColumnIcon(@NotNull QTableColumn column) { } else if (element instanceof QLambdaExpr) { return KdbIcons.Node.Lambda; } else if (element instanceof QAssignmentExpr assignment) { - return getAssignmentIcon(assignment, visibility); + return getAssignmentIcon(assignment, BitUtil.isSet(flags, ICON_FLAG_VISIBILITY)); } else if (element instanceof QVarDeclaration declaration) { return getIcon(declaration.getParent(), flags); } @@ -49,17 +47,12 @@ private Icon getAssignmentIcon(QAssignmentExpr assignment, boolean visibility) { return null; } - Icon i = getExpressionIcon(expression); if (visibility) { - final RowIcon icon = IconManager.getInstance().createLayeredIcon(assignment, i, 0); - icon.setIcon(getVisibilityIcon(assignment), 1); - return icon; + final boolean global = QPsiUtil.isGlobalDeclaration(assignment); + return getExpressionIcon(expression, global); + } else { + return getExpressionIcon(expression); } - return i; - } - - private Icon getVisibilityIcon(QAssignmentExpr assignment) { - return QPsiUtil.isGlobalDeclaration(assignment) ? KdbIcons.Node.PublicItem : KdbIcons.Node.PrivateItem; } private Icon getExpressionIcon(QExpression expression) { @@ -70,4 +63,13 @@ private Icon getExpressionIcon(QExpression expression) { } return KdbIcons.Node.Variable; } + + private Icon getExpressionIcon(QExpression expression, boolean global) { + if (expression instanceof QLambdaExpr) { + return global ? KdbIcons.Node.LambdaPublic : KdbIcons.Node.LambdaPrivate; + } else if (expression instanceof QTableExpr) { + return global ? KdbIcons.Node.TablePublic : KdbIcons.Node.TablePrivate; + } + return global ? KdbIcons.Node.VariablePublic : KdbIcons.Node.VariablePrivate; + } } diff --git a/src/main/java/org/kdb/inside/brains/psi/impl/QSymbolElementImpl.java b/src/main/java/org/kdb/inside/brains/psi/impl/QSymbolElementImpl.java new file mode 100644 index 0000000..596a544 --- /dev/null +++ b/src/main/java/org/kdb/inside/brains/psi/impl/QSymbolElementImpl.java @@ -0,0 +1,56 @@ +package org.kdb.inside.brains.psi.impl; + +import com.intellij.lang.ASTNode; +import com.intellij.navigation.ItemPresentation; +import com.intellij.psi.PsiElement; +import com.intellij.util.IncorrectOperationException; +import icons.KdbIcons; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.kdb.inside.brains.psi.QPsiUtil; +import org.kdb.inside.brains.psi.QSymbol; + +import java.util.Optional; + +public class QSymbolElementImpl extends QPsiElementImpl implements QSymbol { + public QSymbolElementImpl(ASTNode node) { + super(node); + } + + @Override + public String getName() { + return getText().substring(1); + } + + @Override + public @Nullable String getQualifiedName() { + return getName(); + } + + @Override + public ItemPresentation getPresentation() { + return new VariablePresentation(this, KdbIcons.Node.Symbol); + } + + @Override + public int getTextOffset() { + return super.getTextOffset() + 1; + } + + @Override + public @Nullable PsiElement getNameIdentifier() { + return this; + } + + @Override + public PsiElement setName(@NotNull String newName) throws IncorrectOperationException { + Optional.ofNullable(QPsiUtil.createSymbol(getProject(), newName.charAt(0) == '`' ? newName : "`" + newName)) + .map(QSymbol::getFirstChild) + .map(PsiElement::getNode) + .ifPresent(newKeyNode -> { + final ASTNode keyNode = getNode().getFirstChildNode(); + getNode().replaceChild(keyNode, newKeyNode); + }); + return this; + } +} \ No newline at end of file diff --git a/src/main/java/org/kdb/inside/brains/psi/impl/QVariableBase.java b/src/main/java/org/kdb/inside/brains/psi/impl/QVariableBase.java index 208df73..c6817c0 100644 --- a/src/main/java/org/kdb/inside/brains/psi/impl/QVariableBase.java +++ b/src/main/java/org/kdb/inside/brains/psi/impl/QVariableBase.java @@ -1,6 +1,7 @@ package org.kdb.inside.brains.psi.impl; import com.intellij.lang.ASTNode; +import com.intellij.psi.PsiElement; import com.intellij.psi.util.PsiTreeUtil; import org.jetbrains.annotations.NotNull; import org.kdb.inside.brains.psi.*; @@ -45,8 +46,9 @@ private String calculateQualifiedName() { return name; } - // It's namespace name itself - ignore - if (getParent() instanceof QContext) { + // It's namespace name itself or table column - ignore + final PsiElement parent = getParent(); + if (parent instanceof QContext || parent instanceof QTableColumn) { return name; } diff --git a/src/main/java/org/kdb/inside/brains/psi/impl/QVariableDeclarationImpl.java b/src/main/java/org/kdb/inside/brains/psi/impl/QVariableDeclarationImpl.java index 5c9fb7a..6b554af 100644 --- a/src/main/java/org/kdb/inside/brains/psi/impl/QVariableDeclarationImpl.java +++ b/src/main/java/org/kdb/inside/brains/psi/impl/QVariableDeclarationImpl.java @@ -3,7 +3,6 @@ import com.intellij.lang.ASTNode; import com.intellij.navigation.ItemPresentation; import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; import icons.KdbIcons; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -11,7 +10,6 @@ import org.kdb.inside.brains.psi.QVarDeclaration; import org.kdb.inside.brains.psi.QVariable; -import javax.swing.*; import java.util.Optional; public abstract class QVariableDeclarationImpl extends QVariableBase implements QVarDeclaration { @@ -38,26 +36,6 @@ public PsiElement setName(@NotNull String newName) { @Override public ItemPresentation getPresentation() { - return new ItemPresentation() { - @NotNull - @Override - public String getPresentableText() { - return getQualifiedName(); - } - - @NotNull - @Override - public String getLocationString() { - final PsiFile containingFile = getContainingFile(); - return containingFile == null ? "" : containingFile.getName(); - } - - @NotNull - @Override - public Icon getIcon(boolean unused) { - // TODO: Not implemented yet. Icon should depends on the variable type and visibility - return KdbIcons.Node.Variable; - } - }; + return new VariablePresentation(this, KdbIcons.Node.Variable); } } diff --git a/src/main/java/org/kdb/inside/brains/psi/impl/VariablePresentation.java b/src/main/java/org/kdb/inside/brains/psi/impl/VariablePresentation.java new file mode 100644 index 0000000..84e7e15 --- /dev/null +++ b/src/main/java/org/kdb/inside/brains/psi/impl/VariablePresentation.java @@ -0,0 +1,34 @@ +package org.kdb.inside.brains.psi.impl; + +import com.intellij.navigation.ItemPresentation; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiQualifiedNamedElement; +import org.jetbrains.annotations.Nullable; + +import javax.swing.*; + +class VariablePresentation implements ItemPresentation { + private final Icon icon; + private final PsiQualifiedNamedElement element; + + public VariablePresentation(PsiQualifiedNamedElement element, Icon icon) { + this.element = element; + this.icon = icon; + } + + @Override + public @Nullable Icon getIcon(boolean unused) { + return icon; + } + + @Override + public @Nullable String getPresentableText() { + return element.getQualifiedName(); + } + + @Override + public @Nullable String getLocationString() { + final PsiFile containingFile = element.getContainingFile(); + return containingFile == null ? "" : containingFile.getName(); + } +} diff --git a/src/main/java/org/kdb/inside/brains/psi/index/DeclarationRef.java b/src/main/java/org/kdb/inside/brains/psi/index/DeclarationRef.java new file mode 100644 index 0000000..daa2d36 --- /dev/null +++ b/src/main/java/org/kdb/inside/brains/psi/index/DeclarationRef.java @@ -0,0 +1,92 @@ +package org.kdb.inside.brains.psi.index; + +import com.intellij.navigation.NavigationItem; +import com.intellij.psi.PsiElement; +import org.kdb.inside.brains.psi.*; + +public abstract class DeclarationRef { + private DeclarationRef() { + } + + public static DeclarationRef of(QSymbol symbol) { + return new SymbolRef(symbol); + } + + public static DeclarationRef of(QVarDeclaration declaration) { + return new VariableRef(declaration); + } + + public abstract PsiElement getElement(); + + public abstract NavigationItem getNavigationItem(); + + public abstract QExpression getExpression(); + + public abstract boolean isGlobalDeclaration(); + + public void navigate(boolean requestFocus) { + final NavigationItem navigationItem = getNavigationItem(); + if (navigationItem != null) { + navigationItem.navigate(requestFocus); + } + } + + private static abstract class BaseRef extends DeclarationRef { + protected final Element element; + + public BaseRef(Element element) { + this.element = element; + } + + @Override + public final PsiElement getElement() { + return element; + } + } + + private static class SymbolRef extends BaseRef { + private SymbolRef(QSymbol symbol) { + super(symbol); + } + + @Override + public boolean isGlobalDeclaration() { + return true; + } + + @Override + public QExpression getExpression() { + return null; + } + + @Override + public NavigationItem getNavigationItem() { + return null; + } + } + + private static class VariableRef extends BaseRef { + public VariableRef(QVarDeclaration declaration) { + super(declaration); + } + + @Override + public NavigationItem getNavigationItem() { + return element; + } + + @Override + public boolean isGlobalDeclaration() { + return QPsiUtil.isGlobalDeclaration(element); + } + + @Override + public QExpression getExpression() { + final PsiElement parent = element.getParent(); + if (!(parent instanceof QAssignmentExpr assignment)) { + return null; + } + return assignment.getExpression(); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/kdb/inside/brains/psi/index/QDataIndexer.java b/src/main/java/org/kdb/inside/brains/psi/index/QDataIndexer.java index 3d99b41..30caff0 100644 --- a/src/main/java/org/kdb/inside/brains/psi/index/QDataIndexer.java +++ b/src/main/java/org/kdb/inside/brains/psi/index/QDataIndexer.java @@ -21,7 +21,7 @@ import static org.kdb.inside.brains.psi.QTypes.*; public class QDataIndexer implements DataIndexer, FileContent> { - protected static final int VERSION = 14; + protected static final int VERSION = 15; private static final Logger log = Logger.getInstance(QDataIndexer.class); @@ -92,6 +92,41 @@ private int[] findAllOffsets(CharSequence text) { return indexes.toIntArray(); } + static boolean containsSetBackward(CharSequence text, int startPosition) { + int i = startPosition - 1; // ignore symbol char ` + + // find bracket ignoring spaces + for (; i >= 0; i--) { + final char ch = text.charAt(i); + if (Character.isWhitespace(ch)) { + continue; + } + if (ch == '[') { + i--; + break; + } else { + // no bracket found + return false; + } + } + + // ignore spaces + while (i >= 0 && Character.isWhitespace(text.charAt(i))) { + i--; + } + // inverted 'set' and one space before or beginning of the text + return i >= 2 && text.charAt(i) == 't' && text.charAt(i - 1) == 'e' && text.charAt(i - 2) == 's' && (i == 2 || Character.isWhitespace(text.charAt(i - 3))); + } + + static boolean containsSetForward(CharSequence text, int startPosition) { + int i = startPosition; + final int length = text.length(); + while (i < length && Character.isWhitespace(text.charAt(i))) { + i++; + } + return i + 3 <= length && text.charAt(i) == 's' && text.charAt(i + 1) == 'e' && text.charAt(i + 2) == 't' && (i + 3 == length || Character.isWhitespace(text.charAt(i + 3))); + } + private Map.Entry processSymbol(LighterASTNode node, CharSequence text) { final TextRange range = new TextRange(node.getStartOffset() + 1, node.getEndOffset()); final String symbolValue = range.subSequence(text).toString(); @@ -106,6 +141,13 @@ private Map.Entry processSymbol(LighterASTNode nod return null; } + // If it's set - a variable definition + if (containsSetForward(text, range.getEndOffset() + 1)) { + return new AbstractMap.SimpleEntry<>(symbolValue, new IdentifierDescriptor(IdentifierType.VARIABLE, List.of(), range)); + } + if (containsSetBackward(text, range.getStartOffset() - 1)) { + return new AbstractMap.SimpleEntry<>(symbolValue, new IdentifierDescriptor(IdentifierType.VARIABLE, List.of(), range)); + } return new AbstractMap.SimpleEntry<>(symbolValue, new IdentifierDescriptor(IdentifierType.SYMBOL, List.of(), range)); } diff --git a/src/main/java/org/kdb/inside/brains/psi/index/QIndexService.java b/src/main/java/org/kdb/inside/brains/psi/index/QIndexService.java index 052cb32..8249dce 100644 --- a/src/main/java/org/kdb/inside/brains/psi/index/QIndexService.java +++ b/src/main/java/org/kdb/inside/brains/psi/index/QIndexService.java @@ -10,7 +10,7 @@ import com.intellij.util.indexing.FileBasedIndex; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.kdb.inside.brains.psi.QPsiUtil; +import org.kdb.inside.brains.psi.QSymbol; import org.kdb.inside.brains.psi.QVarDeclaration; import java.util.*; @@ -37,11 +37,11 @@ public String firstMatch(@NotNull Predicate keyPredicate, @NotNull Globa return result.value; } - public QVarDeclaration getFirstInFile(@NotNull String qualifiedName, @NotNull PsiFile file) { + public DeclarationRef getFirstInFile(@NotNull String qualifiedName, @NotNull PsiFile file) { final List> values = index.getValues(QIdentifiersIndex.INDEX_ID, qualifiedName, GlobalSearchScope.fileScope(file)); for (List value : values) { for (IdentifierDescriptor descriptor : value) { - final QVarDeclaration declaration = resolveDeclaration(descriptor, file.getVirtualFile()); + final DeclarationRef declaration = resolveDeclaration(descriptor, file.getVirtualFile()); if (declaration != null) { return declaration; } @@ -50,10 +50,10 @@ public QVarDeclaration getFirstInFile(@NotNull String qualifiedName, @NotNull Ps return null; } - public @Nullable QVarDeclaration getFirstGlobalDeclarations(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { - final Result r = new Result<>(); + public @Nullable DeclarationRef getFirstGlobalDeclarations(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { + final Result r = new Result<>(); processValues(s -> s.equals(qualifiedName), scope, (key, file, descriptor) -> { - final QVarDeclaration var = resolveGlobalDeclaration(descriptor, file); + final DeclarationRef var = resolveGlobalDeclaration(descriptor, file); if (var != null) { r.value = var; return false; @@ -63,10 +63,10 @@ public QVarDeclaration getFirstInFile(@NotNull String qualifiedName, @NotNull Ps return r.value; } - public @NotNull Collection getDeclarations(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { - final Set declarations = new HashSet<>(); + public @NotNull Collection getDeclarations(@NotNull String qualifiedName, @NotNull GlobalSearchScope scope) { + final Set declarations = new HashSet<>(); processValues(s -> s.equals(qualifiedName), scope, (key, file, descriptor) -> { - final QVarDeclaration var = resolveDeclaration(descriptor, file); + final DeclarationRef var = resolveDeclaration(descriptor, file); if (var != null) { declarations.add(var); } @@ -76,16 +76,16 @@ public QVarDeclaration getFirstInFile(@NotNull String qualifiedName, @NotNull Ps } @Nullable - private QVarDeclaration resolveGlobalDeclaration(IdentifierDescriptor descriptor, VirtualFile file) { - final QVarDeclaration var = resolveDeclaration(descriptor, file); - if (var != null && QPsiUtil.isGlobalDeclaration(var)) { + private DeclarationRef resolveGlobalDeclaration(IdentifierDescriptor descriptor, VirtualFile file) { + final DeclarationRef var = resolveDeclaration(descriptor, file); + if (var != null && var.isGlobalDeclaration()) { return var; } return null; } public void processValues(@NotNull Predicate keyPredicate, @NotNull GlobalSearchScope scope, @NotNull QIndexService.ValuesProcessor processor) { - // We can't process all in the same thread so we collect value values firstly and then process each + // We can't process all in the same thread, so we collect value values firstly and then process each // See https://github.com/kdbinsidebrains/plugin/issues/76 final List keys = new ArrayList<>(); processAllKeys(key -> { @@ -112,7 +112,7 @@ public void processValues(@NotNull Predicate keyPredicate, @NotNull Glob } @Nullable - private QVarDeclaration resolveDeclaration(IdentifierDescriptor descriptor, VirtualFile file) { + private DeclarationRef resolveDeclaration(IdentifierDescriptor descriptor, VirtualFile file) { if (descriptor.isSymbol()) { return null; } @@ -128,13 +128,17 @@ private QVarDeclaration resolveDeclaration(IdentifierDescriptor descriptor, Virt return null; } - if (el instanceof QVarDeclaration) { - return (QVarDeclaration) el; + if (el instanceof QVarDeclaration d) { + return DeclarationRef.of(d); } final PsiElement parent = el.getParent(); - if (parent instanceof QVarDeclaration) { - return (QVarDeclaration) parent; + if (parent instanceof QSymbol s) { + return DeclarationRef.of(s); + } + + if (parent instanceof QVarDeclaration d) { + return DeclarationRef.of(d); } return null; } diff --git a/src/main/java/org/kdb/inside/brains/psi/refs/QBaseReference.java b/src/main/java/org/kdb/inside/brains/psi/refs/QBaseReference.java index 6450fdf..0547c6f 100644 --- a/src/main/java/org/kdb/inside/brains/psi/refs/QBaseReference.java +++ b/src/main/java/org/kdb/inside/brains/psi/refs/QBaseReference.java @@ -4,7 +4,10 @@ import com.intellij.psi.*; import com.intellij.psi.search.GlobalSearchScope; import org.jetbrains.annotations.NotNull; -import org.kdb.inside.brains.psi.*; +import org.kdb.inside.brains.psi.QPsiElement; +import org.kdb.inside.brains.psi.QSymbol; +import org.kdb.inside.brains.psi.QVariable; +import org.kdb.inside.brains.psi.index.DeclarationRef; import org.kdb.inside.brains.psi.index.QIndexService; import java.util.Collection; @@ -83,25 +86,25 @@ protected ResolveResult[] resolveElement(T element) { } final QIndexService index = QIndexService.getInstance(element); - final QVarDeclaration initial = index.getFirstInFile(name, file); + final DeclarationRef initial = index.getFirstInFile(name, file); if (initial == null) { final GlobalSearchScope scope = GlobalSearchScope.allScope(element.getProject()); - final Collection declarations = QIndexService.getInstance(element).getDeclarations(name, scope); - return multi(declarations.stream().filter(QPsiUtil::isGlobalDeclaration)); + final Collection declarations = QIndexService.getInstance(element).getDeclarations(name, scope); + return multi(declarations.stream().filter(DeclarationRef::isGlobalDeclaration)); } return single(initial); } @NotNull - protected ResolveResult[] single(QVarDeclaration el) { - return new ResolveResult[]{new PsiElementResolveResult(el)}; + protected ResolveResult[] single(DeclarationRef el) { + return new ResolveResult[]{new PsiElementResolveResult(el.getElement())}; } - protected ResolveResult[] multi(Stream variables) { - return variables.map(PsiElementResolveResult::new).toArray(ResolveResult[]::new); + protected ResolveResult[] multi(Stream variables) { + return variables.map(DeclarationRef::getElement).map(PsiElementResolveResult::new).toArray(ResolveResult[]::new); } - protected ResolveResult[] multi(Collection variables) { + protected ResolveResult[] multi(Collection variables) { return multi(variables.stream()); } diff --git a/src/main/java/org/kdb/inside/brains/psi/refs/QSymbolReferenceProvider.java b/src/main/java/org/kdb/inside/brains/psi/refs/QSymbolReferenceProvider.java index 2d40634..5c23b2b 100644 --- a/src/main/java/org/kdb/inside/brains/psi/refs/QSymbolReferenceProvider.java +++ b/src/main/java/org/kdb/inside/brains/psi/refs/QSymbolReferenceProvider.java @@ -23,7 +23,7 @@ public QSymbolReference(@NotNull QSymbol element) { @Override protected String getQualifiedName(QSymbol element) { - return element.getText().substring(1); + return element.getQualifiedName(); } } } \ No newline at end of file diff --git a/src/main/java/org/kdb/inside/brains/psi/refs/QVariableReferenceProvider.java b/src/main/java/org/kdb/inside/brains/psi/refs/QVariableReferenceProvider.java index 633f44f..3749465 100644 --- a/src/main/java/org/kdb/inside/brains/psi/refs/QVariableReferenceProvider.java +++ b/src/main/java/org/kdb/inside/brains/psi/refs/QVariableReferenceProvider.java @@ -8,6 +8,7 @@ import com.intellij.util.ProcessingContext; import org.jetbrains.annotations.NotNull; import org.kdb.inside.brains.psi.*; +import org.kdb.inside.brains.psi.index.DeclarationRef; import java.util.ArrayList; import java.util.List; @@ -99,7 +100,7 @@ private ResolveResult[] resolveQuery(QVariable var, QQueryExpr query) { .toList(); final String qualifiedName = var.getQualifiedName(); - List res = new ArrayList<>(); + List res = new ArrayList<>(); for (QTableExpr table : tables) { Stream.of(table.getKeys(), table.getValues()) .filter(Objects::nonNull) @@ -107,6 +108,7 @@ private ResolveResult[] resolveQuery(QVariable var, QQueryExpr query) { .map(QTableColumn::getVarDeclaration) .filter(Objects::nonNull) .filter(v -> v.getQualifiedName().equals(qualifiedName)) + .map(DeclarationRef::of) .forEach(res::add); } @@ -128,7 +130,7 @@ private ResolveResult[] resolveLambda(QVariable var, QLambdaExpr lambda) { if (QPsiUtil.isGlobalDeclaration(el)) { return resolveElement(var); } else { - return single(el); + return single(DeclarationRef.of(el)); } } return resolveElement(var); diff --git a/src/main/java/org/kdb/inside/brains/view/inspector/InspectorToolWindow.java b/src/main/java/org/kdb/inside/brains/view/inspector/InspectorToolWindow.java index 18f2012..2ce1b0a 100644 --- a/src/main/java/org/kdb/inside/brains/view/inspector/InspectorToolWindow.java +++ b/src/main/java/org/kdb/inside/brains/view/inspector/InspectorToolWindow.java @@ -37,7 +37,6 @@ import com.intellij.openapi.wm.ToolWindowManager; import com.intellij.openapi.wm.ex.ToolWindowEx; import com.intellij.openapi.wm.ex.ToolWindowManagerListener; -import com.intellij.psi.PsiElement; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.ui.*; import com.intellij.ui.content.Content; @@ -61,9 +60,8 @@ import org.kdb.inside.brains.action.BgtAction; import org.kdb.inside.brains.action.EdtAction; import org.kdb.inside.brains.core.*; -import org.kdb.inside.brains.psi.QAssignmentExpr; import org.kdb.inside.brains.psi.QExpression; -import org.kdb.inside.brains.psi.QVarDeclaration; +import org.kdb.inside.brains.psi.index.DeclarationRef; import org.kdb.inside.brains.psi.index.QIndexService; import org.kdb.inside.brains.settings.KdbSettingsService; import org.kdb.inside.brains.view.KdbToolWindowPanel; @@ -294,21 +292,13 @@ public void run(@NotNull ProgressIndicator indicator) { @NotNull private String getSourceContent(String canonicalName) { - final QVarDeclaration declaration = findVarDeclaration(canonicalName); + final DeclarationRef declaration = findVarDeclaration(canonicalName); if (declaration == null) { return ""; } - final PsiElement parent = declaration.getParent(); - if (!(parent instanceof QAssignmentExpr assignment)) { - return ""; - } - - final QExpression expression = assignment.getExpression(); - if (expression == null) { - return ""; - } - return expression.getText(); + final QExpression exp = declaration.getExpression(); + return exp == null ? "" : exp.getText(); } private void showDiffResult(Project project, String canonicalName, String instSource) { @@ -422,7 +412,7 @@ public void dispose() { } @Nullable - private QVarDeclaration getDeclaration(@Nullable TreePath path) { + private DeclarationRef getDeclaration(@Nullable TreePath path) { if (path == null) { return null; } @@ -434,7 +424,7 @@ private QVarDeclaration getDeclaration(@Nullable TreePath path) { } @Nullable - private QVarDeclaration findVarDeclaration(String canonicalName) { + private DeclarationRef findVarDeclaration(String canonicalName) { final QIndexService instance = QIndexService.getInstance(project); try { return instance.getFirstGlobalDeclarations(canonicalName, GlobalSearchScope.allScope(project)); @@ -446,7 +436,7 @@ private QVarDeclaration findVarDeclaration(String canonicalName) { private void scrollPathToSource(@Nullable TreePath path, boolean requestFocus) { final Application application = ApplicationManager.getApplication(); application.executeOnPooledThread(() -> { - final QVarDeclaration declaration = getDeclaration(path); + final DeclarationRef declaration = getDeclaration(path); if (declaration != null) { application.invokeLater(() -> declaration.navigate(requestFocus)); } diff --git a/src/main/java/org/kdb/inside/brains/view/struct/QStructureViewElement.java b/src/main/java/org/kdb/inside/brains/view/struct/QStructureViewElement.java index 2f55b98..e16d3c8 100644 --- a/src/main/java/org/kdb/inside/brains/view/struct/QStructureViewElement.java +++ b/src/main/java/org/kdb/inside/brains/view/struct/QStructureViewElement.java @@ -39,56 +39,134 @@ private QStructureViewElement(PsiElement element, StructureElementType type, Str public static @Nullable QStructureViewElement createViewElement(PsiElement child) { if (child instanceof QImport qImport) { - return new QStructureViewElement(child, StructureElementType.IMPORT, getImportContent(qImport)); - } else if (child instanceof QCommand) { - return new QStructureViewElement(child, StructureElementType.COMMAND, child.getText()); + return createImport(qImport); + } else if (child instanceof QCommand cmd) { + return createCommand(cmd); } else if (child instanceof QContext context) { - final QContextBody body = context.getContextBody(); - final QVarDeclaration nameVar = context.getVariable(); - if (nameVar != null) { - return new QStructureViewElement(child, StructureElementType.CONTEXT, nameVar.getName(), body); - } else { - return new QStructureViewElement(child, StructureElementType.CONTEXT, ".", body); - } - } else if (child instanceof QLambdaExpr) { - return createLambdaElement(child, (QLambdaExpr) child, "\uD835\uDF06"); + return createContext(context); + } else if (child instanceof QLambdaExpr lambda) { + return createLambdaElement(lambda, "\uD835\uDF06"); } else if (child instanceof QTableColumn col) { - final boolean keys = col.getParent() instanceof QTableKeys; - final QVarDeclaration varDeclaration = col.getVarDeclaration(); - - String name = varDeclaration == null ? "" : varDeclaration.getQualifiedName(); - name += getExpressionType(col.getExpression()); - return new QStructureViewElement(child, keys ? StructureElementType.TABLE_KEY_COLUMN : StructureElementType.TABLE_VALUE_COLUMN, name); + return createTable(col); } else if (child instanceof QAssignmentExpr assignment) { - final QVarDeclaration variable = assignment.getVarDeclaration(); - if (variable == null) { - return null; - } - - final QExpression expression = assignment.getExpression(); - if (expression == null) { - return null; - } - - String name = variable.getQualifiedName(); - if (expression instanceof QLambdaExpr) { - return createLambdaElement(child, (QLambdaExpr) expression, name); - } else if (expression instanceof QTableExpr) { - return new QStructureViewElement(child, StructureElementType.TABLE, name, expression); - } else { - name += getExpressionType(expression); - return new QStructureViewElement(child, StructureElementType.VARIABLE, name); - } + return createAssignment(assignment); + } else if (child instanceof QInvokeFunction func) { + return createSet(func); + } + return null; + } + + private static @Nullable QStructureViewElement createSet(QInvokeFunction f) { + final QSystemFunction systemFunction = f.getSystemFunction(); + if (systemFunction != null) { + return createSystemSet(f, systemFunction); + } + + final QCustomFunction cf = f.getCustomFunction(); + if (cf != null) { + return createCustomSet(f, cf); } return null; } + // set[`adfadf;...] + private static @Nullable QStructureViewElement createSystemSet(QInvokeFunction f, QSystemFunction systemFunction) { + if (!"set".equals(systemFunction.getText())) { + return null; + } + final List argumentsList = f.getArgumentsList(); + if (argumentsList.isEmpty()) { + return null; + } + final List expressions = argumentsList.get(0).getExpressions(); + if (expressions.isEmpty()) { + return null; + } + + final PsiElement firstChild = expressions.get(0).getFirstChild(); + if (!(firstChild instanceof QSymbol sym)) { + return null; + } + final String name = sym.getName() + (expressions.size() == 2 ? getExpressionType(expressions.get(1)) : ""); + return new QStructureViewElement(sym, StructureElementType.SYMBOL, name); + } + + // `asdasd set ... + private static @Nullable QStructureViewElement createCustomSet(QInvokeFunction f, QCustomFunction cf) { + final PsiElement firstChild = cf.getExpression().getFirstChild(); + if (!(firstChild instanceof QSymbol sym)) { + return null; + } + final QExpression expression = f.getExpression(); + if (!(expression instanceof QInvokeFunction ff)) { + return null; + } + final QSystemFunction sf = ff.getSystemFunction(); + if (sf == null || !"set".equals(sf.getText())) { + return null; + } + final String name = sym.getName() + getExpressionType(ff.getExpression()); + return new QStructureViewElement(sym, StructureElementType.SYMBOL, name); + } + + private static @Nullable QStructureViewElement createAssignment(QAssignmentExpr assignment) { + final QVarDeclaration variable = assignment.getVarDeclaration(); + if (variable == null) { + return null; + } + + final QExpression expression = assignment.getExpression(); + if (expression == null) { + return null; + } + + String name = variable.getQualifiedName(); + if (expression instanceof QLambdaExpr lambda) { + return createLambdaElement(lambda, name); + } else if (expression instanceof QTableExpr) { + return new QStructureViewElement(assignment, StructureElementType.TABLE, name, expression); + } else { + name += getExpressionType(expression); + return new QStructureViewElement(assignment, StructureElementType.VARIABLE, name); + } + } + + private static @NotNull QStructureViewElement createTable(QTableColumn col) { + final boolean keys = col.getParent() instanceof QTableKeys; + final QVarDeclaration varDeclaration = col.getVarDeclaration(); + + String name = varDeclaration == null ? "" : varDeclaration.getQualifiedName(); + name += getExpressionType(col.getExpression()); + return new QStructureViewElement(col, keys ? StructureElementType.TABLE_KEY_COLUMN : StructureElementType.TABLE_VALUE_COLUMN, name); + } + + private static @NotNull QStructureViewElement createCommand(QCommand cmd) { + return new QStructureViewElement(cmd, StructureElementType.COMMAND, cmd.getText()); + } + + private static @NotNull QStructureViewElement createImport(QImport qImport) { + return new QStructureViewElement(qImport, StructureElementType.IMPORT, getImportContent(qImport)); + } + + private static @NotNull QStructureViewElement createContext(QContext context) { + final QContextBody body = context.getContextBody(); + final QVarDeclaration nameVar = context.getVariable(); + if (nameVar == null) { + return new QStructureViewElement(context, StructureElementType.CONTEXT, ".", body); + } else { + return new QStructureViewElement(nameVar, StructureElementType.CONTEXT, nameVar.getName(), body); + } + } + @NotNull - private static QStructureViewElement createLambdaElement(PsiElement element, QLambdaExpr lambda, String namePrefix) { - return new QStructureViewElement(element, StructureElementType.LAMBDA, getLambdaDescriptor(namePrefix, lambda), lambda.getExpressions()); + private static QStructureViewElement createLambdaElement(QLambdaExpr lambda, String namePrefix) { + return new QStructureViewElement(lambda, StructureElementType.LAMBDA, getLambdaDescriptor(namePrefix, lambda), lambda.getExpressions()); } private static String getExpressionType(QExpression expression) { + if (expression == null) { + return ""; + } if (expression instanceof QTypeCastExpr) { return ": " + QPsiUtil.getTypeCast((QTypeCastExpr) expression); } diff --git a/src/main/java/org/kdb/inside/brains/view/struct/QStructureViewModel.java b/src/main/java/org/kdb/inside/brains/view/struct/QStructureViewModel.java index b09aadf..eb25393 100644 --- a/src/main/java/org/kdb/inside/brains/view/struct/QStructureViewModel.java +++ b/src/main/java/org/kdb/inside/brains/view/struct/QStructureViewModel.java @@ -43,12 +43,13 @@ public boolean isAlwaysLeaf(StructureViewTreeElement element) { @Override public Filter @NotNull [] getFilters() { return new Filter[]{ - new TheElementFilter("SHOW_TABLES", "Show Tables", StructureElementType.TABLE), - new TheElementFilter("SHOW_COLUMNS", "Show Columns", EnumSet.of(StructureElementType.TABLE_VALUE_COLUMN, StructureElementType.TABLE_KEY_COLUMN)), - new TheElementFilter("SHOW_IMPORTS", "Show Imports", StructureElementType.IMPORT), - new TheElementFilter("SHOW_COMMANDS", "Show Commands", StructureElementType.COMMAND), - new TheElementFilter("SHOW_LAMBDAS", "Show Lambdas", StructureElementType.LAMBDA), - new TheElementFilter("SHOW_VARIABLES", "Show Variables", StructureElementType.VARIABLE), + new SingleElementFilter("SHOW_TABLES", "Show Tables", StructureElementType.TABLE), + new MultiElementFilter("SHOW_COLUMNS", "Show Columns", EnumSet.of(StructureElementType.TABLE_VALUE_COLUMN, StructureElementType.TABLE_KEY_COLUMN)), + new SingleElementFilter("SHOW_IMPORTS", "Show Imports", StructureElementType.IMPORT), + new SingleElementFilter("SHOW_COMMANDS", "Show Commands", StructureElementType.COMMAND), + new SingleElementFilter("SHOW_LAMBDAS", "Show Lambdas", StructureElementType.LAMBDA), + new SingleElementFilter("SHOW_VARIABLES", "Show Variables", StructureElementType.VARIABLE), + new SingleElementFilter("SHOW_SYMBOLS", "Show Symbols", StructureElementType.SYMBOL), new TheVisibilityFilter(), }; } @@ -94,42 +95,62 @@ public boolean isVisible(TreeElement treeNode) { } } - private static class TheElementFilter implements Filter { + private static abstract class BaseElementFilter implements Filter { private final String name; - private final String text; - private final Icon icon; - private final EnumSet types; - - private TheElementFilter(String name, String text, StructureElementType type) { - this(name, text, EnumSet.of(type)); - } + private final ActionPresentationData presentation; - private TheElementFilter(String name, String text, EnumSet types) { + public BaseElementFilter(String name, String text, Icon icon) { this.name = name; - this.text = text; - this.icon = types.stream().findFirst().map(StructureElementType::getIcon).orElse(null); - this.types = types; + this.presentation = new ActionPresentationData(text, null, icon); } @Override - public @NonNls - @NotNull String getName() { + public final @NonNls @NotNull String getName() { return name; } @Override - public @NotNull ActionPresentation getPresentation() { - return new ActionPresentationData(text, null, icon); + public final @NotNull ActionPresentation getPresentation() { + return presentation; } @Override - public boolean isVisible(TreeElement treeNode) { - return !types.contains(((QStructureViewElement) treeNode).getType()); + public final boolean isReverted() { + return true; + } + + public final boolean isVisible(TreeElement treeNode) { + return !containsType(((QStructureViewElement) treeNode).getType()); + } + + abstract boolean containsType(StructureElementType type); + } + + private static class SingleElementFilter extends BaseElementFilter { + private final StructureElementType type; + + public SingleElementFilter(String name, String text, StructureElementType type) { + super(name, text, type.getIcon()); + this.type = type; } @Override - public boolean isReverted() { - return true; + boolean containsType(StructureElementType type) { + return this.type == type; + } + } + + private static class MultiElementFilter extends BaseElementFilter { + private final EnumSet types; + + private MultiElementFilter(String name, String text, EnumSet types) { + super(name, text, types.stream().findFirst().map(StructureElementType::getIcon).orElse(null)); + this.types = types; + } + + @Override + boolean containsType(StructureElementType type) { + return types.contains(type); } } } diff --git a/src/main/java/org/kdb/inside/brains/view/struct/StructureElementType.java b/src/main/java/org/kdb/inside/brains/view/struct/StructureElementType.java index ce6501d..79af8ab 100644 --- a/src/main/java/org/kdb/inside/brains/view/struct/StructureElementType.java +++ b/src/main/java/org/kdb/inside/brains/view/struct/StructureElementType.java @@ -9,6 +9,7 @@ public enum StructureElementType { IMPORT(KdbIcons.Node.Import, true, false), COMMAND(KdbIcons.Node.Command, true, false), CONTEXT(KdbIcons.Node.Context, false, true), + SYMBOL(KdbIcons.Node.Symbol, true, false), VARIABLE(KdbIcons.Node.Variable, true, false), LAMBDA(KdbIcons.Node.Lambda, false, true), TABLE(KdbIcons.Node.Table, false, false), diff --git a/src/main/resources/org/kdb/inside/brains/q.bnf b/src/main/resources/org/kdb/inside/brains/q.bnf index b3f74ce..ef32add 100644 --- a/src/main/resources/org/kdb/inside/brains/q.bnf +++ b/src/main/resources/org/kdb/inside/brains/q.bnf @@ -122,7 +122,10 @@ private mode_content ::= statement+ { recoverWhile = end_of_line_recover } mode ::= MODE_PATTERN mode_content? { pin = 1 methods=[modeName="MODE_PATTERN"] } // Mode: [a-Z]) ..... // ============ Custom types ==================== -symbol ::= SYMBOL_PATTERN +symbol ::= SYMBOL_PATTERN { + implements=["org.kdb.inside.brains.psi.QPsiElement" "com.intellij.psi.PsiNameIdentifierOwner" "com.intellij.psi.PsiQualifiedNamedElement" "com.intellij.psi.NavigatablePsiElement"] + mixin="org.kdb.inside.brains.psi.impl.QSymbolElementImpl" +} symbols ::= symbol symbol+ var_reference ::= VARIABLE_PATTERN { diff --git a/src/test/java/org/kdb/inside/brains/psi/index/QDataIndexerTest.java b/src/test/java/org/kdb/inside/brains/psi/index/QDataIndexerTest.java new file mode 100644 index 0000000..c43cbb0 --- /dev/null +++ b/src/test/java/org/kdb/inside/brains/psi/index/QDataIndexerTest.java @@ -0,0 +1,34 @@ +package org.kdb.inside.brains.psi.index; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertTrue; + + +class QDataIndexerTest { + @Test + void containsSetBackward() { + checkBackward("set[`sdfdfasdf;10]"); + checkBackward(" set[`sdfdfasdf;10]"); + checkBackward(" set [`sdfdfasdf;10]"); + checkBackward(" set [ `sdfdfasdf;10]"); + checkBackward(" set [ `sdfdfasdf;10]"); + checkBackward("\n set\n [\n `sdfdfasdf;10]"); + } + + @Test + void containsSetForward() { + checkSetForward("`sdfdfasdf set"); + checkSetForward(" `sdfdfasdf set"); + checkSetForward("\n `sdfdfasdf\n set"); + } + + void checkBackward(String s) { + assertTrue(QDataIndexer.containsSetBackward(s, s.indexOf('`'))); + } + + void checkSetForward(String s) { + final int startPosition = s.indexOf('`'); + assertTrue(QDataIndexer.containsSetForward(s, s.indexOf(' ', startPosition + 1))); + } +} \ No newline at end of file diff --git a/version.properties b/version.properties index 369d6b7..32923f6 100644 --- a/version.properties +++ b/version.properties @@ -1 +1 @@ -pluginVersion=5.11.1 \ No newline at end of file +pluginVersion=5.12.0 \ No newline at end of file