refactoring, extract zen-coding filter EP

This commit is contained in:
Eugene.Kudelevsky
2010-05-11 03:34:55 +04:00
parent ee2d70a6d6
commit d49291919b
7 changed files with 234 additions and 83 deletions
@@ -61,7 +61,7 @@ public class CustomTemplateCallback {
fixInitialState();
}
@Nullable
@NotNull
public PsiElement getContext() {
return getContext(myFile, myStartOffset > 0 ? myStartOffset - 1 : myStartOffset);
}
@@ -231,7 +231,8 @@ public class CustomTemplateCallback {
myEditor.getDocument().deleteString(caretAt - key.length(), caretAt);
}
public static PsiElement getContext(PsiFile file, int offset) {
@NotNull
public static PsiElement getContext(@NotNull PsiFile file, int offset) {
PsiElement element = null;
if (!InjectedLanguageManager.getInstance(file.getProject()).isInjectedFragment(file)) {
element = InjectedLanguageUtil.findInjectedElementNoCommit(file, offset);
@@ -37,6 +37,8 @@
<extensionPoint name="xml.implicitNamespaceDescriptorProvider"
interface="com.intellij.javaee.ImplicitNamespaceDescriptorProvider"/>
<extensionPoint name="xml.zenCodingFilter" interface="com.intellij.codeInsight.template.zencoding.ZenCodingFilter"/>
</extensionPoints>
<extensions defaultExtensionNs="com.intellij">
@@ -17,22 +17,18 @@ package com.intellij.codeInsight.template.zencoding;
import com.intellij.codeInsight.template.CustomTemplateCallback;
import com.intellij.codeInsight.template.impl.TemplateImpl;
import com.intellij.lang.ASTNode;
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.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.*;
import com.intellij.psi.xml.XmlTag;
import com.intellij.psi.xml.XmlToken;
import com.intellij.psi.xml.XmlTokenType;
import com.intellij.util.containers.HashMap;
import com.intellij.util.containers.HashSet;
import com.intellij.util.containers.IntArrayList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
@@ -43,7 +39,6 @@ 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;
@@ -269,8 +264,11 @@ class XmlZenCodingInterpreter {
}
@Nullable
private static Map<String, String> buildPredefinedValues(List<Pair<String, String>> attribute2value, int numberInIteration) {
String attributes = buildAttributesString(attribute2value, numberInIteration);
private static Map<String, String> buildPredefinedValues(List<Pair<String, String>> attribute2value,
int numberInIteration,
CustomTemplateCallback callback) {
String attributes = buildAttributesString(attribute2value, numberInIteration, callback);
assert attributes != null;
attributes = attributes.length() > 0 ? ' ' + attributes : null;
Map<String, String> predefinedValues = null;
if (attributes != null) {
@@ -280,28 +278,19 @@ class XmlZenCodingInterpreter {
return predefinedValues;
}
@NotNull
private static String buildAttributesString(List<Pair<String, String>> attribute2value, int numberInIteration) {
StringBuilder result = new StringBuilder();
for (Iterator<Pair<String, String>> it = attribute2value.iterator(); it.hasNext();) {
Pair<String, String> pair = it.next();
String name = pair.first;
String value = getValue(pair, numberInIteration);
result.append(getAttributeString(name, value));
if (it.hasNext()) {
result.append(' ');
@Nullable
private static String buildAttributesString(List<Pair<String, String>> attribute2value,
int numberInIteration,
CustomTemplateCallback callback) {
PsiElement context = callback.getContext();
for (ZenCodingFilter filter : ZenCodingFilter.EP_NAME.getExtensions()) {
if (filter.isMyContext(context)) {
return filter.buildAttributesString(attribute2value, numberInIteration);
}
}
return result.toString();
return new ZenCodingFilterImpl().buildAttributesString(attribute2value, numberInIteration);
}
private static String getAttributeString(String name, String value) {
return name + "=\"" + value + '"';
}
private static String getValue(Pair<String, String> pair, int numberInIteration) {
return pair.second.replace(NUMBER_IN_ITERATION_PLACE_HOLDER, Integer.toString(numberInIteration + 1));
}
private static void invokeTemplate(TemplateToken token,
final CustomTemplateCallback callback,
@@ -315,13 +304,15 @@ class XmlZenCodingInterpreter {
for (Iterator<Pair<String, String>> iterator = attr2value.iterator(); iterator.hasNext();) {
Pair<String, String> pair = iterator.next();
if (tag.getAttribute(pair.first) != null) {
tag.setAttribute(pair.first, getValue(pair, numberInIteration));
tag.setAttribute(pair.first, ZenCodingUtil.getValue(pair, numberInIteration));
iterator.remove();
}
}
modifiedTemplate.setString(filter(tag, callback));
String s = filter(tag, callback);
assert s != null;
modifiedTemplate.setString(s);
removeVariablesWhichHasNoSegment(modifiedTemplate);
Map<String, String> predefinedValues = buildPredefinedValues(attr2value, numberInIteration);
Map<String, String> predefinedValues = buildPredefinedValues(attr2value, numberInIteration, callback);
callback.expandTemplate(modifiedTemplate, predefinedValues);
return;
}
@@ -330,60 +321,20 @@ class XmlZenCodingInterpreter {
}
else {
// for CSS
Map<String, String> predefinedValues = buildPredefinedValues(attr2value, numberInIteration);
Map<String, String> predefinedValues = buildPredefinedValues(attr2value, numberInIteration, callback);
callback.expandTemplate(token.getKey(), predefinedValues);
}
}
@Nullable
private static String filter(XmlTag tag, CustomTemplateCallback callback) {
if (XmlZenCodingTemplate.isTrueXml(callback)) {
closeUnclosingTags(tag);
}
return tag.getContainingFile().getText();
}
private static boolean isTagClosed(@NotNull XmlTag tag) {
ASTNode node = tag.getNode();
assert node != null;
final ASTNode emptyTagEnd = XmlChildRole.EMPTY_TAG_END_FINDER.findChild(node);
final ASTNode endTagEnd = XmlChildRole.CLOSING_TAG_START_FINDER.findChild(node);
return emptyTagEnd != null || endTagEnd != null;
}
@SuppressWarnings({"ConstantConditions"})
private static void closeUnclosingTags(@NotNull XmlTag root) {
final List<SmartPsiElementPointer<XmlTag>> tagToClose = new ArrayList<SmartPsiElementPointer<XmlTag>>();
Project project = root.getProject();
final SmartPointerManager pointerManager = SmartPointerManager.getInstance(project);
root.accept(new XmlRecursiveElementVisitor() {
@Override
public void visitXmlTag(final XmlTag tag) {
if (!isTagClosed(tag)) {
tagToClose.add(pointerManager.createLazyPointer(tag));
}
}
});
PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project);
for (final SmartPsiElementPointer<XmlTag> pointer : tagToClose) {
final XmlTag tag = pointer.getElement();
if (tag != null) {
final ASTNode child = XmlChildRole.START_TAG_END_FINDER.findChild(tag.getNode());
if (child != null) {
final int offset = child.getTextRange().getStartOffset();
VirtualFile file = tag.getContainingFile().getVirtualFile();
if (file != null) {
final Document document = FileDocumentManager.getInstance().getDocument(file);
documentManager.doPostponedOperationsAndUnblockDocument(document);
ApplicationManager.getApplication().runWriteAction(new Runnable() {
public void run() {
document.replaceString(offset, tag.getTextRange().getEndOffset(), "/>");
}
});
}
}
PsiElement context = callback.getContext();
for (ZenCodingFilter filter : ZenCodingFilter.EP_NAME.getExtensions()) {
if (filter.isMyContext(context)) {
return filter.toString(tag, context);
}
}
documentManager.commitAllDocuments();
return new ZenCodingFilterImpl().toString(tag, context);
}
private static void fail() {
@@ -160,7 +160,10 @@ public class XmlZenCodingTemplate extends ZenCodingTemplate {
}
public static boolean isTrueXml(CustomTemplateCallback callback) {
FileType type = callback.getFileType();
return isTrueXml(callback.getFileType());
}
public static boolean isTrueXml(FileType type) {
return type == StdFileTypes.XHTML || type == StdFileTypes.JSPX || type == StdFileTypes.XML;
}
@@ -265,11 +268,23 @@ public class XmlZenCodingTemplate extends ZenCodingTemplate {
if (PsiTreeUtil.getParentOfType(element, XmlComment.class) != null) {
return false;
}
if (!findApplicableFilter(element)) {
return false;
}
return true;
}
return false;
}
private static boolean findApplicableFilter(@NotNull PsiElement context) {
for (ZenCodingFilter filter : ZenCodingFilter.EP_NAME.getExtensions()) {
if (filter.isMyContext(context)) {
return true;
}
}
return new ZenCodingFilterImpl().isMyContext(context);
}
public static boolean startZenCoding(Editor editor, PsiFile file, String abbreviation) {
int caretAt = editor.getCaretModel().getOffset();
XmlZenCodingTemplate template = CustomLiveTemplate.EP_NAME.findExtension(XmlZenCodingTemplate.class);
@@ -0,0 +1,39 @@
/*
* 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.zencoding;
import com.intellij.openapi.extensions.ExtensionPointName;
import com.intellij.openapi.util.Pair;
import com.intellij.psi.PsiElement;
import com.intellij.psi.xml.XmlTag;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* @author Eugene.Kudelevsky
*/
public interface ZenCodingFilter {
ExtensionPointName<ZenCodingFilter> EP_NAME = new ExtensionPointName<ZenCodingFilter>("com.intellij.xml.zenCodingFilter");
@NotNull
String toString(@NotNull XmlTag tag, @NotNull PsiElement context);
@NotNull
String buildAttributesString(@NotNull List<Pair<String, String>> attribute2value, int numberInIteration);
boolean isMyContext(@NotNull PsiElement context);
}
@@ -0,0 +1,114 @@
/*
* 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.zencoding;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
import com.intellij.psi.xml.XmlChildRole;
import com.intellij.psi.xml.XmlTag;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
* @author Eugene.Kudelevsky
*/
public class ZenCodingFilterImpl implements ZenCodingFilter {
@NotNull
public String toString(@NotNull XmlTag tag, @NotNull PsiElement context) {
FileType fileType = context.getContainingFile().getFileType();
if (XmlZenCodingTemplate.isTrueXml(fileType)) {
closeUnclosingTags(tag);
}
return tag.getContainingFile().getText();
}
@NotNull
public String buildAttributesString(@NotNull List<Pair<String, String>> attribute2value, int numberInIteration) {
StringBuilder result = new StringBuilder();
for (Iterator<Pair<String, String>> it = attribute2value.iterator(); it.hasNext();) {
Pair<String, String> pair = it.next();
String name = pair.first;
String value = ZenCodingUtil.getValue(pair, numberInIteration);
result.append(getAttributeString(name, value));
if (it.hasNext()) {
result.append(' ');
}
}
return result.toString();
}
public boolean isMyContext(@NotNull PsiElement context) {
return true;
}
private static String getAttributeString(String name, String value) {
return name + "=\"" + value + '"';
}
@SuppressWarnings({"ConstantConditions"})
private static void closeUnclosingTags(@NotNull XmlTag root) {
final List<SmartPsiElementPointer<XmlTag>> tagToClose = new ArrayList<SmartPsiElementPointer<XmlTag>>();
Project project = root.getProject();
final SmartPointerManager pointerManager = SmartPointerManager.getInstance(project);
root.accept(new XmlRecursiveElementVisitor() {
@Override
public void visitXmlTag(final XmlTag tag) {
if (!isTagClosed(tag)) {
tagToClose.add(pointerManager.createLazyPointer(tag));
}
}
});
PsiDocumentManager documentManager = PsiDocumentManager.getInstance(project);
for (final SmartPsiElementPointer<XmlTag> pointer : tagToClose) {
final XmlTag tag = pointer.getElement();
if (tag != null) {
final ASTNode child = XmlChildRole.START_TAG_END_FINDER.findChild(tag.getNode());
if (child != null) {
final int offset = child.getTextRange().getStartOffset();
VirtualFile file = tag.getContainingFile().getVirtualFile();
if (file != null) {
final Document document = FileDocumentManager.getInstance().getDocument(file);
documentManager.doPostponedOperationsAndUnblockDocument(document);
ApplicationManager.getApplication().runWriteAction(new Runnable() {
public void run() {
document.replaceString(offset, tag.getTextRange().getEndOffset(), "/>");
}
});
}
}
}
}
documentManager.commitAllDocuments();
}
private static boolean isTagClosed(@NotNull XmlTag tag) {
ASTNode node = tag.getNode();
assert node != null;
final ASTNode emptyTagEnd = XmlChildRole.EMPTY_TAG_END_FINDER.findChild(node);
final ASTNode endTagEnd = XmlChildRole.CLOSING_TAG_START_FINDER.findChild(node);
return emptyTagEnd != null || endTagEnd != null;
}
}
@@ -0,0 +1,29 @@
/*
* 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.zencoding;
import com.intellij.openapi.util.Pair;
/**
* @author Eugene.Kudelevsky
*/
public class ZenCodingUtil {
static final String NUMBER_IN_ITERATION_PLACE_HOLDER = "$";
public static String getValue(Pair<String, String> pair, int numberInIteration) {
return pair.second.replace(NUMBER_IN_ITERATION_PLACE_HOLDER, Integer.toString(numberInIteration + 1));
}
}