package com.jetbrains.lang.dart.projectView;
import com.intellij.ide.projectView.PresentationData;
import com.intellij.ide.projectView.TreeStructureProvider;
import com.intellij.ide.projectView.ViewSettings;
import com.intellij.ide.projectView.impl.nodes.ExternalLibrariesNode;
import com.intellij.ide.projectView.impl.nodes.NamedLibraryElementNode;
import com.intellij.ide.projectView.impl.nodes.PsiDirectoryNode;
import com.intellij.ide.util.treeView.AbstractTreeNode;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.lang.dart.sdk.DartConfigurable;
import com.jetbrains.lang.dart.sdk.DartPackagesLibraryType;
import com.jetbrains.lang.dart.sdk.DartSdk;
import com.jetbrains.lang.dart.util.DartUrlResolver;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import static com.jetbrains.lang.dart.util.DartUrlResolver.PACKAGES_FOLDER_NAME;
import static com.jetbrains.lang.dart.util.PubspecYamlUtil.PUBSPEC_YAML;
public class DartTreeStructureProvider implements TreeStructureProvider, DumbAware {
@NotNull
public Collection<AbstractTreeNode> modify(final @NotNull AbstractTreeNode parentNode,
final @NotNull Collection<AbstractTreeNode> children,
final ViewSettings settings) {
if (parentNode instanceof ExternalLibrariesNode) {
return ContainerUtil.map(children, node -> {
if (node instanceof NamedLibraryElementNode &&
(DartPackagesLibraryType.DART_PACKAGES_LIBRARY_NAME.equals(node.getName()) ||
DartSdk.DART_SDK_LIB_NAME.equals(node.getName()))) {
final boolean isSdkRoot = DartSdk.DART_SDK_LIB_NAME.equals(node.getName());
return new NamedLibraryElementNode(node.getProject(), ((NamedLibraryElementNode)node).getValue(), settings) {
@Override
public boolean canNavigate() {
return isSdkRoot; // no sense to navigate anywhere in case of "Dart Packages" library
}
@Override
public void navigate(boolean requestFocus) {
final Project project = getProject();
if (project != null) {
DartConfigurable.openDartSettings(project);
}
}
};
}
return node;
});
}
if (parentNode instanceof NamedLibraryElementNode &&
(DartPackagesLibraryType.DART_PACKAGES_LIBRARY_NAME.equals(parentNode.getName()) ||
DartSdk.DART_SDK_LIB_NAME.equals(parentNode.getName()))) {
final boolean isSdkRoot = DartSdk.DART_SDK_LIB_NAME.equals(parentNode.getName());
return ContainerUtil.map(children, node -> {
final VirtualFile dir = node instanceof PsiDirectoryNode ? ((PsiDirectoryNode)node).getVirtualFile() : null;
if (dir != null && dir.isInLocalFileSystem() && dir.isDirectory() && (isSdkRoot || "lib".equals(dir.getName()))) {
return new DartSdkOrLibraryRootNode(node.getProject(), ((PsiDirectoryNode)node).getValue(), settings);
}
return node;
});
}
// root/packages/ThisProject and root/packages/PathPackage folders are excluded in dart projects (see DartProjectComponent.excludeBuildAndPackagesFolders),
// this provider adds location string tho these nodes in Project View like "ThisProject (ThisProject/lib)"
final Project project = parentNode.getProject();
final VirtualFile packagesDir = parentNode instanceof PsiDirectoryNode && project != null
? ((PsiDirectoryNode)parentNode).getVirtualFile()
: null;
final VirtualFile parentFolder = packagesDir != null && packagesDir.isDirectory() && PACKAGES_FOLDER_NAME.equals(packagesDir.getName())
? packagesDir.getParent()
: null;
final VirtualFile pubspecYamlFile = parentFolder != null
? parentFolder.findChild(PUBSPEC_YAML)
: null;
if (pubspecYamlFile != null && !pubspecYamlFile.isDirectory()) {
final ArrayList<AbstractTreeNode> modifiedChildren = new ArrayList<>(children);
final DartUrlResolver resolver = DartUrlResolver.getInstance(project, pubspecYamlFile);
resolver.processLivePackages((packageName, packageDir) -> {
final VirtualFile folder = packagesDir.findChild(packageName);
if (folder != null) {
final AbstractTreeNode node = getFolderNode(children, folder);
if (node == null) {
modifiedChildren.add(new SymlinkToLivePackageNode(project, packageName, packageDir));
}
else {
node.getPresentation().setLocationString(getPackageLocationString(packageDir));
}
}
});
return modifiedChildren;
}
return children;
}
@Nullable
private static AbstractTreeNode getFolderNode(final @NotNull Collection<AbstractTreeNode> nodes, final @NotNull VirtualFile folder) {
for (AbstractTreeNode node : nodes) {
if (node instanceof PsiDirectoryNode && folder.equals(((PsiDirectoryNode)node).getVirtualFile())) {
return node;
}
}
return null;
}
private static String getPackageLocationString(@NotNull final VirtualFile packageDir) {
final String path = packageDir.getPath();
final int lastSlashIndex = path.lastIndexOf("/");
final int prevSlashIndex = lastSlashIndex == -1 ? -1 : path.substring(0, lastSlashIndex).lastIndexOf("/");
return FileUtil.toSystemDependentName(prevSlashIndex < 0 ? path : path.substring(prevSlashIndex + 1));
}
private static class SymlinkToLivePackageNode extends AbstractTreeNode<String> {
@NotNull private final String mySymlinkPath;
public SymlinkToLivePackageNode(final @NotNull Project project,
final @NotNull String packageName,
final @NotNull VirtualFile packageDir) {
super(project, packageName);
myName = packageName;
mySymlinkPath = getPackageLocationString(packageDir);
setIcon(DartIconProvider.EXCLUDED_FOLDER_SYMLINK_ICON);
}
@NotNull
public Collection<? extends AbstractTreeNode> getChildren() {
return Collections.emptyList();
}
protected void update(final PresentationData presentation) {
presentation.setIcon(getIcon());
presentation.setPresentableText(myName);
presentation.setLocationString(mySymlinkPath);
}
public int getWeight() {
return 0;
}
}
private static class DartSdkOrLibraryRootNode extends PsiDirectoryNode {
public DartSdkOrLibraryRootNode(final Project project, final PsiDirectory value, final ViewSettings settings) {
super(project, value, settings);
}
@Override
public boolean canNavigate() {
return false; // 'Dart Packages' and 'Dart SDK' libraries are generated automatically, no need to navigate to Project Structure
}
@Override
public boolean canNavigateToSource() {
return false;
}
@Override
protected void updateImpl(final PresentationData data) {
super.updateImpl(data);
final VirtualFile dir = getVirtualFile();
final VirtualFile parentDir = dir == null ? null : dir.getParent();
if (parentDir != null && parentDir.isInLocalFileSystem() && dir.isDirectory() && "lib".equals(dir.getName())) {
data.setPresentableText(parentDir.getName()); // e.g. "path-1.3.6" instead of "lib"
}
data.setLocationString(""); // we do not want 'library root' location string
}
}
}