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