Streaming content to a custom `DiffRequestPanel`

Context

I’m currently embedding a customized DiffRequestPanel (with my own DiffRequestProcessor ) within an Inlay Component.

I’m using DiffContentFactory#createFragment to produce the two versions of code to compare. These two versions are passed to a SimpleDiffRequest, which is what gets displayed.

The contents to compare are:

  1. Selected code in an Editor
  2. Streamed response from an LLM

Problem

My current approach of calling myCustomDiffRequestPanel#setRequest on every new word received from the stream ends up interfering with the functionality of the displayed diff view.
More precisely: it’s impossible to scroll or select within the diff view because of all the calls to setRequest.

Question

Is there another approach to this?
For example, is it possible to update the content of the SimpleDiffRequest being displayed, instead of recreating it?

1 Like

Hi @jeremibg! You can use DocumentContent and modify underlying document when you receive new tokens from LLM.

Here is a super-naive demo:

val content1 = DiffContentFactory.getInstance().create(project, "Hello")
val content2 = DiffContentFactoryEx.getInstanceEx().documentContent(project, false).buildFromText("Text", false)
content2.putUserData(DiffUserDataKeysEx.FORCE_READ_ONLY, true)
BackgroundTaskUtil.submitTask(project) {
  (1..5).forEach {
    runInEdt {
      ApplicationManager.getApplication().runWriteAction {
        WriteCommandAction.runWriteCommandAction(project, Computable {
          content2.document.insertString(0, "Line $it\n")
        })
      }
    }
    Thread.sleep(1000)
  }
}
val request = SimpleDiffRequest("Demo", content1, content2, "Static", "Dynamic")
DiffManager.getInstance().showDiff(project, request)

output

1 Like

Hey @ilia.shulgin , thanks a lot for this suggestion!
This gets me closer, but it’s not behaving as I’d expect.
Would you mind addressing the following remarks?

Remarks

Mainly, having to go through runWriteCommandAction is rather annoying because it delays the content updates. Is there a way to avoid that?

Among other things, despite content2.document being updated in a timely fashion, the diff view itself takes a while before its content is updated.

I tried to find what I should call within the WriteAction, but to no avail. :frowning:

At first, I attempted to call this within the WriteAction:

  private fun onUpdatedUi() {
    myDiffRequestPanel.component.invalidate()
    myDiffRequestPanel.component.revalidate()
    myDiffRequestPanel.component.repaint()
  }

Then, I tried using myProcessor#updateRequest within the WriteAction but:

  • When using force = false parameter, it doesn’t solve the issue
  • When using force = true parameter, it does update the UI more often, but it seems very resource intensive and can eventually freeze the UI

I also tried calling myProcessor.setRequest().

Finally, I tried going through only runInEdt { ApplicationManager.getApplication().runWriteAction { ... } } (i.e. without calling WriteCommandAction#runWriteCommandAction since it’s unclear why that’s necessary anyways)… but that also didn’t resolve the issue.

Would you mind trying out your solution within an embedded Inlay Component (instead of using DiffManager.getInstance().showDiff(project, request)), with a much lower Thread#sleep value? This should help you see the performance issues.
Beyond the selection bug, there is also this issue that comes up when working with this within an Inlay Components.

More quirks

Moreover, for some reason, the very first time I try to select code in the displayed DiffRequestPanel, there is close to a 2-seconds delay before the selection actually happens.
That is the case even once there are no more updates coming in.

@jeremibg I’m sorry for the confusion here.

The problem with update might be related to update in dealying re-diff here - intellij-community/platform/diff-impl/src/com/intellij/diff/tools/util/base/DiffViewerBase.java at 468d9bd86c6065a31d78bfbcc9bc08c0af80d07c · JetBrains/intellij-community · GitHub

In short - if diff viewer is show and document is frequently updated, re-diff might not happen until switching tab. It can also explain “2 seconds delay and never updated” behavior.

I would suggest using batching tokens within some interval. I did an experiment with 500ms and it looks rather smooth.

1 Like

@ilia.shulgin Ah, very interesting!

Instead of delaying things, maybe it would be better to somehow get a grab of the DiffViewerBase and manually call rediff.
Would there be a suggested way to grab this reference?

@jeremibg well, I see 2 ways here:

  1. Register custom FrameDiffTool and do handling in createComponent. You can also add document updater there and rediff either explicitly right after inserting new tokens or by registering document listener.
  2. Doing kind of the same thing, but with DiffExtension.

You can pick an approach depending on whether you want to customize DiffViewer.