refactoring, zen coding shouldn't block live templates
[idea/community.git] / platform / lang-impl / src / com / intellij / codeInsight / template / impl / TemplateState.java
1 /*
2  * Copyright 2000-2009 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.AutoPopupController;
20 import com.intellij.codeInsight.completion.CompletionInitializationContext;
21 import com.intellij.codeInsight.completion.InsertionContext;
22 import com.intellij.codeInsight.completion.OffsetMap;
23 import com.intellij.codeInsight.lookup.*;
24 import com.intellij.codeInsight.template.*;
25 import com.intellij.lang.LanguageLiteralEscapers;
26 import com.intellij.openapi.Disposable;
27 import com.intellij.openapi.application.ApplicationManager;
28 import com.intellij.openapi.command.CommandAdapter;
29 import com.intellij.openapi.command.CommandEvent;
30 import com.intellij.openapi.command.CommandProcessor;
31 import com.intellij.openapi.command.WriteCommandAction;
32 import com.intellij.openapi.command.undo.DocumentReference;
33 import com.intellij.openapi.command.undo.DocumentReferenceManager;
34 import com.intellij.openapi.command.undo.UndoManager;
35 import com.intellij.openapi.command.undo.UndoableAction;
36 import com.intellij.openapi.diagnostic.Logger;
37 import com.intellij.openapi.editor.*;
38 import com.intellij.openapi.editor.event.DocumentAdapter;
39 import com.intellij.openapi.editor.event.DocumentEvent;
40 import com.intellij.openapi.editor.ex.DocumentEx;
41 import com.intellij.openapi.editor.markup.*;
42 import com.intellij.openapi.extensions.Extensions;
43 import com.intellij.openapi.project.Project;
44 import com.intellij.openapi.util.Key;
45 import com.intellij.openapi.util.TextRange;
46 import com.intellij.psi.PsiDocumentManager;
47 import com.intellij.psi.PsiElement;
48 import com.intellij.psi.PsiFile;
49 import com.intellij.psi.codeStyle.CodeStyleManager;
50 import com.intellij.util.IncorrectOperationException;
51 import com.intellij.util.PairProcessor;
52 import com.intellij.util.containers.HashMap;
53 import com.intellij.util.containers.IntArrayList;
54 import org.jetbrains.annotations.NonNls;
55 import org.jetbrains.annotations.NotNull;
56 import org.jetbrains.annotations.Nullable;
57
58 import java.awt.*;
59 import java.util.ArrayList;
60 import java.util.BitSet;
61 import java.util.List;
62 import java.util.Map;
63
64 /**
65  *
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 TemplateSegments mySegments = null;
74   private Map<String, String> myPredefinedVariableValues;
75
76   private RangeMarker myTemplateRange = null;
77   private final ArrayList<RangeHighlighter> myTabStopHighlighters = new ArrayList<RangeHighlighter>();
78   private int myCurrentVariableNumber = -1;
79   private int myCurrentSegmentNumber = -1;
80   private boolean toProcessTab = true;
81
82   private boolean myDocumentChangesTerminateTemplate = true;
83   private boolean myDocumentChanged = false;
84
85   private CommandAdapter myCommandListener;
86
87   private List<TemplateEditingListener> myListeners = new ArrayList<TemplateEditingListener>();
88   private DocumentAdapter myEditorDocumentListener;
89   private final Map myProperties = new HashMap();
90   private boolean myTemplateIndented = false;
91   private Document myDocument;
92   private boolean myFinished;
93   @Nullable private PairProcessor<String, String> myProcessor;
94
95   public TemplateState(@NotNull Project project, final Editor editor) {
96     myProject = project;
97     myEditor = editor;
98     myDocument = myEditor.getDocument();
99   }
100
101   private void initListeners() {
102     myEditorDocumentListener = new DocumentAdapter() {
103       public void beforeDocumentChange(DocumentEvent e) {
104         myDocumentChanged = true;
105       }
106     };
107
108     myCommandListener = new CommandAdapter() {
109       boolean started = false;
110
111       public void commandStarted(CommandEvent event) {
112         if (myEditor != null) {
113           final int offset = myEditor.getCaretModel().getOffset();
114           myDocumentChangesTerminateTemplate = myCurrentSegmentNumber >= 0 &&
115                                                (offset < mySegments.getSegmentStart(myCurrentSegmentNumber) ||
116                                                 offset > mySegments.getSegmentEnd(myCurrentSegmentNumber));
117         }
118         started = true;
119       }
120
121       public void beforeCommandFinished(CommandEvent event) {
122         if (started) {
123           afterChangedUpdate();
124         }
125       }
126     };
127
128     myDocument.addDocumentListener(myEditorDocumentListener);
129     CommandProcessor.getInstance().addCommandListener(myCommandListener);
130   }
131
132   public synchronized void dispose() {
133     if (myEditorDocumentListener != null) {
134       myDocument.removeDocumentListener(myEditorDocumentListener);
135       myEditorDocumentListener = null;
136     }
137     if (myCommandListener != null) {
138       CommandProcessor.getInstance().removeCommandListener(myCommandListener);
139       myCommandListener = null;
140     }
141
142     myProcessor = null;
143
144     //Avoid the leak of the editor
145     releaseEditor();
146     myDocument = null;
147   }
148
149   public boolean isToProcessTab() {
150     return toProcessTab;
151   }
152
153   private void setCurrentVariableNumber(int variableNumber) {
154     myCurrentVariableNumber = variableNumber;
155     final boolean isFinished = variableNumber < 0;
156     ((DocumentEx)myDocument).setStripTrailingSpacesEnabled(isFinished);
157     myCurrentSegmentNumber = isFinished ? -1 : getCurrentSegmentNumber();
158   }
159
160   @Nullable
161   public String getTrimmedVariableValue(int variableIndex) {
162     final TextResult value = getVariableValue(myTemplate.getVariableNameAt(variableIndex));
163     return value == null ? null : value.getText().trim();
164   }
165
166   @Nullable
167   public TextResult getVariableValue(@NotNull String variableName) {
168     if (variableName.equals(TemplateImpl.SELECTION)) {
169       final String selection = (String)getProperties().get(ExpressionContext.SELECTION);
170       return new TextResult(selection == null ? "" : selection);
171     }
172     if (variableName.equals(TemplateImpl.END)) {
173       return new TextResult("");
174     }
175     if (myPredefinedVariableValues != null && myPredefinedVariableValues.containsKey(variableName)) {
176       return new TextResult(myPredefinedVariableValues.get(variableName));
177     }
178     CharSequence text = myDocument.getCharsSequence();
179     int segmentNumber = myTemplate.getVariableSegmentNumber(variableName);
180     if (segmentNumber < 0) {
181       return null;
182     }
183     int start = mySegments.getSegmentStart(segmentNumber);
184     int end = mySegments.getSegmentEnd(segmentNumber);
185     int length = myDocument.getTextLength();
186     if (start > length || end > length) {
187       return null;
188     }
189     return new TextResult(text.subSequence(start, end).toString());
190   }
191
192   @Nullable
193   public TextRange getCurrentVariableRange() {
194     int number = getCurrentSegmentNumber();
195     if (number == -1) return null;
196     return new TextRange(mySegments.getSegmentStart(number), mySegments.getSegmentEnd(number));
197   }
198
199   @Nullable
200   public TextRange getVariableRange(int variableIndex) {
201     return getVariableRange(myTemplate.getVariableNameAt(variableIndex));
202   }
203
204   @Nullable
205   public TextRange getVariableRange(String variableName) {
206     int segment = myTemplate.getVariableSegmentNumber(variableName);
207     if (segment < 0) return null;
208
209     return new TextRange(mySegments.getSegmentStart(segment), mySegments.getSegmentEnd(segment));
210   }
211
212   public boolean isFinished() {
213     return myCurrentVariableNumber < 0;
214   }
215
216   private void releaseAll() {
217     if (mySegments != null) {
218       mySegments.removeAll();
219       mySegments = null;
220     }
221     myTemplateRange = null;
222     myTemplate = null;
223     releaseEditor();
224     myTabStopHighlighters.clear();
225   }
226
227   private void releaseEditor() {
228     if (myEditor != null) {
229       for (RangeHighlighter segmentHighlighter : myTabStopHighlighters) {
230         myEditor.getMarkupModel().removeHighlighter(segmentHighlighter);
231       }
232
233       myEditor = null;
234     }
235   }
236
237   public void start(TemplateImpl template,
238                     @Nullable final PairProcessor<String, String> processor,
239                     @Nullable Map<String, String> predefinedVarValues) {
240     PsiDocumentManager.getInstance(myProject).commitAllDocuments();
241
242     myProcessor = processor;
243
244     final DocumentReference[] refs =
245       myDocument == null ? null : new DocumentReference[]{DocumentReferenceManager.getInstance().create(myDocument)};
246
247     UndoManager.getInstance(myProject).undoableActionPerformed(new UndoableAction() {
248       public void undo() {
249         if (myDocument != null) {
250           fireTemplateCancelled();
251           LookupManager.getInstance(myProject).hideActiveLookup();
252           int oldVar = myCurrentVariableNumber;
253           setCurrentVariableNumber(-1);
254           currentVariableChanged(oldVar);
255         }
256       }
257
258       public void redo() {
259         //TODO:
260         // throw new UnexpectedUndoException("Not implemented");
261       }
262
263       public DocumentReference[] getAffectedDocuments() {
264         return refs;
265       }
266
267       public boolean isGlobal() {
268         return false;
269       }
270     });
271     myTemplateIndented = false;
272     myCurrentVariableNumber = -1;
273     mySegments = new TemplateSegments(myEditor);
274     myTemplate = template;
275     //myArgument = argument;
276     myPredefinedVariableValues = predefinedVarValues;
277
278     if (template.isInline()) {
279       int caretOffset = myEditor.getCaretModel().getOffset();
280       myTemplateRange = myDocument.createRangeMarker(caretOffset, caretOffset + template.getTemplateText().length());
281     }
282     else {
283       PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(myDocument);
284       preprocessTemplate(file, myEditor.getCaretModel().getOffset(), myTemplate.getTemplateText());
285       int caretOffset = myEditor.getCaretModel().getOffset();
286       myTemplateRange = myDocument.createRangeMarker(caretOffset, caretOffset);
287     }
288     myTemplateRange.setGreedyToLeft(true);
289     myTemplateRange.setGreedyToRight(true);
290
291     processAllExpressions(template);
292   }
293
294   private void fireTemplateCancelled() {
295     if (myFinished) return;
296     myFinished = true;
297     TemplateEditingListener[] listeners = myListeners.toArray(new TemplateEditingListener[myListeners.size()]);
298     for (TemplateEditingListener listener : listeners) {
299       listener.templateCancelled(myTemplate);
300     }
301   }
302
303   private void preprocessTemplate(final PsiFile file, int caretOffset, final String textToInsert) {
304     for (TemplatePreprocessor preprocessor : Extensions.getExtensions(TemplatePreprocessor.EP_NAME)) {
305       preprocessor.preprocessTemplate(myEditor, file, caretOffset, textToInsert, myTemplate.getTemplateText());
306     }
307   }
308
309   private void processAllExpressions(final TemplateImpl template) {
310     ApplicationManager.getApplication().runWriteAction(new Runnable() {
311       public void run() {
312         if (!template.isInline()) myDocument.insertString(myTemplateRange.getStartOffset(), template.getTemplateText());
313         for (int i = 0; i < template.getSegmentsCount(); i++) {
314           int segmentOffset = myTemplateRange.getStartOffset() + template.getSegmentOffset(i);
315           mySegments.addSegment(segmentOffset, segmentOffset);
316         }
317
318         calcResults(false);
319         calcResults(false);  //Fixed SCR #[vk500] : all variables should be recalced twice on start.
320         doReformat(null);
321
322         int nextVariableNumber = getNextVariableNumber(-1);
323
324         if (nextVariableNumber >= 0) {
325           fireWaitingForInput();
326         }
327
328         if (nextVariableNumber == -1) {
329           finishTemplateEditing(false);
330         }
331         else {
332           setCurrentVariableNumber(nextVariableNumber);
333           initTabStopHighlighters();
334           initListeners();
335           focusCurrentExpression();
336           currentVariableChanged(-1);
337         }
338       }
339     });
340   }
341
342   public void doReformat(final TextRange range) {
343     RangeMarker rangeMarker = null;
344     if (range != null) {
345       rangeMarker = myDocument.createRangeMarker(range);
346       rangeMarker.setGreedyToLeft(true);
347       rangeMarker.setGreedyToRight(true);
348     }
349     final RangeMarker finalRangeMarker = rangeMarker;
350     final Runnable action = new Runnable() {
351       public void run() {
352         IntArrayList indices = initEmptyVariables();
353         mySegments.setSegmentsGreedy(false);
354         reformat(finalRangeMarker);
355         mySegments.setSegmentsGreedy(true);
356         restoreEmptyVariables(indices);
357       }
358     };
359     ApplicationManager.getApplication().runWriteAction(action);
360   }
361
362   private void shortenReferences() {
363     ApplicationManager.getApplication().runWriteAction(new Runnable() {
364       public void run() {
365         final PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(myDocument);
366         if (file != null) {
367           IntArrayList indices = initEmptyVariables();
368           mySegments.setSegmentsGreedy(false);
369           for (TemplateOptionalProcessor processor : Extensions.getExtensions(TemplateOptionalProcessor.EP_NAME)) {
370             processor.processText(myProject, myTemplate, myDocument, myTemplateRange, myEditor);
371           }
372           mySegments.setSegmentsGreedy(true);
373           restoreEmptyVariables(indices);
374         }
375       }
376     });
377   }
378
379   private void afterChangedUpdate() {
380     if (isFinished()) return;
381     LOG.assertTrue(myTemplate != null);
382     if (myDocumentChanged) {
383       if (myDocumentChangesTerminateTemplate || mySegments.isInvalid()) {
384         final int oldIndex = myCurrentVariableNumber;
385         setCurrentVariableNumber(-1);
386         currentVariableChanged(oldIndex);
387         fireTemplateCancelled();
388       }
389       else {
390         calcResults(true);
391       }
392       myDocumentChanged = false;
393     }
394   }
395
396   private String getExpressionString(int index) {
397     CharSequence text = myDocument.getCharsSequence();
398
399     if (!mySegments.isValid(index)) return "";
400
401     int start = mySegments.getSegmentStart(index);
402     int end = mySegments.getSegmentEnd(index);
403
404     return text.subSequence(start, end).toString();
405   }
406
407   private int getCurrentSegmentNumber() {
408     if (myCurrentVariableNumber == -1) {
409       return -1;
410     }
411     String variableName = myTemplate.getVariableNameAt(myCurrentVariableNumber);
412     return myTemplate.getVariableSegmentNumber(variableName);
413   }
414
415   private void focusCurrentExpression() {
416     if (isFinished()) {
417       return;
418     }
419
420     PsiDocumentManager.getInstance(myProject).commitDocument(myDocument);
421
422     final int currentSegmentNumber = getCurrentSegmentNumber();
423
424     lockSegmentAtTheSameOffsetIfAny();
425
426     if (currentSegmentNumber < 0) return;
427     final int start = mySegments.getSegmentStart(currentSegmentNumber);
428     final int end = mySegments.getSegmentEnd(currentSegmentNumber);
429     myEditor.getCaretModel().moveToOffset(end);
430     myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
431     myEditor.getSelectionModel().removeSelection();
432
433
434     myEditor.getSelectionModel().setSelection(start, end);
435     Expression expressionNode = myTemplate.getExpressionAt(myCurrentVariableNumber);
436
437     final ExpressionContext context = createExpressionContext(start);
438     final LookupElement[] lookupItems = expressionNode.calculateLookupItems(context);
439     final PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(myDocument);
440     if (lookupItems != null && lookupItems.length > 0) {
441       if (((TemplateManagerImpl)TemplateManager.getInstance(myProject)).shouldSkipInTests()) {
442         final String s = lookupItems[0].getLookupString();
443         EditorModificationUtil.insertStringAtCaret(myEditor, s);
444         itemSelected(lookupItems[0], psiFile, currentSegmentNumber, ' ', lookupItems);
445       }
446       else {
447         runLookup(currentSegmentNumber, lookupItems, psiFile);
448       }
449     }
450     else {
451       Result result = expressionNode.calculateResult(context);
452       if (result != null) {
453         result.handleFocused(psiFile, myDocument, mySegments.getSegmentStart(currentSegmentNumber),
454                              mySegments.getSegmentEnd(currentSegmentNumber));
455       }
456     }
457     focusCurrentHighlighter(true);
458   }
459
460   private void runLookup(final int currentSegmentNumber, final LookupElement[] lookupItems, final PsiFile psiFile) {
461     if (myEditor == null) return;
462
463     final LookupManager lookupManager = LookupManager.getInstance(myProject);
464     if (lookupManager.isDisposed()) return;
465
466     final Lookup lookup = lookupManager.showLookup(myEditor, lookupItems);
467     toProcessTab = false;
468     lookup.addLookupListener(new LookupAdapter() {
469       public void lookupCanceled(LookupEvent event) {
470         lookup.removeLookupListener(this);
471         toProcessTab = true;
472       }
473
474       public void itemSelected(LookupEvent event) {
475         lookup.removeLookupListener(this);
476         if (isFinished()) return;
477         toProcessTab = true;
478
479         TemplateState.this.itemSelected(event.getItem(), psiFile, currentSegmentNumber, event.getCompletionChar(), lookupItems);
480       }
481     });
482   }
483
484   private void itemSelected(final LookupElement item,
485                             final PsiFile psiFile,
486                             final int currentSegmentNumber,
487                             final char completionChar,
488                             LookupElement[] elements) {
489     if (item != null) {
490       PsiDocumentManager.getInstance(myProject).commitAllDocuments();
491
492       final OffsetMap offsetMap = new OffsetMap(myDocument);
493       final InsertionContext context = new InsertionContext(offsetMap, (char)0, elements, psiFile, myEditor);
494       context.setTailOffset(myEditor.getCaretModel().getOffset());
495       offsetMap.addOffset(CompletionInitializationContext.START_OFFSET, context.getTailOffset() - item.getLookupString().length());
496       offsetMap.addOffset(CompletionInitializationContext.SELECTION_END_OFFSET, context.getTailOffset());
497       offsetMap.addOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET, context.getTailOffset());
498
499       Integer bracketCount = (Integer)item.getUserData(LookupItem.BRACKETS_COUNT_ATTR);
500       if (bracketCount != null) {
501         final StringBuilder tail = new StringBuilder();
502         for (int i = 0; i < bracketCount.intValue(); i++) {
503           tail.append("[]");
504         }
505         new WriteCommandAction(myProject) {
506           protected void run(com.intellij.openapi.application.Result result) throws Throwable {
507             EditorModificationUtil.insertStringAtCaret(myEditor, tail.toString());
508           }
509         }.execute();
510         PsiDocumentManager.getInstance(myProject).commitDocument(myDocument);
511       }
512
513       final TemplateLookupSelectionHandler handler =
514         item instanceof LookupItem ? ((LookupItem<?>)item).getAttribute(TemplateLookupSelectionHandler.KEY_IN_LOOKUP_ITEM) : null;
515       if (handler != null) {
516         handler.itemSelected(item, psiFile, myDocument, mySegments.getSegmentStart(currentSegmentNumber),
517                              mySegments.getSegmentEnd(currentSegmentNumber));
518       }
519       else {
520         new WriteCommandAction(myProject) {
521           protected void run(com.intellij.openapi.application.Result result) throws Throwable {
522             item.handleInsert(context);
523           }
524         }.execute();
525       }
526
527       if (completionChar == '.') {
528         EditorModificationUtil.insertStringAtCaret(myEditor, ".");
529         AutoPopupController.getInstance(myProject).autoPopupMemberLookup(myEditor, null);
530         return;
531       }
532
533       if (!isFinished()) {
534         calcResults(true);
535       }
536     }
537
538     nextTab();
539   }
540
541   private void unblockDocument() {
542     PsiDocumentManager.getInstance(myProject).commitDocument(myDocument);
543     PsiDocumentManager.getInstance(myProject).doPostponedOperationsAndUnblockDocument(myDocument);
544   }
545
546   private void calcResults(final boolean isQuick) {
547     if (myProcessor != null && myCurrentVariableNumber >= 0) {
548       final String variableName = myTemplate.getVariableNameAt(myCurrentVariableNumber);
549       final TextResult value = getVariableValue(variableName);
550       if (value != null && value.getText().length() > 0) {
551         if (!myProcessor.process(variableName, value.getText())) {
552           finishTemplateEditing(false); // nextTab(); ?
553           return;
554         }
555       }
556     }
557
558     ApplicationManager.getApplication().runWriteAction(new Runnable() {
559       public void run() {
560         BitSet calcedSegments = new BitSet();
561         int maxAttempts = (myTemplate.getVariableCount() + 1) * 3;
562
563         do {
564           maxAttempts--;
565           calcedSegments.clear();
566           for (int i = myCurrentVariableNumber + 1; i < myTemplate.getVariableCount(); i++) {
567             String variableName = myTemplate.getVariableNameAt(i);
568             int segmentNumber = myTemplate.getVariableSegmentNumber(variableName);
569             if (segmentNumber < 0) continue;
570             Expression expression = myTemplate.getExpressionAt(i);
571             Expression defaultValue = myTemplate.getDefaultValueAt(i);
572             String oldValue = getVariableValue(variableName).getText();
573             recalcSegment(segmentNumber, isQuick, expression, defaultValue);
574             final TextResult value = getVariableValue(variableName);
575             assert value != null : "name=" + variableName + "\ntext=" + myTemplate.getTemplateText();
576             String newValue = value.getText();
577             if (!newValue.equals(oldValue)) {
578               calcedSegments.set(segmentNumber);
579             }
580           }
581
582           for (int i = 0; i < myTemplate.getSegmentsCount(); i++) {
583             if (!calcedSegments.get(i)) {
584               String variableName = myTemplate.getSegmentName(i);
585               String newValue = getVariableValue(variableName).getText();
586               int start = mySegments.getSegmentStart(i);
587               int end = mySegments.getSegmentEnd(i);
588               replaceString(newValue, start, end, i);
589             }
590           }
591         }
592         while (!calcedSegments.isEmpty() && maxAttempts >= 0);
593       }
594     });
595   }
596
597   private void recalcSegment(int segmentNumber, boolean isQuick, Expression expressionNode, Expression defaultValue) {
598     String oldValue = getExpressionString(segmentNumber);
599     int start = mySegments.getSegmentStart(segmentNumber);
600     int end = mySegments.getSegmentEnd(segmentNumber);
601     ExpressionContext context = createExpressionContext(start);
602     Result result;
603     if (isQuick) {
604       result = expressionNode.calculateQuickResult(context);
605     }
606     else {
607       result = expressionNode.calculateResult(context);
608       if (expressionNode instanceof ConstantNode) {
609         if (result instanceof TextResult) {
610           TextResult text = (TextResult)result;
611           if (text.getText().length() == 0 && defaultValue != null) {
612             result = defaultValue.calculateResult(context);
613           }
614         }
615       }
616       if (result == null && defaultValue != null) {
617         result = defaultValue.calculateResult(context);
618       }
619     }
620     if (result == null) return;
621
622     PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(myDocument);
623     PsiElement element = psiFile.findElementAt(start);
624     if (result.equalsToText(oldValue, element)) return;
625
626     String newValue = result.toString();
627     if (newValue == null) newValue = "";
628
629     if (element != null) {
630       newValue = LanguageLiteralEscapers.INSTANCE.forLanguage(element.getLanguage()).getEscapedText(element, newValue);
631     }
632
633     replaceString(newValue, start, end, segmentNumber);
634
635     if (result instanceof RecalculatableResult) {
636       shortenReferences();
637       PsiDocumentManager.getInstance(myProject).commitDocument(myDocument);
638       ((RecalculatableResult)result)
639         .handleRecalc(psiFile, myDocument, mySegments.getSegmentStart(segmentNumber), mySegments.getSegmentEnd(segmentNumber));
640     }
641   }
642
643   private void replaceString(String newValue, int start, int end, int segmentNumber) {
644     String oldText = myDocument.getCharsSequence().subSequence(start, end).toString();
645
646     if (!oldText.equals(newValue)) {
647       int segmentNumberWithTheSameStart = mySegments.getSegmentWithTheSameStart(segmentNumber, start);
648       mySegments.setNeighboursGreedy(segmentNumber, false);
649       myDocument.replaceString(start, end, newValue);
650       int newEnd = start + newValue.length();
651       mySegments.replaceSegmentAt(segmentNumber, start, newEnd);
652       mySegments.setNeighboursGreedy(segmentNumber, true);
653
654       if (segmentNumberWithTheSameStart != -1) {
655         mySegments.replaceSegmentAt(segmentNumberWithTheSameStart, newEnd,
656                                     newEnd + mySegments.getSegmentEnd(segmentNumberWithTheSameStart) -
657                                     mySegments.getSegmentStart(segmentNumberWithTheSameStart));
658       }
659     }
660   }
661
662   public void previousTab() {
663     if (isFinished()) {
664       return;
665     }
666
667     myDocumentChangesTerminateTemplate = false;
668
669     final int oldVar = myCurrentVariableNumber;
670     int previousVariableNumber = getPreviousVariableNumber(oldVar);
671     if (previousVariableNumber >= 0) {
672       focusCurrentHighlighter(false);
673       calcResults(false);
674       doReformat(null);
675       setCurrentVariableNumber(previousVariableNumber);
676       focusCurrentExpression();
677       currentVariableChanged(oldVar);
678     }
679   }
680
681   public void nextTab() {
682     if (isFinished()) {
683       return;
684     }
685
686     //some psi operations may block the document, unblock here
687     unblockDocument();
688
689     myDocumentChangesTerminateTemplate = false;
690
691     final int oldVar = myCurrentVariableNumber;
692     int nextVariableNumber = getNextVariableNumber(oldVar);
693     if (nextVariableNumber == -1) {
694       calcResults(false);
695       ApplicationManager.getApplication().runWriteAction(new Runnable() {
696         public void run() {
697           reformat(null);
698         }
699       });
700       finishTemplateEditing(false);
701       return;
702     }
703     focusCurrentHighlighter(false);
704     calcResults(false);
705     doReformat(null);
706     setCurrentVariableNumber(nextVariableNumber);
707     focusCurrentExpression();
708     currentVariableChanged(oldVar);
709   }
710
711   private void lockSegmentAtTheSameOffsetIfAny() {
712     mySegments.lockSegmentAtTheSameOffsetIfAny(getCurrentSegmentNumber());
713   }
714
715   private ExpressionContext createExpressionContext(final int start) {
716     return new ExpressionContext() {
717       public Project getProject() {
718         return myProject;
719       }
720
721       public Editor getEditor() {
722         return myEditor;
723       }
724
725       public int getStartOffset() {
726         return start;
727       }
728
729       public int getTemplateStartOffset() {
730         if (myTemplateRange == null) {
731           return -1;
732         }
733         return myTemplateRange.getStartOffset();
734       }
735
736       public int getTemplateEndOffset() {
737         if (myTemplateRange == null) {
738           return -1;
739         }
740         return myTemplateRange.getEndOffset();
741       }
742
743       public <T> T getProperty(Key<T> key) {
744         return (T)myProperties.get(key);
745       }
746     };
747   }
748
749   public void gotoEnd(boolean brokenOff) {
750     calcResults(false);
751     doReformat(null);
752     finishTemplateEditing(brokenOff);
753   }
754
755   public void gotoEnd() {
756     gotoEnd(false);
757   }
758
759   private void finishTemplateEditing(boolean brokenOff) {
760     if (myTemplate == null) return;
761
762     LookupManager.getInstance(myProject).hideActiveLookup();
763
764     int endSegmentNumber = myTemplate.getEndSegmentNumber();
765     int offset = -1;
766     if (endSegmentNumber >= 0) {
767       offset = mySegments.getSegmentStart(endSegmentNumber);
768     }
769     else {
770       if (!myTemplate.isSelectionTemplate() && !myTemplate.isInline()) { //do not move caret to the end of range for selection templates
771         offset = myTemplateRange.getEndOffset();
772       }
773     }
774
775     if (offset >= 0) {
776       myEditor.getCaretModel().moveToOffset(offset);
777       myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
778     }
779
780     myEditor.getSelectionModel().removeSelection();
781     int selStart = myTemplate.getSelectionStartSegmentNumber();
782     int selEnd = myTemplate.getSelectionEndSegmentNumber();
783     if (selStart >= 0 && selEnd >= 0) {
784       myEditor.getSelectionModel().setSelection(mySegments.getSegmentStart(selStart), mySegments.getSegmentStart(selEnd));
785     }
786     fireBeforeTemplateFinished();
787     final Editor editor = myEditor;
788     int oldVar = myCurrentVariableNumber;
789     setCurrentVariableNumber(-1);
790     currentVariableChanged(oldVar);
791     ((TemplateManagerImpl)TemplateManager.getInstance(myProject)).clearTemplateState(editor);
792     fireTemplateFinished(brokenOff);
793     myListeners.clear();
794     myProject = null;
795   }
796
797   private int getNextVariableNumber(int currentVariableNumber) {
798     for (int i = currentVariableNumber + 1; i < myTemplate.getVariableCount(); i++) {
799       if (checkIfTabStop(i)) {
800         return i;
801       }
802     }
803     return -1;
804   }
805
806   private int getPreviousVariableNumber(int currentVariableNumber) {
807     for (int i = currentVariableNumber - 1; i >= 0; i--) {
808       if (checkIfTabStop(i)) {
809         return i;
810       }
811     }
812     return -1;
813   }
814
815   private boolean checkIfTabStop(int currentVariableNumber) {
816     Expression expression = myTemplate.getExpressionAt(currentVariableNumber);
817     if (expression == null) {
818       return false;
819     }
820     String variableName = myTemplate.getVariableNameAt(currentVariableNumber);
821     if (!(myPredefinedVariableValues != null && myPredefinedVariableValues.containsKey(variableName))) {
822       if (myTemplate.isAlwaysStopAt(currentVariableNumber)) {
823         return true;
824       }
825     }
826     int segmentNumber = myTemplate.getVariableSegmentNumber(variableName);
827     if (segmentNumber < 0) return false;
828     int start = mySegments.getSegmentStart(segmentNumber);
829     ExpressionContext context = createExpressionContext(start);
830     Result result = expression.calculateResult(context);
831     if (result == null) {
832       return true;
833     }
834     LookupElement[] items = expression.calculateLookupItems(context);
835     return items != null && items.length > 1;
836   }
837
838   private IntArrayList initEmptyVariables() {
839     int endSegmentNumber = myTemplate.getEndSegmentNumber();
840     int selStart = myTemplate.getSelectionStartSegmentNumber();
841     int selEnd = myTemplate.getSelectionEndSegmentNumber();
842     IntArrayList indices = new IntArrayList();
843     for (int i = 0; i < myTemplate.getSegmentsCount(); i++) {
844       int length = mySegments.getSegmentEnd(i) - mySegments.getSegmentStart(i);
845       if (length != 0) continue;
846       if (i == endSegmentNumber || i == selStart || i == selEnd) continue;
847
848       String name = myTemplate.getSegmentName(i);
849       for (int j = 0; j < myTemplate.getVariableCount(); j++) {
850         if (myTemplate.getVariableNameAt(j).equals(name)) {
851           Expression e = myTemplate.getExpressionAt(j);
852           @NonNls String marker = "a";
853           if (e instanceof MacroCallNode) {
854             marker = ((MacroCallNode)e).getMacro().getDefaultValue();
855           }
856           int start = mySegments.getSegmentStart(i);
857           int end = start + marker.length();
858           myDocument.insertString(start, marker);
859           mySegments.replaceSegmentAt(i, start, end);
860           indices.add(i);
861           break;
862         }
863       }
864     }
865     return indices;
866   }
867
868   private void restoreEmptyVariables(IntArrayList indices) {
869     for (int i = 0; i < indices.size(); i++) {
870       int index = indices.get(i);
871       myDocument.deleteString(mySegments.getSegmentStart(index), mySegments.getSegmentEnd(index));
872     }
873   }
874
875   private void initTabStopHighlighters() {
876     for (int i = 0; i < myTemplate.getVariableCount(); i++) {
877       String variableName = myTemplate.getVariableNameAt(i);
878       int segmentNumber = myTemplate.getVariableSegmentNumber(variableName);
879       if (segmentNumber < 0) continue;
880       RangeHighlighter segmentHighlighter = getSegmentHighlighter(segmentNumber, false, false);
881       myTabStopHighlighters.add(segmentHighlighter);
882     }
883
884     int endSegmentNumber = myTemplate.getEndSegmentNumber();
885     if (endSegmentNumber >= 0) {
886       RangeHighlighter segmentHighlighter = getSegmentHighlighter(endSegmentNumber, false, true);
887       myTabStopHighlighters.add(segmentHighlighter);
888     }
889   }
890
891   private RangeHighlighter getSegmentHighlighter(int segmentNumber, boolean isSelected, boolean isEnd) {
892     TextAttributes attributes = isSelected ? new TextAttributes(null, null, Color.red, EffectType.BOXED, Font.PLAIN) : new TextAttributes();
893     TextAttributes endAttributes = new TextAttributes();
894
895     RangeHighlighter segmentHighlighter;
896     int start = mySegments.getSegmentStart(segmentNumber);
897     int end = mySegments.getSegmentEnd(segmentNumber);
898     if (isEnd) {
899       segmentHighlighter = myEditor.getMarkupModel()
900         .addRangeHighlighter(start, end, HighlighterLayer.LAST + 1, endAttributes, HighlighterTargetArea.EXACT_RANGE);
901     }
902     else {
903       segmentHighlighter =
904         myEditor.getMarkupModel().addRangeHighlighter(start, end, HighlighterLayer.LAST + 1, attributes, HighlighterTargetArea.EXACT_RANGE);
905     }
906     segmentHighlighter.setGreedyToLeft(true);
907     segmentHighlighter.setGreedyToRight(true);
908     return segmentHighlighter;
909   }
910
911   private void focusCurrentHighlighter(boolean toSelect) {
912     if (isFinished()) {
913       return;
914     }
915     if (myCurrentVariableNumber >= myTabStopHighlighters.size()) {
916       return;
917     }
918     RangeHighlighter segmentHighlighter = myTabStopHighlighters.get(myCurrentVariableNumber);
919     if (segmentHighlighter != null) {
920       final int segmentNumber = getCurrentSegmentNumber();
921       RangeHighlighter newSegmentHighlighter = getSegmentHighlighter(segmentNumber, toSelect, false);
922       if (newSegmentHighlighter != null) {
923         myEditor.getMarkupModel().removeHighlighter(segmentHighlighter);
924         myTabStopHighlighters.set(myCurrentVariableNumber, newSegmentHighlighter);
925       }
926     }
927   }
928
929   private void reformat(RangeMarker rangeMarkerToReformat) {
930     final PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(myDocument);
931     if (file != null) {
932       CodeStyleManager style = CodeStyleManager.getInstance(myProject);
933       for (TemplateOptionalProcessor optionalProcessor : Extensions.getExtensions(TemplateOptionalProcessor.EP_NAME)) {
934         optionalProcessor.processText(myProject, myTemplate, myDocument, myTemplateRange, myEditor);
935       }
936       if (myTemplate.isToReformat()) {
937         try {
938           int endSegmentNumber = myTemplate.getEndSegmentNumber();
939           PsiDocumentManager.getInstance(myProject).commitDocument(myDocument);
940           RangeMarker rangeMarker = null;
941           if (endSegmentNumber >= 0) {
942             int endVarOffset = mySegments.getSegmentStart(endSegmentNumber);
943             PsiElement marker = style.insertNewLineIndentMarker(file, endVarOffset);
944             if (marker != null) rangeMarker = myDocument.createRangeMarker(marker.getTextRange());
945           }
946           int startOffset = rangeMarkerToReformat != null ? rangeMarkerToReformat.getStartOffset() : myTemplateRange.getStartOffset();
947           int endOffset = rangeMarkerToReformat != null ? rangeMarkerToReformat.getEndOffset() : myTemplateRange.getEndOffset();
948           style.reformatText(file, startOffset, endOffset);
949           PsiDocumentManager.getInstance(myProject).commitDocument(myDocument);
950           PsiDocumentManager.getInstance(myProject).doPostponedOperationsAndUnblockDocument(myDocument);
951
952           if (rangeMarker != null && rangeMarker.isValid()) {
953             //[ven] TODO: [max] correct javadoc reformatting to eliminate isValid() check!!!
954             mySegments.replaceSegmentAt(endSegmentNumber, rangeMarker.getStartOffset(), rangeMarker.getEndOffset());
955             myDocument.deleteString(rangeMarker.getStartOffset(), rangeMarker.getEndOffset());
956           }
957         }
958         catch (IncorrectOperationException e) {
959           LOG.error(e);
960         }
961       }
962       else if (myTemplate.isToIndent()) {
963         if (!myTemplateIndented) {
964           smartIndent(myTemplateRange.getStartOffset(), myTemplateRange.getEndOffset());
965           myTemplateIndented = true;
966         }
967       }
968     }
969   }
970
971   private void smartIndent(int startOffset, int endOffset) {
972     int startLineNum = myDocument.getLineNumber(startOffset);
973     int endLineNum = myDocument.getLineNumber(endOffset);
974     if (endLineNum == startLineNum) {
975       return;
976     }
977
978     int indentLineNum = startLineNum;
979
980     int lineLength = 0;
981     for (; indentLineNum >= 0; indentLineNum--) {
982       lineLength = myDocument.getLineEndOffset(indentLineNum) - myDocument.getLineStartOffset(indentLineNum);
983       if (lineLength > 0) {
984         break;
985       }
986     }
987     if (indentLineNum < 0) {
988       return;
989     }
990     StringBuilder buffer = new StringBuilder();
991     CharSequence text = myDocument.getCharsSequence();
992     for (int i = 0; i < lineLength; i++) {
993       char ch = text.charAt(myDocument.getLineStartOffset(indentLineNum) + i);
994       if (ch != ' ' && ch != '\t') {
995         break;
996       }
997       buffer.append(ch);
998     }
999     if (buffer.length() == 0) {
1000       return;
1001     }
1002     String stringToInsert = buffer.toString();
1003     for (int i = startLineNum + 1; i <= endLineNum; i++) {
1004       myDocument.insertString(myDocument.getLineStartOffset(i), stringToInsert);
1005     }
1006   }
1007
1008   public void addTemplateStateListener(TemplateEditingListener listener) {
1009     myListeners.add(listener);
1010   }
1011
1012   private void fireTemplateFinished(boolean brokenOff) {
1013     if (myFinished) return;
1014     myFinished = true;
1015     TemplateEditingListener[] listeners = myListeners.toArray(new TemplateEditingListener[myListeners.size()]);
1016     for (TemplateEditingListener listener : listeners) {
1017       listener.templateFinished(myTemplate, brokenOff);
1018     }
1019   }
1020
1021   private void fireBeforeTemplateFinished() {
1022     TemplateEditingListener[] listeners = myListeners.toArray(new TemplateEditingListener[myListeners.size()]);
1023     for (TemplateEditingListener listener : listeners) {
1024       listener.beforeTemplateFinished(this, myTemplate);
1025     }
1026   }
1027
1028   private void fireWaitingForInput() {
1029     TemplateEditingListener[] listeners = myListeners.toArray(new TemplateEditingListener[myListeners.size()]);
1030     for (TemplateEditingListener listener : listeners) {
1031       listener.waitingForInput(myTemplate);
1032     }
1033   }
1034
1035   private void currentVariableChanged(int oldIndex) {
1036     TemplateEditingListener[] listeners = myListeners.toArray(new TemplateEditingListener[myListeners.size()]);
1037     for (TemplateEditingListener listener : listeners) {
1038       listener.currentVariableChanged(this, myTemplate, oldIndex, myCurrentVariableNumber);
1039     }
1040     if (myCurrentSegmentNumber < 0) {
1041       releaseAll();
1042     }
1043   }
1044
1045   public Map getProperties() {
1046     return myProperties;
1047   }
1048
1049   public TemplateImpl getTemplate() {
1050     return myTemplate;
1051   }
1052
1053   void reset() {
1054     myListeners = new ArrayList<TemplateEditingListener>();
1055   }
1056 }