LineMarkerProviderDescriptor not working properly

已回答

Hello,

I implemented my LineMarkerProviderDescriptor, but it's not working properly. It must display gutter icons in provided lines, but sometimes it shows icons in the right place, sometimes not. Could someone advice on this? Thank you!

 

package io.plugin.ui

import com.intellij.codeInsight.daemon.GutterIconNavigationHandler
import com.intellij.codeInsight.daemon.LineMarkerInfo
import com.intellij.codeInsight.daemon.LineMarkerProviderDescriptor
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.markup.GutterIconRenderer
import com.intellij.openapi.project.Project
import com.intellij.openapi.ui.popup.JBPopupFactory
import com.intellij.openapi.util.TextRange
import com.intellij.psi.*
import com.intellij.ui.awt.RelativePoint
import git4idea.commands.Git
import git4idea.repo.GitRepositoryManager
import java.awt.event.MouseEvent
import java.util.*
import java.util.concurrent.CompletableFuture

class MyDocumentLineMarkerProviderDescriptor : LineMarkerProviderDescriptor() {

    private val logger = logger<MyDocumentLineMarkerProviderDescriptor>()

    override fun getName(): String = "MyDocumentLineMarkerProviderDescriptor"

    override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo<*>? {
        return null
    }

    override fun collectSlowLineMarkers(
            psiElements: MutableList<out PsiElement>,
            result: MutableCollection<in LineMarkerInfo<*>>
    ) {
        logger.info("Enter collectSlowLineMarkers()")

        if (psiElements.isEmpty()) {
            return
        }

        logger.info("Try to get psi element and identify builder by language")

        logger.info("Try to get project from psi")

        logger.info("Get psi file")

        val psiFile = psiElements.find { it is PsiFile } as? PsiFile ?: return
        val filePath = getProjectRelativeFilePath(psiFile) ?: return
        val project = psiFile.project

        val documents = getApiService().getDocuments(project, filePath)

        logger.info("Get diff lines from git")

        logger.info("Start iterate to create line markers")

        val documentsMap = createLineToDocumentsMap(project, documents, psiFile)

        documentsMap.keys.forEach {
            val lineNumber = it

            logger.info("Start iterate line: $lineNumber")

            val lineDocs = documentsMap[lineNumber] ?: return@forEach
            val docs = Collections.unmodifiableList(lineDocs)

            val lineMarkerInfo = createLineMarkerInfo(project, psiElements, lineNumber, docs, psiFile) ?: return@forEach

            logger.info("Add line marker info to result list")

            result.add(lineMarkerInfo)
        }
    }

    private fun createLineToDocumentsMap(
        project: Project,
        documents: List<MyDocument>,
        psiFile: PsiFile
    ): Map<Int, MutableList<MyDocument>> {
        val docs = mutableMapOf<Int, MutableList<MyDocument>>()

        val gitService = getGitService(project) ?: return docs

        documents.forEach { myDocument ->
            val diffLines = gitService.getDiff(psiFile.virtualFile, MyDocument.commit_sha)

            // Some documents may have 0 line number. In this case we will use 1 line.
            val documentStartLine = if (myDocument.start_line == 0) {
                1
            } else {
                myDocument.start_line
            }

            val newLineNumber = diffLines.find {
                it.oldNumber == documentStartLine
            }?.newNumber

            logger.info("document start line: ${documentStartLine}, new line: $newLineNumber")

            val lineNumber = newLineNumber ?: documentStartLine

            if (!docs.contains(lineNumber)) {
                docs[lineNumber] = mutableListOf(myDocument)
            } else {
                val lineDocs: MutableList<myDocument>? = docs[lineNumber]
                lineDocs?.add(myDocument)
            }
        }

        return docs
    }

    private fun createLineMarkerInfo(
        project: Project,
        psiElements: MutableList<out PsiElement>,
        documentLineNumber: Int,
        docs: List<MyDocument>,
        psiFile: PsiFile
    ): LineMarkerInfo<PsiElement>? {
        logger.info("Try get ide document by psi file")

        val document: Document = PsiDocumentManager.getInstance(project).getDocument(psiFile) ?: return null

        if (document.lineCount < documentLineNumber) {
            logger.info("Current document have ${document.lineCount} and line number is $documentLineNumber")
            return null
        }

        val lineNumber = if (documentLineNumber == 0) { 0 } else { documentLineNumber - 1 }
        val lineStartOffset = document.getLineStartOffset(lineNumber)

        logger.info("Target offset: $lineStartOffset")
        logger.info("Try to find PsiElement by offset")

        val element: PsiElement? = psiElements.find {
            document.getLineNumber(it.textRange.startOffset) == lineNumber
        }

        if (element == null) {
            logger.info("PsiElement is null")
            return null
        }

        val documentsTooltipText = docs.joinToString(separator = ", ") { it.title }

        logger.info("PsiElement is $element")

        val completableFuture = CompletableFuture<LineMarkerInfo<PsiElement>>()

        ApplicationManager.getApplication().runReadAction {
            completableFuture.complete(LineMarkerInfo(
                element,
                TextRange(lineStartOffset, lineStartOffset),
                MyIcons.DocumentGutterIcon,
                { documentsTooltipText },
                DocumentGutterIconNavigationHandler(docs),
                GutterIconRenderer.Alignment.LEFT,
                { documentsTooltipText }))
        }

        return completableFuture.get()
    }

    class DocumentGutterIconNavigationHandler(private val documents: List<MyDocument>) :
        GutterIconNavigationHandler<PsiElement> {
        private val logger = logger<DocumentGutterIconNavigationHandler>()

        override fun navigate(mouseEvent: MouseEvent?, psiElement: PsiElement?) {
            logger.info("Enter navigate(). Open popup with documents")

            if (mouseEvent != null) {
                logger.info("Mouse event is not null and it build popup dialog")

                val mainPanel = PopupDialogBuilder.createDialog(documents)

                logger.info("Dialog ready to show")

                val dialog = JBPopupFactory.getInstance().createComponentPopupBuilder(mainPanel, mainPanel)
                    .setRequestFocus(true)
                    .setResizable(true)
                    .createPopup()

                dialog.show(RelativePoint(mouseEvent))

                logger.info("Dialog shown")
            }
        }
    }
}

 

 

 

0

Hi Alexandr,

What does "sometimes it shows icons in the right place, sometimes not" mean? Is it sometimes shown and sometimes not, or is it sometimes in the wrong place?

Regarding the code, I don't really get the point of using runReadAction() and CompletableFuture. What is the point of them? What did you try to achieve here?

0

请先登录再写评论。