an initial version of IDEABKL-5496 Auto-filling the actual Java call arguments
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInsight / template / impl / TemplateState.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.intellij.codeInsight.template.impl;
18
19 import com.intellij.codeInsight.CodeInsightSettings;
20 import com.intellij.codeInsight.lookup.*;
21 import com.intellij.codeInsight.lookup.impl.LookupImpl;
22 import com.intellij.codeInsight.template.*;
23 import com.intellij.codeInsight.template.macro.TemplateCompletionProcessor;
24 import com.intellij.diagnostic.AttachmentFactory;
25 import com.intellij.lang.injection.InjectedLanguageManager;
26 import com.intellij.openapi.Disposable;
27 import com.intellij.openapi.application.ApplicationManager;
28 import com.intellij.openapi.command.CommandAdapter;
29 import com.intellij.openapi.command.CommandEvent;
30 import com.intellij.openapi.command.CommandProcessor;
31 import com.intellij.openapi.command.WriteCommandAction;
32 import com.intellij.openapi.command.undo.BasicUndoableAction;
33 import com.intellij.openapi.command.undo.DocumentReference;
34 import com.intellij.openapi.command.undo.DocumentReferenceManager;
35 import com.intellij.openapi.command.undo.UndoManager;
36 import com.intellij.openapi.diagnostic.Logger;
37 import com.intellij.openapi.editor.*;
38 import com.intellij.openapi.editor.colors.EditorColors;
39 import com.intellij.openapi.editor.colors.EditorColorsManager;
40 import com.intellij.openapi.editor.event.*;
41 import com.intellij.openapi.editor.ex.DocumentEx;
42 import com.intellij.openapi.editor.markup.HighlighterLayer;
43 import com.intellij.openapi.editor.markup.HighlighterTargetArea;
44 import com.intellij.openapi.editor.markup.RangeHighlighter;
45 import com.intellij.openapi.editor.markup.TextAttributes;
46 import com.intellij.openapi.extensions.Extensions;
47 import com.intellij.openapi.project.DumbService;
48 import com.intellij.openapi.project.IndexNotReadyException;
49 import com.intellij.openapi.project.Project;
50 import com.intellij.openapi.util.Disposer;
51 import com.intellij.openapi.util.Key;
52 import com.intellij.openapi.util.TextRange;
53 import com.intellij.openapi.util.text.StringUtil;
54 import com.intellij.psi.PsiDocumentManager;
55 import com.intellij.psi.PsiElement;
56 import com.intellij.psi.PsiFile;
57 import com.intellij.psi.codeStyle.CodeStyleManager;
58 import com.intellij.psi.impl.source.codeStyle.CodeStyleManagerImpl;
59 import com.intellij.psi.util.PsiUtilCore;
60 import com.intellij.refactoring.rename.inplace.InplaceRefactoring;
61 import com.intellij.util.DocumentUtil;
62 import com.intellij.util.IncorrectOperationException;
63 import com.intellij.util.ObjectUtils;
64 import com.intellij.util.PairProcessor;
65 import com.intellij.util.containers.ContainerUtil;
66 import com.intellij.util.containers.HashMap;
67 import com.intellij.util.containers.IntArrayList;
68 import org.jetbrains.annotations.NonNls;
69 import org.jetbrains.annotations.NotNull;
70 import org.jetbrains.annotations.Nullable;
71
72 import java.beans.PropertyChangeEvent;
73 import java.beans.PropertyChangeListener;
74 import java.util.*;
75
76 public class TemplateState implements Disposable {
77   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.template.impl.TemplateState");
78   private Project myProject;
79   private Editor myEditor;
80
81   private TemplateImpl myTemplate;
82   private TemplateImpl myPrevTemplate;
83   private TemplateSegments mySegments = null;
84   private Map<String, String> myPredefinedVariableValues;
85
86   private RangeMarker myTemplateRange = null;
87   private final List<RangeHighlighter> myTabStopHighlighters = new ArrayList<>();
88   private int myCurrentVariableNumber = -1;
89   private int myCurrentSegmentNumber = -1;
90   private boolean ourLookupShown = false;
91
92   private boolean myDocumentChangesTerminateTemplate = true;
93   private boolean myDocumentChanged = false;
94
95   @Nullable private CommandAdapter myCommandListener;
96   @Nullable private CaretListener myCaretListener;
97   @Nullable private LookupListener myLookupListener;
98
99   private final List<TemplateEditingListener> myListeners = ContainerUtil.createLockFreeCopyOnWriteList();
100   private DocumentAdapter myEditorDocumentListener;
101   private final Map myProperties = new HashMap();
102   private boolean myTemplateIndented = false;
103   private Document myDocument;
104   private boolean myFinished;
105   @Nullable private PairProcessor<String, String> myProcessor;
106   private boolean mySelectionCalculated = false;
107   private boolean myStarted;
108
109   TemplateState(@NotNull Project project, @NotNull final Editor editor) {
110     myProject = project;
111     myEditor = editor;
112     myDocument = myEditor.getDocument();
113   }
114
115   private void initListeners() {
116     myEditorDocumentListener = new DocumentAdapter() {
117       @Override
118       public void beforeDocumentChange(DocumentEvent e) {
119         myDocumentChanged = true;
120       }
121     };
122     myLookupListener = new LookupAdapter() {
123       @Override
124       public void itemSelected(LookupEvent event) {
125         if (isCaretOutsideCurrentSegment() && !isCaretInsideNextVariable()) {
126           gotoEnd(true);
127         }
128       }
129     };
130     LookupManager.getInstance(myProject).addPropertyChangeListener(new PropertyChangeListener() {
131       @Override
132       public void propertyChange(PropertyChangeEvent evt) {
133         if (LookupManager.PROP_ACTIVE_LOOKUP.equals(evt.getPropertyName())) {
134           Lookup lookup = (Lookup)evt.getNewValue();
135           if (lookup != null) {
136             lookup.addLookupListener(myLookupListener);
137           }
138         }
139       }
140     }, this);
141     myCommandListener = new CommandAdapter() {
142       boolean started = false;
143
144       @Override
145       public void commandStarted(CommandEvent event) {
146         myDocumentChangesTerminateTemplate = isCaretOutsideCurrentSegment();
147         started = true;
148       }
149
150       @Override
151       public void beforeCommandFinished(CommandEvent event) {
152         if (started && !isDisposed()) {
153           Runnable runnable = () -> afterChangedUpdate();
154           final LookupImpl lookup = myEditor != null ? (LookupImpl)LookupManager.getActiveLookup(myEditor) : null;
155           if (lookup != null) {
156             lookup.performGuardedChange(runnable);
157           }
158           else {
159             runnable.run();
160           }
161         }
162       }
163     };
164
165     myCaretListener = new CaretAdapter() {
166       @Override
167       public void caretAdded(CaretEvent e) {
168         if (isMultiCaretMode()) {
169           finishTemplateEditing();
170         }
171       }
172
173       @Override
174       public void caretRemoved(CaretEvent e) {
175         if (isMultiCaretMode()) {
176           finishTemplateEditing();
177         }
178       }
179     };
180
181     if (myEditor != null) {
182       myEditor.getCaretModel().addCaretListener(myCaretListener);
183     }
184     myDocument.addDocumentListener(myEditorDocumentListener, this);
185     CommandProcessor.getInstance().addCommandListener(myCommandListener, this);
186   }
187
188   private boolean isCaretInsideNextVariable() {
189     if (myEditor != null && myCurrentVariableNumber >= 0) {
190       int nextVar = getNextVariableNumber(myCurrentVariableNumber);
191       TextRange range = nextVar < 0 ? null : getVariableRange(myTemplate.getVariableNameAt(nextVar));
192       return range != null && range.containsOffset(myEditor.getCaretModel().getOffset());
193     }
194     return false;
195   }
196
197   private boolean isCaretOutsideCurrentSegment() {
198     if (myEditor != null && myCurrentSegmentNumber >= 0) {
199       final int offset = myEditor.getCaretModel().getOffset();
200       return offset < mySegments.getSegmentStart(myCurrentSegmentNumber) || offset > mySegments.getSegmentEnd(myCurrentSegmentNumber);
201     }
202     return false;
203   }
204
205   private boolean isMultiCaretMode() {
206     return myEditor != null && myEditor.getCaretModel().getCaretCount() > 1;
207   }
208
209   @Override
210   public synchronized void dispose() {
211     if (myLookupListener != null) {
212       final LookupImpl lookup = myEditor != null ? (LookupImpl)LookupManager.getActiveLookup(myEditor) : null;
213       if (lookup != null) {
214         lookup.removeLookupListener(myLookupListener);
215       }
216       myLookupListener = null;
217     }
218
219     myEditorDocumentListener = null;
220     myCommandListener = null;
221     myCaretListener = null;
222
223     myProcessor = null;
224
225     //Avoid the leak of the editor
226     releaseAll();
227     myDocument = null;
228   }
229
230   public boolean isToProcessTab() {
231     if (isCaretOutsideCurrentSegment()) {
232       return false;
233     }
234     if (ourLookupShown) {
235       final LookupImpl lookup = (LookupImpl)LookupManager.getActiveLookup(myEditor);
236       if (lookup != null && !lookup.isFocused()) {
237         return true;
238       }
239     }
240
241     return !ourLookupShown;
242   }
243
244   private void setCurrentVariableNumber(int variableNumber) {
245     myCurrentVariableNumber = variableNumber;
246     final boolean isFinished = isFinished();
247     if (myDocument != null) {
248       ((DocumentEx)myDocument).setStripTrailingSpacesEnabled(isFinished);
249     }
250     myCurrentSegmentNumber = isFinished ? -1 : getCurrentSegmentNumber();
251   }
252
253   @Nullable
254   public TextResult getVariableValue(@NotNull String variableName) {
255     if (variableName.equals(TemplateImpl.SELECTION)) {
256       return new TextResult(StringUtil.notNullize(getSelectionBeforeTemplate()));
257     }
258     if (variableName.equals(TemplateImpl.END)) {
259       return new TextResult("");
260     }
261     if (myPredefinedVariableValues != null) {
262       String text = myPredefinedVariableValues.get(variableName);
263       if (text != null) {
264         return new TextResult(text);
265       }
266     }
267     CharSequence text = myDocument.getCharsSequence();
268     int segmentNumber = myTemplate.getVariableSegmentNumber(variableName);
269     if (segmentNumber < 0 || mySegments.getSegmentsCount() <= segmentNumber) {
270       return null;
271     }
272     int start = mySegments.getSegmentStart(segmentNumber);
273     int end = mySegments.getSegmentEnd(segmentNumber);
274     int length = myDocument.getTextLength();
275     if (start > length || end > length) {
276       return null;
277     }
278     return new TextResult(text.subSequence(start, end).toString());
279   }
280
281   @Nullable
282   private String getSelectionBeforeTemplate() {
283     return (String)getProperties().get(ExpressionContext.SELECTION);
284   }
285
286   @Nullable
287   public TextRange getCurrentVariableRange() {
288     int number = getCurrentSegmentNumber();
289     if (number == -1) return null;
290     return new TextRange(mySegments.getSegmentStart(number), mySegments.getSegmentEnd(number));
291   }
292
293   @Nullable
294   public TextRange getVariableRange(String variableName) {
295     int segment = myTemplate.getVariableSegmentNumber(variableName);
296     if (segment < 0) return null;
297
298     return new TextRange(mySegments.getSegmentStart(segment), mySegments.getSegmentEnd(segment));
299   }
300
301   public int getSegmentsCount() {
302     return mySegments.getSegmentsCount();
303   }
304
305   public TextRange getSegmentRange(int segment) {
306     return new TextRange(mySegments.getSegmentStart(segment), mySegments.getSegmentEnd(segment));
307   }
308
309   public boolean isFinished() {
310     return myCurrentVariableNumber < 0;
311   }
312
313   private void releaseAll() {
314     if (mySegments != null) {
315       mySegments.removeAll();
316       mySegments = null;
317     }
318     if (myTemplateRange != null) {
319       myTemplateRange.dispose();
320       myTemplateRange = null;
321     }
322     myPrevTemplate = myTemplate;
323     myTemplate = null;
324     myProject = null;
325     releaseEditor();
326   }
327
328   private void releaseEditor() {
329     if (myEditor != null) {
330       for (RangeHighlighter segmentHighlighter : myTabStopHighlighters) {
331         segmentHighlighter.dispose();
332       }
333       myTabStopHighlighters.clear();
334       myEditor = null;
335     }
336   }
337
338   public void start(@NotNull TemplateImpl template,
339                     @Nullable final PairProcessor<String, String> processor,
340                     @Nullable Map<String, String> predefinedVarValues) {
341     LOG.assertTrue(!myStarted, "Already started");
342     myStarted = true;
343
344     final PsiFile file = getPsiFile();
345     myTemplate = substituteTemplate(file, myEditor.getCaretModel().getOffset(), template);
346
347     myProcessor = processor;
348
349     DocumentReference[] refs = myDocument != null
350                                ? new DocumentReference[]{DocumentReferenceManager.getInstance().create(myDocument)}
351                                : null;
352     UndoManager.getInstance(myProject).undoableActionPerformed(new BasicUndoableAction(refs) {
353       @Override
354       public void undo() {
355         if (myDocument != null) {
356           fireTemplateCancelled();
357           LookupManager.getInstance(myProject).hideActiveLookup();
358           int oldVar = myCurrentVariableNumber;
359           setCurrentVariableNumber(-1);
360           currentVariableChanged(oldVar);
361         }
362       }
363
364       @Override
365       public void redo() {
366         //TODO:
367         // throw new UnexpectedUndoException("Not implemented");
368       }
369     });
370     myTemplateIndented = false;
371     myCurrentVariableNumber = -1;
372     mySegments = new TemplateSegments(myEditor);
373     myPrevTemplate = myTemplate;
374
375     //myArgument = argument;
376     myPredefinedVariableValues = predefinedVarValues;
377
378     if (myTemplate.isInline()) {
379       int caretOffset = myEditor.getCaretModel().getOffset();
380       myTemplateRange = myDocument.createRangeMarker(caretOffset, caretOffset + myTemplate.getTemplateText().length());
381     }
382     else {
383       preprocessTemplate(file, myEditor.getCaretModel().getOffset(), myTemplate.getTemplateText());
384       int caretOffset = myEditor.getCaretModel().getOffset();
385       myTemplateRange = myDocument.createRangeMarker(caretOffset, caretOffset);
386     }
387     myTemplateRange.setGreedyToLeft(true);
388     myTemplateRange.setGreedyToRight(true);
389
390     processAllExpressions(myTemplate);
391   }
392
393   private void fireTemplateCancelled() {
394     if (myFinished) return;
395     myFinished = true;
396     for (TemplateEditingListener listener : myListeners) {
397       listener.templateCancelled(myTemplate);
398     }
399   }
400
401   private static TemplateImpl substituteTemplate(final PsiFile file, int caretOffset, TemplateImpl template) {
402     for (TemplateSubstitutor substitutor : Extensions.getExtensions(TemplateSubstitutor.EP_NAME)) {
403       final TemplateImpl substituted = substitutor.substituteTemplate(file, caretOffset, template);
404       if (substituted != null) {
405         template = substituted;
406       }
407     }
408     return template;
409   }
410
411   private void preprocessTemplate(final PsiFile file, int caretOffset, final String textToInsert) {
412     for (TemplatePreprocessor preprocessor : Extensions.getExtensions(TemplatePreprocessor.EP_NAME)) {
413       preprocessor.preprocessTemplate(myEditor, file, caretOffset, textToInsert, myTemplate.getTemplateText());
414     }
415   }
416
417   private void processAllExpressions(@NotNull final TemplateImpl template) {
418     ApplicationManager.getApplication().runWriteAction(() -> {
419       if (!template.isInline()) myDocument.insertString(myTemplateRange.getStartOffset(), template.getTemplateText());
420       for (int i = 0; i < template.getSegmentsCount(); i++) {
421         int segmentOffset = myTemplateRange.getStartOffset() + template.getSegmentOffset(i);
422         mySegments.addSegment(segmentOffset, segmentOffset);
423       }
424
425       LOG.assertTrue(myTemplateRange.isValid(), getRangesDebugInfo());
426       calcResults(false);
427       LOG.assertTrue(myTemplateRange.isValid(), getRangesDebugInfo());
428       calcResults(false);  //Fixed SCR #[vk500] : all variables should be recalced twice on start.
429       LOG.assertTrue(myTemplateRange.isValid(), getRangesDebugInfo());
430       doReformat(null);
431
432       int nextVariableNumber = getNextVariableNumber(-1);
433
434       if (nextVariableNumber >= 0) {
435         fireWaitingForInput();
436       }
437
438       if (nextVariableNumber == -1) {
439         finishTemplateEditing();
440       }
441       else {
442         setCurrentVariableNumber(nextVariableNumber);
443         initTabStopHighlighters();
444         initListeners();
445         focusCurrentExpression();
446         currentVariableChanged(-1);
447         if (isMultiCaretMode()) {
448           finishTemplateEditing();
449         }
450       }
451     });
452   }
453
454   private String getRangesDebugInfo() {
455     return myTemplateRange + "\ntemplateKey: " + myTemplate.getKey() + "\ntemplateText: " + myTemplate.getTemplateText() +
456            "\ntemplateString: " + myTemplate;
457   }
458
459   private void doReformat(final TextRange range) {
460     RangeMarker rangeMarker = null;
461     if (range != null) {
462       rangeMarker = myDocument.createRangeMarker(range);
463       rangeMarker.setGreedyToLeft(true);
464       rangeMarker.setGreedyToRight(true);
465     }
466     final RangeMarker finalRangeMarker = rangeMarker;
467     final Runnable action = () -> {
468       IntArrayList indices = initEmptyVariables();
469       mySegments.setSegmentsGreedy(false);
470       LOG.assertTrue(myTemplateRange.isValid(),
471                      "template key: " + myTemplate.getKey() + "; " +
472                      "template text" + myTemplate.getTemplateText() + "; " +
473                      "variable number: " + getCurrentVariableNumber());
474       reformat(finalRangeMarker);
475       mySegments.setSegmentsGreedy(true);
476       restoreEmptyVariables(indices);
477     };
478     ApplicationManager.getApplication().runWriteAction(action);
479   }
480
481   public void setSegmentsGreedy(boolean greedy) {
482     mySegments.setSegmentsGreedy(greedy);
483   }
484
485   public void setTabStopHighlightersGreedy(boolean greedy) {
486     for (RangeHighlighter highlighter : myTabStopHighlighters) {
487       highlighter.setGreedyToLeft(greedy);
488       highlighter.setGreedyToRight(greedy);
489     }
490   }
491
492   private void shortenReferences() {
493     ApplicationManager.getApplication().runWriteAction(() -> {
494       final PsiFile file = getPsiFile();
495       if (file != null) {
496         IntArrayList indices = initEmptyVariables();
497         mySegments.setSegmentsGreedy(false);
498         for (TemplateOptionalProcessor processor : Extensions.getExtensions(TemplateOptionalProcessor.EP_NAME)) {
499           processor.processText(myProject, myTemplate, myDocument, myTemplateRange, myEditor);
500         }
501         mySegments.setSegmentsGreedy(true);
502         restoreEmptyVariables(indices);
503       }
504     });
505   }
506
507   private void afterChangedUpdate() {
508     if (isFinished()) return;
509     LOG.assertTrue(myTemplate != null, presentTemplate(myPrevTemplate));
510     if (myDocumentChanged) {
511       if (myDocumentChangesTerminateTemplate || mySegments.isInvalid()) {
512         final int oldIndex = myCurrentVariableNumber;
513         setCurrentVariableNumber(-1);
514         currentVariableChanged(oldIndex);
515         fireTemplateCancelled();
516       }
517       else {
518         calcResults(true);
519       }
520       myDocumentChanged = false;
521     }
522   }
523
524   private static String presentTemplate(@Nullable TemplateImpl template) {
525     if (template == null) {
526       return "no template";
527     }
528
529     String message = StringUtil.notNullize(template.getKey());
530     message += "\n\nTemplate#string: " + StringUtil.notNullize(template.getString());
531     message += "\n\nTemplate#text: " + StringUtil.notNullize(template.getTemplateText());
532     return message;
533   }
534
535   private String getExpressionString(int index) {
536     CharSequence text = myDocument.getCharsSequence();
537
538     if (!mySegments.isValid(index)) return "";
539
540     int start = mySegments.getSegmentStart(index);
541     int end = mySegments.getSegmentEnd(index);
542
543     return text.subSequence(start, end).toString();
544   }
545
546   private int getCurrentSegmentNumber() {
547     int varNumber = myCurrentVariableNumber;
548     if (varNumber == -1) {
549       return -1;
550     }
551     String variableName = myTemplate.getVariableNameAt(varNumber);
552     int segmentNumber = myTemplate.getVariableSegmentNumber(variableName);
553     if (segmentNumber < 0) {
554       LOG.error("No segment for variable: var=" + varNumber + "; name=" + variableName + "; " + presentTemplate(myTemplate) +
555                 "; offset: " + myEditor.getCaretModel().getOffset(), AttachmentFactory.createAttachment(myDocument));
556     }
557     return segmentNumber;
558   }
559
560   private void focusCurrentExpression() {
561     if (isFinished()) {
562       return;
563     }
564
565     PsiDocumentManager.getInstance(myProject).commitDocument(myDocument);
566
567     final int currentSegmentNumber = getCurrentSegmentNumber();
568
569     lockSegmentAtTheSameOffsetIfAny();
570
571     if (currentSegmentNumber < 0) return;
572     final int start = mySegments.getSegmentStart(currentSegmentNumber);
573     final int end = mySegments.getSegmentEnd(currentSegmentNumber);
574     if (end >= 0) {
575       myEditor.getCaretModel().moveToOffset(end);
576       myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
577       myEditor.getSelectionModel().removeSelection();
578       myEditor.getSelectionModel().setSelection(start, end);
579     }
580
581     DumbService.getInstance(myProject).withAlternativeResolveEnabled(() -> {
582       Expression expressionNode = getCurrentExpression();
583       List<TemplateExpressionLookupElement> lookupItems;
584       try {
585         lookupItems = getCurrentExpressionLookupItems();
586       }
587       catch (IndexNotReadyException e) {
588         lookupItems = Collections.emptyList();
589       }
590       final PsiFile psiFile = getPsiFile();
591       if (!lookupItems.isEmpty()) {
592         if (((TemplateManagerImpl)TemplateManager.getInstance(myProject)).shouldSkipInTests()) {
593           insertSingleItem(lookupItems);
594         }
595         else {
596           for (LookupElement lookupItem : lookupItems) {
597             assert lookupItem != null : expressionNode;
598           }
599
600           runLookup(lookupItems, expressionNode.getAdvertisingText());
601         }
602       }
603       else {
604         try {
605           Result result = expressionNode.calculateResult(getCurrentExpressionContext());
606           if (result != null) {
607             result.handleFocused(psiFile, myDocument, mySegments.getSegmentStart(currentSegmentNumber),
608                                  mySegments.getSegmentEnd(currentSegmentNumber));
609           }
610         }
611         catch (IndexNotReadyException ignore) {
612         }
613       }
614     });
615     focusCurrentHighlighter(true);
616   }
617
618   PsiFile getPsiFile() {
619     return PsiDocumentManager.getInstance(myProject).getPsiFile(myDocument);
620   }
621
622   private void insertSingleItem(List<TemplateExpressionLookupElement> lookupItems) {
623     TemplateExpressionLookupElement first = lookupItems.get(0);
624     EditorModificationUtil.insertStringAtCaret(myEditor, first.getLookupString());
625     first.handleTemplateInsert(lookupItems, Lookup.AUTO_INSERT_SELECT_CHAR);
626   }
627
628   @NotNull
629   List<TemplateExpressionLookupElement> getCurrentExpressionLookupItems() {
630     LookupElement[] elements = getCurrentExpression().calculateLookupItems(getCurrentExpressionContext());
631     if (elements == null) return Collections.emptyList();
632
633     List<TemplateExpressionLookupElement> result = ContainerUtil.newArrayList();
634     for (int i = 0; i < elements.length; i++) {
635       result.add(new TemplateExpressionLookupElement(this, elements[i], i));
636     }
637     return result;
638   }
639
640   ExpressionContext getCurrentExpressionContext() {
641     return createExpressionContext(mySegments.getSegmentStart(getCurrentSegmentNumber()));
642   }
643
644   Expression getCurrentExpression() {
645     return myTemplate.getExpressionAt(myCurrentVariableNumber);
646   }
647
648   private void runLookup(final List<TemplateExpressionLookupElement> lookupItems, @Nullable String advertisingText) {
649     if (myEditor == null) return;
650
651     final LookupManager lookupManager = LookupManager.getInstance(myProject);
652
653     final LookupImpl lookup = (LookupImpl)lookupManager.showLookup(myEditor, lookupItems.toArray(new LookupElement[lookupItems.size()]));
654     if (lookup == null) return;
655
656     if (CodeInsightSettings.getInstance().AUTO_POPUP_COMPLETION_LOOKUP && myEditor.getUserData(InplaceRefactoring.INPLACE_RENAMER) == null) {
657       lookup.setStartCompletionWhenNothingMatches(true);
658     }
659
660     if (advertisingText != null) {
661       lookup.addAdvertisement(advertisingText, null);
662     }
663     lookup.refreshUi(true, true);
664     ourLookupShown = true;
665     lookup.addLookupListener(new LookupAdapter() {
666       @Override
667       public void lookupCanceled(LookupEvent event) {
668         lookup.removeLookupListener(this);
669         ourLookupShown = false;
670       }
671
672       @Override
673       public void itemSelected(LookupEvent event) {
674         lookup.removeLookupListener(this);
675         if (isFinished()) return;
676         ourLookupShown = false;
677
678         LookupElement item = event.getItem();
679         if (item instanceof TemplateExpressionLookupElement) {
680           ((TemplateExpressionLookupElement)item).handleTemplateInsert(lookupItems, event.getCompletionChar());
681         }
682       }
683     });
684   }
685
686   private void unblockDocument() {
687     PsiDocumentManager.getInstance(myProject).commitDocument(myDocument);
688     PsiDocumentManager.getInstance(myProject).doPostponedOperationsAndUnblockDocument(myDocument);
689   }
690
691   // Hours spent fixing code : 3
692   void calcResults(final boolean isQuick) {
693     if (myProcessor != null && myCurrentVariableNumber >= 0) {
694       final String variableName = myTemplate.getVariableNameAt(myCurrentVariableNumber);
695       final TextResult value = getVariableValue(variableName);
696       if (value != null && !value.getText().isEmpty()) {
697         if (!myProcessor.process(variableName, value.getText())) {
698           finishTemplateEditing(); // nextTab(); ?
699           return;
700         }
701       }
702     }
703
704     fixOverlappedSegments(myCurrentSegmentNumber);
705
706     WriteCommandAction.runWriteCommandAction(myProject, () -> {
707       if (isDisposed()) {
708         return;
709       }
710       BitSet calcedSegments = new BitSet();
711       int maxAttempts = (myTemplate.getVariableCount() + 1) * 3;
712
713       do {
714         maxAttempts--;
715         calcedSegments.clear();
716         for (int i = myCurrentVariableNumber + 1; i < myTemplate.getVariableCount(); i++) {
717           String variableName = myTemplate.getVariableNameAt(i);
718           final int segmentNumber = myTemplate.getVariableSegmentNumber(variableName);
719           if (segmentNumber < 0) continue;
720           final Expression expression = myTemplate.getExpressionAt(i);
721           final Expression defaultValue = myTemplate.getDefaultValueAt(i);
722           String oldValue = getVariableValueText(variableName);
723           DumbService.getInstance(myProject).withAlternativeResolveEnabled(
724             () -> recalcSegment(segmentNumber, isQuick, expression, defaultValue));
725           final TextResult value = getVariableValue(variableName);
726           assert value != null : "name=" + variableName + "\ntext=" + myTemplate.getTemplateText();
727           String newValue = value.getText();
728           if (!newValue.equals(oldValue)) {
729             calcedSegments.set(segmentNumber);
730           }
731         }
732
733         List<TemplateDocumentChange> changes = ContainerUtil.newArrayList();
734         boolean selectionCalculated = false;
735         for (int i = 0; i < myTemplate.getSegmentsCount(); i++) {
736           if (!calcedSegments.get(i)) {
737             String variableName = myTemplate.getSegmentName(i);
738             if (variableName.equals(TemplateImpl.SELECTION)) {
739               if (mySelectionCalculated) {
740                 continue;
741               }
742               selectionCalculated = true;
743             }
744             if (TemplateImpl.END.equals(variableName)) continue; // No need to update end since it can be placed over some other variable
745             String newValue = getVariableValueText(variableName);
746             int start = mySegments.getSegmentStart(i);
747             int end = mySegments.getSegmentEnd(i);
748             changes.add(new TemplateDocumentChange(newValue, start, end, i));
749           }
750         }
751         executeChanges(changes);
752         if (selectionCalculated) {
753           mySelectionCalculated = true;
754         }
755       }
756       while (!calcedSegments.isEmpty() && maxAttempts >= 0);
757     });
758   }
759
760   private static class TemplateDocumentChange {
761     public final String newValue;
762     public final int startOffset;
763     public final int endOffset;
764     public final int segmentNumber;
765
766     private TemplateDocumentChange(String newValue, int startOffset, int endOffset, int segmentNumber) {
767       this.newValue = newValue;
768       this.startOffset = startOffset;
769       this.endOffset = endOffset;
770       this.segmentNumber = segmentNumber;
771     }
772   }
773
774   private void executeChanges(@NotNull List<TemplateDocumentChange> changes) {
775     if (isDisposed() || changes.isEmpty()) {
776       return;
777     }
778     if (changes.size() > 1) {
779       ContainerUtil.sort(changes, (o1, o2) -> {
780         int startDiff = o2.startOffset - o1.startOffset;
781         return startDiff != 0 ? startDiff : o2.endOffset - o1.endOffset;
782       });
783     }
784     DocumentUtil.executeInBulk(myDocument, true, () -> {
785       for (TemplateDocumentChange change : changes) {
786         replaceString(change.newValue, change.startOffset, change.endOffset, change.segmentNumber);
787       }
788     });
789   }
790
791   /**
792    * Must be invoked on every segment change in order to avoid ovelapping editing segment with its neibours
793    */
794   private void fixOverlappedSegments(int currentSegment) {
795     if (currentSegment >= 0) {
796       int currentSegmentStart = mySegments.getSegmentStart(currentSegment);
797       int currentSegmentEnd = mySegments.getSegmentEnd(currentSegment);
798       for (int i = 0; i < mySegments.getSegmentsCount(); i++) {
799         if (i > currentSegment) {
800           final int startOffset = mySegments.getSegmentStart(i);
801           if (currentSegmentStart <= startOffset && startOffset < currentSegmentEnd) {
802             mySegments.replaceSegmentAt(i, currentSegmentEnd, Math.max(mySegments.getSegmentEnd(i), currentSegmentEnd), true);
803           }
804         }
805         else if (i < currentSegment) {
806           final int endOffset = mySegments.getSegmentEnd(i);
807           if (currentSegmentStart < endOffset && endOffset <= currentSegmentEnd) {
808             mySegments.replaceSegmentAt(i, Math.min(mySegments.getSegmentStart(i), currentSegmentStart), currentSegmentStart, true);
809           }
810         }
811       }
812     }
813   }
814
815   @NotNull
816   private String getVariableValueText(String variableName) {
817     TextResult value = getVariableValue(variableName);
818     return value != null ? value.getText() : "";
819   }
820
821   private void recalcSegment(int segmentNumber, boolean isQuick, Expression expressionNode, Expression defaultValue) {
822     String oldValue = getExpressionString(segmentNumber);
823     int start = mySegments.getSegmentStart(segmentNumber);
824     int end = mySegments.getSegmentEnd(segmentNumber);
825
826     PsiDocumentManager.getInstance(myProject).commitDocument(myDocument);
827     PsiFile psiFile = getPsiFile();
828     PsiElement element = psiFile.findElementAt(start);
829     if (element != null) {
830       PsiUtilCore.ensureValid(element);
831     }
832
833     ExpressionContext context = createExpressionContext(start);
834     Result result = isQuick ? expressionNode.calculateQuickResult(context) : expressionNode.calculateResult(context);
835     if (isQuick && result == null) {
836       if (!oldValue.isEmpty()) {
837         return;
838       }
839     }
840
841     final boolean resultIsNullOrEmpty = result == null || result.equalsToText("", element);
842
843     // do not update default value of neighbour segment
844     if (resultIsNullOrEmpty && myCurrentSegmentNumber >= 0 &&
845         (mySegments.getSegmentStart(segmentNumber) == mySegments.getSegmentEnd(myCurrentSegmentNumber) ||
846          mySegments.getSegmentEnd(segmentNumber) == mySegments.getSegmentStart(myCurrentSegmentNumber))) {
847       return;
848     }
849     if (defaultValue != null && resultIsNullOrEmpty) {
850       result = defaultValue.calculateResult(context);
851     }
852     if (element != null) {
853       PsiUtilCore.ensureValid(element);
854     }
855     if (result == null || result.equalsToText(oldValue, element)) return;
856
857     replaceString(StringUtil.notNullize(result.toString()), start, end, segmentNumber);
858
859     if (result instanceof RecalculatableResult) {
860       IntArrayList indices = initEmptyVariables();
861       shortenReferences();
862       PsiDocumentManager.getInstance(myProject).commitDocument(myDocument);
863       ((RecalculatableResult)result)
864         .handleRecalc(psiFile, myDocument, mySegments.getSegmentStart(segmentNumber), mySegments.getSegmentEnd(segmentNumber));
865       restoreEmptyVariables(indices);
866     }
867   }
868
869   private void replaceString(String newValue, int start, int end, int segmentNumber) {
870     TextRange range = TextRange.create(start, end);
871     if (!TextRange.from(0, myDocument.getCharsSequence().length()).contains(range)) {
872       LOG.error("Diagnostic for EA-54980. Can't extract " + range + " range. " + presentTemplate(myTemplate),
873                 AttachmentFactory.createAttachment(myDocument));
874     }
875     String oldText = range.subSequence(myDocument.getCharsSequence()).toString();
876
877     if (!oldText.equals(newValue)) {
878       mySegments.setNeighboursGreedy(segmentNumber, false);
879       myDocument.replaceString(start, end, newValue);
880       int newEnd = start + newValue.length();
881       mySegments.replaceSegmentAt(segmentNumber, start, newEnd);
882       mySegments.setNeighboursGreedy(segmentNumber, true);
883       fixOverlappedSegments(segmentNumber);
884     }
885   }
886
887   public int getCurrentVariableNumber() {
888     return myCurrentVariableNumber;
889   }
890
891   public void previousTab() {
892     if (isFinished()) {
893       return;
894     }
895
896     myDocumentChangesTerminateTemplate = false;
897
898     final int oldVar = myCurrentVariableNumber;
899     int previousVariableNumber = getPreviousVariableNumber(oldVar);
900     if (previousVariableNumber >= 0) {
901       focusCurrentHighlighter(false);
902       calcResults(false);
903       doReformat(null);
904       setCurrentVariableNumber(previousVariableNumber);
905       focusCurrentExpression();
906       currentVariableChanged(oldVar);
907     }
908   }
909
910   public void nextTab() {
911     if (isFinished()) {
912       return;
913     }
914
915     //some psi operations may block the document, unblock here
916     unblockDocument();
917
918     myDocumentChangesTerminateTemplate = false;
919
920     final int oldVar = myCurrentVariableNumber;
921     int nextVariableNumber = getNextVariableNumber(oldVar);
922     if (nextVariableNumber == -1) {
923       calcResults(false);
924       ApplicationManager.getApplication().runWriteAction(() -> reformat(null));
925       finishTemplateEditing();
926       return;
927     }
928     focusCurrentHighlighter(false);
929     calcResults(false);
930     doReformat(null);
931     setCurrentVariableNumber(nextVariableNumber);
932     focusCurrentExpression();
933     currentVariableChanged(oldVar);
934   }
935
936   public void considerNextTabOnLookupItemSelected(LookupElement item) {
937     if (item != null) {
938       ExpressionContext context = getCurrentExpressionContext();
939       for (TemplateCompletionProcessor processor : Extensions.getExtensions(TemplateCompletionProcessor.EP_NAME)) {
940         if (!processor.nextTabOnItemSelected(context, item)) {
941           return;
942         }
943       }
944     }
945     TextRange range = getCurrentVariableRange();
946     if (range != null && range.getLength() > 0) {
947       int caret = myEditor.getCaretModel().getOffset();
948       if (caret == range.getEndOffset() || isCaretInsideNextVariable()) {
949         nextTab();
950       }
951       else if (caret > range.getEndOffset()) {
952         gotoEnd(true);
953       }
954     }
955   }
956
957   private void lockSegmentAtTheSameOffsetIfAny() {
958     mySegments.lockSegmentAtTheSameOffsetIfAny(getCurrentSegmentNumber());
959   }
960
961   private ExpressionContext createExpressionContext(final int start) {
962     return new ExpressionContext() {
963       @Override
964       public Project getProject() {
965         return myProject;
966       }
967
968       @Override
969       public Editor getEditor() {
970         return myEditor;
971       }
972
973       @Override
974       public int getStartOffset() {
975         return start;
976       }
977
978       @Override
979       public int getTemplateStartOffset() {
980         if (myTemplateRange == null) {
981           return -1;
982         }
983         return myTemplateRange.getStartOffset();
984       }
985
986       @Override
987       public int getTemplateEndOffset() {
988         if (myTemplateRange == null) {
989           return -1;
990         }
991         return myTemplateRange.getEndOffset();
992       }
993
994       @Override
995       public <T> T getProperty(Key<T> key) {
996         //noinspection unchecked
997         return (T)myProperties.get(key);
998       }
999
1000       @Nullable
1001       @Override
1002       public PsiElement getPsiElementAtStartOffset() {
1003         Project project = getProject();
1004         int templateStartOffset = getTemplateStartOffset();
1005         int offset = templateStartOffset > 0 ? getTemplateStartOffset() - 1 : getTemplateStartOffset();
1006
1007         PsiDocumentManager.getInstance(project).commitAllDocuments();
1008
1009         Editor editor = getEditor();
1010         if (editor == null) {
1011           return null;
1012         }
1013         PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
1014         return file == null ? null : file.findElementAt(offset);
1015       }
1016     };
1017   }
1018
1019   public void gotoEnd(boolean brokenOff) {
1020     if (isDisposed()) return;
1021     LookupManager.getInstance(myProject).hideActiveLookup();
1022     calcResults(false);
1023     if (!brokenOff) {
1024       doReformat(null);
1025     }
1026     setFinalEditorState(brokenOff);
1027     cleanupTemplateState(brokenOff);
1028   }
1029
1030   public void gotoEnd() {
1031     gotoEnd(true);
1032   }
1033
1034   /**
1035    * @deprecated use this#gotoEnd(true)
1036    */
1037   public void cancelTemplate() {
1038     if (isDisposed()) return;
1039     LookupManager.getInstance(myProject).hideActiveLookup();
1040     cleanupTemplateState(true);
1041   }
1042
1043   private void finishTemplateEditing() {
1044     if (isDisposed()) return;
1045     LookupManager.getInstance(myProject).hideActiveLookup();
1046     setFinalEditorState(false);
1047     cleanupTemplateState(false);
1048   }
1049
1050   private void setFinalEditorState(boolean brokenOff) {
1051     myEditor.getSelectionModel().removeSelection();
1052     if (brokenOff && !((TemplateManagerImpl)TemplateManager.getInstance(myProject)).shouldSkipInTests()) return;
1053
1054     int selectionSegment = myTemplate.getVariableSegmentNumber(TemplateImpl.SELECTION);
1055     int endSegmentNumber = selectionSegment >= 0 && getSelectionBeforeTemplate() == null ? selectionSegment : myTemplate.getEndSegmentNumber();
1056     int offset = -1;
1057     if (endSegmentNumber >= 0) {
1058       offset = mySegments.getSegmentStart(endSegmentNumber);
1059     }
1060     else {
1061       if (!myTemplate.isSelectionTemplate() && !myTemplate.isInline()) { //do not move caret to the end of range for selection templates
1062         offset = myTemplateRange.getEndOffset();
1063       }
1064     }
1065
1066     if (isMultiCaretMode() && getCurrentVariableNumber() > -1) {
1067       offset = -1; //do not move caret in multicaret mode if at least one tab had been made already
1068     }
1069
1070     if (offset >= 0) {
1071       myEditor.getCaretModel().moveToOffset(offset);
1072       myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
1073     }
1074
1075     int selStart = myTemplate.getSelectionStartSegmentNumber();
1076     int selEnd = myTemplate.getSelectionEndSegmentNumber();
1077     if (selStart >= 0 && selEnd >= 0) {
1078       myEditor.getSelectionModel().setSelection(mySegments.getSegmentStart(selStart), mySegments.getSegmentStart(selEnd));
1079     }
1080   }
1081
1082   boolean isDisposed() {
1083     return myDocument == null;
1084   }
1085
1086   private void cleanupTemplateState(boolean brokenOff) {
1087     final Editor editor = myEditor;
1088     fireBeforeTemplateFinished();
1089     if (!isDisposed()) {
1090       int oldVar = myCurrentVariableNumber;
1091       setCurrentVariableNumber(-1);
1092       currentVariableChanged(oldVar);
1093       TemplateManagerImpl.clearTemplateState(editor);
1094       fireTemplateFinished(brokenOff);
1095     }
1096     myListeners.clear();
1097     Disposer.dispose(this);
1098   }
1099
1100   private int getNextVariableNumber(int currentVariableNumber) {
1101     for (int i = currentVariableNumber + 1; i < myTemplate.getVariableCount(); i++) {
1102       if (checkIfTabStop(i)) {
1103         return i;
1104       }
1105     }
1106     return -1;
1107   }
1108
1109   private int getPreviousVariableNumber(int currentVariableNumber) {
1110     for (int i = currentVariableNumber - 1; i >= 0; i--) {
1111       if (checkIfTabStop(i)) {
1112         return i;
1113       }
1114     }
1115     return -1;
1116   }
1117
1118   private boolean checkIfTabStop(int currentVariableNumber) {
1119     Expression expression = myTemplate.getExpressionAt(currentVariableNumber);
1120     if (expression == null) {
1121       return false;
1122     }
1123     if (myCurrentVariableNumber == -1) {
1124       if (myTemplate.skipOnStart(currentVariableNumber)) return false;
1125     }
1126     String variableName = myTemplate.getVariableNameAt(currentVariableNumber);
1127     if (!(myPredefinedVariableValues != null && myPredefinedVariableValues.containsKey(variableName))) {
1128       if (myTemplate.isAlwaysStopAt(currentVariableNumber)) {
1129         return true;
1130       }
1131     }
1132     int segmentNumber = myTemplate.getVariableSegmentNumber(variableName);
1133     if (segmentNumber < 0) return false;
1134     int start = mySegments.getSegmentStart(segmentNumber);
1135     ExpressionContext context = createExpressionContext(start);
1136     Result result = expression.calculateResult(context);
1137     if (result == null) {
1138       return true;
1139     }
1140     LookupElement[] items = expression.calculateLookupItems(context);
1141     return items != null && items.length > 1;
1142   }
1143
1144   private IntArrayList initEmptyVariables() {
1145     int endSegmentNumber = myTemplate.getEndSegmentNumber();
1146     int selStart = myTemplate.getSelectionStartSegmentNumber();
1147     int selEnd = myTemplate.getSelectionEndSegmentNumber();
1148     IntArrayList indices = new IntArrayList();
1149     List<TemplateDocumentChange> changes = ContainerUtil.newArrayList();
1150     for (int i = 0; i < myTemplate.getSegmentsCount(); i++) {
1151       int length = mySegments.getSegmentEnd(i) - mySegments.getSegmentStart(i);
1152       if (length != 0) continue;
1153       if (i == endSegmentNumber || i == selStart || i == selEnd) continue;
1154
1155       String name = myTemplate.getSegmentName(i);
1156       for (int j = 0; j < myTemplate.getVariableCount(); j++) {
1157         if (myTemplate.getVariableNameAt(j).equals(name)) {
1158           Expression e = myTemplate.getExpressionAt(j);
1159           @NonNls String marker = "a";
1160           if (e instanceof MacroCallNode) {
1161             marker = ((MacroCallNode)e).getMacro().getDefaultValue();
1162           }
1163           changes.add(new TemplateDocumentChange(marker, mySegments.getSegmentStart(i), mySegments.getSegmentEnd(i), i));
1164           indices.add(i);
1165           break;
1166         }
1167       }
1168     }
1169     executeChanges(changes);
1170     return indices;
1171   }
1172
1173   private void restoreEmptyVariables(IntArrayList indices) {
1174     List<TextRange> rangesToRemove = ContainerUtil.newArrayList();
1175     for (int i = 0; i < indices.size(); i++) {
1176       int index = indices.get(i);
1177       rangesToRemove.add(TextRange.create(mySegments.getSegmentStart(index), mySegments.getSegmentEnd(index)));
1178     }
1179     Collections.sort(rangesToRemove, (o1, o2) -> {
1180       int startDiff = o2.getStartOffset() - o1.getStartOffset();
1181       return startDiff != 0 ? startDiff : o2.getEndOffset() - o1.getEndOffset();
1182     });
1183     DocumentUtil.executeInBulk(myDocument, true, () -> {
1184       if (isDisposed()) {
1185         return;
1186       }
1187       for (TextRange range : rangesToRemove) {
1188         myDocument.deleteString(range.getStartOffset(), range.getEndOffset());
1189       }
1190     });
1191   }
1192
1193   private void initTabStopHighlighters() {
1194     final Set<String> vars = new HashSet<>();
1195     for (int i = 0; i < myTemplate.getVariableCount(); i++) {
1196       String variableName = myTemplate.getVariableNameAt(i);
1197       if (!vars.add(variableName)) continue;
1198       int segmentNumber = myTemplate.getVariableSegmentNumber(variableName);
1199       if (segmentNumber < 0) continue;
1200       RangeHighlighter segmentHighlighter = getSegmentHighlighter(segmentNumber, false, false);
1201       myTabStopHighlighters.add(segmentHighlighter);
1202     }
1203
1204     int endSegmentNumber = myTemplate.getEndSegmentNumber();
1205     if (endSegmentNumber >= 0) {
1206       RangeHighlighter segmentHighlighter = getSegmentHighlighter(endSegmentNumber, false, true);
1207       myTabStopHighlighters.add(segmentHighlighter);
1208     }
1209   }
1210
1211   private RangeHighlighter getSegmentHighlighter(int segmentNumber, boolean isSelected, boolean isEnd) {
1212     final TextAttributes lvAttr = EditorColorsManager.getInstance().getGlobalScheme().getAttributes(EditorColors.LIVE_TEMPLATE_ATTRIBUTES);
1213     TextAttributes attributes = isSelected ? lvAttr : new TextAttributes();
1214     TextAttributes endAttributes = new TextAttributes();
1215
1216     int start = mySegments.getSegmentStart(segmentNumber);
1217     int end = mySegments.getSegmentEnd(segmentNumber);
1218     RangeHighlighter segmentHighlighter = myEditor.getMarkupModel()
1219       .addRangeHighlighter(start, end, HighlighterLayer.LAST + 1, isEnd ? endAttributes : attributes, HighlighterTargetArea.EXACT_RANGE);
1220     segmentHighlighter.setGreedyToLeft(true);
1221     segmentHighlighter.setGreedyToRight(true);
1222     return segmentHighlighter;
1223   }
1224
1225   private void focusCurrentHighlighter(boolean toSelect) {
1226     if (isFinished()) {
1227       return;
1228     }
1229     if (myCurrentVariableNumber >= myTabStopHighlighters.size()) {
1230       return;
1231     }
1232     RangeHighlighter segmentHighlighter = myTabStopHighlighters.get(myCurrentVariableNumber);
1233     if (segmentHighlighter != null) {
1234       final int segmentNumber = getCurrentSegmentNumber();
1235       RangeHighlighter newSegmentHighlighter = getSegmentHighlighter(segmentNumber, toSelect, false);
1236       if (newSegmentHighlighter != null) {
1237         segmentHighlighter.dispose();
1238         myTabStopHighlighters.set(myCurrentVariableNumber, newSegmentHighlighter);
1239       }
1240     }
1241   }
1242
1243   private void reformat(RangeMarker rangeMarkerToReformat) {
1244     final PsiFile file = getPsiFile();
1245     if (file != null) {
1246       CodeStyleManager style = CodeStyleManager.getInstance(myProject);
1247       DumbService.getInstance(myProject).withAlternativeResolveEnabled(() -> {
1248         for (TemplateOptionalProcessor optionalProcessor : Extensions.getExtensions(TemplateOptionalProcessor.EP_NAME)) {
1249           optionalProcessor.processText(myProject, myTemplate, myDocument, myTemplateRange, myEditor);
1250         }
1251       });
1252       PsiDocumentManager.getInstance(myProject).doPostponedOperationsAndUnblockDocument(myDocument);
1253       // for Python, we need to indent the template even if reformatting is enabled, because otherwise indents would be broken
1254       // and reformat wouldn't be able to fix them
1255       if (myTemplate.isToIndent()) {
1256         if (!myTemplateIndented) {
1257           LOG.assertTrue(myTemplateRange.isValid(), presentTemplate(myTemplate));
1258           smartIndent(myTemplateRange.getStartOffset(), myTemplateRange.getEndOffset());
1259           myTemplateIndented = true;
1260         }
1261       }
1262       if (myTemplate.isToReformat()) {
1263         try {
1264           int endSegmentNumber = myTemplate.getEndSegmentNumber();
1265           PsiDocumentManager.getInstance(myProject).commitDocument(myDocument);
1266           RangeMarker dummyAdjustLineMarkerRange = null;
1267           int endVarOffset = -1;
1268           if (endSegmentNumber >= 0) {
1269             endVarOffset = mySegments.getSegmentStart(endSegmentNumber);
1270             TextRange range = CodeStyleManagerImpl.insertNewLineIndentMarker(file, myDocument, endVarOffset);
1271             if (range != null) dummyAdjustLineMarkerRange = myDocument.createRangeMarker(range);
1272           }
1273           int reformatStartOffset = myTemplateRange.getStartOffset();
1274           int reformatEndOffset = myTemplateRange.getEndOffset();
1275           if (rangeMarkerToReformat != null) {
1276             reformatStartOffset = rangeMarkerToReformat.getStartOffset();
1277             reformatEndOffset = rangeMarkerToReformat.getEndOffset();
1278           }
1279           if (dummyAdjustLineMarkerRange == null && endVarOffset >= 0) {
1280             // There is a possible case that indent marker element was not inserted (e.g. because there is no blank line
1281             // at the target offset). However, we want to reformat white space adjacent to the current template (if any).
1282             PsiElement whiteSpaceElement = CodeStyleManagerImpl.findWhiteSpaceNode(file, endVarOffset);
1283             if (whiteSpaceElement != null) {
1284               TextRange whiteSpaceRange = whiteSpaceElement.getTextRange();
1285               if (whiteSpaceElement.getContainingFile() != null) {
1286                 // Support injected white space nodes.
1287                 whiteSpaceRange = InjectedLanguageManager.getInstance(file.getProject()).injectedToHost(whiteSpaceElement, whiteSpaceRange);
1288               }
1289               reformatStartOffset = Math.min(reformatStartOffset, whiteSpaceRange.getStartOffset());
1290               reformatEndOffset = Math.max(reformatEndOffset, whiteSpaceRange.getEndOffset());
1291             }
1292           }
1293           style.reformatText(file, reformatStartOffset, reformatEndOffset);
1294           PsiDocumentManager.getInstance(myProject).commitDocument(myDocument);
1295           PsiDocumentManager.getInstance(myProject).doPostponedOperationsAndUnblockDocument(myDocument);
1296
1297           if (dummyAdjustLineMarkerRange != null && dummyAdjustLineMarkerRange.isValid()) {
1298             //[ven] TODO: [max] correct javadoc reformatting to eliminate isValid() check!!!
1299             mySegments.replaceSegmentAt(endSegmentNumber, dummyAdjustLineMarkerRange.getStartOffset(), dummyAdjustLineMarkerRange.getEndOffset());
1300             myDocument.deleteString(dummyAdjustLineMarkerRange.getStartOffset(), dummyAdjustLineMarkerRange.getEndOffset());
1301             PsiDocumentManager.getInstance(myProject).commitDocument(myDocument);
1302           }
1303           if (endSegmentNumber >= 0) {
1304             final int offset = mySegments.getSegmentStart(endSegmentNumber);
1305             final int lineStart = myDocument.getLineStartOffset(myDocument.getLineNumber(offset));
1306             // if $END$ is at line start, put it at correct indentation
1307             if (myDocument.getCharsSequence().subSequence(lineStart, offset).toString().trim().isEmpty()) {
1308               final int adjustedOffset = style.adjustLineIndent(file, offset);
1309               mySegments.replaceSegmentAt(endSegmentNumber, adjustedOffset, adjustedOffset);
1310             }
1311           }
1312         }
1313         catch (IncorrectOperationException e) {
1314           LOG.error(e);
1315         }
1316       }
1317     }
1318   }
1319
1320   private void smartIndent(int startOffset, int endOffset) {
1321     int startLineNum = myDocument.getLineNumber(startOffset);
1322     int endLineNum = myDocument.getLineNumber(endOffset);
1323     if (endLineNum == startLineNum) {
1324       return;
1325     }
1326
1327     int selectionIndent = -1;
1328     int selectionStartLine = -1;
1329     int selectionEndLine = -1;
1330     int selectionSegment = myTemplate.getVariableSegmentNumber(TemplateImpl.SELECTION);
1331     if (selectionSegment >= 0) {
1332       int selectionStart = myTemplate.getSegmentOffset(selectionSegment);
1333       selectionIndent = 0;
1334       String templateText = myTemplate.getTemplateText();
1335       while (selectionStart > 0 && templateText.charAt(selectionStart - 1) == ' ') {
1336         // TODO handle tabs
1337         selectionIndent++;
1338         selectionStart--;
1339       }
1340       selectionStartLine = myDocument.getLineNumber(mySegments.getSegmentStart(selectionSegment));
1341       selectionEndLine = myDocument.getLineNumber(mySegments.getSegmentEnd(selectionSegment));
1342     }
1343
1344     int indentLineNum = startLineNum;
1345
1346     int lineLength = 0;
1347     for (; indentLineNum >= 0; indentLineNum--) {
1348       lineLength = myDocument.getLineEndOffset(indentLineNum) - myDocument.getLineStartOffset(indentLineNum);
1349       if (lineLength > 0) {
1350         break;
1351       }
1352     }
1353     if (indentLineNum < 0) {
1354       return;
1355     }
1356     StringBuilder buffer = new StringBuilder();
1357     CharSequence text = myDocument.getCharsSequence();
1358     for (int i = 0; i < lineLength; i++) {
1359       char ch = text.charAt(myDocument.getLineStartOffset(indentLineNum) + i);
1360       if (ch != ' ' && ch != '\t') {
1361         break;
1362       }
1363       buffer.append(ch);
1364     }
1365     if (buffer.length() == 0 && selectionIndent <= 0 || startLineNum >= endLineNum) {
1366       return;
1367     }
1368     String stringToInsert = buffer.toString();
1369     int finalSelectionStartLine = selectionStartLine;
1370     int finalSelectionEndLine = selectionEndLine;
1371     int finalSelectionIndent = selectionIndent;
1372     DocumentUtil.executeInBulk(myDocument, true, () -> {
1373       for (int i = startLineNum + 1; i <= endLineNum; i++) {
1374         if (i > finalSelectionStartLine && i <= finalSelectionEndLine) {
1375           myDocument.insertString(myDocument.getLineStartOffset(i), StringUtil.repeatSymbol(' ', finalSelectionIndent));
1376         }
1377         else {
1378           myDocument.insertString(myDocument.getLineStartOffset(i), stringToInsert);
1379         }
1380       }
1381     });
1382   }
1383
1384   public void addTemplateStateListener(TemplateEditingListener listener) {
1385     myListeners.add(listener);
1386   }
1387
1388   private void fireTemplateFinished(boolean brokenOff) {
1389     if (myFinished) return;
1390     myFinished = true;
1391     for (TemplateEditingListener listener : myListeners) {
1392       listener.templateFinished(ObjectUtils.chooseNotNull(myTemplate, myPrevTemplate), brokenOff);
1393     }
1394   }
1395
1396   private void fireBeforeTemplateFinished() {
1397     for (TemplateEditingListener listener : myListeners) {
1398       listener.beforeTemplateFinished(this, myTemplate);
1399     }
1400   }
1401
1402   private void fireWaitingForInput() {
1403     for (TemplateEditingListener listener : myListeners) {
1404       listener.waitingForInput(myTemplate);
1405     }
1406   }
1407
1408   private void currentVariableChanged(int oldIndex) {
1409     for (TemplateEditingListener listener : myListeners) {
1410       listener.currentVariableChanged(this, myTemplate, oldIndex, myCurrentVariableNumber);
1411     }
1412     if (myCurrentSegmentNumber < 0) {
1413       if (myCurrentVariableNumber >= 0) {
1414         LOG.error("A variable with no segment: " + myCurrentVariableNumber + "; " + presentTemplate(myTemplate));
1415       }
1416       Disposer.dispose(this);
1417     }
1418   }
1419
1420   public Map getProperties() {
1421     return myProperties;
1422   }
1423
1424   public TemplateImpl getTemplate() {
1425     return myTemplate;
1426   }
1427
1428   public Editor getEditor() {
1429     return myEditor;
1430   }
1431 }