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