IJPL-165414: vcs marker: Rework the popup opening

Delegate the popup building and opening to the `LineStatusMarkerPopupService` and control that opened popup is disposed
before creating the new marker panel


(cherry picked from commit fff881f00d75ce4d4b9fa0fc5fa2e2407f7e8813)

IJ-CR-149651

GitOrigin-RevId: 3458a2e0f1e7830d5c67aa539839182840e9d64c
This commit is contained in:
Aleksandr Krasilnikov
2024-11-08 16:50:24 +01:00
committed by intellij-monorepo-bot
parent 814318fa18
commit 6901adc6dc
4 changed files with 69 additions and 75 deletions

View File

@@ -152,14 +152,6 @@ public class LineStatusMarkerPopupPanel extends JPanel {
return size;
}
public static void showPopupAt(@NotNull Editor editor,
@NotNull LineStatusMarkerPopupPanel panel,
@Nullable Point mousePosition,
@NotNull Disposable popupDisposable) {
LineStatusMarkerPopupService.getInstance().showPopupAt(editor, panel, mousePosition, popupDisposable);
}
@NotNull
public static EditorTextField createTextField(@NotNull Editor editor, @NotNull String content) {
EditorTextField field = new EditorTextField(content);

View File

@@ -19,7 +19,10 @@ import com.intellij.openapi.editor.event.*
import com.intellij.openapi.fileEditor.FileEditorManagerEvent
import com.intellij.openapi.fileEditor.FileEditorManagerListener
import com.intellij.openapi.util.Disposer
import com.intellij.ui.*
import com.intellij.ui.EditorTextComponent
import com.intellij.ui.HintHint
import com.intellij.ui.HintListener
import com.intellij.ui.LightweightHint
import com.intellij.util.concurrency.annotations.RequiresEdt
import com.intellij.util.ui.UIUtil
import java.awt.Point
@@ -28,11 +31,29 @@ import java.awt.event.ComponentEvent
import java.awt.event.MouseAdapter
@Service(Service.Level.APP)
internal class LineStatusMarkerPopupService {
private var lastKnownHint: Hint? = null
class LineStatusMarkerPopupService {
private var activePopupDisposable: Disposable? = null
@RequiresEdt
fun showPopupAt(
fun buildAndShowPopup(parentDisposable: Disposable, editor: Editor, mousePosition: Point?, builder: (popupDisposable: Disposable) -> LineStatusMarkerPopupPanel) {
val popupDisposable = getNextPopupDisposable(parentDisposable)
val markerPopupPanel = builder(popupDisposable)
showPopupAt(editor, markerPopupPanel, mousePosition, popupDisposable)
}
private fun getNextPopupDisposable(parentDisposable: Disposable): Disposable {
closeActivePopup()
activePopupDisposable = Disposer.newDisposable(parentDisposable, "LineStatusMarkerPopup")
return activePopupDisposable!!
}
fun closeActivePopup() {
if (activePopupDisposable != null) Disposer.dispose(activePopupDisposable!!)
}
@RequiresEdt
private fun showPopupAt(
editor: Editor,
panel: LineStatusMarkerPopupPanel,
mousePosition: Point?,
@@ -42,16 +63,14 @@ internal class LineStatusMarkerPopupService {
Disposer.register(popupDisposable, Disposable {
UIUtil.invokeLaterIfNeeded {
hint.hide()
resetLastHint()
}
})
hint.addHintListener(HintListener { Disposer.dispose(popupDisposable) })
hint.addHintListener(HintListener { Disposer.dispose(popupDisposable) }) // doesn't need it anymore?
hint.setForceLightweightPopup(true)
// if there are no listeners, events are passed to the top level
panel.addMouseListener(object : MouseAdapter() {})
val line = editor.getCaretModel().logicalPosition.line
val point = HintManagerImpl.getHintPosition(hint, editor, LogicalPosition(line, 0), HintManager.UNDER)
if (mousePosition != null) { // show right after the nearest line
@@ -63,9 +82,7 @@ internal class LineStatusMarkerPopupService {
point.x -= panel.editorTextOffset // align a main editor with the one in popup
trackInnerEditorResizing(panel, hint, popupDisposable)
setupEditorListeners(editor, popupDisposable, hint)
beforeShowNewHint(hint)
setupEditorListeners(editor, popupDisposable)
showHint(hint, editor, point)
if (!hint.isVisible()) {
@@ -73,22 +90,6 @@ internal class LineStatusMarkerPopupService {
}
}
internal fun hidePopup() {
lastKnownHint?.hide()
}
private fun beforeShowNewHint(newHint: LightweightHint) {
if (lastKnownHint != null) {
lastKnownHint!!.hide()
resetLastHint()
}
lastKnownHint = newHint
}
private fun resetLastHint() {
lastKnownHint = null
}
private fun showHint(hint: LightweightHint, editor: Editor, point: Point) {
HintManagerImpl.doShowInGivenLocation(hint, editor, point, HintHint(editor, point), true)
}
@@ -121,26 +122,26 @@ internal class LineStatusMarkerPopupService {
})
}
private fun setupEditorListeners(editor: Editor, popupDisposable: Disposable, hint: LightweightHint) {
trackFileEditorChange(popupDisposable, hint)
trackDocumentChange(editor, popupDisposable, hint)
trackSelection(editor, popupDisposable, hint)
trackCaretPosition(editor, popupDisposable, hint)
trackScrolling(editor, popupDisposable, hint)
trackMouseClick(editor, popupDisposable, hint)
trackEditorLookup(editor, popupDisposable, hint)
private fun setupEditorListeners(editor: Editor, popupDisposable: Disposable) {
trackFileEditorChange(popupDisposable)
trackDocumentChange(editor, popupDisposable)
trackSelection(editor, popupDisposable)
trackCaretPosition(editor, popupDisposable)
trackScrolling(editor, popupDisposable)
trackMouseClick(editor, popupDisposable)
trackEditorLookup(editor, popupDisposable)
}
private fun trackFileEditorChange(popupDisposable: Disposable, hint: LightweightHint) {
private fun trackFileEditorChange(popupDisposable: Disposable) {
ApplicationManager.getApplication().getMessageBus().connect(popupDisposable)
.subscribe<FileEditorManagerListener>(FileEditorManagerListener.FILE_EDITOR_MANAGER, object : FileEditorManagerListener {
override fun selectionChanged(event: FileEditorManagerEvent) {
hint.hide()
closeActivePopup()
}
})
}
private fun trackDocumentChange(editor: Editor, popupDisposable: Disposable, hint: LightweightHint) {
private fun trackDocumentChange(editor: Editor, popupDisposable: Disposable) {
editor.getDocument().addDocumentListener(object : BulkAwareDocumentListener {
override fun documentChangedNonBulk(event: DocumentEvent) {
if (event.getOldLength() != 0 || event.getNewLength() != 0) onDocumentChange()
@@ -151,48 +152,48 @@ internal class LineStatusMarkerPopupService {
}
fun onDocumentChange() {
hint.hide()
closeActivePopup()
}
}, popupDisposable)
}
private fun trackSelection(editor: Editor, popupDisposable: Disposable, hint: LightweightHint) {
private fun trackSelection(editor: Editor, popupDisposable: Disposable) {
editor.getSelectionModel().addSelectionListener(object : SelectionListener {
override fun selectionChanged(e: SelectionEvent) {
hint.hide()
closeActivePopup()
}
}, popupDisposable)
}
private fun trackCaretPosition(editor: Editor, popupDisposable: Disposable, hint: LightweightHint) {
private fun trackCaretPosition(editor: Editor, popupDisposable: Disposable) {
editor.getCaretModel().addCaretListener(object : CaretListener {
override fun caretPositionChanged(event: CaretEvent) {
hint.hide()
closeActivePopup()
}
}, popupDisposable)
}
private fun trackScrolling(editor: Editor, popupDisposable: Disposable, hint: LightweightHint) {
private fun trackScrolling(editor: Editor, popupDisposable: Disposable) {
editor.getScrollingModel().addVisibleAreaListener(object : VisibleAreaListener {
override fun visibleAreaChanged(e: VisibleAreaEvent) {
val old = e.oldRectangle
val new = e.newRectangle
if (old != null && old.x == new.x && old.y == new.y) return
hint.hide()
closeActivePopup()
}
}, popupDisposable)
}
private fun trackMouseClick(editor: Editor, popupDisposable: Disposable, hint: LightweightHint) {
private fun trackMouseClick(editor: Editor, popupDisposable: Disposable) {
editor.addEditorMouseListener(object : EditorMouseListener {
override fun mousePressed(event: EditorMouseEvent) {
hint.hide()
closeActivePopup()
}
}, popupDisposable)
}
private fun trackEditorLookup(editor: Editor, popupDisposable: Disposable, hint: LightweightHint) {
private fun trackEditorLookup(editor: Editor, popupDisposable: Disposable) {
val project = editor.project
if (project != null) LookupManager.hideActiveLookup(project) // reset current
@@ -201,7 +202,7 @@ internal class LineStatusMarkerPopupService {
override fun hintShown(sourceEditor: Editor, editorHint: LightweightHint, flags: Int, hintInfo: HintHint) {
if (sourceEditor !== editor) return
if (editorHint is Lookup) {
hint.hide()
closeActivePopup()
}
}
})
@@ -214,13 +215,17 @@ internal class LineStatusMarkerPopupService {
}
}
private fun closeActivePopup() {
LineStatusMarkerPopupService.instance.closeActivePopup()
}
private class LineStatusMakerEscEditorHandler(private val delegate: EditorActionHandler) : EditorActionHandler() {
override fun isEnabledForCaret(editor: Editor, caret: Caret, dataContext: DataContext?): Boolean {
return delegate.isEnabled(editor, caret, dataContext)
}
override fun doExecute(editor: Editor, caret: Caret?, dataContext: DataContext?) {
LineStatusMarkerPopupService.instance.hidePopup()
closeActivePopup()
delegate.execute(editor, caret, dataContext)
}
}

View File

@@ -14,9 +14,7 @@ import com.intellij.openapi.editor.ex.EditorEx
import com.intellij.openapi.editor.markup.LineMarkerRenderer
import com.intellij.openapi.editor.markup.MarkupEditorFilter
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.NlsContexts
import com.intellij.openapi.vcs.ex.LineStatusMarkerPopupPanel.showPopupAt
import java.awt.Graphics
import java.awt.Point
import java.awt.Rectangle
@@ -57,9 +55,9 @@ abstract class LineStatusMarkerRendererWithPopup(
override fun showHintAt(editor: Editor, range: Range, mousePosition: Point?) {
if (!rangesSource.isValid()) return
val popupDisposable = Disposer.newDisposable(disposable)
val popup = createPopupPanel(editor, range, mousePosition, popupDisposable)
showPopupAt(editor, popup, mousePosition, popupDisposable)
LineStatusMarkerPopupService.instance.buildAndShowPopup(disposable, editor, mousePosition) { popupDisposable ->
createPopupPanel(editor, range, mousePosition, popupDisposable)
}
}
protected open fun getTooltipText(): @NlsContexts.Tooltip String? = null

View File

@@ -34,7 +34,6 @@ import com.intellij.openapi.util.Disposer
import com.intellij.openapi.util.NlsContexts
import com.intellij.openapi.util.text.StringUtil
import com.intellij.openapi.vcs.ex.*
import com.intellij.openapi.vcs.ex.LineStatusMarkerPopupPanel.showPopupAt
import com.intellij.openapi.vcs.ex.Range
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.ui.EditorTextField
@@ -541,22 +540,22 @@ class GitStageLineStatusTracker(
return
}
val popupDisposable = Disposer.newDisposable(tracker.disposable)
LineStatusMarkerPopupService.instance.buildAndShowPopup(tracker.disposable, editor, mousePosition) { popupDisposable->
val stagedTextField = createTextField(editor, tracker.stagedDocument, range.stagedLine1, range.stagedLine2)
val vcsTextField = createTextField(editor, tracker.vcsDocument, range.vcsLine1, range.vcsLine2)
installWordDiff(editor, stagedTextField, vcsTextField, range, popupDisposable)
val stagedTextField = createTextField(editor, tracker.stagedDocument, range.stagedLine1, range.stagedLine2)
val vcsTextField = createTextField(editor, tracker.vcsDocument, range.vcsLine1, range.vcsLine2)
installWordDiff(editor, stagedTextField, vcsTextField, range, popupDisposable)
val editorsPanel = createEditorComponent(editor, stagedTextField, vcsTextField)
val editorsPanel = createEditorComponent(editor, stagedTextField, vcsTextField)
val actions = createToolbarActions(editor, range, mousePosition)
val toolbar = LineStatusMarkerPopupPanel.buildToolbar(editor, actions, popupDisposable)
val actions = createToolbarActions(editor, range, mousePosition)
val toolbar = LineStatusMarkerPopupPanel.buildToolbar(editor, actions, popupDisposable)
val additionalPanel = createStageLinksPanel(editor, range, mousePosition, popupDisposable)
val additionalPanel = createStageLinksPanel(editor, range, mousePosition, popupDisposable)
val popupPanel = LineStatusMarkerPopupPanel.create(editor, toolbar, editorsPanel, additionalPanel)
toolbar.setTargetComponent(popupPanel)
showPopupAt(editor, popupPanel, mousePosition, popupDisposable)
val popupPanel = LineStatusMarkerPopupPanel.create(editor, toolbar, editorsPanel, additionalPanel)
toolbar.setTargetComponent(popupPanel)
popupPanel
}
}
fun createEditorComponent(editor: Editor, stagedTextField: EditorTextField, vcsTextField: EditorTextField): JComponent {