WEB-20321 HTML code style — attribute value quotation

This commit is contained in:
Rustam Vishnyakov
2016-05-16 14:17:58 +03:00
parent 44b790b600
commit 97511d7099
7 changed files with 206 additions and 11 deletions

View File

@@ -403,6 +403,7 @@ public class CodeStyleSettings extends CommonCodeStyleSettings implements Clonea
"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;
// ---------------------------------------------------------------------------------------

View File

@@ -90,6 +90,7 @@ checkbox.parentheses.around.method.arguments=Add parentheses around method argum
checkbox.rename.local.variables.inplace=Enable in-place mode
checkbox.rename.local.variables.preselect=Preselect old name
generated.quote.marks=Generated quote marks:
generated.quote.enforce.format=Enforce on format
editbox.keep.blank.lines=Keep blank lines:
checkbox.keep.white.spaces=Keep white spaces
checkbox.align.text=Align text

View File

@@ -234,6 +234,7 @@
<lang.whiteSpaceFormattingStrategy language="XML"
implementationClass="com.intellij.lang.xml.XmlWhiteSpaceFormattingStrategy"/>
<lang.formatter language="HTML" implementationClass="com.intellij.lang.html.HtmlFormattingModelBuilder"/>
<preFormatProcessor implementation="com.intellij.lang.html.HtmlQuotesFormatPreprocessor"/>
<lang.formatter language="XHTML" implementationClass="com.intellij.lang.xhtml.XhtmlFormattingModelBuilder"/>
<lang.lineWrapStrategy language="XML" implementationClass="com.intellij.psi.formatter.MarkupLineWrapPositionStrategy"/>
<lang.lineWrapStrategy language="HTML" implementationClass="com.intellij.psi.formatter.MarkupLineWrapPositionStrategy"/>

View File

@@ -12,12 +12,12 @@
</head>
<body class="resharperbg">
<div id="container">
<div id=container>
<div id="top">
<div id="logo"><a href="../index.html"><img src="../img/logo_bw.gif" width="124" height="44" alt="JetBrains home"/></a></div>
<div id='top'>
<div id='logo'><a href="../index.html"><img src="../img/logo_bw.gif" width="124" height="44" alt="JetBrains home"/></a></div>
<div id="nav">
<div id=nav>
<ul id="topnav">
<li class="home"><a href="../index.html">Home</a></li>
<li class="act"><a href="../products.html">Products</a></li>
@@ -75,7 +75,7 @@
<div id="quote">
<p class="text">&quot;If you are a C# developer you simply owe it to yourself to get this tool. It has
dramatically made my life better (SERIOUSLY!). I put it up there with amazing tools<6C>&quot;</p>
dramatically made my life better (SERIOUSLY!). I put it up there with amazing tools<6C>&quot;</p>
<p class="author"><a target="" href="http://agiledamon.blogspot.com/2004/09/my-latest-net-developers-best-friend.html">Damon Wilder Carr</a>, <br/>Chief Technologist and CEO,<br/> Agilerfactor</p>
</div>
<p class="spacer"></p>
@@ -121,7 +121,7 @@
<p>Refactoring can significantly improve your code design and efficiency. ReSharper's automated refactoring
support takes care of code consistency and compilability after even the most dramatic modifications.</p>
<p>ReSharper supports the following types of refactoring:</p>
<p><a href="features/refactoring.html">More about this feature<72></a></p>
<p><a href="features/refactoring.html">More about this feature<72></a></p>
</div>
<p>To get the full story on ReSharper&#146;s feature set, please visit the <a href="features/index.html">Features</a> page.</p>
@@ -131,7 +131,7 @@
handwork, giving you more time to focus on the task at hand. Its robust set of features for automatic error-checking
and code correction cuts development time and increases your efficiency. You'll find that ReSharper quickly
pays back it's cost in increased developer productivity and improved code quality.</p>
<p>The wait is over<65> ReSharper is here and now C# developers can experience what we mean when we say
<p>The wait is over<65> ReSharper is here and now C# developers can experience what we mean when we say
"Develop with pleasure!" <a href="download">Download a copy today</a>!</p>
</div>

View File

@@ -3,7 +3,7 @@
<grid id="97358" binding="myPanel" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="4" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="4" y="8" width="828" height="588"/>
<xy x="4" y="8" width="828" height="803"/>
</constraints>
<properties/>
<border type="none"/>
@@ -40,7 +40,7 @@
<properties/>
<border type="none"/>
<children>
<grid id="ca5bb" layout-manager="GridLayoutManager" row-count="8" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<grid id="ca5bb" layout-manager="GridLayoutManager" row-count="9" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<grid row="3" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
@@ -194,6 +194,14 @@
</constraints>
<properties/>
</component>
<component id="bd61d" class="com.intellij.ui.components.JBCheckBox" binding="myEnforceQuotesBox">
<constraints>
<grid row="8" column="1" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text resource-bundle="messages/ApplicationBundle" key="generated.quote.enforce.format"/>
</properties>
</component>
</children>
</grid>
<grid id="b8acc" layout-manager="GridLayoutManager" row-count="1" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">

View File

@@ -29,6 +29,7 @@ import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.ui.EnumComboBoxModel;
import com.intellij.ui.components.JBCheckBox;
import com.intellij.ui.components.JBScrollPane;
import com.intellij.util.ArrayUtil;
import com.intellij.util.PlatformIcons;
@@ -68,6 +69,7 @@ public class CodeStyleHtmlPanel extends CodeStyleAbstractPanel {
private JBScrollPane myJBScrollPane;
private JPanel myRightMarginPanel;
private JComboBox myQuotesCombo;
private JBCheckBox myEnforceQuotesBox;
private RightMarginForm myRightMarginForm;
public CodeStyleHtmlPanel(CodeStyleSettings settings) {
@@ -91,7 +93,14 @@ public class CodeStyleHtmlPanel extends CodeStyleAbstractPanel {
myInlineElementsTagNames.getTextField().setColumns(5);
myDontBreakIfInlineContent.getTextField().setColumns(5);
myQuotesCombo.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
boolean quotesRequired = !CodeStyleSettings.QuoteStyle.None.equals(myQuotesCombo.getSelectedItem());
myEnforceQuotesBox.setEnabled(quotesRequired);
if (!quotesRequired) myEnforceQuotesBox.setSelected(false);
}
});
addPanelToWatch(myPanel);
}
@@ -166,6 +175,7 @@ public class CodeStyleHtmlPanel extends CodeStyleAbstractPanel {
settings.HTML_KEEP_LINE_BREAKS = myShouldKeepBlankLines.isSelected();
settings.HTML_KEEP_LINE_BREAKS_IN_TEXT = myShouldKeepLineBreaksInText.isSelected();
settings.HTML_QUOTE_STYLE = (CodeStyleSettings.QuoteStyle)myQuotesCombo.getSelectedItem();
settings.HTML_ENFORCE_QUOTES = myEnforceQuotesBox.isSelected();
myRightMarginForm.apply(settings);
}
@@ -206,6 +216,7 @@ public class CodeStyleHtmlPanel extends CodeStyleAbstractPanel {
myKeepWhiteSpacesTagNames.setText(settings.HTML_KEEP_WHITESPACES_INSIDE);
myRightMarginForm.reset(settings);
myQuotesCombo.setSelectedItem(settings.HTML_QUOTE_STYLE);
myEnforceQuotesBox.setSelected(settings.HTML_ENFORCE_QUOTES);
}
@Override
@@ -280,7 +291,8 @@ public class CodeStyleHtmlPanel extends CodeStyleAbstractPanel {
return true;
}
return myRightMarginForm.isModified(settings);
return myRightMarginForm.isModified(settings) ||
myEnforceQuotesBox.isSelected() != settings.HTML_ENFORCE_QUOTES;
}
@Override

View File

@@ -0,0 +1,172 @@
/*
* 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.html;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.XmlRecursiveElementVisitor;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.intellij.psi.impl.source.codeStyle.PreFormatProcessor;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.xml.XmlAttributeValue;
import com.intellij.psi.xml.XmlTokenType;
import com.intellij.util.DocumentUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class HtmlQuotesFormatPreprocessor implements PreFormatProcessor {
@NotNull
@Override
public TextRange process(@NotNull ASTNode node, @NotNull TextRange range) {
PsiElement psiElement = node.getPsi();
if (psiElement != null &&
psiElement.isValid() &&
psiElement.getLanguage().is(HTMLLanguage.INSTANCE)) {
CodeStyleSettings settings = CodeStyleSettingsManager.getSettings(psiElement.getProject());
CodeStyleSettings.QuoteStyle quoteStyle = settings.HTML_QUOTE_STYLE;
if (quoteStyle != CodeStyleSettings.QuoteStyle.None && settings.HTML_ENFORCE_QUOTES) {
HtmlQuotesConverter converter = new HtmlQuotesConverter(quoteStyle, psiElement, range);
Document document = converter.getDocument();
if (document != null) {
DocumentUtil.executeInBulk(document, true, converter);
}
return converter.getTextRange();
}
}
return range;
}
private static class HtmlQuotesConverter extends XmlRecursiveElementVisitor implements Runnable {
private TextRange myTextRange;
private final Document myDocument;
private final PsiDocumentManager myDocumentManager;
private final PsiElement myElement;
private int myDelta = 0;
private final char myQuoteChar;
private HtmlQuotesConverter(CodeStyleSettings.QuoteStyle style,
@NotNull PsiElement element,
@NotNull TextRange textRange) {
Project project = element.getProject();
PsiFile file = element.getContainingFile();
myElement = element;
myTextRange = new TextRange(textRange.getStartOffset(), textRange.getEndOffset());
myDocumentManager = PsiDocumentManager.getInstance(project);
myDocument = myDocumentManager.getDocument(file);
switch (style) {
case Single:
myQuoteChar = '\'';
break;
case Double:
myQuoteChar = '"';
break;
default:
myQuoteChar = 0;
}
}
public TextRange getTextRange() {
return myTextRange.grown(myDelta);
}
public Document getDocument() {
return myDocument;
}
@Override
public void visitXmlAttributeValue(XmlAttributeValue value) {
if (myTextRange.contains(value.getTextRange())) {
PsiElement child = value.getFirstChild();
if (child != null &&
!containsQuoteChars(value) // For now we skip values containing quotes to be inserted/replaced
) {
String newValue = null;
if (child.getNode().getElementType() == XmlTokenType.XML_ATTRIBUTE_VALUE_START_DELIMITER) {
PsiElement lastChild = value.getLastChild();
if (lastChild != null && lastChild.getNode().getElementType() == XmlTokenType.XML_ATTRIBUTE_VALUE_END_DELIMITER) {
CharSequence delimiterChars = child.getNode().getChars();
if (delimiterChars.length() == 1) {
char existingQuote = delimiterChars.charAt(0);
if (existingQuote != myQuoteChar) {
newValue = convertQuotes(value);
}
}
}
}
else if (child.getNode().getElementType() == XmlTokenType.XML_ATTRIBUTE_VALUE_TOKEN
&& child == value.getLastChild()) {
newValue = surroundWithQuotes(value);
}
if (newValue != null) {
int startOffset = value.getTextRange().getStartOffset() + myDelta;
int endOffset = value.getTextRange().getEndOffset() + myDelta;
myDocument.replaceString(startOffset, endOffset, newValue);
myDelta += newValue.length() - value.getTextLength();
}
}
}
}
@Nullable
private String convertQuotes(@NotNull XmlAttributeValue value) {
String currValue = value.getNode().getChars().toString();
if (currValue.length() >= 2) {
return myQuoteChar + currValue.substring(1, currValue.length() - 1) + myQuoteChar;
}
return null;
}
@NotNull
private String surroundWithQuotes(@NotNull XmlAttributeValue value) {
String currValue = value.getNode().getChars().toString();
return myQuoteChar + currValue + myQuoteChar;
}
private boolean containsQuoteChars(@NotNull XmlAttributeValue value) {
for (PsiElement child = value.getFirstChild(); child != null; child = child.getNextSibling()) {
if (!isDelimiter(child.getNode().getElementType())) {
CharSequence valueChars = child.getNode().getChars();
for (int i = 0; i < valueChars.length(); i ++) {
if (valueChars.charAt(i) == myQuoteChar) return true;
}
}
}
return false;
}
private static boolean isDelimiter(@NotNull IElementType elementType) {
return elementType == XmlTokenType.XML_ATTRIBUTE_VALUE_START_DELIMITER ||
elementType == XmlTokenType.XML_ATTRIBUTE_VALUE_END_DELIMITER;
}
@Override
public void run() {
if (myDocument != null) {
myDocumentManager.doPostponedOperationsAndUnblockDocument(myDocument);
myElement.accept(this);
myDocumentManager.commitDocument(myDocument);
}
}
}
}