mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
IDEA-74443 Implement Sort by Time in Project View
GitOrigin-RevId: 30511cc19142b341a3d135f99320a01a2bd36830
This commit is contained in:
committed by
intellij-monorepo-bot
parent
8f2235683d
commit
f3d65d6d74
@@ -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);
|
||||
|
||||
+20
-7
@@ -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 {
|
||||
|
||||
+1
-10
@@ -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) {
|
||||
|
||||
+17
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user