Skip to content

Commit

Permalink
Add ErlangTerminatorSmartEnterProcessor
Browse files Browse the repository at this point in the history
Introduce ErlangTerminatorSmartEnterProcessor to handle automatic insertion of terminators during smart enter actions in Erlang code. Added comprehensive tests to ensure the processor handles various scenarios effectively.
  • Loading branch information
ignatov committed Sep 30, 2024
1 parent 1822c4a commit e525961
Show file tree
Hide file tree
Showing 3 changed files with 150 additions and 2 deletions.
1 change: 1 addition & 0 deletions resources/META-INF/ErlangPlugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@

<extendWordSelectionHandler implementation="org.intellij.erlang.editor.ErlangWordSelectioner" order="last"/>
<lang.smartEnterProcessor language="Erlang" implementationClass="org.intellij.erlang.editor.ErlangClausesSmartEnterProcessor" id="ErlangClause"/>
<lang.smartEnterProcessor language="Erlang" implementationClass="org.intellij.erlang.editor.ErlangTerminatorSmartEnterProcessor" id="ErlangTerm" order="after ErlangClause"/>
<lang.elementManipulator forClass="org.intellij.erlang.psi.impl.ErlangStringLiteralImpl"
implementationClass="org.intellij.erlang.editor.ErlangStringManipulator"/>
<lang.elementManipulator forClass="org.intellij.erlang.psi.impl.ErlangASTFactory$ErlangCommentImpl" order="first"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package org.intellij.erlang.editor;

import com.intellij.codeInsight.editorActions.smartEnter.SmartEnterProcessor;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.codeInsight.template.*;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.LogicalPosition;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.util.PsiTreeUtil;
import org.intellij.erlang.psi.ErlangClauseBody;
import org.jetbrains.annotations.NotNull;

public class ErlangTerminatorSmartEnterProcessor extends SmartEnterProcessor {
@Override
public boolean process(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile psiFile) {
int offset = editor.getCaretModel().getOffset();

// Check file boundaries
if (offset <= 0 || offset > psiFile.getTextLength()) {
return false;
}

PsiElement element = psiFile.findElementAt(offset);

// If element is null or whitespace, check the element at position -1
if (element == null || element instanceof PsiWhiteSpace) {
element = psiFile.findElementAt(offset - 1);
}

// If still null after checking previous element, return false
if (element == null) {
return false;
}

ErlangClauseBody clauseBody = PsiTreeUtil.getParentOfType(element, ErlangClauseBody.class, false);

if (clauseBody == null) return false;

TemplateManager templateManager = TemplateManager.getInstance(project);
Template template = createTerminatorTemplate(templateManager);

moveCaretToEndOfLine(editor);
templateManager.startTemplate(editor, template);

return true;
}

private static void moveCaretToEndOfLine(Editor editor) {
LogicalPosition logicalPosition = editor.getCaretModel().getLogicalPosition();
int lineNumber = logicalPosition.line;
int lineEndOffset = editor.getDocument().getLineEndOffset(lineNumber);
editor.getCaretModel().moveToOffset(lineEndOffset);
}

private static Template createTerminatorTemplate(TemplateManager templateManager) {
Template template = templateManager.createTemplate("", "");
template.addVariable("terminator", new TerminatorExpressionNode(), true);
return template;
}

private static class TerminatorExpressionNode extends Expression {
@Override
public Result calculateResult(ExpressionContext context) {
return new TextResult(",");
}

@Override
public Result calculateQuickResult(ExpressionContext context) {
return calculateResult(context);
}

@Override
public LookupElement[] calculateLookupItems(ExpressionContext context) {
return new LookupElement[]{
LookupElementBuilder.create(","),
LookupElementBuilder.create(";"),
LookupElementBuilder.create("."),
};
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,15 @@

import com.intellij.codeInsight.editorActions.smartEnter.SmartEnterProcessor;
import com.intellij.codeInsight.editorActions.smartEnter.SmartEnterProcessors;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupEx;
import com.intellij.codeInsight.lookup.LookupManager;
import com.intellij.codeInsight.template.impl.TemplateManagerImpl;
import com.intellij.openapi.command.WriteCommandAction;
import com.intellij.util.containers.ContainerUtil;

import java.util.List;

import org.intellij.erlang.ErlangLanguage;
import org.intellij.erlang.utils.ErlangLightPlatformCodeInsightFixtureTestCase;
import org.jetbrains.annotations.NotNull;
Expand Down Expand Up @@ -76,13 +84,67 @@ public void testCaseClause() {
_ -><caret>""");
}

private void doTest(@NotNull String before, @NotNull String after) {
public void testTerminatorProcessor() {
doTest(
"foo() -><caret>",
"foo() ->,",
",", ";", "."
);
}

public void testTerminatorProcessorWithExistingContent() {
doTest(
"foo() -> bar(<caret>)",
"foo() -> bar(),",
",", ";", "."
);
}

public void testTerminatorProcessorInCaseClause() {
doTest(
"""
case X of
1 -> one<caret>
end""",
"""
case X of
1 -> one
end"""
);
}

public void testTerminatorProcessorInFunction() {
doTest(
"""
foo() ->
bar()<caret>
baz().""",
"""
foo() ->
bar()<selection>,<caret></selection>
baz().""",
",", ";", "."
);
}

private void doTest(@NotNull String before, @NotNull String after, String... popupStrings) {
if (popupStrings.length > 0) {
TemplateManagerImpl.setTemplateTesting(getTestRootDisposable());
}
myFixture.configureByText("a.erl", before);
WriteCommandAction.writeCommandAction(getProject()).run(() -> {
for (SmartEnterProcessor processor : SmartEnterProcessors.INSTANCE.forKey(ErlangLanguage.INSTANCE)) {
processor.process(myFixture.getProject(), myFixture.getEditor(), myFixture.getFile());
if (processor.process(myFixture.getProject(), myFixture.getEditor(), myFixture.getFile())) break;
}
});

if (popupStrings.length > 0) {
LookupEx lookup = LookupManager.getActiveLookup(myFixture.getEditor());
assertNotNull("Lookup should be shown", lookup);
List<String> lookupStrings = ContainerUtil.map(lookup.getItems(), LookupElement::getLookupString);
assertContainsElements(lookupStrings, popupStrings);
}

myFixture.checkResult(after);
}
}

0 comments on commit e525961

Please sign in to comment.