Skip to content

Commit

Permalink
Closes #13: Adds reference contributor for USD references
Browse files Browse the repository at this point in the history
This commit adds a new psi.referenceContributor that provides support
for references on USD asset/prim path references. You can now Ctrl+Click
reference paths, woohoo!!

This commit also includes the needed structures to support this new
behavior, including an applicationService/Configurable to specify one's
usdresolve installation path as well as a localInspection to highlight
potentially unresolvable asset references if one's usdresolve
installation path isn't configured. The former structure is also the
hopeful backbone for future feature additions (like usdc/usdz file
support with usdcat).
  • Loading branch information
justint committed Jan 10, 2021
1 parent 22d4a65 commit 9e471e4
Show file tree
Hide file tree
Showing 19 changed files with 734 additions and 8 deletions.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 5 additions & 1 deletion src/main/gen/com/justint/usdidea/lang/psi/usdVisitor.java

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package com.justint.usdidea.inspections;

import com.intellij.codeInspection.*;
import com.intellij.openapi.options.ShowSettingsUtil;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.justint.usdidea.lang.psi.usdReferenceItem;
import com.justint.usdidea.settings.USDSettingsConfigurable;
import com.justint.usdidea.settings.USDSettingsState;
import org.jetbrains.annotations.NotNull;

import java.io.File;

public class UsdresolvePathInspection extends LocalInspectionTool {

@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly, @NotNull LocalInspectionToolSession session) {
// Don't bother the user if they've already configured usdresolve
if (USDSettingsState.getInstance().isUsdresolvePathValid()) return PsiElementVisitor.EMPTY_VISITOR;
else return new PsiElementVisitor() {

@Override
public void visitElement(@NotNull PsiElement element) {
if (element instanceof usdReferenceItem) {
usdReferenceItem referenceItem = (usdReferenceItem)element;
PsiElement assetReference = referenceItem.getAssetReference();
if (assetReference != null) {
String assetPathString = assetReference.getText().split("@")[1];
File assetFile = new File(assetPathString);
if (!isFileRelative(assetPathString) && !assetFile.exists()) {
String description = "Cannot resolve asset reference '" + assetReference.getText() + "' without configuration of usdresolve install path";
holder.registerProblem(
element,
description,
ProblemHighlightType.WARNING,
new OpenUSDSettingsQuickFix());
}
}
}
}
};
}

private boolean isFileRelative(@NotNull String assetPathString) {
return assetPathString.contains("./") || assetPathString.contains("../");
}


private static class OpenUSDSettingsQuickFix implements LocalQuickFix {
@NotNull
@Override
public String getFamilyName() {
return "Configure usdresolve path";
}

@Override
public boolean startInWriteAction() {
return false;
}

@Override
public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor problemDescriptor) {
showUSDSettingsDialog(project);
}

public static void showUSDSettingsDialog(@NotNull Project project) {
ShowSettingsUtil.getInstance().showSettingsDialog(project, USDSettingsConfigurable.class);
}
}
}
6 changes: 5 additions & 1 deletion src/main/java/com/justint/usdidea/lang/USD.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,11 @@ Vector ::= leftparens [!rightparens Item? (comma Item)*] rightparens | leftparen

Item ::= InterpolatedArray | Array | Vector | string | number | floatnumber | ReferenceItem | Dict | TimeSample | Boolean

ReferenceItem ::= assetReference pathReference | assetReference | pathReference
ReferenceItem ::= assetReference pathReference | assetReference | pathReference {
mixin="com.justint.usdidea.lang.psi.impl.USDNamedElementImpl"
implements="com.justint.usdidea.lang.psi.USDNamedElement"
methods=[getName setName getNameIdentifier]
}

Dict ::= leftbrace [!rightbrace DictItem (DictItem)*] rightbrace | leftbrace rightbrace

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.justint.usdidea.lang;

import com.intellij.patterns.PlatformPatterns;
import com.intellij.psi.PsiReferenceContributor;
import com.intellij.psi.PsiReferenceRegistrar;
import com.justint.usdidea.lang.psi.impl.USDReferenceReferenceProvider;
import com.justint.usdidea.lang.psi.usdReferenceItem;
import org.jetbrains.annotations.NotNull;

public class USDReferenceContributor extends PsiReferenceContributor {
@Override
public void registerReferenceProviders(@NotNull PsiReferenceRegistrar registrar) {
registrar.registerReferenceProvider(PlatformPatterns.psiElement(usdReferenceItem.class), USDReferenceReferenceProvider.INSTANCE);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.justint.usdidea.lang.psi;

import com.intellij.psi.PsiNameIdentifierOwner;

public interface USDNamedElement extends PsiNameIdentifierOwner {

}
102 changes: 102 additions & 0 deletions src/main/java/com/justint/usdidea/lang/psi/USDReferenceReference.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package com.justint.usdidea.lang.psi;

import com.intellij.execution.ExecutionException;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.PsiReferenceBase;
import com.justint.usdidea.lang.psi.impl.USDPsiImplUtil;
import com.justint.usdidea.util.binscripts.USDResolve;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import java.io.File;
import java.nio.file.InvalidPathException;
import java.nio.file.Paths;

public class USDReferenceReference extends PsiReferenceBase<PsiElement> {
public USDReferenceReference(@NotNull PsiElement element, TextRange rangeInElement) {
super(element, rangeInElement);
}

@Nullable
private PsiFile getResolvedReferenceAssetFile(String referenceLayerPath, File sourceElementFile) {
USDResolve resolver = new USDResolve(referenceLayerPath, sourceElementFile.getParent(), myElement.getProject());
boolean resolverIsValid = resolver.isUSDInstallValid();

String targetLayerPath = null;

// Attempt to resolve this path as a literal filesystem path first
try {
File targetLayerFile = new File(Paths.get(sourceElementFile.getParent(), referenceLayerPath).toString());
targetLayerPath = targetLayerFile.getAbsolutePath();
}
catch (InvalidPathException e) {
// It may be a custom URL path; we'll try the resolver next if possible
}
if (resolverIsValid) {
try {
// Attempt to derive this path from `usdresolve`, if possible
targetLayerPath = resolver.resolvePath();
} catch (ExecutionException | InterruptedException e) {
// Resolver failed/was interrupted
return null;
}
}

if (targetLayerPath == null) return null;

VirtualFile layerVf = VirtualFileManager.getInstance().findFileByUrl("file://" + targetLayerPath);
if (layerVf == null) return null;

return PsiManager.getInstance(myElement.getProject()).findFile(layerVf);
}

@Nullable
@Override
public PsiElement resolve() {
PsiFile resolvedAssetFile = myElement.getContainingFile();
String referencePath = myElement.getText();

boolean hasAssetPath = false;
boolean hasPrimPath = false;

if (referencePath.contains("@")) {
hasAssetPath = true;
}
if (referencePath.contains("<")) {
hasPrimPath = true;
}

if (!hasAssetPath && !hasPrimPath) {
return null;
}
if (hasAssetPath) {
String assetPath = referencePath.split("@")[1];

String sourceElementFilePath = myElement.getContainingFile().getVirtualFile().getPath();
File sourceElementFile = new File(sourceElementFilePath);

resolvedAssetFile = getResolvedReferenceAssetFile(assetPath, sourceElementFile);
if (resolvedAssetFile == null) return null;
}
if (hasPrimPath) {
if (resolvedAssetFile instanceof USDFile) {
String primPath = referencePath.split("<")[1].replace(">", "");
PsiElement foundPrim = USDPsiImplUtil.findPrimInLayerFromPath((USDFile)resolvedAssetFile, primPath);
if (foundPrim != null) return foundPrim;
else return null;
}
else {
// We have a prim path pointing into a non-USD layer asset file... strange, we'll just return no result
return null;
}
}
else {
return resolvedAssetFile;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.justint.usdidea.lang.psi.impl;

import com.intellij.extapi.psi.ASTWrapperPsiElement;
import com.intellij.lang.ASTNode;
import com.intellij.psi.PsiReference;
import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry;
import org.jetbrains.annotations.NotNull;

public abstract class USDNamedElementImpl extends ASTWrapperPsiElement {
public USDNamedElementImpl(@NotNull ASTNode node) {
super(node);
}

@Override
public PsiReference[] getReferences() {
return ReferenceProvidersRegistry.getReferencesFromProviders(this);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.intellij.icons.AllIcons;
import com.intellij.lang.ASTNode;
import com.intellij.navigation.ItemPresentation;
import com.intellij.psi.PsiElement;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.ui.LayeredIcon;
Expand All @@ -12,6 +13,9 @@
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class USDPsiImplUtil {

Expand Down Expand Up @@ -115,6 +119,19 @@ public static String getName(usdVariantSetKey variantSetKey) {
return trimStringNames(variantSetKey.getString().getText());
}

public static String getName(usdReferenceItem referenceItem) {
return referenceItem.getText();
}

public static PsiElement setName(usdReferenceItem referenceItem, String newName) {
// TODO: figure this out...
return referenceItem;
}

public static PsiElement getNameIdentifier(usdReferenceItem referenceItem) {
return referenceItem.getNode().getPsi();
}

@NotNull
public static ItemPresentation getPresentation(final usdDictItem dictItemElement) {
return new ItemPresentation() {
Expand Down Expand Up @@ -286,4 +303,38 @@ public static boolean isDictionary(usdMetadatum metadatumElement) {
}
return false;
}

@Nullable
public static PsiElement findPrimInLayerFromPath(USDFile layer, String path) {
ArrayList<String> prims = new ArrayList<>(Arrays.asList(path.split("/")));
if (prims.size() == 0) {
// The path is pointing to the pseudoroot; return the layer file PsiElement itself
return layer;
}
prims.remove(0); // The pseudoroot ("") is never needed, since local layer reference paths always start with a '/'

boolean found = false;
PsiElement currentPrimPsiElement = layer;
for (int i = 0; i < prims.size(); i++) {
String currentPrim = prims.get(i);

List<usdPrimSpec> children;
if (currentPrimPsiElement instanceof usdPrimSpec) {
children = PsiTreeUtil.getChildrenOfTypeAsList(((usdPrimSpec) currentPrimPsiElement).getBody(), usdPrimSpec.class);
} else {
children = PsiTreeUtil.getChildrenOfTypeAsList(currentPrimPsiElement, usdPrimSpec.class);
}
for (usdPrimSpec child : children) {
if (child.getPrimName().equals(currentPrim)) {
currentPrimPsiElement = child;
if (i + 1 == prims.size()) {
found = true;
}
}
}
}
if (found) {
return currentPrimPsiElement;
} else return null;
}
}

0 comments on commit 9e471e4

Please sign in to comment.