Skip to content

Commit

Permalink
Golang: Display Go external libraries based on importpath structure (…
Browse files Browse the repository at this point in the history
…bazelbuild/intellij PR import bazelbuild#2973)

This is a modified version of the original change by Brandon Lico with a few modifications:
 * read from the fileToImportPathMap directly when modifying the structure
 * add unit tests.

 * We don't inherit from BlazeExternalSyntheticLibrary since that contains irrelevant logic. Also we can't logically fulfill the "Only supports one instance per value of presentableText." condition that's outlined in the class javadoc since we could have nodes with identical presentableText properties in the tree in different positions.

Issue number: bazelbuild#1744 bazelbuild#2365

Modifies the structure of the external "Go Libraries" project view to be based on importpath rather than a flat list of files. See my comment within bazelbuild#2365 for an example of how the new structure looks.

Closes bazelbuild#2973

Unit tests added.

(cherry picked from commit 4d0f56b)
  • Loading branch information
AlexeyGy authored and ittaiz committed Oct 24, 2022
1 parent 5f9ca93 commit cbe54f2
Show file tree
Hide file tree
Showing 9 changed files with 776 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ public final class BlazeExternalSyntheticLibrary extends SyntheticLibrary
* equals, hashcode -- there must only be one instance per value of this text
* @param files collection of files that this synthetic library is responsible for.
*/
BlazeExternalSyntheticLibrary(String presentableText, Collection<File> files) {
public BlazeExternalSyntheticLibrary(String presentableText, Collection<File> files) {
this.presentableText = presentableText;
this.files = ImmutableSet.copyOf(files);
this.validFiles =
Expand Down
4 changes: 3 additions & 1 deletion golang/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,12 @@ intellij_unit_test_suite(
":golang",
"//base",
"//base:unit_test_utils",
"//common/experiments",
"//common/experiments:unit_test_utils",
"//intellij_platform_sdk:jsr305",
"//intellij_platform_sdk:plugin_api_for_tests",
"//intellij_platform_sdk:test_libs",
"//sdkcompat",
"//testing:lib",
"//third_party/go:go_for_tests",
"@junit//jar",
],
Expand Down
1 change: 1 addition & 0 deletions golang/src/META-INF/go-contents.xml
Original file line number Diff line number Diff line change
Expand Up @@ -44,5 +44,6 @@
<documentationProvider implementation="com.google.idea.blaze.golang.resolve.BlazeGoImportResolver$GoPackageDocumentationProvider"/>
<additionalLibraryRootsProvider implementation="com.google.idea.blaze.golang.sync.BlazeGoAdditionalLibraryRootsProvider"/>
<postStartupActivity implementation="com.google.idea.blaze.golang.run.producers.NonBlazeProducerSuppressor"/>
<treeStructureProvider implementation="com.google.idea.blaze.golang.treeview.BlazeGoTreeStructureProvider" order="last"/>
</extensions>
</idea-plugin>
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
import java.util.concurrent.ConcurrentMap;
import javax.annotation.Nullable;

class BlazeGoPackageFactory implements GoPackageFactory {
/** Updates and exposes a map of import paths to files. */
public class BlazeGoPackageFactory implements GoPackageFactory {
@Nullable
@Override
public GoPackage createPackage(GoFile goFile) {
Expand All @@ -54,7 +55,7 @@ public GoPackage createPackage(GoFile goFile) {
}

@Nullable
static ConcurrentMap<File, String> getFileToImportPathMap(Project project) {
public static ConcurrentMap<File, String> getFileToImportPathMap(Project project) {
return SyncCache.getInstance(project)
.get(BlazeGoPackageFactory.class, BlazeGoPackageFactory::buildFileToImportPathMap);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@
/** Provides out-of-project go sources for indexing. */
public final class BlazeGoAdditionalLibraryRootsProvider extends BlazeExternalLibraryProvider {

public static final String GO_EXTERNAL_LIBRARY_ROOT_NAME = "Go Libraries";

@Override
protected String getLibraryName() {
return "Go Libraries";
return GO_EXTERNAL_LIBRARY_ROOT_NAME;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/*
* Copyright 2022 The Bazel Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.google.idea.blaze.golang.treeview;

import com.google.common.collect.ImmutableSet;
import com.intellij.navigation.ItemPresentation;
import com.intellij.openapi.roots.SyntheticLibrary;
import com.intellij.openapi.vfs.VirtualFile;
import icons.BlazeIcons;
import java.util.Comparator;
import java.util.SortedSet;
import java.util.TreeSet;
import javax.swing.Icon;

/** Represents a {@link SyntheticLibrary} with a mutable set of child files. */
final class BlazeGoExternalSyntheticLibrary extends SyntheticLibrary implements ItemPresentation {

private final SortedSet<VirtualFile> childFiles;
private final String presentableText;

BlazeGoExternalSyntheticLibrary(String presentableText, ImmutableSet<VirtualFile> childFiles) {
this.childFiles = new TreeSet<>(Comparator.comparing(VirtualFile::toString));
this.childFiles.addAll(childFiles);
this.presentableText = presentableText;
}

void addFiles(ImmutableSet<VirtualFile> files) {
childFiles.addAll(files);
}

@Override
public String getPresentableText() {
return presentableText;
}

@Override
public Icon getIcon(boolean unused) {
return BlazeIcons.Logo;
}

@Override
public SortedSet<VirtualFile> getSourceRoots() {
return childFiles;
}

@Override
public boolean equals(Object o) {
return o instanceof BlazeGoExternalSyntheticLibrary
&& ((BlazeGoExternalSyntheticLibrary) o).presentableText.equals(presentableText)
&& ((BlazeGoExternalSyntheticLibrary) o).getSourceRoots().equals(getSourceRoots());
}

@Override
public int hashCode() {
return presentableText.hashCode();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
/*
* Copyright 2022 The Bazel Authors. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.idea.blaze.golang.treeview;

import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableMap.toImmutableMap;
import static com.google.idea.blaze.golang.sync.BlazeGoAdditionalLibraryRootsProvider.GO_EXTERNAL_LIBRARY_ROOT_NAME;

import com.google.common.base.Functions;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Streams;
import com.google.idea.blaze.base.settings.Blaze;
import com.google.idea.blaze.golang.resolve.BlazeGoPackageFactory;
import com.google.idea.common.experiments.FeatureRolloutExperiment;
import com.intellij.ide.projectView.TreeStructureProvider;
import com.intellij.ide.projectView.ViewSettings;
import com.intellij.ide.projectView.impl.nodes.PsiFileNode;
import com.intellij.ide.util.treeView.AbstractTreeNode;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.vfs.VfsUtil;
import com.intellij.openapi.vfs.VirtualFile;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.function.Function;

/**
* Modifies the project view by replacing the External Go Libraries root node (containing a flat
* list of sources) with a root node that structures sources based on their import paths.
*/
public final class BlazeGoTreeStructureProvider implements TreeStructureProvider, DumbAware {

public static final FeatureRolloutExperiment experiment =
new FeatureRolloutExperiment("go.import.treestructure");

@Override
public Collection<AbstractTreeNode<?>> modify(
AbstractTreeNode<?> parent, Collection<AbstractTreeNode<?>> children, ViewSettings settings) {
Project project = parent.getProject();
if (!Blaze.isBlazeProject(project)
|| !experiment.isEnabled()
|| !isGoBlazeExternalLibraryRoot(parent)) {
return children;
}
ImmutableMap<VirtualFile, AbstractTreeNode<?>> originalFileNodes =
getOriginalFileNodesMap(children);

Map<File, String> fileToImportPathMap = BlazeGoPackageFactory.getFileToImportPathMap(project);
if (fileToImportPathMap == null) {
return children;
}

SortedMap<String, AbstractTreeNode<?>> newChildren = new TreeMap<>();

ImmutableListMultimap<String, VirtualFile> importPathToFilesMap =
originalFileNodes.keySet().stream()
.collect(
ImmutableListMultimap.toImmutableListMultimap(
// Some nodes may, for some reason, not be in importPathMap, use the empty
// String as a guard character.
virtualFile ->
fileToImportPathMap.getOrDefault(VfsUtil.virtualToIoFile(virtualFile), ""),
Function.identity()));

for (String importPath : importPathToFilesMap.keySet()) {
if (importPath.isEmpty()) {
continue;
}
generateTree(
settings,
project,
newChildren,
importPath,
ImmutableSet.copyOf(importPathToFilesMap.get(importPath)),
originalFileNodes);
}
return Streams.concat(
newChildren.values().stream(),
// Put nodes without an importPath as direct children.
importPathToFilesMap.get("").stream().map(originalFileNodes::get))
.collect(toImmutableList());
}

private static void generateTree(
ViewSettings settings,
Project project,
SortedMap<String, AbstractTreeNode<?>> newChildren,
String importPath,
ImmutableSet<VirtualFile> availableFiles,
Map<VirtualFile, AbstractTreeNode<?>> originalFileNodes) {
Iterator<Path> pathIter = Paths.get(importPath).iterator();
if (!pathIter.hasNext()) {
return;
}
// Root nodes (e.g., src) have to be added directly to newChildren.
String rootName = pathIter.next().toString();
GoSyntheticLibraryElementNode root =
(GoSyntheticLibraryElementNode)
newChildren.computeIfAbsent(
rootName,
(unused) ->
new GoSyntheticLibraryElementNode(
project,
new BlazeGoExternalSyntheticLibrary(rootName, availableFiles),
rootName,
settings,
new TreeMap<>()));

// Child nodes (e.g., package_name under src) are added under root nodes recursively.
GoSyntheticLibraryElementNode leaf =
buildChildTree(settings, project, availableFiles, pathIter, root);

// Required for files to actually show up in the Project View.
availableFiles.forEach((file) -> leaf.addChild(file.getName(), originalFileNodes.get(file)));
}

/**
* Recurse down the import path tree and add elements as children.
*
* <p>Fills previously created nodes with source files from the current import path.
*/
private static GoSyntheticLibraryElementNode buildChildTree(
ViewSettings settings,
Project project,
ImmutableSet<VirtualFile> files,
Iterator<Path> pathIter,
GoSyntheticLibraryElementNode parent) {
while (pathIter.hasNext()) {
parent.addFiles(files);
String dirName = pathIter.next().toString();

// current path already was created, no need to re-create
if (parent.hasChild(dirName)) {
parent = parent.getChildNode(dirName);
continue;
}
GoSyntheticLibraryElementNode libraryNode =
new GoSyntheticLibraryElementNode(
project,
new BlazeGoExternalSyntheticLibrary(dirName, files),
dirName,
settings,
new TreeMap<>());
parent.addChild(dirName, libraryNode);
parent = libraryNode;
}
return parent;
}

private static boolean isGoBlazeExternalLibraryRoot(AbstractTreeNode<?> parent) {
if (parent.getName() == null) {
return false;
}
return parent.getName().equals(GO_EXTERNAL_LIBRARY_ROOT_NAME);
}

private static ImmutableMap<VirtualFile, AbstractTreeNode<?>> getOriginalFileNodesMap(
Collection<AbstractTreeNode<?>> children) {

return children.stream()
.filter(child -> (child instanceof PsiFileNode))
.filter(child -> ((PsiFileNode) child).getVirtualFile() != null)
.collect(
toImmutableMap(child -> ((PsiFileNode) child).getVirtualFile(), Functions.identity()));
}
}
Loading

0 comments on commit cbe54f2

Please sign in to comment.