diff --git a/src/main/java/intellij_awk/AwkEnterAfterUnmatchedBraceHandler.java b/src/main/java/intellij_awk/AwkEnterAfterUnmatchedBraceHandler.java new file mode 100644 index 0000000..08d75e8 --- /dev/null +++ b/src/main/java/intellij_awk/AwkEnterAfterUnmatchedBraceHandler.java @@ -0,0 +1,65 @@ +package intellij_awk; + +import com.intellij.codeInsight.editorActions.enter.EnterAfterUnmatchedBraceHandler; +import com.intellij.openapi.util.Pair; +import com.intellij.psi.*; +import com.intellij.util.text.CharArrayUtil; +import intellij_awk.psi.AwkFile; +import intellij_awk.psi.AwkStatement; +import org.jetbrains.annotations.NotNull; + +public class AwkEnterAfterUnmatchedBraceHandler extends EnterAfterUnmatchedBraceHandler { + @Override + public boolean isApplicable(@NotNull PsiFile file, int caretOffset) { + return file instanceof AwkFile; + } + + @Override + protected Pair calculateOffsetToInsertClosingBrace( + @NotNull PsiFile file, @NotNull CharSequence text, int offset) { + + String rest = text.subSequence(offset, text.length()).toString(); + + // The idea is that to understand where to put the closing `}` we need to recognize the longest + // statement that goes right after `{`. Since the AWK PSI tree can be broken due to incomplete + // code, the only reliable way to do so is to (re-)parse the rest of the file (after `{`) and + // take the first statement. + String code = "BEGIN{" + rest + "}"; + + AwkFile awkFile = + (AwkFile) + PsiFileFactory.getInstance(file.getProject()) + .createFileFromText("dummy.awk", AwkFileType.INSTANCE, code); + + AwkStatement awkStatement = + (AwkStatement) AwkUtil.findFirstMatchedDeep(awkFile, AwkStatement.class::isInstance); + + if (awkStatement == null) { + return Pair.create(null, CharArrayUtil.shiftForwardUntil(text, offset, "\n")); + } + + // System.out.println("awkStatement="+awkStatement.getTextLength()); + // System.out.println("awkStatement="+awkStatement.getText()); + + int pos; + PsiElement possiblyComment = AwkUtil.getNextNotWhitespace(awkStatement); + if (possiblyComment instanceof PsiComment) { + // {print 123 # comment + // here ^_______^ is statement + pos = + offset + + (possiblyComment.getTextRange().getEndOffset() + - awkStatement.getTextRange().getStartOffset()); + } else { + // why not just `offset + awkStatement.getTextLength();` ? + // it's because due to the peculiarity of grammar the recognized `if` statement can have the + // final newline as part of it + pos = offset + (awkStatement.getText().trim()).length(); + } + + // System.out.println( + // "res=" + text.subSequence(0, pos) + "" + text.subSequence(pos, text.length())); + + return Pair.create(null, pos); + } +} diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 89699c3..617ef53 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -108,6 +108,9 @@ + + + diff --git a/src/test/java/intellij_awk/AwkBracesAndQuoteAutoCloseTests.java b/src/test/java/intellij_awk/AwkBracesAndQuoteAutoCloseTests.java index fb3e954..afd927a 100644 --- a/src/test/java/intellij_awk/AwkBracesAndQuoteAutoCloseTests.java +++ b/src/test/java/intellij_awk/AwkBracesAndQuoteAutoCloseTests.java @@ -73,6 +73,75 @@ public void testQuote1() { "BEGIN {\n print \"\"\n print \"\"}"); } + public void testEnterCurlyBrace1() { + doTest( + '\n', + "function f() {\n {print 123\n}", + "function f() {\n {\n print 123\n }\n}"); + } + + public void testEnterCurlyBrace1_1() { + doTest( + '\n', + "function f() {\n {print 123 # comment\n}", + "function f() {\n {\n print 123 # comment\n }\n}"); + } + + public void testEnterCurlyBrace2() { + doTest( + '\n', + "function f() {\n if(1){print 123\n}", + "function f() {\n if(1){\n print 123\n }\n}"); + } + + public void testEnterCurlyBrace3() { + doTest( + '\n', + "function f() {\n while(1){print 123\n}", + "function f() {\n while(1){\n print 123\n }\n}"); + } + + public void testEnterCurlyBrace4() { + doTest( + '\n', + "function f() {\n for (;;) {f(1)\n}", + "function f() {\n for (;;) {\n f(1)\n }\n}"); + } + public void testEnterCurlyBrace5() { + doTest('\n', "{print 123", "{\n print 123\n}"); + } + public void testEnterCurlyBrace6() { + doTest('\n', "BEGIN{print 123", "BEGIN{\n print 123\n}"); + } + public void testEnterCurlyBrace7() { + doTest('\n', "NF {print 123", "NF {\n print 123\n}"); + } + public void testEnterCurlyBrace8() { + doTest('\n', "function f()\n{print 123", "function f()\n{\n print 123\n}"); + } + public void testEnterCurlyBrace9_1() { + doTestEnterCurlyBraceComplex("if (2)"); + } + public void testEnterCurlyBrace9_2() { + doTestEnterCurlyBraceComplex("while (2)"); + } + public void testEnterCurlyBrace9_3() { + doTestEnterCurlyBraceComplex("for(;;)"); + } + public void testEnterCurlyBrace10() { + doTest( + '\n', + "function f() {\n if(1){\n}", + "function f() {\n if(1){\n \n }\n}"); + } + + private void doTestEnterCurlyBraceComplex(String opener) { + doTest( + '\n', + "function f() {\n if (1) {" + opener + " {\n print 123}\n}", + "function f() {\n if (1) {\n "+opener+" {\n print 123}\n }\n}"); + } + private void doTest(char brace, String code, String expectedCode) { myFixture.configureByText("a.awk", code); myFixture.type(brace);