LineMarkerProviderDescriptor not working properly
Answered
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")
}
}
}
}
Please sign in to leave a comment.
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?