Skip to content

Commit

Permalink
refactor: simplify icon reloading
Browse files Browse the repository at this point in the history
de-duplicated icon addition, and handle folded line.
  • Loading branch information
cjlee38 committed Nov 2, 2024
1 parent 17ba456 commit 870a493
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 79 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,19 @@ import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
import com.fasterxml.jackson.module.kotlin.convertValue
import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.FoldRegion
import com.intellij.openapi.editor.event.BulkAwareDocumentListener
import com.intellij.openapi.editor.event.DocumentEvent
import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.editor.ex.FoldingListener
import com.intellij.openapi.editor.ex.RangeHighlighterEx
import com.intellij.openapi.editor.ex.util.EditorUtil
import com.intellij.openapi.editor.impl.event.MarkupModelListener
import com.intellij.openapi.editor.markup.HighlighterLayer
import com.intellij.openapi.editor.markup.HighlighterTargetArea
import com.intellij.openapi.editor.markup.RangeHighlighter
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.ui.update.MergingUpdateQueue
import com.intellij.util.ui.update.Update
Expand All @@ -22,6 +28,7 @@ import io.cjlee.gitnote.jcef.protocol.ProtocolHandler
import io.cjlee.gitnote.jcef.protocol.ProtocolMessaage
import javax.swing.SwingUtilities


/**
* This class is a main component which is responsible to interact with the editor users use.
*
Expand All @@ -35,10 +42,11 @@ class GitNoteDocumentListener(
private lateinit var note: Note
private val mapper = jacksonObjectMapper().registerModule(JavaTimeModule())
private val reloadOnEventThread = { SwingUtilities.invokeLater { this.reload() } }

// queue for saving document not too frequently
private val queue = MergingUpdateQueue("GitNoteQueue", 100, true, null)

private val lineHighlighters = mutableSetOf<RangeHighlighterEx>()
private val lineHighlighters = mutableSetOf<RangeHighlighter>()

init {
reloadOnEventThread()
Expand All @@ -47,100 +55,76 @@ class GitNoteDocumentListener(
editor.addEditorMouseListener(iconVisibility)
editor.addEditorMouseMotionListener(iconVisibility)
}

val disposable = Disposer.newDisposable()
editor.markupModel.addMarkupModelListener(disposable, object : MarkupModelListener {
override fun beforeRemoved(highlighter: RangeHighlighterEx) {
val iconRenderer = highlighter.gutterIconRenderer as? GitNoteGutterIconRenderer ?: return
Disposer.dispose(iconRenderer)
lineHighlighters.remove(highlighter)
}
})
EditorUtil.disposeWithEditor(editor, disposable)

editor.foldingModel.addListener(object : FoldingListener {
override fun onFoldRegionStateChange(region: FoldRegion) {
reloadOnEventThread()
}
}, disposable)
}

override fun documentChanged(event: DocumentEvent) {
this.queue(LOW_PRIORITY) {
FileDocumentManager.getInstance().saveDocument(event.document)
addEmptyMessageIcons(event.document)
addMessageIcons(event.document)
}
}

private fun reload() {
this.queue(HIGH_PRIORITY) {
note = handler.read(file.path, force = true) ?: throw IllegalStateException("no note")
addNoteMessageIcons(editor.document)
addEmptyMessageIcons(editor.document)
addMessageIcons(editor.document)
}
}

private fun addNoteMessageIcons(document: Document) {
lineHighlighters.clear()
private fun addMessageIcons(document: Document) {
editor.markupModel.removeAllHighlighters()
val messagesByLine = note.messages.groupBy { it.line }

val noteLineMessages = note.messages.groupBy { it.line }
(0 until document.lineCount).forEach { line ->
val height = heightOfLine(line)

val lineMessages = noteLineMessages[line]
val protocolHandlers = createProtocolHandlers(line)
editor.markupModel.addRangeHighlighterAndChangeAttributes(
null,
height.first,
height.second,
HighlighterLayer.LAST,
HighlighterTargetArea.EXACT_RANGE,
false
) { highlighter ->
val gitNoteGutterIconRenderer = GitNoteGutterIconRenderer(
lineMessages = lineMessages ?: emptyList(),
protocolHandlers = protocolHandlers,
visible = lineMessages != null,
highlighter = highlighter,
document = document
)
highlighter.gutterIconRenderer = gitNoteGutterIconRenderer
}.also { highlighter -> lineHighlighters.add(highlighter) }
}
}

private fun addEmptyMessageIcons(document: Document) {
// TODO : if highlighters keeps growing, mutable renderer would be answer, using below
// editor.markupModel.allHighlighters
// .map { it.gutterIconRenderer }
// .filterIsInstance<GitNoteGutterIconRenderer>()

lineHighlighters.map { it.gitNoteGutterIconRenderer }
.filter { !it.hasMessage }
.forEach {
lineHighlighters.remove(it.highlighter)
editor.markupModel.removeHighlighter(it.highlighter)
val isLineVisible = editor.foldingModel.allFoldRegions.none { region ->
region.startOffset <= document.getLineStartOffset(line) &&
region.endOffset > document.getLineStartOffset(line) &&
!region.isExpanded &&
document.getLineNumber(region.startOffset) != line
}

(0 until document.lineCount).forEach { line ->
val height = heightOfLine(line)

if (lineHighlighters.any { it.gitNoteGutterIconRenderer.line == line }) {
return@forEach
if (isLineVisible) {
val protocolHandlers = createProtocolHandlers(line)
val lineMessages = messagesByLine[line]

val start = editor.document.getLineStartOffset(line)
val end = editor.document.getLineEndOffset(line)
editor.markupModel.addRangeHighlighterAndChangeAttributes(
null,
start,
end,
HighlighterLayer.LAST,
HighlighterTargetArea.EXACT_RANGE,
false
) { highlighter ->
highlighter.gutterIconRenderer = GitNoteGutterIconRenderer(
lineMessages = lineMessages ?: emptyList(),
protocolHandlers = protocolHandlers,
visible = lineMessages != null,
line = line,
document = document
)
}.also { lineHighlighters.add(it) }
}

val protocolHandlers = createProtocolHandlers(line)
editor.markupModel.addRangeHighlighterAndChangeAttributes(
null,
height.first,
height.second,
HighlighterLayer.LAST,
HighlighterTargetArea.EXACT_RANGE,
false
) { highlighter ->
highlighter.gutterIconRenderer = GitNoteGutterIconRenderer(
lineMessages = emptyList(),
protocolHandlers = protocolHandlers,
visible = false,
highlighter = highlighter,
document = document
)
}.also { highlighter -> lineHighlighters.add(highlighter) }
}
}

private fun heightOfLine(line: Int) : Pair<Int, Int> {
return (editor.document.getLineStartOffset(line) to editor.document.getLineEndOffset(line))
}

private val RangeHighlighterEx.gitNoteGutterIconRenderer: GitNoteGutterIconRenderer
get() = this.gutterIconRenderer as GitNoteGutterIconRenderer

private fun createProtocolHandlers(line: Int): Map<String, ProtocolHandler> {
return mapOf(
"messages/read" to object : ProtocolHandler {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import com.intellij.openapi.Disposable
import com.intellij.openapi.actionSystem.AnAction
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.ex.RangeHighlighterEx
import com.intellij.openapi.editor.markup.GutterDraggableObject
import com.intellij.openapi.editor.markup.GutterIconRenderer
import com.intellij.openapi.util.IconLoader
Expand All @@ -19,15 +18,13 @@ import javax.swing.Icon
import javax.swing.ImageIcon

class GitNoteGutterIconRenderer(
val line: Int,
val lineMessages: List<Message>,
private val protocolHandlers: Map<String, ProtocolHandler>,
var visible: Boolean,
val highlighter: RangeHighlighterEx,
val document: Document
) : GutterIconRenderer() {
) : GutterIconRenderer(), Disposable {
val hasMessage: Boolean = lineMessages.isNotEmpty()
val line: Int
get() = document.getLineNumber(highlighter.startOffset)

override fun getIcon(): Icon {
return when {
Expand Down Expand Up @@ -60,11 +57,20 @@ class GitNoteGutterIconRenderer(

override fun hashCode(): Int = icon.hashCode()

override fun dispose() {
// TODO : dispose
}

override fun getDraggableObject(): GutterDraggableObject? {
// TODO : drag & drop
return super.getDraggableObject()
}

override fun toString(): String {
return "GitNoteGutterIconRenderer(line=$line, lineMessages=$lineMessages, protocolHandlers=$protocolHandlers, visible=$visible, document=$document, hasMessage=$hasMessage)"
}


companion object IconRenderer {
private val ICON = IconLoader.getIcon("/icons/icon.png", GitNoteGutterIconRenderer::class.java)
.let { IconUtil.scale(it, null, (13.0 / it.iconWidth).toFloat()) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import com.intellij.openapi.editor.LogicalPosition
import com.intellij.openapi.editor.event.EditorMouseEvent
import com.intellij.openapi.editor.event.EditorMouseListener
import com.intellij.openapi.editor.event.EditorMouseMotionListener
import com.intellij.openapi.editor.ex.RangeHighlighterEx
import com.intellij.openapi.editor.markup.RangeHighlighter
import javax.swing.JComponent

class IconVisibility(
private val highlighters: Set<RangeHighlighterEx>
private val highlighters: Set<RangeHighlighter>
) : EditorMouseListener, EditorMouseMotionListener {

override fun mouseMoved(e: EditorMouseEvent) = doUpdate(e.editor, e.logicalPosition.line)
Expand Down

0 comments on commit 870a493

Please sign in to comment.