kdoc-formatter
kdoc-formatter copied to clipboard
With latest IntelliJ & plugin version 1.5.6, getting IllegalStateException: Attempt to modify PSI for non-committed Document!
Ever since updating to the latest IntelliJ version, we've been getting the exception with root cause message Attempt to modify PSI for non-committed Document!
.
IntelliJ version info:
Plugin version:
1.5.6

Getting exception:
Got unexpected exception during formatting file:///Users/matthewadams/dev/artesion/site-microservice/site-microservice-support/src/main/kotlin/app/site/cqrs/read/canonical/PersistenceSupport.kt
java.util.concurrent.ExecutionException: java.lang.IllegalStateException: Attempt to modify PSI for non-committed Document!
at java.base/java.util.concurrent.FutureTask.report(FutureTask.java:122)
at java.base/java.util.concurrent.FutureTask.get(FutureTask.java:191)
at com.intellij.codeInsight.actions.AbstractLayoutCodeProcessor$ProcessingTask.checkStop(AbstractLayoutCodeProcessor.java:485)
at com.intellij.codeInsight.actions.AbstractLayoutCodeProcessor$ProcessingTask.performFileProcessing(AbstractLayoutCodeProcessor.java:479)
at com.intellij.codeInsight.actions.AbstractLayoutCodeProcessor$ProcessingTask.lambda$iteration$3(AbstractLayoutCodeProcessor.java:435)
at com.intellij.openapi.project.DumbService.withAlternativeResolveEnabled(DumbService.java:354)
at com.intellij.codeInsight.actions.AbstractLayoutCodeProcessor$ProcessingTask.iteration(AbstractLayoutCodeProcessor.java:435)
at com.intellij.codeInsight.actions.AbstractLayoutCodeProcessor$ProcessingTask.lambda$process$10(AbstractLayoutCodeProcessor.java:522)
at com.intellij.codeInsight.actions.FileRecursiveIterator.lambda$processAll$4(FileRecursiveIterator.java:69)
at com.intellij.openapi.roots.impl.FileIndexBase.lambda$toContentIteratorEx$0(FileIndexBase.java:82)
at com.intellij.openapi.roots.impl.FileIndexBase$1.visitFileEx(FileIndexBase.java:65)
at com.intellij.openapi.vfs.VfsUtilCore.visitChildrenRecursively(VfsUtilCore.java:295)
at com.intellij.openapi.vfs.VfsUtilCore.visitChildrenRecursively(VfsUtilCore.java:327)
at com.intellij.openapi.vfs.VfsUtilCore.visitChildrenRecursively(VfsUtilCore.java:327)
at com.intellij.openapi.vfs.VfsUtilCore.visitChildrenRecursively(VfsUtilCore.java:327)
at com.intellij.openapi.vfs.VfsUtilCore.visitChildrenRecursively(VfsUtilCore.java:327)
at com.intellij.openapi.vfs.VfsUtilCore.visitChildrenRecursively(VfsUtilCore.java:327)
at com.intellij.openapi.vfs.VfsUtilCore.visitChildrenRecursively(VfsUtilCore.java:327)
at com.intellij.openapi.vfs.VfsUtilCore.visitChildrenRecursively(VfsUtilCore.java:327)
at com.intellij.openapi.vfs.VfsUtilCore.visitChildrenRecursively(VfsUtilCore.java:327)
at com.intellij.openapi.roots.impl.FileIndexBase.iterateContentUnderDirectory(FileIndexBase.java:46)
at com.intellij.openapi.roots.impl.ProjectFileIndexImpl.iterateContentUnderDirectory(ProjectFileIndexImpl.java:35)
at com.intellij.openapi.roots.impl.FileIndexBase.iterateContentUnderDirectory(FileIndexBase.java:87)
at com.intellij.openapi.roots.impl.ProjectFileIndexImpl.iterateContentUnderDirectory(ProjectFileIndexImpl.java:35)
at com.intellij.codeInsight.actions.FileRecursiveIterator.processAll(FileRecursiveIterator.java:64)
at com.intellij.codeInsight.actions.AbstractLayoutCodeProcessor$ProcessingTask.process(AbstractLayoutCodeProcessor.java:520)
at com.intellij.codeInsight.actions.AbstractLayoutCodeProcessor.processFilesUnderProgress(AbstractLayoutCodeProcessor.java:371)
at com.intellij.codeInsight.actions.AbstractLayoutCodeProcessor.lambda$runProcessFiles$1(AbstractLayoutCodeProcessor.java:327)
at com.intellij.openapi.progress.impl.CoreProgressManager$1.run(CoreProgressManager.java:252)
at com.intellij.openapi.progress.impl.CoreProgressManager.startTask(CoreProgressManager.java:429)
at com.intellij.openapi.progress.impl.ProgressManagerImpl.startTask(ProgressManagerImpl.java:114)
at com.intellij.openapi.progress.impl.CoreProgressManager.lambda$runProcessWithProgressSynchronously$9(CoreProgressManager.java:513)
at com.intellij.openapi.progress.impl.ProgressRunner.lambda$new$0(ProgressRunner.java:84)
at com.intellij.openapi.progress.impl.ProgressRunner.lambda$submit$3(ProgressRunner.java:252)
at com.intellij.openapi.progress.impl.CoreProgressManager.lambda$runProcess$2(CoreProgressManager.java:186)
at com.intellij.openapi.progress.impl.CoreProgressManager.lambda$executeProcessUnderProgress$13(CoreProgressManager.java:604)
at com.intellij.openapi.progress.impl.CoreProgressManager.registerIndicatorAndRun(CoreProgressManager.java:679)
at com.intellij.openapi.progress.impl.CoreProgressManager.computeUnderProgress(CoreProgressManager.java:635)
at com.intellij.openapi.progress.impl.CoreProgressManager.executeProcessUnderProgress(CoreProgressManager.java:603)
at com.intellij.openapi.progress.impl.ProgressManagerImpl.executeProcessUnderProgress(ProgressManagerImpl.java:60)
at com.intellij.openapi.progress.impl.CoreProgressManager.runProcess(CoreProgressManager.java:173)
at com.intellij.openapi.progress.impl.ProgressRunner.lambda$submit$4(ProgressRunner.java:252)
at java.base/java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1768)
at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
at java.base/java.util.concurrent.Executors$PrivilegedThreadFactory$1$1.run(Executors.java:702)
at java.base/java.util.concurrent.Executors$PrivilegedThreadFactory$1$1.run(Executors.java:699)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
at java.base/java.util.concurrent.Executors$PrivilegedThreadFactory$1.run(Executors.java:699)
at java.base/java.lang.Thread.run(Thread.java:833)
Caused by: java.lang.IllegalStateException: Attempt to modify PSI for non-committed Document!
at com.intellij.pom.core.impl.PomModelImpl.startTransaction(PomModelImpl.java:266)
at com.intellij.pom.core.impl.PomModelImpl.lambda$runTransaction$2(PomModelImpl.java:96)
at com.intellij.openapi.progress.impl.CoreProgressManager.lambda$executeNonCancelableSection$3(CoreProgressManager.java:222)
at com.intellij.openapi.progress.impl.CoreProgressManager.registerIndicatorAndRun(CoreProgressManager.java:679)
at com.intellij.openapi.progress.impl.CoreProgressManager.computeUnderProgress(CoreProgressManager.java:635)
at com.intellij.openapi.progress.impl.CoreProgressManager.lambda$computeInNonCancelableSection$4(CoreProgressManager.java:230)
at com.intellij.openapi.progress.Cancellation.computeInNonCancelableSection(Cancellation.java:99)
at com.intellij.openapi.progress.impl.CoreProgressManager.computeInNonCancelableSection(CoreProgressManager.java:230)
at com.intellij.openapi.progress.impl.CoreProgressManager.executeNonCancelableSection(CoreProgressManager.java:221)
at com.intellij.pom.core.impl.PomModelImpl.runTransaction(PomModelImpl.java:93)
at com.intellij.psi.impl.source.tree.ChangeUtil.prepareAndRunChangeAction(ChangeUtil.java:142)
at com.intellij.psi.impl.source.tree.CompositeElement.replaceChild(CompositeElement.java:625)
at com.intellij.psi.impl.source.codeStyle.CodeEditUtil.replaceChild(CodeEditUtil.java:162)
at com.intellij.psi.impl.source.tree.CompositeElement.replaceChildInternal(CompositeElement.java:455)
at com.intellij.psi.impl.source.tree.SharedImplUtil.doReplace(SharedImplUtil.java:196)
at com.intellij.psi.impl.source.tree.LazyParseablePsiElement.replace(LazyParseablePsiElement.java:232)
at kdocformatter.plugin.KDocPostFormatProcessor.processElement(KDocPostFormatProcessor.kt:30)
at kdocformatter.plugin.KDocPostFormatProcessor.processText(KDocPostFormatProcessor.kt:44)
at com.intellij.psi.impl.source.codeStyle.CoreCodeStyleUtil.postProcessText(CoreCodeStyleUtil.java:104)
at com.intellij.formatting.service.CoreFormattingService.lambda$formatRanges$0(CoreFormattingService.java:64)
at com.intellij.psi.impl.source.codeStyle.CoreCodeStyleUtil.postProcessRanges(CoreCodeStyleUtil.java:94)
at com.intellij.formatting.service.CoreFormattingService.formatRanges(CoreFormattingService.java:64)
at com.intellij.formatting.service.FormattingServiceUtil.formatRanges(FormattingServiceUtil.java:93)
at com.intellij.psi.impl.source.codeStyle.CodeStyleManagerImpl.reformatText(CodeStyleManagerImpl.java:167)
at com.intellij.psi.impl.source.codeStyle.CodeStyleManagerImpl.reformatText(CodeStyleManagerImpl.java:132)
at com.intellij.psi.impl.source.codeStyle.CodeStyleManagerImpl.reformatText(CodeStyleManagerImpl.java:114)
at com.google.googlejavaformat.intellij.CodeStyleManagerDecorator.reformatText(CodeStyleManagerDecorator.java:96)
at com.google.googlejavaformat.intellij.GoogleJavaFormatCodeStyleManager.reformatText(GoogleJavaFormatCodeStyleManager.java:71)
at com.intellij.codeInsight.actions.ReformatCodeProcessor.lambda$doReformat$5(ReformatCodeProcessor.java:196)
at com.intellij.util.SlowOperations.allowSlowOperations(SlowOperations.java:167)
at com.intellij.codeInsight.actions.ReformatCodeProcessor.lambda$doReformat$6(ReformatCodeProcessor.java:186)
at com.intellij.openapi.editor.ex.util.EditorScrollingPositionKeeper.perform(EditorScrollingPositionKeeper.java:100)
at com.intellij.codeInsight.actions.ReformatCodeProcessor.doReformat(ReformatCodeProcessor.java:186)
at com.intellij.codeInsight.actions.ReformatCodeProcessor.lambda$prepareTask$2(ReformatCodeProcessor.java:134)
at com.intellij.application.options.CodeStyle.doWithTemporarySettings(CodeStyle.java:338)
at com.intellij.codeInsight.actions.ReformatCodeProcessor.lambda$prepareTask$3(ReformatCodeProcessor.java:130)
at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
at com.intellij.codeInsight.actions.AbstractLayoutCodeProcessor$ProcessingTask.lambda$performFileProcessing$7(AbstractLayoutCodeProcessor.java:476)
at com.intellij.openapi.command.WriteCommandAction$BuilderImpl.lambda$doRunWriteCommandAction$1(WriteCommandAction.java:150)
at com.intellij.openapi.application.impl.ApplicationImpl.runWriteAction(ApplicationImpl.java:980)
at com.intellij.openapi.command.WriteCommandAction$BuilderImpl.lambda$doRunWriteCommandAction$2(WriteCommandAction.java:148)
at com.intellij.openapi.command.impl.CoreCommandProcessor.executeCommand(CoreCommandProcessor.java:219)
at com.intellij.openapi.command.impl.CoreCommandProcessor.executeCommand(CoreCommandProcessor.java:184)
at com.intellij.openapi.command.WriteCommandAction$BuilderImpl.doRunWriteCommandAction(WriteCommandAction.java:157)
at com.intellij.openapi.command.WriteCommandAction$BuilderImpl.run(WriteCommandAction.java:124)
at com.intellij.codeInsight.actions.AbstractLayoutCodeProcessor$ProcessingTask.lambda$performFileProcessing$8(AbstractLayoutCodeProcessor.java:476)
at com.intellij.openapi.application.TransactionGuardImpl.runWithWritingAllowed(TransactionGuardImpl.java:209)
at com.intellij.openapi.application.TransactionGuardImpl.access$100(TransactionGuardImpl.java:21)
at com.intellij.openapi.application.TransactionGuardImpl$1.run(TransactionGuardImpl.java:191)
at com.intellij.openapi.application.impl.ApplicationImpl.runIntendedWriteActionOnCurrentThread(ApplicationImpl.java:838)
at com.intellij.openapi.application.impl.ApplicationImpl$3.run(ApplicationImpl.java:454)
at com.intellij.openapi.application.impl.LaterInvocator$1.run(LaterInvocator.java:97)
at com.intellij.openapi.application.impl.FlushQueue.doRun(FlushQueue.java:74)
at com.intellij.openapi.application.impl.FlushQueue.runNextEvent(FlushQueue.java:114)
at com.intellij.openapi.application.impl.FlushQueue.flushNow(FlushQueue.java:36)
at java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:318)
at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:779)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:730)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:724)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:749)
at com.intellij.ide.IdeEventQueue.defaultDispatchEvent(IdeEventQueue.java:909)
at com.intellij.ide.IdeEventQueue._dispatchEvent(IdeEventQueue.java:756)
at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$5(IdeEventQueue.java:437)
at com.intellij.openapi.progress.impl.CoreProgressManager.computePrioritized(CoreProgressManager.java:787)
at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$6(IdeEventQueue.java:436)
at com.intellij.openapi.application.TransactionGuardImpl.performActivity(TransactionGuardImpl.java:113)
at com.intellij.ide.IdeEventQueue.performActivity(IdeEventQueue.java:615)
at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$7(IdeEventQueue.java:434)
at com.intellij.openapi.application.impl.ApplicationImpl.runIntendedWriteActionOnCurrentThread(ApplicationImpl.java:838)
at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.java:480)
at com.intellij.ide.IdeEventQueue.pumpEventsForHierarchy(IdeEventQueue.java:956)
at com.intellij.openapi.progress.util.ProgressWindow.lambda$startBlocking$4(ProgressWindow.java:215)
at com.intellij.openapi.application.impl.ApplicationImpl.runUnlockingIntendedWrite(ApplicationImpl.java:864)
at com.intellij.openapi.progress.util.ProgressWindow.lambda$startBlocking$5(ProgressWindow.java:210)
at com.intellij.openapi.progress.util.ProgressWindow.executeInModalContext(ProgressWindow.java:191)
at com.intellij.openapi.progress.util.ProgressWindow.startBlocking(ProgressWindow.java:208)
at com.intellij.openapi.progress.impl.ProgressRunner.lambda$execFromEDT$6(ProgressRunner.java:329)
at java.base/java.util.concurrent.CompletableFuture.uniAcceptNow(CompletableFuture.java:757)
at java.base/java.util.concurrent.CompletableFuture.uniAcceptStage(CompletableFuture.java:735)
at java.base/java.util.concurrent.CompletableFuture.thenAccept(CompletableFuture.java:2182)
at com.intellij.openapi.progress.impl.ProgressRunner.execFromEDT(ProgressRunner.java:326)
at com.intellij.openapi.progress.impl.ProgressRunner.submit(ProgressRunner.java:267)
at com.intellij.openapi.progress.impl.ProgressRunner.submitAndGet(ProgressRunner.java:193)
at com.intellij.openapi.application.impl.ApplicationImpl.runProcessWithProgressSynchronously(ApplicationImpl.java:420)
at com.intellij.openapi.progress.impl.CoreProgressManager.runProcessWithProgressSynchronously(CoreProgressManager.java:524)
at com.intellij.openapi.progress.impl.ProgressManagerImpl.runProcessWithProgressSynchronously(ProgressManagerImpl.java:85)
at com.intellij.openapi.progress.impl.CoreProgressManager.runProcessWithProgressSynchronously(CoreProgressManager.java:248)
at com.intellij.codeInsight.actions.AbstractLayoutCodeProcessor.runProcessFiles(AbstractLayoutCodeProcessor.java:325)
at com.intellij.codeInsight.actions.AbstractLayoutCodeProcessor.run(AbstractLayoutCodeProcessor.java:226)
at com.intellij.codeInsight.actions.ReformatCodeAction.reformatDirectory(ReformatCodeAction.java:188)
at com.intellij.codeInsight.actions.ReformatCodeAction.actionPerformed(ReformatCodeAction.java:118)
at com.intellij.openapi.actionSystem.ex.ActionUtil.doPerformActionOrShowPopup(ActionUtil.java:327)
at com.intellij.openapi.keymap.impl.ActionProcessor.performAction(ActionProcessor.java:47)
at com.intellij.openapi.keymap.impl.IdeKeyEventDispatcher$1.performAction(IdeKeyEventDispatcher.java:584)
at com.intellij.openapi.keymap.impl.IdeKeyEventDispatcher.lambda$doPerformActionInner$9(IdeKeyEventDispatcher.java:706)
at com.intellij.openapi.application.TransactionGuardImpl.performActivity(TransactionGuardImpl.java:105)
at com.intellij.openapi.application.TransactionGuardImpl.performUserActivity(TransactionGuardImpl.java:94)
at com.intellij.openapi.keymap.impl.IdeKeyEventDispatcher.lambda$doPerformActionInner$10(IdeKeyEventDispatcher.java:706)
at com.intellij.openapi.actionSystem.ex.ActionUtil.performDumbAwareWithCallbacks(ActionUtil.java:350)
at com.intellij.openapi.keymap.impl.IdeKeyEventDispatcher.doPerformActionInner(IdeKeyEventDispatcher.java:703)
at com.intellij.openapi.keymap.impl.IdeKeyEventDispatcher.processAction(IdeKeyEventDispatcher.java:647)
at com.intellij.openapi.keymap.impl.IdeKeyEventDispatcher.processAction(IdeKeyEventDispatcher.java:595)
at com.intellij.openapi.keymap.impl.IdeKeyEventDispatcher.processActionOrWaitSecondStroke(IdeKeyEventDispatcher.java:478)
at com.intellij.openapi.keymap.impl.IdeKeyEventDispatcher.inInitState(IdeKeyEventDispatcher.java:467)
at com.intellij.openapi.keymap.impl.IdeKeyEventDispatcher.dispatchKeyEvent(IdeKeyEventDispatcher.java:225)
at com.intellij.ide.IdeEventQueue.dispatchKeyEvent(IdeEventQueue.java:815)
at com.intellij.ide.IdeEventQueue._dispatchEvent(IdeEventQueue.java:750)
at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$5(IdeEventQueue.java:437)
at com.intellij.openapi.progress.impl.CoreProgressManager.computePrioritized(CoreProgressManager.java:787)
at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$6(IdeEventQueue.java:436)
at com.intellij.openapi.application.TransactionGuardImpl.performActivity(TransactionGuardImpl.java:113)
at com.intellij.ide.IdeEventQueue.performActivity(IdeEventQueue.java:615)
at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$7(IdeEventQueue.java:434)
at com.intellij.openapi.application.impl.ApplicationImpl.runIntendedWriteActionOnCurrentThread(ApplicationImpl.java:838)
at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.java:480)
at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:207)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:128)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:117)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:113)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:105)
at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:92)
@tnorbye Do you think you might be able to have a look at this, please?
Sorry, I somehow missed this bug when it was filed in March.
I just took a look -- and I can't repro this, and I've never seen it happen. And from the thread dump, this is the formatting processor which is called from IntelliJ's own formatting hook. The formatting hook does use PSI to manipulate the document -- and the error message is "Attempt to modify PSI for non-committed Document!". So in theory, the fix would be for the plugin to call PsiDocumentManager.getInstance(project).commitDocument(document)
before performing the PSI manipulation.
But -- this should be done before formatting begins, not in the callback for an individual PSI element -- so I would think the formatting action itself should do it, not a formatting processor. And I looked at a handful of other formatting processors; all of them are doing PSI manipulation, and none of them are trying to do document manipulation.
Do you have any other third party plugins installed? I wonder if one of them is doing document manipulation without committing it back to PSI, leaving the document dirty and triggering the above error on the next PSI manipulation.
Sorry, took me a while to get back to this. It looks like the culprit is the ktlint plugin. I'll see about filing an issue there & referencing this one.
Great, thanks for chasing this down!
Here's the issue I filed: https://github.com/nbadal/ktlint-intellij-plugin/issues/323
Hi Tor,
I have been investigating this issue from the ktlint plugin perspective. Your suggestion about using PsiDocumentManager.getInstance(project).commitDocument(document)
is indeed key to solving the issue. But I do believe that it needs to be fixed in the KDoc Formatter. I have created a reproducable scenario with two dummy formatters. One formatter modifies the PSI like KDoc Formatter does. The other formatter modifies the document by replacing the entire text of the document like the Ktlint plugin does.
The extensions are defined like this:
<extensions defaultExtensionNs="com.intellij">
<postFormatProcessor implementation="plugins.DummyKtlintPostFormatProcessor" />
<postFormatProcessor implementation="plugins.DummyKdocPostFormatProcessor1" />
</extensions>
The order of the postFormatterProcessors
is important for reproducing the problem. Only when DummyKdocPostFormatProcessor1
runs after DummyKtlintPostFormatProcessor
the problem with Attempt to modify PSI for non-committed Document!
occurs. In real live it is out of control of the developer which of the plugins in invoked before the others. So it is also important to test the scenario with the plugins in different order.
The DummyKtlintPostFormatProcessor
prepends the existing content of the document with a comment // Formatted with DummyKtlintPostFormatProcessor
and is defined as follows:
package plugins
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.codeStyle.CodeStyleSettings
import com.intellij.psi.impl.source.codeStyle.PostFormatProcessor
const val KTLINT_FORMATTER_COMMENT = "// Formatted with DummyKtlintPostFormatProcessor"
/**
* Like the ktlint-intellij-plugin this post format processor replaces the entire content of the document instead of
* manipulating the PSI.
*/
class DummyKtlintPostFormatProcessor : PostFormatProcessor {
val className = this::class.simpleName
override fun processElement(
source: PsiElement,
settings: CodeStyleSettings,
) = source
override fun processText(
psiFile: PsiFile,
rangeToReformat: TextRange,
settings: CodeStyleSettings,
): TextRange {
val document = psiFile.viewProvider.document
PsiDocumentManager
.getInstance(psiFile.project)
.doPostponedOperationsAndUnblockDocument(document)
WriteCommandAction.runWriteCommandAction(psiFile.project) {
println("$className start")
document.setText("$KTLINT_FORMATTER_COMMENT\n${psiFile.text}")
println("$className finished")
}
return rangeToReformat
}
}
The DummyKdocPostFormatProcessor1
looks for a comment with text // Formatted with DummyKtlintPostFormatProcessor
and alters that text. It is defined as follows and is comparable with current implementation of KDoc Formatter:
package plugins
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.codeStyle.CodeStyleSettings
import com.intellij.psi.impl.source.codeStyle.PostFormatProcessor
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.psi.KtPsiFactory
/**
* Like the current Kdoc Formatter this post format processor manipulates the PSI, but it does not commit document
* changes and does not use the WriteCommandAction while manipulating the PSI. As a result this post formatter throws an
* exception if it runs after [DummyKtlintPostFormatProcessor].
*/
class DummyKdocPostFormatProcessor1 : PostFormatProcessor {
val className = this::class.simpleName
override fun processElement(
source: PsiElement,
settings: CodeStyleSettings,
): PsiElement {
return if (source is PsiComment && source.text == KTLINT_FORMATTER_COMMENT) {
val newComment =
KtPsiFactory(source.project)
.createComment("$KTLINT_FORMATTER_COMMENT (altered by $className)")
source.replace(newComment)
} else {
source
}
}
override fun processText(
psiFile: PsiFile,
rangeToReformat: TextRange,
settings: CodeStyleSettings,
): TextRange {
println("$className start")
// Format all top-level comments in this range
for (element in PsiTreeUtil.findChildrenOfType(psiFile, PsiComment::class.java)) {
if (rangeToReformat.intersects(element.textRange)) {
processElement(element, settings)
}
}
println("$className start")
return rangeToReformat
}
}
Now run the plugin with setup above and start with a simple document containing text:
class Foo
After the first invocation of reformatting the document, it will be changed to:
// Formatted with DummyKtlintPostFormatProcessor
class Foo
At the second invocation of reformatting the document both the post formatters processors will be run and the Attempt to modify PSI for non-committed Document!
exception is thrown.
When DummyKdocPostFormatProcessor1
runs after DummyKtlintPostFormatProcessor
it has to ensure that the document does not contain any uncommitted changes before starting to change the PSI. Next to committing the document, it should also execute the PSI change within a WriteActionCommand. Replace DummyKdocPostFormatProcessor1
with DummyKdocPostFormatProcessor2
below:
package plugins
import com.intellij.openapi.command.WriteCommandAction
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiComment
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.intellij.psi.codeStyle.CodeStyleSettings
import com.intellij.psi.impl.source.codeStyle.PostFormatProcessor
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.kotlin.psi.KtPsiFactory
/**
* Like the current Kdoc Formatter this post format processor manipulates the PSI, but it does only so after committing
* the document changes and uses the WriteCommandAction while manipulating the PSI. As a result this post formatter
* no longer throws an exception if it runs after [DummyKtlintPostFormatProcessor].
*/
class DummyKdocPostFormatProcessor2 : PostFormatProcessor {
val className = this::class.simpleName
override fun processElement(
source: PsiElement,
settings: CodeStyleSettings,
): PsiElement {
return if (source is PsiComment && source.text == KTLINT_FORMATTER_COMMENT) {
val newComment =
KtPsiFactory(source.project)
.createComment("$KTLINT_FORMATTER_COMMENT (altered by $className)")
source.replace(newComment)
} else {
source
}
}
override fun processText(
psiFile: PsiFile,
rangeToReformat: TextRange,
settings: CodeStyleSettings,
): TextRange {
PsiDocumentManager
.getInstance(psiFile.project)
.commitDocument(psiFile.viewProvider.document)
WriteCommandAction.runWriteCommandAction(psiFile.project) {
println("$className start")
// Format all top-level comments in this range
for (element in PsiTreeUtil.findChildrenOfType(psiFile, PsiComment::class.java)) {
if (rangeToReformat.intersects(element.textRange)) {
processElement(element, settings)
}
}
println("$className finished")
}
return rangeToReformat
}
}
Rerun the plugin and start again with file:
class Foo
After the first run of reformatting the document, it is changed to:
// Formatted with DummyKtlintPostFormatProcessor (altered by DummyKdocPostFormatProcessor2)
class Foo
Btw, ktlint plugin has been changed as well to prevent problems in case the KDoc Formatter runs before Ktlint formatter.
@matthewadams Did you have any chance to look into this?
No, apologies still haven't had a chance -- very busy these days and it sounds like nobody is actually blocked since it sounds like you changed ktlint to work around this?
Our team is encountering this issue in a large multi-module project, which is quite frustrating. We've tried the latest versions of ktlint, but to no avail. If I can find some spare time this weekend, I plan to examine the reproduction provided by @paul-dingemans to gain a better understanding of the problem. Maybe I'll even submit a PR if I'm feeling real frisky.
I suspect this issue does not have more traction as this only happens if you have two Intellij formatters enabled and used in the same workspace. Most projects, including many of the other Kotlin projects we have internally, use a build system plugin or CI for linting/format corrections.