be20f7683e299d306a81b4de6a0eca8abfc27a1a
[idea/community.git] / xml / impl / src / com / intellij / codeInsight / completion / XmlSmartEnterProcessor.java
1 /*
2  * Copyright 2000-2009 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.codeInsight.completion;
17
18 import com.intellij.codeInsight.editorActions.smartEnter.SmartEnterProcessor;
19 import com.intellij.codeInsight.template.zencoding.XmlZenCodingTemplate;
20 import com.intellij.lang.ASTNode;
21 import com.intellij.openapi.diagnostic.Logger;
22 import com.intellij.openapi.editor.Document;
23 import com.intellij.openapi.editor.Editor;
24 import com.intellij.openapi.project.Project;
25 import com.intellij.openapi.util.TextRange;
26 import com.intellij.psi.PsiElement;
27 import com.intellij.psi.PsiFile;
28 import com.intellij.psi.util.PsiTreeUtil;
29 import com.intellij.psi.xml.XmlAttribute;
30 import com.intellij.psi.xml.XmlAttributeValue;
31 import com.intellij.psi.xml.XmlChildRole;
32 import com.intellij.psi.xml.XmlTag;
33 import com.intellij.util.IncorrectOperationException;
34 import com.intellij.util.text.CharArrayUtil;
35 import org.jetbrains.annotations.NotNull;
36 import org.jetbrains.annotations.Nullable;
37
38 /**
39  * @author spleaner
40  */
41 public class XmlSmartEnterProcessor extends SmartEnterProcessor {
42   private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.completion.XmlSmartEnterProcessor");
43
44   public boolean process(@NotNull final Project project, @NotNull final Editor editor, @NotNull final PsiFile psiFile) {
45     final PsiElement atCaret = getStatementAtCaret(editor, psiFile);
46     XmlTag tagAtCaret = PsiTreeUtil.getParentOfType(atCaret, XmlTag.class);
47     if (tagAtCaret != null) {
48       try {
49         final ASTNode emptyTagEnd = XmlChildRole.EMPTY_TAG_END_FINDER.findChild(tagAtCaret.getNode());
50         final ASTNode endTagEnd = XmlChildRole.START_TAG_END_FINDER.findChild(tagAtCaret.getNode());
51         if (emptyTagEnd != null || endTagEnd != null) {
52           return XmlZenCodingTemplate.startZenCoding(editor, psiFile, null);
53         }
54
55         int insertionOffset = tagAtCaret.getTextRange().getEndOffset();
56         Document doc = editor.getDocument();
57         int caretAt = editor.getCaretModel().getOffset();
58         final CharSequence text = doc.getCharsSequence();
59         final int probableCommaOffset = CharArrayUtil.shiftForward(text, insertionOffset, " \t");
60         final PsiElement siebling = tagAtCaret.getNextSibling();
61         int caretTo = caretAt;
62         char ch;
63
64         if (caretAt < probableCommaOffset) {
65           final XmlAttribute xmlAttribute = PsiTreeUtil.getParentOfType(atCaret, XmlAttribute.class, false, XmlTag.class);
66
67           CharSequence tagNameText = null;
68           if (xmlAttribute != null) {
69             final ASTNode node = tagAtCaret.getNode();
70             if (node != null) {
71               final ASTNode tagName = XmlChildRole.START_TAG_NAME_FINDER.findChild(node);
72               if (tagName != null) {
73                 tagNameText = tagName.getText();
74               }
75             }
76
77             final XmlAttributeValue valueElement = xmlAttribute.getValueElement();
78             final TextRange textRange = xmlAttribute.getTextRange();
79             caretAt = valueElement == null ? textRange.getStartOffset() : getClosingQuote(xmlAttribute).length() == 0 ? textRange.getEndOffset() : caretAt;
80           }
81
82           if (tagNameText == null) {
83             tagNameText = text.subSequence(tagAtCaret.getTextRange().getStartOffset() + 1, caretAt);
84           }
85
86           final PsiElement element = psiFile.findElementAt(probableCommaOffset);
87           final XmlTag tag = PsiTreeUtil.getParentOfType(element, XmlTag.class);
88           final CharSequence text2insert = getClosingPart(xmlAttribute, tagAtCaret, false);
89
90           if (tag != null && tag.getTextRange().getStartOffset() == probableCommaOffset) {
91             doc.insertString(caretAt, text2insert);
92             if (shouldInsertClosingTag(xmlAttribute, tagAtCaret)) {
93               doc.insertString(tag.getTextRange().getEndOffset() + text2insert.length(), "</" + tagAtCaret.getName() + ">");
94             }
95
96             caretTo = tag.getTextRange().getEndOffset() + text2insert.length();
97           }
98           else {
99             doc.insertString(caretAt, text2insert);
100             if (shouldInsertClosingTag(xmlAttribute, tagAtCaret)) {
101               doc.insertString(probableCommaOffset + text2insert.length(), "</" + tagNameText + ">");
102             }
103
104             caretTo = probableCommaOffset + text2insert.length();
105           }
106         }
107         else if (siebling instanceof XmlTag && siebling.getTextRange().getStartOffset() == caretAt) {
108           final XmlAttribute xmlAttribute = PsiTreeUtil.getParentOfType(atCaret, XmlAttribute.class, false, XmlTag.class);
109           final CharSequence text2insert = getClosingPart(xmlAttribute, tagAtCaret, false);
110
111           doc.insertString(caretAt, text2insert);
112           if (shouldInsertClosingTag(xmlAttribute, tagAtCaret)) {
113             doc.insertString(siebling.getTextRange().getEndOffset() + text2insert.length(), "</" + tagAtCaret.getName() + ">");
114           }
115
116           caretTo = siebling.getTextRange().getEndOffset() + text2insert.length();
117         }
118         else if (probableCommaOffset >= text.length() || ((ch = text.charAt(probableCommaOffset)) != '/' && ch != '>')) {
119           final XmlAttribute xmlAttribute = PsiTreeUtil.getParentOfType(atCaret, XmlAttribute.class, false, XmlTag.class);
120           final CharSequence text2insert = getClosingPart(xmlAttribute, tagAtCaret, true);
121
122           doc.insertString(insertionOffset, text2insert);
123           caretTo = insertionOffset + text2insert.length();
124         }
125
126         if (isUncommited(project)) {
127           commit(editor);
128           tagAtCaret = PsiTreeUtil.getParentOfType(getStatementAtCaret(editor, psiFile), XmlTag.class);
129           editor.getCaretModel().moveToOffset(caretTo);
130         }
131
132         reformat(tagAtCaret);
133         commit(editor);
134       }
135       catch (IncorrectOperationException e) {
136         LOG.error(e);
137       }
138     }
139     return true;
140   }
141
142   protected boolean shouldInsertClosingTag(final XmlAttribute xmlAttribute, final XmlTag tagAtCaret) {
143     return true;
144   }
145
146   protected String getClosingPart(final XmlAttribute xmlAttribute, final XmlTag tagAtCaret, final boolean emptyTag) {
147     return getClosingQuote(xmlAttribute) + (emptyTag ? "/>" : ">");
148   }
149
150   @NotNull
151   protected static CharSequence getClosingQuote(@Nullable final XmlAttribute attribute) {
152     if (attribute == null) {
153       return "";
154     }
155
156     final XmlAttributeValue element = attribute.getValueElement();
157     if (element == null) {
158       return "";
159     }
160
161     final String s = element.getText();
162     if (s != null && s.length() > 0) {
163       if (s.charAt(0) == '"' && s.charAt(s.length() - 1) != '"') {
164         return "\"";
165       }
166       else if (s.charAt(0) == '\'' && s.charAt(s.length() - 1) != '\'') {
167         return "'";
168       }
169     }
170
171     return "";
172   }
173 }