From d4a92f030415660ffba0bb15d5b379212f91b533 Mon Sep 17 00:00:00 2001 From: peter Date: Tue, 2 Oct 2012 13:45:05 +0200 Subject: [PATCH] move special insertion logic in live templates to a dedicated lookup element decorator --- .../impl/TemplateExpressionLookupElement.java | 97 +++++++++++++ .../template/impl/TemplateState.java | 128 ++++++++---------- 2 files changed, 150 insertions(+), 75 deletions(-) create mode 100644 platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateExpressionLookupElement.java diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateExpressionLookupElement.java b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateExpressionLookupElement.java new file mode 100644 index 000000000000..c82c2af7e1a0 --- /dev/null +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateExpressionLookupElement.java @@ -0,0 +1,97 @@ +/* + * Copyright 2000-2012 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 com.intellij.codeInsight.template.impl; + +import com.intellij.codeInsight.AutoPopupController; +import com.intellij.codeInsight.completion.CompletionInitializationContext; +import com.intellij.codeInsight.completion.InsertionContext; +import com.intellij.codeInsight.completion.OffsetMap; +import com.intellij.codeInsight.completion.PrioritizedLookupElement; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupElementDecorator; +import com.intellij.codeInsight.lookup.LookupItem; +import com.intellij.codeInsight.template.TemplateLookupSelectionHandler; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.EditorModificationUtil; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.TextRange; +import com.intellij.psi.PsiDocumentManager; +import com.intellij.psi.PsiFile; + +import java.util.List; + +/** +* @author peter +*/ +class TemplateExpressionLookupElement extends LookupElementDecorator { + private TemplateState myState; + + public TemplateExpressionLookupElement(final TemplateState state, LookupElement element, int index) { + super(PrioritizedLookupElement.withPriority(element, Integer.MAX_VALUE - 10 - index)); + myState = state; + } + + private static InsertionContext createInsertionContext(LookupElement item, + PsiFile psiFile, + List elements, + Editor editor) { + final OffsetMap offsetMap = new OffsetMap(editor.getDocument()); + final InsertionContext context = new InsertionContext(offsetMap, (char)0, elements.toArray(new LookupElement[elements.size()]), psiFile, editor, false); + context.setTailOffset(editor.getCaretModel().getOffset()); + offsetMap.addOffset(CompletionInitializationContext.START_OFFSET, context.getTailOffset() - item.getLookupString().length()); + offsetMap.addOffset(CompletionInitializationContext.SELECTION_END_OFFSET, context.getTailOffset()); + offsetMap.addOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET, context.getTailOffset()); + return context; + } + + void handleTemplateInsert(List elements) { + InsertionContext context = createInsertionContext(this, myState.getPsiFile(), elements, myState.getEditor()); + handleInsert(context); + Disposer.dispose(context.getOffsetMap()); + } + + @Override + public void handleInsert(InsertionContext context) { + LookupElement item = getDelegate(); + Project project = context.getProject(); + Editor editor = context.getEditor(); + + PsiDocumentManager.getInstance(project).commitAllDocuments(); + + TextRange range = myState.getCurrentVariableRange(); + final TemplateLookupSelectionHandler handler = + item instanceof LookupItem ? ((LookupItem)item).getAttribute(TemplateLookupSelectionHandler.KEY_IN_LOOKUP_ITEM) : null; + if (handler != null && range != null) { + handler.itemSelected(item, context.getFile(), context.getDocument(), range.getStartOffset(), range.getEndOffset()); + } + else { + super.handleInsert(context); + } + + if (context.getCompletionChar() == '.') { + EditorModificationUtil.insertStringAtCaret(editor, "."); + AutoPopupController.getInstance(project).autoPopupMemberLookup(editor, null); + return; + } + + if (!myState.isFinished()) { + myState.calcResults(true); + } + + myState.nextTab(); + } +} diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateState.java b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateState.java index 2ee637c7a880..968a10b8057a 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateState.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateState.java @@ -16,12 +16,11 @@ package com.intellij.codeInsight.template.impl; -import com.intellij.codeInsight.AutoPopupController; import com.intellij.codeInsight.CodeInsightSettings; -import com.intellij.codeInsight.completion.CompletionInitializationContext; -import com.intellij.codeInsight.completion.InsertionContext; -import com.intellij.codeInsight.completion.OffsetMap; -import com.intellij.codeInsight.lookup.*; +import com.intellij.codeInsight.lookup.LookupAdapter; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupEvent; +import com.intellij.codeInsight.lookup.LookupManager; import com.intellij.codeInsight.lookup.impl.LookupImpl; import com.intellij.codeInsight.template.*; import com.intellij.lang.LanguageLiteralEscapers; @@ -31,7 +30,6 @@ import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.command.CommandAdapter; import com.intellij.openapi.command.CommandEvent; import com.intellij.openapi.command.CommandProcessor; -import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.command.undo.BasicUndoableAction; import com.intellij.openapi.command.undo.DocumentReference; import com.intellij.openapi.command.undo.DocumentReferenceManager; @@ -44,7 +42,6 @@ import com.intellij.openapi.editor.ex.DocumentEx; import com.intellij.openapi.editor.markup.*; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; @@ -57,6 +54,7 @@ import com.intellij.psi.util.PsiUtilBase; import com.intellij.refactoring.rename.inplace.InplaceRefactoring; import com.intellij.util.IncorrectOperationException; import com.intellij.util.PairProcessor; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.HashMap; import com.intellij.util.containers.IntArrayList; import org.jetbrains.annotations.NonNls; @@ -313,7 +311,7 @@ public class TemplateState implements Disposable { myTemplateRange = myDocument.createRangeMarker(caretOffset, caretOffset + template.getTemplateText().length()); } else { - PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(myDocument); + PsiFile file = getPsiFile(); preprocessTemplate(file, myEditor.getCaretModel().getOffset(), myTemplate.getTemplateText()); int caretOffset = myEditor.getCaretModel().getOffset(); myTemplateRange = myDocument.createRangeMarker(caretOffset, caretOffset); @@ -402,7 +400,7 @@ public class TemplateState implements Disposable { ApplicationManager.getApplication().runWriteAction(new Runnable() { @Override public void run() { - final PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(myDocument); + final PsiFile file = getPsiFile(); if (file != null) { IntArrayList indices = initEmptyVariables(); mySegments.setSegmentsGreedy(false); @@ -486,26 +484,23 @@ public class TemplateState implements Disposable { myEditor.getSelectionModel().setSelection(start, end); } - Expression expressionNode = myTemplate.getExpressionAt(myCurrentVariableNumber); - final ExpressionContext context = createExpressionContext(start); - final LookupElement[] lookupItems = expressionNode.calculateLookupItems(context); - final PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(myDocument); - if (lookupItems != null && lookupItems.length > 0) { + Expression expressionNode = getCurrentExpression(); + final List lookupItems = getCurrentExpressionLookupItems(); + final PsiFile psiFile = getPsiFile(); + if (!lookupItems.isEmpty()) { if (((TemplateManagerImpl)TemplateManager.getInstance(myProject)).shouldSkipInTests()) { - final String s = lookupItems[0].getLookupString(); - EditorModificationUtil.insertStringAtCaret(myEditor, s); - itemSelected(lookupItems[0], psiFile, currentSegmentNumber, ' ', lookupItems); + insertSingleItem(lookupItems); } else { for (LookupElement lookupItem : lookupItems) { assert lookupItem != null : expressionNode; } - runLookup(currentSegmentNumber, lookupItems, expressionNode.getAdvertisingText(), psiFile); + runLookup(lookupItems, expressionNode.getAdvertisingText()); } } else { - Result result = expressionNode.calculateResult(context); + Result result = expressionNode.calculateResult(getCurrentExpressionContext()); if (result != null) { result.handleFocused(psiFile, myDocument, mySegments.getSegmentStart(currentSegmentNumber), mySegments.getSegmentEnd(currentSegmentNumber)); @@ -514,12 +509,42 @@ public class TemplateState implements Disposable { focusCurrentHighlighter(true); } - private void runLookup(final int currentSegmentNumber, final LookupElement[] lookupItems, String advertisingText, final PsiFile psiFile) { + PsiFile getPsiFile() { + return PsiDocumentManager.getInstance(myProject).getPsiFile(myDocument); + } + + private void insertSingleItem(List lookupItems) { + TemplateExpressionLookupElement first = lookupItems.get(0); + EditorModificationUtil.insertStringAtCaret(myEditor, first.getLookupString()); + first.handleTemplateInsert(lookupItems); + } + + @NotNull + List getCurrentExpressionLookupItems() { + LookupElement[] elements = getCurrentExpression().calculateLookupItems(getCurrentExpressionContext()); + if (elements == null) return Collections.emptyList(); + + List result = ContainerUtil.newArrayList(); + for (int i = 0; i < elements.length; i++) { + result.add(new TemplateExpressionLookupElement(this, elements[i], i)); + } + return result; + } + + ExpressionContext getCurrentExpressionContext() { + return createExpressionContext(mySegments.getSegmentStart(getCurrentSegmentNumber())); + } + + Expression getCurrentExpression() { + return myTemplate.getExpressionAt(myCurrentVariableNumber); + } + + private void runLookup(final List lookupItems, String advertisingText) { if (myEditor == null) return; final LookupManager lookupManager = LookupManager.getInstance(myProject); - final LookupImpl lookup = (LookupImpl)lookupManager.showLookup(myEditor, lookupItems); + final LookupImpl lookup = (LookupImpl)lookupManager.showLookup(myEditor, lookupItems.toArray(new LookupElement[lookupItems.size()])); if (lookup == null) return; if (CodeInsightSettings.getInstance().AUTO_POPUP_COMPLETION_LOOKUP && myEditor.getUserData(InplaceRefactoring.INPLACE_RENAMER) == null) { @@ -542,68 +567,21 @@ public class TemplateState implements Disposable { if (isFinished()) return; ourLookupShown = false; - TemplateState.this.itemSelected(event.getItem(), psiFile, currentSegmentNumber, event.getCompletionChar(), lookupItems); + LookupElement item = event.getItem(); + if (item instanceof TemplateExpressionLookupElement) { + ((TemplateExpressionLookupElement)item).handleTemplateInsert(lookupItems); + } } }); } - private void itemSelected(final LookupElement item, - final PsiFile psiFile, - final int currentSegmentNumber, - final char completionChar, - LookupElement[] elements) { - if (item != null) { - PsiDocumentManager.getInstance(myProject).commitAllDocuments(); - - final OffsetMap offsetMap = new OffsetMap(myDocument); - final InsertionContext context = new InsertionContext(offsetMap, (char)0, elements, psiFile, myEditor, false); - context.setTailOffset(myEditor.getCaretModel().getOffset()); - offsetMap.addOffset(CompletionInitializationContext.START_OFFSET, context.getTailOffset() - item.getLookupString().length()); - offsetMap.addOffset(CompletionInitializationContext.SELECTION_END_OFFSET, context.getTailOffset()); - offsetMap.addOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET, context.getTailOffset()); - - final TemplateLookupSelectionHandler handler = - item instanceof LookupItem ? ((LookupItem)item).getAttribute(TemplateLookupSelectionHandler.KEY_IN_LOOKUP_ITEM) : null; - if (handler != null) { - handler.itemSelected(item, psiFile, myDocument, mySegments.getSegmentStart(currentSegmentNumber), - mySegments.getSegmentEnd(currentSegmentNumber)); - } - else { - new WriteCommandAction(myProject) { - @Override - protected void run(com.intellij.openapi.application.Result result) throws Throwable { - item.handleInsert(context); - } - }.execute(); - } - Disposer.dispose(offsetMap); - - if (completionChar == '.') { - EditorModificationUtil.insertStringAtCaret(myEditor, "."); - AutoPopupController.getInstance(myProject).autoPopupMemberLookup(myEditor, null); - return; - } - - if (!isFinished()) { - calcResults(true); - } - } - - new WriteCommandAction(myProject) { - @Override - protected void run(com.intellij.openapi.application.Result result) throws Throwable { - nextTab(); - } - }.execute(); - } - private void unblockDocument() { PsiDocumentManager.getInstance(myProject).commitDocument(myDocument); PsiDocumentManager.getInstance(myProject).doPostponedOperationsAndUnblockDocument(myDocument); } // Hours spent fixing code : 1 - private void calcResults(final boolean isQuick) { + void calcResults(final boolean isQuick) { if (myProcessor != null && myCurrentVariableNumber >= 0) { final String variableName = myTemplate.getVariableNameAt(myCurrentVariableNumber); final TextResult value = getVariableValue(variableName); @@ -672,7 +650,7 @@ public class TemplateState implements Disposable { int end = mySegments.getSegmentEnd(segmentNumber); PsiDocumentManager.getInstance(myProject).commitDocument(myDocument); - PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(myDocument); + PsiFile psiFile = getPsiFile(); PsiElement element = psiFile.findElementAt(start); ExpressionContext context = createExpressionContext(start); @@ -1012,7 +990,7 @@ public class TemplateState implements Disposable { } private void reformat(RangeMarker rangeMarkerToReformat) { - final PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(myDocument); + final PsiFile file = getPsiFile(); if (file != null) { CodeStyleManager style = CodeStyleManager.getInstance(myProject); for (TemplateOptionalProcessor optionalProcessor : Extensions.getExtensions(TemplateOptionalProcessor.EP_NAME)) {