/* * Copyright 2000-2017 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.psi.codeStyle; import com.intellij.configurationStore.UnknownElementCollector; import com.intellij.configurationStore.UnknownElementWriter; import com.intellij.lang.Language; import com.intellij.lang.LanguageUtil; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.extensions.ExtensionException; import com.intellij.openapi.extensions.Extensions; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.fileTypes.FileTypeManager; import com.intellij.openapi.fileTypes.FileTypes; import com.intellij.openapi.fileTypes.LanguageFileType; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.*; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; import com.intellij.util.Processor; import com.intellij.util.ReflectionUtil; import com.intellij.util.SystemProperties; import com.intellij.util.containers.ClassMap; import org.jdom.Element; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; import javax.swing.*; import java.util.*; import java.util.regex.Pattern; import java.util.regex.PatternSyntaxException; public class CodeStyleSettings extends CommonCodeStyleSettings implements Cloneable, JDOMExternalizable, ImportsLayoutSettings { public static final int MAX_RIGHT_MARGIN = 1000; private static final Logger LOG = Logger.getInstance(CodeStyleSettings.class); private final ClassMap<CustomCodeStyleSettings> myCustomSettings = new ClassMap<>(); @NonNls private static final String REPEAT_ANNOTATIONS = "REPEAT_ANNOTATIONS"; @NonNls private static final String ADDITIONAL_INDENT_OPTIONS = "ADDITIONAL_INDENT_OPTIONS"; @NonNls private static final String FILETYPE = "fileType"; private CommonCodeStyleSettingsManager myCommonSettingsManager = new CommonCodeStyleSettingsManager(this); private static CodeStyleSettings myDefaults; private UnknownElementWriter myUnknownElementWriter = UnknownElementWriter.EMPTY; public CodeStyleSettings() { this(true); } public CodeStyleSettings(boolean loadExtensions) { super(null); initTypeToName(); initImportsByDefault(); if (loadExtensions) { final CodeStyleSettingsProvider[] codeStyleSettingsProviders = Extensions.getExtensions(CodeStyleSettingsProvider.EXTENSION_POINT_NAME); for (final CodeStyleSettingsProvider provider : codeStyleSettingsProviders) { addCustomSettings(provider.createCustomSettings(this)); } } } private void initImportsByDefault() { PACKAGES_TO_USE_IMPORT_ON_DEMAND.addEntry(new PackageEntry(false, "java.awt", false)); PACKAGES_TO_USE_IMPORT_ON_DEMAND.addEntry(new PackageEntry(false,"javax.swing", false)); IMPORT_LAYOUT_TABLE.addEntry(PackageEntry.ALL_OTHER_IMPORTS_ENTRY); IMPORT_LAYOUT_TABLE.addEntry(PackageEntry.BLANK_LINE_ENTRY); IMPORT_LAYOUT_TABLE.addEntry(new PackageEntry(false, "javax", true)); IMPORT_LAYOUT_TABLE.addEntry(new PackageEntry(false, "java", true)); IMPORT_LAYOUT_TABLE.addEntry(PackageEntry.BLANK_LINE_ENTRY); IMPORT_LAYOUT_TABLE.addEntry(PackageEntry.ALL_OTHER_STATIC_IMPORTS_ENTRY); } private void initTypeToName() { initGeneralLocalVariable(PARAMETER_TYPE_TO_NAME); initGeneralLocalVariable(LOCAL_VARIABLE_TYPE_TO_NAME); PARAMETER_TYPE_TO_NAME.addPair("*Exception", "e"); } private static void initGeneralLocalVariable(@NonNls TypeToNameMap map) { map.addPair("int", "i"); map.addPair("byte", "b"); map.addPair("char", "c"); map.addPair("long", "l"); map.addPair("short", "i"); map.addPair("boolean", "b"); map.addPair("double", "v"); map.addPair("float", "v"); map.addPair("java.lang.Object", "o"); map.addPair("java.lang.String", "s"); } public void setParentSettings(CodeStyleSettings parent) { myParentSettings = parent; } public CodeStyleSettings getParentSettings() { return myParentSettings; } private void addCustomSettings(CustomCodeStyleSettings settings) { if (settings != null) { synchronized (myCustomSettings) { myCustomSettings.put(settings.getClass(), settings); } } } public <T extends CustomCodeStyleSettings> T getCustomSettings(@NotNull Class<T> aClass) { synchronized (myCustomSettings) { //noinspection unchecked return (T)myCustomSettings.get(aClass); } } @Override public CodeStyleSettings clone() { CodeStyleSettings clone = new CodeStyleSettings(); clone.copyFrom(this); return clone; } private void copyCustomSettingsFrom(@NotNull CodeStyleSettings from) { synchronized (myCustomSettings) { myCustomSettings.clear(); for (final CustomCodeStyleSettings settings : from.getCustomSettingsValues()) { addCustomSettings((CustomCodeStyleSettings)settings.clone()); } FIELD_TYPE_TO_NAME.copyFrom(from.FIELD_TYPE_TO_NAME); STATIC_FIELD_TYPE_TO_NAME.copyFrom(from.STATIC_FIELD_TYPE_TO_NAME); PARAMETER_TYPE_TO_NAME.copyFrom(from.PARAMETER_TYPE_TO_NAME); LOCAL_VARIABLE_TYPE_TO_NAME.copyFrom(from.LOCAL_VARIABLE_TYPE_TO_NAME); PACKAGES_TO_USE_IMPORT_ON_DEMAND.copyFrom(from.PACKAGES_TO_USE_IMPORT_ON_DEMAND); IMPORT_LAYOUT_TABLE.copyFrom(from.IMPORT_LAYOUT_TABLE); OTHER_INDENT_OPTIONS.copyFrom(from.OTHER_INDENT_OPTIONS); myAdditionalIndentOptions.clear(); for (Map.Entry<FileType, IndentOptions> optionEntry : from.myAdditionalIndentOptions.entrySet()) { IndentOptions options = optionEntry.getValue(); myAdditionalIndentOptions.put(optionEntry.getKey(), (IndentOptions)options.clone()); } myCommonSettingsManager = from.myCommonSettingsManager.clone(this); } } public void copyFrom(CodeStyleSettings from) { copyPublicFields(from, this); copyCustomSettingsFrom(from); } public boolean USE_SAME_INDENTS; public boolean IGNORE_SAME_INDENTS_FOR_LANGUAGES; public boolean AUTODETECT_INDENTS = true; @Deprecated public final IndentOptions JAVA_INDENT_OPTIONS = new IndentOptions(); @Deprecated public final IndentOptions JSP_INDENT_OPTIONS = new IndentOptions(); @Deprecated public final IndentOptions XML_INDENT_OPTIONS = new IndentOptions(); public final IndentOptions OTHER_INDENT_OPTIONS = new IndentOptions(); private final Map<FileType,IndentOptions> myAdditionalIndentOptions = new LinkedHashMap<>(); private static final String ourSystemLineSeparator = SystemProperties.getLineSeparator(); /** * Line separator. It can be null if choosen line separator is "System-dependent"! */ public String LINE_SEPARATOR; /** * @return line separator. If choosen line separator is "System-dependent" method returns default separator for this OS. */ public String getLineSeparator() { return LINE_SEPARATOR != null ? LINE_SEPARATOR : ourSystemLineSeparator; } // region Java settings (legacy) //----------------- NAMING CONVENTIONS -------------------- public String FIELD_NAME_PREFIX = ""; public String STATIC_FIELD_NAME_PREFIX = ""; public String PARAMETER_NAME_PREFIX = ""; public String LOCAL_VARIABLE_NAME_PREFIX = ""; public String FIELD_NAME_SUFFIX = ""; public String STATIC_FIELD_NAME_SUFFIX = ""; public String PARAMETER_NAME_SUFFIX = ""; public String LOCAL_VARIABLE_NAME_SUFFIX = ""; public boolean PREFER_LONGER_NAMES = true; public final TypeToNameMap FIELD_TYPE_TO_NAME = new TypeToNameMap(); public final TypeToNameMap STATIC_FIELD_TYPE_TO_NAME = new TypeToNameMap(); @NonNls public final TypeToNameMap PARAMETER_TYPE_TO_NAME = new TypeToNameMap(); public final TypeToNameMap LOCAL_VARIABLE_TYPE_TO_NAME = new TypeToNameMap(); //----------------- 'final' modifier settings ------- public boolean GENERATE_FINAL_LOCALS; public boolean GENERATE_FINAL_PARAMETERS; //----------------- visibility ----------------------------- public String VISIBILITY = "public"; //----------------- generate parentheses around method arguments ---------- public boolean PARENTHESES_AROUND_METHOD_ARGUMENTS = true; //----------------- annotations ---------------- public boolean USE_EXTERNAL_ANNOTATIONS; public boolean INSERT_OVERRIDE_ANNOTATION = true; //----------------- override ------------------- public boolean REPEAT_SYNCHRONIZED = true; private List<String> myRepeatAnnotations = new ArrayList<>(); public List<String> getRepeatAnnotations() { return myRepeatAnnotations; } public void setRepeatAnnotations(List<String> repeatAnnotations) { myRepeatAnnotations.clear(); myRepeatAnnotations.addAll(repeatAnnotations); } //----------------- FUNCTIONAL EXPRESSIONS ----- public boolean REPLACE_INSTANCEOF = false; public boolean REPLACE_CAST = false; public boolean REPLACE_NULL_CHECK = true; //----------------- IMPORTS -------------------- public boolean LAYOUT_STATIC_IMPORTS_SEPARATELY = true; public boolean USE_FQ_CLASS_NAMES; /** @deprecated use com.intellij.psi.codeStyle.JavaCodeStyleSettings.CLASS_NAMES_IN_JAVADOC */ @Deprecated public boolean USE_FQ_CLASS_NAMES_IN_JAVADOC = true; public boolean USE_SINGLE_CLASS_IMPORTS = true; public boolean INSERT_INNER_CLASS_IMPORTS; public int CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND = 5; public int NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND = 3; public final PackageEntryTable PACKAGES_TO_USE_IMPORT_ON_DEMAND = new PackageEntryTable(); public final PackageEntryTable IMPORT_LAYOUT_TABLE = new PackageEntryTable(); @Override public boolean isLayoutStaticImportsSeparately() { return LAYOUT_STATIC_IMPORTS_SEPARATELY; } @Override public void setLayoutStaticImportsSeparately(boolean value) { LAYOUT_STATIC_IMPORTS_SEPARATELY = value; } @Override public int getNamesCountToUseImportOnDemand() { return NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND; } @Override public void setNamesCountToUseImportOnDemand(int value) { NAMES_COUNT_TO_USE_IMPORT_ON_DEMAND = value; } @Override public int getClassCountToUseImportOnDemand() { return CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND; } @Override public void setClassCountToUseImportOnDemand(int value) { CLASS_COUNT_TO_USE_IMPORT_ON_DEMAND = value; } @Override public boolean isInsertInnerClassImports() { return INSERT_INNER_CLASS_IMPORTS; } @Override public void setInsertInnerClassImports(boolean value) { INSERT_INNER_CLASS_IMPORTS = value; } @Override public boolean isUseSingleClassImports() { return USE_SINGLE_CLASS_IMPORTS; } @Override public void setUseSingleClassImports(boolean value) { USE_SINGLE_CLASS_IMPORTS = value; } @Override public boolean isUseFqClassNames() { return USE_FQ_CLASS_NAMES; } @Override public void setUseFqClassNames(boolean value) { USE_FQ_CLASS_NAMES = value; } @Override public PackageEntryTable getImportLayoutTable() { return IMPORT_LAYOUT_TABLE; } @Override public PackageEntryTable getPackagesToUseImportOnDemand() { return PACKAGES_TO_USE_IMPORT_ON_DEMAND; } // endregion // region ORDER OF MEMBERS public int STATIC_FIELDS_ORDER_WEIGHT = 1; public int FIELDS_ORDER_WEIGHT = 2; public int CONSTRUCTORS_ORDER_WEIGHT = 3; public int STATIC_METHODS_ORDER_WEIGHT = 4; public int METHODS_ORDER_WEIGHT = 5; public int STATIC_INNER_CLASSES_ORDER_WEIGHT = 6; public int INNER_CLASSES_ORDER_WEIGHT = 7; // endregion // region WRAPPING /** * @deprecated Use get/setRightMargin() methods instead. */ @Deprecated public int RIGHT_MARGIN = 120; /** * <b>Do not use this field directly since it doesn't reflect a setting for a specific language which may * overwrite this one. Call {@link #isWrapOnTyping(Language)} method instead.</b> * * @see #WRAP_ON_TYPING */ public boolean WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN; // endregion // region Javadoc formatting options public boolean ENABLE_JAVADOC_FORMATTING = true; /** * Align parameter comments to longest parameter name */ public boolean JD_ALIGN_PARAM_COMMENTS = true; /** * Align exception comments to longest exception name */ public boolean JD_ALIGN_EXCEPTION_COMMENTS = true; public boolean JD_ADD_BLANK_AFTER_PARM_COMMENTS; public boolean JD_ADD_BLANK_AFTER_RETURN; public boolean JD_ADD_BLANK_AFTER_DESCRIPTION = true; public boolean JD_P_AT_EMPTY_LINES = true; public boolean JD_KEEP_INVALID_TAGS = true; public boolean JD_KEEP_EMPTY_LINES = true; public boolean JD_DO_NOT_WRAP_ONE_LINE_COMMENTS; public boolean JD_USE_THROWS_NOT_EXCEPTION = true; public boolean JD_KEEP_EMPTY_PARAMETER = true; public boolean JD_KEEP_EMPTY_EXCEPTION = true; public boolean JD_KEEP_EMPTY_RETURN = true; public boolean JD_LEADING_ASTERISKS_ARE_ENABLED = true; public boolean JD_PRESERVE_LINE_FEEDS; public boolean JD_PARAM_DESCRIPTION_ON_NEW_LINE; // endregion // region Legacy(!) XML formatting options /** * @deprecated Use XmlCodeStyleSettings. */ public boolean XML_KEEP_WHITESPACES; /** * @deprecated Use XmlCodeStyleSettings. */ public int XML_ATTRIBUTE_WRAP = WRAP_AS_NEEDED; /** * @deprecated Use XmlCodeStyleSettings. */ public int XML_TEXT_WRAP = WRAP_AS_NEEDED; /** * @deprecated Use XmlCodeStyleSettings. */ public boolean XML_KEEP_LINE_BREAKS = true; /** * @deprecated Use XmlCodeStyleSettings. */ public boolean XML_KEEP_LINE_BREAKS_IN_TEXT = true; /** * @deprecated Use XmlCodeStyleSettings. */ public int XML_KEEP_BLANK_LINES = 2; /** * @deprecated Use XmlCodeStyleSettings. */ public boolean XML_ALIGN_ATTRIBUTES = true; /** * @deprecated Use XmlCodeStyleSettings. */ public boolean XML_ALIGN_TEXT; /** * @deprecated Use XmlCodeStyleSettings. */ public boolean XML_SPACE_AROUND_EQUALITY_IN_ATTRIBUTE; /** * @deprecated Use XmlCodeStyleSettings. */ public boolean XML_SPACE_AFTER_TAG_NAME; /** * @deprecated Use XmlCodeStyleSettings. */ public boolean XML_SPACE_INSIDE_EMPTY_TAG; /** * @deprecated Use XmlCodeStyleSettings. */ public boolean XML_KEEP_WHITE_SPACES_INSIDE_CDATA; /** * @deprecated Use XmlCodeStyleSettings. */ public int XML_WHITE_SPACE_AROUND_CDATA; // endregion // region HTML formatting options (legacy) public boolean HTML_KEEP_WHITESPACES; public int HTML_ATTRIBUTE_WRAP = WRAP_AS_NEEDED; public int HTML_TEXT_WRAP = WRAP_AS_NEEDED; public boolean HTML_KEEP_LINE_BREAKS = true; public boolean HTML_KEEP_LINE_BREAKS_IN_TEXT = true; public int HTML_KEEP_BLANK_LINES = 2; public boolean HTML_ALIGN_ATTRIBUTES = true; public boolean HTML_ALIGN_TEXT; public boolean HTML_SPACE_AROUND_EQUALITY_IN_ATTRINUTE; public boolean HTML_SPACE_AFTER_TAG_NAME; public boolean HTML_SPACE_INSIDE_EMPTY_TAG; @NonNls public String HTML_ELEMENTS_TO_INSERT_NEW_LINE_BEFORE = "body,div,p,form,h1,h2,h3"; @NonNls public String HTML_ELEMENTS_TO_REMOVE_NEW_LINE_BEFORE = "br"; @NonNls public String HTML_DO_NOT_INDENT_CHILDREN_OF = "html,body,thead,tbody,tfoot"; public int HTML_DO_NOT_ALIGN_CHILDREN_OF_MIN_LINES; @NonNls public String HTML_KEEP_WHITESPACES_INSIDE = "span,pre,textarea"; @NonNls public String HTML_INLINE_ELEMENTS = "a,abbr,acronym,b,basefont,bdo,big,br,cite,cite,code,dfn,em,font,i,img,input,kbd,label,q,s,samp,select,span,strike,strong,sub,sup,textarea,tt,u,var"; @NonNls public String HTML_DONT_ADD_BREAKS_IF_INLINE_CONTENT = "title,h1,h2,h3,h4,h5,h6,p"; public QuoteStyle HTML_QUOTE_STYLE = QuoteStyle.Double; public boolean HTML_ENFORCE_QUOTES = false; public HtmlTagNewLineStyle HTML_NEWLINE_BEFORE_FIRST_ATTRIBUTE = HtmlTagNewLineStyle.Never; public HtmlTagNewLineStyle HTML_NEWLINE_AFTER_LAST_ATTRIBUTE = HtmlTagNewLineStyle.Never; // endregion // true if <%page import="x.y.z, x.y.t"%> // false if <%page import="x.y.z"%> // <%page import="x.y.t"%> public boolean JSP_PREFER_COMMA_SEPARATED_IMPORT_LIST; //---------------------------------------------------------------------------------------- // region Formatter control public boolean FORMATTER_TAGS_ENABLED; public String FORMATTER_ON_TAG = "@formatter:on"; public String FORMATTER_OFF_TAG = "@formatter:off"; public volatile boolean FORMATTER_TAGS_ACCEPT_REGEXP; private volatile Pattern myFormatterOffPattern; private volatile Pattern myFormatterOnPattern; @Nullable public Pattern getFormatterOffPattern() { if (myFormatterOffPattern == null && FORMATTER_TAGS_ENABLED && FORMATTER_TAGS_ACCEPT_REGEXP) { myFormatterOffPattern = getPatternOrDisableRegexp(FORMATTER_OFF_TAG); } return myFormatterOffPattern; } public void setFormatterOffPattern(@Nullable Pattern formatterOffPattern) { myFormatterOffPattern = formatterOffPattern; } @Nullable public Pattern getFormatterOnPattern() { if (myFormatterOffPattern == null && FORMATTER_TAGS_ENABLED && FORMATTER_TAGS_ACCEPT_REGEXP) { myFormatterOnPattern = getPatternOrDisableRegexp(FORMATTER_ON_TAG); } return myFormatterOnPattern; } public void setFormatterOnPattern(@Nullable Pattern formatterOnPattern) { myFormatterOnPattern = formatterOnPattern; } @Nullable private Pattern getPatternOrDisableRegexp(@NotNull String markerText) { try { return Pattern.compile(markerText); } catch (PatternSyntaxException pse) { LOG.error("Loaded regexp pattern is invalid: '" + markerText + "', error message: " + pse.getMessage()); FORMATTER_TAGS_ACCEPT_REGEXP = false; return null; } } // endregion //---------------------------------------------------------------------------------------- private CodeStyleSettings myParentSettings; private boolean myLoadedAdditionalIndentOptions; @NotNull private Collection<CustomCodeStyleSettings> getCustomSettingsValues() { synchronized (myCustomSettings) { return Collections.unmodifiableCollection(myCustomSettings.values()); } } @Override public void readExternal(Element element) throws InvalidDataException { DefaultJDOMExternalizer.readExternal(this, element); if (LAYOUT_STATIC_IMPORTS_SEPARATELY) { // add <all other static imports> entry if there is none boolean found = false; for (PackageEntry entry : IMPORT_LAYOUT_TABLE.getEntries()) { if (entry == PackageEntry.ALL_OTHER_STATIC_IMPORTS_ENTRY) { found = true; break; } } if (!found) { PackageEntry last = IMPORT_LAYOUT_TABLE.getEntryCount() == 0 ? null : IMPORT_LAYOUT_TABLE.getEntryAt(IMPORT_LAYOUT_TABLE.getEntryCount() - 1); if (last != PackageEntry.BLANK_LINE_ENTRY) { IMPORT_LAYOUT_TABLE.addEntry(PackageEntry.BLANK_LINE_ENTRY); } IMPORT_LAYOUT_TABLE.addEntry(PackageEntry.ALL_OTHER_STATIC_IMPORTS_ENTRY); } } myRepeatAnnotations.clear(); Element annotations = element.getChild(REPEAT_ANNOTATIONS); if (annotations != null) { for (Element anno : annotations.getChildren("ANNO")) { myRepeatAnnotations.add(anno.getAttributeValue("name")); } } UnknownElementCollector unknownElementCollector = new UnknownElementCollector(); for (CustomCodeStyleSettings settings : getCustomSettingsValues()) { settings.getKnownTagNames().forEach(unknownElementCollector::addKnownName); settings.readExternal(element); } unknownElementCollector.addKnownName(ADDITIONAL_INDENT_OPTIONS); List<Element> list = element.getChildren(ADDITIONAL_INDENT_OPTIONS); for (Element additionalIndentElement : list) { String fileTypeId = additionalIndentElement.getAttributeValue(FILETYPE); if (!StringUtil.isEmpty(fileTypeId)) { FileType target = FileTypeManager.getInstance().getFileTypeByExtension(fileTypeId); if (FileTypes.UNKNOWN == target || FileTypes.PLAIN_TEXT == target || target.getDefaultExtension().isEmpty()) { target = new TempFileType(fileTypeId); } IndentOptions options = getDefaultIndentOptions(target); options.readExternal(additionalIndentElement); registerAdditionalIndentOptions(target, options); } } unknownElementCollector.addKnownName(CommonCodeStyleSettingsManager.COMMON_SETTINGS_TAG); myCommonSettingsManager.readExternal(element); myUnknownElementWriter = unknownElementCollector.createWriter(element); if (USE_SAME_INDENTS) { IGNORE_SAME_INDENTS_FOR_LANGUAGES = true; } } @Override public void writeExternal(Element element) throws WriteExternalException { CodeStyleSettings parentSettings = new CodeStyleSettings(); DefaultJDOMExternalizer.writeExternal(this, element, new DifferenceFilter<>(this, parentSettings)); myUnknownElementWriter.write(element, getCustomSettingsValues(), CustomCodeStyleSettings::getTagName, settings -> { CustomCodeStyleSettings parentCustomSettings = parentSettings.getCustomSettings(settings.getClass()); if (parentCustomSettings == null) { throw new WriteExternalException("Custom settings are null for " + settings.getClass()); } settings.writeExternal(element, parentCustomSettings); }); if (!myAdditionalIndentOptions.isEmpty()) { FileType[] fileTypes = myAdditionalIndentOptions.keySet().toArray(new FileType[myAdditionalIndentOptions.keySet().size()]); Arrays.sort(fileTypes, Comparator.comparing(FileType::getDefaultExtension)); for (FileType fileType : fileTypes) { Element additionalIndentOptions = new Element(ADDITIONAL_INDENT_OPTIONS); myAdditionalIndentOptions.get(fileType).serialize(additionalIndentOptions, getDefaultIndentOptions(fileType)); additionalIndentOptions.setAttribute(FILETYPE, fileType.getDefaultExtension()); if (!additionalIndentOptions.getChildren().isEmpty()) { element.addContent(additionalIndentOptions); } } } myCommonSettingsManager.writeExternal(element); if (!myRepeatAnnotations.isEmpty()) { Element annos = new Element(REPEAT_ANNOTATIONS); for (String annotation : myRepeatAnnotations) { annos.addContent(new Element("ANNO").setAttribute("name", annotation)); } element.addContent(annos); } } private static IndentOptions getDefaultIndentOptions(FileType fileType) { final FileTypeIndentOptionsProvider[] providers = Extensions.getExtensions(FileTypeIndentOptionsProvider.EP_NAME); for (final FileTypeIndentOptionsProvider provider : providers) { if (provider.getFileType().equals(fileType)) { return getFileTypeIndentOptions(provider); } } return new IndentOptions(); } @Override @Nullable public IndentOptions getIndentOptions() { return OTHER_INDENT_OPTIONS; } /** * If the file type has an associated language and language indent options are defined, returns these options. Otherwise attempts to find * indent options from {@code FileTypeIndentOptionsProvider}. If none are found, other indent options are returned. * @param fileType The file type to search indent options for. * @return File type indent options or {@code OTHER_INDENT_OPTIONS}. * * @see FileTypeIndentOptionsProvider * @see LanguageCodeStyleSettingsProvider */ public IndentOptions getIndentOptions(@Nullable FileType fileType) { IndentOptions indentOptions = getLanguageIndentOptions(fileType); if (indentOptions != null) return indentOptions; if (USE_SAME_INDENTS || fileType == null) return OTHER_INDENT_OPTIONS; if (!myLoadedAdditionalIndentOptions) { loadAdditionalIndentOptions(); } indentOptions = myAdditionalIndentOptions.get(fileType); if (indentOptions != null) return indentOptions; return OTHER_INDENT_OPTIONS; } /** * If the document has an associated PsiFile, returns options for this file. Otherwise attempts to find associated VirtualFile and * return options for corresponding FileType. If none are found, other indent options are returned. * * @param project The project in which PsiFile should be searched. * @param document The document to search indent options for. * @return Indent options from the indent options providers or file type indent options or {@code OTHER_INDENT_OPTIONS}. * @see FileIndentOptionsProvider * @see FileTypeIndentOptionsProvider * @see LanguageCodeStyleSettingsProvider */ @NotNull public IndentOptions getIndentOptionsByDocument(@Nullable Project project, @NotNull Document document) { PsiFile file = project != null ? PsiDocumentManager.getInstance(project).getPsiFile(document) : null; if (file != null) return getIndentOptionsByFile(file); VirtualFile vFile = FileDocumentManager.getInstance().getFile(document); FileType fileType = vFile != null ? vFile.getFileType() : null; return getIndentOptions(fileType); } @NotNull public IndentOptions getIndentOptionsByFile(@Nullable PsiFile file) { return getIndentOptionsByFile(file, null); } @NotNull public IndentOptions getIndentOptionsByFile(@Nullable PsiFile file, @Nullable TextRange formatRange) { return getIndentOptionsByFile(file, formatRange, false, null); } /** * Retrieves indent options for PSI file from an associated document or (if not defined in the document) from file indent options * providers. * @param file The PSI file to retrieve options for. * @param formatRange The text range within the file for formatting purposes or null if there is either no specific range or multiple * ranges. If the range covers the entire file (full reformat), options stored in the document are ignored and * indent options are taken from file indent options providers. * @param ignoreDocOptions Ignore options stored in the document and use file indent options providers even if there is no text range * or the text range doesn't cover the entire file. * @param providerProcessor A callback object containing a reference to indent option provider which has returned indent options. * @return Indent options from the associated document or file indent options providers. * @see FileIndentOptionsProvider */ @NotNull public IndentOptions getIndentOptionsByFile(@Nullable PsiFile file, @Nullable TextRange formatRange, boolean ignoreDocOptions, @Nullable Processor<FileIndentOptionsProvider> providerProcessor) { if (file != null && file.isValid()) { boolean isFullReformat = isFileFullyCoveredByRange(file, formatRange); if (!ignoreDocOptions && !isFullReformat) { IndentOptions options = IndentOptions.retrieveFromAssociatedDocument(file); if (options != null) { FileIndentOptionsProvider provider = options.getFileIndentOptionsProvider(); if (providerProcessor != null && provider != null) { providerProcessor.process(provider); } return options; } } for (FileIndentOptionsProvider provider : Extensions.getExtensions(FileIndentOptionsProvider.EP_NAME)) { if (!isFullReformat || provider.useOnFullReformat()) { IndentOptions indentOptions = provider.getIndentOptions(this, file); if (indentOptions != null) { if (providerProcessor != null) { providerProcessor.process(provider); } indentOptions.setFileIndentOptionsProvider(provider); logIndentOptions(file, provider, indentOptions); return indentOptions; } } } Language language = LanguageUtil.getLanguageForPsi(file.getProject(), file.getVirtualFile()); if (language != null) { IndentOptions options = getIndentOptions(language); if (options != null) { return options; } } return getIndentOptions(file.getFileType()); } else return OTHER_INDENT_OPTIONS; } private static boolean isFileFullyCoveredByRange(@NotNull PsiFile file, @Nullable TextRange formatRange) { return formatRange != null && file.getTextRange().equals(formatRange); } private static void logIndentOptions(@NotNull PsiFile file, @NotNull FileIndentOptionsProvider provider, @NotNull IndentOptions options) { LOG.debug("Indent options returned by " + provider.getClass().getName() + " for " + file.getName() + ": indent size=" + options.INDENT_SIZE + ", use tabs=" + options.USE_TAB_CHARACTER + ", tab size=" + options.TAB_SIZE); } @Nullable private IndentOptions getLanguageIndentOptions(@Nullable FileType fileType) { if (fileType == null || !(fileType instanceof LanguageFileType)) return null; Language lang = ((LanguageFileType)fileType).getLanguage(); return getIndentOptions(lang); } @Nullable private IndentOptions getIndentOptions(Language lang) { CommonCodeStyleSettings langSettings = getCommonSettings(lang); return langSettings == this ? null : langSettings.getIndentOptions(); } public boolean isSmartTabs(FileType fileType) { return getIndentOptions(fileType).SMART_TABS; } public int getIndentSize(FileType fileType) { return getIndentOptions(fileType).INDENT_SIZE; } public int getContinuationIndentSize(FileType fileType) { return getIndentOptions(fileType).CONTINUATION_INDENT_SIZE; } public int getTabSize(FileType fileType) { return getIndentOptions(fileType).TAB_SIZE; } public boolean useTabCharacter(FileType fileType) { return getIndentOptions(fileType).USE_TAB_CHARACTER; } //used in generate equals/hashCode @SuppressWarnings("unused") public boolean isGenerateFinalLocals() { return GENERATE_FINAL_LOCALS; } //used in generate equals/hashCode @SuppressWarnings("unused") public boolean isGenerateFinalParameters() { return GENERATE_FINAL_PARAMETERS; } public static class TypeToNameMap implements JDOMExternalizable { private final List<String> myPatterns = new ArrayList<>(); private final List<String> myNames = new ArrayList<>(); public void addPair(String pattern, String name) { myPatterns.add(pattern); myNames.add(name); } public String nameByType(String type) { for (int i = 0; i < myPatterns.size(); i++) { String pattern = myPatterns.get(i); if (StringUtil.startsWithChar(pattern, '*')) { if (type.endsWith(pattern.substring(1))) { return myNames.get(i); } } else { if (type.equals(pattern)) { return myNames.get(i); } } } return null; } @Override public void readExternal(@NonNls Element element) throws InvalidDataException { myPatterns.clear(); myNames.clear(); for (final Object o : element.getChildren("pair")) { @NonNls Element e = (Element)o; String pattern = e.getAttributeValue("type"); String name = e.getAttributeValue("name"); if (pattern == null || name == null) { throw new InvalidDataException(); } myPatterns.add(pattern); myNames.add(name); } } @Override public void writeExternal(Element parentNode) throws WriteExternalException { for (int i = 0; i < myPatterns.size(); i++) { String pattern = myPatterns.get(i); String name = myNames.get(i); @NonNls Element element = new Element("pair"); parentNode.addContent(element); element.setAttribute("type", pattern); element.setAttribute("name", name); } } public void copyFrom(TypeToNameMap from) { assert from != this; myPatterns.clear(); myPatterns.addAll(from.myPatterns); myNames.clear(); myNames.addAll(from.myNames); } @Override public boolean equals(Object other) { if (other instanceof TypeToNameMap) { TypeToNameMap otherMap = (TypeToNameMap)other; return myPatterns.equals(otherMap.myPatterns) && myNames.equals(otherMap.myNames); } return false; } @Override public int hashCode() { int code = 0; for (String myPattern : myPatterns) { code += myPattern.hashCode(); } for (String myName : myNames) { code += myName.hashCode(); } return code; } } private void registerAdditionalIndentOptions(FileType fileType, IndentOptions options) { boolean exist = false; for (final FileType existing : myAdditionalIndentOptions.keySet()) { if (Comparing.strEqual(existing.getDefaultExtension(), fileType.getDefaultExtension())) { exist = true; break; } } if (!exist) { myAdditionalIndentOptions.put(fileType, options); } } private void loadAdditionalIndentOptions() { synchronized (myAdditionalIndentOptions) { myLoadedAdditionalIndentOptions = true; final FileTypeIndentOptionsProvider[] providers = Extensions.getExtensions(FileTypeIndentOptionsProvider.EP_NAME); for (final FileTypeIndentOptionsProvider provider : providers) { if (!myAdditionalIndentOptions.containsKey(provider.getFileType())) { registerAdditionalIndentOptions(provider.getFileType(), getFileTypeIndentOptions(provider)); } } } } private static IndentOptions getFileTypeIndentOptions(FileTypeIndentOptionsProvider provider) { try { return provider.createIndentOptions(); } catch (AbstractMethodError error) { LOG.error("Plugin uses obsolete API.", new ExtensionException(provider.getClass())); return new IndentOptions(); } } @TestOnly public void clearCodeStyleSettings() { CodeStyleSettings cleanSettings = new CodeStyleSettings(); copyFrom(cleanSettings); myAdditionalIndentOptions.clear(); //hack myLoadedAdditionalIndentOptions = false; } private static class TempFileType implements FileType { private final String myExtension; private TempFileType(@NotNull final String extension) { myExtension = extension; } @Override @NotNull public String getName() { return "TempFileType"; } @Override @NotNull public String getDescription() { return "TempFileType"; } @Override @NotNull public String getDefaultExtension() { return myExtension; } @Override public Icon getIcon() { return null; } @Override public boolean isBinary() { return false; } @Override public boolean isReadOnly() { return false; } @Override public String getCharset(@NotNull VirtualFile file, @NotNull byte[] content) { return null; } } public CommonCodeStyleSettings getCommonSettings(Language lang) { return myCommonSettingsManager.getCommonSettings(lang); } /** * @param langName The language name. * @return Language-specific code style settings or shared settings if not found. * @see CommonCodeStyleSettingsManager#getCommonSettings */ public CommonCodeStyleSettings getCommonSettings(String langName) { return myCommonSettingsManager.getCommonSettings(langName); } /** * Retrieves right margin for the given language. The language may overwrite default RIGHT_MARGIN value with its own RIGHT_MARGIN * in language's CommonCodeStyleSettings instance. * * @param language The language to get right margin for or null if root (default) right margin is requested. * @return The right margin for the language if it is defined (not null) and its settings contain non-negative margin. Root (default) * margin otherwise (CodeStyleSettings.RIGHT_MARGIN). */ public int getRightMargin(@Nullable Language language) { if (language != null) { CommonCodeStyleSettings langSettings = getCommonSettings(language); if (langSettings != null) { if (langSettings.RIGHT_MARGIN >= 0) return langSettings.RIGHT_MARGIN; } } return getDefaultRightMargin(); } /** * Assigns another right margin for the language or (if it is null) to root (default) margin. * * @param language The language to assign the right margin to or null if root (default) right margin is to be changed. * @param rightMargin New right margin. */ public void setRightMargin(@Nullable Language language, int rightMargin) { if (language != null) { CommonCodeStyleSettings langSettings = getCommonSettings(language); if (langSettings != null) { langSettings.RIGHT_MARGIN = rightMargin; return; } } setDefaultRightMargin(rightMargin); } @SuppressWarnings("deprecation") public int getDefaultRightMargin() { return RIGHT_MARGIN; } @SuppressWarnings("deprecation") public void setDefaultRightMargin(int rightMargin) { RIGHT_MARGIN = rightMargin; } /** * Defines whether or not wrapping should occur when typing reaches right margin. * @param language The language to check the option for or null for a global option. * @return True if wrapping on right margin is enabled. */ public boolean isWrapOnTyping(@Nullable Language language) { if (language != null) { CommonCodeStyleSettings langSettings = getCommonSettings(language); if (langSettings != null) { if (langSettings.WRAP_ON_TYPING != WrapOnTyping.DEFAULT.intValue) { return langSettings.WRAP_ON_TYPING == WrapOnTyping.WRAP.intValue; } } } //noinspection deprecation return WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN; } public enum HtmlTagNewLineStyle { Never("Never"), WhenMultiline("When multiline"); public final String description; HtmlTagNewLineStyle(String description) { this.description = description; } @Override public String toString() { return description; } } public enum QuoteStyle { Single("'"), Double("\""), None(""); public final String quote; QuoteStyle(String quote) { this.quote = quote; } } @Override public boolean equals(Object obj) { if (!(obj instanceof CodeStyleSettings)) return false; if (!ReflectionUtil.comparePublicNonFinalFields(this, obj)) return false; if (!myCommonSettingsManager.equals(((CodeStyleSettings)obj).myCommonSettingsManager)) return false; for (CustomCodeStyleSettings customSettings : myCustomSettings.values()) { if (!customSettings.equals(((CodeStyleSettings)obj).getCustomSettings(customSettings.getClass()))) return false; } return true; } public static CodeStyleSettings getDefaults() { if (myDefaults == null) { myDefaults = new CodeStyleSettings(); } return myDefaults; } }