Add feature for data and inline class parameters smart typing
When one typing data/inline class primary ctor this feature adds missing val keyword for parameters i.e. data class xxx(x: Int<caret>) when typing comma symbol convert code to data class xxx(val x: Int,<caret>) Fixed #KT-34567
This commit is contained in:
@@ -33,6 +33,7 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
public class KotlinEditorOptions implements PersistentStateComponent<KotlinEditorOptions> {
|
public class KotlinEditorOptions implements PersistentStateComponent<KotlinEditorOptions> {
|
||||||
private boolean donTShowConversionDialog = false;
|
private boolean donTShowConversionDialog = false;
|
||||||
private boolean enableJavaToKotlinConversion = true;
|
private boolean enableJavaToKotlinConversion = true;
|
||||||
|
private boolean autoAddValKeywordToDataClassParameters = true;
|
||||||
|
|
||||||
public boolean isDonTShowConversionDialog() {
|
public boolean isDonTShowConversionDialog() {
|
||||||
return donTShowConversionDialog;
|
return donTShowConversionDialog;
|
||||||
@@ -42,6 +43,14 @@ public class KotlinEditorOptions implements PersistentStateComponent<KotlinEdito
|
|||||||
this.donTShowConversionDialog = donTShowConversionDialog;
|
this.donTShowConversionDialog = donTShowConversionDialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isAutoAddValKeywordToDataClassParameters() {
|
||||||
|
return autoAddValKeywordToDataClassParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAutoAddValKeywordToDataClassParameters(boolean autoAddValKeywordToDataClassParameters) {
|
||||||
|
this.autoAddValKeywordToDataClassParameters = autoAddValKeywordToDataClassParameters;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isEnableJavaToKotlinConversion() {
|
public boolean isEnableJavaToKotlinConversion() {
|
||||||
return enableJavaToKotlinConversion;
|
return enableJavaToKotlinConversion;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,9 @@ public class KotlinEditorOptionsConfigurable extends BeanConfigurable<KotlinEdit
|
|||||||
checkBox("Don't show Java to Kotlin conversion dialog on paste",
|
checkBox("Don't show Java to Kotlin conversion dialog on paste",
|
||||||
instance::isDonTShowConversionDialog,
|
instance::isDonTShowConversionDialog,
|
||||||
instance::setDonTShowConversionDialog);
|
instance::setDonTShowConversionDialog);
|
||||||
|
checkBox("Auto add val keyword to data/inline class constructor parameters",
|
||||||
|
instance::isAutoAddValKeywordToDataClassParameters,
|
||||||
|
instance::setAutoAddValKeywordToDataClassParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import com.intellij.codeInsight.CodeInsightSettings;
|
|||||||
import com.intellij.codeInsight.editorActions.TypedHandlerDelegate;
|
import com.intellij.codeInsight.editorActions.TypedHandlerDelegate;
|
||||||
import com.intellij.codeInsight.highlighting.BraceMatcher;
|
import com.intellij.codeInsight.highlighting.BraceMatcher;
|
||||||
import com.intellij.codeInsight.highlighting.BraceMatchingUtil;
|
import com.intellij.codeInsight.highlighting.BraceMatchingUtil;
|
||||||
|
import com.intellij.ide.PowerSaveMode;
|
||||||
import com.intellij.lang.ASTNode;
|
import com.intellij.lang.ASTNode;
|
||||||
import com.intellij.openapi.application.ApplicationManager;
|
import com.intellij.openapi.application.ApplicationManager;
|
||||||
import com.intellij.openapi.editor.Document;
|
import com.intellij.openapi.editor.Document;
|
||||||
@@ -82,6 +83,9 @@ public class KotlinTypedHandler extends TypedHandlerDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch (c) {
|
switch (c) {
|
||||||
|
case ')':
|
||||||
|
dataClassValParameterInsert(project, editor, file, /*beforeType = */ true);
|
||||||
|
break;
|
||||||
case '<':
|
case '<':
|
||||||
kotlinLTTyped = CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET &&
|
kotlinLTTyped = CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET &&
|
||||||
LtGtTypingUtils.shouldAutoCloseAngleBracket(editor.getCaretModel().getOffset(), editor);
|
LtGtTypingUtils.shouldAutoCloseAngleBracket(editor.getCaretModel().getOffset(), editor);
|
||||||
@@ -263,6 +267,9 @@ public class KotlinTypedHandler extends TypedHandlerDelegate {
|
|||||||
LtGtTypingUtils.handleKotlinAutoCloseLT(editor);
|
LtGtTypingUtils.handleKotlinAutoCloseLT(editor);
|
||||||
return Result.STOP;
|
return Result.STOP;
|
||||||
}
|
}
|
||||||
|
else if (c == ',' || c == ')') {
|
||||||
|
dataClassValParameterInsert(project, editor, file, /*beforeType = */ false);
|
||||||
|
}
|
||||||
else if (c == '{' && CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET) {
|
else if (c == '{' && CodeInsightSettings.getInstance().AUTOINSERT_PAIR_BRACKET) {
|
||||||
PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument());
|
PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument());
|
||||||
|
|
||||||
@@ -328,6 +335,46 @@ public class KotlinTypedHandler extends TypedHandlerDelegate {
|
|||||||
return Result.CONTINUE;
|
return Result.CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void dataClassValParameterInsert(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file, boolean beforeType) {
|
||||||
|
|
||||||
|
if (!KotlinEditorOptions.getInstance().isAutoAddValKeywordToDataClassParameters()) return;
|
||||||
|
|
||||||
|
PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument());
|
||||||
|
|
||||||
|
int commaOffset = editor.getCaretModel().getOffset();
|
||||||
|
if (!beforeType) commaOffset--;
|
||||||
|
if (commaOffset < 1) return;
|
||||||
|
|
||||||
|
PsiElement elementOnCaret = file.findElementAt(commaOffset);
|
||||||
|
if (elementOnCaret == null) return;
|
||||||
|
|
||||||
|
boolean contextMatched = false;
|
||||||
|
PsiElement parentElement = elementOnCaret.getParent();
|
||||||
|
if (parentElement instanceof KtParameterList) {
|
||||||
|
parentElement = parentElement.getParent();
|
||||||
|
if (parentElement instanceof KtPrimaryConstructor) {
|
||||||
|
parentElement = parentElement.getParent();
|
||||||
|
if (parentElement instanceof KtClass) {
|
||||||
|
KtClass klassElement = ((KtClass)parentElement);
|
||||||
|
contextMatched = klassElement.isData() || klassElement.hasModifier(KtTokens.INLINE_KEYWORD);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!contextMatched) return;
|
||||||
|
|
||||||
|
PsiElement leftElement = PsiTreeUtil.skipWhitespacesAndCommentsBackward(elementOnCaret);
|
||||||
|
|
||||||
|
if (!(leftElement instanceof KtParameter)) return;
|
||||||
|
|
||||||
|
KtParameter ktParameter = (KtParameter)leftElement;
|
||||||
|
if (ktParameter.hasValOrVar()) return;
|
||||||
|
KtTypeReference typeReference = ktParameter.getTypeReference();
|
||||||
|
if (typeReference == null) return;
|
||||||
|
if (typeReference.getTextLength() == 0) return;
|
||||||
|
|
||||||
|
editor.getDocument().insertString(leftElement.getTextOffset(), "val ");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copied from
|
* Copied from
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -503,7 +503,8 @@ class TypedHandlerTest : LightCodeInsightTestCase() {
|
|||||||
|open class A
|
|open class A
|
||||||
|class B
|
|class B
|
||||||
| :<caret>
|
| :<caret>
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun testColonOfSuperTypeListInObject() {
|
fun testColonOfSuperTypeListInObject() {
|
||||||
@@ -518,7 +519,8 @@ class TypedHandlerTest : LightCodeInsightTestCase() {
|
|||||||
|interface A
|
|interface A
|
||||||
|object B
|
|object B
|
||||||
| :<caret>
|
| :<caret>
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun testColonOfSuperTypeListInCompanionObject() {
|
fun testColonOfSuperTypeListInCompanionObject() {
|
||||||
@@ -537,7 +539,8 @@ class TypedHandlerTest : LightCodeInsightTestCase() {
|
|||||||
| companion object
|
| companion object
|
||||||
| :<caret>
|
| :<caret>
|
||||||
|}
|
|}
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun testColonOfSuperTypeListBeforeBody() {
|
fun testColonOfSuperTypeListBeforeBody() {
|
||||||
@@ -554,7 +557,8 @@ class TypedHandlerTest : LightCodeInsightTestCase() {
|
|||||||
|class B
|
|class B
|
||||||
| :<caret> {
|
| :<caret> {
|
||||||
|}
|
|}
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun testColonOfSuperTypeListNotNullIndent() {
|
fun testColonOfSuperTypeListNotNullIndent() {
|
||||||
@@ -573,7 +577,8 @@ class TypedHandlerTest : LightCodeInsightTestCase() {
|
|||||||
| class B
|
| class B
|
||||||
| :<caret>
|
| :<caret>
|
||||||
|}
|
|}
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun testChainCallContinueWithDot() {
|
fun testChainCallContinueWithDot() {
|
||||||
@@ -592,7 +597,8 @@ class TypedHandlerTest : LightCodeInsightTestCase() {
|
|||||||
| Test()
|
| Test()
|
||||||
| .<caret>
|
| .<caret>
|
||||||
|}
|
|}
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun testChainCallContinueWithSafeCall() {
|
fun testChainCallContinueWithSafeCall() {
|
||||||
@@ -611,7 +617,8 @@ class TypedHandlerTest : LightCodeInsightTestCase() {
|
|||||||
| Test()
|
| Test()
|
||||||
| ?.<caret>
|
| ?.<caret>
|
||||||
|}
|
|}
|
||||||
""")
|
"""
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun testContinueWithElvis() {
|
fun testContinueWithElvis() {
|
||||||
@@ -821,6 +828,75 @@ class TypedHandlerTest : LightCodeInsightTestCase() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun testValInserterOnClass() =
|
||||||
|
testValInserter(',', """data class xxx(val x: Int<caret>)""", """data class xxx(val x: Int,<caret>)""")
|
||||||
|
|
||||||
|
fun testValInserterOnSimpleDataClass() =
|
||||||
|
testValInserter(',', """data class xxx(x: Int<caret>)""", """data class xxx(val x: Int,<caret>)""")
|
||||||
|
|
||||||
|
fun testValInserterOnValWithComment() =
|
||||||
|
testValInserter(',', """data class xxx(x: Int /*comment*/ <caret>)""", """data class xxx(val x: Int /*comment*/ ,<caret>)""")
|
||||||
|
|
||||||
|
fun testValInserterOnValWithInitializer() =
|
||||||
|
testValInserter(',', """data class xxx(x: Int = 2<caret>)""", """data class xxx(val x: Int = 2,<caret>)""")
|
||||||
|
|
||||||
|
fun testValInserterOnValWithInitializerWithOutType() =
|
||||||
|
testValInserter(',', """data class xxx(x = 2<caret>)""", """data class xxx(x = 2,<caret>)""")
|
||||||
|
|
||||||
|
fun testValInserterOnValWithGenericType() =
|
||||||
|
testValInserter(',', """data class xxx(x: A<B><caret>)""", """data class xxx(val x: A<B>,<caret>)""")
|
||||||
|
|
||||||
|
fun testValInserterOnValWithNoType() =
|
||||||
|
testValInserter(',', """data class xxx(x<caret>)""", """data class xxx(x,<caret>)""")
|
||||||
|
|
||||||
|
fun testValInserterOnValWithIncompleteGenericType() =
|
||||||
|
testValInserter(',', """data class xxx(x: A<B,C<caret>)""", """data class xxx(x: A<B,C,<caret>)""")
|
||||||
|
|
||||||
|
fun testValInserterOnValWithInvalidComma() =
|
||||||
|
testValInserter(',', """data class xxx(x:<caret> A<B>)""", """data class xxx(x:,<caret> A<B>)""")
|
||||||
|
|
||||||
|
fun testValInserterOnValWithInvalidGenericType() =
|
||||||
|
testValInserter(',', """data class xxx(x: A><caret>)""", """data class xxx(x: A>,<caret>)""")
|
||||||
|
|
||||||
|
fun testValInserterOnInMultiline() =
|
||||||
|
testValInserter(
|
||||||
|
',',
|
||||||
|
"""
|
||||||
|
|data class xxx(
|
||||||
|
| val a: A,
|
||||||
|
| b: B<caret>
|
||||||
|
| val c: C
|
||||||
|
|)
|
||||||
|
""",
|
||||||
|
"""
|
||||||
|
|data class xxx(
|
||||||
|
| val a: A,
|
||||||
|
| val b: B,<caret>
|
||||||
|
| val c: C
|
||||||
|
|)
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
|
||||||
|
fun testValInserterOnValInsertedInsideOtherParameters() =
|
||||||
|
testValInserter(
|
||||||
|
',',
|
||||||
|
"""data class xxx(val a: A, b: A<caret>val c: A)""",
|
||||||
|
"""data class xxx(val a: A, val b: A,<caret>val c: A)"""
|
||||||
|
)
|
||||||
|
|
||||||
|
fun testValInserterOnSimpleInlineClass() =
|
||||||
|
testValInserter(')', """inline class xxx(a: A<caret>)""", """inline class xxx(val a: A)<caret>""")
|
||||||
|
|
||||||
|
fun testValInserterOnValInsertedWithSquare() =
|
||||||
|
testValInserter(')', """data class xxx(val a: A, b: A<caret>)""", """data class xxx(val a: A, val b: A)<caret>""")
|
||||||
|
|
||||||
|
|
||||||
|
fun testValInserterOnTypingMissedSquare() =
|
||||||
|
testValInserter(')', """data class xxx(val a: A, b: A<caret>""", """data class xxx(val a: A, val b: A)<caret>""")
|
||||||
|
|
||||||
|
fun testValInserterWithDisabledSetting() =
|
||||||
|
testValInserter(',', """data class xxx(x: Int<caret>)""", """data class xxx(x: Int,<caret>)""", inserterEnabled = false)
|
||||||
|
|
||||||
fun testEnterInFunctionWithExpressionBody() {
|
fun testEnterInFunctionWithExpressionBody() {
|
||||||
doTypeTest(
|
doTypeTest(
|
||||||
'\n',
|
'\n',
|
||||||
@@ -908,6 +984,17 @@ class TypedHandlerTest : LightCodeInsightTestCase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun testValInserter(ch: Char, beforeText: String, afterText: String, inserterEnabled: Boolean = true) {
|
||||||
|
val editorOptions = KotlinEditorOptions.getInstance()
|
||||||
|
val wasEnabled = editorOptions.isAutoAddValKeywordToDataClassParameters
|
||||||
|
try {
|
||||||
|
editorOptions.isAutoAddValKeywordToDataClassParameters = inserterEnabled
|
||||||
|
doTypeTest(ch, beforeText, afterText)
|
||||||
|
} finally {
|
||||||
|
editorOptions.isAutoAddValKeywordToDataClassParameters = wasEnabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun doLtGtTestNoAutoClose(initText: String) {
|
private fun doLtGtTestNoAutoClose(initText: String) {
|
||||||
doLtGtTest(initText, false)
|
doLtGtTest(initText, false)
|
||||||
|
|||||||
Reference in New Issue
Block a user