mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 15:19:59 +07:00
976 lines
35 KiB
Java
976 lines
35 KiB
Java
package com.intellij.codeInsight.completion;
|
|
|
|
import com.intellij.codeInsight.AutoPopupController;
|
|
import com.intellij.codeInsight.CodeInsightSettings;
|
|
import com.intellij.codeInsight.TailType;
|
|
import com.intellij.codeInsight.generation.GenerateMembersUtil;
|
|
import com.intellij.codeInsight.generation.OverrideImplementUtil;
|
|
import com.intellij.codeInsight.lookup.Lookup;
|
|
import com.intellij.codeInsight.lookup.LookupItem;
|
|
import com.intellij.codeInsight.template.Template;
|
|
import com.intellij.codeInsight.template.TemplateManager;
|
|
import com.intellij.codeInsight.template.TemplateStateListener;
|
|
import com.intellij.featureStatistics.FeatureUsageTracker;
|
|
import com.intellij.ide.util.MemberChooser;
|
|
import com.intellij.openapi.application.ApplicationManager;
|
|
import com.intellij.openapi.command.CommandProcessor;
|
|
import com.intellij.openapi.diagnostic.Logger;
|
|
import com.intellij.openapi.editor.Document;
|
|
import com.intellij.openapi.editor.Editor;
|
|
import com.intellij.openapi.editor.RangeMarker;
|
|
import com.intellij.openapi.editor.ScrollType;
|
|
import com.intellij.openapi.editor.ex.EditorEx;
|
|
import com.intellij.openapi.editor.ex.EditorHighlighter;
|
|
import com.intellij.openapi.editor.ex.HighlighterIterator;
|
|
import com.intellij.openapi.project.Project;
|
|
import com.intellij.openapi.util.Key;
|
|
import com.intellij.openapi.util.TextRange;
|
|
import com.intellij.pom.java.LanguageLevel;
|
|
import com.intellij.psi.*;
|
|
import com.intellij.psi.codeStyle.CodeStyleManager;
|
|
import com.intellij.psi.codeStyle.CodeStyleSettings;
|
|
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
|
|
import com.intellij.psi.infos.CandidateInfo;
|
|
import com.intellij.psi.tree.IElementType;
|
|
import com.intellij.psi.tree.java.IJavaElementType;
|
|
import com.intellij.util.IncorrectOperationException;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
public class DefaultInsertHandler implements InsertHandler{
|
|
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.DefaultInsertHandler");
|
|
|
|
protected static final Object EXPANDED_TEMPLATE_ATTR = Key.create("EXPANDED_TEMPLATE_ATTR");
|
|
|
|
protected CompletionContext myContext;
|
|
private int myStartOffset;
|
|
private LookupData myLookupData;
|
|
private LookupItem myLookupItem;
|
|
|
|
private Project myProject;
|
|
private PsiFile myFile;
|
|
private Editor myEditor;
|
|
protected Document myDocument;
|
|
private InsertHandlerState myState;
|
|
|
|
public void handleInsert(final CompletionContext context,
|
|
int startOffset, LookupData data, LookupItem item,
|
|
final boolean signatureSelected, final char completionChar) {
|
|
boolean toClear = true;
|
|
try{
|
|
toClear = handleInsertInner(context, startOffset, data, item, signatureSelected, completionChar);
|
|
}
|
|
finally{
|
|
if (toClear) {
|
|
clear();
|
|
}
|
|
}
|
|
}
|
|
|
|
private void clear() {
|
|
myEditor = null;
|
|
myDocument = null;
|
|
myProject = null;
|
|
myFile = null;
|
|
myState = null;
|
|
myLookupData = null;
|
|
myLookupItem = null;
|
|
myContext = null;
|
|
}
|
|
|
|
public boolean handleInsertInner(final CompletionContext context,
|
|
int startOffset, LookupData data, LookupItem item,
|
|
final boolean signatureSelected, final char completionChar) {
|
|
|
|
LOG.assertTrue(CommandProcessor.getInstance().getCurrentCommand() != null);
|
|
PsiDocumentManager.getInstance(context.project).commitDocument(context.editor.getDocument());
|
|
myContext = context;
|
|
myStartOffset = startOffset;
|
|
myLookupData = data;
|
|
myLookupItem = item;
|
|
|
|
myProject = myContext.project;
|
|
myFile = myContext.file;
|
|
myEditor = myContext.editor;
|
|
myDocument = myEditor.getDocument();
|
|
|
|
if (myLookupItem.getObject() instanceof Template
|
|
&& myLookupItem.getAttribute(EXPANDED_TEMPLATE_ATTR) == null){
|
|
handleTemplate(context, signatureSelected, completionChar);
|
|
// we could not clear in this case since handleTemplate has templateFinished lisntener that works
|
|
// with e.g. myLookupItem
|
|
return false;
|
|
}
|
|
|
|
int tailType = getTailType(completionChar);
|
|
|
|
adjustContextAfterLookupStringInsertion();
|
|
myState = new InsertHandlerState(myContext.selectionEndOffset, myContext.selectionEndOffset);
|
|
|
|
final boolean overwrite = completionChar != 0
|
|
? completionChar == Lookup.REPLACE_SELECT_CHAR
|
|
: myLookupItem.getAttribute(LookupItem.OVERWRITE_ON_AUTOCOMPLETE_ATTR) != null;
|
|
|
|
|
|
final boolean needLeftParenth = isToInsertParenth(tailType);
|
|
final boolean hasParams = needLeftParenth && hasParams(signatureSelected);
|
|
tailType = modifyTailTypeBasedOnMethodReturnType(signatureSelected, needLeftParenth, hasParams, tailType);
|
|
|
|
if (overwrite)
|
|
removeEndOfIdentifier(needLeftParenth && hasParams);
|
|
else if(myContext.identifierEndOffset != myContext.selectionEndOffset)
|
|
context.resetParensInfo();
|
|
|
|
handleParenses(hasParams, needLeftParenth, tailType);
|
|
handleBrackets();
|
|
|
|
RangeMarker saveMaker = null;
|
|
final boolean generateAnonymousBody = myLookupItem.getAttribute(LookupItem.GENERATE_ANONYMOUS_BODY_ATTR) != null;
|
|
if (generateAnonymousBody){
|
|
saveMaker = myDocument.createRangeMarker(myState.caretOffset, myState.caretOffset);
|
|
myDocument.insertString(myState.tailOffset, "{}");
|
|
myState.caretOffset = myState.tailOffset + 1;
|
|
myState.tailOffset += 2;
|
|
}
|
|
|
|
myState.caretOffset = processTail(tailType, myState.caretOffset, myState.tailOffset);
|
|
|
|
myEditor.getCaretModel().moveToOffset(myState.caretOffset);
|
|
myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
|
|
myEditor.getSelectionModel().removeSelection();
|
|
|
|
try{
|
|
addImportForItem(myFile, myStartOffset, myLookupItem);
|
|
}
|
|
catch(IncorrectOperationException e){
|
|
LOG.error(e);
|
|
}
|
|
|
|
if (needLeftParenth && hasParams){
|
|
// Invoke parameters popup
|
|
final PsiMethod method = myLookupItem.getObject() instanceof PsiMethod ? (PsiMethod)myLookupItem.getObject() : null;
|
|
AutoPopupController.getInstance(myProject).autoPopupParameterInfo(myEditor, signatureSelected ? method : null);
|
|
}
|
|
|
|
if (tailType == TailType.DOT){
|
|
AutoPopupController.getInstance(myProject).autoPopupMemberLookup(myEditor);
|
|
}
|
|
|
|
if (generateAnonymousBody){
|
|
generateAnonymousBody();
|
|
if (hasParams){
|
|
int offset = saveMaker.getStartOffset();
|
|
myEditor.getCaretModel().moveToOffset(offset);
|
|
myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
|
|
myEditor.getSelectionModel().removeSelection();
|
|
}
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
private int modifyTailTypeBasedOnMethodReturnType(boolean signatureSelected, boolean needLeftParenth, boolean hasParams, int tailType) {
|
|
Object completion = myLookupItem.getObject();
|
|
if(completion instanceof PsiMethod){
|
|
final PsiMethod method = ((PsiMethod)completion);
|
|
if (signatureSelected) {
|
|
if(PsiType.VOID.equals(method.getReturnType()) && !(myContext.file instanceof PsiCodeFragment)) {
|
|
tailType = TailType.SEMICOLON;
|
|
}
|
|
}
|
|
else {
|
|
final boolean hasOverloads = hasOverloads();
|
|
if (!hasOverloads) {
|
|
if(PsiType.VOID.equals(method.getReturnType()) && !(myContext.file instanceof PsiCodeFragment)) {
|
|
tailType = TailType.SEMICOLON;
|
|
}
|
|
}
|
|
|
|
// [dsl]todo[dsl,ven,ik]: need to write something better here
|
|
}
|
|
}
|
|
return tailType;
|
|
}
|
|
|
|
private void adjustContextAfterLookupStringInsertion(){
|
|
// Handling lookup auto insert
|
|
myContext.shiftOffsets(myLookupItem.getLookupString().length() - myContext.prefix.length() - (myContext.selectionEndOffset - myContext.startOffset));
|
|
}
|
|
|
|
private void handleBrackets(){
|
|
// brackets
|
|
final Integer bracketsAttr = (Integer)myLookupItem.getAttribute(LookupItem.BRACKETS_COUNT_ATTR);
|
|
if (bracketsAttr != null){
|
|
int count = bracketsAttr.intValue();
|
|
if(count > 0)
|
|
myState.caretOffset = myState.tailOffset + 1;
|
|
for(int i = 0; i < count; i++){
|
|
myDocument.insertString(myState.tailOffset, "[]");
|
|
myState.tailOffset += 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void handleParenses(final boolean hasParams, final boolean needParenth, int tailType){
|
|
final CodeInsightSettings settings = CodeInsightSettings.getInstance();
|
|
boolean insertRightParenth = !settings.INSERT_SINGLE_PARENTH
|
|
|| (settings.INSERT_DOUBLE_PARENTH_WHEN_NO_ARGS && !hasParams)
|
|
|| myLookupItem.getAttribute(LookupItem.GENERATE_ANONYMOUS_BODY_ATTR) != null
|
|
|| (tailType != TailType.NONE && tailType != TailType.LPARENTH);
|
|
|
|
// if(tailType == TailType.LPARENTH) tailType = TailType.NONE; //???
|
|
if (needParenth){
|
|
if (myContext.lparenthOffset >= 0){
|
|
myState.tailOffset = myContext.argListEndOffset;
|
|
if (myContext.rparenthOffset < 0 && insertRightParenth){
|
|
myDocument.insertString(myState.tailOffset, ")");
|
|
myState.tailOffset += 1;
|
|
myContext.argListEndOffset = myState.tailOffset;
|
|
}
|
|
if (hasParams){
|
|
myState.caretOffset = myContext.lparenthOffset + 1;
|
|
}
|
|
else{
|
|
myState.caretOffset = myContext.argListEndOffset;
|
|
}
|
|
}
|
|
else{
|
|
final CodeStyleSettings styleSettings = CodeStyleSettingsManager.getSettings(myProject);
|
|
myState.tailOffset = myContext.selectionEndOffset;
|
|
myState.caretOffset = myContext.selectionEndOffset;
|
|
|
|
if(styleSettings.SPACE_BEFORE_METHOD_CALL_PARENTHESES){
|
|
myDocument.insertString(myState.tailOffset++, " ");
|
|
myState.caretOffset ++;
|
|
}
|
|
if (insertRightParenth){
|
|
myDocument.insertString(myState.tailOffset, "()");
|
|
myState.tailOffset += 2;
|
|
if (hasParams){
|
|
myState.caretOffset++;
|
|
}
|
|
else{
|
|
myState.caretOffset += 2;
|
|
}
|
|
}
|
|
else{
|
|
myDocument.insertString(myState.tailOffset++, "(");
|
|
myState.caretOffset ++;
|
|
}
|
|
|
|
if(hasParams && styleSettings.SPACE_WITHIN_METHOD_CALL_PARENTHESES){
|
|
myDocument.insertString(myState.caretOffset++, " ");
|
|
myState.tailOffset++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private boolean isToInsertParenth(int tailType){
|
|
boolean needParens = false;
|
|
if (tailType == TailType.LPARENTH){
|
|
needParens = true;
|
|
}
|
|
else if (myLookupItem.getObject() instanceof PsiMethod){
|
|
PsiElement place = myFile.findElementAt(myStartOffset);
|
|
if (myLookupItem.getObject() instanceof PsiAnnotationMethod) {
|
|
if (place instanceof PsiIdentifier && (place.getParent() instanceof PsiNameValuePair
|
|
|| place.getParent().getParent() instanceof PsiNameValuePair)) return false;
|
|
}
|
|
needParens = place == null || !(place.getParent() instanceof PsiImportStaticReferenceElement);
|
|
}
|
|
else if (myLookupItem.getAttribute(LookupItem.NEW_OBJECT_ATTR) != null){
|
|
PsiDocumentManager.getInstance(myProject).commitDocument(myDocument);
|
|
needParens = true;
|
|
final PsiClass aClass = (PsiClass)myLookupItem.getObject();
|
|
|
|
PsiElement place = myFile.findElementAt(myStartOffset);
|
|
|
|
if(myLookupItem.getAttribute(LookupItem.DONT_CHECK_FOR_INNERS) == null){
|
|
PsiClass[] classes = aClass.getInnerClasses();
|
|
for(int i = 0; i < classes.length; i++){
|
|
PsiClass inner = classes[i];
|
|
if (!inner.hasModifierProperty(PsiModifier.STATIC)) continue;
|
|
if (!inner.getManager().getResolveHelper().isAccessible(inner, place, null)) continue;
|
|
needParens = false;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return needParens;
|
|
}
|
|
|
|
private boolean hasParams(boolean signatureSelected){
|
|
boolean hasParms = false;
|
|
if (myLookupItem.getObject() instanceof PsiMethod){
|
|
final PsiMethod method = (PsiMethod)myLookupItem.getObject();
|
|
hasParms = method.getParameterList().getParameters().length > 0;
|
|
if (!signatureSelected){
|
|
hasParms = hasParms || hasOverloads();
|
|
}
|
|
}
|
|
else if (myLookupItem.getAttribute(LookupItem.NEW_OBJECT_ATTR) != null){
|
|
PsiDocumentManager.getInstance(myProject).commitDocument(myDocument);
|
|
final PsiClass aClass = (PsiClass)myLookupItem.getObject();
|
|
|
|
final PsiElement place = myFile.findElementAt(myStartOffset);
|
|
|
|
final PsiMethod[] constructors = aClass.getConstructors();
|
|
for(int i = 0; i < constructors.length; i++){
|
|
PsiMethod constructor = constructors[i];
|
|
if (!aClass.getManager().getResolveHelper().isAccessible(constructor, place, null)) continue;
|
|
if (constructor.getParameterList().getParameters().length > 0){
|
|
hasParms = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if("synchronized".equals(myLookupItem.getLookupString())
|
|
|| "catch".equals(myLookupItem.getLookupString()))
|
|
hasParms = true;
|
|
return hasParms;
|
|
}
|
|
|
|
private boolean hasOverloads() {
|
|
boolean hasParms = false;
|
|
String name = ((PsiMethod)myLookupItem.getObject()).getName();
|
|
for(int i = 0; i < myLookupData.items.length; i++){
|
|
LookupItem item1 = myLookupData.items[i];
|
|
if (myLookupItem == item1) continue;
|
|
if (item1.getObject() instanceof PsiMethod){
|
|
String name1 = ((PsiMethod)item1.getObject()).getName();
|
|
if (name1.equals(name)){
|
|
hasParms = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return hasParms;
|
|
}
|
|
|
|
protected void removeEndOfIdentifier(boolean needParenth){
|
|
myContext.init();
|
|
myDocument.deleteString(myContext.selectionEndOffset, myContext.identifierEndOffset);
|
|
int shift = -(myContext.identifierEndOffset - myContext.selectionEndOffset);
|
|
myContext.shiftOffsets(shift);
|
|
myContext.selectionEndOffset = myContext.identifierEndOffset;
|
|
if(myContext.lparenthOffset > 0 && !needParenth){
|
|
myDocument.deleteString(myContext.lparenthOffset, myContext.argListEndOffset);
|
|
myContext.resetParensInfo();
|
|
}
|
|
}
|
|
|
|
private int getTailType(final char completionChar){
|
|
final Integer attr = (Integer)myLookupItem.getAttribute(CompletionUtil.TAIL_TYPE_ATTR);
|
|
int tailType = attr != null ? attr.intValue() : TailType.NONE;
|
|
|
|
switch(completionChar){
|
|
case '.':
|
|
tailType = TailType.DOT;
|
|
break;
|
|
case ',':
|
|
tailType = TailType.COMMA;
|
|
break;
|
|
case ';':
|
|
tailType = TailType.SEMICOLON;
|
|
break;
|
|
case '=':
|
|
tailType = TailType.EQ;
|
|
break;
|
|
case ' ':
|
|
tailType = TailType.SPACE;
|
|
break;
|
|
case ':':
|
|
tailType = TailType.CASE_COLON; //?
|
|
break;
|
|
case '(':
|
|
tailType = TailType.LPARENTH;
|
|
break;
|
|
case '<':
|
|
tailType = '<';
|
|
break;
|
|
case '>':
|
|
tailType = '>';
|
|
break;
|
|
case '[':
|
|
tailType = '[';
|
|
break;
|
|
}
|
|
return tailType;
|
|
}
|
|
|
|
private void handleTemplate(final CompletionContext context, final boolean signatureSelected, final char completionChar){
|
|
Template template = (Template)myLookupItem.getObject();
|
|
|
|
int offset1 = context.startOffset;
|
|
final RangeMarker offsetRangeMarker = context.editor.getDocument().createRangeMarker(offset1, offset1);
|
|
|
|
TemplateManager.getInstance(myProject).startTemplate(
|
|
context.editor,
|
|
template,
|
|
new TemplateStateListener() {
|
|
public void templateFinished(Template template) {
|
|
myLookupItem.setAttribute(EXPANDED_TEMPLATE_ATTR, Boolean.TRUE);
|
|
|
|
if (!offsetRangeMarker.isValid()) return;
|
|
|
|
final int offset = offsetRangeMarker.getStartOffset();
|
|
|
|
String lookupString =
|
|
context.editor.getDocument().getCharsSequence().subSequence(
|
|
offset,
|
|
context.editor.getCaretModel().getOffset()).toString();
|
|
myLookupItem.setLookupString(lookupString);
|
|
|
|
CompletionContext newContext = new CompletionContext(context.project, context.editor, context.file, offset, offset);
|
|
handleInsert(newContext, myStartOffset, myLookupData, myLookupItem, signatureSelected, completionChar);
|
|
}
|
|
}
|
|
);
|
|
return;
|
|
}
|
|
|
|
private int processTail(int tailType, int caretOffset, int tailOffset) {
|
|
CodeStyleSettings styleSettings = CodeStyleSettingsManager.getSettings(myProject);
|
|
int textLength = myDocument.getTextLength();
|
|
CharSequence chars = myDocument.getCharsSequence();
|
|
|
|
switch(tailType){
|
|
case TailType.NONE:
|
|
break;
|
|
case TailType.SEMICOLON:
|
|
if (tailOffset == textLength || chars.charAt(tailOffset) != ';'){
|
|
myDocument.insertString(tailOffset, ";");
|
|
}
|
|
if (caretOffset == tailOffset){
|
|
caretOffset++;
|
|
}
|
|
tailOffset++;
|
|
break;
|
|
case TailType.COMMA:
|
|
if (styleSettings.SPACE_BEFORE_COMMA){
|
|
if (tailOffset == textLength || chars.charAt(tailOffset) != ' '){
|
|
myDocument.insertString(tailOffset, " ");
|
|
}
|
|
if (caretOffset == tailOffset){
|
|
caretOffset++;
|
|
}
|
|
tailOffset++;
|
|
}
|
|
|
|
if (tailOffset == textLength || chars.charAt(tailOffset) != ','){
|
|
myDocument.insertString(tailOffset, ",");
|
|
}
|
|
if (caretOffset == tailOffset){
|
|
caretOffset++;
|
|
}
|
|
tailOffset++;
|
|
|
|
if (styleSettings.SPACE_AFTER_COMMA){
|
|
if (tailOffset == textLength || chars.charAt(tailOffset) != ' '){
|
|
myDocument.insertString(tailOffset, " ");
|
|
}
|
|
if (caretOffset == tailOffset){
|
|
caretOffset++;
|
|
}
|
|
tailOffset++;
|
|
}
|
|
break;
|
|
|
|
case TailType.SPACE:
|
|
if (tailOffset == textLength || chars.charAt(tailOffset) != ' '){
|
|
myDocument.insertString(tailOffset, " ");
|
|
}
|
|
if (caretOffset == tailOffset){
|
|
caretOffset++;
|
|
}
|
|
tailOffset++;
|
|
break;
|
|
|
|
case TailType.DOT:
|
|
if (tailOffset == textLength || chars.charAt(tailOffset) != '.'){
|
|
myDocument.insertString(tailOffset, ".");
|
|
}
|
|
if (caretOffset == tailOffset){
|
|
caretOffset++;
|
|
}
|
|
tailOffset++;
|
|
break;
|
|
|
|
case TailType.CAST_RPARENTH:
|
|
FeatureUsageTracker.getInstance().triggerFeatureUsed("editing.completion.smarttype.casting");
|
|
// no breaks over here!!!
|
|
case TailType.CALL_RPARENTH:
|
|
case TailType.IF_RPARENTH:
|
|
case TailType.WHILE_RPARENTH:
|
|
case TailType.CALL_RPARENTH_SEMICOLON:
|
|
caretOffset = processRparenthTail(tailType, caretOffset, tailOffset);
|
|
break;
|
|
|
|
case TailType.COND_EXPR_COLON:
|
|
if (tailOffset < textLength - 1 && chars.charAt(tailOffset) == ' ' && chars.charAt(tailOffset + 1) == ':'){
|
|
if (caretOffset == tailOffset){
|
|
caretOffset += 2;
|
|
}
|
|
tailOffset += 2;
|
|
}
|
|
else if (tailOffset < textLength && chars.charAt(tailOffset) == ':'){
|
|
if (caretOffset == tailOffset){
|
|
caretOffset++;
|
|
}
|
|
tailOffset++;
|
|
}
|
|
else{
|
|
myDocument.insertString(tailOffset, " : ");
|
|
if (caretOffset == tailOffset){
|
|
caretOffset += 3;
|
|
}
|
|
tailOffset += 3;
|
|
}
|
|
break;
|
|
|
|
case TailType.EQ:
|
|
if (tailOffset < textLength - 1 && chars.charAt(tailOffset) == ' ' && chars.charAt(tailOffset + 1) == '='){
|
|
if (caretOffset == tailOffset){
|
|
caretOffset += 2;
|
|
}
|
|
tailOffset += 2;
|
|
}
|
|
else if (tailOffset < textLength && chars.charAt(tailOffset) == '='){
|
|
if (caretOffset == tailOffset){
|
|
caretOffset++;
|
|
}
|
|
tailOffset++;
|
|
}
|
|
else{
|
|
if (styleSettings.SPACE_AROUND_ASSIGNMENT_OPERATORS){
|
|
myDocument.insertString(tailOffset, " =");
|
|
textLength+=2;
|
|
if (caretOffset == tailOffset){
|
|
caretOffset += 2;
|
|
}
|
|
tailOffset += 2;
|
|
}
|
|
else{
|
|
myDocument.insertString(tailOffset, "=");
|
|
textLength++;
|
|
if (caretOffset == tailOffset){
|
|
caretOffset++;
|
|
}
|
|
tailOffset++;
|
|
}
|
|
}
|
|
if (styleSettings.SPACE_AROUND_ASSIGNMENT_OPERATORS){
|
|
if (tailOffset == textLength || chars.charAt(tailOffset) != ' '){
|
|
myDocument.insertString(tailOffset, " ");
|
|
}
|
|
if (caretOffset == tailOffset){
|
|
caretOffset++;
|
|
}
|
|
tailOffset++;
|
|
}
|
|
break;
|
|
case TailType.CASE_COLON:
|
|
if (tailOffset == textLength || chars.charAt(tailOffset) != ':'){
|
|
myDocument.insertString(tailOffset, ":");
|
|
}
|
|
if (caretOffset == tailOffset){
|
|
caretOffset++;
|
|
}
|
|
tailOffset++;
|
|
break;
|
|
|
|
default:
|
|
if (tailOffset == textLength || chars.charAt(tailOffset) != tailType){
|
|
myDocument.insertString(tailOffset, "" + (char)tailType);
|
|
}
|
|
if (caretOffset == tailOffset){
|
|
caretOffset++;
|
|
}
|
|
tailOffset++;
|
|
break;
|
|
case TailType.UNKNOWN:
|
|
case TailType.LPARENTH:
|
|
}
|
|
return caretOffset;
|
|
}
|
|
|
|
private int processRparenthTail(int tailType, int caretOffset, int tailOffset) {
|
|
CodeStyleSettings styleSettings = CodeStyleSettingsManager.getSettings(myProject);
|
|
CharSequence chars = myDocument.getCharsSequence();
|
|
int textLength = myDocument.getTextLength();
|
|
|
|
EditorHighlighter highlighter = ((EditorEx) myEditor).getHighlighter();
|
|
|
|
int existingRParenthOffset = -1;
|
|
for(HighlighterIterator iterator = highlighter.createIterator(tailOffset); !iterator.atEnd(); iterator.advance()){
|
|
IElementType tokenType = iterator.getTokenType();
|
|
if (tokenType instanceof IJavaElementType && JavaTokenType.WHITE_SPACE_OR_COMMENT_BIT_SET.isInSet(tokenType)) continue;
|
|
if (tokenType == JavaTokenType.RPARENTH){
|
|
existingRParenthOffset = iterator.getStart();
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (existingRParenthOffset >= 0){
|
|
PsiDocumentManager.getInstance(myProject).commitDocument(myDocument);
|
|
TextRange range = getRangeToCheckParensBalance(myFile, myStartOffset);
|
|
int balance = calcParensBalance(myDocument, highlighter, range.getStartOffset(), range.getEndOffset());
|
|
if (balance > 0){
|
|
existingRParenthOffset = -1;
|
|
}
|
|
}
|
|
|
|
boolean spaceWithinParens;
|
|
switch(tailType){
|
|
case TailType.CALL_RPARENTH:
|
|
case TailType.CALL_RPARENTH_SEMICOLON:
|
|
spaceWithinParens = styleSettings.SPACE_WITHIN_METHOD_CALL_PARENTHESES;
|
|
break;
|
|
|
|
case TailType.IF_RPARENTH:
|
|
spaceWithinParens = styleSettings.SPACE_WITHIN_IF_PARENTHESES;
|
|
break;
|
|
|
|
case TailType.WHILE_RPARENTH:
|
|
spaceWithinParens = styleSettings.SPACE_WITHIN_WHILE_PARENTHESES;
|
|
break;
|
|
|
|
case TailType.CAST_RPARENTH:
|
|
spaceWithinParens = styleSettings.SPACE_WITHIN_CAST_PARENTHESES;
|
|
caretOffset = tailOffset;
|
|
break;
|
|
|
|
default:
|
|
spaceWithinParens = false;
|
|
LOG.assertTrue(false);
|
|
}
|
|
|
|
if (existingRParenthOffset < 0){
|
|
if (spaceWithinParens){
|
|
myDocument.insertString(tailOffset, " ");
|
|
if (caretOffset == tailOffset){
|
|
caretOffset++;
|
|
}
|
|
tailOffset++;
|
|
}
|
|
myDocument.insertString(tailOffset, ")");
|
|
if (caretOffset == tailOffset){
|
|
caretOffset++;
|
|
}
|
|
tailOffset++;
|
|
}
|
|
else{
|
|
if (spaceWithinParens){
|
|
if (tailOffset == existingRParenthOffset){
|
|
myDocument.insertString(tailOffset, " ");
|
|
if (caretOffset == tailOffset){
|
|
caretOffset++;
|
|
}
|
|
tailOffset++;
|
|
existingRParenthOffset++;
|
|
}
|
|
}
|
|
if (caretOffset == tailOffset){
|
|
caretOffset = existingRParenthOffset + 1;
|
|
}
|
|
tailOffset = existingRParenthOffset + 1;
|
|
}
|
|
|
|
if (tailType == TailType.CAST_RPARENTH && styleSettings.SPACE_AFTER_TYPE_CAST){
|
|
if (tailOffset == textLength || chars.charAt(tailOffset) != ' '){
|
|
myDocument.insertString(tailOffset, " ");
|
|
}
|
|
if (caretOffset == tailOffset){
|
|
caretOffset++;
|
|
}
|
|
tailOffset++;
|
|
}
|
|
|
|
if (tailType == TailType.CALL_RPARENTH_SEMICOLON){
|
|
if (tailOffset == textLength || chars.charAt(tailOffset) != ';'){
|
|
myDocument.insertString(tailOffset, ";");
|
|
}
|
|
if (caretOffset == tailOffset){
|
|
caretOffset++;
|
|
}
|
|
tailOffset++;
|
|
}
|
|
|
|
return caretOffset;
|
|
}
|
|
|
|
private static TextRange getRangeToCheckParensBalance(PsiFile file, int startOffset){
|
|
PsiElement element = file.findElementAt(startOffset);
|
|
PsiElement prevElement = element;
|
|
element = element.getParent();
|
|
while(true){
|
|
if (!(element instanceof PsiExpression) &&
|
|
!(element instanceof PsiExpressionList) &&
|
|
!(element instanceof PsiJavaCodeReferenceElement) &&
|
|
!(element instanceof PsiTypeElement)
|
|
){
|
|
if (element instanceof PsiIfStatement){
|
|
PsiIfStatement ifStatement = (PsiIfStatement)element;
|
|
int start = ifStatement.getTextRange().getStartOffset();
|
|
PsiStatement then = ifStatement.getThenBranch();
|
|
int end = then != null ? then.getTextRange().getStartOffset() : ifStatement.getTextRange().getEndOffset();
|
|
return new TextRange(start, end);
|
|
}
|
|
else { //TODO: other statements with '()'
|
|
break;
|
|
}
|
|
}
|
|
|
|
prevElement = element;
|
|
element = element.getParent();
|
|
}
|
|
final int start = prevElement.getTextRange().getStartOffset();
|
|
int end = prevElement.getTextRange().getEndOffset();
|
|
PsiElement errorElement = prevElement.getNextSibling();
|
|
|
|
while(errorElement instanceof PsiErrorElement){
|
|
end += errorElement.getTextLength();
|
|
while(errorElement.getNextSibling() == null && errorElement.getParent() != null)
|
|
errorElement = errorElement.getParent();
|
|
|
|
errorElement = errorElement.getNextSibling();
|
|
}
|
|
return new TextRange(start, end);
|
|
}
|
|
|
|
private static int calcParensBalance(Document document, EditorHighlighter highlighter, int rangeStart, int rangeEnd){
|
|
LOG.assertTrue(0 <= rangeStart);
|
|
LOG.assertTrue(rangeStart <= rangeEnd);
|
|
LOG.assertTrue(rangeEnd <= document.getTextLength());
|
|
|
|
HighlighterIterator iterator = highlighter.createIterator(rangeStart);
|
|
int balance = 0;
|
|
while(!iterator.atEnd() && iterator.getStart() < rangeEnd){
|
|
IElementType tokenType = iterator.getTokenType();
|
|
if (tokenType == JavaTokenType.LPARENTH){
|
|
balance++;
|
|
}
|
|
else if (tokenType == JavaTokenType.RPARENTH){
|
|
balance--;
|
|
}
|
|
iterator.advance();
|
|
}
|
|
return balance;
|
|
}
|
|
|
|
private void generateAnonymousBody(){
|
|
PsiDocumentManager.getInstance(myProject).commitAllDocuments();
|
|
|
|
int offset = myEditor.getCaretModel().getOffset();
|
|
PsiElement element = myFile.findElementAt(offset);
|
|
if (element == null) return;
|
|
if (element.getParent() instanceof PsiAnonymousClass){
|
|
try{
|
|
CodeStyleManager.getInstance(myProject).reformat(element.getParent());
|
|
}
|
|
catch(IncorrectOperationException e){
|
|
LOG.error(e);
|
|
}
|
|
offset = element.getParent().getTextRange().getEndOffset() - 1;
|
|
myEditor.getCaretModel().moveToOffset(offset);
|
|
myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
|
|
myEditor.getSelectionModel().removeSelection();
|
|
}
|
|
final SmartPsiElementPointer pointer = SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(element);
|
|
ApplicationManager.getApplication().invokeLater(new Runnable() {
|
|
public void run(){
|
|
CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
|
|
public void run() {
|
|
PsiDocumentManager.getInstance(myProject).commitDocument(myDocument);
|
|
PsiElement element = pointer.getElement();
|
|
if (element == null) return;
|
|
|
|
while(true){
|
|
if (element instanceof PsiFile) return;
|
|
PsiElement parent = element.getParent();
|
|
if (parent instanceof PsiAnonymousClass) break;
|
|
element = parent;
|
|
}
|
|
final PsiAnonymousClass aClass = (PsiAnonymousClass)element.getParent();
|
|
|
|
final CandidateInfo[] candidatesToImplement = OverrideImplementUtil.getMethodsToOverrideImplement(aClass, true);
|
|
boolean invokeOverride = candidatesToImplement.length == 0;
|
|
if (invokeOverride){
|
|
chooseAndOverrideMethodsInAdapter(myProject, myEditor, aClass);
|
|
}
|
|
else{
|
|
ApplicationManager.getApplication().runWriteAction(new Runnable() {
|
|
public void run() {
|
|
try{
|
|
PsiMethod[] prototypes = OverrideImplementUtil.overrideOrImplementMethods(aClass, candidatesToImplement, false, false);
|
|
Object[] resultMembers = GenerateMembersUtil.insertMembersBeforeAnchor(aClass, null, prototypes);
|
|
GenerateMembersUtil.positionCaret(myEditor, (PsiElement)resultMembers[0], true);
|
|
}
|
|
catch(IncorrectOperationException ioe){
|
|
LOG.error(ioe);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
clear();
|
|
}
|
|
}, "generate anonymous body", null);
|
|
}
|
|
});
|
|
}
|
|
|
|
private static void chooseAndOverrideMethodsInAdapter(final Project project, final Editor editor, final PsiAnonymousClass aClass) {
|
|
PsiClass baseClass = aClass.getBaseClassType().resolve();
|
|
if (baseClass == null) return;
|
|
PsiMethod[] allBaseMethods = baseClass.getMethods();
|
|
if(allBaseMethods.length == 0) return;
|
|
|
|
List<CandidateInfo> methods = new ArrayList<CandidateInfo>();
|
|
for(int i = 0; i < allBaseMethods.length; i++){
|
|
final PsiMethod method = allBaseMethods[i];
|
|
if(OverrideImplementUtil.isOverridable(method)) {
|
|
methods.add(new CandidateInfo(method, PsiSubstitutor.UNKNOWN));
|
|
}
|
|
}
|
|
|
|
boolean isJdk15Enabled = LanguageLevel.JDK_1_5.compareTo(PsiManager.getInstance(project).getEffectiveLanguageLevel()) <= 0;
|
|
final MemberChooser chooser = new MemberChooser(methods.toArray(), false, true, project, isJdk15Enabled);
|
|
chooser.setTitle("Select Methods to Override");
|
|
chooser.setCopyJavadocVisible(true);
|
|
|
|
chooser.show();
|
|
Object[] selectedElements = chooser.getSelectedElements();
|
|
if (selectedElements == null || selectedElements.length == 0) return;
|
|
|
|
CandidateInfo[] selectedCandidates = new CandidateInfo[selectedElements.length];
|
|
|
|
try{
|
|
System.arraycopy(selectedElements, 0, selectedCandidates, 0, selectedCandidates.length);
|
|
final PsiMethod[] prototypes = OverrideImplementUtil.overrideOrImplementMethods(aClass, selectedCandidates, chooser.isCopyJavadoc(), chooser.isInsertOverrideAnnotation());
|
|
|
|
for(int i = 0; i < prototypes.length; i++){
|
|
PsiMethod prototype = prototypes[i];
|
|
PsiStatement[] statements = prototype.getBody().getStatements();
|
|
if (statements.length > 0 && prototype.getReturnType() == PsiType.VOID){
|
|
statements[0].delete(); // remove "super(..)" call
|
|
}
|
|
}
|
|
|
|
final int offset = editor.getCaretModel().getOffset();
|
|
ApplicationManager.getApplication().runWriteAction(new Runnable() {
|
|
public void run() {
|
|
try{
|
|
Object[] resultMembers = GenerateMembersUtil.insertMembersAtOffset(project, editor.getDocument(), aClass.getContainingFile(), offset, prototypes);
|
|
GenerateMembersUtil.positionCaret(editor, (PsiElement)resultMembers[0], true);
|
|
}
|
|
catch(IncorrectOperationException e){
|
|
LOG.error(e);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
catch(IncorrectOperationException ioe){
|
|
LOG.error(ioe);
|
|
}
|
|
}
|
|
|
|
private static void addImportForItem(PsiFile file, int startOffset, LookupItem item) throws IncorrectOperationException {
|
|
PsiDocumentManager.getInstance(file.getProject()).commitAllDocuments();
|
|
|
|
Object o = item.getObject();
|
|
if (o instanceof PsiClass){
|
|
PsiClass aClass = (PsiClass)o;
|
|
int length = aClass.getName().length();
|
|
addImportForClass(file, startOffset, startOffset + length, aClass);
|
|
}
|
|
else if (o instanceof PsiType){
|
|
PsiType type = ((PsiType)o).getDeepComponentType();
|
|
if (type instanceof PsiClassType) {
|
|
PsiClass refClass = ((PsiClassType) type).resolve();
|
|
if (refClass != null){
|
|
int length = refClass.getName().length();
|
|
addImportForClass(file, startOffset, startOffset + length, refClass);
|
|
}
|
|
}
|
|
}
|
|
else if (o instanceof PsiMethod){
|
|
PsiMethod method = (PsiMethod)o;
|
|
if (method.isConstructor()){
|
|
PsiClass aClass = (PsiClass)item.getAttribute(LookupItem.CONTAINING_CLASS_ATTR);
|
|
if (aClass == null){
|
|
aClass = method.getContainingClass();
|
|
}
|
|
if (aClass != null){
|
|
int length = method.getName().length();
|
|
addImportForClass(file, startOffset, startOffset + length, aClass);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void addImportForClass(PsiFile file, int startOffset, int endOffset, PsiClass aClass) throws IncorrectOperationException {
|
|
SmartPsiElementPointer pointer = SmartPointerManager.getInstance(file.getProject()).createSmartPsiElementPointer(aClass);
|
|
LOG.assertTrue(CommandProcessor.getInstance().getCurrentCommand() != null);
|
|
LOG.assertTrue(ApplicationManager.getApplication().getCurrentWriteAction(null) != null);
|
|
|
|
final PsiManager manager = file.getManager();
|
|
final PsiResolveHelper helper = manager.getResolveHelper();
|
|
|
|
final Document document = PsiDocumentManager.getInstance(manager.getProject()).getDocument(file);
|
|
|
|
CharSequence chars = document.getCharsSequence();
|
|
int length = document.getTextLength();
|
|
PsiElement element = file.findElementAt(startOffset);
|
|
String refText = chars.subSequence(startOffset, endOffset).toString();
|
|
PsiClass refClass = helper.resolveReferencedClass(refText, element);
|
|
if (refClass != null && (refClass.getQualifiedName() == null/* local classes and parameters*/
|
|
|| manager.areElementsEquivalent(aClass, refClass))) return;
|
|
boolean insertSpace = endOffset < length && Character.isJavaIdentifierPart(chars.charAt(endOffset));
|
|
|
|
if (insertSpace){
|
|
document.insertString(endOffset, " ");
|
|
}
|
|
String name = aClass.getName();
|
|
document.replaceString(startOffset, endOffset, name);
|
|
endOffset = startOffset + name.length();
|
|
|
|
PsiDocumentManager.getInstance(manager.getProject()).commitAllDocuments();
|
|
|
|
element = file.findElementAt(startOffset);
|
|
if (element instanceof PsiIdentifier){
|
|
PsiElement parent = element.getParent();
|
|
if (parent instanceof PsiJavaCodeReferenceElement && !((PsiJavaCodeReferenceElement)parent).isQualified()){
|
|
PsiJavaCodeReferenceElement ref = (PsiJavaCodeReferenceElement)parent;
|
|
final PsiElement pointerElement = pointer.getElement();
|
|
if(pointerElement instanceof PsiClass){
|
|
if (!(ref instanceof PsiImportStaticReferenceElement)) {
|
|
PsiJavaCodeReferenceElement newRef = (PsiJavaCodeReferenceElement)ref.bindToElement(pointerElement);
|
|
endOffset = newRef.getTextRange().getEndOffset();
|
|
}
|
|
else {
|
|
final PsiImportStaticStatement statement = ((PsiImportStaticReferenceElement)ref).bindToTargetClass((PsiClass) pointerElement);
|
|
endOffset = statement.getTextRange().getEndOffset();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (insertSpace){
|
|
document.deleteString(endOffset, endOffset + 1);
|
|
}
|
|
}
|
|
|
|
private static class InsertHandlerState{
|
|
int tailOffset;
|
|
int caretOffset;
|
|
|
|
public InsertHandlerState(int caretOffset, int tailOffset){
|
|
this.caretOffset = caretOffset;
|
|
this.tailOffset = tailOffset;
|
|
}
|
|
}
|
|
}
|