diff --git a/platform/lang-impl/src/com/intellij/openapi/fileEditor/impl/text/PsiAwareTextEditorImpl.kt b/platform/lang-impl/src/com/intellij/openapi/fileEditor/impl/text/PsiAwareTextEditorImpl.kt index 5072e5d85f2b..5300c0f9bd90 100644 --- a/platform/lang-impl/src/com/intellij/openapi/fileEditor/impl/text/PsiAwareTextEditorImpl.kt +++ b/platform/lang-impl/src/com/intellij/openapi/fileEditor/impl/text/PsiAwareTextEditorImpl.kt @@ -19,6 +19,7 @@ import com.intellij.openapi.actionSystem.CompositeDataProvider import com.intellij.openapi.actionSystem.DataProvider import com.intellij.openapi.actionSystem.PlatformDataKeys import com.intellij.openapi.application.readAction +import com.intellij.openapi.components.serviceAsync import com.intellij.openapi.components.serviceIfCreated import com.intellij.openapi.diagnostic.ControlFlowException import com.intellij.openapi.diagnostic.logger @@ -26,12 +27,10 @@ import com.intellij.openapi.editor.impl.EditorImpl import com.intellij.openapi.fileEditor.impl.text.AsyncEditorLoader.Companion.isEditorLoaded import com.intellij.openapi.progress.ProcessCanceledException import com.intellij.openapi.progress.ProgressManager -import com.intellij.openapi.progress.blockingContextToIndicator import com.intellij.openapi.project.Project import com.intellij.openapi.util.Segment import com.intellij.openapi.util.TextRange import com.intellij.openapi.vfs.VirtualFile -import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiFile import com.intellij.psi.PsiManager import java.util.concurrent.CancellationException @@ -58,10 +57,8 @@ open class PsiAwareTextEditorImpl : TextEditorImpl { override suspend fun loadEditorInBackground(): Runnable { val editor = editor - val document = editor.document class State { - var foldingState: CodeFoldingState? = null var focusZones: List? = null var items: DocRenderPassFactory.Items? = null var buffer: HintsBuffer? = null @@ -70,16 +67,8 @@ open class PsiAwareTextEditorImpl : TextEditorImpl { } val state = State() - val psiManager = PsiManager.getInstance(project) + val psiManager = project.serviceAsync() readAction { - if (!project.isDefault && PsiDocumentManager.getInstance(project).isCommitted(document)) { - state.foldingState = catchingExceptions { - blockingContextToIndicator { - CodeFoldingManager.getInstance(project).buildInitialFoldings(document) - } - } - } - val psiFile = psiManager.findFile(file) state.psiFile = psiFile state.focusZones = catchingExceptions { FocusModePassFactory.calcFocusZones(psiFile) } @@ -96,7 +85,6 @@ open class PsiAwareTextEditorImpl : TextEditorImpl { } return Runnable { - state.foldingState?.setToEditor(editor) state.focusZones?.let { focusZones -> FocusModePassFactory.setToEditor(focusZones, editor) if (editor is EditorImpl) { @@ -177,7 +165,7 @@ private inline fun catchingExceptionsAsync(computable: () -> T?): T? { } // not `inline` to ensure that this function is not used for a `suspend` task -private fun catchingExceptions(computable: () -> T?): T? { +internal fun catchingExceptions(computable: () -> T?): T? { try { return computable() } diff --git a/platform/lang-impl/src/com/intellij/openapi/fileEditor/impl/text/PsiAwareTextEditorProvider.kt b/platform/lang-impl/src/com/intellij/openapi/fileEditor/impl/text/PsiAwareTextEditorProvider.kt index 5910fd46a217..5115fbf11cb7 100644 --- a/platform/lang-impl/src/com/intellij/openapi/fileEditor/impl/text/PsiAwareTextEditorProvider.kt +++ b/platform/lang-impl/src/com/intellij/openapi/fileEditor/impl/text/PsiAwareTextEditorProvider.kt @@ -4,19 +4,27 @@ package com.intellij.openapi.fileEditor.impl.text import com.intellij.codeHighlighting.BackgroundEditorHighlighter import com.intellij.codeInsight.daemon.impl.TextEditorBackgroundHighlighter import com.intellij.codeInsight.folding.CodeFoldingManager +import com.intellij.openapi.application.EDT import com.intellij.openapi.application.readAction +import com.intellij.openapi.components.serviceAsync import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.editor.Editor import com.intellij.openapi.editor.EditorFactory +import com.intellij.openapi.editor.ex.EditorEx import com.intellij.openapi.editor.impl.EditorFactoryImpl import com.intellij.openapi.fileEditor.* import com.intellij.openapi.fileEditor.impl.text.AsyncEditorLoader.Companion.isEditorLoaded import com.intellij.openapi.fileEditor.impl.text.TextEditorImpl.Companion.createAsyncEditorLoader +import com.intellij.openapi.progress.blockingContextToIndicator import com.intellij.openapi.project.Project import com.intellij.openapi.util.JDOMUtil import com.intellij.openapi.util.WriteExternalException import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiDocumentManager +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.async +import kotlinx.coroutines.withContext import org.jdom.Element import org.jetbrains.annotations.NonNls import java.util.function.Supplier @@ -38,11 +46,39 @@ open class PsiAwareTextEditorProvider : TextEditorProvider(), AsyncFileEditorPro val factory = EditorFactory.getInstance() as EditorFactoryImpl val highlighter = asyncLoader.createHighlighterAsync(document, file) + val foldingState = if (project.isDefault) null else asyncLoader.coroutineScope.async(CoroutineName("buildInitialFoldings")) { + val codeFoldingManager = project.serviceAsync() + readAction { + if (PsiDocumentManager.getInstance(project).isCommitted(document)) { + catchingExceptions { + blockingContextToIndicator { + codeFoldingManager.buildInitialFoldings(document) + } + } + } + else { + null + } + } + } + + val tasks: List Unit> = listOf( + { editor -> configureHighlighter(highlighter, editor) }, + { editor -> + val state = foldingState?.await() + if (state != null) { + withContext(Dispatchers.EDT) { + state.setToEditor(editor) + } + } + } + ) + return object : AsyncFileEditorProvider.Builder() { override fun build(): FileEditor { val editor = factory.createMainEditor(document, project, file) val textEditor = PsiAwareTextEditorImpl(project = project, file = file, editor = editor, asyncLoader = asyncLoader) - asyncLoader.start(textEditor = textEditor, highlighterDeferred = highlighter) + asyncLoader.start(textEditor = textEditor, tasks = tasks) return textEditor } } diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/AsyncEditorLoader.kt b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/AsyncEditorLoader.kt index 021134f234d0..172f805beb56 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/AsyncEditorLoader.kt +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/AsyncEditorLoader.kt @@ -36,7 +36,7 @@ private val ASYNC_LOADER = Key.create("ASYNC_LOADER") class AsyncEditorLoader internal constructor(private val project: Project, private val provider: TextEditorProvider, - private val coroutineScope: CoroutineScope) { + @JvmField val coroutineScope: CoroutineScope) { private val delayedActions = ArrayDeque() private var delayedScrollState: DelayedScrollState? = null @@ -79,50 +79,75 @@ class AsyncEditorLoader internal constructor(private val project: Project, // executed in the same EDT task where TextEditorImpl is created @Internal @RequiresEdt - fun start(textEditor: TextEditorImpl, highlighterDeferred: Deferred) { + fun start(textEditor: TextEditorImpl, tasks: List Unit>) { val editor = textEditor.editor editor.putUserData(ASYNC_LOADER, this) if (ApplicationManager.getApplication().isUnitTestMode) { - startInTests(highlighterDeferred = highlighterDeferred, editor = editor, textEditor = textEditor) + startInTests(tasks = tasks, editor = editor, textEditor = textEditor) return } + val continuationDeferred = coroutineScope.async { + textEditor.loadEditorInBackground() + } + + // `openEditorImpl` uses runWithModalProgressBlocking, + // but an async editor load is performed in the background, out of the `openEditorImpl` call + val modality = ModalityState.any().asContextElement() + + val taskJob = coroutineScope.async(modality) { + for (task in tasks) { + async { task(editor) } + } + } + // don't show yet another loading indicator on project open - use 3-second delay val loadingDecorator = AsyncLoadingDecorator( startDelay = if (EditorsSplitters.isOpenedInBulk(textEditor.file)) 3_000.milliseconds else 300.milliseconds, ) - // `openEditorImpl` uses runWithModalProgressBlocking, - // but an async editor load is performed in the background, out of the `openEditorImpl` call - val modality = ModalityState.any().asContextElement() - val editorComponent = textEditor.component val indicatorJob = loadingDecorator.startLoading(scope = coroutineScope + modality, addUi = editorComponent::addLoadingDecoratorUi) - val configureHighlighterJob = coroutineScope.launch(modality) { - configureHighlighter(highlighterDeferred, editor) - } - - val continuationDeferred = coroutineScope.async { - textEditor.loadEditorInBackground() - } - coroutineScope.launch(modality) { val continuation = continuationDeferred.await() - configureHighlighterJob.join() - loadingDecorator.stopLoading(scope = this, indicatorJob = indicatorJob) + // await instead of joint to get errors here + taskJob.await() + + indicatorJob.cancel() withContext(Dispatchers.EDT) { - loaded(continuation = continuation, editor = editor) + editor.putUserData(ASYNC_LOADER, null) + editor.scrollingModel.disableAnimation() + try { + delayedScrollState?.let { + delayedScrollState = null + restoreCaretPosition(editor = editor, delayedScrollState = it, coroutineScope = coroutineScope) + } + + loadingDecorator.stopLoading(scope = this) + + runCatching { + continuation?.run() + }.getOrLogException(logger()) + + while (true) { + (delayedActions.pollFirst() ?: break).run() + } + } + finally { + editor.scrollingModel.enableAnimation() + } } EditorNotifications.getInstance(project).updateNotifications(textEditor.file) } } - private fun startInTests(highlighterDeferred: Deferred, editor: EditorEx, textEditor: TextEditorImpl) { + private fun startInTests(tasks: List Unit>, editor: EditorEx, textEditor: TextEditorImpl) { val continuation = runWithModalProgressBlocking(project, "") { - withRawProgressReporter { // required for switch to - configureHighlighter(highlighterDeferred, editor) + // required for switch to + withRawProgressReporter { + tasks.map { it(editor) } textEditor.loadEditorInBackground() } } @@ -139,30 +164,6 @@ class AsyncEditorLoader internal constructor(private val project: Project, } } - private fun loaded(continuation: Runnable?, editor: EditorEx) { - editor.putUserData(ASYNC_LOADER, null) - - runCatching { - continuation?.run() - }.getOrLogException(logger()) - - editor.scrollingModel.disableAnimation() - - delayedScrollState?.let { - delayedScrollState = null - restoreCaretPosition(editor = editor, delayedScrollState = it, coroutineScope = coroutineScope) - } - - try { - while (true) { - (delayedActions.pollFirst() ?: break).run() - } - } - finally { - editor.scrollingModel.enableAnimation() - } - } - @RequiresReadLock fun getEditorState(level: FileEditorStateLevel, editor: Editor): TextEditorState { return provider.getStateImpl(project, editor, level) @@ -194,18 +195,18 @@ private fun restoreCaretPosition(editor: EditorEx, delayedScrollState: DelayedSc val viewport = editor.scrollPane.viewport fun isReady(): Boolean { - val extentSize = viewport.extentSize + val extentSize = viewport.extentSize?.takeIf { it.width != 0 && it.height != 0 } ?: viewport.preferredSize return extentSize.width != 0 && extentSize.height != 0 } - if (isReady()) { + if (viewport.isShowing && isReady()) { doScroll() } else { var listenerHandle: DisposableHandle? = null val listener = object : javax.swing.event.ChangeListener { override fun stateChanged(e: ChangeEvent) { - if (!isReady()) { + if (!viewport.isShowing || !isReady()) { return } @@ -222,7 +223,7 @@ private fun restoreCaretPosition(editor: EditorEx, delayedScrollState: DelayedSc } } -private suspend fun configureHighlighter(highlighterDeferred: Deferred, editor: EditorEx) { +suspend fun configureHighlighter(highlighterDeferred: Deferred, editor: EditorEx) { val highlighter = highlighterDeferred.await() subtask("editor highlighter set", Dispatchers.EDT) { editor.settings.setLanguageSupplier { getDocumentLanguage(editor) } diff --git a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/TextEditorImpl.kt b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/TextEditorImpl.kt index 65b307476444..4c3d6b74b719 100644 --- a/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/TextEditorImpl.kt +++ b/platform/platform-impl/src/com/intellij/openapi/fileEditor/impl/text/TextEditorImpl.kt @@ -1,6 +1,8 @@ // Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.openapi.fileEditor.impl.text +import com.intellij.diagnostic.StartUpMeasurer +import com.intellij.diagnostic.rootTask import com.intellij.ide.IdeBundle import com.intellij.ide.structureView.StructureViewBuilder import com.intellij.lang.Language @@ -28,6 +30,7 @@ import org.jetbrains.annotations.NonNls import java.beans.PropertyChangeListener import java.beans.PropertyChangeSupport import javax.swing.JComponent +import kotlin.coroutines.EmptyCoroutineContext private val TRANSIENT_EDITOR_STATE_KEY = Key.create("transientState") @@ -46,8 +49,9 @@ open class TextEditorImpl @Internal constructor(@JvmField protected val project: file = file, editor = editor, asyncLoader = createAsyncEditorLoader(provider, project)) { + val highlighter = asyncLoader.createHighlighterAsync(editor.document, file) @Suppress("LeakingThis") - asyncLoader.start(textEditor = this, highlighterDeferred = asyncLoader.createHighlighterAsync(editor.document, file)) + asyncLoader.start(textEditor = this, tasks = listOf { configureHighlighter(highlighter, it) }) } init { @@ -70,9 +74,12 @@ open class TextEditorImpl @Internal constructor(@JvmField protected val project: // don't pollute global scope companion object { fun createAsyncEditorLoader(provider: TextEditorProvider, project: Project): AsyncEditorLoader { - return AsyncEditorLoader(project = project, - provider = provider, - coroutineScope = project.service().coroutineScope.childScope(supervisor = false)) + val context = if (StartUpMeasurer.isEnabled()) rootTask() else EmptyCoroutineContext + return AsyncEditorLoader( + project = project, + provider = provider, + coroutineScope = project.service().coroutineScope.childScope(supervisor = false, context = context), + ) } @Internal diff --git a/platform/platform-impl/src/com/intellij/openapi/ui/AsyncLoadingDecorator.kt b/platform/platform-impl/src/com/intellij/openapi/ui/AsyncLoadingDecorator.kt index edaf52b10915..7a56271a7df6 100644 --- a/platform/platform-impl/src/com/intellij/openapi/ui/AsyncLoadingDecorator.kt +++ b/platform/platform-impl/src/com/intellij/openapi/ui/AsyncLoadingDecorator.kt @@ -37,9 +37,7 @@ internal class AsyncLoadingDecorator(private val startDelay: Duration) { } } - fun stopLoading(scope: CoroutineScope, indicatorJob: Job) { - // no need to join - executed in EDT - indicatorJob.cancel() + fun stopLoading(scope: CoroutineScope) { scope.launch(Dispatchers.EDT) { val loadingLayer = loadingLayer ?: return@launch try {