/*
* Copyright 2000-2013 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.coldFusion.UI.editorActions.typedHandlers;
import com.intellij.codeInsight.generation.CommenterDataHolder;
import com.intellij.codeInsight.generation.SelfManagingCommenter;
import com.intellij.coldFusion.model.lexer.CfmlTokenTypes;
import com.intellij.coldFusion.model.lexer.CfscriptTokenTypes;
import com.intellij.coldFusion.model.psi.impl.CfmlTagScriptImpl;
import com.intellij.lang.ASTNode;
import com.intellij.lang.Commenter;
import com.intellij.lang.Language;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.FileViewProvider;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.text.CharArrayUtil;
import org.jetbrains.annotations.NotNull;
/**
* Created by Lera Nikolaenko
* Date: 28.10.2008
*/
// TODO: uncomment html style comments
public class CfmlCommenter implements Commenter, SelfManagingCommenter<CfmlCommenter.MyCommenterData> {
private static final String CF_SCRIPT_BLOCK_COMMENT_PREFIX = "/*";
private static final String CF_SCRIPT_BLOCK_COMMENT_SUFFIX = "*/";
private static final String CF_SCRIPT_LINE_COMMENT_PREFIX = "//";
private static final String CFML_COMMENT_PREFIX = "<!---";
private static final String CFML_COMMENT_SUFFIX = "--->";
public String getLineCommentPrefix() {
return null;
}
public String getBlockCommentPrefix() {
return "<!---";
}
public String getBlockCommentSuffix() {
return "--->";
}
public String getCommentedBlockCommentPrefix() {
return "<!—";
}
public String getCommentedBlockCommentSuffix() {
return "—>";
}
private static boolean isOffsetWithinCfscript(int offset, @NotNull Document document, @NotNull PsiFile file) {
PsiElement at = getCfmlElementAtOffset(offset, file);
if (at != null) {
CfmlTagScriptImpl scriptTag = PsiTreeUtil.getParentOfType(at, CfmlTagScriptImpl.class);
if (scriptTag != null) {
return scriptTag.isInsideTag(offset);
}
else {
PsiElement firstChild = file.getFirstChild();
if (firstChild != null) {
ASTNode theDeepestChild = firstChild.getNode();
if (theDeepestChild == null) {
return false;
}
while (theDeepestChild.getFirstChildNode() != null) {
theDeepestChild = theDeepestChild.getFirstChildNode();
}
IElementType elementType = theDeepestChild.getElementType();
if (elementType == CfscriptTokenTypes.COMMENT ||
elementType == CfscriptTokenTypes.COMPONENT_KEYWORD ||
elementType == CfscriptTokenTypes.INTERFACE_KEYWORD ||
elementType == CfscriptTokenTypes.IMPORT_KEYWORD) {
return true;
}
}
}
}
return false;
}
private static PsiElement getCfmlElementAtOffset(int offset, PsiFile file) {
final FileViewProvider fileViewProvider = file.getViewProvider();
file = fileViewProvider.getPsi(fileViewProvider.getBaseLanguage());
final Language cfmlLanguage = file.getLanguage();
return fileViewProvider.findElementAt(offset, cfmlLanguage);
}
private static int skipWhiteSpaces(int offset, PsiFile file) {
PsiElement at = getCfmlElementAtOffset(offset, file);
if (at != null && at.getNode().getElementType() == CfmlTokenTypes.WHITE_SPACE) {
return at.getTextRange().getEndOffset();
}
return offset;
}
public MyCommenterData createLineCommentingState(int startLine, int endLine, @NotNull Document document, @NotNull PsiFile file) {
int lineStartOffset = document.getLineStartOffset(startLine);
return new MyCommenterData(isOffsetWithinCfscript(lineStartOffset, document, file), lineStartOffset);
}
public MyCommenterData createBlockCommentingState(int selectionStart,
int selectionEnd,
@NotNull Document document,
@NotNull PsiFile file) {
return new MyCommenterData(isOffsetWithinCfscript(selectionStart, document, file), selectionStart);
}
public void commentLine(int line, int offset, @NotNull Document document, @NotNull MyCommenterData data) {
final int originalLineEndOffset = document.getLineEndOffset(line);
offset = CharArrayUtil.shiftForward(document.getCharsSequence(), offset, " \t");
if (data.isIsWithinCfscript()) {
document.insertString(offset, CF_SCRIPT_LINE_COMMENT_PREFIX);
}
else {
document.insertString(originalLineEndOffset, CFML_COMMENT_SUFFIX);
document.insertString(offset, CFML_COMMENT_PREFIX);
}
}
public void uncommentLine(int line, int offset, @NotNull Document document, @NotNull MyCommenterData data) {
int rangeEnd = document.getLineEndOffset(line);
if (!data.isIsWithinCfscript()) {
final String commentSuffix = CFML_COMMENT_SUFFIX;
document.deleteString(
rangeEnd - commentSuffix.length(),
rangeEnd);
}
final String commentPrefix = data.getLineCommentPrefix();
document.deleteString(
offset,
offset + commentPrefix.length());
}
public boolean isLineCommented(int line, int offset, @NotNull Document document, @NotNull MyCommenterData data) {
int rangeEnd = document.getLineEndOffset(line);
boolean commented = true;
if (!data.isIsWithinCfscript()) {
final String commentSuffix = CFML_COMMENT_SUFFIX;
if (!CharArrayUtil.regionMatches(
document.getCharsSequence(),
rangeEnd - commentSuffix.length(),
commentSuffix
)) {
commented = false;
}
}
final String commentPrefix = data.getLineCommentPrefix();
if (commented && !CharArrayUtil.regionMatches(
document.getCharsSequence(),
offset,
commentPrefix
)) {
commented = false;
}
return commented;
}
public String getCommentPrefix(int line, @NotNull Document document, @NotNull MyCommenterData data) {
return data.getLineCommentPrefix();
}
// TODO: to uncomment all commented blocks withing selection
public TextRange getBlockCommentRange(int selectionStart, int selectionEnd, @NotNull Document document, @NotNull MyCommenterData data) {
String commentSuffix = data.getBlockCommentSuffix();
String commentPrefix = data.getBlockCommentPrefix();
selectionStart = CharArrayUtil.shiftForward(document.getCharsSequence(), selectionStart, " \t\n");
selectionEnd = CharArrayUtil.shiftBackward(document.getCharsSequence(), selectionEnd - 1, " \t\n") + 1;
if (selectionEnd < selectionStart) {
selectionEnd = selectionStart;
}
if (CharArrayUtil.regionMatches(document.getCharsSequence(), selectionEnd - commentSuffix.length(), commentSuffix) &&
CharArrayUtil.regionMatches(document.getCharsSequence(), selectionStart, commentPrefix)) {
return new TextRange(selectionStart, selectionEnd);
}
return null;
}
public String getBlockCommentPrefix(int selectionStart, @NotNull Document document, @NotNull MyCommenterData data) {
return data.getBlockCommentPrefix();
}
public String getBlockCommentSuffix(int selectionEnd, @NotNull Document document, @NotNull MyCommenterData data) {
return data.getBlockCommentSuffix();
}
public void uncommentBlockComment(int startOffset, int endOffset, Document document, MyCommenterData data) {
String commentSuffix = data.getBlockCommentSuffix();
String commentPrefix = data.getBlockCommentPrefix();
int startBlockLine = document.getLineNumber(startOffset);
int endBlockLine = document.getLineNumber(endOffset);
if (document.getCharsSequence().subSequence(document.getLineStartOffset(startBlockLine),
document.getLineEndOffset(startBlockLine)).toString()
.matches("\\s*\\Q" + commentPrefix + "\\E\\s*")) {
if (document.getCharsSequence().subSequence(document.getLineStartOffset(endBlockLine),
document.getLineEndOffset(endBlockLine)).toString()
.matches("\\s*\\Q" + commentSuffix + "\\E\\s*")) {
document.deleteString(document.getLineStartOffset(endBlockLine), document.getLineEndOffset(endBlockLine) + 1);
document.deleteString(document.getLineStartOffset(startBlockLine), document.getLineEndOffset(startBlockLine) + 1);
return;
}
}
document.deleteString(endOffset - commentSuffix.length(), endOffset);
document.deleteString(startOffset, startOffset + commentPrefix.length());
}
@NotNull
public TextRange insertBlockComment(int startOffset, int endOffset, Document document, MyCommenterData data) {
int startLineNumber = document.getLineNumber(startOffset);
int startLineStart = document.getLineStartOffset(startLineNumber);
if (startLineStart == startOffset) {
int endLineNumber = document.getLineNumber(endOffset);
int endLineStart = document.getLineStartOffset(endLineNumber);
if (endLineStart == endOffset) {
String commentStart = data.getBlockCommentPrefix() + "\n";
String commentEnd = data.getBlockCommentSuffix() + "\n";
document.insertString(endOffset, commentEnd);
document.insertString(startOffset, commentStart);
return new TextRange(startOffset, endOffset + commentStart.length() + commentEnd.length());
}
}
document.insertString(endOffset, data.getBlockCommentSuffix());
document.insertString(startOffset, data.getBlockCommentPrefix());
return new TextRange(startOffset, endOffset + data.getBlockCommentSuffix().length() + data.getBlockCommentPrefix().length());
}
static class MyCommenterData extends CommenterDataHolder {
private boolean myIsWithinCfscript;
private int myStartOffset;
private MyCommenterData(boolean isWithinCfscript, int startOffset) {
myIsWithinCfscript = isWithinCfscript;
myStartOffset = startOffset;
}
public boolean isIsWithinCfscript() {
return myIsWithinCfscript;
}
public String getLineCommentPrefix() {
if (myIsWithinCfscript) {
return CfmlCommenter.CF_SCRIPT_LINE_COMMENT_PREFIX;
}
return CfmlCommenter.CFML_COMMENT_PREFIX;
}
public String getBlockCommentSuffix() {
if (myIsWithinCfscript) {
return CfmlCommenter.CF_SCRIPT_BLOCK_COMMENT_SUFFIX;
}
return CfmlCommenter.CFML_COMMENT_SUFFIX;
}
public String getBlockCommentPrefix() {
if (myIsWithinCfscript) {
return CfmlCommenter.CF_SCRIPT_BLOCK_COMMENT_PREFIX;
}
return CfmlCommenter.CFML_COMMENT_PREFIX;
}
public int getStartOffset() {
return myStartOffset;
}
}
}