From 95aa31da9b3fb5a000c45e247632ed5dccbe9fda Mon Sep 17 00:00:00 2001 From: Saljack Date: Fri, 5 Jun 2026 18:49:42 +0200 Subject: [PATCH] Remember sort order of the file table in git diff view Closes #7742 --- .../netbeans/modules/git/GitModuleConfig.java | 51 +++++++ .../modules/git/ui/diff/DiffFileTable.java | 46 ++++++ .../git/ui/diff/MultiDiffPanelController.java | 3 + .../git/ui/diff/DiffFileTableTest.java | 134 ++++++++++++++++++ 4 files changed, 234 insertions(+) create mode 100644 ide/git/test/unit/src/org/netbeans/modules/git/ui/diff/DiffFileTableTest.java diff --git a/ide/git/src/org/netbeans/modules/git/GitModuleConfig.java b/ide/git/src/org/netbeans/modules/git/GitModuleConfig.java index dab906c5a432..0ce04e0ca0f1 100644 --- a/ide/git/src/org/netbeans/modules/git/GitModuleConfig.java +++ b/ide/git/src/org/netbeans/modules/git/GitModuleConfig.java @@ -28,10 +28,15 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; +import java.util.SequencedMap; import java.util.Set; +import java.util.StringJoiner; import java.util.logging.Level; import java.util.prefs.Preferences; +import javax.swing.SortOrder; import org.netbeans.libs.git.GitURI; import org.netbeans.modules.git.FileInformation.Mode; import org.netbeans.modules.git.ui.repository.RepositoryInfo; @@ -68,6 +73,8 @@ public final class GitModuleConfig { private static final String GURI_PASSPHRASE = "guri_passphrase"; private static final String PROP_STATUS_VIEW_MODE = "statusViewMode"; //NOI18N private static final String PROP_DIFF_VIEW_MODE = "diffViewMode"; //NOI18N + private static final String KEY_SORTING = "sortingStatus."; //NOI18N + private static final String SORTING_SEPARATOR = "###"; //NOI18N private static final String DELIMITER = "<=~=>"; // NOI18N private static final String KEY_SHOW_HISTORY_MERGES = "showHistoryMerges"; //NOI18N private static final String KEY_SHOW_FILE_INFO = "showFileInfo"; //NOI18N @@ -467,6 +474,50 @@ public void setDiffViewMode (int value) { getPreferences().putInt(PROP_DIFF_VIEW_MODE, value); } + public SequencedMap getSortingStatus (String panel) { + SequencedMap sortingState = new LinkedHashMap<>(); + String packed = getPreferences().get(KEY_SORTING + panel, null); + if (packed != null) { + String[] tokens = packed.split(SORTING_SEPARATOR); + for (int i = 0; i + 1 < tokens.length; i += 2) { + try { + sortingState.put(tokens[i], toSortOrder(Integer.parseInt(tokens[i + 1]))); + } catch (NumberFormatException ex) { + // ignore + } + } + } + return sortingState; + } + + public void setSortingStatus (String panel, Map sortingState) { + StringJoiner packed = new StringJoiner(SORTING_SEPARATOR); + for (Map.Entry e : sortingState.entrySet()) { + packed.add(e.getKey()).add(Integer.toString(toSortingState(e.getValue()))); + } + if (packed.length() > 0) { + getPreferences().put(KEY_SORTING + panel, packed.toString()); + } else { + getPreferences().remove(KEY_SORTING + panel); + } + } + + private static int toSortingState (SortOrder order) { + return switch (order) { + case ASCENDING -> 1; + case DESCENDING -> -1; + case UNSORTED -> 0; + }; + } + + private static SortOrder toSortOrder (int sortingState) { + return switch (sortingState) { + case 1 -> SortOrder.ASCENDING; + case -1 -> SortOrder.DESCENDING; + default -> SortOrder.UNSORTED; + }; + } + public int getStatusViewMode (int def) { return getPreferences().getInt(PROP_STATUS_VIEW_MODE, def); } diff --git a/ide/git/src/org/netbeans/modules/git/ui/diff/DiffFileTable.java b/ide/git/src/org/netbeans/modules/git/ui/diff/DiffFileTable.java index 9549e87e95b1..7361c2373d1b 100644 --- a/ide/git/src/org/netbeans/modules/git/ui/diff/DiffFileTable.java +++ b/ide/git/src/org/netbeans/modules/git/ui/diff/DiffFileTable.java @@ -28,7 +28,10 @@ import javax.swing.JComponent; import javax.swing.JPopupMenu; import javax.swing.JTable; +import javax.swing.SortOrder; import javax.swing.table.DefaultTableCellRenderer; +import javax.swing.table.TableColumnModel; +import org.netbeans.modules.git.GitModuleConfig; import org.netbeans.modules.git.GitStatusNode.GitStatusProperty; import org.netbeans.modules.versioning.util.status.VCSStatusTableModel; import org.netbeans.modules.versioning.util.status.VCSStatusTable; @@ -36,6 +39,8 @@ import org.netbeans.modules.versioning.util.FilePathCellRenderer; import org.netbeans.modules.versioning.util.status.VCSStatusNode.NameProperty; import org.netbeans.modules.versioning.util.status.VCSStatusNode.PathProperty; +import org.netbeans.swing.etable.ETable; +import org.netbeans.swing.etable.ETableColumn; import org.openide.cookies.EditorCookie; import org.openide.nodes.Node; import org.openide.nodes.PropertySupport.ReadOnly; @@ -63,10 +68,51 @@ class DiffFileTable extends VCSStatusTable implements DiffFileViewComp private PropertyChangeListener changeListener; private final MultiDiffPanelController controller; + private static final String SORTING_PANEL = "diffView"; //NOI18N + + private static final List COLUMN_NAMES = List.of( + NameProperty.NAME, + GitStatusProperty.NAME, + PathProperty.NAME + ); + public DiffFileTable (VCSStatusTableModel model, MultiDiffPanelController controller) { super(model); this.controller = controller; setDefaultRenderer(new DiffTableCellRenderer()); + setSortingStatus(); + } + + private void setSortingStatus () { + Map sortingStatus = GitModuleConfig.getDefault().getSortingStatus(SORTING_PANEL); + ETable eTable = (ETable) getTable(); + int rank = 1; + for (Map.Entry e : sortingStatus.entrySet()) { + SortOrder state = e.getValue(); + int index = COLUMN_NAMES.indexOf(e.getKey()); + if (state != SortOrder.UNSORTED && index >= 0) { + eTable.setColumnSorted(index, state == SortOrder.ASCENDING, rank++); + } + } + } + + void storeSortingStatus () { + List sortedColumns = new ArrayList<>(COLUMN_NAMES.size()); + TableColumnModel columnModel = getTable().getColumnModel(); + for (int i = 0; i < columnModel.getColumnCount(); ++i) { + if (columnModel.getColumn(i) instanceof ETableColumn eColumn + && eColumn.isSorted() + && eColumn.getModelIndex() < COLUMN_NAMES.size()) { + sortedColumns.add(eColumn); + } + } + sortedColumns.sort(Comparator.comparingInt(ETableColumn::getSortRank)); + Map sortingStatus = new LinkedHashMap<>(sortedColumns.size()); + for (ETableColumn column : sortedColumns) { + sortingStatus.put(COLUMN_NAMES.get(column.getModelIndex()), + column.isAscending() ? SortOrder.ASCENDING : SortOrder.DESCENDING); + } + GitModuleConfig.getDefault().setSortingStatus(SORTING_PANEL, sortingStatus); } @Override diff --git a/ide/git/src/org/netbeans/modules/git/ui/diff/MultiDiffPanelController.java b/ide/git/src/org/netbeans/modules/git/ui/diff/MultiDiffPanelController.java index 2d6f978bb57a..fc012c5772da 100644 --- a/ide/git/src/org/netbeans/modules/git/ui/diff/MultiDiffPanelController.java +++ b/ide/git/src/org/netbeans/modules/git/ui/diff/MultiDiffPanelController.java @@ -426,6 +426,9 @@ UndoRedo getUndoRedo () { } public void componentClosed () { + if (fileListComponent != null) { + fileListComponent.storeSortingStatus(); + } setSetups(Collections.emptyMap(), Collections.emptyMap()); prevAction.setEnabled(false); nextAction.setEnabled(false); diff --git a/ide/git/test/unit/src/org/netbeans/modules/git/ui/diff/DiffFileTableTest.java b/ide/git/test/unit/src/org/netbeans/modules/git/ui/diff/DiffFileTableTest.java new file mode 100644 index 000000000000..179e0091801f --- /dev/null +++ b/ide/git/test/unit/src/org/netbeans/modules/git/ui/diff/DiffFileTableTest.java @@ -0,0 +1,134 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.netbeans.modules.git.ui.diff; + +import java.util.LinkedHashMap; +import java.util.Map; +import javax.swing.JScrollPane; +import javax.swing.SortOrder; +import org.netbeans.junit.NbTestCase; +import org.netbeans.modules.git.GitModuleConfig; +import org.netbeans.modules.versioning.util.status.VCSStatusNode.NameProperty; +import org.netbeans.modules.versioning.util.status.VCSStatusNode.PathProperty; +import org.netbeans.modules.versioning.util.status.VCSStatusTableModel; +import org.netbeans.swing.etable.ETable; +import org.netbeans.swing.etable.ETableColumn; +import org.netbeans.swing.etable.ETableColumnModel; + +/** + * Tests that the diff file table persists and restores its sort order. + */ +public class DiffFileTableTest extends NbTestCase { + + private static final String SORTING_PANEL = "diffView"; + private static final int COLUMN_INDEX_NAME = 0; + private static final int COLUMN_INDEX_PATH = 2; + + public DiffFileTableTest (String name) { + super(name); + } + + @Override + protected boolean runInEQ () { + return true; + } + + @Override + protected void setUp() throws Exception { + super.setUp(); + GitModuleConfig.getDefault().setSortingStatus(SORTING_PANEL, Map.of()); + } + + public void testNoSortingRestoredByDefault () { + DiffFileTable t = createTable(); + + assertNull(findSortedColumn(t, 1)); + } + + public void testRestoreSorting () { + GitModuleConfig.getDefault().setSortingStatus(SORTING_PANEL, + Map.of(PathProperty.NAME, SortOrder.DESCENDING)); + + DiffFileTable t = createTable(); + + ETableColumn column = findSortedColumn(t, 1); + assertNotNull(column); + assertEquals(COLUMN_INDEX_PATH, column.getModelIndex()); + assertFalse(column.isAscending()); + } + + public void testRestoreMultiColumnSorting () { + LinkedHashMap sortingStatus = new LinkedHashMap<>(); + sortingStatus.put(PathProperty.NAME, SortOrder.ASCENDING); + sortingStatus.put(NameProperty.NAME, SortOrder.DESCENDING); + GitModuleConfig.getDefault().setSortingStatus(SORTING_PANEL, sortingStatus); + + DiffFileTable t = createTable(); + + ETableColumn primary = findSortedColumn(t, 1); + assertNotNull(primary); + assertEquals(COLUMN_INDEX_PATH, primary.getModelIndex()); + assertTrue(primary.isAscending()); + ETableColumn secondary = findSortedColumn(t, 2); + assertNotNull(secondary); + assertEquals(COLUMN_INDEX_NAME, secondary.getModelIndex()); + assertFalse(secondary.isAscending()); + } + + public void testPersistSorting () { + DiffFileTable t = createTable(); + getETable(t).setColumnSorted(COLUMN_INDEX_NAME, true, 1); + + t.storeSortingStatus(); + + Map sortingStatus = GitModuleConfig.getDefault().getSortingStatus(SORTING_PANEL); + assertEquals(Map.of(NameProperty.NAME, SortOrder.ASCENDING), sortingStatus); + } + + public void testPersistUnsorted () { + GitModuleConfig.getDefault().setSortingStatus(SORTING_PANEL, + Map.of(PathProperty.NAME, SortOrder.ASCENDING)); + DiffFileTable t = createTable(); + ((ETableColumnModel) getETable(t).getColumnModel()).clearSortedColumns(); + + t.storeSortingStatus(); + + assertTrue(GitModuleConfig.getDefault().getSortingStatus(SORTING_PANEL).isEmpty()); + } + + private static DiffFileTable createTable () { + return new DiffFileTable(new VCSStatusTableModel(new DiffNode[0]), null); + } + + private static ETable getETable (DiffFileTable table) { + return (ETable) ((JScrollPane) table.getComponent()).getViewport().getView(); + } + + private static ETableColumn findSortedColumn (DiffFileTable table, int sortRank) { + ETable eTable = getETable(table); + for (int i = 0; i < eTable.getColumnModel().getColumnCount(); ++i) { + ETableColumn column = (ETableColumn) eTable.getColumnModel().getColumn(i); + if (column.isSorted() && column.getSortRank() == sortRank) { + return column; + } + } + return null; + } +}