blob: 7b43dfdff4f31e6822fd8c9b6123c94aa3dd1a71 [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.openapi.fileTypes.impl;
import com.intellij.ide.highlighter.FileTypeRegistrator;
import com.intellij.ide.highlighter.custom.SyntaxTable;
import com.intellij.ide.highlighter.custom.impl.CustomFileTypeEditor;
import com.intellij.lang.Commenter;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.fileTypes.*;
import com.intellij.openapi.fileTypes.ex.ExternalizableFileType;
import com.intellij.openapi.options.ExternalInfo;
import com.intellij.openapi.options.ExternalizableScheme;
import com.intellij.openapi.options.SettingsEditor;
import com.intellij.openapi.util.*;
import com.intellij.util.text.StringTokenizer;
import org.jdom.Element;
import org.jdom.output.XMLOutputter;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
public class AbstractFileType extends UserFileType<AbstractFileType> implements ExternalizableFileType, ExternalizableScheme,
CustomSyntaxTableFileType {
private static final String SEMICOLON = ";";
protected SyntaxTable mySyntaxTable;
private SyntaxTable myDefaultSyntaxTable;
protected Commenter myCommenter = null;
@NonNls public static final String ELEMENT_HIGHLIGHTING = "highlighting";
@NonNls private static final String ELEMENT_OPTIONS = "options";
@NonNls private static final String ELEMENT_OPTION = "option";
@NonNls private static final String ATTRIBUTE_VALUE = "value";
@NonNls private static final String VALUE_LINE_COMMENT = "LINE_COMMENT";
@NonNls private static final String VALUE_COMMENT_START = "COMMENT_START";
@NonNls private static final String VALUE_COMMENT_END = "COMMENT_END";
@NonNls private static final String VALUE_HEX_PREFIX = "HEX_PREFIX";
@NonNls private static final String VALUE_NUM_POSTFIXES = "NUM_POSTFIXES";
@NonNls private static final String VALUE_HAS_BRACES = "HAS_BRACES";
@NonNls private static final String VALUE_HAS_BRACKETS = "HAS_BRACKETS";
@NonNls private static final String VALUE_HAS_PARENS = "HAS_PARENS";
@NonNls private static final String VALUE_HAS_STRING_ESCAPES = "HAS_STRING_ESCAPES";
@NonNls private static final String VALUE_LINE_COMMENT_AT_START = "LINE_COMMENT_AT_START";
@NonNls private static final String ELEMENT_KEYWORDS = "keywords";
@NonNls private static final String ATTRIBUTE_IGNORE_CASE = "ignore_case";
@NonNls private static final String ELEMENT_KEYWORD = "keyword";
@NonNls private static final String ELEMENT_KEYWORDS2 = "keywords2";
@NonNls private static final String ELEMENT_KEYWORDS3 = "keywords3";
@NonNls private static final String ELEMENT_KEYWORDS4 = "keywords4";
@NonNls private static final String ATTRIBUTE_NAME = "name";
@NonNls public static final String ELEMENT_EXTENSIONMAP = "extensionMap";
private final ExternalInfo myExternalInfo = new ExternalInfo();
public AbstractFileType(SyntaxTable syntaxTable) {
mySyntaxTable = syntaxTable;
}
public void initSupport() {
for (FileTypeRegistrator registrator : Extensions.getRootArea().getExtensionPoint(FileTypeRegistrator.EP_NAME).getExtensions()) {
registrator.initFileType(this);
}
}
@Override
public SyntaxTable getSyntaxTable() {
return mySyntaxTable;
}
public Commenter getCommenter() {
return myCommenter;
}
public void setSyntaxTable(SyntaxTable syntaxTable) {
mySyntaxTable = syntaxTable;
}
public AbstractFileType clone() {
return (AbstractFileType)super.clone();
}
public void copyFrom(UserFileType newType) {
super.copyFrom(newType);
if (newType instanceof AbstractFileType) {
mySyntaxTable = ((CustomSyntaxTableFileType)newType).getSyntaxTable();
myExternalInfo.copy(((AbstractFileType)newType).myExternalInfo);
}
}
public boolean isBinary() {
return false;
}
public void readExternal(final Element typeElement) throws InvalidDataException {
Element element = typeElement.getChild(ELEMENT_HIGHLIGHTING);
if (element != null) {
SyntaxTable table = readSyntaxTable(element);
if (table != null) {
setSyntaxTable(table);
}
}
}
public static SyntaxTable readSyntaxTable(Element root) {
SyntaxTable table = new SyntaxTable();
for (final Object o : root.getChildren()) {
Element element = (Element)o;
if (ELEMENT_OPTIONS.equals(element.getName())) {
for (final Object o1 : element.getChildren(ELEMENT_OPTION)) {
Element e = (Element)o1;
String name = e.getAttributeValue(ATTRIBUTE_NAME);
String value = e.getAttributeValue(ATTRIBUTE_VALUE);
if (VALUE_LINE_COMMENT.equals(name)) {
table.setLineComment(value);
}
else if (VALUE_COMMENT_START.equals(name)) {
table.setStartComment(value);
}
else if (VALUE_COMMENT_END.equals(name)) {
table.setEndComment(value);
}
else if (VALUE_HEX_PREFIX.equals(name)) {
table.setHexPrefix(value);
}
else if (VALUE_NUM_POSTFIXES.equals(name)) {
table.setNumPostfixChars(value);
}
else if (VALUE_LINE_COMMENT_AT_START.equals(name)) {
table.lineCommentOnlyAtStart = Boolean.valueOf(value).booleanValue();
}
else if (VALUE_HAS_BRACES.equals(name)) {
table.setHasBraces(Boolean.valueOf(value).booleanValue());
}
else if (VALUE_HAS_BRACKETS.equals(name)) {
table.setHasBrackets(Boolean.valueOf(value).booleanValue());
}
else if (VALUE_HAS_PARENS.equals(name)) {
table.setHasParens(Boolean.valueOf(value).booleanValue());
} else if (VALUE_HAS_STRING_ESCAPES.equals(name)) {
table.setHasStringEscapes(Boolean.valueOf(value).booleanValue());
}
}
}
else if (ELEMENT_KEYWORDS.equals(element.getName())) {
boolean ignoreCase = Boolean.valueOf(element.getAttributeValue(ATTRIBUTE_IGNORE_CASE)).booleanValue();
table.setIgnoreCase(ignoreCase);
loadKeywords(element, table.getKeywords1());
}
else if (ELEMENT_KEYWORDS2.equals(element.getName())) {
loadKeywords(element, table.getKeywords2());
}
else if (ELEMENT_KEYWORDS3.equals(element.getName())) {
loadKeywords(element, table.getKeywords3());
}
else if (ELEMENT_KEYWORDS4.equals(element.getName())) {
loadKeywords(element, table.getKeywords4());
}
}
boolean DUMP_TABLE = false;
if (DUMP_TABLE) {
Element element = new Element("temp");
writeTable(element, table);
XMLOutputter outputter = JDOMUtil.createOutputter("\n");
try {
outputter.output((Element)element.getContent().get(0), System.out);
} catch (IOException ex) {}
}
return table;
}
private static void loadKeywords(Element element, Set<String> keywords) {
String value = element.getAttributeValue(ELEMENT_KEYWORDS);
if (value != null) {
StringTokenizer tokenizer = new StringTokenizer(value, SEMICOLON);
while(tokenizer.hasMoreElements()) {
String keyword = tokenizer.nextToken().trim();
if (keyword.length() != 0) keywords.add(keyword);
}
}
for (final Object o1 : element.getChildren(ELEMENT_KEYWORD)) {
keywords.add(((Element)o1).getAttributeValue(ATTRIBUTE_NAME));
}
}
public void writeExternal(final Element element) throws WriteExternalException {
SyntaxTable table = getSyntaxTable();
writeTable(element, table);
}
private static void writeTable(Element element, SyntaxTable table) {
Element highlightingElement = new Element(ELEMENT_HIGHLIGHTING);
Element optionsElement = new Element(ELEMENT_OPTIONS);
Element lineComment = new Element(ELEMENT_OPTION);
lineComment.setAttribute(ATTRIBUTE_NAME, VALUE_LINE_COMMENT);
lineComment.setAttribute(ATTRIBUTE_VALUE, table.getLineComment());
optionsElement.addContent(lineComment);
String commentStart = table.getStartComment();
if (commentStart != null) {
Element commentStartElement = new Element(ELEMENT_OPTION);
commentStartElement.setAttribute(ATTRIBUTE_NAME, VALUE_COMMENT_START);
commentStartElement.setAttribute(ATTRIBUTE_VALUE, commentStart);
optionsElement.addContent(commentStartElement);
}
String endComment = table.getEndComment();
if (endComment != null) {
Element commentEndElement = new Element(ELEMENT_OPTION);
commentEndElement.setAttribute(ATTRIBUTE_NAME, VALUE_COMMENT_END);
commentEndElement.setAttribute(ATTRIBUTE_VALUE, endComment);
optionsElement.addContent(commentEndElement);
}
String prefix = table.getHexPrefix();
if (prefix != null) {
Element hexPrefix = new Element(ELEMENT_OPTION);
hexPrefix.setAttribute(ATTRIBUTE_NAME, VALUE_HEX_PREFIX);
hexPrefix.setAttribute(ATTRIBUTE_VALUE, prefix);
optionsElement.addContent(hexPrefix);
}
String chars = table.getNumPostfixChars();
if (chars != null) {
Element numPostfixes = new Element(ELEMENT_OPTION);
numPostfixes.setAttribute(ATTRIBUTE_NAME, VALUE_NUM_POSTFIXES);
numPostfixes.setAttribute(ATTRIBUTE_VALUE, chars);
optionsElement.addContent(numPostfixes);
}
addElementOption(optionsElement, VALUE_HAS_BRACES, table.isHasBraces());
addElementOption(optionsElement, VALUE_HAS_BRACKETS, table.isHasBrackets());
addElementOption(optionsElement, VALUE_HAS_PARENS, table.isHasParens());
addElementOption(optionsElement, VALUE_HAS_STRING_ESCAPES, table.isHasStringEscapes());
addElementOption(optionsElement, VALUE_LINE_COMMENT_AT_START, table.lineCommentOnlyAtStart);
highlightingElement.addContent(optionsElement);
writeKeywords(table.getKeywords1(), ELEMENT_KEYWORDS, highlightingElement).setAttribute(ATTRIBUTE_IGNORE_CASE, String.valueOf(table.isIgnoreCase()));
writeKeywords(table.getKeywords2(), ELEMENT_KEYWORDS2, highlightingElement);
writeKeywords(table.getKeywords3(), ELEMENT_KEYWORDS3, highlightingElement);
writeKeywords(table.getKeywords4(), ELEMENT_KEYWORDS4, highlightingElement);
element.addContent(highlightingElement);
}
private static void addElementOption(final Element optionsElement, final String valueHasParens, final boolean hasParens) {
if (!hasParens) return;
Element supportParens = new Element(ELEMENT_OPTION);
supportParens.setAttribute(ATTRIBUTE_NAME, valueHasParens);
supportParens.setAttribute(ATTRIBUTE_VALUE, String.valueOf(hasParens));
optionsElement.addContent(supportParens);
}
private static Element writeKeywords(Set<String> keywords, String tagName, Element highlightingElement) {
if (keywords.size() == 0 && !ELEMENT_KEYWORDS.equals(tagName)) return null;
Element keywordsElement = new Element(tagName);
String[] strings = keywords.toArray(new String[keywords.size()]);
Arrays.sort(strings);
StringBuilder keywordsAttribute = new StringBuilder();
for (final String keyword : strings) {
if (keyword.indexOf(SEMICOLON) == -1) {
if (keywordsAttribute.length() != 0) keywordsAttribute.append(SEMICOLON);
keywordsAttribute.append(keyword);
} else {
Element e = new Element(ELEMENT_KEYWORD);
e.setAttribute(ATTRIBUTE_NAME, keyword);
keywordsElement.addContent(e);
}
}
if (keywordsAttribute.length() != 0) {
keywordsElement.setAttribute(ELEMENT_KEYWORDS, keywordsAttribute.toString());
}
highlightingElement.addContent(keywordsElement);
return keywordsElement;
}
public void markDefaultSettings() {
myDefaultSyntaxTable = mySyntaxTable;
}
public boolean isModified() {
return !Comparing.equal(myDefaultSyntaxTable, getSyntaxTable());
}
@NonNls private static final String ELEMENT_MAPPING = "mapping";
@NonNls private static final String ATTRIBUTE_EXT = "ext";
@NonNls private static final String ATTRIBUTE_PATTERN = "pattern";
/** Applied for removed mappings approved by user */
@NonNls private static final String ATTRIBUTE_APPROVED = "approved";
@NonNls private static final String ELEMENT_REMOVED_MAPPING = "removed_mapping";
@NonNls private static final String ATTRIBUTE_TYPE = "type";
@NotNull
public static List<Pair<FileNameMatcher, String>> readAssociations(@NotNull Element e) {
ArrayList<Pair<FileNameMatcher, String>> result = new ArrayList<Pair<FileNameMatcher, String>>();
List mappings = e.getChildren(ELEMENT_MAPPING);
for (Object mapping1 : mappings) {
Element mapping = (Element)mapping1;
String ext = mapping.getAttributeValue(ATTRIBUTE_EXT);
String pattern = mapping.getAttributeValue(ATTRIBUTE_PATTERN);
FileNameMatcher matcher = ext != null ? new ExtensionFileNameMatcher(ext) : FileTypeManager.parseFromString(pattern);
result.add(Pair.create(matcher, mapping.getAttributeValue(ATTRIBUTE_TYPE)));
}
return result;
}
@NotNull
public static List<Trinity<FileNameMatcher, String, Boolean>> readRemovedAssociations(@NotNull Element e) {
ArrayList<Trinity<FileNameMatcher, String, Boolean>> result = new ArrayList<Trinity<FileNameMatcher, String, Boolean>>();
List removedMappings = e.getChildren(ELEMENT_REMOVED_MAPPING);
for (Object removedMapping : removedMappings) {
Element mapping = (Element)removedMapping;
String ext = mapping.getAttributeValue(ATTRIBUTE_EXT);
String pattern = mapping.getAttributeValue(ATTRIBUTE_PATTERN);
String approved = mapping.getAttributeValue(ATTRIBUTE_APPROVED);
FileNameMatcher matcher = ext != null ? new ExtensionFileNameMatcher(ext) : FileTypeManager.parseFromString(pattern);
result.add(new Trinity<FileNameMatcher, String, Boolean>(matcher, mapping.getAttributeValue(ATTRIBUTE_TYPE), Boolean.parseBoolean(approved)));
}
return result;
}
public static Element writeMapping(@NotNull FileType type, final FileNameMatcher matcher, boolean specifyTypeName) {
Element mapping = new Element(ELEMENT_MAPPING);
if (matcher instanceof ExtensionFileNameMatcher) {
mapping.setAttribute(ATTRIBUTE_EXT, ((ExtensionFileNameMatcher)matcher).getExtension());
}
else if (matcher instanceof WildcardFileNameMatcher) {
mapping.setAttribute(ATTRIBUTE_PATTERN, ((WildcardFileNameMatcher)matcher).getPattern());
}
else if (matcher instanceof ExactFileNameMatcher) {
mapping.setAttribute(ATTRIBUTE_PATTERN, ((ExactFileNameMatcher)matcher).getFileName());
}
else {
return null;
}
if (specifyTypeName) {
mapping.setAttribute(ATTRIBUTE_TYPE, type.getName());
}
return mapping;
}
static Element writeRemovedMapping(final FileType type, final FileNameMatcher matcher, final boolean specifyTypeName, boolean approved) {
Element mapping = new Element(ELEMENT_REMOVED_MAPPING);
if (matcher instanceof ExtensionFileNameMatcher) {
mapping.setAttribute(ATTRIBUTE_EXT, ((ExtensionFileNameMatcher)matcher).getExtension());
if (approved) {
mapping.setAttribute(ATTRIBUTE_APPROVED, "true");
}
}
else if (matcher instanceof WildcardFileNameMatcher) {
mapping.setAttribute(ATTRIBUTE_PATTERN, ((WildcardFileNameMatcher)matcher).getPattern());
}
else if (matcher instanceof ExactFileNameMatcher) {
mapping.setAttribute(ATTRIBUTE_PATTERN, ((ExactFileNameMatcher)matcher).getFileName());
}
else {
return null;
}
if (specifyTypeName) {
mapping.setAttribute(ATTRIBUTE_TYPE, type.getName());
}
return mapping;
}
public SettingsEditor<AbstractFileType> getEditor() {
return new CustomFileTypeEditor();
}
public void setCommenter(final Commenter commenter) {
myCommenter = commenter;
}
@NotNull
public ExternalInfo getExternalInfo() {
return myExternalInfo;
}
}