mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
doc commit thread optimizations (part of IJPL-50077 freezing on replace)
- use executor instead of spawning tens of thousands of hard-to-manage coroutines littering the thread dump - do not hard-retain changed documents, allowing them to be gced, because if nobody needs the document, it needn't be committed - ditch cachedViewProviders because it leaks psi/Document while sitting in the executor queue; instead, cache the view provider right before the commit started but not sooner GitOrigin-RevId: 7c9068cd7fe168e552970bb34493c311c1f376b8
This commit is contained in:
committed by
intellij-monorepo-bot
parent
cdb4b9a22b
commit
eb3c74590b
@@ -4,14 +4,11 @@ package com.intellij.core;
|
||||
import com.intellij.openapi.application.ModalityState;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.FileViewProvider;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.impl.DocumentCommitProcessor;
|
||||
import com.intellij.psi.impl.PsiDocumentManagerBase;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
final class MockDocumentCommitProcessor implements DocumentCommitProcessor {
|
||||
@Override
|
||||
public void commitSynchronously(@NotNull Document document, @NotNull Project project, @NotNull PsiFile psiFile) {
|
||||
@@ -22,8 +19,7 @@ final class MockDocumentCommitProcessor implements DocumentCommitProcessor {
|
||||
@NotNull PsiDocumentManagerBase documentManager,
|
||||
@NotNull Document document,
|
||||
@NotNull Object reason,
|
||||
@NotNull ModalityState modality,
|
||||
@NotNull List<FileViewProvider> cachedViewProviders) {
|
||||
@NotNull ModalityState modality) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,11 @@ package com.intellij.psi.impl;
|
||||
import com.intellij.openapi.application.ModalityState;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.FileViewProvider;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public interface DocumentCommitProcessor {
|
||||
void commitSynchronously(@NotNull Document document, @NotNull Project project, @NotNull PsiFile psiFile);
|
||||
@@ -20,6 +17,5 @@ public interface DocumentCommitProcessor {
|
||||
@NotNull PsiDocumentManagerBase documentManager,
|
||||
@NotNull Document document,
|
||||
@NonNls @NotNull Object reason,
|
||||
@NotNull ModalityState modality,
|
||||
@NotNull List<FileViewProvider> cachedViewProviders);
|
||||
@NotNull ModalityState modality);
|
||||
}
|
||||
|
||||
@@ -693,7 +693,7 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen
|
||||
// this client obviously expects all documents to be committed ASAP even inside modal dialog
|
||||
for (Document document : myUncommittedDocuments) {
|
||||
try (AccessToken ignore = SlowOperations.knownIssue("IJPL-162971")) {
|
||||
retainProviderAndCommitAsync(document, "re-added because performWhenAllCommitted(" + modality + ") was called", modality);
|
||||
commitAsync(document, "re-added because performWhenAllCommitted(" + modality + ") was called", modality);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1077,7 +1077,7 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen
|
||||
commitDocument(document);
|
||||
}
|
||||
else if (!document.isInBulkUpdate() && myPerformBackgroundCommit) {
|
||||
retainProviderAndCommitAsync(document, event, ModalityState.defaultModalityState());
|
||||
commitAsync(document, event, ModalityState.defaultModalityState());
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -1092,17 +1092,17 @@ public abstract class PsiDocumentManagerBase extends PsiDocumentManager implemen
|
||||
|
||||
@Override
|
||||
public void bulkUpdateFinished(@NotNull Document document) {
|
||||
retainProviderAndCommitAsync(document, "Bulk update finished", ModalityState.defaultModalityState());
|
||||
commitAsync(document, "Bulk update finished", ModalityState.defaultModalityState());
|
||||
}
|
||||
|
||||
private void retainProviderAndCommitAsync(@NotNull Document document,
|
||||
@NotNull Object reason,
|
||||
@NotNull ModalityState modality) {
|
||||
private void commitAsync(@NotNull Document document,
|
||||
@NotNull Object reason,
|
||||
@NotNull ModalityState modality) {
|
||||
List<FileViewProvider> viewProviders = getCachedViewProviders(document);
|
||||
if (FileViewProviderUtil.isEventSystemEnabled(viewProviders)) {
|
||||
ThreadingAssertions.assertEventDispatchThread();
|
||||
// make cached provider non-gcable temporarily (until commit end) to avoid surprising getCachedProvider()==null
|
||||
myDocumentCommitProcessor.commitAsynchronously(myProject, this, document, reason, modality, viewProviders);
|
||||
myDocumentCommitProcessor.commitAsynchronously(myProject, this, document, reason, modality);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.psi.impl
|
||||
|
||||
import com.intellij.codeInsight.multiverse.isEventSystemEnabled
|
||||
import com.intellij.diagnostic.PluginException
|
||||
import com.intellij.lang.FileASTNode
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.application.ModalityState
|
||||
import com.intellij.openapi.application.ReadAction
|
||||
@@ -12,6 +12,7 @@ import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.diagnostic.logger
|
||||
import com.intellij.openapi.editor.Document
|
||||
import com.intellij.openapi.editor.ex.DocumentEx
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager
|
||||
import com.intellij.openapi.progress.EmptyProgressIndicator
|
||||
import com.intellij.openapi.progress.ProcessCanceledException
|
||||
import com.intellij.openapi.progress.ProgressIndicatorProvider
|
||||
@@ -20,48 +21,46 @@ import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.ProperTextRange
|
||||
import com.intellij.openapi.util.TextRange
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.platform.util.coroutines.childScope
|
||||
import com.intellij.psi.*
|
||||
import com.intellij.psi.text.BlockSupport
|
||||
import com.intellij.util.SmartList
|
||||
import com.intellij.util.concurrency.waitAllTasksExecuted
|
||||
import com.intellij.util.concurrency.BoundedTaskExecutor
|
||||
import com.intellij.util.concurrency.SequentialTaskExecutor
|
||||
import com.intellij.util.concurrency.annotations.RequiresReadLock
|
||||
import com.intellij.util.ui.EDT
|
||||
import kotlinx.coroutines.*
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.annotations.NonNls
|
||||
import org.jetbrains.annotations.TestOnly
|
||||
import java.lang.ref.Reference
|
||||
import java.lang.ref.WeakReference
|
||||
import java.util.concurrent.Callable
|
||||
import java.util.concurrent.TimeUnit
|
||||
import java.util.concurrent.TimeoutException
|
||||
|
||||
private val LOG = logger<DocumentCommitThread>()
|
||||
|
||||
@ApiStatus.Internal
|
||||
class DocumentCommitThread(coroutineScope: CoroutineScope) : DocumentCommitProcessor {
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
private val childScope = coroutineScope.childScope("Document Commit Pool", Dispatchers.Default.limitedParallelism(1))
|
||||
|
||||
class DocumentCommitThread : DocumentCommitProcessor, Disposable {
|
||||
@Volatile
|
||||
private var isDisposed = false
|
||||
private val myExecutor = SequentialTaskExecutor.createSequentialApplicationPoolExecutor("Document Commit Pool")
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun getInstance(): DocumentCommitThread = service<DocumentCommitProcessor>() as DocumentCommitThread
|
||||
}
|
||||
|
||||
init {
|
||||
coroutineScope.coroutineContext.job.invokeOnCompletion {
|
||||
isDisposed = true
|
||||
}
|
||||
override fun dispose() {
|
||||
(myExecutor as BoundedTaskExecutor).clearAndCancelAll()
|
||||
isDisposed = true
|
||||
}
|
||||
|
||||
override fun commitAsynchronously(
|
||||
project: Project,
|
||||
documentManager: PsiDocumentManagerBase,
|
||||
document: Document,
|
||||
reason: @NonNls Any,
|
||||
reason: Any,
|
||||
modality: ModalityState,
|
||||
cachedViewProviders: List<FileViewProvider>,
|
||||
) {
|
||||
assert(!isDisposed) { "already disposed" }
|
||||
if (!project.isInitialized()) {
|
||||
@@ -70,23 +69,15 @@ class DocumentCommitThread(coroutineScope: CoroutineScope) : DocumentCommitProce
|
||||
|
||||
require(documentManager.myProject === project) { "Wrong project: $project; expected: ${documentManager.myProject}" }
|
||||
|
||||
assert(cachedViewProviders.isEventSystemEnabled()) {
|
||||
"Asynchronous commit is only supported for physical PSI, " +
|
||||
"document=$document, cachedViewProviders=$cachedViewProviders (${cachedViewProviders.map { it.javaClass }})"
|
||||
}
|
||||
TransactionGuard.getInstance().assertWriteSafeContext(modality)
|
||||
|
||||
val task = CommitTask(project, document, reason, modality, documentManager.getLastCommittedText(document), cachedViewProviders)
|
||||
val task = CommitTask(project, document, reason, modality)
|
||||
ReadAction
|
||||
.nonBlocking(Callable { commitUnderProgress(task, synchronously = false, documentManager) })
|
||||
.expireWhen { isDisposed || project.isDisposed() || !documentManager.isInUncommittedSet(document) || !task.isStillValid() }
|
||||
.expireWhen { isDisposed || project.isDisposed() || task.stillValidDocument().let { document -> document == null || !documentManager.isInUncommittedSet(document) || FileDocumentManager.getInstance().getFile(document)?.isValid != true } }
|
||||
.coalesceBy(task)
|
||||
.finishOnUiThread(modality) { it() }
|
||||
.submit {
|
||||
childScope.launch {
|
||||
it.run()
|
||||
}
|
||||
}
|
||||
.submit(myExecutor)
|
||||
}
|
||||
|
||||
override fun commitSynchronously(document: Document, project: Project, psiFile: PsiFile) {
|
||||
@@ -97,48 +88,54 @@ class DocumentCommitThread(coroutineScope: CoroutineScope) : DocumentCommitProce
|
||||
}
|
||||
|
||||
val documentManager = PsiDocumentManager.getInstance(project) as PsiDocumentManagerBase
|
||||
val task = CommitTask(project, document, "Sync commit", ModalityState.defaultModalityState(), documentManager.getLastCommittedText(document), listOf(psiFile.getViewProvider()))
|
||||
val task = CommitTask(project, document, "Sync commit", ModalityState.defaultModalityState())
|
||||
|
||||
commitUnderProgress(task, synchronously = true, documentManager)()
|
||||
}
|
||||
|
||||
@RequiresReadLock
|
||||
// returns finish commit Runnable (to be invoked later in EDT) or null on failure
|
||||
private fun commitUnderProgress(task: CommitTask, synchronously: Boolean, documentManager: PsiDocumentManagerBase): () -> Unit {
|
||||
val document = task.document
|
||||
val project = task.project
|
||||
if (!synchronously) {
|
||||
ApplicationManager.getApplication().assertIsNonDispatchThread()
|
||||
}
|
||||
ApplicationManager.getApplication().assertReadAccessAllowed()
|
||||
val document = task.myDocumentRef.get()?: return {}
|
||||
val project = task.myProject
|
||||
val finishProcessors = SmartList<BooleanRunnable>()
|
||||
val reparseInjectedProcessors = SmartList<BooleanRunnable>()
|
||||
|
||||
val viewProviders = documentManager.getCachedViewProviders(document)
|
||||
if (viewProviders.isEmpty()) {
|
||||
finishProcessors.add(handleCommitWithoutPsi(task, documentManager))
|
||||
|
||||
val psiManager = PsiManagerEx.getInstanceEx(project)
|
||||
val virtualFile = FileDocumentManager.getInstance().getFile(document)
|
||||
val viewProvider = if (virtualFile == null) null else psiManager.findViewProvider(virtualFile)
|
||||
if (viewProvider == null) {
|
||||
finishProcessors.add(handleCommitWithoutPsi(task, document, documentManager))
|
||||
}
|
||||
else {
|
||||
// While we were messing around transferring things to background thread, the ViewProvider can become obsolete
|
||||
// when, e.g., a virtual file was renamed.
|
||||
// Store new provider to retain it from GC
|
||||
task.cachedViewProviders = viewProviders
|
||||
task.cachedViewProvider = viewProvider
|
||||
|
||||
// todo IJPL-339 check if this is correct
|
||||
for (viewProvider in viewProviders) {
|
||||
for (file in viewProvider.getAllFiles()) {
|
||||
val oldFileNode = file.getNode()
|
||||
if (oldFileNode == null) {
|
||||
throw AssertionError("No node for " + file.javaClass + " in " + file.getViewProvider().javaClass +
|
||||
" of size " + StringUtil.formatFileSize(document.textLength.toLong()) +
|
||||
" (is too large = " + SingleRootFileViewProvider
|
||||
.isTooLargeForIntelligence(viewProvider.getVirtualFile(), document.textLength.toLong()) + ")")
|
||||
}
|
||||
val changedPsiRange = ChangedPsiRangeUtil.getChangedPsiRange(
|
||||
file,
|
||||
document,
|
||||
task.myLastCommittedText,
|
||||
document.getImmutableCharSequence(),
|
||||
)
|
||||
if (changedPsiRange != null) {
|
||||
val finishProcessor = doCommit(task, file, oldFileNode, changedPsiRange, reparseInjectedProcessors, documentManager)
|
||||
finishProcessors.add(finishProcessor)
|
||||
}
|
||||
for (psiFile in viewProvider.getAllFiles()) {
|
||||
val oldFileNode = psiFile.getNode()
|
||||
if (oldFileNode == null) {
|
||||
throw AssertionError("No node for " + psiFile.javaClass + " in " + psiFile.getViewProvider().javaClass +
|
||||
" of size " + StringUtil.formatFileSize(document.textLength.toLong()) +
|
||||
" (is too large = " + SingleRootFileViewProvider
|
||||
.isTooLargeForIntelligence(viewProvider.getVirtualFile(), document.textLength.toLong()) + ")")
|
||||
}
|
||||
val changedPsiRange = ChangedPsiRangeUtil.getChangedPsiRange(
|
||||
psiFile,
|
||||
document,
|
||||
task.myLastCommittedText,
|
||||
document.getImmutableCharSequence(),
|
||||
)
|
||||
if (changedPsiRange != null) {
|
||||
val finishProcessor = doCommit(task, synchronously, document, psiFile, oldFileNode, changedPsiRange, reparseInjectedProcessors, documentManager)
|
||||
finishProcessors.add(finishProcessor)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -148,16 +145,16 @@ class DocumentCommitThread(coroutineScope: CoroutineScope) : DocumentCommitProce
|
||||
return@task
|
||||
}
|
||||
|
||||
val success = documentManager.finishCommit(document, finishProcessors, reparseInjectedProcessors, synchronously, task.reason)
|
||||
val success = documentManager.finishCommit(document, finishProcessors, reparseInjectedProcessors, synchronously, task.myReason)
|
||||
if (synchronously) {
|
||||
assert(success)
|
||||
}
|
||||
if (synchronously || success) {
|
||||
assert(!documentManager.isInUncommittedSet(document))
|
||||
}
|
||||
if (!success && viewProviders.isEventSystemEnabled()) {
|
||||
if (!success && viewProvider?.isEventSystemEnabled() == true) {
|
||||
// add a document back to the queue
|
||||
commitAsynchronously(project, documentManager, document, "Re-added back", task.myCreationModality, viewProviders)
|
||||
commitAsynchronously(project, documentManager, document, "Re-added back", task.myCreationModality)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -167,178 +164,200 @@ class DocumentCommitThread(coroutineScope: CoroutineScope) : DocumentCommitProce
|
||||
// NB: failures applying EDT tasks are not handled - i.e., failed documents are added back to the queue and the method returns
|
||||
@TestOnly
|
||||
fun waitForAllCommits(timeout: Long, timeUnit: TimeUnit) {
|
||||
val boundedTaskExecutor = myExecutor as BoundedTaskExecutor
|
||||
if (!ApplicationManager.getApplication().isDispatchThread()) {
|
||||
while (childScope.coroutineContext.job.children.any()) {
|
||||
waitAllTasksExecuted(childScope, timeout, timeUnit)
|
||||
}
|
||||
boundedTaskExecutor.waitAllTasksExecuted(timeout, timeUnit)
|
||||
return
|
||||
}
|
||||
|
||||
assert(!ApplicationManager.getApplication().isWriteAccessAllowed())
|
||||
|
||||
EDT.dispatchAllInvocationEvents()
|
||||
while (childScope.coroutineContext.job.children.any()) {
|
||||
waitAllTasksExecuted(childScope, timeout, timeUnit)
|
||||
val deadLine = System.nanoTime() + timeUnit.toNanos(timeout)
|
||||
while (!boundedTaskExecutor.isEmpty) {
|
||||
try {
|
||||
boundedTaskExecutor.waitAllTasksExecuted(10, TimeUnit.MILLISECONDS)
|
||||
}
|
||||
catch (e: TimeoutException) {
|
||||
if (System.nanoTime() > deadLine) {
|
||||
throw e
|
||||
}
|
||||
}
|
||||
EDT.dispatchAllInvocationEvents()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CommitTask(
|
||||
val project: Project,
|
||||
val document: Document,
|
||||
val reason: @NonNls Any,
|
||||
val myCreationModality: ModalityState,
|
||||
val myLastCommittedText: CharSequence,
|
||||
// to retain viewProvider to avoid surprising getCachedProvider() == null half-way through commit
|
||||
@field:Volatile var cachedViewProviders: List<FileViewProvider>,
|
||||
) {
|
||||
// store initial document modification sequence here to check if it changed later before commit in EDT
|
||||
private val modificationSequence = (document as DocumentEx).modificationSequence
|
||||
private class CommitTask {
|
||||
val myProject: Project
|
||||
val myReason: @NonNls Any
|
||||
val myCreationModality: ModalityState
|
||||
val myDocumentRef: Reference<Document>
|
||||
val myLastCommittedText: CharSequence
|
||||
// store initial document modification sequence here to check if it changed later before commit in EDT
|
||||
private val myModificationSequence: Int
|
||||
@Volatile var cachedViewProvider: FileViewProvider? = null
|
||||
|
||||
override fun toString(): @NonNls String {
|
||||
val reasonInfo = " task reason: " + StringUtil.first(reason.toString(), 180, true) +
|
||||
(if (isStillValid()) "" else "; changed: old seq=$modificationSequence, new seq=${(document as DocumentEx).modificationSequence}")
|
||||
val contextInfo = " modality: $myCreationModality"
|
||||
return System.identityHashCode(this).toString() + "; " + contextInfo + reasonInfo
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is CommitTask) return false
|
||||
|
||||
return document == other.document && project == other.project
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = 31 * document.hashCode() + project.hashCode()
|
||||
|
||||
fun isStillValid(): Boolean = (document as DocumentEx).modificationSequence == modificationSequence
|
||||
}
|
||||
|
||||
private fun handleCommitWithoutPsi(
|
||||
task: CommitTask,
|
||||
documentManager: PsiDocumentManagerBase,
|
||||
): BooleanRunnable {
|
||||
return BooleanRunnable {
|
||||
if (task.isStillValid() && documentManager.getCachedViewProviders(task.document).isEmpty()) {
|
||||
documentManager.handleCommitWithoutPsi(task.document)
|
||||
true
|
||||
constructor(
|
||||
project: Project,
|
||||
document: Document,
|
||||
reason: @NonNls Any,
|
||||
creationModality: ModalityState,
|
||||
) {
|
||||
myProject = project
|
||||
myReason = reason.toString() // convert to string to avoid leaking document in case somebody passed a Document or DocumentEvent here
|
||||
myCreationModality = creationModality
|
||||
myDocumentRef = WeakReference(document)
|
||||
myLastCommittedText = PsiDocumentManager.getInstance(project).getLastCommittedText(document)
|
||||
myModificationSequence = (document as DocumentEx).modificationSequence
|
||||
}
|
||||
else {
|
||||
false
|
||||
override fun toString(): @NonNls String {
|
||||
val document = stillValidDocument()
|
||||
val reasonInfo = " task reason: " + StringUtil.first(myReason.toString(), 180, true) +
|
||||
document + "; changed: old seq=$myModificationSequence, new seq=${(document as? DocumentEx)?.modificationSequence}"
|
||||
val contextInfo = " modality: $myCreationModality"
|
||||
return System.identityHashCode(this).toString() + "; " + contextInfo + reasonInfo
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (other !is CommitTask) return false
|
||||
|
||||
return myDocumentRef.get() == other.myDocumentRef.get() && myProject == other.myProject
|
||||
}
|
||||
|
||||
override fun hashCode(): Int = 31 * myLastCommittedText.hashCode() + myProject.hashCode()
|
||||
// return null if the document is changed or gced
|
||||
fun stillValidDocument(): Document? {
|
||||
val document = myDocumentRef.get()
|
||||
return if (document is DocumentEx && document.modificationSequence == myModificationSequence) {
|
||||
document
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// returns runnable to execute under the write action in AWT to finish the commit
|
||||
private fun doCommit(
|
||||
task: CommitTask,
|
||||
file: PsiFile,
|
||||
oldFileNode: FileASTNode,
|
||||
changedPsiRange: ProperTextRange,
|
||||
outReparseInjectedProcessors: MutableList<BooleanRunnable>,
|
||||
documentManager: PsiDocumentManagerBase,
|
||||
): BooleanRunnable {
|
||||
val document = task.document
|
||||
val newDocumentText = document.getImmutableCharSequence()
|
||||
// returns runnable to execute under the write action in AWT to finish the commit
|
||||
@RequiresReadLock
|
||||
private fun doCommit(
|
||||
task: CommitTask,
|
||||
synchronously: Boolean,
|
||||
document: Document,
|
||||
psiFile: PsiFile,
|
||||
oldFileNode: FileASTNode,
|
||||
changedPsiRange: ProperTextRange,
|
||||
outReparseInjectedProcessors: MutableList<BooleanRunnable>,
|
||||
documentManager: PsiDocumentManagerBase,
|
||||
): BooleanRunnable {
|
||||
if (!synchronously) {
|
||||
ApplicationManager.getApplication().assertIsNonDispatchThread()
|
||||
}
|
||||
ApplicationManager.getApplication().assertReadAccessAllowed()
|
||||
val newDocumentText = document.getImmutableCharSequence()
|
||||
|
||||
val data = document.getUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY)
|
||||
if (data != null) {
|
||||
document.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, null)
|
||||
file.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, data)
|
||||
}
|
||||
val data = document.getUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY)
|
||||
if (data != null) {
|
||||
document.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, null)
|
||||
psiFile.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, data)
|
||||
}
|
||||
|
||||
val diffLog: DiffLog
|
||||
val indicator = ProgressIndicatorProvider.getGlobalProgressIndicator() ?: EmptyProgressIndicator()
|
||||
try {
|
||||
val result = BlockSupportImpl.reparse(file, oldFileNode, changedPsiRange, newDocumentText, indicator, task.myLastCommittedText)
|
||||
diffLog = result.log
|
||||
val diffLog: DiffLog
|
||||
val indicator = ProgressIndicatorProvider.getGlobalProgressIndicator() ?: EmptyProgressIndicator()
|
||||
try {
|
||||
val result = BlockSupportImpl.reparse(psiFile, oldFileNode, changedPsiRange, newDocumentText, indicator, task.myLastCommittedText)
|
||||
diffLog = result.log
|
||||
|
||||
val injectedRunnables = documentManager.reparseChangedInjectedFragments(
|
||||
document,
|
||||
psiFile,
|
||||
changedPsiRange,
|
||||
indicator,
|
||||
result.oldRoot,
|
||||
result.newRoot,
|
||||
)
|
||||
outReparseInjectedProcessors.addAll(injectedRunnables)
|
||||
}
|
||||
catch (e: ProcessCanceledException) {
|
||||
throw e
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
LOG.error(e)
|
||||
return BooleanRunnable {
|
||||
documentManager.forceReload(psiFile.getViewProvider().getVirtualFile(), listOf(psiFile.getViewProvider()))
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
val injectedRunnables = documentManager.reparseChangedInjectedFragments(
|
||||
document,
|
||||
file,
|
||||
changedPsiRange,
|
||||
indicator,
|
||||
result.oldRoot,
|
||||
result.newRoot,
|
||||
)
|
||||
outReparseInjectedProcessors.addAll(injectedRunnables)
|
||||
}
|
||||
catch (e: ProcessCanceledException) {
|
||||
throw e
|
||||
}
|
||||
catch (e: Throwable) {
|
||||
LOG.error(e)
|
||||
return BooleanRunnable {
|
||||
documentManager.forceReload(file.getViewProvider().getVirtualFile(), listOf(file.getViewProvider()))
|
||||
val viewProvider = psiFile.getViewProvider() //todo IJPL-339 figure out correct check here
|
||||
if (task.stillValidDocument() == null || viewProvider !in documentManager.getCachedViewProviders(document)) { // optimistic locking failed
|
||||
return@BooleanRunnable false
|
||||
}
|
||||
|
||||
if (!ApplicationManager.getApplication().isWriteAccessAllowed() && documentManager.isEventSystemEnabled(document)) {
|
||||
val vFile = viewProvider.getVirtualFile()
|
||||
LOG.error("Write action expected" + "; document=" + document + "; file=" + psiFile + " of " + psiFile.javaClass + "; file.valid=" + psiFile.isValid() + "; file.eventSystemEnabled=" + viewProvider.isEventSystemEnabled() + "; viewProvider=" + viewProvider + " of " + viewProvider.javaClass + "; language=" + psiFile.getLanguage() + "; vFile=" + vFile + " of " + vFile.javaClass + "; free-threaded=" + AbstractFileViewProvider.isFreeThreaded(viewProvider))
|
||||
}
|
||||
|
||||
diffLog.doActualPsiChange(psiFile)
|
||||
|
||||
assertAfterCommit(document, psiFile, oldFileNode) // just to make an impression the field is used
|
||||
Reference.reachabilityFence(task.cachedViewProvider)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
return BooleanRunnable {
|
||||
val viewProvider = file.getViewProvider()
|
||||
//todo IJPL-339 figure out correct check here
|
||||
if (!task.isStillValid() || viewProvider !in documentManager.getCachedViewProviders(document)) {
|
||||
// optimistic locking failed
|
||||
return@BooleanRunnable false
|
||||
private fun handleCommitWithoutPsi(
|
||||
task: CommitTask,
|
||||
document: Document,
|
||||
documentManager: PsiDocumentManagerBase,
|
||||
): BooleanRunnable {
|
||||
return BooleanRunnable {
|
||||
if (task.stillValidDocument() != null && documentManager.getCachedViewProviders(document).isEmpty()) {
|
||||
documentManager.handleCommitWithoutPsi(document)
|
||||
true
|
||||
}
|
||||
else {
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun assertAfterCommit(document: Document, psiFile: PsiFile, oldFileNode: FileASTNode) {
|
||||
if (oldFileNode.getTextLength() == document.textLength) {
|
||||
return
|
||||
}
|
||||
|
||||
if (!ApplicationManager.getApplication().isWriteAccessAllowed() && documentManager.isEventSystemEnabled(document)) {
|
||||
val vFile = viewProvider.getVirtualFile()
|
||||
LOG.error("Write action expected" +
|
||||
"; document=" + document +
|
||||
"; file=" + file + " of " + file.javaClass +
|
||||
"; file.valid=" + file.isValid() +
|
||||
"; file.eventSystemEnabled=" + viewProvider.isEventSystemEnabled() +
|
||||
"; viewProvider=" + viewProvider + " of " + viewProvider.javaClass +
|
||||
"; language=" + file.getLanguage() +
|
||||
"; vFile=" + vFile + " of " + vFile.javaClass +
|
||||
"; free-threaded=" + AbstractFileViewProvider.isFreeThreaded(viewProvider))
|
||||
val documentText = document.text
|
||||
val fileText = psiFile.getText()
|
||||
val sameText = fileText == documentText
|
||||
val errorMessage = "commitDocument() left PSI inconsistent: " + DebugUtil.diagnosePsiDocumentInconsistency(psiFile, document) +
|
||||
"; node.length=" + oldFileNode.getTextLength() +
|
||||
"; doc.text" + (if (sameText) "==" else "!=") + "file.text" +
|
||||
"; file name:" + psiFile.getName() +
|
||||
"; type:" + psiFile.getFileType() +
|
||||
"; lang:" + psiFile.getLanguage()
|
||||
PluginException.logPluginError(LOG, errorMessage, null, psiFile.getLanguage().javaClass)
|
||||
|
||||
psiFile.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, true)
|
||||
try {
|
||||
val blockSupport = BlockSupport.getInstance(psiFile.getProject())
|
||||
val diffLog = blockSupport.reparseRange(
|
||||
psiFile,
|
||||
psiFile.getNode(),
|
||||
TextRange(0, documentText.length),
|
||||
documentText,
|
||||
StandardProgressIndicatorBase(),
|
||||
oldFileNode.getText(),
|
||||
)
|
||||
diffLog.doActualPsiChange(psiFile)
|
||||
|
||||
if (oldFileNode.getTextLength() != document.textLength) {
|
||||
PluginException.logPluginError(LOG, "PSI is broken beyond repair in: $psiFile", null, psiFile.getLanguage().javaClass)
|
||||
}
|
||||
}
|
||||
|
||||
diffLog.doActualPsiChange(file)
|
||||
|
||||
assertAfterCommit(document, file, oldFileNode)
|
||||
// just to make an impression the field is used
|
||||
Reference.reachabilityFence(task.cachedViewProviders)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun assertAfterCommit(document: Document, file: PsiFile, oldFileNode: FileASTNode) {
|
||||
if (oldFileNode.getTextLength() == document.textLength) {
|
||||
return
|
||||
}
|
||||
|
||||
val documentText = document.text
|
||||
val fileText = file.getText()
|
||||
val sameText = fileText == documentText
|
||||
val errorMessage = "commitDocument() left PSI inconsistent: " + DebugUtil.diagnosePsiDocumentInconsistency(file, document) +
|
||||
"; node.length=" + oldFileNode.getTextLength() +
|
||||
"; doc.text" + (if (sameText) "==" else "!=") + "file.text" +
|
||||
"; file name:" + file.getName() +
|
||||
"; type:" + file.getFileType() +
|
||||
"; lang:" + file.getLanguage()
|
||||
PluginException.logPluginError(LOG, errorMessage, null, file.getLanguage().javaClass)
|
||||
|
||||
file.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, true)
|
||||
try {
|
||||
val blockSupport = BlockSupport.getInstance(file.getProject())
|
||||
val diffLog = blockSupport.reparseRange(
|
||||
file,
|
||||
file.getNode(),
|
||||
TextRange(0, documentText.length),
|
||||
documentText,
|
||||
StandardProgressIndicatorBase(),
|
||||
oldFileNode.getText(),
|
||||
)
|
||||
diffLog.doActualPsiChange(file)
|
||||
|
||||
if (oldFileNode.getTextLength() != document.textLength) {
|
||||
PluginException.logPluginError(LOG, "PSI is broken beyond repair in: $file", null, file.getLanguage().javaClass)
|
||||
finally {
|
||||
psiFile.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, null)
|
||||
}
|
||||
}
|
||||
finally {
|
||||
file.putUserData(BlockSupport.DO_NOT_REPARSE_INCREMENTALLY, null)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ final class PsiChangeHandler extends PsiTreeChangeAdapter implements Runnable {
|
||||
|
||||
private PsiFile getRawCachedPsiFile(@NotNull Document document) {
|
||||
VirtualFile virtualFile = FileDocumentManager.getInstance().getFile(document);
|
||||
return virtualFile == null ? null : TextEditorBackgroundHighlighter.getCachedFileToHighlight(myProject, virtualFile, CodeInsightContexts.anyContext());
|
||||
return virtualFile == null || !virtualFile.isValid() ? null : TextEditorBackgroundHighlighter.getCachedFileToHighlight(myProject, virtualFile, CodeInsightContexts.anyContext());
|
||||
}
|
||||
|
||||
private void addChangesFromCompositeDirtyRange(@NotNull PsiFile psiFile,
|
||||
|
||||
Reference in New Issue
Block a user