Properties files: code formatting is introduced

This commit is contained in:
Dmitry Batkovich
2016-02-01 11:02:47 +03:00
parent c0f8150025
commit 6dd7c4bcbe
12 changed files with 376 additions and 104 deletions
@@ -56,7 +56,7 @@ public class PropertiesElementFactory {
@Nullable Project project,
boolean escape) {
if (delimiter == null) {
delimiter = project == null ? PropertiesCodeStyleSettings.DEFAULT_KEY_VALUE_DELIMITER : PropertiesCodeStyleSettings.getInstance(project).KEY_VALUE_DELIMITER;
delimiter = project == null ? '=' : PropertiesCodeStyleSettings.getInstance(project).getDelimiter();
}
return (escape ? escape(name) : name) + String.valueOf(delimiter) + (escape ? escapeValue(value) : value);
}
@@ -17,15 +17,17 @@ package com.intellij.lang.properties.psi.codeStyle;
import com.intellij.lang.properties.PropertiesLanguage;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.InvalidDataException;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.intellij.psi.codeStyle.CustomCodeStyleSettings;
import org.jdom.Element;
/**
* @author Dmitry Batkovich
*/
public class PropertiesCodeStyleSettings extends CustomCodeStyleSettings {
public final static char DEFAULT_KEY_VALUE_DELIMITER = '=';
public final static char[] DELIMITERS = new char[]{'=', ':', ' '};
public PropertiesCodeStyleSettings(CodeStyleSettings container) {
super(PropertiesLanguage.INSTANCE.getID(), container);
@@ -35,5 +37,41 @@ public class PropertiesCodeStyleSettings extends CustomCodeStyleSettings {
return CodeStyleSettingsManager.getSettings(project).getCustomSettings(PropertiesCodeStyleSettings.class);
}
public char KEY_VALUE_DELIMITER = DEFAULT_KEY_VALUE_DELIMITER;
public boolean SPACES_AROUND_KEY_VALUE_DELIMITER = false;
public int KEY_VALUE_DELIMITER_CODE = 0;
public char getDelimiter() {
return DELIMITERS[KEY_VALUE_DELIMITER_CODE];
}
@Override
public void readExternal(Element parentElement) throws InvalidDataException {
super.readExternal(parentElement);
parentElement = parentElement.getChild(getTagName());
if (parentElement != null) {
Character delimiter = null;
for (final Object o : parentElement.getChildren("option")) {
Element e = (Element)o;
String fieldName = e.getAttributeValue("name");
if ("KEY_VALUE_DELIMITER".equals(fieldName)) {
final String value = e.getAttributeValue("value");
delimiter = value.charAt(0);
break;
}
}
if (delimiter != null) {
switch (delimiter) {
case '=':
KEY_VALUE_DELIMITER_CODE = 0;
break;
case ':':
KEY_VALUE_DELIMITER_CODE = 1;
break;
case ' ':
KEY_VALUE_DELIMITER_CODE = 2;
break;
}
}
}
}
}
@@ -1,37 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.intellij.lang.properties.psi.codeStyle.PropertiesCodeStyleSettingsPanel">
<grid id="27dc6" binding="myPanel" layout-manager="GridLayoutManager" row-count="2" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="10" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="500" height="400"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="e8f52" class="com.intellij.ui.components.JBLabel">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Key/value delimiter:"/>
</properties>
</component>
<vspacer id="3237e">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
<component id="35c5d" class="com.intellij.openapi.ui.ComboBox" binding="myDelimiterCombo">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="0" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
</component>
<hspacer id="6d3f9">
<constraints>
<grid row="0" column="2" row-span="1" col-span="1" vsize-policy="1" hsize-policy="6" anchor="0" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
</hspacer>
</children>
</grid>
</form>
@@ -15,63 +15,48 @@
*/
package com.intellij.lang.properties.psi.codeStyle;
import com.intellij.application.options.CodeStyleAbstractPanel;
import com.intellij.application.options.codeStyle.OptionTableWithPreviewPanel;
import com.intellij.openapi.editor.colors.EditorColorsScheme;
import com.intellij.openapi.editor.highlighter.EditorHighlighter;
import com.intellij.openapi.editor.highlighter.EditorHighlighterFactory;
import com.intellij.openapi.fileTypes.FileType;
import com.intellij.openapi.fileTypes.StdFileTypes;
import com.intellij.openapi.options.ConfigurationException;
import com.intellij.openapi.ui.ComboBox;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.LanguageCodeStyleSettingsProvider;
import com.intellij.testFramework.LightVirtualFile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
/**
* @author Dmitry Batkovich
*/
public class PropertiesCodeStyleSettingsPanel extends CodeStyleAbstractPanel {
private final static String WHITESPACE_ELEMENT = "Whitespace symbol";
private ComboBox myDelimiterCombo;
private JPanel myPanel;
public class PropertiesCodeStyleSettingsPanel extends OptionTableWithPreviewPanel {
public PropertiesCodeStyleSettingsPanel(CodeStyleSettings settings) {
super(settings);
final DefaultComboBoxModel model = new DefaultComboBoxModel();
model.addElement(':');
model.addElement('=');
model.addElement(WHITESPACE_ELEMENT);
myDelimiterCombo.setModel(model);
selectChar(settings.getCustomSettings(PropertiesCodeStyleSettings.class));
}
private void selectChar(PropertiesCodeStyleSettings settings) {
myDelimiterCombo.setSelectedItem(settings.KEY_VALUE_DELIMITER == ' ' ? WHITESPACE_ELEMENT : settings.KEY_VALUE_DELIMITER);
}
private char getSelectedChar() {
final Object item = myDelimiterCombo.getModel().getSelectedItem();
if (item instanceof Character) {
return (Character)item;
}
assert item == WHITESPACE_ELEMENT;
return ' ';
}
private void createUIComponents() {
init();
}
@Override
protected int getRightMargin() {
return 0;
public LanguageCodeStyleSettingsProvider.SettingsType getSettingsType() {
return LanguageCodeStyleSettingsProvider.SettingsType.BLANK_LINES_SETTINGS;
}
@Override
protected void initTables() {
addOption("ALIGN_GROUP_FIELD_DECLARATIONS", "Align properties in column");
showStandardOptions("SPACE_AROUND_ASSIGNMENT_OPERATORS", "ALIGN_GROUP_FIELD_DECLARATIONS");
showCustomOption(PropertiesCodeStyleSettings.class, "SPACES_AROUND_KEY_VALUE_DELIMITER",
"Insert space around key-value delimiter", null);
showCustomOption(PropertiesCodeStyleSettings.class,
"KEY_VALUE_DELIMITER_CODE",
"Key-value delimiter", null,
new String[]{"=", ":", "whitespace symbol"}, new int[]{0, 1, 2});
}
@Nullable
@Override
protected EditorHighlighter createHighlighter(EditorColorsScheme scheme) {
return null;
return EditorHighlighterFactory.getInstance().createEditorHighlighter(new LightVirtualFile("p.properties"), scheme, null);
}
@NotNull
@@ -83,29 +68,9 @@ public class PropertiesCodeStyleSettingsPanel extends CodeStyleAbstractPanel {
@Nullable
@Override
protected String getPreviewText() {
return null;
}
@Override
public void apply(CodeStyleSettings settings) throws ConfigurationException {
final PropertiesCodeStyleSettings propertiesCodeStyleSettings = settings.getCustomSettings(PropertiesCodeStyleSettings.class);
propertiesCodeStyleSettings.KEY_VALUE_DELIMITER = getSelectedChar();
}
@Override
public boolean isModified(CodeStyleSettings settings) {
final PropertiesCodeStyleSettings propertiesCodeStyleSettings = settings.getCustomSettings(PropertiesCodeStyleSettings.class);
return propertiesCodeStyleSettings.KEY_VALUE_DELIMITER != getSelectedChar();
}
@Nullable
@Override
public JComponent getPanel() {
return myPanel;
}
@Override
protected void resetImpl(CodeStyleSettings settings) {
selectChar(settings.getCustomSettings(PropertiesCodeStyleSettings.class));
return "key1=value\n" +
"some_key=some_value\n" +
"#commentaries\n" +
"last.key=some text here";
}
}
@@ -39,7 +39,7 @@ public class WrongPropertyKeyValueDelimiterInspection extends PropertySuppressab
return PsiElementVisitor.EMPTY_VISITOR;
}
final PropertiesCodeStyleSettings codeStyleSettings = PropertiesCodeStyleSettings.getInstance(holder.getProject());
final char codeStyleKeyValueDelimiter = codeStyleSettings.KEY_VALUE_DELIMITER;
final char codeStyleKeyValueDelimiter = codeStyleSettings.getDelimiter();
return new PsiElementVisitor() {
@Override
public void visitElement(PsiElement element) {
@@ -129,7 +129,7 @@ public class AlphaUnsortedPropertiesFileInspection extends LocalInspectionTool {
return Comparing.compare(p1.getKey(), p2.getKey(), String.CASE_INSENSITIVE_ORDER);
}
});
final char delimiter = PropertiesCodeStyleSettings.getInstance(file.getProject()).KEY_VALUE_DELIMITER;
final char delimiter = PropertiesCodeStyleSettings.getInstance(file.getProject()).getDelimiter();
final StringBuilder rawText = new StringBuilder();
for (int i = 0; i < properties.size(); i++) {
IProperty property = properties.get(i);
@@ -112,6 +112,7 @@
<internalFileTemplate name="XML Properties File.xml"/>
<refactoring.copyHandler implementation="com.intellij.lang.properties.editor.PropertiesCopyHandler"/>
<lang.formatter language="Properties" implementationClass="com.intellij.lang.properties.formatting.PropertiesFormattingModelBuilder"/>
</extensions>
<project-components>
@@ -0,0 +1,85 @@
/*
* Copyright 2000-2016 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.lang.properties.formatting;
import com.intellij.formatting.*;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.formatter.FormattingDocumentModelImpl;
import com.intellij.psi.impl.source.SourceTreeToPsiMap;
import com.intellij.psi.impl.source.tree.TreeElement;
import com.intellij.psi.impl.source.tree.TreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* @author Dmitry Batkovich
*/
public class PropertiesFormattingModelBuilder implements FormattingModelBuilder {
@NotNull
@Override
public PropertiesFormattingModel createModel(PsiElement element, CodeStyleSettings settings) {
final ASTNode root = TreeUtil.getFileElement((TreeElement)SourceTreeToPsiMap.psiElementToTree(element));
final FormattingDocumentModelImpl documentModel = FormattingDocumentModelImpl.createOn(element.getContainingFile());
return new PropertiesFormattingModel(root, documentModel, settings);
}
@Nullable
@Override
public TextRange getRangeAffectingIndent(PsiFile file, int offset, ASTNode elementAtOffset) {
return null;
}
private static class PropertiesFormattingModel implements FormattingModel {
private final FormattingDocumentModelImpl myDocumentModel;
private PropertiesRootBlock myRoot;
public PropertiesFormattingModel(ASTNode root, FormattingDocumentModelImpl documentModel, CodeStyleSettings settings) {
myRoot = new PropertiesRootBlock(root, null, settings);
myDocumentModel = documentModel;
}
@NotNull
@Override
public Block getRootBlock() {
return myRoot;
}
@NotNull
@Override
public FormattingDocumentModel getDocumentModel() {
return myDocumentModel;
}
@Override
public TextRange replaceWhiteSpace(TextRange textRange, String whiteSpace) {
return textRange;
}
@Override
public TextRange shiftIndentInsideRange(ASTNode node, TextRange range, int indent) {
return null;
}
@Override
public void commitChanges() {
}
}
}
@@ -0,0 +1,109 @@
/*
* Copyright 2000-2016 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.lang.properties.formatting;
import com.intellij.formatting.Alignment;
import com.intellij.formatting.Block;
import com.intellij.formatting.Spacing;
import com.intellij.formatting.Wrap;
import com.intellij.lang.ASTNode;
import com.intellij.lang.properties.parsing.PropertiesTokenTypes;
import com.intellij.lang.properties.parsing.PropertyListStubElementType;
import com.intellij.lang.properties.parsing.PropertyStubElementType;
import com.intellij.lang.properties.psi.codeStyle.PropertiesCodeStyleSettings;
import com.intellij.lang.properties.psi.impl.PropertyKeyImpl;
import com.intellij.lang.properties.psi.impl.PropertyValueImpl;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.formatter.common.AbstractBlock;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
/**
* @author Dmitry Batkovich
*/
public class PropertiesRootBlock extends AbstractBlock {
private final CodeStyleSettings mySettings;
private Alignment mySeparatorAlignment;
protected PropertiesRootBlock(@NotNull ASTNode node,
@Nullable Wrap wrap, CodeStyleSettings settings) {
super(node, wrap, Alignment.createAlignment());
mySettings = settings;
mySeparatorAlignment = Alignment.createAlignment(true, Alignment.Anchor.LEFT);
}
@Override
protected List<Block> buildChildren() {
final List<Block> result = new ArrayList<>();
ASTNode child = myNode.getFirstChildNode();
while (child != null) {
if (!(child instanceof PsiWhiteSpace)) {
if (child.getElementType() instanceof PropertyListStubElementType) {
ASTNode propertyNode = child.getFirstChildNode();
while (propertyNode != null) {
if (propertyNode.getElementType() instanceof PropertyStubElementType) {
collectPropertyBlock(propertyNode, result);
}
else if (PropertiesTokenTypes.END_OF_LINE_COMMENT.equals(propertyNode.getElementType())) {
result.add(new PropertyBlock(propertyNode, null));
}
propertyNode = propertyNode.getTreeNext();
}
break;
}
}
if (PropertiesTokenTypes.END_OF_LINE_COMMENT.equals(child.getElementType())) {
result.add(new PropertyBlock(child, null));
}
child = child.getTreeNext();
}
return result;
}
private void collectPropertyBlock(ASTNode propertyNode, List<Block> collector) {
final ASTNode key = propertyNode.getFirstChildNode();
if (key instanceof PropertyKeyImpl) {
collector.add(new PropertyBlock(key, null));
final ASTNode separator = key.getTreeNext();
if (separator != null && PropertiesTokenTypes.KEY_VALUE_SEPARATOR.equals(separator.getElementType())) {
collector.add(new SeparatorBlock(separator, mySettings.ALIGN_GROUP_FIELD_DECLARATIONS ? mySeparatorAlignment : null));
final ASTNode value = separator.getTreeNext();
if (value instanceof PropertyValueImpl) {
collector.add(new PropertyBlock(value, null));
}
}
}
}
@Nullable
@Override
public Spacing getSpacing(@Nullable Block child1, @NotNull Block child2) {
return mySettings.getCustomSettings(PropertiesCodeStyleSettings.class).SPACES_AROUND_KEY_VALUE_DELIMITER &&
(child1 instanceof SeparatorBlock || child2 instanceof SeparatorBlock)
? Spacing.createSpacing(1, 1, 0, true, 0)
: Spacing.createSpacing(0, 0, 0, true, 0);
}
@Override
public boolean isLeaf() {
return false;
}
}
@@ -0,0 +1,56 @@
/*
* Copyright 2000-2016 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.lang.properties.formatting;
import com.intellij.formatting.*;
import com.intellij.lang.ASTNode;
import com.intellij.psi.formatter.common.AbstractBlock;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
/**
* @author Dmitry Batkovich
*/
public class PropertyBlock extends AbstractBlock {
protected PropertyBlock(@NotNull ASTNode node,
@Nullable Alignment alignment) {
super(node, null, alignment);
}
@Override
protected List<Block> buildChildren() {
return Collections.emptyList();
}
@Nullable
@Override
public Spacing getSpacing(@Nullable Block child1, @NotNull Block child2) {
return null;
}
@Override
public boolean isLeaf() {
return true;
}
@Override
public Indent getIndent() {
return Indent.getAbsoluteNoneIndent();
}
}
@@ -0,0 +1,55 @@
/*
* Copyright 2000-2016 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.lang.properties.formatting;
import com.intellij.formatting.Alignment;
import com.intellij.formatting.Block;
import com.intellij.formatting.Indent;
import com.intellij.formatting.Spacing;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collections;
import java.util.List;
/**
* @author Dmitry Batkovich
*/
public class SeparatorBlock extends PropertyBlock {
private final static Logger LOG = Logger.getInstance(SeparatorBlock.class);
protected SeparatorBlock(@NotNull ASTNode node,
@Nullable Alignment alignment) {
super(node, alignment);
}
@NotNull
@Override
public TextRange getTextRange() {
final String nodeText = myNode.getText();
int separatorLocalOffset = StringUtil.indexOfAny(nodeText, "=:");
if (separatorLocalOffset == -1 && !nodeText.isEmpty() && nodeText.charAt(0) == ' ') {
separatorLocalOffset = 0;
}
LOG.assertTrue(separatorLocalOffset > -1, "Invalid separator \"" + myNode.getText() + "\'");
final int separatorOffset = myNode.getStartOffset() + separatorLocalOffset;
return new TextRange(separatorOffset, separatorOffset + 1);
}
}
@@ -182,12 +182,12 @@ public class PropertiesFileTest extends LightPlatformCodeInsightFixtureTestCase
public void testNonDefaultKeyValueDelimiter() {
final PropertiesCodeStyleSettings codeStyleSettings = PropertiesCodeStyleSettings.getInstance(getProject());
codeStyleSettings.KEY_VALUE_DELIMITER = ':';
codeStyleSettings.KEY_VALUE_DELIMITER_CODE = 1;
final PropertyImpl property = (PropertyImpl)PropertiesElementFactory.createProperty(getProject(), "xxx", "yyy");
final Character delimiter = property.getKeyValueDelimiter();
assertNotNull(delimiter);
assertEquals(':', (char)delimiter);
assertEquals("xxx:yyy", property.getPsiElement().getText());
codeStyleSettings.KEY_VALUE_DELIMITER = PropertiesCodeStyleSettings.DEFAULT_KEY_VALUE_DELIMITER;
codeStyleSettings.KEY_VALUE_DELIMITER_CODE = 0;
}
}