How do I run a write action in FormattingService#formatElement/Ranges?
Answered
I`m currently trying to format javadocs by modifying the PSI.
Using a combination of Application#invokeLater
and WriteCommandAction#runWriteCommandAction
(or WriteAction#run
with CommandProcessor#executeCommand
) causes an infinite loop which formattes the same file over and over, if `optimize imports` is turned on.
Just using WriteCommandAction#runWriteCommandAction
throws
java.lang.IllegalStateException: Must not start write action from within read action in the other thread - deadlock is coming
And only using WriteAction#run
throws
com.intellij.openapi.diagnostic.RuntimeExceptionWithAttachments: Access is allowed from Event Dispatch Thread (EDT) only; see https://jb.gg/ij-platform-threading for details
Using Application#runWriteAction
as from https://plugins.jetbrains.com/docs/intellij/general-threading-rules.html throws
java.lang.IllegalStateException: Current thread: Thread[ApplicationImpl pooled thread 9,4,main]; expected: Thread[AWT-EventQueue-0,6,main]
which maybe states that write actions must be executed from event dispatch threads.
What should I do? I have no clue because all examples just use AsyncDocumentFormattingService
.
Please sign in to leave a comment.
Hi,
Please describe what exactly you are trying to implement and what APIs/extension points you use. Also, please share your code.
I'm trying to directly implement
FormattingService
with the extensionformattingService
(orcom.intellij.formattingService
) to customize javadoc block tag description alignment:Most sources say that to run write actions, I should use
Application#invokeLater
withWriteCommandAction#runWriteCommandAction
, so I used them as above informatElement(PsiElement, boolean)
. However, this causes an infinite loop which IntelliJ tries to format the same file over and over, when ‘Optimize imports’ option is enabled.I want to know if this is the right way to run write actions, and whether this behavior is a bug. Or should I always use
AsyncDocumentFormattingService
, which seems that I don't need to care about running write actions individually?Below is my enviornment, if needed.
I don't see what can be the reason for retriggering formatting. Maybe the reason is in the formatting logic itself. Is it possible that you can share it? Also, is the formatElement triggered for the same element each time? What element is it? A file? Could you please also share the stacktrace of your formatElement method call (when it is in the loop, not only the first call)?
This went too long, so I created a file collection here: https://gist.github.com/spacedvoid/f0d657193c91d64e502c4134af20b88b
The prefixed number and underscore is just to maintain the order; you can ignore them.
JavadocFormatter.java
is the code that i used to format the javadoc; it was mostly made with trial and error, and doesn't work. I was trying to traverse the javadoc PSI tree and format it.ReformatExtension.java
is the code that I used when testing Intellij, replacing the actual formatting process with a log. It still behaves the same.1st iteration, when I just clicked ‘Reformat Code’ and the specific options popup did not appear because of the breakpoint:
1st_iteration.stacktrace
And
idea.log
:1st_iteration.idea.log
Weirdly,
PsiElement#getText()
returned an empty string.2nd and later,
2nd_iteration.stacktrace
And
idea.log
at the 2nd iteration:2nd_iteration.idea.log
…and it had some issues while showing the whole file's text.
But strangly, after the 3rd iteration,
idea.log
started to only show this:And after resuming from the breakpoint, it sometimes went normal, without spamming the log anymore.
But it was not the case when I applied the plugin to my own IDE:
main.idea.log
And the last log entry kept repeating until I manually canceled the formatting. I just guess that it was a problem with debugging.
Here are some top of my head tips after reading through the IntelliJ Platform code. Have you questioned, if you really need a separate write action? From what I see in CodeStyleManagerImpl.java, the element has already been checked as writable. Additionally, have you looked at AbstractDocumentFormattingService.java? In lines 46-52 you see that basically only the text offset of the element is extracted, the range is reformatted and the changed element in this range is returned. And if you look at this test case, you'll see that it is a simple string replacement on the document level.
I'd say that you test gradually:
Remove the `invokeLater()` and leave only the log messages, but remove the logging of `element.getText()`. Then you can try out on which elements the methods are called (e.g. in comparison when you have selected text).
If that works without running into any infinite loop, you could try the same approach as in AbstractDocumentFormattingService. You look for the right PSI element and use its text range and the document to replace the string with the formatted version. Then committing the document and returning the element at this range.
I hope this helps investigate.
I don't know the entire behavior of the Intellij source code, but if you were talking about
CheckUtil#checkWritable
, it explicitly states that it checks whether the file/directory is not read-only, separate from whether I can write at the file with/without the lock.If you look at line 48, and follow into
PsiDocumentManagerBase#commitDocument
, go to line 343, go to#doCommit
, and go into the branches ofif(ApplicationManager.getApplication().isDispatchThread())
, it runs the commit in write actions.Since
Document
explicitly states that its content is a text file loaded into memory,AbstractDocumentFormattingService#formatDocument
is called to modify the text in memory, andAbstractDocumentFormattingService
callsPsiDocumentManagerBase#commitDocument
to update the actual file, if I'm correct. And since I'm not going to make a lexer and parse the entire file, I do need the PSI tree, but I can't find anything to get aPsiFile
from aDocument
exceptPsiDocumentManager#getPsiFile
, and it does not clearly state how I should modify it.And I think I made some mistakes while uploading the log files before, so I'll make an update about it.
Yes, you are right. The checkWritable was not what I thought it was after a quick glance. Could you try reworking your logic using my small debugging test example below and the example given here? My example works and I don't run into the issues you have. However, I'm using JavaPsiFacade and its PsiElementFactory to create the new PSI element for replacement.
Sorry for the late update, was busy for real-life problems.
I'm highly suspecting that this entire formatting system is messed up. I can't reproduce the behavior, even with my previous code. But your code does work with the following modifications:
But not in all situations, like the comment at the class itself. Also, the formatter always calls
#formatRanges
with the entire file range, even if I state that no features are available.Don't know what they've changed between 2023.3.5 and 2024.1, release notes were too long, didn't read, but I'll just use it for now. I'll make an update if this still happens. Thanks for answering my questions.
Updated to 2024.1, in case: