zen coding: activate live templates to a buffer, and then create a new one live template
authorEugene Kudelevsky <Eugene.Kudelevsky@jetbrains.com>
Sat, 10 Apr 2010 12:24:28 +0000 (16:24 +0400)
committerEugene Kudelevsky <Eugene.Kudelevsky@jetbrains.com>
Sat, 10 Apr 2010 12:24:28 +0000 (16:24 +0400)
platform/lang-impl/src/com/intellij/codeInsight/template/CustomTemplateCallback.java
platform/lang-impl/src/com/intellij/codeInsight/template/LiveTemplateBuilder.java [new file with mode: 0644]
xml/impl/src/com/intellij/codeInsight/template/zencoding/XmlZenCodingInterpreter.java
xml/impl/src/com/intellij/codeInsight/template/zencoding/XmlZenCodingTemplate.java

index 50693ba4d0bcfc000d6aa193d8e91725ff14d695..cdc489bea740ffacd3e52c49c9f159310d1c702d 100644 (file)
@@ -18,16 +18,13 @@ 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.application.ApplicationManager;
-import com.intellij.openapi.editor.Document;
 import com.intellij.openapi.editor.Editor;
-import com.intellij.openapi.editor.RangeMarker;
-import com.intellij.openapi.editor.ScrollType;
 import com.intellij.openapi.fileTypes.FileType;
 import com.intellij.openapi.project.Project;
-import com.intellij.psi.PsiDocumentManager;
+import com.intellij.psi.PsiElement;
 import com.intellij.psi.PsiFile;
-import com.intellij.psi.codeStyle.CodeStyleManager;
+import com.intellij.psi.PsiFileFactory;
+import com.intellij.util.LocalTimeCounter;
 import com.intellij.util.containers.HashMap;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
@@ -46,12 +43,14 @@ public class CustomTemplateCallback {
   private final PsiFile myFile;
   private int myStartOffset;
   private final Project myProject;
-  private RangeMarker myGlobalMarker;
-  private RangeMarker myEndOffsetMarker;
-  private final Map<Object, RangeMarker> myCheckpoints = new HashMap<Object, RangeMarker>();
+  private LiveTemplateBuilder.Marker myEndOffsetMarker;
+  private final Map<Object, LiveTemplateBuilder.Marker> myCheckpoints = new HashMap<Object, LiveTemplateBuilder.Marker>();
 
   private FileType myFileType;
 
+  private final LiveTemplateBuilder myBuilder = new LiveTemplateBuilder();
+  private int myOffset = 0;
+
   public CustomTemplateCallback(Editor editor, PsiFile file) {
     myEditor = editor;
     myFile = file;
@@ -60,17 +59,19 @@ public class CustomTemplateCallback {
     fixInitialState();
   }
 
+  @Nullable
+  public PsiElement getContext() {
+    int offset = myStartOffset;
+    return myFile.findElementAt(offset > 0 ? offset - 1 : offset);
+  }
+
   public void fixInitialState() {
     myStartOffset = myEditor.getCaretModel().getOffset();
-    myGlobalMarker = myEditor.getDocument().createRangeMarker(myStartOffset, myStartOffset);
-    myGlobalMarker.setGreedyToLeft(true);
-    myGlobalMarker.setGreedyToRight(true);
   }
 
   public void fixEndOffset() {
     if (myEndOffsetMarker == null) {
-      int offset = myEditor.getCaretModel().getOffset();
-      myEndOffsetMarker = myEditor.getDocument().createRangeMarker(offset, offset);
+      myEndOffsetMarker = myBuilder.createMarker(myOffset);
     }
   }
 
@@ -121,7 +122,7 @@ public class CustomTemplateCallback {
    * @param listener
    * @return returns if template invokation is finished
    */
-  public boolean startTemplate(@NotNull Template template,
+  /*public boolean startTemplate(@NotNull Template template,
                                Map<String, String> predefinedVarValues,
                                @Nullable final TemplateInvokationListener listener) {
     final boolean[] templateEnded = new boolean[]{false};
@@ -153,27 +154,33 @@ public class CustomTemplateCallback {
       listener.finished(false);
     }
     return templateFinished[0];
+  }*/
+  public boolean startTemplate(@NotNull TemplateImpl template,
+                               Map<String, String> predefinedVarValues,
+                               @Nullable final TemplateInvokationListener listener) {
+    moveToOffset(myBuilder.insertTemplate(myOffset, template, predefinedVarValues));
+    if (listener != null) {
+      listener.finished(false);
+    }
+    return true;
   }
 
-  private void reformat() {
+  /*private void reformat() {
     CodeStyleManager style = CodeStyleManager.getInstance(myProject);
     style.reformatText(myFile, myGlobalMarker.getStartOffset(), myGlobalMarker.getEndOffset());
-  }
+  }*/
 
   public void fixStartOfTemplate(@NotNull Object key) {
-    int offset = myEditor.getCaretModel().getOffset();
-    RangeMarker marker = myEditor.getDocument().createRangeMarker(offset, offset);
-    marker.setGreedyToLeft(true);
-    marker.setGreedyToRight(true);
+    LiveTemplateBuilder.Marker marker = myBuilder.createMarker(myOffset);
     myCheckpoints.put(key, marker);
   }
 
   public void gotoEndOfTemplate(@NotNull Object key) {
-    myEditor.getCaretModel().moveToOffset(getEndOfTemplate(key));
+    moveToOffset(getEndOfTemplate(key));
   }
 
   public int getEndOfTemplate(@NotNull Object key) {
-    RangeMarker marker = myCheckpoints.get(key);
+    LiveTemplateBuilder.Marker marker = myCheckpoints.get(key);
     if (marker == null) {
       throw new IllegalArgumentException();
     }
@@ -181,7 +188,7 @@ public class CustomTemplateCallback {
   }
 
   public int getStartOfTemplate(@NotNull Object key) {
-    RangeMarker marker = myCheckpoints.get(key);
+    LiveTemplateBuilder.Marker marker = myCheckpoints.get(key);
     if (marker == null) {
       throw new IllegalArgumentException();
     }
@@ -190,17 +197,22 @@ public class CustomTemplateCallback {
 
   public void gotoEndOffset() {
     if (myEndOffsetMarker != null) {
-      myEditor.getCaretModel().moveToOffset(myEndOffsetMarker.getStartOffset());
+      moveToOffset(myEndOffsetMarker.getStartOffset());
     }
   }
 
   public void finish() {
-    myEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
+    /*myEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
     final CodeStyleManager style = CodeStyleManager.getInstance(myProject);
     if (myGlobalMarker != null) {
       style.reformatText(myFile, myGlobalMarker.getStartOffset(), myGlobalMarker.getEndOffset());
-    }
+    }*/
     gotoEndOffset();
+    if (myOffset < myBuilder.getText().length()) {
+      myBuilder.insertVariableSegment(myOffset, TemplateImpl.END);
+    }
+    TemplateImpl template = myBuilder.buildTemplate();
+    myTemplateManager.startTemplate(myEditor, template, false, myBuilder.getPredefinedValues(), null);
   }
 
   private static List<TemplateImpl> getMatchingTemplates(@NotNull String templateKey) {
@@ -222,11 +234,13 @@ public class CustomTemplateCallback {
   }
 
   public int getOffset() {
-    return myEditor.getCaretModel().getOffset();
+    return myOffset;
   }
 
-  public PsiFile getFile() {
-    return myFile;
+  @NotNull
+  public PsiFile parseCurrentText(FileType fileType) {
+    return PsiFileFactory.getInstance(myProject)
+      .createFileFromText("dummy.xml", fileType, myBuilder.getText(), LocalTimeCounter.currentTime(), false);
   }
 
   public Project getProject() {
@@ -234,12 +248,10 @@ public class CustomTemplateCallback {
   }
 
   public void moveToOffset(int offset) {
-    myEditor.getCaretModel().moveToOffset(offset);
+    myOffset = offset;
   }
 
   public void insertString(int offset, String text) {
-    Document document = myEditor.getDocument();
-    document.insertString(offset, text);
-    PsiDocumentManager.getInstance(myProject).commitDocument(document);
+    myBuilder.insertText(offset, text);
   }
 }
diff --git a/platform/lang-impl/src/com/intellij/codeInsight/template/LiveTemplateBuilder.java b/platform/lang-impl/src/com/intellij/codeInsight/template/LiveTemplateBuilder.java
new file mode 100644 (file)
index 0000000..9269ec6
--- /dev/null
@@ -0,0 +1,198 @@
+/*
+ * 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.Variable;
+import com.intellij.util.containers.HashMap;
+import com.intellij.util.containers.HashSet;
+
+import java.util.*;
+
+/**
+ * @author Eugene.Kudelevsky
+ */
+public class LiveTemplateBuilder {
+  private final StringBuilder myText = new StringBuilder();
+  private final List<Variable> myVariables = new ArrayList<Variable>();
+  private final Set<String> myVarNames = new HashSet<String>();
+  private final List<VarOccurence> myVariableOccurences = new ArrayList<VarOccurence>();
+  private final List<Marker> myMarkers = new ArrayList<Marker>();
+  private final Map<String, String> myPredefinedValues = new HashMap<String, String>();
+
+  public CharSequence getText() {
+    return myText;
+  }
+
+  public void insertVariableSegment(int offset, String name) {
+    myVariableOccurences.add(new VarOccurence(name, offset));
+  }
+
+  private static class VarOccurence {
+    String myName;
+    int myOffset;
+
+    private VarOccurence(String name, int offset) {
+      myName = name;
+      myOffset = offset;
+    }
+  }
+
+  public TemplateImpl buildTemplate() {
+    TemplateImpl template = new TemplateImpl("", "");
+    for (Variable variable : myVariables) {
+      template.addVariable(variable.getName(), variable.getExpressionString(), variable.getDefaultValueString(), variable.isAlwaysStopAt());
+    }
+    Collections.sort(myVariableOccurences, new Comparator<VarOccurence>() {
+      public int compare(VarOccurence o1, VarOccurence o2) {
+        if (o1.myOffset < o2.myOffset) {
+          return -1;
+        }
+        if (o1.myOffset > o2.myOffset) {
+          return 1;
+        }
+        return 0;
+      }
+    });
+    int last = 0;
+    for (VarOccurence occurence : myVariableOccurences) {
+      template.addTextSegment(myText.substring(last, occurence.myOffset));
+      template.addVariableSegment(occurence.myName);
+      last = occurence.myOffset;
+    }
+    template.addTextSegment(myText.substring(last));
+    template.setToReformat(true);
+    return template;
+  }
+
+  public Map<String, String> getPredefinedValues() {
+    return myPredefinedValues;
+  }
+
+  public void insertText(int offset, String text) {
+    int delta = text.length();
+    for (VarOccurence occurence : myVariableOccurences) {
+      if (occurence.myOffset >= offset) {
+        occurence.myOffset += delta;
+      }
+    }
+    myText.insert(offset, text);
+    updateMarkers(offset, text);
+  }
+
+  private void updateMarkers(int offset, String text) {
+    for (Marker marker : myMarkers) {
+      if (offset < marker.getStartOffset()) {
+        marker.myStartOffset += text.length();
+      }
+      else if (offset <= marker.getEndOffset()) {
+        marker.myEndOffset += text.length();
+      }
+    }
+  }
+
+  private String generateUniqueVarName(Set<String> existingNames) {
+    String prefix = "VAR";
+    int i = 0;
+    while (myVarNames.contains(prefix + i) || existingNames.contains(prefix + i)) {
+      i++;
+    }
+    return prefix + i;
+  }
+
+  /*private static String preslashQuotes(String s) {
+    StringBuilder builder = new StringBuilder();
+    for (int i = 0; i < s.length(); i++) {
+      char c = s.charAt(i);
+      if (c == '"') {
+        builder.append('\\');
+      }
+      builder.append(c);
+    }
+    return builder.toString();
+  }*/
+
+  public int insertTemplate(int offset, TemplateImpl template, Map<String, String> predefinedVarValues) {
+    String text = template.getTemplateText();
+    insertText(offset, text);
+    Map<String, String> newVarNames = new HashMap<String, String>();
+    Set<String> oldVarNames = new HashSet<String>();
+    for (int i = 0; i < template.getVariableCount(); i++) {
+      String varName = template.getVariableNameAt(i);
+      oldVarNames.add(varName);
+    }
+    for (int i = 0; i < template.getVariableCount(); i++) {
+      String varName = template.getVariableNameAt(i);
+      if (!TemplateImpl.INTERNAL_VARS_SET.contains(varName)) {
+        String newVarName;
+        if (myVarNames.contains(varName)) {
+          oldVarNames.remove(varName);
+          newVarName = generateUniqueVarName(oldVarNames);
+          newVarNames.put(varName, newVarName);
+        }
+        else {
+          newVarName = varName;
+        }
+        if (predefinedVarValues != null && predefinedVarValues.containsKey(varName)) {
+          myPredefinedValues.put(newVarName, predefinedVarValues.get(varName));
+        }
+        Variable var =
+          new Variable(newVarName, template.getExpressionStringAt(i), template.getDefaultValueStringAt(i), template.isAlwaysStopAt(i));
+        myVariables.add(var);
+        myVarNames.add(newVarName);
+      }
+    }
+    int end = -1;
+    for (int i = 0; i < template.getSegmentsCount(); i++) {
+      String segmentName = template.getSegmentName(i);
+      int localOffset = template.getSegmentOffset(i);
+      if (!TemplateImpl.INTERNAL_VARS_SET.contains(segmentName)) {
+        if (newVarNames.containsKey(segmentName)) {
+          segmentName = newVarNames.get(segmentName);
+        }
+        myVariableOccurences.add(new VarOccurence(segmentName, offset + localOffset));
+      }
+      else if (TemplateImpl.END.equals(segmentName)) {
+        end = offset + localOffset;
+      }
+    }
+    return end >= 0 ? end : offset + text.length();
+  }
+
+  Marker createMarker(int offset) {
+    Marker marker = new Marker(offset, offset);
+    myMarkers.add(marker);
+    return marker;
+  }
+
+  static class Marker {
+    int myStartOffset;
+    int myEndOffset;
+
+    private Marker(int startOffset, int endOffset) {
+      myStartOffset = startOffset;
+      myEndOffset = endOffset;
+    }
+
+    public int getStartOffset() {
+      return myStartOffset;
+    }
+
+    public int getEndOffset() {
+      return myEndOffset;
+    }
+  }
+}
index ba17fc7599475697fb0b9d258c8ea4c17d87d2e1..853a9e0365ced1b1f00f0e585cce7e6cee007cba 100644 (file)
@@ -23,6 +23,7 @@ import com.intellij.openapi.application.ApplicationManager;
 import com.intellij.openapi.diagnostic.Logger;
 import com.intellij.openapi.editor.Document;
 import com.intellij.openapi.fileEditor.FileDocumentManager;
+import com.intellij.openapi.fileTypes.StdFileTypes;
 import com.intellij.openapi.project.Project;
 import com.intellij.openapi.util.Pair;
 import com.intellij.openapi.util.TextRange;
@@ -85,7 +86,7 @@ class XmlZenCodingInterpreter {
     int endOfTemplate = myCallback.getEndOfTemplate(templateBoundsKey);
     int offset = myCallback.getOffset();
 
-    PsiFile file = myCallback.getFile();
+    PsiFile file = myCallback.parseCurrentText(StdFileTypes.XML);
 
     PsiElement element = file.findElementAt(offset);
     if (element instanceof XmlToken && ((XmlToken)element).getTokenType() == XmlTokenType.XML_END_TAG_START) {
index e00ae99dc61623c25f27ddede46f68ad446488dd..53dcca8c5549af7db348b0316b2db0809d359736 100644 (file)
@@ -298,8 +298,7 @@ public class XmlZenCodingTemplate implements CustomLiveTemplate {
 
   public String computeTemplateKey(@NotNull CustomTemplateCallback callback) {
     Editor editor = callback.getEditor();
-    int offset = callback.getOffset();
-    PsiElement element = callback.getFile().findElementAt(offset > 0 ? offset - 1 : offset);
+    PsiElement element = callback.getContext();
     int line = editor.getCaretModel().getLogicalPosition().line;
     int lineStart = editor.getDocument().getLineStartOffset(line);
     int parentStart;