enter handler for regexes.

This commit is contained in:
Maxim.Medvedev
2012-02-06 13:20:32 +04:00
parent fc939bb1eb
commit aace22a3d5
15 changed files with 230 additions and 54 deletions
@@ -22,10 +22,10 @@ import com.intellij.psi.PsiElement;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.tree.TokenSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.plugins.groovy.GroovyFileType;
import org.jetbrains.plugins.groovy.formatter.GroovyBlock;
import org.jetbrains.plugins.groovy.lang.editor.actions.GroovyEditorActionUtil;
import org.jetbrains.plugins.groovy.lang.groovydoc.psi.api.GrDocComment;
import org.jetbrains.plugins.groovy.lang.groovydoc.psi.api.GrDocTag;
import org.jetbrains.plugins.groovy.lang.parser.GroovyElementTypes;
@@ -50,6 +50,7 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMe
*/
public abstract class GroovyIndentProcessor implements GroovyElementTypes {
public static final int GDOC_COMMENT_INDENT = 1;
private static TokenSet GSTRING_TOKENS_INNER = TokenSet.create(mGSTRING_BEGIN,mGSTRING_CONTENT,mGSTRING_END,mDOLLAR);
/**
* Calculates indent, based on code style, between parent block and child node
@@ -72,14 +73,12 @@ public abstract class GroovyIndentProcessor implements GroovyElementTypes {
return Indent.getContinuationIndent();
}
if (GroovyEditorActionUtil.GSTRING_TOKENS_INNER.contains(child.getElementType()) &&
mGSTRING_BEGIN != child.getElementType()) {
if (GSTRING_TOKENS_INNER.contains(child.getElementType()) && mGSTRING_BEGIN != child.getElementType()) {
return Indent.getAbsoluteNoneIndent();
}
if (psiParent instanceof GrListOrMap) {
if (mLBRACK.equals(child.getElementType()) ||
mRBRACK.equals(child.getElementType())) {
if (mLBRACK.equals(child.getElementType()) || mRBRACK.equals(child.getElementType())) {
return Indent.getNoneIndent();
} else {
return Indent.getContinuationWithoutFirstIndent();
@@ -23,12 +23,13 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.intellij.psi.tree.TokenSet;
import org.jetbrains.plugins.groovy.GroovyFileType;
import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrString;
import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mGSTRING_LITERAL;
import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mSTRING_LITERAL;
/**
* @author ilyas
*/
@@ -66,7 +67,7 @@ public class GroovyEditorActionUtil {
if (child != null && child.getNode() != null) {
ASTNode node = child.getNode();
assert node != null;
return node.getElementType() == GroovyTokenTypes.mSTRING_LITERAL;
return node.getElementType() == mSTRING_LITERAL;
}
return false;
}
@@ -76,23 +77,8 @@ public class GroovyEditorActionUtil {
if (child != null && child.getNode() != null) {
ASTNode node = child.getNode();
assert node != null;
return node.getElementType() == GroovyTokenTypes.mGSTRING_LITERAL;
return node.getElementType() == mGSTRING_LITERAL;
}
return false;
}
public static TokenSet GSTRING_TOKENS = TokenSet.create(
GroovyTokenTypes.mGSTRING_BEGIN,
GroovyTokenTypes.mGSTRING_CONTENT,
GroovyTokenTypes.mGSTRING_END,
GroovyTokenTypes.mGSTRING_LITERAL,
GroovyTokenTypes.mDOLLAR
);
public static TokenSet GSTRING_TOKENS_INNER = TokenSet.create(
GroovyTokenTypes.mGSTRING_BEGIN,
GroovyTokenTypes.mGSTRING_CONTENT,
GroovyTokenTypes.mGSTRING_END,
GroovyTokenTypes.mDOLLAR
);
}
@@ -50,13 +50,64 @@ import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.GrRefere
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrLiteral;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrStringInjection;
import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.*;
import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mCLOSABLE_BLOCK_OP;
import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mDOLLAR;
import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mDOLLAR_SLASH_REGEX_BEGIN;
import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mDOLLAR_SLASH_REGEX_CONTENT;
import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mDOLLAR_SLASH_REGEX_END;
import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mDOLLAR_SLASH_REGEX_LITERAL;
import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mGSTRING_BEGIN;
import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mGSTRING_CONTENT;
import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mGSTRING_END;
import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mGSTRING_LITERAL;
import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mIDENT;
import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mLBRACK;
import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mLCURLY;
import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mNLS;
import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mRBRACK;
import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mRCURLY;
import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mREGEX_BEGIN;
import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mREGEX_CONTENT;
import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mREGEX_END;
import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mREGEX_LITERAL;
import static org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes.mSTRING_LITERAL;
import static org.jetbrains.plugins.groovy.lang.parser.GroovyElementTypes.*;
/**
* @author ilyas
*/
public class GroovyEnterHandler extends EnterHandlerDelegateAdapter {
private static final TokenSet GSTRING_TOKENS = TokenSet.create(mGSTRING_BEGIN, mGSTRING_CONTENT, mGSTRING_END, mGSTRING_LITERAL);
private static final TokenSet REGEX_TOKENS = TokenSet.create(mREGEX_BEGIN, mREGEX_CONTENT, mREGEX_END, mDOLLAR_SLASH_REGEX_BEGIN,
mDOLLAR_SLASH_REGEX_CONTENT, mDOLLAR_SLASH_REGEX_END);
private static final TokenSet AFTER_DOLLAR = TokenSet.create(mLCURLY, mIDENT, mGSTRING_CONTENT, mDOLLAR, mGSTRING_END, mREGEX_CONTENT,
mDOLLAR_SLASH_REGEX_CONTENT, mREGEX_END, mDOLLAR_SLASH_REGEX_END);
private static final TokenSet ALL_STRINGS = TokenSet.create(mSTRING_LITERAL, mGSTRING_LITERAL, mGSTRING_BEGIN, mGSTRING_END,
mGSTRING_CONTENT, mRCURLY, mIDENT, mDOLLAR, mREGEX_BEGIN, mREGEX_CONTENT,
mREGEX_END, mDOLLAR_SLASH_REGEX_BEGIN, mDOLLAR_SLASH_REGEX_CONTENT,
mDOLLAR_SLASH_REGEX_END, mREGEX_LITERAL, mDOLLAR_SLASH_REGEX_LITERAL);
private static final TokenSet BEFORE_DOLLAR =TokenSet.create(mGSTRING_BEGIN, mGSTRING_CONTENT, mREGEX_BEGIN, mREGEX_CONTENT,
mDOLLAR_SLASH_REGEX_BEGIN, mDOLLAR_SLASH_REGEX_CONTENT);
private static final TokenSet EXPR_END = TokenSet.create(mRCURLY, mIDENT);
private static final TokenSet AFTER_EXPR_END = TokenSet.create(mGSTRING_END, mGSTRING_CONTENT, mDOLLAR, mREGEX_END, mREGEX_CONTENT,
mDOLLAR_SLASH_REGEX_END, mDOLLAR_SLASH_REGEX_CONTENT);
private static final TokenSet STRING_END = TokenSet.create(mSTRING_LITERAL, mGSTRING_LITERAL, mGSTRING_END, mREGEX_END,
mDOLLAR_SLASH_REGEX_END, mREGEX_LITERAL, mDOLLAR_SLASH_REGEX_LITERAL);
private static final TokenSet INNER_STRING_TOKENS = TokenSet.create(mGSTRING_BEGIN, mGSTRING_CONTENT, mGSTRING_END, mREGEX_BEGIN,
mREGEX_CONTENT, mREGEX_END, mDOLLAR_SLASH_REGEX_BEGIN,
mDOLLAR_SLASH_REGEX_CONTENT, mDOLLAR_SLASH_REGEX_END,
GSTRING_INJECTION);
public Result preprocessEnter(@NotNull PsiFile file,
@NotNull Editor editor,
@NotNull Ref<Integer> caretOffset,
@@ -204,22 +255,6 @@ public class GroovyEnterHandler extends EnterHandlerDelegateAdapter {
return false;
}
private static final TokenSet AFTER_DOLLAR = TokenSet.create(mLCURLY, mIDENT, mGSTRING_CONTENT, mDOLLAR, mGSTRING_END);
private static final TokenSet ALL_STRINGS = TokenSet
.create(mSTRING_LITERAL, mGSTRING_LITERAL, mGSTRING_BEGIN, mGSTRING_END, mGSTRING_CONTENT, mRCURLY, mIDENT, mDOLLAR, mREGEX_BEGIN,
mREGEX_CONTENT, mREGEX_END, mDOLLAR_SLASH_REGEX_BEGIN, mDOLLAR_SLASH_REGEX_CONTENT, mDOLLAR_SLASH_REGEX_END, mREGEX_LITERAL,
mDOLLAR_SLASH_REGEX_LITERAL);
private static final TokenSet BEFORE_DOLLAR = TokenSet.create(mGSTRING_BEGIN, mGSTRING_CONTENT);
private static final TokenSet EXPR_END = TokenSet.create(mRCURLY, mIDENT);
private static final TokenSet AFTER_EXPR_END = TokenSet.create(mGSTRING_END, mGSTRING_CONTENT, mDOLLAR);
private static final TokenSet STRING_END = TokenSet.create(mSTRING_LITERAL, mGSTRING_LITERAL, mGSTRING_END);
private static boolean handleInString(Editor editor, int caretOffset, DataContext dataContext, EditorActionHandler originalHandler) {
Project project = PlatformDataKeys.PROJECT.getData(dataContext);
if (project == null) return false;
@@ -241,6 +276,16 @@ public class GroovyEnterHandler extends EnterHandlerDelegateAdapter {
ASTNode node = stringElement.getNode();
if (node == null) return false;
// For expression injection in GString like "abc ${}<caret> abc"
if (!INNER_STRING_TOKENS.contains(node.getElementType()) && checkGStringInjection(stringElement)) {
stringElement = stringElement.getParent().getParent().getNextSibling();
if (stringElement == null) return false;
node = stringElement.getNode();
if (node == null) return false;
}
boolean isInsertIndent = isInsertIndent(caretOffset, stringElement.getTextRange().getStartOffset(), fileText);
// For simple String literals like 'abcdef'
if (mSTRING_LITERAL == node.getElementType()) {
if (GroovyEditorActionUtil.isPlainStringLiteral(node)) {
@@ -258,20 +303,13 @@ public class GroovyEnterHandler extends EnterHandlerDelegateAdapter {
EditorModificationUtil.insertStringAtCaret(editor, "\n");
}
else {
originalHandler.execute(editor, dataContext);
insertLineFeedInString(editor, dataContext, originalHandler, isInsertIndent);
}
return true;
}
// For expression injection in GString like "abc ${}<caret> abc"
if (!GroovyEditorActionUtil.GSTRING_TOKENS.contains(node.getElementType()) && checkGStringInnerExpression(stringElement)) {
stringElement = stringElement.getParent().getParent().getNextSibling();
if (stringElement == null) return false;
node = stringElement.getNode();
if (node == null) return false;
}
if (GroovyEditorActionUtil.GSTRING_TOKENS.contains(node.getElementType())) {
if (GSTRING_TOKENS.contains(node.getElementType()) ||
node.getElementType() == mDOLLAR && node.getTreeParent().getTreeParent().getElementType() == GSTRING) {
PsiElement parent = stringElement.getParent();
if (node.getElementType() == mGSTRING_LITERAL) {
parent = stringElement;
@@ -296,13 +334,55 @@ public class GroovyEnterHandler extends EnterHandlerDelegateAdapter {
}
}
else {
originalHandler.execute(editor, dataContext);
insertLineFeedInString(editor, dataContext, originalHandler, isInsertIndent);
}
return true;
}
if (REGEX_TOKENS.contains(node.getElementType()) ||
node.getElementType() == mDOLLAR && node.getTreeParent().getTreeParent().getElementType() == REGEX) {
PsiElement parent = stringElement.getParent();
if (node.getElementType() == mREGEX_LITERAL || node.getElementType() == mDOLLAR_SLASH_REGEX_LITERAL) {
parent = stringElement;
}
else {
while (parent != null && !(parent instanceof GrLiteral)) {
parent = parent.getParent();
}
}
if (parent == null || parent.getLastChild() instanceof PsiErrorElement) return false;
PsiElement exprSibling = stringElement.getNextSibling();
boolean rightFromDollar = exprSibling instanceof GrExpression && exprSibling.getTextRange().getStartOffset() == caretOffset;
if (rightFromDollar) {
editor.getCaretModel().moveToOffset(caretOffset - 1);
}
insertLineFeedInString(editor, dataContext, originalHandler, isInsertIndent);
if (rightFromDollar) {
editor.getCaretModel().moveCaretRelatively(1, 0, false, false, true);
}
return true;
}
return false;
}
private static void insertLineFeedInString(Editor editor,
DataContext dataContext,
EditorActionHandler originalHandler,
boolean isInsertIndent) {
if (isInsertIndent) {
originalHandler.execute(editor, dataContext);
}
else {
EditorModificationUtil.insertStringAtCaret(editor, "\n");
}
}
private static boolean isInsertIndent(int caret, int stringOffset, String text) {
final int i = text.indexOf('\n', stringOffset);
return stringOffset < i && i < caret;
}
private static void convertEndToMultiline(int caretOffset, Document document, String fileText) {
if (caretOffset < fileText.length() && fileText.charAt(caretOffset) == '\'' ||
caretOffset > 0 && fileText.charAt(caretOffset - 1) == '\'') {
@@ -339,13 +419,13 @@ public class GroovyEnterHandler extends EnterHandlerDelegateAdapter {
return true;
}
private static boolean checkGStringInnerExpression(PsiElement element) {
private static boolean checkGStringInjection(PsiElement element) {
if (element != null && (element.getParent() instanceof GrReferenceExpression || element.getParent() instanceof GrClosableBlock)) {
final PsiElement parent = element.getParent().getParent();
if (!(parent instanceof GrStringInjection)) return false;
PsiElement nextSibling = parent.getNextSibling();
if (nextSibling == null) return false;
return GroovyEditorActionUtil.GSTRING_TOKENS_INNER.contains(nextSibling.getNode().getElementType());
return INNER_STRING_TOKENS.contains(nextSibling.getNode().getElementType());
}
return false;
}
@@ -81,6 +81,7 @@ public class EnterActionTest extends GroovyFormatterTestCase {
public void testGstring8() throws Throwable { doTest(); }
public void testGstring9() throws Throwable { doTest(); }
public void testMultilineIndent() throws Throwable { doTest(); }
public void testMultilineIndent2() throws Throwable { doTest(); }
public void testSpaces1() throws Throwable { doTest(); }
public void testString1() throws Throwable { doTest(); }
public void testString2() throws Throwable { doTest(); }
@@ -92,6 +93,16 @@ public class EnterActionTest extends GroovyFormatterTestCase {
public void testString8() throws Throwable { doTest(); }
public void testString9() throws Throwable { doTest(); }
public void testString10() throws Throwable { doTest(); }
public void testRegex1() {doTest()}
public void testRegex2() {doTest()}
public void testRegex3() {doTest()}
public void testRegex4() {doTest()}
public void testRegex5() {doTest()}
public void testRegex6() {doTest()}
public void testRegex7() {doTest()}
public void testRegex8() {doTest()}
public void testRegex9() {doTest()}
public void testRegex10() {doTest()}
def doTest(String before, String after) {
myFixture.configureByText("a.groovy", before)
@@ -0,0 +1,16 @@
def foo() {
'''<caret>
class Foo {
void foo() {}
}
'''
}
-----
def foo() {
'''
<caret>
class Foo {
void foo() {}
}
'''
}
@@ -0,0 +1,8 @@
cl {
/a<caret>bc/
}
-----
cl {
/a
<caret>bc/
}
@@ -0,0 +1,8 @@
cl {
$/a$<caret>{}bc/$
}
-----
cl {
$/a
$<caret>{}bc/$
}
+10
View File
@@ -0,0 +1,10 @@
cl {
/a
b<caret>bc/
}
-----
cl {
/a
b
<caret>bc/
}
@@ -0,0 +1,8 @@
cl {
/a${}<caret>bc/
}
-----
cl {
/a${}
<caret>bc/
}
@@ -0,0 +1,8 @@
cl {
/a<caret>${}bc/
}
-----
cl {
/a
<caret>${}bc/
}
@@ -0,0 +1,8 @@
cl {
/a$<caret>{}bc/
}
-----
cl {
/a
$<caret>{}bc/
}
@@ -0,0 +1,8 @@
cl {
$/a<caret>bc/$
}
-----
cl {
$/a
<caret>bc/$
}
+10
View File
@@ -0,0 +1,10 @@
cl {
$/a
b<caret>bc/$
}
-----
cl {
$/a
b
<caret>bc/$
}
@@ -0,0 +1,8 @@
cl {
$/a${}<caret>bc/$
}
-----
cl {
$/a${}
<caret>bc/$
}
@@ -0,0 +1,8 @@
cl {
$/a<caret>${}bc/$
}
-----
cl {
$/a
<caret>${}bc/$
}