[terminal] IJPL-182482 Add repaint / paint callbacks to EditorImpl

These are low-level hacky hooks that are needed to track
repaint requests and the following paint operations
to measure terminal frontend output latency.
With these hooks we can try to measure when the output
is actually painted.

GitOrigin-RevId: d308e50ebffd0705251c3d560e2979d9ff8a7f48
This commit is contained in:
Sergei Tachenov
2025-04-15 07:32:53 +00:00
committed by intellij-monorepo-bot
parent add7b1857f
commit 2d39d61306
3 changed files with 57 additions and 0 deletions
@@ -81,6 +81,8 @@ public final class EditorComponentImpl extends JTextComponent implements Scrolla
private final EditorImpl editor;
private @Nullable Runnable myRepaintCallback;
public EditorComponentImpl(@NotNull EditorImpl editor) {
this.editor = editor;
enableEvents(AWTEvent.KEY_EVENT_MASK | AWTEvent.INPUT_METHOD_EVENT_MASK);
@@ -136,6 +138,10 @@ public final class EditorComponentImpl extends JTextComponent implements Scrolla
setupEditorSwingCaretUpdatesCourierIfRequired();
}
void setRepaintCallback(@Nullable Runnable repaintCallback) {
myRepaintCallback = repaintCallback;
}
@Override
public void uiSettingsChanged(@NotNull UISettings uiSettings) {
UISettingsUtils settingsUtils = UISettingsUtils.with(uiSettings);
@@ -282,6 +288,9 @@ public final class EditorComponentImpl extends JTextComponent implements Scrolla
int topOverhang = Math.max(0, editor.myView.getTopOverhang());
int bottomOverhang = Math.max(0, editor.myView.getBottomOverhang());
repaint(x, y - topOverhang, width, height + topOverhang + bottomOverhang);
if (myRepaintCallback != null && isShowing() && width > 0 && height > 0) {
myRepaintCallback.run();
}
}
//--implementation of Scrollable interface--------------------------------------
@@ -2038,6 +2038,40 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
LOG.assertTrue(success || isReleased);
}
/**
* Sets a callback invoked after every repaint request.
* <p>
* The callback is only invoked if the component is showing and the request has a non-empty rectangle.
* In other words, it's invoked when the repaint request is likely to be followed by an actual painting operation.
* </p>
* <p>
* The callback must be fast, non-intrusive, and should not throw any exceptions.
* </p>
* <p>
* This is an internal hack, which is why it doesn't follow the usual add-listener pattern.
* </p>
* @param callback the callback, {@code null} removes the callback
*/
@ApiStatus.Internal
public void setRepaintCallback(@Nullable Runnable callback) {
myEditorComponent.setRepaintCallback(callback);
}
/**
* Sets a callback invoked after every paint operation.
* <p>
* The callback must be fast, non-intrusive, and should not throw any exceptions.
* </p>
* <p>
* This is an internal hack, which is why it doesn't follow the usual add-listener pattern.
* </p>
* @param callback the callback, {@code null} removes the callback
*/
@ApiStatus.Internal
public void setPaintCallback(@Nullable Runnable callback) {
myView.setPaintCallback(callback);
}
@Override
public boolean isStickySelection() {
return myState.isStickySelection();
@@ -24,6 +24,7 @@ import com.intellij.util.concurrency.annotations.RequiresEdt;
import org.intellij.lang.annotations.JdkConstants;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
import java.awt.*;
@@ -75,6 +76,8 @@ public final class EditorView implements TextDrawingCallback, Disposable, Dumpab
private final Object myLock = new Object();
private @Nullable Runnable myPaintCallback;
public EditorView(@NotNull EditorImpl editor) {
this(editor, editor.getEditorModel());
}
@@ -99,6 +102,14 @@ public final class EditorView implements TextDrawingCallback, Disposable, Dumpab
Disposer.register(this, mySizeManager);
}
/**
* @see EditorImpl#setPaintCallback(Runnable)
*/
@ApiStatus.Internal
public void setPaintCallback(@Nullable Runnable paintCallback) {
myPaintCallback = paintCallback;
}
@RequiresEdt
public int yToVisualLine(int y) {
assertNotInBulkMode();
@@ -211,6 +222,9 @@ public final class EditorView implements TextDrawingCallback, Disposable, Dumpab
getSoftWrapModel().prepareToMapping();
checkFontRenderContext(g.getFontRenderContext());
myPainter.paint(g);
if (myPaintCallback != null) {
myPaintCallback.run();
}
}
@RequiresEdt