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
;
59 import java
.util
.ArrayList
;
60 import java
.util
.BitSet
;
61 import java
.util
.List
;
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
) {
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
));
119 public void beforeCommandFinished(CommandEvent event
) {
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;
142 //Avoid the leak of the editor
147 public boolean isToProcessTab() {
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();
159 public String
getTrimmedVariableValue(int variableIndex
) {
160 final TextResult value
= getVariableValue(myTemplate
.getVariableNameAt(variableIndex
));
161 return value
== null ?
null : value
.getText().trim();
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) {
179 int start
= mySegments
.getSegmentStart(segmentNumber
);
180 int end
= mySegments
.getSegmentEnd(segmentNumber
);
181 int length
= myDocument
.getTextLength();
182 if (start
> length
|| end
> length
) {
185 return new TextResult(text
.subSequence(start
, end
).toString());
189 public TextRange
getCurrentVariableRange() {
190 int number
= getCurrentSegmentNumber();
191 if (number
== -1) return null;
192 return new TextRange(mySegments
.getSegmentStart(number
), mySegments
.getSegmentEnd(number
));
196 public TextRange
getVariableRange(int variableIndex
) {
197 return getVariableRange(myTemplate
.getVariableNameAt(variableIndex
));
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();
217 myTemplateRange
= null;
220 myTabStopHighlighters
.clear();
223 private void releaseEditor() {
224 if (myEditor
!= null) {
225 for (RangeHighlighter segmentHighlighter
: myTabStopHighlighters
) {
226 myEditor
.getMarkupModel().removeHighlighter(segmentHighlighter
);
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
240 : new DocumentReference
[] {DocumentReferenceManager
.getInstance().create(myDocument
) };
242 UndoManager
.getInstance(myProject
).undoableActionPerformed(
243 new UndoableAction() {
245 if (myDocument
!= null) {
246 fireTemplateCancelled();
247 LookupManager
.getInstance(myProject
).hideActiveLookup();
248 int oldVar
= myCurrentVariableNumber
;
249 setCurrentVariableNumber(-1);
250 currentVariableChanged(oldVar
);
256 // throw new UnexpectedUndoException("Not implemented");
259 public DocumentReference
[] getAffectedDocuments() {
263 public boolean isGlobal() {
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());
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;
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(
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
);
315 calcResults(false); //Fixed SCR #[vk500] : all variables should be recalced twice on start.
318 int nextVariableNumber
= getNextVariableNumber(-1);
319 if (nextVariableNumber
== -1) {
320 finishTemplateEditing();
323 setCurrentVariableNumber(nextVariableNumber
);
324 initTabStopHighlighters();
326 focusCurrentExpression();
327 currentVariableChanged(-1);
334 private void doReformat() {
335 final Runnable action
= new Runnable() {
337 IntArrayList indices
= initEmptyVariables();
338 mySegments
.setSegmentsGreedy(false);
340 mySegments
.setSegmentsGreedy(true);
341 restoreEmptyVariables(indices
);
344 ApplicationManager
.getApplication().runWriteAction(action
);
347 private void shortenReferences() {
348 ApplicationManager
.getApplication().runWriteAction(new Runnable() {
350 final PsiFile file
= PsiDocumentManager
.getInstance(myProject
).getPsiFile(myDocument
);
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();
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) {
395 String variableName
= myTemplate
.getVariableNameAt(myCurrentVariableNumber
);
396 return myTemplate
.getVariableSegmentNumber(variableName
);
399 private void focusCurrentExpression() {
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
);
430 runLookup(currentSegmentNumber
, lookupItems
, psiFile
);
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);
457 public void itemSelected(LookupEvent event
) {
458 lookup
.removeLookupListener(this);
459 if (isFinished()) return;
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
) {
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
++) {
484 new WriteCommandAction(myProject
) {
485 protected void run(com
.intellij
.openapi
.application
.Result result
) throws Throwable
{
486 EditorModificationUtil
.insertStringAtCaret(myEditor
, tail
.toString());
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
));
497 new WriteCommandAction(myProject
) {
498 protected void run(com
.intellij
.openapi
.application
.Result result
) throws Throwable
{
499 item
.handleInsert(context
);
504 if (completionChar
== '.') {
505 EditorModificationUtil
.insertStringAtCaret(myEditor
, ".");
506 AutoPopupController
.getInstance(myProject
).autoPopupMemberLookup(myEditor
, null);
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(); ?
535 ApplicationManager
.getApplication().runWriteAction(
538 BitSet calcedSegments
= new BitSet();
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
);
581 result
= expressionNode
.calculateQuickResult(context
);
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
) {
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
,
635 newEnd
+ mySegments
.getSegmentEnd(segmentNumberWithTheSameStart
) - mySegments
.getSegmentStart(segmentNumberWithTheSameStart
)
641 public void previousTab() {
646 myDocumentChangesTerminateTemplate
= false;
648 final int oldVar
= myCurrentVariableNumber
;
649 int previousVariableNumber
= getPreviousVariableNumber(oldVar
);
650 if (previousVariableNumber
>= 0) {
651 focusCurrentHighlighter(false);
654 setCurrentVariableNumber(previousVariableNumber
);
655 focusCurrentExpression();
656 currentVariableChanged(oldVar
);
660 public void nextTab() {
665 //some psi operations may block the document, unblock here
668 myDocumentChangesTerminateTemplate
= false;
670 final int oldVar
= myCurrentVariableNumber
;
671 int nextVariableNumber
= getNextVariableNumber(oldVar
);
672 if (nextVariableNumber
== -1) {
674 ApplicationManager
.getApplication().runWriteAction(new Runnable() {
679 finishTemplateEditing();
682 focusCurrentHighlighter(false);
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() {
700 public Editor
getEditor() {
704 public int getStartOffset() {
708 public int getTemplateStartOffset() {
709 if (myTemplateRange
== null) {
712 return myTemplateRange
.getStartOffset();
715 public int getTemplateEndOffset() {
716 if (myTemplateRange
== null) {
719 return myTemplateRange
.getEndOffset();
722 public <T
> T
getProperty(Key
<T
> key
) {
723 return (T
) myProperties
.get(key
);
728 public void gotoEnd() {
731 finishTemplateEditing();
734 private void finishTemplateEditing() {
735 if (myTemplate
== null) return;
737 LookupManager
.getInstance(myProject
).hideActiveLookup();
739 int endSegmentNumber
= myTemplate
.getEndSegmentNumber();
741 if (endSegmentNumber
>= 0) {
742 offset
= mySegments
.getSegmentStart(endSegmentNumber
);
744 if (!myTemplate
.isSelectionTemplate() && !myTemplate
.isInline()) { //do not move caret to the end of range for selection templates
745 offset
= myTemplateRange
.getEndOffset();
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();
774 private int getNextVariableNumber(int currentVariableNumber
) {
775 for (int i
= currentVariableNumber
+ 1; i
< myTemplate
.getVariableCount(); i
++) {
776 if (checkIfTabStop(i
)) {
783 private int getPreviousVariableNumber(int currentVariableNumber
) {
784 for (int i
= currentVariableNumber
- 1; i
>= 0; i
--) {
785 if (checkIfTabStop(i
)) {
792 private boolean checkIfTabStop(int currentVariableNumber
) {
793 Expression expression
= myTemplate
.getExpressionAt(currentVariableNumber
);
794 if (expression
== null) {
797 if (myTemplate
.isAlwaysStopAt(currentVariableNumber
)) {
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) {
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
);
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
);
876 segmentHighlighter
= myEditor
.getMarkupModel()
877 .addRangeHighlighter(start
, end
, HighlighterLayer
.LAST
+ 1, endAttributes
, HighlighterTargetArea
.EXACT_RANGE
);
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
) {
892 if (myCurrentVariableNumber
>= myTabStopHighlighters
.size()) {
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
);
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()) {
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
) {
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
) {
952 int indentLineNum
= startLineNum
;
955 for (; indentLineNum
>= 0; indentLineNum
--) {
956 lineLength
= myDocument
.getLineEndOffset(indentLineNum
) - myDocument
.getLineStartOffset(indentLineNum
);
957 if (lineLength
> 0) {
961 if (indentLineNum
< 0) {
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') {
973 if (buffer
.length() == 0) {
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;
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) {
1012 public Map
getProperties() {
1013 return myProperties
;
1016 public TemplateImpl
getTemplate() {
1021 myListeners
= new ArrayList
<TemplateEditingListener
>();