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