blob: 1d20283aa708415f73ff4d641c16a8e8e788b949 [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.lexer;
import com.intellij.lang.HtmlInlineScriptTokenTypesProvider;
import com.intellij.lang.HtmlScriptContentProvider;
import com.intellij.lang.Language;
import com.intellij.lang.LanguageHtmlInlineScriptTokenTypesProvider;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.PlainTextLanguage;
import com.intellij.openapi.fileTypes.SyntaxHighlighter;
import com.intellij.openapi.fileTypes.SyntaxHighlighterFactory;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.xml.XmlTokenType;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.Map;
public class HtmlHighlightingLexer extends BaseHtmlLexer {
private static final Logger LOG = Logger.getInstance("#com.intellij.lexer.HtmlHighlightingLexer");
private static final int EMBEDDED_LEXER_ON = 0x1 << BASE_STATE_SHIFT;
private static final int EMBEDDED_LEXER_STATE_SHIFT = BASE_STATE_SHIFT + 1;
private static final FileType ourInlineScriptFileType;
static {
// At the moment only JS.
HtmlInlineScriptTokenTypesProvider provider =
LanguageHtmlInlineScriptTokenTypesProvider.getInlineScriptProvider(Language.findLanguageByID("JavaScript"));
ourInlineScriptFileType = provider != null ? provider.getFileType() : null;
}
private final FileType ourStyleFileType;// = FileTypeManager.getInstance().getStdFileType("CSS");
protected Lexer elLexer;
private Lexer embeddedLexer;
private Lexer styleLexer;
private Map<String, Lexer> scriptLexers = new HashMap<String, Lexer>();
private boolean hasNoEmbeddments;
public HtmlHighlightingLexer() {
this(null);
}
public HtmlHighlightingLexer(FileType styleFileType) {
this(new MergingLexerAdapter(new FlexAdapter(new _HtmlLexer()), TOKENS_TO_MERGE), true, styleFileType);
}
protected HtmlHighlightingLexer(Lexer lexer, boolean caseInsensitive, FileType styleFileType) {
super(lexer, caseInsensitive);
ourStyleFileType = styleFileType;
XmlEmbeddmentHandler value = new XmlEmbeddmentHandler();
registerHandler(XmlTokenType.XML_ATTRIBUTE_VALUE_TOKEN, value);
registerHandler(XmlTokenType.XML_DATA_CHARACTERS, value);
registerHandler(XmlTokenType.XML_COMMENT_CHARACTERS, value);
}
@Override
public void start(@NotNull CharSequence buffer, int startOffset, int endOffset, int initialState) {
super.start(buffer, startOffset, endOffset, initialState);
if ((initialState & EMBEDDED_LEXER_ON) != 0) {
int state = initialState >> EMBEDDED_LEXER_STATE_SHIFT;
setEmbeddedLexer();
LOG.assertTrue(embeddedLexer != null);
embeddedLexer.start(buffer, startOffset, skipToTheEndOfTheEmbeddment(), state);
}
else {
embeddedLexer = null;
scriptLexers.clear();
}
}
private void setEmbeddedLexer() {
Lexer newLexer = null;
if (hasSeenStyle()) {
if (styleLexer == null) {
if (ourStyleFileType == null) {
styleLexer = null;
}
else {
SyntaxHighlighter highlighter = SyntaxHighlighterFactory.getSyntaxHighlighter(ourStyleFileType, null, null);
LOG.assertTrue(highlighter != null, ourStyleFileType);
styleLexer = highlighter.getHighlightingLexer();
}
}
newLexer = styleLexer;
}
else if (hasSeenScript()) {
Lexer scriptLexer = scriptLexers.get(scriptType);
if (scriptLexer == null) {
if (hasSeenTag()) {
HtmlScriptContentProvider provider = findScriptContentProvider(scriptType);
if (provider != null) {
scriptLexer = provider.getHighlightingLexer();
}
else {
scriptLexer = SyntaxHighlighterFactory.getSyntaxHighlighter(PlainTextLanguage.INSTANCE, null, null).getHighlightingLexer();
}
}
else if (hasSeenAttribute()) {
SyntaxHighlighter syntaxHighlighter =
ourInlineScriptFileType != null ? SyntaxHighlighterFactory.getSyntaxHighlighter(ourInlineScriptFileType, null, null) : null;
scriptLexer = syntaxHighlighter != null ? syntaxHighlighter.getHighlightingLexer() : null;
}
scriptLexers.put(scriptType, scriptLexer);
}
newLexer = scriptLexer;
}
else {
newLexer = createELLexer(newLexer);
}
if (newLexer != null) {
embeddedLexer = newLexer;
}
}
@Nullable
protected Lexer createELLexer(Lexer newLexer) {
return newLexer;
}
@Override
public void advance() {
if (embeddedLexer != null) {
embeddedLexer.advance();
if (embeddedLexer.getTokenType() == null) {
embeddedLexer = null;
}
}
if (embeddedLexer == null) {
super.advance();
}
}
@Override
public IElementType getTokenType() {
if (embeddedLexer != null) {
return embeddedLexer.getTokenType();
}
else {
IElementType tokenType = super.getTokenType();
// TODO: fix no DOCTYPE highlighting
if (tokenType == null) return tokenType;
if (tokenType == XmlTokenType.XML_NAME) {
// we need to convert single xml_name for tag name and attribute name into to separate
// lex types for the highlighting!
final int state = getState() & BASE_STATE_MASK;
if (isHtmlTagState(state)) {
tokenType = XmlTokenType.XML_TAG_NAME;
}
}
else if (tokenType == XmlTokenType.XML_WHITE_SPACE || tokenType == XmlTokenType.XML_REAL_WHITE_SPACE) {
if (hasSeenTag() && (hasSeenStyle() || hasSeenScript())) {
tokenType = XmlTokenType.XML_WHITE_SPACE;
}
else {
tokenType = getState() != 0 ? XmlTokenType.TAG_WHITE_SPACE : XmlTokenType.XML_REAL_WHITE_SPACE;
}
}
else if (tokenType == XmlTokenType.XML_CHAR_ENTITY_REF ||
tokenType == XmlTokenType.XML_ENTITY_REF_TOKEN
) {
// we need to convert char entity ref & entity ref in comments as comment chars
final int state = getState() & BASE_STATE_MASK;
if (state == _HtmlLexer.COMMENT) return XmlTokenType.XML_COMMENT_CHARACTERS;
}
return tokenType;
}
}
@Override
public int getTokenStart() {
if (embeddedLexer != null) {
return embeddedLexer.getTokenStart();
}
else {
return super.getTokenStart();
}
}
@Override
public int getTokenEnd() {
if (embeddedLexer != null) {
return embeddedLexer.getTokenEnd();
}
else {
return super.getTokenEnd();
}
}
@Override
public int getState() {
int state = super.getState();
state |= embeddedLexer != null ? EMBEDDED_LEXER_ON : 0;
if (embeddedLexer != null) state |= embeddedLexer.getState() << EMBEDDED_LEXER_STATE_SHIFT;
return state;
}
@Override
protected boolean isHtmlTagState(int state) {
return state == _HtmlLexer.START_TAG_NAME || state == _HtmlLexer.END_TAG_NAME ||
state == _HtmlLexer.START_TAG_NAME2 || state == _HtmlLexer.END_TAG_NAME2;
}
public void setHasNoEmbeddments(boolean hasNoEmbeddments) {
this.hasNoEmbeddments = hasNoEmbeddments;
}
public class XmlEmbeddmentHandler implements TokenHandler {
@Override
public void handleElement(Lexer lexer) {
if (!hasSeenStyle() && !hasSeenScript() || hasNoEmbeddments) return;
final IElementType tokenType = lexer.getTokenType();
if (tokenType == XmlTokenType.XML_ATTRIBUTE_VALUE_TOKEN && hasSeenAttribute() ||
tokenType == XmlTokenType.XML_DATA_CHARACTERS && hasSeenTag() ||
tokenType == XmlTokenType.XML_COMMENT_CHARACTERS && hasSeenTag()
) {
setEmbeddedLexer();
if (embeddedLexer != null) {
embeddedLexer.start(
getBufferSequence(),
HtmlHighlightingLexer.super.getTokenStart(),
skipToTheEndOfTheEmbeddment(),
embeddedLexer instanceof EmbedmentLexer ? ((EmbedmentLexer)embeddedLexer).getEmbeddedInitialState(tokenType) : 0
);
if (embeddedLexer.getTokenType() == null) {
// no content for embeddment
embeddedLexer = null;
}
}
}
}
}
public class ElEmbeddmentHandler implements TokenHandler {
@Override
public void handleElement(Lexer lexer) {
setEmbeddedLexer();
if (embeddedLexer != null) {
embeddedLexer.start(getBufferSequence(), HtmlHighlightingLexer.super.getTokenStart(), HtmlHighlightingLexer.super.getTokenEnd());
}
}
}
}