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