From 642c71b6fada84438b43a4b886beca50b9081852 Mon Sep 17 00:00:00 2001 From: Ondro Mihalyi Date: Sat, 4 Apr 2026 17:41:28 +0200 Subject: [PATCH] Optimizations of status change notifications ## Skip firing events for up-to-date Skip firing events for up-to-date files that are not yet in the cache, since UPTODATE is the default for managed files. This drastically reduces the time spent in the refreshStatusesBatch method on big repositories executed when Commit dialog opens. For example, on the Netbeans repository, from around 8 seconds to 20ms. ## Batch status change notifications Batch status change notifications to avoid per-file event overhead. Replace per-file PROP_FILE_STATUS_CHANGED firing in refreshStatusesBatch with a single PROP_FILES_STATUS_CHANGED batch event, eliminating 100k redundant propertyChange/schedule/SwingUtilities.invokeLater calls on first load. This improves performance a bit because it eliminates many method calls. However, in the end, the number of files changed is the same so the event handler still needs to process all of them. ## Move status updates to a background thread File status update requires I/O operation to refresh files metadata from FS. This is very slow when many files need to be updated. Moving them to a background thread offloads this slow operation from the main thread. Updates can run asynchronously without blocking the main thread that fires the status events, they just update UI hints, they have no impact no behavior. For the whole Netbeans repository, this shortens the time it takes to complete the firePropertyChange event from 6 seconds to 1 second. --- ide/git/nbproject/project.properties | 2 +- .../netbeans/modules/git/FileStatusCache.java | 10 +- .../src/org/netbeans/modules/git/GitVCS.java | 19 ++- .../git/ui/diff/MultiDiffPanelController.java | 50 +++--- .../ui/status/VersioningPanelController.java | 8 + .../core/VersioningAnnotationProvider.java | 146 +++++++++--------- .../ui/diff/DiffSidebarManager.java | 61 ++++---- 7 files changed, 169 insertions(+), 127 deletions(-) diff --git a/ide/git/nbproject/project.properties b/ide/git/nbproject/project.properties index f546c5f6b429..a3fbf1012c3d 100644 --- a/ide/git/nbproject/project.properties +++ b/ide/git/nbproject/project.properties @@ -15,7 +15,7 @@ # specific language governing permissions and limitations # under the License. javac.compilerargs=-Xlint -javac.release=17 +javac.release=21 nbm.homepage=http://netbeans.org/projects/versioncontrol/pages/Git_main nbm.module.author=Ondrej Vrabec nbm.needs.restart=true diff --git a/ide/git/src/org/netbeans/modules/git/FileStatusCache.java b/ide/git/src/org/netbeans/modules/git/FileStatusCache.java index 011f0983bd2e..c333fccbc4c0 100644 --- a/ide/git/src/org/netbeans/modules/git/FileStatusCache.java +++ b/ide/git/src/org/netbeans/modules/git/FileStatusCache.java @@ -57,6 +57,7 @@ public class FileStatusCache { public static final String PROP_FILE_STATUS_CHANGED = "status.changed"; // NOI18N + public static final String PROP_FILES_STATUS_CHANGED = "status.changed.batch"; // NOI18N private final CacheIndex conflictedFiles, modifiedFiles, ignoredFiles; private static final Logger LOG = Logger.getLogger("org.netbeans.modules.git.status.cache"); //NOI18N @@ -562,6 +563,7 @@ private void refreshStatusesBatch (Map interestingFiles) { boolean fireEvent = true; current = getInfo(file); fi = checkForIgnore(fi, current, file); + boolean upToDateOnFirstLoad = current == null && fi.getStatus().equals(EnumSet.of(Status.UPTODATE)); if (equivalent(fi, current)) { // no need to fire an event if (Utilities.isWindows() || Utilities.isMac()) { @@ -570,6 +572,10 @@ private void refreshStatusesBatch (Map interestingFiles) { } else { continue; } + } else if (upToDateOnFirstLoad) { + // file is up-to-date and not yet in cache: UPTODATE is the implicit default, + // no cache update or event needed + continue; } boolean addToIndex = updateCachedValue(fi, file); indexUpdates.add(new IndexUpdateItem(file, fi, addToIndex)); @@ -579,8 +585,8 @@ private void refreshStatusesBatch (Map interestingFiles) { } updateIndexBatch(indexUpdates); } - for (ChangedEvent event : events) { - fireFileStatusChanged(event); + if (!events.isEmpty()) { + listenerSupport.firePropertyChange(PROP_FILES_STATUS_CHANGED, null, events); } } diff --git a/ide/git/src/org/netbeans/modules/git/GitVCS.java b/ide/git/src/org/netbeans/modules/git/GitVCS.java index c8951d42c32a..d5ff165c2413 100644 --- a/ide/git/src/org/netbeans/modules/git/GitVCS.java +++ b/ide/git/src/org/netbeans/modules/git/GitVCS.java @@ -23,6 +23,8 @@ import java.beans.PropertyChangeListener; import java.util.MissingResourceException; import java.io.File; +import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.logging.Logger; import java.util.prefs.PreferenceChangeEvent; @@ -38,9 +40,9 @@ * @author ondra */ @VersioningSystem.Registration( - displayName="#CTL_Git_DisplayName", - menuLabel="#CTL_Git_MainMenu", - metadataFolderNames={".git"}, + displayName="#CTL_Git_DisplayName", + menuLabel="#CTL_Git_MainMenu", + metadataFolderNames={".git"}, actionsCategory="Git" ) public class GitVCS extends VersioningSystem implements PropertyChangeListener, PreferenceChangeListener { @@ -48,7 +50,7 @@ public class GitVCS extends VersioningSystem implements PropertyChangeListener, private static final Logger LOG = Logger.getLogger("org.netbeans.modules.git.GitVCS"); //NOI18N public GitVCS() { - putProperty(PROP_DISPLAY_NAME, getDisplayName()); + putProperty(PROP_DISPLAY_NAME, getDisplayName()); putProperty(PROP_MENU_LABEL, org.openide.util.NbBundle.getMessage(GitVCS.class, "CTL_Git_MainMenu")); // NOI18N GitModuleConfig.getDefault().getPreferences().addPreferenceChangeListener(this); Git.getInstance().registerGitVCS(this); @@ -77,7 +79,7 @@ public void getOriginalFile (File workingCopy, File originalFile) { public File getTopmostManagedAncestor(File file) { return Git.getInstance().getTopmostManagedAncestor(file); } - + @Override public CollocationQueryImplementation getCollocationQueryImplementation() { return collocationQueryImplementation; @@ -111,6 +113,13 @@ public void propertyChange(PropertyChangeEvent event) { if (event.getPropertyName().equals(FileStatusCache.PROP_FILE_STATUS_CHANGED)) { FileStatusCache.ChangedEvent changedEvent = (FileStatusCache.ChangedEvent) event.getNewValue(); fireStatusChanged(changedEvent.getFile()); + } else if (event.getPropertyName().equals(FileStatusCache.PROP_FILES_STATUS_CHANGED)) { + List changedEvents = (List) event.getNewValue(); + Set files = HashSet.newHashSet(changedEvents.size()); + for (FileStatusCache.ChangedEvent e : changedEvents) { + files.add(e.getFile()); + } + fireStatusChanged(files); } else if (event.getPropertyName().equals(Git.PROP_ANNOTATIONS_CHANGED)) { fireAnnotationsChanged((Set) event.getNewValue()); } else if (event.getPropertyName().equals(Git.PROP_VERSIONED_FILES_CHANGED)) { 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 6ff1626e0f41..2d6f978bb57a 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 @@ -196,7 +196,7 @@ public class MultiDiffPanelController implements ActionListener, PropertyChangeL private GitProgressSupport statusRefreshSupport; private PreferenceChangeListener prefList; - + private final Revision revisionOriginalLeft; private final Revision revisionOriginalRight; private Revision revisionLeft; @@ -211,7 +211,7 @@ public class MultiDiffPanelController implements ActionListener, PropertyChangeL private int popupViewIndex; private int requestedRightLine = -1; private int requestedLeftLine = -1; - + private static final int VIEW_MODE_TABLE = 1; private static final int VIEW_MODE_TREE = 2; private int currentSetupDiffLengthChanged; @@ -270,7 +270,7 @@ public MultiDiffPanelController (File file, Revision rev1, Revision rev2, int re prepareTask = RP.create(dpt); prepareTask.schedule(0); } - + private MultiDiffPanelController (VCSContext context, Revision revisionLeft, Revision revisionRight, boolean fixedRevisions) { this.context = context; this.revisionLeft = revisionOriginalLeft = revisionLeft; @@ -315,7 +315,7 @@ private void setActiveComponent (DiffFileViewComponent fileComponent) panel.splitPane.setTopComponent(getActiveFileComponent().getComponent()); panel.splitPane.setDividerLocation(gg); } - + private void refreshSelectionCombos () { if (!fixedRevisions && GitUtils.getRepositoryRoots(context).size() == 1) { panel.cmbDiffTreeFirst.setEnabled(false); @@ -432,7 +432,7 @@ public void componentClosed () { cancelBackgroundTasks(); setups.clear(); editorCookies.clear(); - + if (list != null) { Git.getInstance().getFileStatusCache().removePropertyChangeListener(list); } @@ -569,7 +569,7 @@ private void initToolbarButtons () { panel.treeButton.setVisible(false); } } - + private JComponent getInfoPanelLoading () { if (infoPanelLoadingFromRepo == null) { infoPanelLoadingFromRepo = new NoContentPanel(NbBundle.getMessage(MultiDiffPanel.class, "MSG_DiffPanel_NoContent")); @@ -812,11 +812,11 @@ public void run () { public void run () { multiTextDiffSupport = null; } - + }); } } - + private String findEncoding () { for (File f : actionRoots.getValue()) { FileObject fo = FileUtil.toFileObject(f); @@ -861,7 +861,7 @@ private void exportDiff (ByteArrayOutputStream bos) throws GitException { actionRoots.getKey(), Bundle.MSG_DiffPanel_multiTextualDiff_preparing()); } } - + }); displayDiffView(); } @@ -891,7 +891,7 @@ private Map.Entry getSelectedActionRoots () { ctx = GitUtils.getContextForFiles(filterExcluded(selectedFiles)); return GitUtils.getActionRoots(ctx); } - + private File[] filterExcluded (File[] files) { List filtered = new ArrayList<>(files.length); for (File f : files) { @@ -903,7 +903,7 @@ private File[] filterExcluded (File[] files) { } return filtered.toArray(new File[0]); } - + private boolean showingFileComponent() { return getActiveFileComponent() != null; } @@ -1064,7 +1064,7 @@ private DiffMode getDiffMode () { } return diffMode; } - + private Revision getSelectedRevision (JComboBox cmbDiffTree) { Object selectedItem = cmbDiffTree.getSelectedItem(); Revision selection = null; @@ -1123,15 +1123,25 @@ public void propertyChange (PropertyChangeEvent evt) { if (FileStatusCache.PROP_FILE_STATUS_CHANGED.equals(evt.getPropertyName())) { FileStatusCache.ChangedEvent changedEvent = (FileStatusCache.ChangedEvent) evt.getNewValue(); if (LOG.isLoggable(Level.FINE)) { - LOG.log(Level.FINE, "File status for file {0} changed from {1} to {2}", new Object[] { - changedEvent.getFile(), + LOG.log(Level.FINE, "File status for file {0} changed from {1} to {2}", new Object[] { + changedEvent.getFile(), changedEvent.getOldInfo(), changedEvent.getNewInfo() } ); } - if (revisionLeft == Revision.HEAD // remove when we're able to refresh single file changes for Local vs. any revision + if (revisionLeft == Revision.HEAD // remove when we're able to refresh single file changes for Local vs. any revision && revisionRight == Revision.LOCAL && affectsView(changedEvent)) { applyChange(changedEvent); } + } else if (FileStatusCache.PROP_FILES_STATUS_CHANGED.equals(evt.getPropertyName())) { + if (revisionLeft == Revision.HEAD && revisionRight == Revision.LOCAL) { + @SuppressWarnings("unchecked") + List changedEvents = (List) evt.getNewValue(); + for (FileStatusCache.ChangedEvent changedEvent : changedEvents) { + if (affectsView(changedEvent)) { + applyChange(changedEvent); + } + } + } } else if (DiffController.PROP_DIFFERENCES.equals(evt.getPropertyName())) { // something has changed Setup setup = currentSetup; @@ -1537,7 +1547,7 @@ private boolean isLocal () { * Eliminates unnecessary cache.listFiles call as well as the whole node creation process () */ private final class ApplyChangesTask implements Runnable, Cancellable { - + private volatile boolean canceled; @Override @@ -1651,7 +1661,7 @@ public boolean cancel () { return true; } } - + private class RefreshComboTask implements Runnable { @Override @@ -1663,7 +1673,7 @@ public void run () { if (revisionOriginalLeft != Revision.HEAD) { modelLeft.add(Revision.HEAD); } - modelRight.add(revisionOriginalRight); + modelRight.add(revisionOriginalRight); if (revisionOriginalRight != Revision.LOCAL) { modelRight.add(Revision.LOCAL); } @@ -1767,7 +1777,7 @@ public void run() { GitClientExceptionHandler.notifyException(exception, true); } } - + private DiffNode[] prepareSetupsToRefresh () { return Mutex.EVENT.readAccess(new Mutex.Action() { @Override @@ -1810,7 +1820,7 @@ private int getMatchingLine (StreamSource ss2, StreamSource ss1, int requestedRi } } }// - + @NbBundle.Messages({ "MSG_Revision_Select_Tooltip=Select a revision from the picker" }) diff --git a/ide/git/src/org/netbeans/modules/git/ui/status/VersioningPanelController.java b/ide/git/src/org/netbeans/modules/git/ui/status/VersioningPanelController.java index 2a0ed8d38079..5fd2266740c5 100644 --- a/ide/git/src/org/netbeans/modules/git/ui/status/VersioningPanelController.java +++ b/ide/git/src/org/netbeans/modules/git/ui/status/VersioningPanelController.java @@ -408,6 +408,14 @@ public void propertyChange (PropertyChangeEvent evt) { if (affectsView((FileStatusCache.ChangedEvent) evt.getNewValue())) { applyChange(changedEvent); } + } else if (FileStatusCache.PROP_FILES_STATUS_CHANGED.equals(evt.getPropertyName())) { + @SuppressWarnings("unchecked") + List changedEvents = (List) evt.getNewValue(); + for (FileStatusCache.ChangedEvent changedEvent : changedEvents) { + if (affectsView(changedEvent)) { + applyChange(changedEvent); + } + } } } diff --git a/ide/versioning.core/src/org/netbeans/modules/versioning/core/VersioningAnnotationProvider.java b/ide/versioning.core/src/org/netbeans/modules/versioning/core/VersioningAnnotationProvider.java index 69eb1ebe538f..d9c3983897c1 100644 --- a/ide/versioning.core/src/org/netbeans/modules/versioning/core/VersioningAnnotationProvider.java +++ b/ide/versioning.core/src/org/netbeans/modules/versioning/core/VersioningAnnotationProvider.java @@ -47,13 +47,13 @@ /** * Plugs into IDE filesystem and delegates annotation work to registered versioning systems. - * + * * @author Maros Sandor */ public class VersioningAnnotationProvider { - + private static final VersioningAnnotationProvider instance = new VersioningAnnotationProvider(); - + private static final Logger LOG = Logger.getLogger(VersioningAnnotationProvider.class.getName()); private static final int CACHE_INITIAL_SIZE = 500; private static final long CACHE_ITEM_MAX_AGE = getMaxAge(); @@ -62,11 +62,11 @@ public class VersioningAnnotationProvider { private VersioningAnnotationProvider() { } - + public static VersioningAnnotationProvider getDefault() { return instance; } - + private VersioningSystem getOwner(VCSFileProxy file, Boolean isFile) { return file == null ? null : VersioningManager.getInstance().getOwner(file, isFile); } @@ -101,7 +101,7 @@ public Action[] actions(Set files) { SystemAction.get(InitLHSystemAction.class) }; } - + List actions = new ArrayList<>(); LocalHistoryActions localHistoryAction = null; @@ -136,8 +136,8 @@ public Action[] actions(Set files) { vs = owners.keySet().iterator().next(); } else { return actions.toArray(Action[]::new); - } - + } + VCSAnnotator an = null; if (vs != null) { an = vs.getVCSAnnotator(); @@ -150,13 +150,13 @@ public Action[] actions(Set files) { return actions.toArray(Action[]::new); } - + public static class VersioningSystemActions extends AbstractVersioningSystemActions { } public static class LocalHistoryActions extends AbstractVersioningSystemActions { } - + public static class InitLHSystemAction extends InitVersioningSystemAction { public InitLHSystemAction() { super(); @@ -166,7 +166,7 @@ public String getName() { return NbBundle.getMessage(VersioningAnnotationProvider.class, "CTL_MenuItem_LocalHistory"); } } - + public static class InitVersioningSystemAction extends SystemAction implements Presenter.Popup { @Override @@ -184,9 +184,9 @@ public HelpCtx getHelpCtx() { return null; } } - + public abstract static class AbstractVersioningSystemActions extends SystemAction implements ContextAwareAction { - + private VersioningSystem system; @Override @@ -213,7 +213,7 @@ public void setVersioningSystem(VersioningSystem system) { this.system = system; } } - + private static class RealVersioningSystemActions extends AbstractAction implements Presenter.Popup { private final VersioningSystem system; @@ -234,10 +234,10 @@ public void actionPerformed(ActionEvent e) { public JMenuItem getPopupPresenter() { return new VersioningSystemMenuItem(); } - + @NbBundle.Messages("LBL_PopupMenu_Initializing=Initializing...") private class VersioningSystemMenuItem extends JMenu { - + private boolean popupContructed; public VersioningSystemMenuItem() { @@ -284,7 +284,7 @@ static void refreshAllAnnotations() { instance.refreshAnnotations(null); } } - + /** * Refreshes annotations for all given files and all parent folders of those files. * @@ -295,77 +295,83 @@ void refreshAnnotations(Set files) { } void refreshAnnotations(Set files, boolean removeFromCache) { - if (files == null) { + + if (files == null) { LOG.log(Level.FINE, "refreshing all annotations"); //NOI18N refreshAllAnnotationsTask.schedule(2000); return; } - - if (removeFromCache) { - LOG.log(Level.FINE, "refreshing annotations for {0}", files); //NOI18N - if (LOG.isLoggable(Level.FINEST)) { - LOG.log(Level.FINEST, "refreshing annotations called from:", new Exception()); //NOI18N - } - } - - for (VCSFileProxy file : files) { - // try to limit the number of normalizeFile calls: - // let's find the closest existent FO, then list it's parents with FileObject.getParent(); - FileObject fo = file.toFileObject(); - if (fo == null) { - fo = getExistingParent(file); - } else { - // file exists, plan it to refresh - addToMap(filesToRefresh, fo, removeFromCache); - fo = fo.getParent(); - } + + final RequestProcessor.Task refreshAnnotationsTask = rp.create(() -> { if (removeFromCache) { - // fo is the closest existing parent - for (FileObject parent = fo; parent != null; parent = parent.getParent()) { - // plan parent to refresh - addToMap(parentsToRefresh, parent, removeFromCache); + LOG.log(Level.FINE, "refreshing annotations for {0}", files); //NOI18N + if (LOG.isLoggable(Level.FINEST)) { + LOG.log(Level.FINEST, "refreshing annotations called from:", new Exception()); //NOI18N } } - } - - fireFileStatusChangedTask.schedule(2000); + + for (VCSFileProxy file : files) { + // try to limit the number of normalizeFile calls: + // let's find the closest existent FO, then list it's parents with FileObject.getParent(); + FileObject fo = file.toFileObject(); + if (fo == null) { + fo = getExistingParent(file); + } else { + // file exists, plan it to refresh + addToMap(filesToRefresh, fo, removeFromCache); + fo = fo.getParent(); + } + + if (removeFromCache) { + // fo is the closest existing parent + for (FileObject parent = fo; parent != null; parent = parent.getParent()) { + // plan parent to refresh + addToMap(parentsToRefresh, parent, removeFromCache); + } + } + } + + fireFileStatusChangedTask.schedule(2000); + }); + + refreshAnnotationsTask.schedule(0); } - + /** - * Stores all files which have to be refreshed + * Stores all files which have to be refreshed */ private final Map> filesToRefresh = new HashMap<>(); - + /** - * Stores all parents from files which have to be refreshed + * Stores all parents from files which have to be refreshed */ private final Map> parentsToRefresh = new HashMap<>(); - + private RequestProcessor rp = new RequestProcessor("Versioning fire FileStatusChanged", 1, true); - + /** * Refreshes all annotations and clears the maps holding all files and their parents which have to be refreshed */ - private RequestProcessor.Task refreshAllAnnotationsTask = rp.create(new Runnable() { + private RequestProcessor.Task refreshAllAnnotationsTask = rp.create(new Runnable() { @Override - public void run() { + public void run() { clearMap(filesToRefresh); clearMap(parentsToRefresh); labelCache.removeAll(); iconCache.removeAll(); - + VersioningManager.deliverStatusEvent(new VCSAnnotationEvent(true, true)); } - }); - + }); + /** * Refreshes all files stored in filesToRefresh and parentsToRefresh - */ + */ private RequestProcessor.Task fireFileStatusChangedTask = rp.create(new Runnable() { @Override public void run() { - + // createInitializingMenu and fire for all files which have to be refreshed List fileEvents = new ArrayList<>(); List folderEvents = new ArrayList<>(); @@ -384,7 +390,7 @@ public void run() { } else { files.add(fo); } - } + } set.clear(); e.setValue(new HashSet<>()); if(!files.isEmpty()) { @@ -393,33 +399,33 @@ public void run() { if(!folders.isEmpty()) { folderEvents.add(new VCSAnnotationEvent(folders, true, true)); } - } - } + } + } fireFileStatusEvents(fileEvents); fireFileStatusEvents(folderEvents); // createInitializingMenu and fire events for all parent from each file which has to be refreshed - List parentEvents = new ArrayList<>(); + List parentEvents = new ArrayList<>(); synchronized(parentsToRefresh) { for (Map.Entry> e : parentsToRefresh.entrySet()) { Set set = e.getValue(); Set files = new HashSet<>(set); parentEvents.add(new VCSAnnotationEvent(files, true, false)); - e.setValue(new HashSet<>()); + e.setValue(new HashSet<>()); set.clear(); } } fireFileStatusEvents(parentEvents); } - + private void fireFileStatusEvents(Collection events) { for(VCSAnnotationEvent event : events) { VersioningManager.deliverStatusEvent(event); } - } - }); - + } + }); + private void clearMap(Map> map) { synchronized(map) { if(!map.isEmpty()) { @@ -427,7 +433,7 @@ private void clearMap(Map> map) { } } } - + private void addToMap(Map> map, FileObject fo, boolean removeFromCache) { if(fo == null) { return; @@ -438,8 +444,8 @@ private void addToMap(Map> map, FileObject fo, boole } catch (FileStateInvalidException e) { // ignore files in invalid filesystems return; - } - synchronized (map) { + } + synchronized (map) { map.computeIfAbsent(fs, k -> new HashSet<>()) .add(fo); if (removeFromCache) { @@ -624,7 +630,7 @@ private T annotate(VCSAnnotator annotator, T initialValue, VCSContext context) { return null; } } - + private T annotate (T initialValue, Set files) { long ft = System.currentTimeMillis(); if (LOG.isLoggable(Level.FINEST)) { diff --git a/ide/versioning.ui/src/org/netbeans/modules/versioning/ui/diff/DiffSidebarManager.java b/ide/versioning.ui/src/org/netbeans/modules/versioning/ui/diff/DiffSidebarManager.java index ce3b35f8a464..98e67c26e364 100644 --- a/ide/versioning.ui/src/org/netbeans/modules/versioning/ui/diff/DiffSidebarManager.java +++ b/ide/versioning.ui/src/org/netbeans/modules/versioning/ui/diff/DiffSidebarManager.java @@ -45,7 +45,7 @@ /** * Central place of diff integration into editor and errorstripe. - * + * * @author Maros Sandor */ public class DiffSidebarManager implements PreferenceChangeListener, PropertyChangeListener { @@ -58,14 +58,14 @@ public class DiffSidebarManager implements PreferenceChangeListener, PropertyCha * Temporary top folder for diffsidebar in a single session */ private static File tempDir; - + /** * Request processor for long running tasks. */ private static final RequestProcessor blockingRequestProcessor = new RequestProcessor("Diffsidebar long tasks", 1, false, false); static final Logger LOG = Logger.getLogger(DiffSidebarManager.class.getName()); private static final boolean LOG_STACKTRACE = Boolean.getBoolean("versioning.diffsidebar.refresh.logstacktrace"); //NOI18N - + public static synchronized DiffSidebarManager getInstance() { if (instance == null) { instance = new DiffSidebarManager(); @@ -82,37 +82,40 @@ public static synchronized DiffSidebarManager getInstance() { private final Map sideBars = new WeakHashMap(); private DiffSidebarManager() { - sidebarEnabled = getPreferences().getBoolean(SIDEBAR_ENABLED, true); + sidebarEnabled = getPreferences().getBoolean(SIDEBAR_ENABLED, true); getPreferences().addPreferenceChangeListener(this); } public void refreshSidebars(final Set proxies) { - // pushing the change ... we may as well listen for changes in versioning manager - Set fileObjects = null; - if (proxies != null) { - fileObjects = new HashSet(proxies.size()); - for (VCSFileProxy file : proxies) { - fileObjects.add(file.toFileObject()); + + createDiffSidebarTask(() -> { + // pushing the change ... we may as well listen for changes in versioning manager + Set fileObjects = null; + if (proxies != null) { + fileObjects = new HashSet(proxies.size()); + for (VCSFileProxy file : proxies) { + fileObjects.add(file.toFileObject()); + } + fileObjects.remove(null); } - fileObjects.remove(null); - } - if (LOG_STACKTRACE && (fileObjects == null || !fileObjects.isEmpty())) { - LOG.log(Level.INFO, "Refreshing: " + fileObjects, new Exception()); - } - final Set fileObjectsToRefresh = fileObjects; - SwingUtilities.invokeLater(new Runnable() { - public void run() { - synchronized (sideBars) { - for (DiffSidebar bar : sideBars.keySet()) { - if (matches(bar, fileObjectsToRefresh)) { - bar.refresh(); + if (LOG_STACKTRACE && (fileObjects == null || !fileObjects.isEmpty())) { + LOG.log(Level.INFO, "Refreshing: " + fileObjects, new Exception()); + } + final Set fileObjectsToRefresh = fileObjects; + SwingUtilities.invokeLater(new Runnable() { + public void run() { + synchronized (sideBars) { + for (DiffSidebar bar : sideBars.keySet()) { + if (matches(bar, fileObjectsToRefresh)) { + bar.refresh(); + } } } } - } - }); + }); + }).schedule(0); } - + private boolean matches(DiffSidebar sidebar, Set fileObjects) { if (fileObjects == null) return true; for (FileObject fileObject : fileObjects) { @@ -123,10 +126,10 @@ private boolean matches(DiffSidebar sidebar, Set fileObjects) { Preferences getPreferences() { return NbPreferences.forModule(DiffSidebarManager.class); - } + } /** - * Creates a new task needed by a diff sidebar to update its structures (compute diff). - * + * Creates a new task needed by a diff sidebar to update its structures (compute diff). + * * @param runnable a runnable task * @return RP task */ @@ -155,7 +158,7 @@ private DiffSidebar getSideBar(JTextComponent target) { return null; } LOG.log(Level.FINE, "requested sidebar for {0}", file.getPath()); - + sideBar = new DiffSidebar(target, file); sideBars.put(sideBar, null); sideBar.setSidebarVisible(sidebarEnabled);