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