IDEA-74443 Implement Sort by Time in Project View

GitOrigin-RevId: 30511cc19142b341a3d135f99320a01a2bd36830
This commit is contained in:
Sergei Tachenov
2023-05-12 13:57:33 +00:00
committed by intellij-monorepo-bot
parent 8f2235683d
commit f3d65d6d74
21 changed files with 181 additions and 17 deletions
@@ -15,6 +15,12 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.tree.TreePath;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.attribute.FileTime;
import java.time.LocalDateTime;
import java.time.Month;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
@@ -94,6 +100,41 @@ public class ProjectTreeSortingTest extends BaseProjectViewTestCase {
""");
}
public void testSortByTime() {
setModificationTime("a.java", LocalDateTime.of(2023, Month.MAY, 8, 0, 0));
setModificationTime("a.txt", LocalDateTime.of(2023, Month.MAY, 8, 0, 1));
setModificationTime("b.java", LocalDateTime.of(2023, Month.MAY, 7, 0, 1));
setModificationTime("b.txt", LocalDateTime.of(2023, Month.MAY, 7, 0, 0));
myProjectView.setSortByTime(myPane.getId(), false);
myStructure.setSortByTime(false);
assertTree("""
-sortByTime
a.java
a.txt
b.java
b.txt
""");
myProjectView.setSortByTime(myPane.getId(), true);
myStructure.setSortByTime(true);
assertTree("""
-sortByTime
b.txt
b.java
a.java
a.txt
""");
}
private void setModificationTime(String fileName, LocalDateTime time) {
try {
Files.setLastModifiedTime(getContentRoot().toNioPath().resolve(fileName), FileTime.from(time.toInstant(ZoneOffset.UTC)));
}
catch (IOException e) {
throw new RuntimeException(e);
}
}
public void testFoldersOnTop() {
// first, check with 'sort by type' disabled
myProjectView.setSortByType(myPane.getId(), false);
@@ -8,4 +8,5 @@ data class NodeSortSettings(
val isManualOrder: Boolean,
val isFoldersAlwaysOnTop: Boolean,
val isSortByType: Boolean,
val isSortByTime: Boolean,
)
@@ -255,6 +255,11 @@ public abstract class ProjectViewNode <Value> extends AbstractTreeNode<Value> im
return null;
}
@Nullable
public Comparable<?> getTimeSortKey() {
return null;
}
public boolean shouldDrillDownOnEmptyElement() {
return false;
}
@@ -144,8 +144,16 @@ public abstract class ProjectView {
public abstract void setManualOrder(@NotNull String paneId, boolean enabled);
public abstract boolean isSortByType(String paneId);
public abstract void setSortByType(@NotNull String paneId, final boolean sortByType);
public boolean isSortByTime(String paneId) {
return false;
}
public void setSortByTime(@NotNull String paneId, final boolean sortByTime) {
}
public abstract AbstractProjectViewPane getCurrentProjectViewPane();
@NotNull
@@ -15,6 +15,10 @@ public interface ProjectViewSettings extends ViewSettings {
return false;
}
default boolean isSortByTime() {
return false;
}
/**
* If {@code true} then {@link com.intellij.ide.projectView.impl.NestingTreeStructureProvider} will modify the tree presentation
* according to the rules managed by {@link com.intellij.ide.projectView.impl.ProjectViewFileNestingService}: some peer files will be
@@ -111,6 +115,12 @@ public interface ProjectViewSettings extends ViewSettings {
return view != null && view.isFoldersAlwaysOnTop(getPaneID(view));
}
@Override
public boolean isSortByTime() {
ProjectView view = getProjectView();
return view != null && view.isSortByTime(getPaneID(view));
}
@Override
public boolean isShowMembers() {
ProjectView view = getProjectView();
@@ -1024,6 +1024,11 @@ public abstract class AbstractProjectViewPane implements DataProvider, Disposabl
return true;
}
@ApiStatus.Internal
public boolean supportsSortByTime() {
return true;
}
@NotNull
private static Color getFileForegroundColor(@NotNull Project project, @NotNull VirtualFile file) {
FileEditorManager manager = FileEditorManager.getInstance(project);
@@ -1,9 +1,9 @@
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
package com.intellij.ide.projectView.impl;
import com.intellij.ide.projectView.NodeSortSettings;
import com.intellij.ide.projectView.ProjectView;
import com.intellij.ide.projectView.ProjectViewNode;
import com.intellij.ide.projectView.NodeSortSettings;
import com.intellij.ide.util.treeView.AbstractTreeNode;
import com.intellij.ide.util.treeView.AlphaComparator;
import com.intellij.ide.util.treeView.NodeDescriptor;
@@ -49,7 +49,7 @@ public class GroupByTypeComparator implements Comparator<NodeDescriptor<?>> {
if (descriptor1 instanceof ProjectViewNode<?> node1 && descriptor2 instanceof ProjectViewNode<?> node2) {
NodeSortSettings settings = new NodeSortSettings(isManualOrder(), isFoldersAlwaysOnTop(), isSortByType());
NodeSortSettings settings = new NodeSortSettings(isManualOrder(), isFoldersAlwaysOnTop(), isSortByType(), isSortByTime());
int sortResult = node1.getSortOrder(settings).compareTo(node2.getSortOrder(settings));
if (sortResult != 0) return sortResult;
@@ -74,15 +74,21 @@ public class GroupByTypeComparator implements Comparator<NodeDescriptor<?>> {
}
}
if (settings.isSortByType()) {
final Comparable typeSortKey1 = node1.getTypeSortKey();
final Comparable typeSortKey2 = node2.getTypeSortKey();
if (settings.isSortByTime()) {
final Comparable<?> timeSortKey1 = node1.getTimeSortKey();
final Comparable<?> timeSortKey2 = node2.getTimeSortKey();
int result = compare(timeSortKey1, timeSortKey2);
if (result != 0) return result;
}
else if (settings.isSortByType()) {
final Comparable<?> typeSortKey1 = node1.getTypeSortKey();
final Comparable<?> typeSortKey2 = node2.getTypeSortKey();
int result = compare(typeSortKey1, typeSortKey2);
if (result != 0) return result;
}
else {
final Comparable typeSortKey1 = node1.getSortKey();
final Comparable typeSortKey2 = node2.getSortKey();
final Comparable<?> typeSortKey1 = node1.getSortKey();
final Comparable<?> typeSortKey2 = node2.getSortKey();
if (typeSortKey1 != null && typeSortKey2 != null) {
int result = compare(typeSortKey1, typeSortKey2);
if (result != 0) return result;
@@ -116,6 +122,13 @@ public class GroupByTypeComparator implements Comparator<NodeDescriptor<?>> {
return ProjectView.getInstance(project).isSortByType(myPaneId);
}
protected boolean isSortByTime() {
if (project == null) {
return false;
}
return ProjectView.getInstance(project).isSortByTime(myPaneId);
}
protected boolean isAbbreviateQualifiedNames() {
return project != null && ProjectView.getInstance(project).isAbbreviatePackageNames(myPaneId);
}
@@ -70,4 +70,9 @@ public abstract class ProjectTreeStructure extends AbstractProjectTreeStructure
return ProjectView.getInstance(myProject).isShowVisibilityIcons(myId);
}
@Override
public boolean isSortByTime() {
return ProjectView.getInstance(myProject).isSortByTime(myId);
}
}
@@ -516,6 +516,28 @@ public class ProjectViewImpl extends ProjectView implements PersistentStateCompo
}
};
private final Option mySortByTime = new Option() {
@Override
public boolean isEnabled(@NotNull AbstractProjectViewPane pane) {
return pane.supportsSortByTime();
}
@Override
public boolean isSelected() {
return myCurrentState.getSortByTime();
}
@Override
public void setSelected(boolean selected) {
if (myProject.isDisposed()) return;
boolean updated = selected != isSelected();
myCurrentState.setSortByTime(selected);
getDefaultState().setSortByTime(selected);
getGlobalOptions().setSortByTime(selected);
if (updated) updatePanes(true);
}
};
private String myCurrentViewId;
private String myCurrentViewSubId;
// - options
@@ -1824,11 +1846,21 @@ public class ProjectViewImpl extends ProjectView implements PersistentStateCompo
return mySortByType.isSelected() && mySortByType.isEnabled(paneId);
}
@Override
public boolean isSortByTime(String paneId) {
return mySortByTime.isSelected() && mySortByTime.isEnabled(paneId);
}
@Override
public void setSortByType(@NotNull String paneId, final boolean sortByType) {
if (mySortByType.isEnabled(paneId)) mySortByType.setSelected(sortByType);
}
@Override
public void setSortByTime(@NotNull String paneId, boolean sortByTime) {
if (mySortByTime.isEnabled(paneId)) mySortByTime.setSelected(sortByTime);
}
boolean isSelectOpenedFileEnabled() {
return !isAutoscrollFromSourceEnabled(myCurrentViewId) || AdvancedSettings.getBoolean("project.view.do.not.autoscroll.to.libraries");
}
@@ -2019,6 +2051,12 @@ public class ProjectViewImpl extends ProjectView implements PersistentStateCompo
super(view -> view.mySortByType);
}
}
static final class SortByTime extends Action {
SortByTime() {
super(view -> view.mySortByTime);
}
}
}
private static final class ProjectViewPaneChangesCollector extends CounterUsagesCollector {
@@ -13,8 +13,6 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFileSystemItem
import com.intellij.ui.SimpleTextAttributes
import com.intellij.util.text.JBDateFormat
import java.nio.file.Files
import java.nio.file.attribute.BasicFileAttributes
internal fun appendInplaceComments(node: ProjectViewNode<*>, appender: InplaceCommentAppender) {
val parentNode = node.parent
@@ -26,14 +24,7 @@ internal fun appendInplaceComments(node: ProjectViewNode<*>, appender: InplaceCo
// To be used in Rider once it migrates from legacy logic, don't change the signature and/or visibility.
fun appendInplaceComments(appender: InplaceCommentAppender, project: Project?, file: VirtualFile?) {
val ioFile = if (file == null || file.isDirectory || !file.isInLocalFileSystem) null else file.toNioPath()
val fileAttributes = try {
if (ioFile == null) null else Files.readAttributes(ioFile, BasicFileAttributes::class.java)
}
catch (ignored: Exception) {
null
}
val fileAttributes = getFileAttributes(file)
if (fileAttributes != null) {
appender.append(" ", SimpleTextAttributes.REGULAR_ATTRIBUTES)
val attributes = SimpleTextAttributes.GRAYED_SMALL_ATTRIBUTES
@@ -16,6 +16,7 @@ class ProjectViewSharedSettings : PersistentStateComponent<ProjectViewSharedSett
var flattenPackages: Boolean = false
var showMembers: Boolean = false
var sortByType: Boolean = false
var sortByTime: Boolean = false
var showModules: Boolean = true
var flattenModules: Boolean = false
var showExcludedFiles: Boolean = true
@@ -38,6 +38,7 @@ class ProjectViewState : PersistentStateComponent<ProjectViewState> {
var showURL = ProjectViewSettings.Immutable.DEFAULT.isShowURL
var showVisibilityIcons = ProjectViewSettings.Immutable.DEFAULT.isShowVisibilityIcons
var sortByType = false
var sortByTime = false
var useFileNestingRules = ProjectViewSettings.Immutable.DEFAULT.isUseFileNestingRules
override fun noStateLoaded() {
@@ -62,6 +63,7 @@ class ProjectViewState : PersistentStateComponent<ProjectViewState> {
showURL = Registry.`is`("project.tree.structure.show.url")
showVisibilityIcons = ProjectViewSharedSettings.instance.showVisibilityIcons
sortByType = ProjectViewSharedSettings.instance.sortByType
sortByTime = ProjectViewSharedSettings.instance.sortByTime
}
override fun loadState(state: ProjectViewState) {
@@ -49,6 +49,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import static com.intellij.ide.projectView.impl.ProjectViewUtilKt.getFileAttributes;
import static com.intellij.ide.util.treeView.NodeRenderer.getSimpleTextAttributes;
/**
@@ -58,6 +59,7 @@ import static com.intellij.ide.util.treeView.NodeRenderer.getSimpleTextAttribute
*/
public abstract class AbstractPsiBasedNode<Value> extends ProjectViewNode<Value> implements ValidateableNode, StatePreservingNavigatable {
private static final Logger LOG = Logger.getInstance(AbstractPsiBasedNode.class.getName());
private volatile long timestamp;
protected AbstractPsiBasedNode(final Project project,
@NotNull Value value,
@@ -139,6 +141,11 @@ public abstract class AbstractPsiBasedNode<Value> extends ProjectViewNode<Value>
return PsiUtilCore.getVirtualFile(psiElement);
}
@Override
public @Nullable Comparable<?> getTimeSortKey() {
return timestamp == 0 ? null : timestamp;
}
@Override
protected void appendInplaceComments(@NotNull InplaceCommentAppender appender) {
if (UISettings.getInstance().getShowInplaceComments()) {
@@ -204,9 +211,19 @@ public abstract class AbstractPsiBasedNode<Value> extends ProjectViewNode<Value>
updateImpl(data);
data.setIcon(patchIcon(myProject, data.getIcon(true), getVirtualFile()));
CompoundProjectViewNodeDecorator.get(myProject).decorate(this, data);
updateTimestamp();
});
}
private void updateTimestamp() {
if (getSettings() instanceof ProjectViewSettings projectViewSettings && !projectViewSettings.isSortByTime()) {
timestamp = 0; // skip for performance reasons
return;
}
var attributes = getFileAttributes(getVirtualFile());
timestamp = attributes == null ? 0 : attributes.lastModifiedTime().toMillis();
}
@Iconable.IconFlags
protected int getIconableFlags() {
int flags = 0;
@@ -17,6 +17,8 @@ import com.intellij.openapi.vfs.VirtualFile
import com.intellij.psi.PsiDirectory
import com.intellij.util.SmartList
import org.jetbrains.annotations.ApiStatus
import java.nio.file.Files
import java.nio.file.attribute.BasicFileAttributes
internal fun getNodeElement(userObject: Any?): Any? {
return when (userObject) {
@@ -108,3 +110,14 @@ fun getSelectedLibrary(userObjectsPath: Array<out Any?>?): LibraryOrderEntry? {
?: return null
return ModuleRootManager.getInstance(module).fileIndex.getOrderEntryForFile(directory.virtualFile) as? LibraryOrderEntry
}
internal fun getFileAttributes(file: VirtualFile?): BasicFileAttributes? {
val ioFile = if (file == null || file.isDirectory || !file.isInLocalFileSystem) null else file.toNioPath()
val fileAttributes = try {
if (ioFile == null) null else Files.readAttributes(ioFile, BasicFileAttributes::class.java)
}
catch (ignored: Exception) {
null
}
return fileAttributes
}
@@ -1471,6 +1471,8 @@ action.ProjectView.ShowVisibilityIcons.text=Show Visibility Icons
action.ProjectView.ShowVisibilityIcons.description=Show icon of element visibility before its name
action.ProjectView.SortByType.text=Sort by Type
action.ProjectView.SortByType.description=Sort files of different types separately
action.ProjectView.SortByTime.text=Sort by Modification Time
action.ProjectView.SortByTime.description=Sort files of by their last modification time
action.ScopeView.EditScopes.text=Ed_it Scopes...
action.ScopeView.EditScopes.description=Edit Scopes...
action.ProjectView.FileNesting.text=File Nesting...
@@ -927,6 +927,8 @@
<action id="ProjectView.SortByType"
class="com.intellij.ide.projectView.impl.ProjectViewImpl$Action$SortByType"
icon="AllIcons.ObjectBrowser.SortByType"/>
<action id="ProjectView.SortByTime"
class="com.intellij.ide.projectView.impl.ProjectViewImpl$Action$SortByTime"/>
<action id="ProjectView.FoldersAlwaysOnTop"
class="com.intellij.ide.projectView.impl.ProjectViewImpl$Action$FoldersAlwaysOnTop"/>
</group>
@@ -20,6 +20,7 @@ public class TestProjectTreeStructure extends AbstractProjectTreeStructure imple
protected boolean myHideEmptyMiddlePackages;
protected boolean myFlattenPackages;
private boolean myFlattenModules;
private boolean mySortByTime;
protected boolean myShowLibraryContents = true;
public TestProjectTreeStructure(@NotNull Project project, Disposable parentDisposable) {
@@ -80,6 +81,15 @@ public class TestProjectTreeStructure extends AbstractProjectTreeStructure imple
myFlattenModules = flattenModules;
}
@Override
public boolean isSortByTime() {
return mySortByTime;
}
public void setSortByTime(boolean sortByTime) {
mySortByTime = sortByTime;
}
public void setHideEmptyMiddlePackages(boolean hideEmptyMiddlePackages) {
myHideEmptyMiddlePackages = hideEmptyMiddlePackages;
}