support passing an argument to live template (WI-626)
authorEugene Kudelevsky <Eugene.Kudelevsky@jetbrains.com>
Fri, 22 Jan 2010 14:35:56 +0000 (17:35 +0300)
committerEugene Kudelevsky <Eugene.Kudelevsky@jetbrains.com>
Fri, 22 Jan 2010 14:35:56 +0000 (17:35 +0300)
platform/lang-impl/src/com/intellij/codeInsight/template/impl/ListTemplatesHandler.java
platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateImpl.java
platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateManagerImpl.java
platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateSettings.java
platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateState.java

index ed84e8e15998cfdd46315a532bb0065f57059f69..dacc6011198ce3b96dd8bfd29d799223af400d2b 100644 (file)
@@ -1,24 +1,24 @@
-
 /*
- * Copyright 2000-2009 JetBrains s.r.o.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
+* Copyright 2000-2009 JetBrains s.r.o.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
 
 package com.intellij.codeInsight.template.impl;
 
 import com.intellij.codeInsight.CodeInsightActionHandler;
 import com.intellij.codeInsight.CodeInsightBundle;
+import com.intellij.codeInsight.completion.impl.CamelHumpMatcher;
 import com.intellij.codeInsight.hint.HintManager;
 import com.intellij.codeInsight.lookup.*;
 import com.intellij.codeInsight.lookup.impl.LookupImpl;
@@ -35,8 +35,9 @@ import org.jetbrains.annotations.NotNull;
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
 
-public class ListTemplatesHandler implements CodeInsightActionHandler{
+public class ListTemplatesHandler implements CodeInsightActionHandler {
   public void invoke(@NotNull final Project project, @NotNull final Editor editor, @NotNull PsiFile file) {
     if (!file.isWritable()) return;
     EditorUtil.fillVirtualSpaceUntilCaret(editor);
@@ -51,11 +52,11 @@ public class ListTemplatesHandler implements CodeInsightActionHandler{
         matchingTemplates.add(template);
       }
     }
-    
+
     if (matchingTemplates.size() == 0) {
       String text = prefix.length() == 0
-        ? CodeInsightBundle.message("templates.no.defined")
-        : CodeInsightBundle.message("templates.no.defined.with.prefix", prefix);
+                    ? CodeInsightBundle.message("templates.no.defined")
+                    : CodeInsightBundle.message("templates.no.defined.with.prefix", prefix);
       HintManager.getInstance().showErrorHint(editor, text);
       return;
     }
@@ -65,27 +66,42 @@ public class ListTemplatesHandler implements CodeInsightActionHandler{
 
   public static void showTemplatesLookup(final Project project, final Editor editor, String prefix, List<TemplateImpl> matchingTemplates) {
     ArrayList<LookupItem> array = new ArrayList<LookupItem>();
-    for (TemplateImpl template: matchingTemplates) {
+    for (TemplateImpl template : matchingTemplates) {
       array.add(new LookupItem(template, template.getKey()));
     }
     LookupElement[] items = array.toArray(new LookupElement[array.size()]);
 
-    final LookupImpl lookup = (LookupImpl) LookupManager.getInstance(project).createLookup(editor, items, prefix, LookupArranger.DEFAULT);
-    lookup.addLookupListener(
-      new LookupAdapter() {
-        public void itemSelected(LookupEvent event) {
-          final LookupElement lookupElement = event.getItem();
-          if (lookupElement != null) {
-            final TemplateImpl template = (TemplateImpl)lookupElement.getObject();
-            new WriteCommandAction(project) {
-              protected void run(Result result) throws Throwable {
-                ((TemplateManagerImpl) TemplateManager.getInstance(project)).startTemplateWithPrefix(editor, template, null);
-              }
-            }.execute();
-          }
-        }
-      }
-    );
+    final LookupImpl lookup = (LookupImpl)LookupManager.getInstance(project).createLookup(editor, items, prefix, LookupArranger.DEFAULT);
+    lookup.addLookupListener(new MyLookupAdapter(project, editor, null));
+    lookup.show();
+  }
+
+  private static String computePrefix(TemplateImpl template, String argument) {
+    String key = template.getKey();
+    if (argument == null) {
+      return key;
+    }
+    if (key.length() > 0 && Character.isJavaIdentifierPart(key.charAt(key.length() - 1))) {
+      return key + ' ' + argument;
+    }
+    return key + argument;
+  }
+
+  public static void showTemplatesLookup(final Project project,
+                                         final Editor editor,
+                                         Map<TemplateImpl, String> template2Argument) {
+    ArrayList<LookupItem> array = new ArrayList<LookupItem>();
+    for (TemplateImpl template : template2Argument.keySet()) {
+      String argument = template2Argument.get(template);
+      String prefix = computePrefix(template, argument);
+      LookupItem item = new LookupItem(template, prefix);
+      item.setPrefixMatcher(new CamelHumpMatcher(prefix));
+      array.add(item);
+    }
+    LookupElement[] items = array.toArray(new LookupElement[array.size()]);
+
+    final LookupImpl lookup = (LookupImpl)LookupManager.getInstance(project).createLookup(editor, items, null, LookupArranger.DEFAULT);
+    lookup.addLookupListener(new MyLookupAdapter(project, editor, template2Argument));
     lookup.show();
   }
 
@@ -93,10 +109,10 @@ public class ListTemplatesHandler implements CodeInsightActionHandler{
     return true;
   }
 
-  private static String getPrefix(Document document, int offset) {
+  private String getPrefix(Document document, int offset) {
     CharSequence chars = document.getCharsSequence();
     int start = offset;
-    while(true){
+    while (true) {
       if (start == 0) break;
       char c = chars.charAt(start - 1);
       if (!isInPrefix(c)) break;
@@ -105,7 +121,29 @@ public class ListTemplatesHandler implements CodeInsightActionHandler{
     return chars.subSequence(start, offset).toString();
   }
 
-  private static boolean isInPrefix(final char c) {
+  private boolean isInPrefix(final char c) {
     return Character.isJavaIdentifierPart(c) || c == '.';
   }
+
+  private static class MyLookupAdapter extends LookupAdapter {
+    private final Project myProject;
+    private final Editor myEditor;
+    private final Map<TemplateImpl, String> myTemplate2Argument;
+
+    public MyLookupAdapter(Project project, Editor editor, Map<TemplateImpl, String> template2Argument) {
+      myProject = project;
+      myEditor = editor;
+      myTemplate2Argument = template2Argument;
+    }
+
+    public void itemSelected(LookupEvent event) {
+      final TemplateImpl template = (TemplateImpl)event.getItem().getObject();
+      final String argument = myTemplate2Argument != null ? myTemplate2Argument.get(template) : null;
+      new WriteCommandAction(myProject) {
+        protected void run(Result result) throws Throwable {
+          ((TemplateManagerImpl)TemplateManager.getInstance(myProject)).startTemplateWithPrefix(myEditor, template, null, argument);
+        }
+      }.execute();
+    }
+  }
 }
index 0eeec65d928f47261c8f6765e7e2ba02f5bccf14..dac35a95ccd90dbb2a54484ce9078ff37827fc31 100644 (file)
@@ -88,6 +88,7 @@ public class TemplateImpl extends Template implements SchemeElement {
   @NonNls public static final String SELECTION = "SELECTION";
   @NonNls public static final String SELECTION_START = "SELECTION_START";
   @NonNls public static final String SELECTION_END = "SELECTION_END";
+  @NonNls public static final String ARG = "ARG";
 
   public static final Set<String> INTERNAL_VARS_SET = new HashSet<String>(Arrays.asList(
       END, SELECTION, SELECTION_START, SELECTION_END));
@@ -381,6 +382,13 @@ public class TemplateImpl extends Template implements SchemeElement {
     return false;
   }
 
+  public boolean hasArgument() {
+    for (Variable v : myVariables) {
+      if (v.getName().equals(ARG)) return true;
+    }
+    return false;
+  }
+
   public void setId(final String id) {
     myId = id;
   }
index cefc84993e895cc969551b270cd1e44349479330..566327bb87ca4d4aa670ce8ac9374b002b5b97ae 100644 (file)
@@ -36,6 +36,7 @@ import com.intellij.psi.PsiDocumentManager;
 import com.intellij.psi.PsiFile;
 import com.intellij.psi.util.PsiUtilBase;
 import com.intellij.util.PairProcessor;
+import com.intellij.util.containers.HashMap;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
@@ -59,7 +60,8 @@ public class TemplateManagerImpl extends TemplateManager implements ProjectCompo
     myDisposables.clear();
   }
 
-  public void initComponent() { }
+  public void initComponent() {
+  }
 
   public void projectClosed() {
   }
@@ -133,12 +135,17 @@ public class TemplateManagerImpl extends TemplateManager implements ProjectCompo
     startTemplate(editor, selectionString, template, null, null);
   }
 
-  public void startTemplate(@NotNull Editor editor, @NotNull Template template, TemplateEditingListener listener,
+  public void startTemplate(@NotNull Editor editor,
+                            @NotNull Template template,
+                            TemplateEditingListener listener,
                             final PairProcessor<String, String> processor) {
     startTemplate(editor, null, template, listener, processor);
   }
 
-  private void startTemplate(final Editor editor, final String selectionString, final Template template, TemplateEditingListener listener,
+  private void startTemplate(final Editor editor,
+                             final String selectionString,
+                             final Template template,
+                             TemplateEditingListener listener,
                              final PairProcessor<String, String> processor) {
     final TemplateState templateState = initTemplateState(editor);
 
@@ -147,23 +154,21 @@ public class TemplateManagerImpl extends TemplateManager implements ProjectCompo
     if (listener != null) {
       templateState.addTemplateStateListener(listener);
     }
-    CommandProcessor.getInstance().executeCommand(
-      myProject, new Runnable() {
-        public void run() {
-          if (selectionString != null) {
-            ApplicationManager.getApplication().runWriteAction(new Runnable() {
-              public void run() {
-                EditorModificationUtil.deleteSelectedText(editor);
-              }
-            });
-          } else {
-            editor.getSelectionModel().removeSelection();
-          }
-          templateState.start((TemplateImpl) template, processor);
+    CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+      public void run() {
+        if (selectionString != null) {
+          ApplicationManager.getApplication().runWriteAction(new Runnable() {
+            public void run() {
+              EditorModificationUtil.deleteSelectedText(editor);
+            }
+          });
+        }
+        else {
+          editor.getSelectionModel().removeSelection();
         }
-      },
-      CodeInsightBundle.message("insert.code.template.command"), null
-    );
+        templateState.start((TemplateImpl)template, processor, null);
+      }
+    }, CodeInsightBundle.message("insert.code.template.command"), null);
 
     if (shouldSkipInTests()) {
       if (!templateState.isFinished()) templateState.gotoEnd();
@@ -178,6 +183,23 @@ public class TemplateManagerImpl extends TemplateManager implements ProjectCompo
     startTemplate(editor, null, template, listener, null);
   }
 
+  private static int passArgumentBack(CharSequence text, int caretOffset) {
+    int i = caretOffset - 1;
+    for (; i >= 0; i--) {
+      char c = text.charAt(i);
+      if (!Character.isJavaIdentifierPart(c)) {
+        break;
+      }
+    }
+    return i + 1;
+  }
+
+  private static <T, U> void addToMap(@NotNull Map<T, U> map, @NotNull Collection<? extends T> keys, U value) {
+    for (T key : keys) {
+      map.put(key, value);
+    }
+  }
+
   public boolean startTemplate(final Editor editor, char shortcutChar, final PairProcessor<String, String> processor) {
     final Document document = editor.getDocument();
     PsiFile file = PsiUtilBase.getPsiFileInEditor(editor, myProject);
@@ -185,78 +207,121 @@ public class TemplateManagerImpl extends TemplateManager implements ProjectCompo
 
     TemplateSettings templateSettings = TemplateSettings.getInstance();
     CharSequence text = document.getCharsSequence();
+
     final int caretOffset = editor.getCaretModel().getOffset();
-    String key = null;
-    List<TemplateImpl> candidates = Collections.emptyList();
-    for (int i = templateSettings.getMaxKeyLength(); i >= 1 ; i--) {
-      int wordStart = caretOffset - i;
-      if (wordStart < 0) {
-        continue;
-      }
-      key = text.subSequence(wordStart, caretOffset).toString();
-      if (Character.isJavaIdentifierStart(key.charAt(0))) {
-        if (wordStart > 0 && Character.isJavaIdentifierPart(text.charAt(wordStart - 1))) {
-          continue;
+    List<TemplateImpl> candidatesWithoutArgument = findMatchingTemplates(text, caretOffset, shortcutChar, templateSettings, false);
+
+    int argumentOffset = passArgumentBack(text, caretOffset);
+    String argument = null;
+    if (argumentOffset >= 0) {
+      argument = text.subSequence(argumentOffset, caretOffset).toString();
+      if (argumentOffset > 0 && text.charAt(argumentOffset - 1) == ' ') {
+        if (argumentOffset - 2 >= 0 && Character.isJavaIdentifierPart(text.charAt(argumentOffset - 2))) {
+          argumentOffset--;
         }
       }
-
-      candidates = templateSettings.collectMatchingCandidates(key, shortcutChar);
-      if (!candidates.isEmpty()) break;
     }
+    List<TemplateImpl> candidatesWithArgument = findMatchingTemplates(text, argumentOffset, shortcutChar, templateSettings, true);
 
-    if (candidates.isEmpty()) return false;
+    if (candidatesWithArgument.isEmpty() && candidatesWithoutArgument.isEmpty()) return false;
 
-    CommandProcessor.getInstance().executeCommand(
-      myProject, new Runnable() {
-        public void run() {
-          PsiDocumentManager.getInstance(myProject).commitDocument(document);
-        }
-      },
-      "", null
-    );
+    CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+      public void run() {
+        PsiDocumentManager.getInstance(myProject).commitDocument(document);
+      }
+    }, "", null);
+
+    candidatesWithoutArgument = filterApplicableCandidates(file, caretOffset, candidatesWithoutArgument);
+    candidatesWithArgument = filterApplicableCandidates(file, argumentOffset, candidatesWithArgument);
+    Map<TemplateImpl, String> candidate2Argument = new HashMap<TemplateImpl, String>();
+    addToMap(candidate2Argument, candidatesWithoutArgument, null);
+    addToMap(candidate2Argument, candidatesWithArgument, argument);
 
-    candidates = filterApplicableCandidates(file, caretOffset - key.length(), candidates);
-    if (candidates.isEmpty()) {
+    if (candidate2Argument.isEmpty()) {
       return false;
     }
     if (!FileDocumentManager.getInstance().requestWriting(editor.getDocument(), myProject)) {
       return false;
     }
 
-    if (candidates.size() == 1) {
-      TemplateImpl template = candidates.get(0);
-      startTemplateWithPrefix(editor, template, processor);
+    if (candidate2Argument.size() == 1) {
+      TemplateImpl template = candidate2Argument.keySet().iterator().next();
+      if (candidatesWithoutArgument.size() == 1) {
+        int templateStart = caretOffset - template.getKey().length();
+        startTemplateWithPrefix(editor, template, templateStart, processor, null);
+      }
+      else {
+        int templateStart = argumentOffset - template.getKey().length();
+        startTemplateWithPrefix(editor, template, templateStart, processor, argument);
+      }
     }
     else {
-      ListTemplatesHandler.showTemplatesLookup(myProject, editor, key, candidates);
+      ListTemplatesHandler.showTemplatesLookup(myProject, editor, candidate2Argument);
     }
-    
+
     return true;
   }
 
-  public void startTemplateWithPrefix(final Editor editor, final TemplateImpl template, @Nullable final PairProcessor<String, String> processor) {
+  private static List<TemplateImpl> findMatchingTemplates(CharSequence text,
+                                                          int caretOffset,
+                                                          char shortcutChar,
+                                                          TemplateSettings settings,
+                                                          boolean hasArgument) {
+    String key;
+    List<TemplateImpl> candidates = Collections.emptyList();
+    for (int i = settings.getMaxKeyLength(); i >= 1; i--) {
+      int wordStart = caretOffset - i;
+      if (wordStart < 0) {
+        continue;
+      }
+      key = text.subSequence(wordStart, caretOffset).toString();
+      if (Character.isJavaIdentifierStart(key.charAt(0))) {
+        if (wordStart > 0 && Character.isJavaIdentifierPart(text.charAt(wordStart - 1))) {
+          continue;
+        }
+      }
+
+      candidates = settings.collectMatchingCandidates(key, shortcutChar, hasArgument);
+      if (!candidates.isEmpty()) break;
+    }
+    return candidates;
+  }
+
+  public void startTemplateWithPrefix(final Editor editor,
+                                      final TemplateImpl template,
+                                      @Nullable final PairProcessor<String, String> processor,
+                                      @Nullable String argument) {
+    final int caretOffset = editor.getCaretModel().getOffset();
+    int startOffset = caretOffset - template.getKey().length();
+    if (argument != null) {
+      startOffset -= argument.length();
+    }
+    startTemplateWithPrefix(editor, template, startOffset, processor, argument);
+  }
+
+  public void startTemplateWithPrefix(final Editor editor,
+                                      final TemplateImpl template,
+                                      final int templateStart,
+                                      @Nullable final PairProcessor<String, String> processor,
+                                      @Nullable final String argument) {
     final int caretOffset = editor.getCaretModel().getOffset();
-    final int wordStart = caretOffset - template.getKey().length();
     final TemplateState templateState = initTemplateState(editor);
     CommandProcessor commandProcessor = CommandProcessor.getInstance();
-    commandProcessor.executeCommand(
-      myProject, new Runnable() {
-        public void run() {
-          editor.getDocument().deleteString(wordStart, caretOffset);
-          editor.getCaretModel().moveToOffset(wordStart);
-          editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
-          editor.getSelectionModel().removeSelection();
-          templateState.start(template, processor);
-        }
-      },
-      CodeInsightBundle.message("insert.code.template.command"), null
-    );
+    commandProcessor.executeCommand(myProject, new Runnable() {
+      public void run() {
+        editor.getDocument().deleteString(templateStart, caretOffset);
+        editor.getCaretModel().moveToOffset(templateStart);
+        editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
+        editor.getSelectionModel().removeSelection();
+        templateState.start(template, processor, argument);
+      }
+    }, CodeInsightBundle.message("insert.code.template.command"), null);
   }
 
-  private static List<TemplateImpl> filterApplicableCandidates(PsiFile file, int offset, List<TemplateImpl> candidates) {
+  private static List<TemplateImpl> filterApplicableCandidates(PsiFile file, int caretOffset, List<TemplateImpl> candidates) {
     List<TemplateImpl> result = new ArrayList<TemplateImpl>();
     for (TemplateImpl candidate : candidates) {
-      if (isApplicable(file, offset, candidate)) {
+      if (isApplicable(file, caretOffset - candidate.getKey().length(), candidate)) {
         result.add(candidate);
       }
     }
@@ -266,16 +331,20 @@ public class TemplateManagerImpl extends TemplateManager implements ProjectCompo
   public TemplateContextType getContextType(@NotNull PsiFile file, int offset) {
     final TemplateContextType[] typeCollection = getAllContextTypes();
     LinkedList<TemplateContextType> userDefinedExtensionsFirst = new LinkedList<TemplateContextType>();
-    for(TemplateContextType contextType: typeCollection) {
-      if (contextType.getClass().getName().startsWith("com.intellij.codeInsight.template")) userDefinedExtensionsFirst.addLast(contextType);
-      else userDefinedExtensionsFirst.addFirst(contextType);
+    for (TemplateContextType contextType : typeCollection) {
+      if (contextType.getClass().getName().startsWith("com.intellij.codeInsight.template")) {
+        userDefinedExtensionsFirst.addLast(contextType);
+      }
+      else {
+        userDefinedExtensionsFirst.addFirst(contextType);
+      }
     }
-    for(TemplateContextType contextType: userDefinedExtensionsFirst) {
+    for (TemplateContextType contextType : userDefinedExtensionsFirst) {
       if (contextType.isInContext(file, offset)) {
         return contextType;
       }
     }
-    assert false: "OtherContextType should match any context";
+    assert false : "OtherContextType should match any context";
     return null;
   }
 
index c5ddf57fc5bbe278315090d1e336b922e953d264..a1f72df86d463ebd0f9fb66ab90a227561e08836 100644 (file)
@@ -673,7 +673,7 @@ public class TemplateSettings implements PersistentStateComponent<Element>, Expo
     return mySchemesManager.getAllSchemes();
   }
 
-  public List<TemplateImpl> collectMatchingCandidates(String key, char shortcutChar) {
+  public List<TemplateImpl> collectMatchingCandidates(String key, char shortcutChar, boolean hasArgument) {
     final Collection<TemplateImpl> templates = getTemplates(key);
     List<TemplateImpl> candidates = new ArrayList<TemplateImpl>();
     for (TemplateImpl template : templates) {
@@ -686,6 +686,9 @@ public class TemplateSettings implements PersistentStateComponent<Element>, Expo
       if (template.isSelectionTemplate()) {
         continue;
       }
+      if (hasArgument && !template.hasArgument()) {
+        continue;
+      }
       candidates.add(template);
     }
     return candidates;
index c49899a941007354623b523ee12b07fa6f4fbef1..98b09560cae8c9cf0aedae4b912f5e45448b68a4 100644 (file)
@@ -71,6 +71,7 @@ public class TemplateState implements Disposable {
 
   private TemplateImpl myTemplate;
   private TemplateSegments mySegments = null;
+  private String myArgument;
 
   private RangeMarker myTemplateRange = null;
   private final ArrayList<RangeHighlighter> myTabStopHighlighters = new ArrayList<RangeHighlighter>();
@@ -106,6 +107,7 @@ public class TemplateState implements Disposable {
 
     myCommandListener = new CommandAdapter() {
       boolean started = false;
+
       public void commandStarted(CommandEvent event) {
         if (myEditor != null) {
           final int offset = myEditor.getCaretModel().getOffset();
@@ -170,6 +172,9 @@ public class TemplateState implements Disposable {
     if (variableName.equals(TemplateImpl.END)) {
       return new TextResult("");
     }
+    if (variableName.equals(TemplateImpl.ARG) && myArgument != null) {
+      return new TextResult(myArgument);
+    }
 
     CharSequence text = myDocument.getCharsSequence();
     int segmentNumber = myTemplate.getVariableSegmentNumber(variableName);
@@ -230,45 +235,43 @@ public class TemplateState implements Disposable {
     }
   }
 
-  public void start(TemplateImpl template, @Nullable final PairProcessor<String, String> processor) {
+  public void start(TemplateImpl template, @Nullable final PairProcessor<String, String> processor, @Nullable String argument) {
     PsiDocumentManager.getInstance(myProject).commitAllDocuments();
 
     myProcessor = processor;
 
-    final DocumentReference[] refs = myDocument == null
-                                     ? null
-                                     : new DocumentReference[] {DocumentReferenceManager.getInstance().create(myDocument) };
-
-    UndoManager.getInstance(myProject).undoableActionPerformed(
-      new UndoableAction() {
-        public void undo() {
-          if (myDocument != null) {
-            fireTemplateCancelled();
-            LookupManager.getInstance(myProject).hideActiveLookup();
-            int oldVar = myCurrentVariableNumber;
-            setCurrentVariableNumber(-1);
-            currentVariableChanged(oldVar);
-          }
+    final DocumentReference[] refs =
+      myDocument == null ? null : new DocumentReference[]{DocumentReferenceManager.getInstance().create(myDocument)};
+
+    UndoManager.getInstance(myProject).undoableActionPerformed(new UndoableAction() {
+      public void undo() {
+        if (myDocument != null) {
+          fireTemplateCancelled();
+          LookupManager.getInstance(myProject).hideActiveLookup();
+          int oldVar = myCurrentVariableNumber;
+          setCurrentVariableNumber(-1);
+          currentVariableChanged(oldVar);
         }
+      }
 
-        public void redo() {
-          //TODO:
-          // throw new UnexpectedUndoException("Not implemented");
-        }
+      public void redo() {
+        //TODO:
+        // throw new UnexpectedUndoException("Not implemented");
+      }
 
-        public DocumentReference[] getAffectedDocuments() {
-          return refs;
-        }
+      public DocumentReference[] getAffectedDocuments() {
+        return refs;
+      }
 
-        public boolean isGlobal() {
-          return false;
-        }
+      public boolean isGlobal() {
+        return false;
       }
-    );
+    });
     myTemplateIndented = false;
     myCurrentVariableNumber = -1;
     mySegments = new TemplateSegments(myEditor);
     myTemplate = template;
+    myArgument = argument;
 
 
     if (template.isInline()) {
@@ -297,39 +300,37 @@ public class TemplateState implements Disposable {
   }
 
   private void preprocessTemplate(final PsiFile file, int caretOffset, final String textToInsert) {
-    for(TemplatePreprocessor preprocessor: Extensions.getExtensions(TemplatePreprocessor.EP_NAME)) {
+    for (TemplatePreprocessor preprocessor : Extensions.getExtensions(TemplatePreprocessor.EP_NAME)) {
       preprocessor.preprocessTemplate(myEditor, file, caretOffset, textToInsert, myTemplate.getTemplateText());
     }
   }
 
   private void processAllExpressions(final TemplateImpl template) {
-    ApplicationManager.getApplication().runWriteAction(
-      new Runnable() {
-        public void run() {
-          if (!template.isInline()) myDocument.insertString(myTemplateRange.getStartOffset(), template.getTemplateText());
-          for (int i = 0; i < template.getSegmentsCount(); i++) {
-            int segmentOffset = myTemplateRange.getStartOffset() + template.getSegmentOffset(i);
-            mySegments.addSegment(segmentOffset, segmentOffset);
-          }
+    ApplicationManager.getApplication().runWriteAction(new Runnable() {
+      public void run() {
+        if (!template.isInline()) myDocument.insertString(myTemplateRange.getStartOffset(), template.getTemplateText());
+        for (int i = 0; i < template.getSegmentsCount(); i++) {
+          int segmentOffset = myTemplateRange.getStartOffset() + template.getSegmentOffset(i);
+          mySegments.addSegment(segmentOffset, segmentOffset);
+        }
 
-          calcResults(false);
-          calcResults(false);  //Fixed SCR #[vk500] : all variables should be recalced twice on start.
-          doReformat();
+        calcResults(false);
+        calcResults(false);  //Fixed SCR #[vk500] : all variables should be recalced twice on start.
+        doReformat();
 
-          int nextVariableNumber = getNextVariableNumber(-1);
-          if (nextVariableNumber == -1) {
-            finishTemplateEditing();
-          }
-          else {
-            setCurrentVariableNumber(nextVariableNumber);
-            initTabStopHighlighters();
-            initListeners();
-            focusCurrentExpression();
-            currentVariableChanged(-1);
-          }
+        int nextVariableNumber = getNextVariableNumber(-1);
+        if (nextVariableNumber == -1) {
+          finishTemplateEditing();
+        }
+        else {
+          setCurrentVariableNumber(nextVariableNumber);
+          initTabStopHighlighters();
+          initListeners();
+          focusCurrentExpression();
+          currentVariableChanged(-1);
         }
       }
-    );
+    });
   }
 
   private void doReformat() {
@@ -352,7 +353,7 @@ public class TemplateState implements Disposable {
         if (file != null) {
           IntArrayList indices = initEmptyVariables();
           mySegments.setSegmentsGreedy(false);
-          for(TemplateOptionalProcessor processor: Extensions.getExtensions(TemplateOptionalProcessor.EP_NAME)) {
+          for (TemplateOptionalProcessor processor : Extensions.getExtensions(TemplateOptionalProcessor.EP_NAME)) {
             processor.processText(myProject, myTemplate, myDocument, myTemplateRange, myEditor);
           }
           mySegments.setSegmentsGreedy(true);
@@ -371,7 +372,8 @@ public class TemplateState implements Disposable {
         setCurrentVariableNumber(-1);
         currentVariableChanged(oldIndex);
         fireTemplateCancelled();
-      } else {
+      }
+      else {
         calcResults(true);
       }
       myDocumentChanged = false;
@@ -427,15 +429,16 @@ public class TemplateState implements Disposable {
         final String s = lookupItems[0].getLookupString();
         EditorModificationUtil.insertStringAtCaret(myEditor, s);
         itemSelected(lookupItems[0], psiFile, currentSegmentNumber, ' ', lookupItems);
-      } else {
+      }
+      else {
         runLookup(currentSegmentNumber, lookupItems, psiFile);
       }
     }
     else {
       Result result = expressionNode.calculateResult(context);
       if (result != null) {
-        result.handleFocused(psiFile, myDocument,
-                             mySegments.getSegmentStart(currentSegmentNumber), mySegments.getSegmentEnd(currentSegmentNumber));
+        result.handleFocused(psiFile, myDocument, mySegments.getSegmentStart(currentSegmentNumber),
+                             mySegments.getSegmentEnd(currentSegmentNumber));
       }
     }
     focusCurrentHighlighter(true);
@@ -465,7 +468,11 @@ public class TemplateState implements Disposable {
     });
   }
 
-  private void itemSelected(final LookupElement item, final PsiFile psiFile, final int currentSegmentNumber, final char completionChar, LookupElement[] elements) {
+  private void itemSelected(final LookupElement item,
+                            final PsiFile psiFile,
+                            final int currentSegmentNumber,
+                            final char completionChar,
+                            LookupElement[] elements) {
     if (item != null) {
       PsiDocumentManager.getInstance(myProject).commitAllDocuments();
 
@@ -490,11 +497,13 @@ public class TemplateState implements Disposable {
         PsiDocumentManager.getInstance(myProject).commitDocument(myDocument);
       }
 
-      final TemplateLookupSelectionHandler handler = item instanceof LookupItem ? ((LookupItem<?>)item).getAttribute(TemplateLookupSelectionHandler.KEY_IN_LOOKUP_ITEM) : null;
+      final TemplateLookupSelectionHandler handler =
+        item instanceof LookupItem ? ((LookupItem<?>)item).getAttribute(TemplateLookupSelectionHandler.KEY_IN_LOOKUP_ITEM) : null;
       if (handler != null) {
-        handler.itemSelected(item, psiFile, myDocument,
-                             mySegments.getSegmentStart(currentSegmentNumber), mySegments.getSegmentEnd(currentSegmentNumber));
-      } else {
+        handler.itemSelected(item, psiFile, myDocument, mySegments.getSegmentStart(currentSegmentNumber),
+                             mySegments.getSegmentEnd(currentSegmentNumber));
+      }
+      else {
         new WriteCommandAction(myProject) {
           protected void run(com.intellij.openapi.application.Result result) throws Throwable {
             item.handleInsert(context);
@@ -533,45 +542,43 @@ public class TemplateState implements Disposable {
       }
     }
 
-    ApplicationManager.getApplication().runWriteAction(
-      new Runnable() {
-        public void run() {
-          BitSet calcedSegments = new BitSet();
-          int maxAttempts = (myTemplate.getVariableCount()+1)*3;
-
-          do {
-            maxAttempts--;
-            calcedSegments.clear();
-            for (int i = myCurrentVariableNumber + 1; i < myTemplate.getVariableCount(); i++) {
-              String variableName = myTemplate.getVariableNameAt(i);
-              int segmentNumber = myTemplate.getVariableSegmentNumber(variableName);
-              if (segmentNumber < 0) continue;
-              Expression expression = myTemplate.getExpressionAt(i);
-              Expression defaultValue = myTemplate.getDefaultValueAt(i);
-              String oldValue = getVariableValue(variableName).getText();
-              recalcSegment(segmentNumber, isQuick, expression, defaultValue);
-              final TextResult value = getVariableValue(variableName);
-              assert value != null : "name=" + variableName + "\ntext=" + myTemplate.getTemplateText();
-              String newValue = value.getText();
-              if (!newValue.equals(oldValue)) {
-                calcedSegments.set(segmentNumber);
-              }
+    ApplicationManager.getApplication().runWriteAction(new Runnable() {
+      public void run() {
+        BitSet calcedSegments = new BitSet();
+        int maxAttempts = (myTemplate.getVariableCount() + 1) * 3;
+
+        do {
+          maxAttempts--;
+          calcedSegments.clear();
+          for (int i = myCurrentVariableNumber + 1; i < myTemplate.getVariableCount(); i++) {
+            String variableName = myTemplate.getVariableNameAt(i);
+            int segmentNumber = myTemplate.getVariableSegmentNumber(variableName);
+            if (segmentNumber < 0) continue;
+            Expression expression = myTemplate.getExpressionAt(i);
+            Expression defaultValue = myTemplate.getDefaultValueAt(i);
+            String oldValue = getVariableValue(variableName).getText();
+            recalcSegment(segmentNumber, isQuick, expression, defaultValue);
+            final TextResult value = getVariableValue(variableName);
+            assert value != null : "name=" + variableName + "\ntext=" + myTemplate.getTemplateText();
+            String newValue = value.getText();
+            if (!newValue.equals(oldValue)) {
+              calcedSegments.set(segmentNumber);
             }
+          }
 
-            for (int i = 0; i < myTemplate.getSegmentsCount(); i++) {
-              if (!calcedSegments.get(i)) {
-                String variableName = myTemplate.getSegmentName(i);
-                String newValue = getVariableValue(variableName).getText();
-                int start = mySegments.getSegmentStart(i);
-                int end = mySegments.getSegmentEnd(i);
-                replaceString(newValue, start, end, i);
-              }
+          for (int i = 0; i < myTemplate.getSegmentsCount(); i++) {
+            if (!calcedSegments.get(i)) {
+              String variableName = myTemplate.getSegmentName(i);
+              String newValue = getVariableValue(variableName).getText();
+              int start = mySegments.getSegmentStart(i);
+              int end = mySegments.getSegmentEnd(i);
+              replaceString(newValue, start, end, i);
             }
           }
-          while (!calcedSegments.isEmpty() && maxAttempts >= 0);
         }
+        while (!calcedSegments.isEmpty() && maxAttempts >= 0);
       }
-    );
+    });
   }
 
   private void recalcSegment(int segmentNumber, boolean isQuick, Expression expressionNode, Expression defaultValue) {
@@ -615,8 +622,8 @@ public class TemplateState implements Disposable {
     if (result instanceof RecalculatableResult) {
       shortenReferences();
       PsiDocumentManager.getInstance(myProject).commitDocument(myDocument);
-      ((RecalculatableResult) result).handleRecalc(psiFile, myDocument,
-                                                   mySegments.getSegmentStart(segmentNumber), mySegments.getSegmentEnd(segmentNumber));
+      ((RecalculatableResult)result)
+        .handleRecalc(psiFile, myDocument, mySegments.getSegmentStart(segmentNumber), mySegments.getSegmentEnd(segmentNumber));
     }
   }
 
@@ -632,11 +639,9 @@ public class TemplateState implements Disposable {
       mySegments.setNeighboursGreedy(segmentNumber, true);
 
       if (segmentNumberWithTheSameStart != -1) {
-        mySegments.replaceSegmentAt(
-          segmentNumberWithTheSameStart,
-          newEnd,
-          newEnd + mySegments.getSegmentEnd(segmentNumberWithTheSameStart) - mySegments.getSegmentStart(segmentNumberWithTheSameStart)
-        );
+        mySegments.replaceSegmentAt(segmentNumberWithTheSameStart, newEnd,
+                                    newEnd + mySegments.getSegmentEnd(segmentNumberWithTheSameStart) -
+                                    mySegments.getSegmentStart(segmentNumberWithTheSameStart));
       }
     }
   }
@@ -723,7 +728,7 @@ public class TemplateState implements Disposable {
       }
 
       public <T> T getProperty(Key<T> key) {
-        return (T) myProperties.get(key);
+        return (T)myProperties.get(key);
       }
     };
   }
@@ -743,7 +748,8 @@ public class TemplateState implements Disposable {
     int offset = -1;
     if (endSegmentNumber >= 0) {
       offset = mySegments.getSegmentStart(endSegmentNumber);
-    } else {
+    }
+    else {
       if (!myTemplate.isSelectionTemplate() && !myTemplate.isInline()) { //do not move caret to the end of range for selection templates
         offset = myTemplateRange.getEndOffset();
       }
@@ -758,10 +764,7 @@ public class TemplateState implements Disposable {
     int selStart = myTemplate.getSelectionStartSegmentNumber();
     int selEnd = myTemplate.getSelectionEndSegmentNumber();
     if (selStart >= 0 && selEnd >= 0) {
-      myEditor.getSelectionModel().setSelection(
-        mySegments.getSegmentStart(selStart),
-        mySegments.getSegmentStart(selEnd)
-      );
+      myEditor.getSelectionModel().setSelection(mySegments.getSegmentStart(selStart), mySegments.getSegmentStart(selEnd));
     }
     fireBeforeTemplateFinished();
     final Editor editor = myEditor;
@@ -797,10 +800,12 @@ public class TemplateState implements Disposable {
     if (expression == null) {
       return false;
     }
-    if (myTemplate.isAlwaysStopAt(currentVariableNumber)) {
-      return true;
-    }
     String variableName = myTemplate.getVariableNameAt(currentVariableNumber);
+    if (!(TemplateImpl.ARG.equals(variableName) && myArgument != null)) {
+      if (myTemplate.isAlwaysStopAt(currentVariableNumber)) {
+        return true;
+      }
+    }
     int segmentNumber = myTemplate.getVariableSegmentNumber(variableName);
     if (segmentNumber < 0) return false;
     int start = mySegments.getSegmentStart(segmentNumber);
@@ -867,9 +872,7 @@ public class TemplateState implements Disposable {
   }
 
   private RangeHighlighter getSegmentHighlighter(int segmentNumber, boolean isSelected, boolean isEnd) {
-    TextAttributes attributes = isSelected
-                                ? new TextAttributes(null, null, Color.red, EffectType.BOXED, Font.PLAIN)
-                                : new TextAttributes();
+    TextAttributes attributes = isSelected ? new TextAttributes(null, null, Color.red, EffectType.BOXED, Font.PLAIN) : new TextAttributes();
     TextAttributes endAttributes = new TextAttributes();
 
     RangeHighlighter segmentHighlighter;
@@ -880,8 +883,8 @@ public class TemplateState implements Disposable {
         .addRangeHighlighter(start, end, HighlighterLayer.LAST + 1, endAttributes, HighlighterTargetArea.EXACT_RANGE);
     }
     else {
-      segmentHighlighter = myEditor.getMarkupModel()
-        .addRangeHighlighter(start, end, HighlighterLayer.LAST + 1, attributes, HighlighterTargetArea.EXACT_RANGE);
+      segmentHighlighter =
+        myEditor.getMarkupModel().addRangeHighlighter(start, end, HighlighterLayer.LAST + 1, attributes, HighlighterTargetArea.EXACT_RANGE);
     }
     segmentHighlighter.setGreedyToLeft(true);
     segmentHighlighter.setGreedyToRight(true);
@@ -910,7 +913,7 @@ public class TemplateState implements Disposable {
     final PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(myDocument);
     if (file != null) {
       CodeStyleManager style = CodeStyleManager.getInstance(myProject);
-      for(TemplateOptionalProcessor optionalProcessor : Extensions.getExtensions(TemplateOptionalProcessor.EP_NAME)) {
+      for (TemplateOptionalProcessor optionalProcessor : Extensions.getExtensions(TemplateOptionalProcessor.EP_NAME)) {
         optionalProcessor.processText(myProject, myTemplate, myDocument, myTemplateRange, myEditor);
       }
       if (myTemplate.isToReformat()) {
@@ -921,7 +924,7 @@ public class TemplateState implements Disposable {
           if (endSegmentNumber >= 0) {
             int endVarOffset = mySegments.getSegmentStart(endSegmentNumber);
             PsiElement marker = style.insertNewLineIndentMarker(file, endVarOffset);
-            if(marker != null) rangeMarker = myDocument.createRangeMarker(marker.getTextRange());
+            if (marker != null) rangeMarker = myDocument.createRangeMarker(marker.getTextRange());
           }
           style.reformatText(file, myTemplateRange.getStartOffset(), myTemplateRange.getEndOffset());
           PsiDocumentManager.getInstance(myProject).commitDocument(myDocument);