mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
[platform] an ability to perform periodic VFS refresh when the IDE is inactive (IJPL-181)
GitOrigin-RevId: 039f752cf36f0fff0c6f6a2645976b9e8b8f3f1e
This commit is contained in:
committed by
intellij-monorepo-bot
parent
6ea7e71a61
commit
1bf8a93f55
@@ -24,9 +24,11 @@ import com.intellij.openapi.progress.blockingContext
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.project.getOpenedProjects
|
||||
import com.intellij.openapi.util.NlsContexts
|
||||
import com.intellij.openapi.util.registry.Registry
|
||||
import com.intellij.openapi.vfs.newvfs.ManagingFS
|
||||
import com.intellij.openapi.vfs.newvfs.NewVirtualFile
|
||||
import com.intellij.openapi.vfs.newvfs.RefreshQueue
|
||||
import com.intellij.openapi.vfs.newvfs.RefreshQueueImpl
|
||||
import com.intellij.openapi.vfs.newvfs.RefreshSession
|
||||
import com.intellij.openapi.wm.IdeFrame
|
||||
import com.intellij.platform.ide.progress.ModalTaskOwner
|
||||
@@ -55,7 +57,8 @@ internal class SaveAndSyncHandlerImpl(private val coroutineScope: CoroutineScope
|
||||
private val blockSaveOnFrameDeactivationCount = AtomicInteger()
|
||||
private val blockSyncOnFrameActivationCount = AtomicInteger()
|
||||
|
||||
private val refreshSession = AtomicReference<RefreshSession>()
|
||||
private val refreshSession = AtomicReference<RefreshSession?>()
|
||||
private var bgRefreshJob: Job? = null
|
||||
|
||||
private val saveAppAndProjectsSettingsTask = SaveTask()
|
||||
private val saveQueue = ArrayDeque<SaveTask>()
|
||||
@@ -117,6 +120,7 @@ internal class SaveAndSyncHandlerImpl(private val coroutineScope: CoroutineScope
|
||||
}
|
||||
|
||||
coroutineScope.awaitCancellationAndInvoke {
|
||||
bgRefreshJob?.cancel()
|
||||
refreshSession.get()?.cancel()
|
||||
}
|
||||
}
|
||||
@@ -179,19 +183,24 @@ internal class SaveAndSyncHandlerImpl(private val coroutineScope: CoroutineScope
|
||||
.subscribe(ApplicationActivationListener.TOPIC, object : ApplicationActivationListener {
|
||||
override fun applicationDeactivated(ideFrame: IdeFrame) {
|
||||
externalChangesModificationTracker.incModificationCount()
|
||||
if (!settings.isSaveOnFrameDeactivation || !canSyncOrSave()) {
|
||||
return
|
||||
|
||||
if (settings.isSaveOnFrameDeactivation && canSyncOrSave()) {
|
||||
// for many tasks (compilation, web development, etc.), it is important to save documents on frame deactivation ASAP
|
||||
(FileDocumentManager.getInstance() as FileDocumentManagerImpl).saveAllDocuments(false)
|
||||
if (addToSaveQueue(saveAppAndProjectsSettingsTask)) {
|
||||
requestSave()
|
||||
}
|
||||
}
|
||||
|
||||
// for web development, it is crucially important to save documents on frame deactivation as early as possible
|
||||
(FileDocumentManager.getInstance() as FileDocumentManagerImpl).saveAllDocuments(false)
|
||||
|
||||
if (addToSaveQueue(saveAppAndProjectsSettingsTask)) {
|
||||
requestSave()
|
||||
if (settings.isBackgroundSync) {
|
||||
bgRefreshJob = startBackgroundSync()
|
||||
}
|
||||
}
|
||||
|
||||
override fun applicationActivated(ideFrame: IdeFrame) {
|
||||
bgRefreshJob?.cancel()
|
||||
bgRefreshJob = null
|
||||
|
||||
if (settings.isSyncOnFrameActivation) {
|
||||
scheduleRefresh()
|
||||
}
|
||||
@@ -303,6 +312,27 @@ internal class SaveAndSyncHandlerImpl(private val coroutineScope: CoroutineScope
|
||||
private fun canSyncOrSave(): Boolean =
|
||||
!LaterInvocator.isInModalContext() && !ProgressManager.getInstance().hasModalProgressIndicator()
|
||||
|
||||
private fun startBackgroundSync(): Job {
|
||||
LOG.debug("starting background VFS sync")
|
||||
val bgRefreshSession = AtomicReference<RefreshSession?>()
|
||||
val job = coroutineScope.launch(CoroutineName("background sync")) {
|
||||
val roots = listOf(*ManagingFS.getInstance().localRoots)
|
||||
val queue = RefreshQueue.getInstance() as RefreshQueueImpl
|
||||
val interval = Registry.intValue("vfs.background.refresh.interval", 15).coerceIn(0, Int.MAX_VALUE).seconds
|
||||
while (true) {
|
||||
if (roots.any { it is NewVirtualFile && it.isDirty }) {
|
||||
val session = queue.createBackgroundRefreshSession(roots)
|
||||
bgRefreshSession.set(session)
|
||||
session.launch()
|
||||
bgRefreshSession.set(null)
|
||||
}
|
||||
delay(interval)
|
||||
}
|
||||
}
|
||||
job.invokeOnCompletion { if (it is CancellationException) bgRefreshSession.getAndSet(null)?.cancel() }
|
||||
return job
|
||||
}
|
||||
|
||||
override fun scheduleRefresh() {
|
||||
externalChangesModificationTracker.incModificationCount()
|
||||
check(refreshRequests.tryEmit(Unit))
|
||||
|
||||
@@ -83,6 +83,7 @@ checkbox.save.files.automatically=Save files if the IDE is idle for
|
||||
checkbox.save.files.on.frame.deactivation=Save files when switching to a different application or a built-in terminal
|
||||
checkbox.safe.write=Back up files before saving
|
||||
checkbox.synchronize.files.on.frame.activation=Synchronize external changes when switching to the IDE window or opening an editor tab
|
||||
checkbox.synchronize.files.in.background=Synchronize external changes periodically when the IDE is inactive (experimental)
|
||||
label.autosave.comment=Autosave cannot be disabled completely. <a>How it works</a>
|
||||
title.accessibility=Accessibility
|
||||
checkbox.support.screen.readers=Support screen readers
|
||||
@@ -3023,4 +3024,4 @@ plugin.advertiser.upgrade.to=Upgrade to
|
||||
plugin.advertiser.free.trial.action=free 1 month trial
|
||||
|
||||
inline.completion.tooltip.shortcuts.accept.description=to accept
|
||||
inline.completion.tooltip.shortcuts.accept.select.custom=Custom...
|
||||
inline.completion.tooltip.shortcuts.accept.select.custom=Custom...
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.io.storage.HeavyProcessLatch;
|
||||
import com.intellij.util.ui.EDT;
|
||||
import kotlinx.coroutines.CoroutineScope;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.TestOnly;
|
||||
@@ -178,7 +179,7 @@ public final class RefreshQueueImpl extends RefreshQueue implements Disposable {
|
||||
|
||||
@Override
|
||||
public @NotNull RefreshSession createSession(boolean async, boolean recursively, @Nullable Runnable finishRunnable, @NotNull ModalityState state) {
|
||||
return new RefreshSessionImpl(async, recursively, finishRunnable, state);
|
||||
return new RefreshSessionImpl(async, recursively, false, finishRunnable, state);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -186,6 +187,11 @@ public final class RefreshQueueImpl extends RefreshQueue implements Disposable {
|
||||
new RefreshSessionImpl(async, events).launch();
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
public @NotNull RefreshSession createBackgroundRefreshSession(@NotNull List<@NotNull VirtualFile> files) {
|
||||
return new RefreshSessionImpl(files);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
synchronized (mySessions) {
|
||||
|
||||
@@ -40,6 +40,7 @@ final class RefreshSessionImpl extends RefreshSession {
|
||||
|
||||
private final boolean myIsAsync;
|
||||
private final boolean myIsRecursive;
|
||||
private final boolean myIsBackground;
|
||||
private final Runnable myFinishRunnable;
|
||||
private final ModalityState myModality;
|
||||
private final @Nullable Throwable myStartTrace;
|
||||
@@ -51,9 +52,10 @@ final class RefreshSessionImpl extends RefreshSession {
|
||||
private volatile boolean myCancelled;
|
||||
private volatile boolean myLaunched;
|
||||
|
||||
RefreshSessionImpl(boolean async, boolean recursive, @Nullable Runnable finishRunnable, @NotNull ModalityState modality) {
|
||||
RefreshSessionImpl(boolean async, boolean recursive, boolean background, @Nullable Runnable finishRunnable, @NotNull ModalityState modality) {
|
||||
myIsAsync = async;
|
||||
myIsRecursive = recursive;
|
||||
myIsBackground = background;
|
||||
myFinishRunnable = finishRunnable;
|
||||
myModality = getSaneModalityState(modality);
|
||||
TransactionGuard.getInstance().assertWriteSafeContext(myModality);
|
||||
@@ -61,8 +63,13 @@ final class RefreshSessionImpl extends RefreshSession {
|
||||
myStartTrace = app.isUnitTestMode() && (async || !app.isDispatchThread()) ? new Throwable() : null;
|
||||
}
|
||||
|
||||
RefreshSessionImpl(List<VirtualFile> files) {
|
||||
this(false, true, true, null, getSaneModalityState(ModalityState.defaultModalityState()));
|
||||
addAllFiles(files);
|
||||
}
|
||||
|
||||
RefreshSessionImpl(boolean async, List<? extends VFileEvent> events) {
|
||||
this(async, false, null, getSaneModalityState(ModalityState.defaultModalityState()));
|
||||
this(async, false, false, null, getSaneModalityState(ModalityState.defaultModalityState()));
|
||||
var filtered = events.stream().filter(Objects::nonNull).toList();
|
||||
if (filtered.size() < events.size()) LOG.error("The list of events must not contain null elements");
|
||||
myEvents.addAll(filtered);
|
||||
@@ -168,7 +175,7 @@ final class RefreshSessionImpl extends RefreshSession {
|
||||
count++;
|
||||
if (LOG.isTraceEnabled()) LOG.trace("events=" + events.size());
|
||||
}
|
||||
while (myIsRecursive && count < RETRY_LIMIT && ContainerUtil.exists(workQueue, f -> ((NewVirtualFile)f).isDirty()));
|
||||
while (myIsRecursive && !myIsBackground && count < RETRY_LIMIT && ContainerUtil.exists(workQueue, f -> ((NewVirtualFile)f).isDirty()));
|
||||
|
||||
t = NANOSECONDS.toMillis(System.nanoTime() - t);
|
||||
int localRoots = 0, archiveRoots = 0, otherRoots = 0;
|
||||
|
||||
@@ -1752,6 +1752,10 @@ non.edt.swing.report.categories=unknown
|
||||
non.edt.swing.report.categories.description=Log non-EDT swing access. Requires `idea.check.swing.threading=true` system property to work \
|
||||
Possible values (might use several separated by comma): none,all,preloader,component,configurable,unknown
|
||||
|
||||
vfs.background.refresh.interval=15
|
||||
vfs.background.refresh.interval.description=A delay between subsequent filesystem scans for \
|
||||
"Settings | Appearance & Behavior | System Settings | Synchronize ... when the IDE is inactive" option, in seconds
|
||||
|
||||
vfs.refresh.worker.parallelism=6
|
||||
vfs.refresh.worker.parallelism.description=How many threads will be used to access the file system for detecting changes. \
|
||||
Modern SSD and NVMe drives allow running many operations in parallel.
|
||||
|
||||
Reference in New Issue
Block a user