blob: 729e6062c626ee328a907bdc72bd4318109f126e [file] [log] [blame]
/*
* Copyright 2000-2014 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.editorActions;
import com.intellij.application.options.editor.WebEditorOptions;
import com.intellij.lang.ASTNode;
import com.intellij.lang.xml.XMLLanguage;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorModificationUtil;
import com.intellij.openapi.editor.ScrollType;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.tree.TreeUtil;
import com.intellij.psi.templateLanguages.OuterLanguageElement;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.xml.*;
import com.intellij.xml.util.XmlTagUtil;
import com.intellij.xml.util.XmlUtil;
import org.jetbrains.annotations.NotNull;
public class XmlSlashTypedHandler extends TypedHandlerDelegate {
@Override
public Result beforeCharTyped(final char c, final Project project, final Editor editor, final PsiFile editedFile, final FileType fileType) {
if ((editedFile.getLanguage() instanceof XMLLanguage || editedFile.getViewProvider().getBaseLanguage() instanceof XMLLanguage) && c == '/') {
PsiDocumentManager.getInstance(project).commitAllDocuments();
PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
final int offset = editor.getCaretModel().getOffset();
if (file == null) return Result.CONTINUE;
FileViewProvider provider = file.getViewProvider();
PsiElement element = provider.findElementAt(offset, XMLLanguage.class);
if (element instanceof XmlToken) {
final IElementType tokenType = ((XmlToken)element).getTokenType();
if (tokenType == XmlTokenType.XML_EMPTY_ELEMENT_END &&
offset == element.getTextOffset()
) {
editor.getCaretModel().moveToOffset(offset + 1);
editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
return Result.STOP;
} else if (tokenType == XmlTokenType.XML_TAG_END &&
offset == element.getTextOffset()
) {
final ASTNode parentNode = element.getParent().getNode();
final ASTNode child = XmlChildRole.CLOSING_TAG_START_FINDER.findChild(parentNode);
if (child != null && offset + 1 == child.getTextRange().getStartOffset()) {
editor.getDocument().replaceString(offset + 1, parentNode.getTextRange().getEndOffset(),"");
}
}
}
}
return Result.CONTINUE;
}
@Override
public Result charTyped(final char c, final Project project, @NotNull final Editor editor, @NotNull final PsiFile editedFile) {
if (!WebEditorOptions.getInstance().isAutoCloseTag()) return Result.CONTINUE;
if ((editedFile.getLanguage() instanceof XMLLanguage || editedFile.getViewProvider().getBaseLanguage() instanceof XMLLanguage) && c == '/') {
PsiDocumentManager.getInstance(project).commitAllDocuments();
PsiFile file = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
if (file == null) return Result.CONTINUE;
FileViewProvider provider = file.getViewProvider();
final int offset = editor.getCaretModel().getOffset();
PsiElement element = provider.findElementAt(offset - 1, XMLLanguage.class);
if (element == null) return Result.CONTINUE;
if (!(element.getLanguage() instanceof XMLLanguage)) return Result.CONTINUE;
ASTNode prevLeaf = element.getNode();
if (prevLeaf == null) return Result.CONTINUE;
final String prevLeafText = prevLeaf.getText();
if ("</".equals(prevLeafText) && prevLeaf.getElementType() == XmlTokenType.XML_END_TAG_START) {
XmlTag tag = PsiTreeUtil.getParentOfType(element, XmlTag.class);
if (tag != null && StringUtil.isNotEmpty(tag.getName()) && TreeUtil.findSibling(prevLeaf, XmlTokenType.XML_NAME) == null) {
// check for template language like JSP
if (provider instanceof MultiplePsiFilesPerDocumentFileViewProvider) {
PsiElement element1 = SingleRootFileViewProvider.findElementAt(file, offset - 1);
if (element1 != null && element1.getText().startsWith("</")) {
// case of top-level jsp tag
XmlTag tag1 = PsiTreeUtil.getParentOfType(element1, XmlTag.class);
if (shouldReplace(tag, tag1)) {
tag = tag1;
}
else {
// if we have enclosing jsp tag, actual tag to be completed will be previous sibling
tag1 = PsiTreeUtil.getPrevSiblingOfType(element1.getParent(), XmlTag.class);
if (shouldReplace(tag, tag1)) {
tag = tag1;
}
}
}
}
EditorModificationUtil.insertStringAtCaret(editor, tag.getName() + ">", false);
return Result.STOP;
}
}
if (!"/".equals(prevLeafText.trim())) return Result.CONTINUE;
while((prevLeaf = TreeUtil.prevLeaf(prevLeaf)) != null && prevLeaf.getElementType() == XmlTokenType.XML_WHITE_SPACE);
if(prevLeaf instanceof OuterLanguageElement) {
element = file.getViewProvider().findElementAt(offset - 1, file.getLanguage());
prevLeaf = element != null ? element.getNode() : null;
while((prevLeaf = TreeUtil.prevLeaf(prevLeaf)) != null && prevLeaf.getElementType() == XmlTokenType.XML_WHITE_SPACE);
}
if(prevLeaf == null) return Result.CONTINUE;
XmlTag tag = PsiTreeUtil.getParentOfType(prevLeaf.getPsi(), XmlTag.class);
if(tag == null) { // prevLeaf maybe in one tree and element in another
PsiElement element2 = provider.findElementAt(prevLeaf.getStartOffset(), XMLLanguage.class);
tag = PsiTreeUtil.getParentOfType(element2, XmlTag.class);
if (tag == null) return Result.CONTINUE;
}
final XmlToken startToken = XmlUtil.getTokenOfType(tag, XmlTokenType.XML_START_TAG_START);
if (startToken == null || !startToken.getText().equals("<")) return Result.CONTINUE;
if (XmlUtil.getTokenOfType(tag, XmlTokenType.XML_TAG_END) != null) return Result.CONTINUE;
if (XmlUtil.getTokenOfType(tag, XmlTokenType.XML_EMPTY_ELEMENT_END) != null) return Result.CONTINUE;
if (PsiTreeUtil.getParentOfType(element, XmlAttributeValue.class) != null) return Result.CONTINUE;
EditorModificationUtil.insertStringAtCaret(editor, ">", false);
return Result.STOP;
}
return Result.CONTINUE;
}
public boolean shouldReplace(XmlTag tag, XmlTag tag1) {
return tag1 != null && tag1 != tag && tag1.getTextOffset() > tag.getTextOffset() &&
XmlUtil.getTokenOfType(tag1, XmlTokenType.XML_EMPTY_ELEMENT_END) == null && XmlTagUtil.getEndTagNameElement(tag1) == null;
}
}