move special insertion logic in live templates to a dedicated lookup element decorator

This commit is contained in:
peter
2012-10-02 16:31:59 +02:00
parent 3c94462991
commit d4a92f0304
2 changed files with 150 additions and 75 deletions
@@ -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<LookupElement> {
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<? extends LookupElement> 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<? extends LookupElement> 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();
}
}
@@ -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<TemplateExpressionLookupElement> 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<TemplateExpressionLookupElement> lookupItems) {
TemplateExpressionLookupElement first = lookupItems.get(0);
EditorModificationUtil.insertStringAtCaret(myEditor, first.getLookupString());
first.handleTemplateInsert(lookupItems);
}
@NotNull
List<TemplateExpressionLookupElement> getCurrentExpressionLookupItems() {
LookupElement[] elements = getCurrentExpression().calculateLookupItems(getCurrentExpressionContext());
if (elements == null) return Collections.emptyList();
List<TemplateExpressionLookupElement> 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<TemplateExpressionLookupElement> 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)) {