forked from bazelbuild/intellij
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Ignore CMake cache directories (bazelbuild#124)
Add an Action that modifies the local project view to ignore the CMake cache directories. Can be triggered manually, and it will trigger automatically on every sync.
- Loading branch information
Borja Lorente Escobar
authored and
Borja Lorente
committed
Oct 11, 2023
1 parent
6600e3c
commit 6e65698
Showing
12 changed files
with
313 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
229 changes: 229 additions & 0 deletions
229
apple/src/com/actions/ExcludeDirectoryInProjectView.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,229 @@ | ||
package com.apple.idea.extensions.actions; | ||
|
||
import com.google.common.collect.ImmutableSet; | ||
import com.google.idea.blaze.base.actions.BlazeProjectAction; | ||
import com.google.idea.blaze.base.async.FutureUtil; | ||
import com.google.idea.blaze.base.model.primitives.WorkspacePath; | ||
import com.google.idea.blaze.base.model.primitives.WorkspaceRoot; | ||
import com.google.idea.blaze.base.projectview.ProjectViewEdit; | ||
import com.google.idea.blaze.base.projectview.ProjectViewManager; | ||
import com.google.idea.blaze.base.projectview.ProjectViewSet; | ||
import com.google.idea.blaze.base.projectview.section.ListSection; | ||
import com.google.idea.blaze.base.projectview.section.sections.DirectoryEntry; | ||
import com.google.idea.blaze.base.projectview.section.sections.DirectorySection; | ||
import com.google.idea.blaze.base.scope.BlazeContext; | ||
import com.google.idea.blaze.base.settings.ui.OpenProjectViewAction; | ||
import com.google.idea.blaze.base.sync.BlazeSyncManager; | ||
import com.google.idea.blaze.base.sync.SyncListener; | ||
import com.google.idea.blaze.base.sync.SyncMode; | ||
import com.google.idea.blaze.base.sync.SyncResult; | ||
import com.google.idea.blaze.base.sync.projectstructure.DirectoryStructure; | ||
import com.google.idea.blaze.base.sync.projectview.ImportRoots; | ||
import com.google.idea.sdkcompat.general.EditorNotificationCompat; | ||
import com.intellij.notification.NotificationAction; | ||
import com.intellij.notification.NotificationGroupManager; | ||
import com.intellij.notification.NotificationType; | ||
import com.intellij.openapi.actionSystem.AnActionEvent; | ||
import com.intellij.openapi.actionSystem.PlatformDataKeys; | ||
import com.intellij.openapi.diagnostic.Logger; | ||
import com.intellij.openapi.fileEditor.FileEditor; | ||
import com.intellij.openapi.progress.ProgressIndicator; | ||
import com.intellij.openapi.progress.ProgressManager; | ||
import com.intellij.openapi.progress.Task; | ||
import com.intellij.openapi.project.Project; | ||
import com.intellij.openapi.roots.FileIndexFacade; | ||
import com.intellij.openapi.ui.Messages; | ||
import com.intellij.openapi.vfs.VirtualFile; | ||
import com.intellij.ui.EditorNotificationPanel; | ||
import com.intellij.ui.EditorNotificationProvider; | ||
import org.jetbrains.annotations.NotNull; | ||
import org.jetbrains.annotations.Nullable; | ||
|
||
import javax.swing.*; | ||
import java.io.File; | ||
import java.util.Collection; | ||
import java.util.HashSet; | ||
import java.util.Map; | ||
import java.util.Optional; | ||
import java.util.function.Function; | ||
|
||
/** | ||
* Action to exclude CMake directories. | ||
* It can be triggered in several ways: | ||
* - Manually, via dropdown menus or right-cliking on the file name. | ||
* - Triggered automatically when you open a file that should be excluded. | ||
* - Triggered automatically (in the background) at the end of every sync. | ||
*/ | ||
public class ExcludeDirectoryInProjectView extends BlazeProjectAction { | ||
private static final String CMAKE_CACHE_DIR_MARKER = "CMakeCache.txt"; | ||
private static final String IGNORED_DIRECTORIES_NOTIFICATION_MSG = "Some directories were identified as CMake files and excluded. Go to the project view for the full list."; | ||
|
||
@Override | ||
protected void actionPerformedInBlazeProject(Project project, AnActionEvent e) { | ||
VirtualFile vf = e.getData(PlatformDataKeys.VIRTUAL_FILE); | ||
WorkspaceRoot wsRoot = WorkspaceRoot.fromProject(project); | ||
if (vf == null || !wsRoot.isInWorkspace(vf)) { | ||
return; | ||
} | ||
WorkspacePath wsPath = wsRoot.workspacePathFor(vf); | ||
markIgnoredAndResync(project, wsPath); | ||
} | ||
|
||
/** | ||
* Will be triggered when we open a file. | ||
* If we detect the file to be inside a CMake cache directory, we will ask the user to ignore it. | ||
*/ | ||
public static class FileOpenListener implements EditorNotificationProvider { | ||
|
||
@Override | ||
public @Nullable Function<FileEditor, JComponent> collectNotificationData(@NotNull Project project, @NotNull VirtualFile virtualFile) { | ||
WorkspaceRoot wsRoot = WorkspaceRoot.fromProject(project); | ||
Optional<VirtualFile> maybeCmakeCacheDir = findParentCmakeCacheDir(wsRoot, virtualFile); | ||
if (maybeCmakeCacheDir.isEmpty()) { | ||
return null; | ||
} | ||
VirtualFile cmakeCacheDir = maybeCmakeCacheDir.get(); | ||
FileIndexFacade fileIndex = FileIndexFacade.getInstance(project); | ||
if (fileIndex.isExcludedFile(cmakeCacheDir)) { | ||
return null; | ||
} | ||
|
||
return fileEditor -> { | ||
EditorNotificationPanel panel = EditorNotificationCompat.Warning(fileEditor); | ||
panel.setText("It looks like this file belongs to a CMake cache directory. Please mark it as ignored."); | ||
panel.createActionLabel("Exclude and Resync", () -> { | ||
markIgnoredAndResync(project, wsRoot.workspacePathFor(cmakeCacheDir)); | ||
}); | ||
return panel; | ||
}; | ||
} | ||
} | ||
|
||
/** | ||
* Triggered after every sync, it will walk the import roots (what you have specified in `directories`) | ||
* and gather everything that looks like a CMake cache directory. | ||
* Then, it will mark them as ignored and trigger a sync. | ||
* The traversal is done as an interruptible process in the background, so there should be no performance implication. | ||
*/ | ||
public static class ExcludeCmakeCachedirsSyncListener implements SyncListener { | ||
|
||
private static final Logger logger = | ||
Logger.getInstance(ExcludeCmakeCachedirsSyncListener.class); | ||
|
||
private HashSet<WorkspacePath> walkDirectories(WorkspacePath current, DirectoryStructure ds, Collection<WorkspacePath> excludedDirectories, WorkspaceRoot wsRoot) { | ||
if (excludedDirectories.contains(current)) { | ||
return new HashSet<>(); | ||
} | ||
if (isCmakeCacheDir(wsRoot.fileForPath(current))) { | ||
HashSet<WorkspacePath> cmakeCacheDir = new HashSet<>(); | ||
cmakeCacheDir.add(current); | ||
// Early return to avoid traversing the children. | ||
return cmakeCacheDir; | ||
} | ||
HashSet<WorkspacePath> toIgnore = new HashSet<>(); | ||
for (Map.Entry<WorkspacePath, DirectoryStructure> child : ds.directories.entrySet()) { | ||
HashSet<WorkspacePath> toIgnoreInChildren = walkDirectories(child.getKey(), child.getValue(), excludedDirectories, wsRoot); | ||
toIgnore.addAll(toIgnoreInChildren); | ||
} | ||
return toIgnore; | ||
} | ||
|
||
@Override | ||
public void afterSync(Project project, BlazeContext context, SyncMode syncMode, SyncResult syncResult, ImmutableSet<Integer> buildIds) { | ||
WorkspaceRoot wsRoot = WorkspaceRoot.fromProject(project); | ||
ProjectViewSet pvs = ProjectViewManager.getInstance(project).reloadProjectView(context); | ||
ImportRoots importRoots = ImportRoots.forProjectSafe(project); | ||
ProgressManager.getInstance().run(new Task.Backgroundable(project, "Detect CMake cache directories", true) { | ||
@Override | ||
public void run(@NotNull ProgressIndicator progressIndicator) { | ||
DirectoryStructure directoryStructure = FutureUtil.waitForFuture( | ||
context, | ||
DirectoryStructure.getRootDirectoryStructure(project, wsRoot, pvs) | ||
).run().result(); | ||
if (directoryStructure == null) { | ||
logger.error("Failed to get excluded directories from project view."); | ||
progressIndicator.stop(); | ||
return; | ||
} | ||
|
||
HashSet<WorkspacePath> directoriesToIgnore = new HashSet<>(); | ||
for (WorkspacePath root : importRoots.rootDirectories()) { | ||
directoriesToIgnore.addAll(walkDirectories(root, directoryStructure.directories.get(root), importRoots.excludeDirectories(), wsRoot)); | ||
} | ||
if (!directoriesToIgnore.isEmpty()) { | ||
directoriesToIgnore.forEach(dir -> { | ||
markIgnored(project, dir); | ||
}); | ||
resync(project); | ||
ExcludeDirectoryInProjectView.notifyProjectViewChanged(project); | ||
} | ||
progressIndicator.stop(); | ||
} | ||
}); | ||
} | ||
} | ||
|
||
private static void markIgnored(Project project, WorkspacePath directoryToIgnore) { | ||
ProjectViewEdit edit = ProjectViewEdit.editLocalProjectView( | ||
project, | ||
builder -> { | ||
ListSection<DirectoryEntry> directorySection = builder.getLast(DirectorySection.KEY); | ||
builder.replace( | ||
directorySection, | ||
ListSection.update(DirectorySection.KEY, directorySection) | ||
.add(DirectoryEntry.exclude(directoryToIgnore)) | ||
); | ||
return true; | ||
} | ||
); | ||
if (edit == null) { | ||
Messages.showErrorDialog( | ||
"Could not modify project view. Check for errors in your project view and try again", | ||
"Error"); | ||
return; | ||
} | ||
edit.apply(); | ||
} | ||
|
||
private static void markIgnoredAndResync(Project project, WorkspacePath directoryToIgnore) { | ||
markIgnored(project, directoryToIgnore); | ||
resync(project); | ||
notifyProjectViewChanged(project); | ||
} | ||
|
||
private static void resync(Project project) { | ||
BlazeSyncManager.getInstance(project) | ||
.incrementalProjectSync(/* reason= */ "ExcludeDirectoryAction"); | ||
} | ||
|
||
private static void notifyProjectViewChanged(Project project) { | ||
NotificationGroupManager.getInstance() | ||
.getNotificationGroup("Apple.Bazel.NotificationGroup") | ||
.createNotification("Ignored CMake cache directories", IGNORED_DIRECTORIES_NOTIFICATION_MSG, NotificationType.INFORMATION) | ||
.addAction(NotificationAction.createSimpleExpiring( | ||
"See changes", | ||
() -> OpenProjectViewAction.openLocalProjectViewFile(project))) | ||
.notify(project); | ||
} | ||
|
||
|
||
public static boolean isCmakeCacheDir(@NotNull File f) { | ||
return f.exists() && f.isDirectory() && f.listFiles((dir, name) -> name.equals(CMAKE_CACHE_DIR_MARKER)).length > 0; | ||
} | ||
|
||
public static boolean isCmakeCacheDir(@NotNull VirtualFile vf) { | ||
return vf.exists() && vf.isDirectory() && vf.findChild(CMAKE_CACHE_DIR_MARKER) != null; | ||
} | ||
|
||
@NotNull | ||
protected static Optional<VirtualFile> findParentCmakeCacheDir(WorkspaceRoot wsRoot, VirtualFile vf) { | ||
if (vf == null || !vf.exists() || !wsRoot.isInWorkspace(vf)) { | ||
return Optional.empty(); | ||
} | ||
if (isCmakeCacheDir(vf)) { | ||
return Optional.of(vf); | ||
} | ||
return findParentCmakeCacheDir(wsRoot, vf.getParent()); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
22 changes: 22 additions & 0 deletions
22
base/src/com/google/idea/blaze/base/actions/ActionMetadataExtractor.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package com.google.idea.blaze.base.actions; | ||
|
||
import com.intellij.ide.projectView.impl.nodes.NamedLibraryElementNode; | ||
import com.intellij.openapi.actionSystem.CommonDataKeys; | ||
import com.intellij.openapi.actionSystem.DataContext; | ||
import com.intellij.pom.Navigatable; | ||
|
||
import javax.annotation.Nullable; | ||
|
||
public class ActionMetadataExtractor { | ||
@Nullable | ||
public static NamedLibraryElementNode findLibraryNode(DataContext dataContext) { | ||
Navigatable[] navigatables = CommonDataKeys.NAVIGATABLE_ARRAY.getData(dataContext); | ||
if (navigatables != null && navigatables.length == 1) { | ||
Navigatable navigatable = navigatables[0]; | ||
if (navigatable instanceof NamedLibraryElementNode) { | ||
return (NamedLibraryElementNode) navigatable; | ||
} | ||
} | ||
return null; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.