update copyrights
[fedora-idea.git] / platform / lang-impl / src / com / intellij / codeInsight / template / impl / TemplateState.java
blob896945df10a0fdc6d05c28eefabeb253ed8331d8
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.intellij.codeInsight.template.impl;
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;
58 import java.awt.*;
59 import java.util.ArrayList;
60 import java.util.BitSet;
61 import java.util.List;
62 import java.util.Map;
64 /**
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;
72 private TemplateImpl myTemplate;
73 private TemplateSegments mySegments = null;
75 private RangeMarker myTemplateRange = null;
76 private final ArrayList<RangeHighlighter> myTabStopHighlighters = new ArrayList<RangeHighlighter>();
77 private int myCurrentVariableNumber = -1;
78 private int myCurrentSegmentNumber = -1;
79 private boolean toProcessTab = true;
81 private boolean myDocumentChangesTerminateTemplate = true;
82 private boolean myDocumentChanged = false;
84 private CommandAdapter myCommandListener;
86 private List<TemplateEditingListener> myListeners = new ArrayList<TemplateEditingListener>();
87 private DocumentAdapter myEditorDocumentListener;
88 private final Map myProperties = new HashMap();
89 private boolean myTemplateIndented = false;
90 private Document myDocument;
91 private boolean myFinished;
92 @Nullable private PairProcessor<String, String> myProcessor;
94 public TemplateState(@NotNull Project project, final Editor editor) {
95 myProject = project;
96 myEditor = editor;
97 myDocument = myEditor.getDocument();
100 private void initListeners() {
101 myEditorDocumentListener = new DocumentAdapter() {
102 public void beforeDocumentChange(DocumentEvent e) {
103 myDocumentChanged = true;
107 myCommandListener = new CommandAdapter() {
108 boolean started = false;
109 public void commandStarted(CommandEvent event) {
110 if (myEditor != null) {
111 final int offset = myEditor.getCaretModel().getOffset();
112 myDocumentChangesTerminateTemplate = myCurrentSegmentNumber >= 0 &&
113 (offset < mySegments.getSegmentStart(myCurrentSegmentNumber) ||
114 offset > mySegments.getSegmentEnd(myCurrentSegmentNumber));
116 started = true;
119 public void beforeCommandFinished(CommandEvent event) {
120 if (started) {
121 afterChangedUpdate();
126 myDocument.addDocumentListener(myEditorDocumentListener);
127 CommandProcessor.getInstance().addCommandListener(myCommandListener);
130 public synchronized void dispose() {
131 if (myEditorDocumentListener != null) {
132 myDocument.removeDocumentListener(myEditorDocumentListener);
133 myEditorDocumentListener = null;
135 if (myCommandListener != null) {
136 CommandProcessor.getInstance().removeCommandListener(myCommandListener);
137 myCommandListener = null;
140 myProcessor = null;
142 //Avoid the leak of the editor
143 releaseEditor();
144 myDocument = null;
147 public boolean isToProcessTab() {
148 return toProcessTab;
151 private void setCurrentVariableNumber(int variableNumber) {
152 myCurrentVariableNumber = variableNumber;
153 final boolean isFinished = variableNumber < 0;
154 ((DocumentEx)myDocument).setStripTrailingSpacesEnabled(isFinished);
155 myCurrentSegmentNumber = isFinished ? -1 : getCurrentSegmentNumber();
158 @Nullable
159 public String getTrimmedVariableValue(int variableIndex) {
160 final TextResult value = getVariableValue(myTemplate.getVariableNameAt(variableIndex));
161 return value == null ? null : value.getText().trim();
164 @Nullable
165 public TextResult getVariableValue(@NotNull String variableName) {
166 if (variableName.equals(TemplateImpl.SELECTION)) {
167 final String selection = (String)getProperties().get(ExpressionContext.SELECTION);
168 return new TextResult(selection == null ? "" : selection);
170 if (variableName.equals(TemplateImpl.END)) {
171 return new TextResult("");
174 CharSequence text = myDocument.getCharsSequence();
175 int segmentNumber = myTemplate.getVariableSegmentNumber(variableName);
176 if (segmentNumber < 0) {
177 return null;
179 int start = mySegments.getSegmentStart(segmentNumber);
180 int end = mySegments.getSegmentEnd(segmentNumber);
181 int length = myDocument.getTextLength();
182 if (start > length || end > length) {
183 return null;
185 return new TextResult(text.subSequence(start, end).toString());
188 @Nullable
189 public TextRange getCurrentVariableRange() {
190 int number = getCurrentSegmentNumber();
191 if (number == -1) return null;
192 return new TextRange(mySegments.getSegmentStart(number), mySegments.getSegmentEnd(number));
195 @Nullable
196 public TextRange getVariableRange(int variableIndex) {
197 return getVariableRange(myTemplate.getVariableNameAt(variableIndex));
200 @Nullable
201 public TextRange getVariableRange(String variableName) {
202 int segment = myTemplate.getVariableSegmentNumber(variableName);
203 if (segment < 0) return null;
205 return new TextRange(mySegments.getSegmentStart(segment), mySegments.getSegmentEnd(segment));
208 public boolean isFinished() {
209 return myCurrentVariableNumber < 0;
212 private void releaseAll() {
213 if (mySegments != null) {
214 mySegments.removeAll();
215 mySegments = null;
217 myTemplateRange = null;
218 myTemplate = null;
219 releaseEditor();
220 myTabStopHighlighters.clear();
223 private void releaseEditor() {
224 if (myEditor != null) {
225 for (RangeHighlighter segmentHighlighter : myTabStopHighlighters) {
226 myEditor.getMarkupModel().removeHighlighter(segmentHighlighter);
229 myEditor = null;
233 public void start(TemplateImpl template, @Nullable final PairProcessor<String, String> processor) {
234 PsiDocumentManager.getInstance(myProject).commitAllDocuments();
236 myProcessor = processor;
238 final DocumentReference[] refs = myDocument == null
239 ? null
240 : new DocumentReference[] {DocumentReferenceManager.getInstance().create(myDocument) };
242 UndoManager.getInstance(myProject).undoableActionPerformed(
243 new UndoableAction() {
244 public void undo() {
245 if (myDocument != null) {
246 fireTemplateCancelled();
247 LookupManager.getInstance(myProject).hideActiveLookup();
248 int oldVar = myCurrentVariableNumber;
249 setCurrentVariableNumber(-1);
250 currentVariableChanged(oldVar);
254 public void redo() {
255 //TODO:
256 // throw new UnexpectedUndoException("Not implemented");
259 public DocumentReference[] getAffectedDocuments() {
260 return refs;
263 public boolean isGlobal() {
264 return false;
268 myTemplateIndented = false;
269 myCurrentVariableNumber = -1;
270 mySegments = new TemplateSegments(myEditor);
271 myTemplate = template;
274 if (template.isInline()) {
275 int caretOffset = myEditor.getCaretModel().getOffset();
276 myTemplateRange = myDocument.createRangeMarker(caretOffset, caretOffset + template.getTemplateText().length());
278 else {
279 preprocessTemplate(PsiDocumentManager.getInstance(myProject).getPsiFile(myDocument), myEditor.getCaretModel().getOffset(), myTemplate.getTemplateText());
280 int caretOffset = myEditor.getCaretModel().getOffset();
281 myTemplateRange = myDocument.createRangeMarker(caretOffset, caretOffset);
283 myTemplateRange.setGreedyToLeft(true);
284 myTemplateRange.setGreedyToRight(true);
286 processAllExpressions(template);
289 private void fireTemplateCancelled() {
290 if (myFinished) return;
291 myFinished = true;
292 TemplateEditingListener[] listeners = myListeners.toArray(new TemplateEditingListener[myListeners.size()]);
293 for (TemplateEditingListener listener : listeners) {
294 listener.templateCancelled(myTemplate);
298 private void preprocessTemplate(final PsiFile file, int caretOffset, final String textToInsert) {
299 for(TemplatePreprocessor preprocessor: Extensions.getExtensions(TemplatePreprocessor.EP_NAME)) {
300 preprocessor.preprocessTemplate(myEditor, file, caretOffset, textToInsert, myTemplate.getTemplateText());
304 private void processAllExpressions(final TemplateImpl template) {
305 ApplicationManager.getApplication().runWriteAction(
306 new Runnable() {
307 public void run() {
308 if (!template.isInline()) myDocument.insertString(myTemplateRange.getStartOffset(), template.getTemplateText());
309 for (int i = 0; i < template.getSegmentsCount(); i++) {
310 int segmentOffset = myTemplateRange.getStartOffset() + template.getSegmentOffset(i);
311 mySegments.addSegment(segmentOffset, segmentOffset);
314 calcResults(false);
315 calcResults(false); //Fixed SCR #[vk500] : all variables should be recalced twice on start.
316 doReformat();
318 int nextVariableNumber = getNextVariableNumber(-1);
319 if (nextVariableNumber == -1) {
320 finishTemplateEditing();
322 else {
323 setCurrentVariableNumber(nextVariableNumber);
324 initTabStopHighlighters();
325 initListeners();
326 focusCurrentExpression();
327 currentVariableChanged(-1);
334 private void doReformat() {
335 final Runnable action = new Runnable() {
336 public void run() {
337 IntArrayList indices = initEmptyVariables();
338 mySegments.setSegmentsGreedy(false);
339 reformat();
340 mySegments.setSegmentsGreedy(true);
341 restoreEmptyVariables(indices);
344 ApplicationManager.getApplication().runWriteAction(action);
347 private void shortenReferences() {
348 ApplicationManager.getApplication().runWriteAction(new Runnable() {
349 public void run() {
350 final PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(myDocument);
351 if (file != null) {
352 IntArrayList indices = initEmptyVariables();
353 mySegments.setSegmentsGreedy(false);
354 for(TemplateOptionalProcessor processor: Extensions.getExtensions(TemplateOptionalProcessor.EP_NAME)) {
355 processor.processText(myProject, myTemplate, myDocument, myTemplateRange, myEditor);
357 mySegments.setSegmentsGreedy(true);
358 restoreEmptyVariables(indices);
364 private void afterChangedUpdate() {
365 if (isFinished()) return;
366 LOG.assertTrue(myTemplate != null);
367 if (myDocumentChanged) {
368 if (myDocumentChangesTerminateTemplate || mySegments.isInvalid()) {
369 final int oldIndex = myCurrentVariableNumber;
370 setCurrentVariableNumber(-1);
371 currentVariableChanged(oldIndex);
372 fireTemplateCancelled();
373 } else {
374 calcResults(true);
376 myDocumentChanged = false;
380 private String getExpressionString(int index) {
381 CharSequence text = myDocument.getCharsSequence();
383 if (!mySegments.isValid(index)) return "";
385 int start = mySegments.getSegmentStart(index);
386 int end = mySegments.getSegmentEnd(index);
388 return text.subSequence(start, end).toString();
391 private int getCurrentSegmentNumber() {
392 if (myCurrentVariableNumber == -1) {
393 return -1;
395 String variableName = myTemplate.getVariableNameAt(myCurrentVariableNumber);
396 return myTemplate.getVariableSegmentNumber(variableName);
399 private void focusCurrentExpression() {
400 if (isFinished()) {
401 return;
404 PsiDocumentManager.getInstance(myProject).commitDocument(myDocument);
406 final int currentSegmentNumber = getCurrentSegmentNumber();
408 lockSegmentAtTheSameOffsetIfAny();
410 if (currentSegmentNumber < 0) return;
411 final int start = mySegments.getSegmentStart(currentSegmentNumber);
412 final int end = mySegments.getSegmentEnd(currentSegmentNumber);
413 myEditor.getCaretModel().moveToOffset(end);
414 myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
415 myEditor.getSelectionModel().removeSelection();
418 myEditor.getSelectionModel().setSelection(start, end);
419 Expression expressionNode = myTemplate.getExpressionAt(myCurrentVariableNumber);
421 final ExpressionContext context = createExpressionContext(start);
422 final LookupElement[] lookupItems = expressionNode.calculateLookupItems(context);
423 final PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(myDocument);
424 if (lookupItems != null && lookupItems.length > 0) {
425 if (((TemplateManagerImpl)TemplateManager.getInstance(myProject)).shouldSkipInTests()) {
426 final String s = lookupItems[0].getLookupString();
427 EditorModificationUtil.insertStringAtCaret(myEditor, s);
428 itemSelected(lookupItems[0], psiFile, currentSegmentNumber, ' ', lookupItems);
429 } else {
430 runLookup(currentSegmentNumber, lookupItems, psiFile);
433 else {
434 Result result = expressionNode.calculateResult(context);
435 if (result != null) {
436 result.handleFocused(psiFile, myDocument,
437 mySegments.getSegmentStart(currentSegmentNumber), mySegments.getSegmentEnd(currentSegmentNumber));
440 focusCurrentHighlighter(true);
443 private void runLookup(final int currentSegmentNumber, final LookupElement[] lookupItems, final PsiFile psiFile) {
444 if (myEditor == null) return;
446 final LookupManager lookupManager = LookupManager.getInstance(myProject);
447 if (lookupManager.isDisposed()) return;
449 final Lookup lookup = lookupManager.showLookup(myEditor, lookupItems);
450 toProcessTab = false;
451 lookup.addLookupListener(new LookupAdapter() {
452 public void lookupCanceled(LookupEvent event) {
453 lookup.removeLookupListener(this);
454 toProcessTab = true;
457 public void itemSelected(LookupEvent event) {
458 lookup.removeLookupListener(this);
459 if (isFinished()) return;
460 toProcessTab = true;
462 TemplateState.this.itemSelected(event.getItem(), psiFile, currentSegmentNumber, event.getCompletionChar(), lookupItems);
467 private void itemSelected(final LookupElement item, final PsiFile psiFile, final int currentSegmentNumber, final char completionChar, LookupElement[] elements) {
468 if (item != null) {
469 PsiDocumentManager.getInstance(myProject).commitAllDocuments();
471 final OffsetMap offsetMap = new OffsetMap(myDocument);
472 final InsertionContext context = new InsertionContext(offsetMap, (char)0, elements, psiFile, myEditor);
473 context.setTailOffset(myEditor.getCaretModel().getOffset());
474 offsetMap.addOffset(CompletionInitializationContext.START_OFFSET, context.getTailOffset() - item.getLookupString().length());
475 offsetMap.addOffset(CompletionInitializationContext.SELECTION_END_OFFSET, context.getTailOffset());
476 offsetMap.addOffset(CompletionInitializationContext.IDENTIFIER_END_OFFSET, context.getTailOffset());
478 Integer bracketCount = (Integer)item.getUserData(LookupItem.BRACKETS_COUNT_ATTR);
479 if (bracketCount != null) {
480 final StringBuilder tail = new StringBuilder();
481 for (int i = 0; i < bracketCount.intValue(); i++) {
482 tail.append("[]");
484 new WriteCommandAction(myProject) {
485 protected void run(com.intellij.openapi.application.Result result) throws Throwable {
486 EditorModificationUtil.insertStringAtCaret(myEditor, tail.toString());
488 }.execute();
489 PsiDocumentManager.getInstance(myProject).commitDocument(myDocument);
492 final TemplateLookupSelectionHandler handler = item instanceof LookupItem ? ((LookupItem<?>)item).getAttribute(TemplateLookupSelectionHandler.KEY_IN_LOOKUP_ITEM) : null;
493 if (handler != null) {
494 handler.itemSelected(item, psiFile, myDocument,
495 mySegments.getSegmentStart(currentSegmentNumber), mySegments.getSegmentEnd(currentSegmentNumber));
496 } else {
497 new WriteCommandAction(myProject) {
498 protected void run(com.intellij.openapi.application.Result result) throws Throwable {
499 item.handleInsert(context);
501 }.execute();
504 if (completionChar == '.') {
505 EditorModificationUtil.insertStringAtCaret(myEditor, ".");
506 AutoPopupController.getInstance(myProject).autoPopupMemberLookup(myEditor, null);
507 return;
510 if (!isFinished()) {
511 calcResults(true);
515 nextTab();
518 private void unblockDocument() {
519 PsiDocumentManager.getInstance(myProject).commitDocument(myDocument);
520 PsiDocumentManager.getInstance(myProject).doPostponedOperationsAndUnblockDocument(myDocument);
523 private void calcResults(final boolean isQuick) {
524 if (myProcessor != null && myCurrentVariableNumber >= 0) {
525 final String variableName = myTemplate.getVariableNameAt(myCurrentVariableNumber);
526 final TextResult value = getVariableValue(variableName);
527 if (value != null && value.getText().length() > 0) {
528 if (!myProcessor.process(variableName, value.getText())) {
529 finishTemplateEditing(); // nextTab(); ?
530 return;
535 ApplicationManager.getApplication().runWriteAction(
536 new Runnable() {
537 public void run() {
538 BitSet calcedSegments = new BitSet();
540 do {
541 calcedSegments.clear();
542 for (int i = myCurrentVariableNumber + 1; i < myTemplate.getVariableCount(); i++) {
543 String variableName = myTemplate.getVariableNameAt(i);
544 int segmentNumber = myTemplate.getVariableSegmentNumber(variableName);
545 if (segmentNumber < 0) continue;
546 Expression expression = myTemplate.getExpressionAt(i);
547 Expression defaultValue = myTemplate.getDefaultValueAt(i);
548 String oldValue = getVariableValue(variableName).getText();
549 recalcSegment(segmentNumber, isQuick, expression, defaultValue);
550 final TextResult value = getVariableValue(variableName);
551 assert value != null : "name=" + variableName + "\ntext=" + myTemplate.getTemplateText();
552 String newValue = value.getText();
553 if (!newValue.equals(oldValue)) {
554 calcedSegments.set(segmentNumber);
558 for (int i = 0; i < myTemplate.getSegmentsCount(); i++) {
559 if (!calcedSegments.get(i)) {
560 String variableName = myTemplate.getSegmentName(i);
561 String newValue = getVariableValue(variableName).getText();
562 int start = mySegments.getSegmentStart(i);
563 int end = mySegments.getSegmentEnd(i);
564 replaceString(newValue, start, end, i);
568 while (!calcedSegments.isEmpty());
574 private void recalcSegment(int segmentNumber, boolean isQuick, Expression expressionNode, Expression defaultValue) {
575 String oldValue = getExpressionString(segmentNumber);
576 int start = mySegments.getSegmentStart(segmentNumber);
577 int end = mySegments.getSegmentEnd(segmentNumber);
578 ExpressionContext context = createExpressionContext(start);
579 Result result;
580 if (isQuick) {
581 result = expressionNode.calculateQuickResult(context);
583 else {
584 result = expressionNode.calculateResult(context);
585 if (expressionNode instanceof ConstantNode) {
586 if (result instanceof TextResult) {
587 TextResult text = (TextResult)result;
588 if (text.getText().length() == 0 && defaultValue != null) {
589 result = defaultValue.calculateResult(context);
593 if (result == null && defaultValue != null) {
594 result = defaultValue.calculateResult(context);
597 if (result == null) return;
599 PsiFile psiFile = PsiDocumentManager.getInstance(myProject).getPsiFile(myDocument);
600 PsiElement element = psiFile.findElementAt(start);
601 if (result.equalsToText(oldValue, element)) return;
603 String newValue = result.toString();
604 if (newValue == null) newValue = "";
606 if (element != null) {
607 newValue = LanguageLiteralEscapers.INSTANCE.forLanguage(element.getLanguage()).getEscapedText(element, newValue);
610 replaceString(newValue, start, end, segmentNumber);
612 if (result instanceof RecalculatableResult) {
613 shortenReferences();
614 PsiDocumentManager.getInstance(myProject).commitDocument(myDocument);
615 ((RecalculatableResult) result).handleRecalc(psiFile, myDocument,
616 mySegments.getSegmentStart(segmentNumber), mySegments.getSegmentEnd(segmentNumber));
620 private void replaceString(String newValue, int start, int end, int segmentNumber) {
621 String oldText = myDocument.getCharsSequence().subSequence(start, end).toString();
623 if (!oldText.equals(newValue)) {
624 int segmentNumberWithTheSameStart = mySegments.getSegmentWithTheSameStart(segmentNumber, start);
625 mySegments.setNeighboursGreedy(segmentNumber, false);
626 myDocument.replaceString(start, end, newValue);
627 int newEnd = start + newValue.length();
628 mySegments.replaceSegmentAt(segmentNumber, start, newEnd);
629 mySegments.setNeighboursGreedy(segmentNumber, true);
631 if (segmentNumberWithTheSameStart != -1) {
632 mySegments.replaceSegmentAt(
633 segmentNumberWithTheSameStart,
634 newEnd,
635 newEnd + mySegments.getSegmentEnd(segmentNumberWithTheSameStart) - mySegments.getSegmentStart(segmentNumberWithTheSameStart)
641 public void previousTab() {
642 if (isFinished()) {
643 return;
646 myDocumentChangesTerminateTemplate = false;
648 final int oldVar = myCurrentVariableNumber;
649 int previousVariableNumber = getPreviousVariableNumber(oldVar);
650 if (previousVariableNumber >= 0) {
651 focusCurrentHighlighter(false);
652 calcResults(false);
653 doReformat();
654 setCurrentVariableNumber(previousVariableNumber);
655 focusCurrentExpression();
656 currentVariableChanged(oldVar);
660 public void nextTab() {
661 if (isFinished()) {
662 return;
665 //some psi operations may block the document, unblock here
666 unblockDocument();
668 myDocumentChangesTerminateTemplate = false;
670 final int oldVar = myCurrentVariableNumber;
671 int nextVariableNumber = getNextVariableNumber(oldVar);
672 if (nextVariableNumber == -1) {
673 calcResults(false);
674 ApplicationManager.getApplication().runWriteAction(new Runnable() {
675 public void run() {
676 reformat();
679 finishTemplateEditing();
680 return;
682 focusCurrentHighlighter(false);
683 calcResults(false);
684 doReformat();
685 setCurrentVariableNumber(nextVariableNumber);
686 focusCurrentExpression();
687 currentVariableChanged(oldVar);
690 private void lockSegmentAtTheSameOffsetIfAny() {
691 mySegments.lockSegmentAtTheSameOffsetIfAny(getCurrentSegmentNumber());
694 private ExpressionContext createExpressionContext(final int start) {
695 return new ExpressionContext() {
696 public Project getProject() {
697 return myProject;
700 public Editor getEditor() {
701 return myEditor;
704 public int getStartOffset() {
705 return start;
708 public int getTemplateStartOffset() {
709 if (myTemplateRange == null) {
710 return -1;
712 return myTemplateRange.getStartOffset();
715 public int getTemplateEndOffset() {
716 if (myTemplateRange == null) {
717 return -1;
719 return myTemplateRange.getEndOffset();
722 public <T> T getProperty(Key<T> key) {
723 return (T) myProperties.get(key);
728 public void gotoEnd() {
729 calcResults(false);
730 doReformat();
731 finishTemplateEditing();
734 private void finishTemplateEditing() {
735 if (myTemplate == null) return;
737 LookupManager.getInstance(myProject).hideActiveLookup();
739 int endSegmentNumber = myTemplate.getEndSegmentNumber();
740 int offset = -1;
741 if (endSegmentNumber >= 0) {
742 offset = mySegments.getSegmentStart(endSegmentNumber);
743 } else {
744 if (!myTemplate.isSelectionTemplate() && !myTemplate.isInline()) { //do not move caret to the end of range for selection templates
745 offset = myTemplateRange.getEndOffset();
749 if (offset >= 0) {
750 myEditor.getCaretModel().moveToOffset(offset);
751 myEditor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
754 myEditor.getSelectionModel().removeSelection();
755 int selStart = myTemplate.getSelectionStartSegmentNumber();
756 int selEnd = myTemplate.getSelectionEndSegmentNumber();
757 if (selStart >= 0 && selEnd >= 0) {
758 myEditor.getSelectionModel().setSelection(
759 mySegments.getSegmentStart(selStart),
760 mySegments.getSegmentStart(selEnd)
763 fireBeforeTemplateFinished();
764 final Editor editor = myEditor;
765 int oldVar = myCurrentVariableNumber;
766 setCurrentVariableNumber(-1);
767 currentVariableChanged(oldVar);
768 ((TemplateManagerImpl)TemplateManager.getInstance(myProject)).clearTemplateState(editor);
769 fireTemplateFinished();
770 myListeners.clear();
771 myProject = null;
774 private int getNextVariableNumber(int currentVariableNumber) {
775 for (int i = currentVariableNumber + 1; i < myTemplate.getVariableCount(); i++) {
776 if (checkIfTabStop(i)) {
777 return i;
780 return -1;
783 private int getPreviousVariableNumber(int currentVariableNumber) {
784 for (int i = currentVariableNumber - 1; i >= 0; i--) {
785 if (checkIfTabStop(i)) {
786 return i;
789 return -1;
792 private boolean checkIfTabStop(int currentVariableNumber) {
793 Expression expression = myTemplate.getExpressionAt(currentVariableNumber);
794 if (expression == null) {
795 return false;
797 if (myTemplate.isAlwaysStopAt(currentVariableNumber)) {
798 return true;
800 String variableName = myTemplate.getVariableNameAt(currentVariableNumber);
801 int segmentNumber = myTemplate.getVariableSegmentNumber(variableName);
802 if (segmentNumber < 0) return false;
803 int start = mySegments.getSegmentStart(segmentNumber);
804 ExpressionContext context = createExpressionContext(start);
805 Result result = expression.calculateResult(context);
806 if (result == null) {
807 return true;
809 LookupElement[] items = expression.calculateLookupItems(context);
810 return items != null && items.length > 1;
813 private IntArrayList initEmptyVariables() {
814 int endSegmentNumber = myTemplate.getEndSegmentNumber();
815 int selStart = myTemplate.getSelectionStartSegmentNumber();
816 int selEnd = myTemplate.getSelectionEndSegmentNumber();
817 IntArrayList indices = new IntArrayList();
818 for (int i = 0; i < myTemplate.getSegmentsCount(); i++) {
819 int length = mySegments.getSegmentEnd(i) - mySegments.getSegmentStart(i);
820 if (length != 0) continue;
821 if (i == endSegmentNumber || i == selStart || i == selEnd) continue;
823 String name = myTemplate.getSegmentName(i);
824 for (int j = 0; j < myTemplate.getVariableCount(); j++) {
825 if (myTemplate.getVariableNameAt(j).equals(name)) {
826 Expression e = myTemplate.getExpressionAt(j);
827 @NonNls String marker = "a";
828 if (e instanceof MacroCallNode) {
829 marker = ((MacroCallNode)e).getMacro().getDefaultValue();
831 int start = mySegments.getSegmentStart(i);
832 int end = start + marker.length();
833 myDocument.insertString(start, marker);
834 mySegments.replaceSegmentAt(i, start, end);
835 indices.add(i);
836 break;
840 return indices;
843 private void restoreEmptyVariables(IntArrayList indices) {
844 for (int i = 0; i < indices.size(); i++) {
845 int index = indices.get(i);
846 myDocument.deleteString(mySegments.getSegmentStart(index), mySegments.getSegmentEnd(index));
850 private void initTabStopHighlighters() {
851 for (int i = 0; i < myTemplate.getVariableCount(); i++) {
852 String variableName = myTemplate.getVariableNameAt(i);
853 int segmentNumber = myTemplate.getVariableSegmentNumber(variableName);
854 if (segmentNumber < 0) continue;
855 RangeHighlighter segmentHighlighter = getSegmentHighlighter(segmentNumber, false, false);
856 myTabStopHighlighters.add(segmentHighlighter);
859 int endSegmentNumber = myTemplate.getEndSegmentNumber();
860 if (endSegmentNumber >= 0) {
861 RangeHighlighter segmentHighlighter = getSegmentHighlighter(endSegmentNumber, false, true);
862 myTabStopHighlighters.add(segmentHighlighter);
866 private RangeHighlighter getSegmentHighlighter(int segmentNumber, boolean isSelected, boolean isEnd) {
867 TextAttributes attributes = isSelected
868 ? new TextAttributes(null, null, Color.red, EffectType.BOXED, Font.PLAIN)
869 : new TextAttributes();
870 TextAttributes endAttributes = new TextAttributes();
872 RangeHighlighter segmentHighlighter;
873 int start = mySegments.getSegmentStart(segmentNumber);
874 int end = mySegments.getSegmentEnd(segmentNumber);
875 if (isEnd) {
876 segmentHighlighter = myEditor.getMarkupModel()
877 .addRangeHighlighter(start, end, HighlighterLayer.LAST + 1, endAttributes, HighlighterTargetArea.EXACT_RANGE);
879 else {
880 segmentHighlighter = myEditor.getMarkupModel()
881 .addRangeHighlighter(start, end, HighlighterLayer.LAST + 1, attributes, HighlighterTargetArea.EXACT_RANGE);
883 segmentHighlighter.setGreedyToLeft(true);
884 segmentHighlighter.setGreedyToRight(true);
885 return segmentHighlighter;
888 private void focusCurrentHighlighter(boolean toSelect) {
889 if (isFinished()) {
890 return;
892 if (myCurrentVariableNumber >= myTabStopHighlighters.size()) {
893 return;
895 RangeHighlighter segmentHighlighter = myTabStopHighlighters.get(myCurrentVariableNumber);
896 if (segmentHighlighter != null) {
897 final int segmentNumber = getCurrentSegmentNumber();
898 RangeHighlighter newSegmentHighlighter = getSegmentHighlighter(segmentNumber, toSelect, false);
899 if (newSegmentHighlighter != null) {
900 myEditor.getMarkupModel().removeHighlighter(segmentHighlighter);
901 myTabStopHighlighters.set(myCurrentVariableNumber, newSegmentHighlighter);
906 private void reformat() {
907 final PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(myDocument);
908 if (file != null) {
909 CodeStyleManager style = CodeStyleManager.getInstance(myProject);
910 for(TemplateOptionalProcessor optionalProcessor : Extensions.getExtensions(TemplateOptionalProcessor.EP_NAME)) {
911 optionalProcessor.processText(myProject, myTemplate, myDocument, myTemplateRange, myEditor);
913 if (myTemplate.isToReformat()) {
914 try {
915 int endSegmentNumber = myTemplate.getEndSegmentNumber();
916 PsiDocumentManager.getInstance(myProject).commitDocument(myDocument);
917 RangeMarker rangeMarker = null;
918 if (endSegmentNumber >= 0) {
919 int endVarOffset = mySegments.getSegmentStart(endSegmentNumber);
920 PsiElement marker = style.insertNewLineIndentMarker(file, endVarOffset);
921 if(marker != null) rangeMarker = myDocument.createRangeMarker(marker.getTextRange());
923 style.reformatText(file, myTemplateRange.getStartOffset(), myTemplateRange.getEndOffset());
924 PsiDocumentManager.getInstance(myProject).commitDocument(myDocument);
926 if (rangeMarker != null && rangeMarker.isValid()) {
927 //[ven] TODO: [max] correct javadoc reformatting to eliminate isValid() check!!!
928 mySegments.replaceSegmentAt(endSegmentNumber, rangeMarker.getStartOffset(), rangeMarker.getEndOffset());
929 myDocument.deleteString(rangeMarker.getStartOffset(), rangeMarker.getEndOffset());
932 catch (IncorrectOperationException e) {
933 LOG.error(e);
936 else if (myTemplate.isToIndent()) {
937 if (!myTemplateIndented) {
938 smartIndent(myTemplateRange.getStartOffset(), myTemplateRange.getEndOffset());
939 myTemplateIndented = true;
945 private void smartIndent(int startOffset, int endOffset) {
946 int startLineNum = myDocument.getLineNumber(startOffset);
947 int endLineNum = myDocument.getLineNumber(endOffset);
948 if (endLineNum == startLineNum) {
949 return;
952 int indentLineNum = startLineNum;
954 int lineLength = 0;
955 for (; indentLineNum >= 0; indentLineNum--) {
956 lineLength = myDocument.getLineEndOffset(indentLineNum) - myDocument.getLineStartOffset(indentLineNum);
957 if (lineLength > 0) {
958 break;
961 if (indentLineNum < 0) {
962 return;
964 StringBuilder buffer = new StringBuilder();
965 CharSequence text = myDocument.getCharsSequence();
966 for (int i = 0; i < lineLength; i++) {
967 char ch = text.charAt(myDocument.getLineStartOffset(indentLineNum) + i);
968 if (ch != ' ' && ch != '\t') {
969 break;
971 buffer.append(ch);
973 if (buffer.length() == 0) {
974 return;
976 String stringToInsert = buffer.toString();
977 for (int i = startLineNum + 1; i <= endLineNum; i++) {
978 myDocument.insertString(myDocument.getLineStartOffset(i), stringToInsert);
982 public void addTemplateStateListener(TemplateEditingListener listener) {
983 myListeners.add(listener);
986 private void fireTemplateFinished() {
987 if (myFinished) return;
988 myFinished = true;
989 TemplateEditingListener[] listeners = myListeners.toArray(new TemplateEditingListener[myListeners.size()]);
990 for (TemplateEditingListener listener : listeners) {
991 listener.templateFinished(myTemplate);
995 private void fireBeforeTemplateFinished() {
996 TemplateEditingListener[] listeners = myListeners.toArray(new TemplateEditingListener[myListeners.size()]);
997 for (TemplateEditingListener listener : listeners) {
998 listener.beforeTemplateFinished(this, myTemplate);
1002 private void currentVariableChanged(int oldIndex) {
1003 TemplateEditingListener[] listeners = myListeners.toArray(new TemplateEditingListener[myListeners.size()]);
1004 for (TemplateEditingListener listener : listeners) {
1005 listener.currentVariableChanged(this, myTemplate, oldIndex, myCurrentVariableNumber);
1007 if (myCurrentSegmentNumber < 0) {
1008 releaseAll();
1012 public Map getProperties() {
1013 return myProperties;
1016 public TemplateImpl getTemplate() {
1017 return myTemplate;
1020 void reset() {
1021 myListeners = new ArrayList<TemplateEditingListener>();