2 * Copyright 2000-2009 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com
.intellij
.codeInsight
.completion
;
18 import com
.intellij
.codeInsight
.editorActions
.smartEnter
.SmartEnterProcessor
;
19 import com
.intellij
.lang
.ASTNode
;
20 import com
.intellij
.openapi
.diagnostic
.Logger
;
21 import com
.intellij
.openapi
.editor
.Document
;
22 import com
.intellij
.openapi
.editor
.Editor
;
23 import com
.intellij
.openapi
.project
.Project
;
24 import com
.intellij
.openapi
.util
.TextRange
;
25 import com
.intellij
.psi
.PsiElement
;
26 import com
.intellij
.psi
.PsiFile
;
27 import com
.intellij
.psi
.util
.PsiTreeUtil
;
28 import com
.intellij
.psi
.xml
.XmlAttribute
;
29 import com
.intellij
.psi
.xml
.XmlAttributeValue
;
30 import com
.intellij
.psi
.xml
.XmlChildRole
;
31 import com
.intellij
.psi
.xml
.XmlTag
;
32 import com
.intellij
.util
.IncorrectOperationException
;
33 import com
.intellij
.util
.text
.CharArrayUtil
;
34 import org
.jetbrains
.annotations
.NotNull
;
35 import org
.jetbrains
.annotations
.Nullable
;
40 public class XmlSmartEnterProcessor
extends SmartEnterProcessor
{
41 private static final Logger LOG
= Logger
.getInstance("#com.intellij.codeInsight.completion.XmlSmartEnterProcessor");
43 public boolean process(@NotNull final Project project
, @NotNull final Editor editor
, @NotNull final PsiFile psiFile
) {
44 final PsiElement atCaret
= getStatementAtCaret(editor
, psiFile
);
45 XmlTag tagAtCaret
= PsiTreeUtil
.getParentOfType(atCaret
, XmlTag
.class);
46 if (tagAtCaret
!= null) {
48 final ASTNode emptyTagEnd
= XmlChildRole
.EMPTY_TAG_END_FINDER
.findChild(tagAtCaret
.getNode());
49 final ASTNode endTagEnd
= XmlChildRole
.START_TAG_END_FINDER
.findChild(tagAtCaret
.getNode());
50 if (emptyTagEnd
!= null || endTagEnd
!= null) {
54 int insertionOffset
= tagAtCaret
.getTextRange().getEndOffset();
55 Document doc
= editor
.getDocument();
56 int caretAt
= editor
.getCaretModel().getOffset();
57 final CharSequence text
= doc
.getCharsSequence();
58 final int probableCommaOffset
= CharArrayUtil
.shiftForward(text
, insertionOffset
, " \t");
59 final PsiElement siebling
= tagAtCaret
.getNextSibling();
60 int caretTo
= caretAt
;
63 if (caretAt
< probableCommaOffset
) {
64 final XmlAttribute xmlAttribute
= PsiTreeUtil
.getParentOfType(atCaret
, XmlAttribute
.class, false, XmlTag
.class);
66 CharSequence tagNameText
= null;
67 if (xmlAttribute
!= null) {
68 final ASTNode node
= tagAtCaret
.getNode();
70 final ASTNode tagName
= XmlChildRole
.START_TAG_NAME_FINDER
.findChild(node
);
71 if (tagName
!= null) {
72 tagNameText
= tagName
.getText();
76 final XmlAttributeValue valueElement
= xmlAttribute
.getValueElement();
77 final TextRange textRange
= xmlAttribute
.getTextRange();
78 caretAt
= valueElement
== null ? textRange
.getStartOffset() : getClosingQuote(xmlAttribute
).length() == 0 ? textRange
.getEndOffset() : caretAt
;
81 if (tagNameText
== null) {
82 tagNameText
= text
.subSequence(tagAtCaret
.getTextRange().getStartOffset() + 1, caretAt
);
85 final PsiElement element
= psiFile
.findElementAt(probableCommaOffset
);
86 final XmlTag tag
= PsiTreeUtil
.getParentOfType(element
, XmlTag
.class);
87 final CharSequence text2insert
= getClosingPart(xmlAttribute
, tagAtCaret
, false);
89 if (tag
!= null && tag
.getTextRange().getStartOffset() == probableCommaOffset
) {
90 doc
.insertString(caretAt
, text2insert
);
91 if (shouldInsertClosingTag(xmlAttribute
, tagAtCaret
)) {
92 doc
.insertString(tag
.getTextRange().getEndOffset() + text2insert
.length(), "</" + tagAtCaret
.getName() + ">");
95 caretTo
= tag
.getTextRange().getEndOffset() + text2insert
.length();
98 doc
.insertString(caretAt
, text2insert
);
99 if (shouldInsertClosingTag(xmlAttribute
, tagAtCaret
)) {
100 doc
.insertString(probableCommaOffset
+ text2insert
.length(), "</" + tagNameText
+ ">");
103 caretTo
= probableCommaOffset
+ text2insert
.length();
106 else if (siebling
instanceof XmlTag
&& siebling
.getTextRange().getStartOffset() == caretAt
) {
107 final XmlAttribute xmlAttribute
= PsiTreeUtil
.getParentOfType(atCaret
, XmlAttribute
.class, false, XmlTag
.class);
108 final CharSequence text2insert
= getClosingPart(xmlAttribute
, tagAtCaret
, false);
110 doc
.insertString(caretAt
, text2insert
);
111 if (shouldInsertClosingTag(xmlAttribute
, tagAtCaret
)) {
112 doc
.insertString(siebling
.getTextRange().getEndOffset() + text2insert
.length(), "</" + tagAtCaret
.getName() + ">");
115 caretTo
= siebling
.getTextRange().getEndOffset() + text2insert
.length();
117 else if (probableCommaOffset
>= text
.length() || ((ch
= text
.charAt(probableCommaOffset
)) != '/' && ch
!= '>')) {
118 final XmlAttribute xmlAttribute
= PsiTreeUtil
.getParentOfType(atCaret
, XmlAttribute
.class, false, XmlTag
.class);
119 final CharSequence text2insert
= getClosingPart(xmlAttribute
, tagAtCaret
, true);
121 doc
.insertString(insertionOffset
, text2insert
);
122 caretTo
= insertionOffset
+ text2insert
.length();
125 if (isUncommited(project
)) {
127 tagAtCaret
= PsiTreeUtil
.getParentOfType(getStatementAtCaret(editor
, psiFile
), XmlTag
.class);
128 editor
.getCaretModel().moveToOffset(caretTo
);
131 reformat(tagAtCaret
);
134 catch (IncorrectOperationException e
) {
141 protected boolean shouldInsertClosingTag(final XmlAttribute xmlAttribute
, final XmlTag tagAtCaret
) {
145 protected String
getClosingPart(final XmlAttribute xmlAttribute
, final XmlTag tagAtCaret
, final boolean emptyTag
) {
146 return getClosingQuote(xmlAttribute
) + (emptyTag ?
"/>" : ">");
150 protected static CharSequence
getClosingQuote(@Nullable final XmlAttribute attribute
) {
151 if (attribute
== null) {
155 final XmlAttributeValue element
= attribute
.getValueElement();
156 if (element
== null) {
160 final String s
= element
.getText();
161 if (s
!= null && s
.length() > 0) {
162 if (s
.charAt(0) == '"' && s
.charAt(s
.length() - 1) != '"') {
165 else if (s
.charAt(0) == '\'' && s
.charAt(s
.length() - 1) != '\'') {