How can I not block the ui or at least show a cancelable progress indicator for a long-ish task in a quick fix invocation?

Answered

I have this invoke method in an implementation of LocalQuickFixAndIntentionActionOnPsiElement

override fun invoke(project: Project, file: PsiFile, editor: Editor?, startElement: PsiElement, endElement: PsiElement) {
        if (editor == null) return

        val (panel, popup, constraints) = getPanelAndPopupAndConstraints()
        panel.add(createLabel("Loading..."), constraints)

        val point = editor.offsetToXY(startElement.textOffset)
        point.translate(-editor.scrollingModel.horizontalScrollOffset, -editor.scrollingModel.verticalScrollOffset)

        popup.show(editor.component, point.x, point.y)

        object : SwingWorker<PsiDocComment?, Unit>() {
            override fun doInBackground(): PsiDocComment? {
                return ReadAction.compute<PsiDocComment?, Exception> {
                    val prompt = when (startElement) {
                        is PsiMethod -> getMethodPrompt(startElement, file)
                        is PsiClass -> getClassPrompt(startElement, file)
                        else -> return@compute null
                    }
                    return@compute getDocComment(project, prompt)
                }
            }

            override fun done() {
                popup.isVisible = false
                val result = try { get() } catch (e: Exception) { null } ?: return

                ApplicationManager.getApplication().invokeAndWait {
                    WriteCommandAction.runWriteCommandAction(project) {
                        val anchorBefore = startElement.children.firstOrNull { it !is PsiDocComment }
                        if (anchorBefore != null) {
                            startElement.addBefore(result, anchorBefore)
                        } else {
                            startElement.add(result)
                        }
                    }
                }
            }
        }.execute()
    }

I know this isn't really the right way of doing something like this, but for the life of me I cannot figure out from the docs how to do it correctly. I want to either not block the ui while the docComment is being generated (it's prompting an azure gpt model to do so), or I want to display a cancelable modal indicating clearly that the docs are being generated.

The process usually takes like 3 seconds, but sometimes on large classes it can take like 25.

Can anyone help me? Thanks.

0
3 comments

Okay I figured out a better way to do this, but it uses a deprecated method runProcessWithProgressAsynchronously(Project, String, Runnable, Runnable, Runnable, PerformInBackgroundOption). Not sure how to convert it to use the non-deprecated one, though I did try.

 

 override fun invoke(project: Project, file: PsiFile, editor: Editor?, startElement: PsiElement, endElement: PsiElement) {
    if (editor == null) return

    ProgressManager.getInstance().runProcessWithProgressAsynchronously(project, "Operation in progress", {
        val progressIndicator = ProgressIndicatorProvider.getGlobalProgressIndicator()
        progressIndicator.isIndeterminate = true
        progressIndicator.text = "Constructing AI Prompt..."

        val result = ReadAction.compute<PsiDocComment?, Exception> {
            val prompt = when (startElement) {
                is PsiMethod -> getMethodPrompt(startElement, file)
                is PsiClass -> getClassPrompt(startElement, file)
                else -> return@compute null
            }
            progressIndicator.text = "Generating Javadoc..."
            return@compute getDocComment(project, prompt)
        }

        if (result != null) {
            progressIndicator.text = "Inserting Javadoc..."
            WriteCommandAction.runWriteCommandAction(project) {
                val anchorBefore = startElement.children.firstOrNull { it !is PsiDocComment }
                if (anchorBefore != null) {
                    startElement.addBefore(result, anchorBefore)
                } else {
                    startElement.add(result)
                }
            }
        }
    }, {}, {}, PerformInBackgroundOption.DEAF)
}

Any help with using the non-deprecated version?

 

0

Hi,

Did you try to implement com.intellij.openapi.progress.ProgressManager#run(com.intellij.openapi.progress.Task)? You should call it with a task extending com.intellij.openapi.progress.Task.Backgroundable.

If you tried, what was the problem?

0

I tried so many things in so many iterations that I honestly cannot remember.

Just tried it though, ended up with this, and it works great! Thank you.

 

ProgressManager.getInstance().run(object : Task.Backgroundable(project, "Operation in progress", true, DEAF) {
    override fun run(indicator: ProgressIndicator) {
        indicator.isIndeterminate = true
        indicator.text = "Constructing AI Prompt..."

        val result = ReadAction.compute<PsiDocComment?, Exception> {
            val prompt = when (element) {
                is PsiMethod -> getMethodPrompt(element, file)
                is PsiClass -> getClassPrompt(element, file)
                is PsiDocCommentOwner -> getDocOwnerPrompt(element, file)
                else -> return@compute null
            }
            indicator.text = "Generating Javadoc..."
            return@compute getDocComment(project, prompt)
        } ?: return

        indicator.text = "Inserting Javadoc..."
        WriteCommandAction.runWriteCommandAction(project) {
            val anchorBefore = element.children.firstOrNull { it !is PsiDocComment }
            if (anchorBefore != null) {
                element.addBefore(result, anchorBefore)
            } else {
                element.add(result)
            }
        }
    }
})

 

0

Please sign in to leave a comment.