custom live templates support
authorEugene Kudelevsky <Eugene.Kudelevsky@jetbrains.com>
Tue, 9 Feb 2010 17:39:23 +0000 (20:39 +0300)
committerEugene Kudelevsky <Eugene.Kudelevsky@jetbrains.com>
Tue, 9 Feb 2010 17:39:23 +0000 (20:39 +0300)
platform/lang-impl/src/com/intellij/codeInsight/template/CustomLiveTemplate.java [new file with mode: 0644]
platform/lang-impl/src/com/intellij/codeInsight/template/CustomTemplateCallback.java [new file with mode: 0644]
platform/lang-impl/src/com/intellij/codeInsight/template/Iteration.java [new file with mode: 0644]
platform/lang-impl/src/com/intellij/codeInsight/template/TemplateEditingAdapter.java
platform/lang-impl/src/com/intellij/codeInsight/template/TemplateEditingListener.java
platform/lang-impl/src/com/intellij/codeInsight/template/TemplateInvokationListener.java [new file with mode: 0644]
platform/lang-impl/src/com/intellij/codeInsight/template/TemplateManager.java
platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateManagerImpl.java
platform/lang-impl/src/com/intellij/codeInsight/template/impl/TemplateState.java
platform/platform-resources/src/META-INF/LangExtensionPoints.xml

diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/CustomLiveTemplate.java b/platform/lang-impl/src/com/intellij/codeInsight/template/CustomLiveTemplate.java
new file mode 100644 (file)
index 0000000..74912fd
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * 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;
+
+import com.intellij.openapi.extensions.ExtensionPointName;
+import com.intellij.psi.PsiFile;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public interface CustomLiveTemplate {
+  ExtensionPointName<CustomLiveTemplate> EP_NAME = ExtensionPointName.create("com.intellij.customLiveTemplate");
+
+  boolean isApplicable(@NotNull String key, @NotNull CustomTemplateCallback callback);
+
+  void execute(@NotNull String key, @NotNull CustomTemplateCallback callback, @Nullable TemplateInvokationListener listener);
+}
diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/CustomTemplateCallback.java b/platform/lang-impl/src/com/intellij/codeInsight/template/CustomTemplateCallback.java
new file mode 100644 (file)
index 0000000..a21c5a1
--- /dev/null
@@ -0,0 +1,123 @@
+/*
+ * 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;
+
+import com.intellij.codeInsight.template.impl.TemplateImpl;
+import com.intellij.codeInsight.template.impl.TemplateManagerImpl;
+import com.intellij.codeInsight.template.impl.TemplateSettings;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.project.Project;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.codeStyle.CodeStyleManager;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.List;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public class CustomTemplateCallback {
+  private final TemplateManager myTemplateManager;
+  private final Editor myEditor;
+  private final PsiFile myFile;
+  private int myStartOffset;
+  private int myStartLength;
+  private Project myProject;
+
+  public CustomTemplateCallback(Editor editor, PsiFile file) {
+    myEditor = editor;
+    myFile = file;
+    myProject = file.getProject();
+    myTemplateManager = TemplateManagerImpl.getInstance(myProject);
+  }
+
+  public void fixInitialEditorState() {
+    myStartOffset = myEditor.getCaretModel().getOffset();
+    myStartLength = myEditor.getDocument().getCharsSequence().length();
+  }
+
+  public boolean isLiveTemplateApplicable(@NotNull String key) {
+    List<TemplateImpl> templates = getMatchingTemplates(key);
+    templates = TemplateManagerImpl.filterApplicableCandidates(myFile, myStartOffset, templates);
+    return templates.size() > 0;
+  }
+
+  /**
+   * @param key
+   * @param listener
+   * @return returns if template invokation is finished
+   */
+  public boolean startTemplate(@NotNull String key, @Nullable TemplateInvokationListener listener) {
+    int caretOffset = myEditor.getCaretModel().getOffset();
+    List<TemplateImpl> templates = getMatchingTemplates(key);
+    templates = TemplateManagerImpl.filterApplicableCandidates(myFile, caretOffset, templates);
+    if (templates.size() == 1) {
+      TemplateImpl template = templates.get(0);
+      return startTemplate(template, listener);
+    }
+    else if (listener != null) {
+      listener.finished(false, false);
+    }
+    return true;
+  }
+
+  /**
+   * @param template
+   * @param listener
+   * @return returns if template invokation is finished
+   */
+  public boolean startTemplate(@NotNull Template template, @Nullable final TemplateInvokationListener listener) {
+    final boolean[] templateEnded = new boolean[]{false};
+    final boolean[] templateFinished = new boolean[]{false};
+    myTemplateManager.startTemplate(myEditor, template, new TemplateEditingAdapter() {
+
+      @Override
+      public void templateExpanded(Template template) {
+        int lengthAfter = myEditor.getDocument().getCharsSequence().length();
+        CodeStyleManager style = CodeStyleManager.getInstance(myProject);
+        style.reformatText(myFile, myStartOffset, myStartOffset + lengthAfter - myStartLength);
+      }
+
+      @Override
+      public void templateFinished(Template template) {
+        templateFinished[0] = true;
+        if (templateEnded[0] && listener != null) {
+          listener.finished(true, true);
+        }
+      }
+    }, false);
+    templateEnded[0] = true;
+    if (templateFinished[0] && listener != null) {
+      listener.finished(false, true);
+    }
+    return templateFinished[0];
+  }
+
+  private static List<TemplateImpl> getMatchingTemplates(@NotNull String templateKey) {
+    TemplateSettings settings = TemplateSettings.getInstance();
+    return settings.collectMatchingCandidates(templateKey, settings.getDefaultShortcutChar(), false);
+  }
+
+  @NotNull
+  public Editor getEditor() {
+    return myEditor;
+  }
+
+  public PsiFile getFile() {
+    return myFile;
+  }
+}
diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/Iteration.java b/platform/lang-impl/src/com/intellij/codeInsight/template/Iteration.java
new file mode 100644 (file)
index 0000000..3c7b7ee
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public abstract class Iteration {
+  protected final int myMaxIndex;
+  protected int myIndex;
+  private final Iteration myParentIteration;
+
+  public Iteration(int index, int maxIndex, Iteration parentIteration) {
+    myIndex = index;
+    myMaxIndex = maxIndex;
+    myParentIteration = parentIteration;
+  }
+
+  protected void next() {
+    myIndex++;
+    if (myIndex < myMaxIndex) {
+      iter();
+    }
+    else if (myIndex == myMaxIndex && myParentIteration != null) {
+      myParentIteration.next();
+    }
+  }
+
+  public boolean isFinished() {
+    return myIndex >= myMaxIndex;
+  }
+
+  // returns if next() is going to be invoked
+  protected abstract void iter();
+}
index c29d5ed0ecefa130307684722127d0c8b249d3bd..cf2f82d599e2b6a6460fbef976617d59ed311444 100644 (file)
@@ -34,4 +34,7 @@ public abstract class TemplateEditingAdapter implements TemplateEditingListener
 
   public void currentVariableChanged(TemplateState templateState, Template template, int oldIndex, int newIndex) {
   }
+
+  public void templateExpanded(Template template) {
+  }
 }
index 990f3767e1d4068f822490b7be854142d1f46f77..1fc89f4fd39fe3b1af914c3d9e273d1c5c16bbb5 100644 (file)
@@ -26,4 +26,5 @@ public interface TemplateEditingListener {
   void templateFinished(Template template);
   void templateCancelled(Template template);
   void currentVariableChanged(TemplateState templateState, Template template, int oldIndex, int newIndex);
+  void templateExpanded(Template template); 
 }
diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/TemplateInvokationListener.java b/platform/lang-impl/src/com/intellij/codeInsight/template/TemplateInvokationListener.java
new file mode 100644 (file)
index 0000000..0ad5a09
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public interface TemplateInvokationListener {
+  void finished(boolean inSeparateEvent, boolean success);
+}
index 9a67f8ab31e95245bee06700e640b6b7c4bc227b..f0369f22b35e579bae98c3e186f44c13e31d8bf2 100644 (file)
@@ -1,19 +1,18 @@
-
 /*
- * 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;
 
@@ -36,8 +35,15 @@ public abstract class TemplateManager {
 
   public abstract void startTemplate(@NotNull Editor editor, @NotNull Template template, TemplateEditingListener listener);
 
-  public abstract void startTemplate(@NotNull Editor editor, @NotNull Template template, TemplateEditingListener listener,
-                            final PairProcessor<String, String> callback);
+  public abstract void startTemplate(@NotNull final Editor editor,
+                                     @NotNull final Template template,
+                                     TemplateEditingListener listener,
+                                     boolean inSeparateCommand);
+
+  public abstract void startTemplate(@NotNull Editor editor,
+                                     @NotNull Template template,
+                                     TemplateEditingListener listener,
+                                     final PairProcessor<String, String> callback);
 
   public abstract boolean startTemplate(@NotNull Editor editor, char shortcutChar);
 
index 566327bb87ca4d4aa670ce8ac9374b002b5b97ae..302e249ba15d34858bb1057923826a2c76fc112b 100644 (file)
@@ -132,21 +132,22 @@ public class TemplateManagerImpl extends TemplateManager implements ProjectCompo
   }
 
   public void startTemplate(@NotNull Editor editor, String selectionString, @NotNull Template template) {
-    startTemplate(editor, selectionString, template, null, null);
+    startTemplate(editor, selectionString, template, null, null, true);
   }
 
   public void startTemplate(@NotNull Editor editor,
                             @NotNull Template template,
                             TemplateEditingListener listener,
                             final PairProcessor<String, String> processor) {
-    startTemplate(editor, null, template, listener, processor);
+    startTemplate(editor, null, template, listener, processor, true);
   }
 
   private void startTemplate(final Editor editor,
                              final String selectionString,
                              final Template template,
                              TemplateEditingListener listener,
-                             final PairProcessor<String, String> processor) {
+                             final PairProcessor<String, String> processor,
+                             boolean inSeparateCommand) {
     final TemplateState templateState = initTemplateState(editor);
 
     templateState.getProperties().put(ExpressionContext.SELECTION, selectionString);
@@ -154,7 +155,7 @@ public class TemplateManagerImpl extends TemplateManager implements ProjectCompo
     if (listener != null) {
       templateState.addTemplateStateListener(listener);
     }
-    CommandProcessor.getInstance().executeCommand(myProject, new Runnable() {
+    Runnable r = new Runnable() {
       public void run() {
         if (selectionString != null) {
           ApplicationManager.getApplication().runWriteAction(new Runnable() {
@@ -168,7 +169,13 @@ public class TemplateManagerImpl extends TemplateManager implements ProjectCompo
         }
         templateState.start((TemplateImpl)template, processor, null);
       }
-    }, CodeInsightBundle.message("insert.code.template.command"), null);
+    };
+    if (inSeparateCommand) {
+      CommandProcessor.getInstance().executeCommand(myProject, r, CodeInsightBundle.message("insert.code.template.command"), null);
+    }
+    else {
+      r.run();
+    }
 
     if (shouldSkipInTests()) {
       if (!templateState.isFinished()) templateState.gotoEnd();
@@ -180,7 +187,14 @@ public class TemplateManagerImpl extends TemplateManager implements ProjectCompo
   }
 
   public void startTemplate(@NotNull final Editor editor, @NotNull final Template template, TemplateEditingListener listener) {
-    startTemplate(editor, null, template, listener, null);
+    startTemplate(editor, null, template, listener, null, false);
+  }
+
+  public void startTemplate(@NotNull final Editor editor,
+                            @NotNull final Template template,
+                            TemplateEditingListener listener,
+                            boolean inSeparateCommand) {
+    startTemplate(editor, null, template, listener, null, inSeparateCommand);
   }
 
   private static int passArgumentBack(CharSequence text, int caretOffset) {
@@ -200,12 +214,47 @@ public class TemplateManagerImpl extends TemplateManager implements ProjectCompo
     }
   }
 
+  private static String getCurrentLineBeforeCaret(@NotNull Editor editor) {
+    CaretModel caretModel = editor.getCaretModel();
+    int line = caretModel.getLogicalPosition().line;
+    int lineStart = editor.getDocument().getLineStartOffset(line);
+    int offset = caretModel.getOffset();
+    String s = editor.getDocument().getCharsSequence().subSequence(lineStart, offset).toString();
+    int index = 0;
+    while (index < s.length() && Character.isWhitespace(s.charAt(index))) {
+      index++;
+    }
+    return index < s.length() ? s.substring(index) : s;
+  }
+
   public boolean startTemplate(final Editor editor, char shortcutChar, final PairProcessor<String, String> processor) {
-    final Document document = editor.getDocument();
     PsiFile file = PsiUtilBase.getPsiFileInEditor(editor, myProject);
     if (file == null) return false;
-
     TemplateSettings templateSettings = TemplateSettings.getInstance();
+    if (shortcutChar == templateSettings.getDefaultShortcutChar()) {
+      for (final CustomLiveTemplate customLiveTemplate : CustomLiveTemplate.EP_NAME.getExtensions()) {
+        final String currentLineBeforeCaret = getCurrentLineBeforeCaret(editor);
+        final CustomTemplateCallback callback = new CustomTemplateCallback(editor, file);
+        if (customLiveTemplate.isApplicable(currentLineBeforeCaret, callback)) {
+          int offset = editor.getCaretModel().getOffset();
+          final int startOffset = offset - currentLineBeforeCaret.length();
+          editor.getDocument().deleteString(startOffset, offset);
+          callback.fixInitialEditorState();
+          customLiveTemplate.execute(currentLineBeforeCaret, callback, null);
+          return true;
+        }
+      }
+    }
+    return startNonCustomTemplate(templateSettings, file, editor, shortcutChar, processor);
+  }
+
+  private boolean startNonCustomTemplate(TemplateSettings templateSettings,
+                                         PsiFile file,
+                                         Editor editor,
+                                         char shortcutChar,
+                                         PairProcessor<String, String> processor) {
+    final Document document = editor.getDocument();
+
     CharSequence text = document.getCharsSequence();
 
     final int caretOffset = editor.getCaretModel().getOffset();
@@ -258,15 +307,14 @@ public class TemplateManagerImpl extends TemplateManager implements ProjectCompo
     else {
       ListTemplatesHandler.showTemplatesLookup(myProject, editor, candidate2Argument);
     }
-
     return true;
   }
 
-  private static List<TemplateImpl> findMatchingTemplates(CharSequence text,
-                                                          int caretOffset,
-                                                          char shortcutChar,
-                                                          TemplateSettings settings,
-                                                          boolean hasArgument) {
+  public 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--) {
@@ -318,7 +366,7 @@ public class TemplateManagerImpl extends TemplateManager implements ProjectCompo
     }, CodeInsightBundle.message("insert.code.template.command"), null);
   }
 
-  private static List<TemplateImpl> filterApplicableCandidates(PsiFile file, int caretOffset, List<TemplateImpl> candidates) {
+  public static List<TemplateImpl> filterApplicableCandidates(PsiFile file, int caretOffset, List<TemplateImpl> candidates) {
     List<TemplateImpl> result = new ArrayList<TemplateImpl>();
     for (TemplateImpl candidate : candidates) {
       if (isApplicable(file, caretOffset - candidate.getKey().length(), candidate)) {
index 98b09560cae8c9cf0aedae4b912f5e45448b68a4..7d870c0158ca831208da2662652436ed50e49b48 100644 (file)
@@ -318,6 +318,8 @@ public class TemplateState implements Disposable {
         calcResults(false);  //Fixed SCR #[vk500] : all variables should be recalced twice on start.
         doReformat();
 
+        fireTemplateExpanded();
+
         int nextVariableNumber = getNextVariableNumber(-1);
         if (nextVariableNumber == -1) {
           finishTemplateEditing();
@@ -1006,6 +1008,13 @@ public class TemplateState implements Disposable {
     }
   }
 
+  private void fireTemplateExpanded() {
+    TemplateEditingListener[] listeners = myListeners.toArray(new TemplateEditingListener[myListeners.size()]);
+    for (TemplateEditingListener listener : listeners) {
+      listener.templateExpanded(myTemplate);
+    }
+  }
+
   private void currentVariableChanged(int oldIndex) {
     TemplateEditingListener[] listeners = myListeners.toArray(new TemplateEditingListener[myListeners.size()]);
     for (TemplateEditingListener listener : listeners) {
index d5172d75f071f765562a846b32aed5d71c279578..8c3965844b6c520bb33023140d35b9bc8733b16d 100644 (file)
   <extensionPoint name="lang.unwrapDescriptor" beanClass="com.intellij.lang.LanguageExtensionPoint"/>
   <extensionPoint name="lang.parserDefinition" beanClass="com.intellij.lang.LanguageExtensionPoint"/>
   <extensionPoint name="lang.treePatcher" beanClass="com.intellij.lang.LanguageExtensionPoint"/>
-  <extensionPoint name="lang.tokenSeparatorGenerator" beanClass="com.intellij.lang.LanguageExtensionPoint"/>
 
   <extensionPoint name="lang.fileViewProviderFactory" beanClass="com.intellij.lang.LanguageExtensionPoint"/>
   <extensionPoint name="fileType.fileViewProviderFactory" beanClass="com.intellij.openapi.fileTypes.FileTypeExtensionPoint"/>
   <extensionPoint name="liveTemplateMacro" interface="com.intellij.codeInsight.template.Macro"/>
   <extensionPoint name="liveTemplateOptionalProcessor" interface="com.intellij.codeInsight.template.impl.TemplateOptionalProcessor"/>
   <extensionPoint name="liveTemplatePreprocessor" interface="com.intellij.codeInsight.template.impl.TemplatePreprocessor"/>
+  <extensionPoint name="customLiveTemplate" interface="com.intellij.codeInsight.template.CustomLiveTemplate"/>
 
   <extensionPoint name="fileTemplateGroup"
                   interface="com.intellij.ide.fileTemplates.FileTemplateGroupDescriptorFactory"/>