IDEA-102173 Android XML: automatically insert app-level namespace when complete custom attrs instead of inserting it without an prefix

This commit is contained in:
Eugene Kudelevsky
2013-08-05 15:48:50 +04:00
parent 0301435334
commit d519e622f6
4 changed files with 89 additions and 5 deletions
@@ -20,11 +20,11 @@ import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.xml.XmlElement;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.xml.XmlName;
import com.intellij.util.xml.impl.DomInvocationHandler;
import com.intellij.util.xml.impl.DomManagerImpl;
import com.intellij.util.xml.reflect.DomAttributeChildDescription;
import com.intellij.util.xml.XmlName;
import com.intellij.xml.XmlAttributeDescriptor;
import com.intellij.xml.NamespaceAwareXmlAttributeDescriptor;
import com.intellij.xml.util.XmlUtil;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.Nullable;
@@ -32,7 +32,7 @@ import org.jetbrains.annotations.Nullable;
/**
* @author mike
*/
public class DomAttributeXmlDescriptor implements XmlAttributeDescriptor {
public class DomAttributeXmlDescriptor implements NamespaceAwareXmlAttributeDescriptor {
private final DomAttributeChildDescription myDescription;
private final Project myProject;
@@ -114,6 +114,11 @@ public class DomAttributeXmlDescriptor implements XmlAttributeDescriptor {
return myDescription.getXmlName().getLocalName();
}
@Nullable
public String getNamespace() {
return myDescription.getXmlName().getNamespaceKey();
}
public void init(final PsiElement element) {
throw new UnsupportedOperationException("Method init not implemented in " + getClass());
}
@@ -21,10 +21,16 @@ import com.intellij.lang.html.HTMLLanguage;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ScrollType;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.xml.XmlFile;
import com.intellij.util.text.CharArrayUtil;
import com.intellij.xml.XmlNamespaceHelper;
import com.intellij.xml.util.HtmlUtil;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
/**
* @author peter
@@ -32,6 +38,18 @@ import com.intellij.xml.util.HtmlUtil;
public class XmlAttributeInsertHandler implements InsertHandler<LookupElement> {
public static final XmlAttributeInsertHandler INSTANCE = new XmlAttributeInsertHandler();
private final String myNamespacePrefixToInsert;
private final String myNamespaceToInsert;
public XmlAttributeInsertHandler() {
this(null, null);
}
public XmlAttributeInsertHandler(@Nullable String namespaceToInsert, @Nullable String namespacePrefixToInsert) {
myNamespaceToInsert = namespaceToInsert;
myNamespacePrefixToInsert = namespacePrefixToInsert;
}
public void handleInsert(InsertionContext context, LookupElement item) {
final Editor editor = context.getEditor();
@@ -68,5 +86,15 @@ public class XmlAttributeInsertHandler implements InsertHandler<LookupElement> {
editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
editor.getSelectionModel().removeSelection();
AutoPopupController.getInstance(editor.getProject()).scheduleAutoPopup(editor);
if (myNamespaceToInsert != null && file instanceof XmlFile) {
final XmlNamespaceHelper helper = XmlNamespaceHelper.getHelper(context.getFile());
if (helper != null) {
PsiDocumentManager.getInstance(context.getProject()).commitDocument(document);
helper.insertNamespaceDeclaration((XmlFile)file, editor, Collections.singleton(myNamespaceToInsert),
myNamespacePrefixToInsert, null);
}
}
}
}
@@ -18,13 +18,16 @@ package com.intellij.codeInsight.completion;
import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiReference;
import com.intellij.psi.impl.source.xml.XmlAttributeImpl;
import com.intellij.psi.impl.source.xml.XmlAttributeReference;
import com.intellij.psi.meta.PsiPresentableMetaData;
import com.intellij.psi.xml.XmlAttribute;
import com.intellij.psi.xml.XmlFile;
import com.intellij.psi.xml.XmlTag;
import com.intellij.util.ProcessingContext;
import com.intellij.xml.NamespaceAwareXmlAttributeDescriptor;
import com.intellij.xml.XmlAttributeDescriptor;
import com.intellij.xml.XmlElementDescriptor;
import com.intellij.xml.XmlExtension;
@@ -68,7 +71,8 @@ public class XmlAttributeReferenceCompletionProvider extends CompletionProvider<
XmlAttribute attribute,
@Nullable InsertHandler<LookupElement> replacementInsertHandler) {
final XmlTag tag = attribute.getParent();
final XmlExtension extension = XmlExtension.getExtension(tag.getContainingFile());
final PsiFile file = tag.getContainingFile();
final XmlExtension extension = XmlExtension.getExtension(file);
final String prefix = attribute.getName().contains(":") && ((XmlAttributeImpl) attribute).getRealLocalName().length() > 0
? attribute.getNamespacePrefix() + ":"
: null;
@@ -80,6 +84,29 @@ public class XmlAttributeReferenceCompletionProvider extends CompletionProvider<
for (XmlAttributeDescriptor descriptor : descriptors) {
if (isValidVariant(attribute, descriptor, attributes, extension)) {
String name = descriptor.getName(tag);
InsertHandler<LookupElement> insertHandler = XmlAttributeInsertHandler.INSTANCE;
if (replacementInsertHandler != null) {
insertHandler = replacementInsertHandler;
}
else if (descriptor instanceof NamespaceAwareXmlAttributeDescriptor) {
final String namespace = ((NamespaceAwareXmlAttributeDescriptor)descriptor).getNamespace();
if (file instanceof XmlFile &&
namespace != null &&
namespace.length() > 0 &&
!name.contains(":") &&
tag.getPrefixByNamespace(namespace) == null) {
String suggestedPrefix = ExtendedTagInsertHandler.suggestPrefix((XmlFile)file, namespace);
if (suggestedPrefix != null) {
suggestedPrefix = makePrefixUnique(suggestedPrefix, tag);
name = suggestedPrefix + ":" + name;
insertHandler = new XmlAttributeInsertHandler(namespace, suggestedPrefix);
}
}
}
if (prefix == null || name.startsWith(prefix)) {
if (prefix != null && name.length() > prefix.length()) {
name = descriptor.getName(tag).substring(prefix.length());
@@ -94,7 +121,7 @@ public class XmlAttributeReferenceCompletionProvider extends CompletionProvider<
}
element = element
.withCaseSensitivity(caseSensitive)
.withInsertHandler(replacementInsertHandler != null ? replacementInsertHandler : XmlAttributeInsertHandler.INSTANCE);
.withInsertHandler(insertHandler);
result.addElement(
descriptor.isRequired() ? PrioritizedLookupElement.withPriority(element.appendTailText("(required)", true), 100) : element);
}
@@ -102,6 +129,19 @@ public class XmlAttributeReferenceCompletionProvider extends CompletionProvider<
}
}
@NotNull
private static String makePrefixUnique(@NotNull String basePrefix, @NotNull XmlTag context) {
if (context.getNamespaceByPrefix(basePrefix).isEmpty()) {
return basePrefix;
}
int i = 1;
while (!context.getNamespaceByPrefix(basePrefix + i).isEmpty()) {
i++;
}
return basePrefix + i;
}
private static boolean isValidVariant(XmlAttribute attribute,
@NotNull XmlAttributeDescriptor descriptor,
final XmlAttribute[] attributes,
@@ -0,0 +1,11 @@
package com.intellij.xml;
import org.jetbrains.annotations.Nullable;
/**
* @author Eugene.Kudelevsky
*/
public interface NamespaceAwareXmlAttributeDescriptor extends XmlAttributeDescriptor {
@Nullable
String getNamespace();
}