"wrap with abbreviation" feature
authorEugene.Kudelevsky <ekudel@gmail.com>
Thu, 25 Mar 2010 18:12:12 +0000 (21:12 +0300)
committerEugene.Kudelevsky <ekudel@gmail.com>
Thu, 25 Mar 2010 18:12:12 +0000 (21:12 +0300)
platform/lang-impl/src/com/intellij/codeInsight/template/impl/SurroundWithTemplateHandler.java
platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateManagerImpl.java
platform/lang-impl/src/com/intellij/codeInsight/template/impl/WrapWithCustomTemplateAction.java [new file with mode: 0644]
platform/platform-resources-en/src/messages/XmlBundle.properties
platform/platform-resources/src/META-INF/XmlPlugin.xml
xml/impl/src/com/intellij/codeInsight/completion/XmlSmartEnterProcessor.java
xml/impl/src/com/intellij/codeInsight/template/zencoding/XmlZenCodingInterpreter.java
xml/impl/src/com/intellij/codeInsight/template/zencoding/XmlZenCodingTemplate.java

index 756edcbc097103f4e554402d58550bebe5bd22b2..c93f919e8752fbe3d3c2ab8b6e1000ac8027f183 100644 (file)
@@ -20,6 +20,7 @@ import com.intellij.codeInsight.CodeInsightActionHandler;
 import com.intellij.codeInsight.CodeInsightBundle;
 import com.intellij.codeInsight.CodeInsightUtilBase;
 import com.intellij.codeInsight.hint.HintManager;
+import com.intellij.codeInsight.template.CustomLiveTemplate;
 import com.intellij.ide.DataManager;
 import com.intellij.openapi.actionSystem.DefaultActionGroup;
 import com.intellij.openapi.editor.Editor;
@@ -42,8 +43,9 @@ public class SurroundWithTemplateHandler implements CodeInsightActionHandler {
       if (!editor.getSelectionModel().hasSelection()) return;
     }
     PsiDocumentManager.getInstance(project).commitDocument(editor.getDocument());
-    ArrayList<TemplateImpl> array = getApplicableTemplates(editor, file, true);
-    if (array.isEmpty()) {
+    List<CustomLiveTemplate> customTemplates = getApplicableCustomTemplates(editor, file);
+    ArrayList<TemplateImpl> templates = getApplicableTemplates(editor, file, true);
+    if (templates.isEmpty() && customTemplates.isEmpty()) {
       HintManager.getInstance().showErrorHint(editor, CodeInsightBundle.message("templates.surround.no.defined"));
       return;
     }
@@ -52,16 +54,17 @@ public class SurroundWithTemplateHandler implements CodeInsightActionHandler {
 
     Set<Character> usedMnemonicsSet = new HashSet<Character>();
     DefaultActionGroup group = new DefaultActionGroup();
-    for (TemplateImpl template : array) {
+    for (CustomLiveTemplate customTemplate : customTemplates) {
+      group.add(new WrapWithCustomTemplateAction(customTemplate, editor, file, usedMnemonicsSet));
+    }
+    for (TemplateImpl template : templates) {
       group.add(new InvokeTemplateAction(template, editor, project, usedMnemonicsSet));
     }
 
-    final ListPopup popup = JBPopupFactory.getInstance().createActionGroupPopup(
-      CodeInsightBundle.message("templates.select.template.chooser.title"),
-      group,
-      DataManager.getInstance().getDataContext(editor.getContentComponent()),
-      JBPopupFactory.ActionSelectionAid.MNEMONICS,
-      false);
+    final ListPopup popup = JBPopupFactory.getInstance()
+      .createActionGroupPopup(CodeInsightBundle.message("templates.select.template.chooser.title"), group,
+                              DataManager.getInstance().getDataContext(editor.getContentComponent()),
+                              JBPopupFactory.ActionSelectionAid.MNEMONICS, false);
 
     popup.showInBestPositionFor(editor);
   }
@@ -70,6 +73,16 @@ public class SurroundWithTemplateHandler implements CodeInsightActionHandler {
     return true;
   }
 
+  private static List<CustomLiveTemplate> getApplicableCustomTemplates(Editor editor, PsiFile file) {
+    List<CustomLiveTemplate> result = new ArrayList<CustomLiveTemplate>();
+    int offset = editor.getCaretModel().getOffset();
+    for (CustomLiveTemplate template : CustomLiveTemplate.EP_NAME.getExtensions()) {
+      if (template.isApplicable(file, offset, true)) {
+        result.add(template);
+      }
+    }
+    return result;
+  }
 
   public static ArrayList<TemplateImpl> getApplicableTemplates(Editor editor, PsiFile file, boolean selection) {
     int offset = editor.getCaretModel().getOffset();
index f468b813c4b07304cf279af050050e117688ed77..26f37bf59d6b311bee9a1286bfe53c57541d7a3b 100644 (file)
@@ -220,11 +220,50 @@ public class TemplateManagerImpl extends TemplateManager implements ProjectCompo
     }
   }
 
+  private static boolean containsTemplateStartingBefore(Map<TemplateImpl, String> template2argument,
+                                                        int offset,
+                                                        int caretOffset,
+                                                        CharSequence text) {
+    for (TemplateImpl template : template2argument.keySet()) {
+      String argument = template2argument.get(template);
+      int templateStart = getTemplateStart(template, argument, caretOffset, text);
+      if (templateStart <= offset) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   public boolean startTemplate(final Editor editor, char shortcutChar, final PairProcessor<String, String> processor) {
     PsiFile file = PsiUtilBase.getPsiFileInEditor(editor, myProject);
     if (file == null) return false;
     TemplateSettings templateSettings = TemplateSettings.getInstance();
+
     Map<TemplateImpl, String> template2argument = findMatchingTemplates(file, editor, shortcutChar, templateSettings);
+
+    if (shortcutChar == templateSettings.getDefaultShortcutChar()) {
+      for (final CustomLiveTemplate customLiveTemplate : CustomLiveTemplate.EP_NAME.getExtensions()) {
+        int caretOffset = editor.getCaretModel().getOffset();
+        if (customLiveTemplate.isApplicable(file, caretOffset, false)) {
+          final CustomTemplateCallback callback = new CustomTemplateCallback(editor, file);
+          String key = customLiveTemplate.computeTemplateKey(callback);
+          if (key != null) {
+            int offsetBeforeKey = caretOffset - key.length();
+            CharSequence text = editor.getDocument().getCharsSequence();
+            if (template2argument == null || !containsTemplateStartingBefore(template2argument, offsetBeforeKey, caretOffset, text)) {
+              callback.getEditor().getDocument().deleteString(offsetBeforeKey, caretOffset);
+              callback.fixInitialState();
+              customLiveTemplate.expand(key, callback, new TemplateInvokationListener() {
+                public void finished(boolean inSeparateEvent) {
+                  callback.finish();
+                }
+              });
+              return true;
+            }
+          }
+        }
+      }
+    }
     return startNonCustomTemplates(template2argument, editor, processor);
   }
 
diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/impl/WrapWithCustomTemplateAction.java b/platform/lang-impl/src/com/intellij/codeInsight/template/impl/WrapWithCustomTemplateAction.java
new file mode 100644 (file)
index 0000000..e5fcba1
--- /dev/null
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2000-2010 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.template.CustomLiveTemplate;
+import com.intellij.codeInsight.template.CustomTemplateCallback;
+import com.intellij.codeInsight.template.TemplateInvokationListener;
+import com.intellij.openapi.actionSystem.AnAction;
+import com.intellij.openapi.actionSystem.AnActionEvent;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.vfs.ReadonlyStatusHandler;
+import com.intellij.openapi.vfs.VirtualFile;
+import com.intellij.psi.PsiFile;
+
+import java.util.Set;
+
+/**
+ * Created by IntelliJ IDEA.
+ * User: Eugene.Kudelevsky
+ * Date: 25.03.2010
+ * Time: 1:50:53
+ * To change this template use File | Settings | File Templates.
+ */
+public class WrapWithCustomTemplateAction extends AnAction {
+  private final CustomLiveTemplate myTemplate;
+  private final Editor myEditor;
+  private final PsiFile myFile;
+
+  public WrapWithCustomTemplateAction(CustomLiveTemplate template,
+                                      final Editor editor,
+                                      final PsiFile file,
+                                      final Set<Character> usedMnemonicsSet) {
+    super(InvokeTemplateAction.extractMnemonic(template.getTitle(), usedMnemonicsSet));
+    myTemplate = template;
+    myFile = file;
+    myEditor = editor;
+  }
+
+
+  public void actionPerformed(AnActionEvent e) {
+    final Document document = myEditor.getDocument();
+    final VirtualFile file = FileDocumentManager.getInstance().getFile(document);
+    if (file != null) {
+      ReadonlyStatusHandler.getInstance(myFile.getProject()).ensureFilesWritable(file);
+    }
+
+    String selection = myEditor.getSelectionModel().getSelectedText();
+
+    if (selection != null) {
+      selection = selection.trim();
+      final CustomTemplateCallback callback = new CustomTemplateCallback(myEditor, myFile);
+      myTemplate.wrap(selection, callback, new TemplateInvokationListener() {
+        public void finished(boolean inSeparateEvent) {
+          callback.finish();
+        }
+      });
+    }
+  }
+}
index 9fe4c785ea5e44302f5676df26e426ab478129c3..888bd111a133a8e422b541ebcca8c5713ea767a9 100644 (file)
@@ -232,5 +232,7 @@ label.text.path.to.profiles.ini=Path to \"profiles.ini\":
 label.text.profile=&Profile:
 button.text.settings=Settings...
 display.name.firefox.settings=Firefox Settings
-zen.coding.settings=XML Zen Coding
+zen.coding.title=XML Zen Coding
 zen.coding.enable.label=Enable Zen Coding
+zen.coding.enter.abbreviation.dialog.label=Please entrer an abbreviation
+zen.coding.incorrect.abbreviation.error=Incorrect abbreviation
index 7e8665c34c8a407f35cbb9b9d053dd4212ef50e3..19e768f3bdfdc5aac2196029513df3c8151791a0 100644 (file)
 
     <highlightVisitor implementation="com.intellij.codeInsight.daemon.impl.analysis.XmlHighlightVisitor"/>
 
+    <customLiveTemplate implementation="com.intellij.codeInsight.template.zencoding.XmlZenCodingTemplate"/>
+
     <externalAnnotator language="XML" implementationClass="com.intellij.lang.xml.XMLExternalAnnotator"/>
     <externalAnnotator language="HTML" implementationClass="com.intellij.lang.xml.XMLExternalAnnotator"/>
     <externalAnnotator language="XHTML" implementationClass="com.intellij.lang.xml.XMLExternalAnnotator"/>
index 745dd2353a4ba87ac28bbfcaa73f892fe2517ed9..be20f7683e299d306a81b4de6a0eca8abfc27a1a 100644 (file)
@@ -49,7 +49,7 @@ public class XmlSmartEnterProcessor extends SmartEnterProcessor {
         final ASTNode emptyTagEnd = XmlChildRole.EMPTY_TAG_END_FINDER.findChild(tagAtCaret.getNode());
         final ASTNode endTagEnd = XmlChildRole.START_TAG_END_FINDER.findChild(tagAtCaret.getNode());
         if (emptyTagEnd != null || endTagEnd != null) {
-          return XmlZenCodingTemplate.startZenCoding(editor, psiFile);
+          return XmlZenCodingTemplate.startZenCoding(editor, psiFile, null);
         }
 
         int insertionOffset = tagAtCaret.getTextRange().getEndOffset();
index 5f6358b5af68c1f0355f86c2afb4ca779719cc46..5751a50e62387476bc33af354b19f0690bbfa2fe 100644 (file)
@@ -49,16 +49,27 @@ import java.util.*;
 class XmlZenCodingInterpreter {
   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.template.zencoding.XmlZenCodingInterpreter");
   private static final String ATTRS = "ATTRS";
+  private static final String NUMBER_IN_ITERATION_PLACE_HOLDER = "$";
 
   private final List<Token> myTokens;
+  private final int myFromIndex;
+
   private final CustomTemplateCallback myCallback;
   private final TemplateInvokationListener myListener;
-  private static final String NUMBER_IN_ITERATION_PLACE_HOLDER = "$";
+  private final String mySurroundedText;
+
   private State myState;
 
-  XmlZenCodingInterpreter(List<Token> tokens, CustomTemplateCallback callback, State initialState, TemplateInvokationListener listener) {
+  private XmlZenCodingInterpreter(List<Token> tokens,
+                                  int fromIndex,
+                                  CustomTemplateCallback callback,
+                                  State initialState,
+                                  String surroundedText,
+                                  TemplateInvokationListener listener) {
     myTokens = tokens;
+    myFromIndex = fromIndex;
     myCallback = callback;
+    mySurroundedText = surroundedText;
     myListener = listener;
     myState = initialState;
   }
@@ -114,7 +125,20 @@ class XmlZenCodingInterpreter {
     }*/
   }
 
-  public boolean invoke(int startIndex) {
+  // returns if expanding finished
+
+  public static boolean interpret(List<Token> tokens,
+                                  int startIndex,
+                                  CustomTemplateCallback callback,
+                                  State initialState,
+                                  String surroundedText,
+                                  TemplateInvokationListener listener) {
+    XmlZenCodingInterpreter interpreter =
+      new XmlZenCodingInterpreter(tokens, startIndex, callback, initialState, surroundedText, listener);
+    return interpreter.invoke(startIndex);
+  }
+
+  private boolean invoke(int startIndex) {
     final int n = myTokens.size();
     TemplateToken templateToken = null;
     int number = -1;
@@ -126,7 +150,7 @@ class XmlZenCodingInterpreter {
           if (templateToken != null) {
             if (token instanceof MarkerToken || token instanceof OperationToken) {
               final char sign = token instanceof OperationToken ? ((OperationToken)token).mySign : XmlZenCodingTemplate.MARKER;
-              if (sign == XmlZenCodingTemplate.MARKER || sign == '+') {
+              if (sign == '+' || (mySurroundedText == null && sign == XmlZenCodingTemplate.MARKER)) {
                 final Object key = new Object();
                 myCallback.fixStartOfTemplate(key);
                 TemplateInvokationListener listener = new TemplateInvokationListener() {
@@ -148,7 +172,7 @@ class XmlZenCodingInterpreter {
                 }
                 templateToken = null;
               }
-              else if (sign == '>') {
+              else if (sign == '>' || (mySurroundedText != null && sign == XmlZenCodingTemplate.MARKER)) {
                 if (!startTemplateAndGotoChild(templateToken, finalI)) {
                   return false;
                 }
@@ -184,14 +208,14 @@ class XmlZenCodingInterpreter {
         case AFTER_NUMBER:
           if (token instanceof MarkerToken || token instanceof OperationToken) {
             char sign = token instanceof OperationToken ? ((OperationToken)token).mySign : XmlZenCodingTemplate.MARKER;
-            if (sign == XmlZenCodingTemplate.MARKER || sign == '+') {
+            if (sign == '+' || (mySurroundedText == null && sign == XmlZenCodingTemplate.MARKER)) {
               if (!invokeTemplateSeveralTimes(templateToken, 0, number, finalI)) {
                 return false;
               }
               templateToken = null;
             }
             else if (number > 1) {
-              return invokeTemplateAndProcessTail(templateToken, 0, number, i + 1);
+              return invokeTemplateAndProcessTail(templateToken, 0, number, i + 1, startIndex != myFromIndex);
             }
             else {
               assert number == 1;
@@ -208,7 +232,10 @@ class XmlZenCodingInterpreter {
           break;
       }
     }
-    finish(startIndex == n);
+    if (mySurroundedText != null) {
+      insertText(myCallback, mySurroundedText);
+    }
+    finish(startIndex != myFromIndex);
     return true;
   }
 
@@ -262,10 +289,18 @@ class XmlZenCodingInterpreter {
     return true;
   }
 
+  private static void insertText(CustomTemplateCallback callback, String text) {
+    Editor editor = callback.getEditor();
+    int offset = editor.getCaretModel().getOffset();
+    editor.getDocument().insertString(offset, text);
+    PsiDocumentManager.getInstance(callback.getProject()).commitAllDocuments();
+  }
+
   private boolean invokeTemplateAndProcessTail(final TemplateToken templateToken,
                                                final int startIndex,
                                                final int count,
-                                               final int tailStart) {
+                                               final int tailStart,
+                                               boolean separateEvent) {
     final Object key = new Object();
     myCallback.fixStartOfTemplate(key);
     for (int i = startIndex; i < count; i++) {
@@ -274,21 +309,19 @@ class XmlZenCodingInterpreter {
       TemplateInvokationListener listener = new TemplateInvokationListener() {
         public void finished(boolean inSeparateEvent) {
           gotoChild(key);
-          XmlZenCodingInterpreter interpreter =
-            new XmlZenCodingInterpreter(myTokens, myCallback, State.WORD, new TemplateInvokationListener() {
-              public void finished(boolean inSeparateEvent) {
-                if (myCallback.getOffset() != myCallback.getEndOfTemplate(key)) {
-                  myCallback.fixEndOffset();
-                }
-                myCallback.gotoEndOfTemplate(key);
-                if (inSeparateEvent) {
-                  invokeTemplateAndProcessTail(templateToken, finalI + 1, count, tailStart);
-                }
+          if (interpret(myTokens, tailStart, myCallback, State.WORD, mySurroundedText, new TemplateInvokationListener() {
+            public void finished(boolean inSeparateEvent) {
+              if (myCallback.getOffset() != myCallback.getEndOfTemplate(key)) {
+                myCallback.fixEndOffset();
               }
-            });
-          if (interpreter.invoke(tailStart)) {
+              myCallback.gotoEndOfTemplate(key);
+              if (inSeparateEvent) {
+                invokeTemplateAndProcessTail(templateToken, finalI + 1, count, tailStart, true);
+              }
+            }
+          })) {
             if (inSeparateEvent) {
-              invokeTemplateAndProcessTail(templateToken, finalI + 1, count, tailStart);
+              invokeTemplateAndProcessTail(templateToken, finalI + 1, count, tailStart, true);
             }
           }
           else {
@@ -300,7 +333,7 @@ class XmlZenCodingInterpreter {
         return false;
       }
     }
-    finish(count == 0);
+    finish(separateEvent);
     return true;
   }
 
index aa690594f3abe99717b714a807c0832bb8a8187f..4c177487d934421b5841cc27eeef76cacfba37ed 100644 (file)
 package com.intellij.codeInsight.template.zencoding;
 
 import com.intellij.application.options.editor.WebEditorOptions;
+import com.intellij.codeInsight.CodeInsightBundle;
+import com.intellij.codeInsight.template.CustomLiveTemplate;
 import com.intellij.codeInsight.template.CustomTemplateCallback;
 import com.intellij.codeInsight.template.TemplateInvokationListener;
 import com.intellij.codeInsight.template.impl.TemplateImpl;
 import com.intellij.lang.xml.XMLLanguage;
+import com.intellij.openapi.application.ApplicationManager;
+import com.intellij.openapi.command.CommandProcessor;
 import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.editor.EditorModificationUtil;
 import com.intellij.openapi.fileTypes.FileType;
 import com.intellij.openapi.fileTypes.StdFileTypes;
+import com.intellij.openapi.ui.InputValidatorEx;
+import com.intellij.openapi.ui.Messages;
 import com.intellij.openapi.util.Pair;
+import com.intellij.psi.PsiDocumentManager;
 import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiFile;
 import com.intellij.psi.PsiFileFactory;
@@ -32,6 +40,7 @@ import com.intellij.psi.xml.XmlFile;
 import com.intellij.psi.xml.XmlTag;
 import com.intellij.util.LocalTimeCounter;
 import com.intellij.util.containers.HashSet;
+import com.intellij.xml.XmlBundle;
 import org.apache.xerces.util.XML11Char;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -44,7 +53,7 @@ import java.util.Set;
 /**
  * @author Eugene.Kudelevsky
  */
-public class XmlZenCodingTemplate {
+public class XmlZenCodingTemplate implements CustomLiveTemplate {
   static final char MARKER = '$';
   private static final String OPERATIONS = ">+*";
   private static final String SELECTORS = ".#[";
@@ -307,18 +316,7 @@ public class XmlZenCodingTemplate {
       parentStart = element != null ? element.getTextRange().getStartOffset() : 0;
       int startOffset = parentStart > lineStart ? parentStart : lineStart;
       String key = computeKey(editor, startOffset);
-      List<Token> tokens = parse(key, callback);
-      if (tokens != null && check(tokens)) {
-        // !! required if Zen Coding if invoked by TemplateManagerImpl action
-        /*if (tokens.size() == 2) {
-          Token token = tokens.get(0);
-          if (token instanceof TemplateToken) {
-            if (key.equals(((TemplateToken)token).myKey) && callback.isLiveTemplateApplicable(key)) {
-              // do not activate only live template
-              return null;
-            }
-          }
-        }*/
+      if (checkTemplateKey(key, callback)) {
         return key;
       }
       if (element != null) {
@@ -329,7 +327,29 @@ public class XmlZenCodingTemplate {
     return null;
   }
 
+  private static boolean checkTemplateKey(String key, CustomTemplateCallback callback) {
+    List<Token> tokens = parse(key, callback);
+    if (tokens != null && check(tokens)) {
+      // !! required if Zen Coding if invoked by TemplateManagerImpl action
+      /*if (tokens.size() == 2) {
+        Token token = tokens.get(0);
+        if (token instanceof TemplateToken) {
+          if (key.equals(((TemplateToken)token).myKey) && callback.isLiveTemplateApplicable(key)) {
+            // do not activate only live template
+            return null;
+          }
+        }
+      }*/
+      return true;
+    }
+    return false;
+  }
+
   public boolean isApplicable(PsiFile file, int offset, boolean selection) {
+    return selection && isApplicable(file, offset);
+  }
+
+  private static boolean isApplicable(PsiFile file, int offset) {
     WebEditorOptions webEditorOptions = WebEditorOptions.getInstance();
     if (!webEditorOptions.isZenCodingEnabled()) {
       return false;
@@ -344,13 +364,66 @@ public class XmlZenCodingTemplate {
   }
 
   public void expand(String key, @NotNull CustomTemplateCallback callback, @Nullable TemplateInvokationListener listener) {
+    expand(key, callback, null, listener);
+  }
+
+  private static void expand(String key,
+                             @NotNull CustomTemplateCallback callback,
+                             String surroundedText,
+                             @Nullable TemplateInvokationListener listener) {
     List<Token> tokens = parse(key, callback);
     assert tokens != null;
-    XmlZenCodingInterpreter interpreter = new XmlZenCodingInterpreter(tokens, callback, State.WORD, listener);
-    interpreter.invoke(0);
+    XmlZenCodingInterpreter.interpret(tokens, 0, callback, State.WORD, surroundedText, listener);
   }
 
-  public void wrap(String selection, @NotNull CustomTemplateCallback callback, @Nullable TemplateInvokationListener listener) {
+  public void wrap(final String selection,
+                   @NotNull final CustomTemplateCallback callback,
+                   @Nullable final TemplateInvokationListener listener) {
+    InputValidatorEx validator = new InputValidatorEx() {
+      public String getErrorText(String inputString) {
+        if (!checkTemplateKey(inputString, callback)) {
+          return XmlBundle.message("zen.coding.incorrect.abbreviation.error");
+        }
+        return null;
+      }
+
+      public boolean checkInput(String inputString) {
+        return getErrorText(inputString) == null;
+      }
+
+      public boolean canClose(String inputString) {
+        return checkInput(inputString);
+      }
+    };
+    final String abbreviation = Messages
+      .showInputDialog(callback.getProject(), XmlBundle.message("zen.coding.enter.abbreviation.dialog.label"),
+                       XmlBundle.message("zen.coding.title"), Messages.getQuestionIcon(), "", validator);
+    if (abbreviation != null) {
+      doWrap(selection, abbreviation, callback, listener);
+    }
+  }
+
+  private static void doWrap(final String selection,
+                             final String abbreviation,
+                             final CustomTemplateCallback callback,
+                             final TemplateInvokationListener listener) {
+    ApplicationManager.getApplication().runWriteAction(new Runnable() {
+      public void run() {
+        CommandProcessor.getInstance().executeCommand(callback.getProject(), new Runnable() {
+          public void run() {
+            EditorModificationUtil.deleteSelectedText(callback.getEditor());
+            PsiDocumentManager.getInstance(callback.getProject()).commitAllDocuments();
+            callback.fixInitialState();
+            expand(abbreviation, callback, selection, listener);
+          }
+        }, CodeInsightBundle.message("insert.code.template.command"), null);
+      }
+    });
+  }
+
+  @NotNull
+  public String getTitle() {
+    return XmlBundle.message("zen.coding.title");
   }
 
   @Nullable
@@ -361,25 +434,34 @@ public class XmlZenCodingTemplate {
     return document == null ? null : document.getRootTag();
   }
 
-  public static boolean startZenCoding(Editor editor, PsiFile file) {
+  public static boolean startZenCoding(Editor editor, PsiFile file, String abbreviation) {
     int caretAt = editor.getCaretModel().getOffset();
-    XmlZenCodingTemplate template = new XmlZenCodingTemplate();
-    if (template.isApplicable(file, caretAt, false)) {
+    if (isApplicable(file, caretAt)) {
       final CustomTemplateCallback callback = new CustomTemplateCallback(editor, file);
-      String key = template.computeTemplateKey(callback);
-      if (key != null) {
-        int offsetBeforeKey = caretAt - key.length();
-        callback.getEditor().getDocument().deleteString(offsetBeforeKey, caretAt);
-        template.expand(key, callback, new TemplateInvokationListener() {
-          public void finished(boolean inSeparateEvent) {
-            callback.finish();
-          }
-        });
-        return true;
+      TemplateInvokationListener listener = new TemplateInvokationListener() {
+        public void finished(boolean inSeparateEvent) {
+          callback.finish();
+        }
+      };
+      if (abbreviation != null) {
+        String selection = callback.getEditor().getSelectionModel().getSelectedText();
+        assert selection != null;
+        selection = selection.trim();
+        doWrap(selection, abbreviation, callback, listener);
+      }
+      else {
+        XmlZenCodingTemplate template = new XmlZenCodingTemplate();
+        String key = template.computeTemplateKey(callback);
+        if (key != null) {
+          int offsetBeforeKey = caretAt - key.length();
+          callback.getEditor().getDocument().deleteString(offsetBeforeKey, caretAt);
+          template.expand(key, callback, listener);
+          return true;
+        }
+        // if it is simple live template invokation, we should start it using TemplateManager because template may be ambiguous
+        /*TemplateManager manager = TemplateManager.getInstance(file.getProject());
+        return manager.startTemplate(editor, TemplateSettings.getInstance().getDefaultShortcutChar());*/
       }
-      // if it is simple live template invokation, we should start it using TemplateManager because template may be ambiguous
-      /*TemplateManager manager = TemplateManager.getInstance(file.getProject());
-      return manager.startTemplate(editor, TemplateSettings.getInstance().getDefaultShortcutChar());*/
     }
     return false;
   }