mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
show editor as early as possible - move building buildInitialFoldings to a separate task
GitOrigin-RevId: 50621466c7363d933ff565970949bb07907b2d37
This commit is contained in:
committed by
intellij-monorepo-bot
parent
ed00a296d5
commit
edf0b232ec
+3
-15
@@ -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<Segment>? = 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<PsiManager>()
|
||||
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 <T : Any> catchingExceptionsAsync(computable: () -> T?): T? {
|
||||
}
|
||||
|
||||
// not `inline` to ensure that this function is not used for a `suspend` task
|
||||
private fun <T : Any> catchingExceptions(computable: () -> T?): T? {
|
||||
internal fun <T : Any> catchingExceptions(computable: () -> T?): T? {
|
||||
try {
|
||||
return computable()
|
||||
}
|
||||
|
||||
+37
-1
@@ -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<CodeFoldingManager>()
|
||||
readAction {
|
||||
if (PsiDocumentManager.getInstance(project).isCommitted(document)) {
|
||||
catchingExceptions {
|
||||
blockingContextToIndicator {
|
||||
codeFoldingManager.buildInitialFoldings(document)
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val tasks: List<suspend (EditorEx) -> 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
|
||||
}
|
||||
}
|
||||
|
||||
+50
-49
@@ -36,7 +36,7 @@ private val ASYNC_LOADER = Key.create<AsyncEditorLoader>("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<Runnable>()
|
||||
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<EditorHighlighter>) {
|
||||
fun start(textEditor: TextEditorImpl, tasks: List<suspend (EditorEx) -> 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<AsyncEditorLoader>())
|
||||
|
||||
while (true) {
|
||||
(delayedActions.pollFirst() ?: break).run()
|
||||
}
|
||||
}
|
||||
finally {
|
||||
editor.scrollingModel.enableAnimation()
|
||||
}
|
||||
}
|
||||
EditorNotifications.getInstance(project).updateNotifications(textEditor.file)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startInTests(highlighterDeferred: Deferred<EditorHighlighter>, editor: EditorEx, textEditor: TextEditorImpl) {
|
||||
private fun startInTests(tasks: List<suspend (EditorEx) -> 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<AsyncEditorLoader>())
|
||||
|
||||
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<EditorHighlighter>, editor: EditorEx) {
|
||||
suspend fun configureHighlighter(highlighterDeferred: Deferred<EditorHighlighter>, editor: EditorEx) {
|
||||
val highlighter = highlighterDeferred.await()
|
||||
subtask("editor highlighter set", Dispatchers.EDT) {
|
||||
editor.settings.setLanguageSupplier { getDocumentLanguage(editor) }
|
||||
|
||||
+11
-4
@@ -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<TransientEditorState>("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<AsyncEditorLoaderService>().coroutineScope.childScope(supervisor = false))
|
||||
val context = if (StartUpMeasurer.isEnabled()) rootTask() else EmptyCoroutineContext
|
||||
return AsyncEditorLoader(
|
||||
project = project,
|
||||
provider = provider,
|
||||
coroutineScope = project.service<AsyncEditorLoaderService>().coroutineScope.childScope(supervisor = false, context = context),
|
||||
)
|
||||
}
|
||||
|
||||
@Internal
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user