import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ScrollType;
+import com.intellij.openapi.util.Pair;
import com.intellij.psi.xml.XmlFile;
import com.intellij.util.containers.HashMap;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
/**
* @author Eugene.Kudelevsky
private static final String ATTRS = "ATTRS";
- private static final String POSSIBLE_OPERATIONS = ">+*";
- private static final String HTML_SELECTORS = ".#";
+ private static final String OPERATIONS = ">+*";
+ private static final String SELECTORS = ".#[";
private static final char MARKER = '$';
+ private static final String ID = "id";
+ private static final String CLASS = "class";
private static enum MyState {
OPERATION, WORD, AFTER_NUMBER, NUMBER
private static class MyTemplateToken extends MyToken {
final String myKey;
+ final Map<String, String> myAttribute2Value;
- MyTemplateToken(String key) {
+ MyTemplateToken(String key, Map<String, String> attribute2value) {
myKey = key;
+ myAttribute2Value = attribute2value;
}
}
}
private static boolean isTemplateKeyPart(char c) {
- return !Character.isWhitespace(c) && POSSIBLE_OPERATIONS.indexOf(c) < 0;
+ return !Character.isWhitespace(c) && OPERATIONS.indexOf(c) < 0;
}
private static int parseNonNegativeInt(@NotNull String s) {
private static String getPrefix(@NotNull String templateKey) {
for (int i = 0, n = templateKey.length(); i < n; i++) {
char c = templateKey.charAt(i);
- if (HTML_SELECTORS.indexOf(c) >= 0) {
+ if (SELECTORS.indexOf(c) >= 0) {
return templateKey.substring(0, i);
}
}
return templateKey;
}
+ @Nullable
+ private static Pair<String, String> parseAttrNameAndValue(@NotNull String text) {
+ int eqIndex = text.indexOf('=');
+ if (eqIndex > 0) {
+ return new Pair<String, String>(text.substring(0, eqIndex), text.substring(eqIndex + 1));
+ }
+ return null;
+ }
+
+ @Nullable
+ private static MyTemplateToken parseSelectors(@NotNull String text) {
+ String templateKey = null;
+ Map<String, String> attribute2value = new HashMap<String, String>();
+ final List<String> classes = new ArrayList<String>();
+ StringBuilder builder = new StringBuilder();
+ char lastDelim = 0;
+ text += MARKER;
+ for (int i = 0, n = text.length(); i < n; i++) {
+ char c = text.charAt(i);
+ if (c == '#' || c == '.' || c == '[' || c == ']' || i == n - 1) {
+ if (c != ']') {
+ switch (lastDelim) {
+ case 0:
+ templateKey = builder.toString();
+ break;
+ case '#':
+ attribute2value.put(ID, builder.toString());
+ break;
+ case '.':
+ if (builder.length() > 0) {
+ classes.add(builder.toString());
+ }
+ else {
+ return null;
+ }
+ break;
+ case ']':
+ if (builder.length() > 0) {
+ return null;
+ }
+ break;
+ default:
+ return null;
+ }
+ }
+ else if (lastDelim != '[') {
+ return null;
+ }
+ else {
+ Pair<String, String> pair = parseAttrNameAndValue(builder.toString());
+ if (pair == null || attribute2value.containsKey(pair.first)) {
+ return null;
+ }
+ attribute2value.put(pair.first, pair.second);
+ }
+ lastDelim = c;
+ builder = new StringBuilder();
+ }
+ else {
+ builder.append(c);
+ }
+ }
+ if (classes.size() > 0) {
+ if (attribute2value.containsKey(CLASS)) {
+ return null;
+ }
+ StringBuilder classesAttrValue = new StringBuilder();
+ for (int i = 0; i < classes.size(); i++) {
+ classesAttrValue.append(classes.get(i));
+ if (i < classes.size() - 1) {
+ classesAttrValue.append(' ');
+ }
+ }
+ attribute2value.put(CLASS, classesAttrValue.toString());
+ }
+ return new MyTemplateToken(templateKey, attribute2value);
+ }
+
@Nullable
private static List<MyToken> parse(@NotNull String text, @NotNull CustomTemplateCallback callback) {
text += MARKER;
List<MyToken> result = new ArrayList<MyToken>();
for (int i = 0, n = text.length(); i < n; i++) {
char c = text.charAt(i);
- if (i == n - 1 || POSSIBLE_OPERATIONS.indexOf(c) >= 0) {
+ if (i == n - 1 || OPERATIONS.indexOf(c) >= 0) {
String key = templateKeyBuilder.toString();
templateKeyBuilder = new StringBuilder();
int num = parseNonNegativeInt(key);
if (!callback.isLiveTemplateApplicable(prefix) && prefix.indexOf('<') >= 0) {
return null;
}
- result.add(new MyTemplateToken(key));
+ MyTemplateToken token = parseSelectors(key);
+ if (token == null) {
+ return null;
+ }
+ result.add(token);
}
result.add(i < n - 1 ? new MyOperationToken(c) : new MyMarkerToken());
}
}
@NotNull
- private static String buildAttributesString(@Nullable String id, @NotNull List<String> classes) {
+ private static String buildAttributesString(Map<String, String> attribute2value) {
StringBuilder result = new StringBuilder();
- if (id != null) {
- result.append("id=\"").append(id).append('"');
- if (classes.size() > 0) {
+ Set<Map.Entry<String, String>> entries = attribute2value.entrySet();
+ for (Iterator<Map.Entry<String, String>> it = entries.iterator(); it.hasNext();) {
+ Map.Entry<String, String> entry = it.next();
+ String name = entry.getKey();
+ String value = entry.getValue();
+ result.append(name).append("=\"").append(value).append('"');
+ if (it.hasNext()) {
result.append(' ');
}
}
- if (classes.size() > 0) {
- result.append("class=\"");
- for (int i = 0; i < classes.size(); i++) {
- result.append(classes.get(i));
- if (i < classes.size() - 1) {
- result.append(' ');
- }
- }
- result.append('"');
- }
return result.toString();
}
- private static boolean prepareAndInvokeTemplate(String key,
- final CustomTemplateCallback callback,
- final TemplateInvokationListener listener) {
- String templateKey = null;
- String id = null;
- final List<String> classes = new ArrayList<String>();
- StringBuilder builder = new StringBuilder();
- char lastDelim = 0;
- key += MARKER;
- for (int i = 0, n = key.length(); i < n; i++) {
- char c = key.charAt(i);
- if (c == '#' || c == '.' || i == n - 1) {
- switch (lastDelim) {
- case 0:
- templateKey = builder.toString();
- break;
- case '#':
- id = builder.toString();
- break;
- case '.':
- if (builder.length() > 0) {
- classes.add(builder.toString());
- }
- break;
+ private static boolean invokeTemplate(MyTemplateToken token,
+ final CustomTemplateCallback callback,
+ final TemplateInvokationListener listener) {
+ String attributes = buildAttributesString(token.myAttribute2Value);
+ attributes = attributes.length() > 0 ? ' ' + attributes : null;
+ Map<String, String> predefinedValues = null;
+ if (attributes != null) {
+ predefinedValues = new HashMap<String, String>();
+ predefinedValues.put(ATTRS, attributes);
+ }
+ if (callback.isLiveTemplateApplicable(token.myKey)) {
+ if (attributes != null && !callback.templateContainsVars(token.myKey, ATTRS)) {
+ TemplateImpl newTemplate = generateTemplateWithAttributes(token.myKey, attributes, callback);
+ if (newTemplate != null) {
+ return callback.startTemplate(newTemplate, predefinedValues, listener);
}
- lastDelim = c;
- builder = new StringBuilder();
}
- else {
- builder.append(c);
+ return callback.startTemplate(token.myKey, predefinedValues, listener);
+ }
+ else {
+ TemplateImpl template = new TemplateImpl("", "");
+ template.addTextSegment('<' + token.myKey);
+ if (attributes != null) {
+ template.addVariable(ATTRS, "", "", false);
+ template.addVariableSegment(ATTRS);
}
+ template.addTextSegment(">");
+ template.addVariableSegment(TemplateImpl.END);
+ template.addTextSegment("</" + token.myKey + ">");
+ template.setToReformat(true);
+ return callback.startTemplate(template, predefinedValues, listener);
}
- String attributes = buildAttributesString(id, classes);
- return startTemplate(templateKey, callback, listener, attributes.length() > 0 ? ' ' + attributes : null);
}
private static int findPlaceToInsertAttrs(@NotNull TemplateImpl template) {
return null;
}
- private static boolean startTemplate(String key,
- CustomTemplateCallback callback,
- TemplateInvokationListener listener,
- @Nullable String attributes) {
- Map<String, String> predefinedValues = null;
- if (attributes != null) {
- predefinedValues = new HashMap<String, String>();
- predefinedValues.put(ATTRS, attributes);
- }
- if (callback.isLiveTemplateApplicable(key)) {
- if (attributes != null && !callback.templateContainsVars(key, ATTRS)) {
- TemplateImpl newTemplate = generateTemplateWithAttributes(key, attributes, callback);
- if (newTemplate != null) {
- return callback.startTemplate(newTemplate, predefinedValues, listener);
- }
- }
- return callback.startTemplate(key, predefinedValues, listener);
- }
- else {
- TemplateImpl template = new TemplateImpl("", "");
- template.addTextSegment('<' + key);
- if (attributes != null) {
- template.addVariable(ATTRS, "", "", false);
- template.addVariableSegment(ATTRS);
- }
- template.addTextSegment(">");
- template.addVariableSegment(TemplateImpl.END);
- template.addTextSegment("</" + key + ">");
- template.setToReformat(true);
- return callback.startTemplate(template, predefinedValues, listener);
- }
- }
-
private static boolean hasClosingTag(CharSequence text, CharSequence tagName, int offset, int rightBound) {
if (offset + 1 < text.length() && text.charAt(offset) == '<' && text.charAt(offset + 1) == '/') {
CharSequence closingTagName = parseTagName(text, offset + 2, rightBound);
public boolean invoke(int startIndex) {
final int n = myTokens.size();
- String templateKey = null;
+ MyTemplateToken templateToken = null;
int number = -1;
for (int i = startIndex; i < n; i++) {
final int finalI = i;
MyToken token = myTokens.get(i);
switch (myState) {
case OPERATION:
- if (templateKey != null) {
+ if (templateToken != null) {
if (token instanceof MyMarkerToken || token instanceof MyOperationToken) {
final char sign = token instanceof MyOperationToken ? ((MyOperationToken)token).mySign : MARKER;
if (sign == MARKER || sign == '+') {
}
}
};
- if (!prepareAndInvokeTemplate(templateKey, myCallback, listener)) {
+ if (!invokeTemplate(templateToken, myCallback, listener)) {
return false;
}
- templateKey = null;
+ templateToken = null;
}
else if (sign == '>') {
- if (!startTemplateAndGotoChild(templateKey, finalI)) {
+ if (!startTemplateAndGotoChild(templateToken, finalI)) {
return false;
}
- templateKey = null;
+ templateToken = null;
}
else if (sign == '*') {
myState = MyState.NUMBER;
break;
case WORD:
if (token instanceof MyTemplateToken) {
- templateKey = ((MyTemplateToken)token).myKey;
+ templateToken = ((MyTemplateToken)token);
myState = MyState.OPERATION;
}
else {
if (token instanceof MyMarkerToken || token instanceof MyOperationToken) {
char sign = token instanceof MyOperationToken ? ((MyOperationToken)token).mySign : MARKER;
if (sign == MARKER || sign == '+') {
- if (!invokeTemplateSeveralTimes(templateKey, number, finalI)) {
+ if (!invokeTemplateSeveralTimes(templateToken, number, finalI)) {
return false;
}
- templateKey = null;
+ templateToken = null;
}
else if (number > 1) {
- return invokeTemplateAndProcessTail(templateKey, i + 1, number);
+ return invokeTemplateAndProcessTail(templateToken, i + 1, number);
}
else {
assert number == 1;
- if (!startTemplateAndGotoChild(templateKey, finalI)) {
+ if (!startTemplateAndGotoChild(templateToken, finalI)) {
return false;
}
- templateKey = null;
+ templateToken = null;
}
myState = MyState.WORD;
}
return true;
}
- private boolean startTemplateAndGotoChild(String templateKey, final int index) {
+ private boolean startTemplateAndGotoChild(MyTemplateToken templateToken, final int index) {
final Object key = new Object();
myCallback.fixStartOfTemplate(key);
TemplateInvokationListener listener = new TemplateInvokationListener() {
}
}
};
- if (!prepareAndInvokeTemplate(templateKey, myCallback, listener)) {
+ if (!invokeTemplate(templateToken, myCallback, listener)) {
return false;
}
return true;
}
- private boolean invokeTemplateSeveralTimes(final String templateKey, final int count, final int index) {
+ private boolean invokeTemplateSeveralTimes(final MyTemplateToken templateToken, final int count, final int index) {
final Object key = new Object();
myCallback.fixStartOfTemplate(key);
for (int i = 0; i < count; i++) {
if (inSeparateEvent) {
int newCount = count - finalI - 1;
if (newCount > 0) {
- invokeTemplateSeveralTimes(templateKey, newCount, index);
+ invokeTemplateSeveralTimes(templateToken, newCount, index);
}
else {
invoke(index + 1);
}
}
};
- if (!prepareAndInvokeTemplate(templateKey, myCallback, listener)) {
+ if (!invokeTemplate(templateToken, myCallback, listener)) {
return false;
}
}
return true;
}
- private boolean invokeTemplateAndProcessTail(final String templateKey, final int tailStart, final int count) {
+ private boolean invokeTemplateAndProcessTail(final MyTemplateToken templateToken, final int tailStart, final int count) {
final Object key = new Object();
myCallback.fixStartOfTemplate(key);
for (int i = 0; i < count; i++) {
fixEndOffset();
myCallback.gotoEndOfTemplate(key);
if (inSeparateEvent) {
- invokeTemplateAndProcessTail(templateKey, tailStart, count - finalI - 1);
+ invokeTemplateAndProcessTail(templateToken, tailStart, count - finalI - 1);
}
}
});
if (interpreter.invoke(tailStart)) {
if (inSeparateEvent) {
- invokeTemplateAndProcessTail(templateKey, tailStart, count - finalI - 1);
+ invokeTemplateAndProcessTail(templateToken, tailStart, count - finalI - 1);
}
}
else {
}
}
};
- if (!prepareAndInvokeTemplate(templateKey, myCallback, listener) || flag[0]) {
+ if (!invokeTemplate(templateToken, myCallback, listener) || flag[0]) {
return false;
}
}