Integrate command-line-based static code analyzer as an external annotator efficiently?

I’m in the process of integrating a command-line-based static code analyzer. I’ve already integrated it as a global inspection for bulk inspection profile runs, and now I’m trying to integrate it for something closer to real-time. When I’ve done that in the past, I’ve used the externalAnnotator EP. However, this specific static code analyzer can take 5-6 seconds (or more) to execute for a given file, and it can be a bit of a CPU hog while doing its work, so even as an externalAnnotator, I’m finding that it can cause standard editing workflows to “stutter” a bit if the user types a few characters, pauses for think time such that the external annotator kicks in, and resumes typing.

Other tools that have integrated this specific static code analyzer only run it when an editor tab is first opened and when an editor tab’s file is saved. I can obviously do the same thing (and actually have already prototyped that), but it means that this particular integration works differently from pretty much all other real-time static code analyzers.

Has anyone else faced this problem before and figured out a good way to integrate it so that the analysis results are available in a more (near-)real-time fashion but executing the external process doesn’t interfere with actual IDE usage?

Can you tell why the stuttering happens?

I’m using ExternalAnnotator with cli-based analyzers, but they’re usually not using the CPU too much.
As far as I can tell, ExternalAnnotator is made for this scenario. Of course, highlighting would be delayed until the analyzer returned results, but the editor shouldn’t slow down while it’s analyzing.

If process execution or any long-running operation was executed inside collectInformation() or apply() and not in doAnnotate, as it’s intended, then I’d understand why the editor could be blocked or slowed down.
Only doAnnotate is executed outside a ReadAction and any long-running code should be executed there.

Are you calling ProgressManager.checkCanceled() in apply() ? It’s executed under progress in a ReadAction. Unnoticed or delayed cancellation (e.g. after typing) would slow down the editor, too.

If the analyzer is using so much CPU that it’s slowing down the machine, then an LSP-like approach to keep it running and sync edits may be better if it’s related to the init or startup of the analyzer, but it’s probably not possible without changes to the analyzer.

Just a few thoughts…

1 Like

Thanks, @jansorg. I’m a freaking idiot. I was doing the heavy-lifting in collectInformation() instead of doAnnotate(). Totally explains everything! Moving it to the right place makes all the difference. That’s what I get for working on this over the weekend while solo-parenting a sick kid. Thanks for the delicate nudge in the right direction!