User: joe Date: 12 Nov 26 01:07 Revision: ceb4f92ff2e14a4279da90e5b7df7c47577c2782 Summary: Try to block formatting only outside the Javadoc comment TeamCity URL: http://ci.mcdev.io:80/viewModification.html?tab=vcsModificationFiles&modId=10720&personal=false Index: src/main/kotlin/platform/fabric/annotater/javadoc/AnnotaterJavadocFormattingModelBuilder.kt =================================================================== --- src/main/kotlin/platform/fabric/annotater/javadoc/AnnotaterJavadocFormattingModelBuilder.kt (revision ceb4f92ff2e14a4279da90e5b7df7c47577c2782) +++ src/main/kotlin/platform/fabric/annotater/javadoc/AnnotaterJavadocFormattingModelBuilder.kt (revision ceb4f92ff2e14a4279da90e5b7df7c47577c2782) @@ -0,0 +1,85 @@ +/* + * Minecraft Development for IntelliJ + * + * https://mcdev.io/ + * + * Copyright (C) 2026 minecraft-dev + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published + * by the Free Software Foundation, version 3.0 only. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + + +package com.demonwav.mcdev.platform.fabric.annotater.javadoc + +import com.intellij.formatting.CustomFormattingModelBuilder +import com.intellij.formatting.FormattingContext +import com.intellij.formatting.FormattingModel +import com.intellij.formatting.FormattingModelBuilder +import com.intellij.formatting.FormattingModelEx +import com.intellij.lang.ASTNode +import com.intellij.lang.LanguageFormatting +import com.intellij.lang.java.JavaLanguage +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiFile + +/** + * Formats annotater Javadoc editors with Java's formatter, but only changes whitespace outside the guarded blocks, so + * that the read-only copied source code is left as it is. + */ +class AnnotaterJavadocFormattingModelBuilder : CustomFormattingModelBuilder { + override fun isEngagedToFormat(context: PsiElement) = AnnotaterJavadocVirtualFile.of(context.containingFile) != null + + override fun createModel(formattingContext: FormattingContext): FormattingModel = + EditableRangeFormattingModel(javaBuilder().createModel(formattingContext), formattingContext.containingFile) + + override fun getRangeAffectingIndent(file: PsiFile, offset: Int, elementAtOffset: ASTNode): TextRange? = + javaBuilder().getRangeAffectingIndent(file, offset, elementAtOffset) + + private fun javaBuilder(): FormattingModelBuilder = + LanguageFormatting.INSTANCE.allForLanguage(JavaLanguage.INSTANCE).first { it !is CustomFormattingModelBuilder } + + /** + * The formatter shifts the following offsets by how much the returned range's length changed, so returning the + * original range leaves the whitespace unchanged. + */ + private class EditableRangeFormattingModel( + private val delegate: FormattingModel, + file: PsiFile, + ) : FormattingModelEx, FormattingModel by delegate { + private val document = file.viewProvider.document + + private fun isEditable(range: TextRange) = document.getRangeGuard(range.startOffset, range.endOffset) == null + + override fun replaceWhiteSpace(textRange: TextRange, whiteSpace: String): TextRange = + if (isEditable(textRange)) { + delegate.replaceWhiteSpace(textRange, whiteSpace) + } else { + textRange + } + + override fun replaceWhiteSpace(textRange: TextRange, nodeAfter: ASTNode?, whiteSpace: String): TextRange = + when { + !isEditable(textRange) -> textRange + delegate is FormattingModelEx -> delegate.replaceWhiteSpace(textRange, nodeAfter, whiteSpace) + else -> delegate.replaceWhiteSpace(textRange, whiteSpace) + } + + override fun shiftIndentInsideRange(node: ASTNode?, range: TextRange, indent: Int): TextRange = + if (isEditable(range)) { + delegate.shiftIndentInsideRange(node, range, indent) + } else { + range + } + } +} Index: src/main/kotlin/platform/fabric/annotater/javadoc/AnnotaterJavadocFormattingRestriction.kt =================================================================== --- src/main/kotlin/platform/fabric/annotater/javadoc/AnnotaterJavadocFormattingRestriction.kt (revision 00e8c96bd7e8e8b7612ddee6b5f67b013ebac087) +++ src/main/kotlin/platform/fabric/annotater/javadoc/AnnotaterJavadocFormattingRestriction.kt (revision 00e8c96bd7e8e8b7612ddee6b5f67b013ebac087) @@ -1,32 +0,0 @@ -/* - * Minecraft Development for IntelliJ - * - * https://mcdev.io/ - * - * Copyright (C) 2026 minecraft-dev - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published - * by the Free Software Foundation, version 3.0 only. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ - -package com.demonwav.mcdev.platform.fabric.annotater.javadoc - -import com.intellij.lang.LanguageFormattingRestriction -import com.intellij.psi.PsiElement - -/** - * Prevents reformatting annotater Javadoc editors, which would change the read-only copied source code. - */ -class AnnotaterJavadocFormattingRestriction : LanguageFormattingRestriction { - override fun isFormatterAllowed(context: PsiElement) = - AnnotaterJavadocVirtualFile.of(context.containingFile) == null -} Index: src/main/resources/META-INF/plugin.xml =================================================================== --- src/main/resources/META-INF/plugin.xml (revision 00e8c96bd7e8e8b7612ddee6b5f67b013ebac087) +++ src/main/resources/META-INF/plugin.xml (revision ceb4f92ff2e14a4279da90e5b7df7c47577c2782) @@ -721,8 +721,8 @@ implementation="com.demonwav.mcdev.platform.fabric.annotater.javadoc.AnnotaterJavadocIntentionActionFilter"/> - + JAVA com.demonwav.mcdev.platform.fabric.annotater.javadoc.EditAnnotaterJavadocIntention Index: src/test/kotlin/platform/fabric/annotater/AnnotaterJavadocEditorTest.kt =================================================================== --- src/test/kotlin/platform/fabric/annotater/AnnotaterJavadocEditorTest.kt (revision 00e8c96bd7e8e8b7612ddee6b5f67b013ebac087) +++ src/test/kotlin/platform/fabric/annotater/AnnotaterJavadocEditorTest.kt (revision ceb4f92ff2e14a4279da90e5b7df7c47577c2782) @@ -51,10 +51,12 @@ import com.intellij.openapi.util.TextRange import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiClass +import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.PsiMember import com.intellij.psi.PsiMethod +import com.intellij.psi.codeStyle.CodeStyleManager import com.intellij.psi.codeStyle.modifier.TransientCodeStyleSettings import com.intellij.psi.javadoc.PsiDocComment import com.intellij.psi.search.GlobalSearchScope @@ -132,7 +134,7 @@ public class Level { public boolean addEntity(net.minecraft.util.Ticker ticker, String[] names, int count) { - return true; + return true ; } } """.trimIndent(), @@ -511,4 +513,57 @@ val hidden = AnnotaterJavadocRendering.generateDoc(clampMethod, null)!! assertFalse("Existing method docs" in hidden, hidden) } + + // TODO: uncomment in 2026.3 + // @Test + // @DisplayName("Inserting a Javadoc stub formats it") + // fun javadocStub() { + // val inlineEditor = startEditing(clampCopy(null, placeholderFor = clamp), clamp) + // try { + // fixture.openFileInEditor(inlineEditor.file) + // fixture.editor.caretModel.moveToOffset(fixture.editor.document.text.indexOf("/// ") + 4) + // withAnnotaterCodeStyle { fixture.completeBasic() } + // fixture.lookup.currentItem = fixture.lookupElements!!.single { it.lookupString == "Insert Javadoc Stub" } + // withAnnotaterCodeStyle { fixture.finishLookup(Lookup.NORMAL_SELECT_CHAR) } + // val text = fixture.editor.document.text + // assertTrue( + // text.contains( + // " /// \n /// @param value\n /// @param min\n /// @param max\n /// @return\n" + + // " public static int clamp(", + // ), + // text, + // ) + // } finally { + // Disposer.dispose(inlineEditor) + // } + // } + + @Test + @DisplayName("Formatting only changes the edited Javadoc") + fun formatting() { + val addEntity = AnnotaterJavadocTarget.MethodTarget( + "net/minecraft/world/level/Level", + "addEntity", + "(Lnet/minecraft/util/Ticker;[Ljava/lang/String;I)Z", + ) + val copy = AnnotaterJavadocSourceCopy.create(findMember("net.minecraft.world.level.Level", "addEntity"), null) { + if (it == addEntity) "Adds an entity." else null + }!! + val inlineEditor = startEditing(copy, addEntity) + val document = FileDocumentManager.getInstance().getDocument(inlineEditor.file)!! + try { + WriteCommandAction.runWriteCommandAction(project) { + val offset = document.text.indexOf("/// Adds an entity.") + "/// Adds an entity.".length + document.insertString(offset, "\n /// More.") + val documentManager = PsiDocumentManager.getInstance(project) + documentManager.commitDocument(document) + CodeStyleManager.getInstance(project).reformat(documentManager.getPsiFile(document)!!) -} + } + val text = document.text + assertTrue(text.contains(" /// Adds an entity.\n /// More.\n"), text) + assertTrue(text.contains("return true ;"), text) + } finally { + Disposer.dispose(inlineEditor) + } + } +}