Rewrite JetFunctionInsertHandler at kotlin

JetFunctionInsertHandler.kt is a replacement for
JetFunctionInsertHandler.java
This commit is contained in:
Nikolay Krasko
2013-08-21 15:03:13 +04:00
parent b958963cbc
commit ba0c9892d6
7 changed files with 210 additions and 230 deletions
+3
View File
@@ -1,5 +1,8 @@
<component name="libraryTable">
<library name="idea-full">
<ANNOTATIONS>
<root url="file://$PROJECT_DIR$/annotations" />
</ANNOTATIONS>
<CLASSES>
<root url="file://$PROJECT_DIR$/ideaSDK/lib" />
</CLASSES>
@@ -0,0 +1,5 @@
<root>
<item name='com.intellij.codeInsight.completion.InsertionContext com.intellij.openapi.project.Project getProject()'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
</root>
@@ -0,0 +1,5 @@
<root>
<item name='com.intellij.openapi.editor.Document java.lang.String getText()'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
</root>
@@ -37,24 +37,16 @@ import org.jetbrains.jet.lang.resolve.lazy.KotlinCodeAnalyzer;
import org.jetbrains.jet.lang.types.JetType;
import org.jetbrains.jet.lang.types.lang.KotlinBuiltIns;
import org.jetbrains.jet.plugin.JetDescriptorIconProvider;
import org.jetbrains.jet.plugin.completion.handlers.JetClassInsertHandler;
import org.jetbrains.jet.plugin.completion.handlers.JetFunctionInsertHandler;
import org.jetbrains.jet.plugin.completion.handlers.JetJavaClassInsertHandler;
import org.jetbrains.jet.plugin.completion.handlers.*;
import org.jetbrains.jet.renderer.DescriptorRenderer;
import java.util.List;
import static org.jetbrains.jet.plugin.completion.handlers.JetFunctionInsertHandler.EMPTY_FUNCTION_HANDLER;
import static org.jetbrains.jet.plugin.completion.handlers.JetFunctionInsertHandler.PARAMS_BRACES_FUNCTION_HANDLER;
import static org.jetbrains.jet.plugin.completion.handlers.JetFunctionInsertHandler.PARAMS_PARENTHESIS_FUNCTION_HANDLER;
public final class DescriptorLookupConverter {
private final static JetFunctionInsertHandler EMPTY_FUNCTION_HANDLER = new JetFunctionInsertHandler(
JetFunctionInsertHandler.CaretPosition.AFTER_BRACKETS, JetFunctionInsertHandler.BracketType.PARENTHESIS);
private final static JetFunctionInsertHandler PARAMS_PARENTHESIS_FUNCTION_HANDLER = new JetFunctionInsertHandler(
JetFunctionInsertHandler.CaretPosition.IN_BRACKETS, JetFunctionInsertHandler.BracketType.PARENTHESIS);
private final static JetFunctionInsertHandler PARAMS_BRACES_FUNCTION_HANDLER = new JetFunctionInsertHandler(
JetFunctionInsertHandler.CaretPosition.IN_BRACKETS, JetFunctionInsertHandler.BracketType.BRACES);
private DescriptorLookupConverter() {}
@NotNull
@@ -118,9 +110,8 @@ public final class DescriptorLookupConverter {
return EMPTY_FUNCTION_HANDLER;
}
if (functionDescriptor.getValueParameters().size() == 1
&& KotlinBuiltIns.getInstance().isFunctionOrExtensionFunctionType(
functionDescriptor.getValueParameters().get(0).getType())) {
if (functionDescriptor.getValueParameters().size() == 1 &&
KotlinBuiltIns.getInstance().isFunctionOrExtensionFunctionType(functionDescriptor.getValueParameters().get(0).getType())) {
return PARAMS_BRACES_FUNCTION_HANDLER;
}
@@ -40,7 +40,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lexer.JetToken;
import org.jetbrains.jet.lexer.JetTokens;
import org.jetbrains.jet.plugin.completion.handlers.JetFunctionInsertHandler;
import org.jetbrains.jet.plugin.completion.handlers.JetKeywordInsertHandler;
import org.jetbrains.jet.plugin.completion.handlers.JetTemplateInsertHandler;
@@ -49,6 +48,7 @@ import java.util.Collection;
import java.util.List;
import static org.jetbrains.jet.lexer.JetTokens.*;
import static org.jetbrains.jet.plugin.completion.handlers.JetFunctionInsertHandler.EMPTY_FUNCTION_HANDLER;
/**
* A keyword contributor for Kotlin
@@ -56,8 +56,6 @@ import static org.jetbrains.jet.lexer.JetTokens.*;
public class JetKeywordCompletionContributor extends CompletionContributor {
private final static InsertHandler<LookupElement> KEYWORDS_INSERT_HANDLER = new JetKeywordInsertHandler();
private final static InsertHandler<LookupElement> FUNCTION_INSERT_HANDLER = new JetFunctionInsertHandler(
JetFunctionInsertHandler.CaretPosition.AFTER_BRACKETS, JetFunctionInsertHandler.BracketType.PARENTHESIS);
private final static ElementFilter GENERAL_FILTER = new NotFilter(new OrFilter(
new CommentFilter(), // or
@@ -250,7 +248,7 @@ public class JetKeywordCompletionContributor extends CompletionContributor {
return lookupElementBuilder.withInsertHandler(KEYWORDS_INSERT_HANDLER);
}
return lookupElementBuilder.withInsertHandler(FUNCTION_INSERT_HANDLER);
return lookupElementBuilder.withInsertHandler(EMPTY_FUNCTION_HANDLER);
}
});
}
@@ -1,210 +0,0 @@
/*
* Copyright 2010-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jet.plugin.completion.handlers;
import com.intellij.codeInsight.AutoPopupController;
import com.intellij.codeInsight.completion.InsertHandler;
import com.intellij.codeInsight.completion.InsertionContext;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
import org.jetbrains.jet.lang.descriptors.SimpleFunctionDescriptor;
import org.jetbrains.jet.lang.psi.JetFile;
import org.jetbrains.jet.lang.psi.JetImportDirective;
import org.jetbrains.jet.lang.psi.JetQualifiedExpression;
import org.jetbrains.jet.lang.resolve.DescriptorUtils;
import org.jetbrains.jet.lang.resolve.name.FqName;
import org.jetbrains.jet.plugin.completion.JetLookupObject;
import org.jetbrains.jet.plugin.formatter.JetCodeStyleSettings;
import org.jetbrains.jet.plugin.quickfix.ImportInsertHelper;
/**
* Performs number of code modification after insertion jet function:
* - Insert ()
* - Add import directive if necessary for top level functions
*/
public class JetFunctionInsertHandler implements InsertHandler<LookupElement> {
public enum CaretPosition { IN_BRACKETS, AFTER_BRACKETS }
public enum BracketType { PARENTHESIS, BRACES }
private final CaretPosition caretPosition;
private final BracketType bracketType;
public JetFunctionInsertHandler(CaretPosition caretPosition, BracketType bracketType) {
if (caretPosition == CaretPosition.AFTER_BRACKETS && bracketType == BracketType.BRACES) {
throw new IllegalArgumentException("CaretPosition.AFTER_BRACKETS with bracketType == BracketType.BRACES combination is not supported");
}
this.caretPosition = caretPosition;
this.bracketType = bracketType;
}
@Override
public void handleInsert(InsertionContext context, LookupElement item) {
PsiDocumentManager.getInstance(context.getProject()).commitAllDocuments();
if (context.getCompletionChar() == '(') {
context.setAddCompletionChar(false);
}
int startOffset = context.getStartOffset();
PsiElement element = context.getFile().findElementAt(startOffset);
if (element == null) {
return;
}
if (shouldAddBrackets(element)) {
addBrackets(context, element);
}
addImport(context, item);
}
private static boolean shouldAddBrackets(PsiElement element) {
return PsiTreeUtil.getParentOfType(element, JetImportDirective.class) == null;
}
private void addBrackets(InsertionContext context, PsiElement offsetElement) {
int offset = context.getSelectionEndOffset();
Document document = context.getDocument();
String documentText = document.getText();
char completionChar = context.getCompletionChar();
boolean braces = bracketType == BracketType.BRACES && completionChar != '(';
char openingBracket = braces ? '{' : '(';
char closingBracket = braces ? '}' : ')';
int openingBracketIndex = indexOfSkippingSpace(documentText, openingBracket, offset);
int inBracketsShift = 0;
if (openingBracketIndex == -1) {
// Insert ()/{} if it's not already exist
if (braces) {
if (completionChar == ' ') {
context.setAddCompletionChar(false);
}
if (isInsertSpacesInOneLineFunctionEnabled(offsetElement.getProject())) {
document.insertString(offset, " { }");
inBracketsShift = 1;
}
else {
document.insertString(offset, " {}");
}
}
else {
document.insertString(offset, "()");
}
PsiDocumentManager.getInstance(context.getProject()).commitDocument(document);
documentText = document.getText();
}
openingBracketIndex = indexOfSkippingSpace(documentText, openingBracket, offset);
assert openingBracketIndex != -1 : "If there wasn't open bracket it should already have been inserted";
// CaretPosition.AFTER_BRACKETS mode cannot work when there are some non-empty chars between open and close bracket
int closeBracketIndex = indexOfSkippingSpace(documentText, closingBracket, openingBracketIndex + 1);
Editor editor = context.getEditor();
// Satisfy TypedHandler.handleRParen() algorithm for preventing doubling ')' char if user typed "()" manually.
boolean forcePlaceCaretIntoParentheses = completionChar == '(';
if (caretPosition == CaretPosition.IN_BRACKETS || forcePlaceCaretIntoParentheses || closeBracketIndex == -1) {
editor.getCaretModel().moveToOffset(openingBracketIndex + 1 + inBracketsShift);
AutoPopupController.getInstance(context.getProject()).autoPopupParameterInfo(editor, offsetElement);
}
else {
editor.getCaretModel().moveToOffset(closeBracketIndex + 1);
}
PsiDocumentManager.getInstance(context.getProject()).commitDocument(context.getDocument());
}
private static int indexOfSkippingSpace(String str, char ch, int startIndex) {
for (int i = startIndex; i < str.length(); i++) {
char currentChar = str.charAt(i);
if (ch == currentChar) {
return i;
}
if (!Character.isWhitespace(currentChar)) {
return -1;
}
}
return -1;
}
private static boolean isInsertSpacesInOneLineFunctionEnabled(Project project) {
CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(project);
JetCodeStyleSettings jetSettings = settings.getCustomSettings(JetCodeStyleSettings.class);
return jetSettings.INSERT_WHITESPACES_IN_SIMPLE_ONE_LINE_METHOD;
}
private static void addImport(final InsertionContext context, final @NotNull LookupElement item) {
ApplicationManager.getApplication().runReadAction(new Runnable() {
@Override
public void run() {
int startOffset = context.getStartOffset();
PsiElement element = context.getFile().findElementAt(startOffset);
if (element == null) {
return;
}
if (context.getFile() instanceof JetFile && item.getObject() instanceof JetLookupObject) {
DeclarationDescriptor descriptor = ((JetLookupObject) item.getObject()).getDescriptor();
if (descriptor instanceof SimpleFunctionDescriptor) {
final JetFile file = (JetFile) context.getFile();
final SimpleFunctionDescriptor functionDescriptor = (SimpleFunctionDescriptor) descriptor;
// Don't insert import for qualified expression if don't try to insert extension function
if (PsiTreeUtil.getParentOfType(element, JetQualifiedExpression.class) != null &&
functionDescriptor.getReceiverParameter() == null) {
return;
}
if (DescriptorUtils.isTopLevelDeclaration(functionDescriptor)) {
ApplicationManager.getApplication().runWriteAction(new Runnable() {
@Override
public void run() {
FqName fqn = DescriptorUtils.getFQName(functionDescriptor).toSafe();
ImportInsertHelper.addImportDirectiveIfNeeded(fqn, file);
}
});
}
}
}
}
});
}
}
@@ -0,0 +1,188 @@
/*
* Copyright 2010-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.jet.plugin.completion.handlers
import com.intellij.codeInsight.completion.InsertHandler
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.completion.InsertionContext
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiTreeUtil
import org.jetbrains.jet.lang.psi.JetImportDirective
import com.intellij.codeInsight.AutoPopupController
import com.intellij.psi.codeStyle.CodeStyleSettingsManager
import com.intellij.openapi.project.Project
import org.jetbrains.jet.plugin.formatter.JetCodeStyleSettings
import com.intellij.openapi.application.ApplicationManager
import org.jetbrains.jet.lang.psi.JetFile
import org.jetbrains.jet.plugin.completion.JetLookupObject
import org.jetbrains.jet.lang.descriptors.SimpleFunctionDescriptor
import org.jetbrains.jet.lang.psi.JetQualifiedExpression
import org.jetbrains.jet.lang.resolve.DescriptorUtils
import org.jetbrains.jet.lang.resolve.name.FqName
import org.jetbrains.jet.plugin.quickfix.ImportInsertHelper
public enum class CaretPosition {
IN_BRACKETS
AFTER_BRACKETS
}
public enum class BracketType {
PARENTHESIS
BRACES
}
public class JetFunctionInsertHandler(val caretPosition : CaretPosition, val bracketType : BracketType) : InsertHandler<LookupElement?> {
{
if (caretPosition == CaretPosition.AFTER_BRACKETS && bracketType == BracketType.BRACES) {
throw IllegalArgumentException("CaretPosition.AFTER_BRACKETS with bracketType == BracketType.BRACES combination is not supported")
}
}
public override fun handleInsert(context: InsertionContext?, item: LookupElement?) {
if (context == null || item == null) return
PsiDocumentManager.getInstance(context.getProject())?.commitAllDocuments()
if ((context.getCompletionChar()) == '(') {
context.setAddCompletionChar(false)
}
val startOffset = context.getStartOffset()
val element = context.getFile().findElementAt(startOffset)
if (element == null) return
if (shouldAddBrackets(element)) {
addBrackets(context, element)
}
addImport(context, item)
}
private fun addBrackets(context : InsertionContext, offsetElement : PsiElement) {
val offset = context.getSelectionEndOffset()
val document = context.getDocument()
val completionChar = context.getCompletionChar()
val braces = bracketType == BracketType.BRACES && completionChar != '('
val openingBracket = if (braces) '{' else '('
val closingBracket = if (braces) '}' else ')'
var documentText = document.getText()
var openingBracketIndex = indexOfSkippingSpace(documentText, openingBracket, offset)
var inBracketsShift = 0
if (openingBracketIndex == -1) {
if (braces) {
if (completionChar == ' ') {
context.setAddCompletionChar(false)
}
if (isInsertSpacesInOneLineFunctionEnabled(offsetElement.getProject())) {
document.insertString(offset, " { }")
inBracketsShift = 1
}
else {
document.insertString(offset, " {}")
}
}
else {
document.insertString(offset, "()")
}
PsiDocumentManager.getInstance(context.getProject())?.commitDocument(document)
documentText = document.getText()
}
openingBracketIndex = indexOfSkippingSpace(documentText, openingBracket, offset)
assert (openingBracketIndex != -1) { "If there wasn't open bracket it should already have been inserted" }
val closeBracketIndex = indexOfSkippingSpace(documentText, closingBracket, openingBracketIndex + 1)
val editor = context.getEditor()
var forcePlaceCaretIntoParentheses : Boolean = completionChar == '('
if (caretPosition == CaretPosition.IN_BRACKETS || forcePlaceCaretIntoParentheses || closeBracketIndex == -1) {
editor.getCaretModel().moveToOffset(openingBracketIndex + 1 + inBracketsShift)
AutoPopupController.getInstance(context.getProject())?.autoPopupParameterInfo(editor, offsetElement)
}
else {
editor.getCaretModel().moveToOffset(closeBracketIndex + 1)
}
PsiDocumentManager.getInstance(context.getProject())?.commitDocument(context.getDocument())
}
class object {
public val EMPTY_FUNCTION_HANDLER: JetFunctionInsertHandler = JetFunctionInsertHandler(CaretPosition.AFTER_BRACKETS, BracketType.PARENTHESIS)
public val PARAMS_PARENTHESIS_FUNCTION_HANDLER: JetFunctionInsertHandler = JetFunctionInsertHandler(CaretPosition.IN_BRACKETS, BracketType.PARENTHESIS)
public val PARAMS_BRACES_FUNCTION_HANDLER: JetFunctionInsertHandler = JetFunctionInsertHandler(CaretPosition.IN_BRACKETS, BracketType.BRACES)
private fun shouldAddBrackets(element : PsiElement) : Boolean {
return PsiTreeUtil.getParentOfType(element, javaClass<JetImportDirective>()) == null
}
private fun indexOfSkippingSpace(str : String, ch : Char, startIndex : Int) : Int {
for (i in startIndex..str.length() - 1) {
val currentChar = str.charAt(i)
if (ch == currentChar) {
return i
}
if (!Character.isWhitespace(currentChar)) {
return -1
}
}
return -1
}
private open fun isInsertSpacesInOneLineFunctionEnabled(project : Project) : Boolean {
val settings = CodeStyleSettingsManager.getSettings(project)
val jetSettings = settings.getCustomSettings(javaClass<JetCodeStyleSettings>())!!
return jetSettings.INSERT_WHITESPACES_IN_SIMPLE_ONE_LINE_METHOD
}
private open fun addImport(context : InsertionContext, item : LookupElement) {
ApplicationManager.getApplication()?.runReadAction { () : Unit ->
val startOffset = context.getStartOffset()
val element = context.getFile().findElementAt(startOffset)
if (element == null) return@runReadAction
if (context.getFile() is JetFile && item.getObject() is JetLookupObject) {
val descriptor = (item.getObject() as JetLookupObject).getDescriptor()
if (descriptor is SimpleFunctionDescriptor) {
val file = context.getFile() as JetFile
val functionDescriptor = descriptor as SimpleFunctionDescriptor
if (PsiTreeUtil.getParentOfType(element, javaClass<JetQualifiedExpression>()) != null &&
functionDescriptor.getReceiverParameter() == null) {
return@runReadAction
}
if (DescriptorUtils.isTopLevelDeclaration(functionDescriptor)) {
ApplicationManager.getApplication()?.runWriteAction {
val fqn = DescriptorUtils.getFQName(functionDescriptor).toSafe()
ImportInsertHelper.addImportDirectiveIfNeeded(fqn, file)
}
}
}
}
}
}
}
}