diff --git a/platform/lang-api/src/com/intellij/psi/codeStyle/CodeStyleSettings.java b/platform/lang-api/src/com/intellij/psi/codeStyle/CodeStyleSettings.java index 518fdbe2b5ae..d6472836103a 100644 --- a/platform/lang-api/src/com/intellij/psi/codeStyle/CodeStyleSettings.java +++ b/platform/lang-api/src/com/intellij/psi/codeStyle/CodeStyleSettings.java @@ -402,7 +402,7 @@ public class CodeStyleSettings extends CommonCodeStyleSettings implements Clonea @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; // --------------------------------------------------------------------------------------- @@ -1019,4 +1019,15 @@ public class CodeStyleSettings extends CommonCodeStyleSettings implements Clonea return WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN; } + public enum QuoteStyle { + Single("'"), + Double("\""), + None(""); + + public final String quote; + + QuoteStyle(String quote) { + this.quote = quote; + } + } } diff --git a/platform/platform-resources-en/src/messages/ApplicationBundle.properties b/platform/platform-resources-en/src/messages/ApplicationBundle.properties index 3fec13c55433..0601f5484249 100644 --- a/platform/platform-resources-en/src/messages/ApplicationBundle.properties +++ b/platform/platform-resources-en/src/messages/ApplicationBundle.properties @@ -89,6 +89,7 @@ checkbox.wrap.text=Wrap text checkbox.parentheses.around.method.arguments=Add parentheses around method arguments checkbox.rename.local.variables.inplace=Enable in-place mode checkbox.rename.local.variables.preselect=Preselect old name +generated.quote.marks=Generated quote marks: editbox.keep.blank.lines=Keep blank lines: checkbox.keep.white.spaces=Keep white spaces checkbox.align.text=Align text diff --git a/platform/util/src/com/intellij/util/ReflectionUtil.java b/platform/util/src/com/intellij/util/ReflectionUtil.java index 24bbe450d20d..d5dbcabf2af0 100644 --- a/platform/util/src/com/intellij/util/ReflectionUtil.java +++ b/platform/util/src/com/intellij/util/ReflectionUtil.java @@ -576,7 +576,7 @@ public class ReflectionUtil { public static void copyFieldValue(@NotNull Object from, @NotNull Object to, @NotNull Field field) throws IllegalAccessException { Class fieldType = field.getType(); - if (fieldType.isPrimitive() || fieldType.equals(String.class)) { + if (fieldType.isPrimitive() || fieldType.equals(String.class) || fieldType.isEnum()) { field.set(to, field.get(from)); } else { diff --git a/xml/impl/src/com/intellij/application/options/CodeStyleHtmlPanel.form b/xml/impl/src/com/intellij/application/options/CodeStyleHtmlPanel.form index bf79ab8f021e..8c53c6bd7664 100644 --- a/xml/impl/src/com/intellij/application/options/CodeStyleHtmlPanel.form +++ b/xml/impl/src/com/intellij/application/options/CodeStyleHtmlPanel.form @@ -40,7 +40,7 @@ - + @@ -180,6 +180,20 @@ + + + + + + + + + + + + + + diff --git a/xml/impl/src/com/intellij/application/options/CodeStyleHtmlPanel.java b/xml/impl/src/com/intellij/application/options/CodeStyleHtmlPanel.java index 497305c5ed84..3b971b9cd921 100644 --- a/xml/impl/src/com/intellij/application/options/CodeStyleHtmlPanel.java +++ b/xml/impl/src/com/intellij/application/options/CodeStyleHtmlPanel.java @@ -28,6 +28,7 @@ import com.intellij.openapi.util.text.StringUtil; 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.JBScrollPane; import com.intellij.util.ArrayUtil; import com.intellij.util.PlatformIcons; @@ -66,6 +67,7 @@ public class CodeStyleHtmlPanel extends CodeStyleAbstractPanel { private TextFieldWithBrowseButton myDontBreakIfInlineContent; private JBScrollPane myJBScrollPane; private JPanel myRightMarginPanel; + private JComboBox myQuotesCombo; private RightMarginForm myRightMarginForm; public CodeStyleHtmlPanel(CodeStyleSettings settings) { @@ -73,6 +75,7 @@ public class CodeStyleHtmlPanel extends CodeStyleAbstractPanel { installPreviewPanel(myPreviewPanel); fillWrappingCombo(myWrapAttributes); + fillQuotesCombo(myQuotesCombo); customizeField(ApplicationBundle.message("title.insert.new.line.before.tags"), myInsertNewLineTagNames); customizeField(ApplicationBundle.message("title.remove.line.breaks.before.tags"), myRemoveNewLineTagNames); @@ -162,9 +165,15 @@ public class CodeStyleHtmlPanel extends CodeStyleAbstractPanel { settings.HTML_KEEP_WHITESPACES_INSIDE = myKeepWhiteSpacesTagNames.getText(); settings.HTML_KEEP_LINE_BREAKS = myShouldKeepBlankLines.isSelected(); settings.HTML_KEEP_LINE_BREAKS_IN_TEXT = myShouldKeepLineBreaksInText.isSelected(); + settings.HTML_QUOTE_STYLE = (CodeStyleSettings.QuoteStyle)myQuotesCombo.getSelectedItem(); myRightMarginForm.apply(settings); } + @NotNull + protected String getQuotes() { + return ApplicationBundle.message("single.quotes").equals(myQuotesCombo.getSelectedItem()) ? "'" : "\""; + } + private static int getIntValue(JTextField keepBlankLines) { try { return Integer.parseInt(keepBlankLines.getText()); @@ -196,6 +205,7 @@ public class CodeStyleHtmlPanel extends CodeStyleAbstractPanel { myDontBreakIfInlineContent.setText(settings.HTML_DONT_ADD_BREAKS_IF_INLINE_CONTENT); myKeepWhiteSpacesTagNames.setText(settings.HTML_KEEP_WHITESPACES_INSIDE); myRightMarginForm.reset(settings); + myQuotesCombo.setSelectedItem(settings.HTML_QUOTE_STYLE); } @Override @@ -266,6 +276,10 @@ public class CodeStyleHtmlPanel extends CodeStyleAbstractPanel { return true; } + if (myQuotesCombo.getSelectedItem() != settings.HTML_QUOTE_STYLE) { + return true; + } + return myRightMarginForm.isModified(settings); } @@ -290,4 +304,8 @@ public class CodeStyleHtmlPanel extends CodeStyleAbstractPanel { protected void prepareForReformat(final PsiFile psiFile) { //psiFile.putUserData(PsiUtil.FILE_LANGUAGE_LEVEL_KEY, LanguageLevel.HIGHEST); } + + private static void fillQuotesCombo(JComboBox combo) { + combo.setModel(new EnumComboBoxModel<>(CodeStyleSettings.QuoteStyle.class)); + } } diff --git a/xml/impl/src/com/intellij/codeInsight/completion/XmlAttributeInsertHandler.java b/xml/impl/src/com/intellij/codeInsight/completion/XmlAttributeInsertHandler.java index 4249e2b271e9..38167f930812 100644 --- a/xml/impl/src/com/intellij/codeInsight/completion/XmlAttributeInsertHandler.java +++ b/xml/impl/src/com/intellij/codeInsight/completion/XmlAttributeInsertHandler.java @@ -27,6 +27,7 @@ import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; +import com.intellij.psi.codeStyle.CodeStyleSchemes; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.psi.xml.XmlAttribute; import com.intellij.psi.xml.XmlFile; @@ -34,6 +35,7 @@ import com.intellij.psi.xml.XmlTag; import com.intellij.util.IncorrectOperationException; import com.intellij.util.text.CharArrayUtil; import com.intellij.xml.XmlNamespaceHelper; +import com.intellij.xml.util.HtmlUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -66,14 +68,20 @@ public class XmlAttributeInsertHandler implements InsertHandler { final PsiFile file = context.getFile(); final CharSequence chars = document.getCharsSequence(); - final boolean insertQuotes = WebEditorOptions.getInstance().isInsertQuotesForAttributeValue(); - final boolean hasQuotes = CharArrayUtil.regionMatches(chars, caretOffset, "=\""); - if (!hasQuotes && !CharArrayUtil.regionMatches(chars, caretOffset, "='")) { + final String quote = getAttributeQuote(HtmlUtil.hasHtml(file) || HtmlUtil.supportsXmlTypedHandlers(file)); + final boolean insertQuotes = WebEditorOptions.getInstance().isInsertQuotesForAttributeValue() && StringUtil.isNotEmpty(quote); + final boolean hasQuotes = CharArrayUtil.regionMatches(chars, caretOffset, "=\"") || + CharArrayUtil.regionMatches(chars, caretOffset, "='"); + if (!hasQuotes) { PsiElement fileContext = file.getContext(); - String toInsert= "=\"\""; + String toInsert = null; if(fileContext != null) { if (fileContext.getText().startsWith("\"")) toInsert = "=''"; + if (fileContext.getText().startsWith("\'")) toInsert = "=\"\""; + } + if (toInsert == null) { + toInsert = "=" + quote + quote; } if (!insertQuotes) toInsert = "="; @@ -118,6 +126,10 @@ public class XmlAttributeInsertHandler implements InsertHandler { } } + public static String getAttributeQuote(boolean html) { + return html ? CodeStyleSchemes.getInstance().getCurrentScheme().getCodeStyleSettings().HTML_QUOTE_STYLE.quote : "\""; + } + private static void qualifyWithPrefix(@NotNull String namespacePrefix, @NotNull PsiElement context) { final PsiElement parent = context.getParent(); diff --git a/xml/impl/src/com/intellij/codeInsight/completion/XmlTagInsertHandler.java b/xml/impl/src/com/intellij/codeInsight/completion/XmlTagInsertHandler.java index 9938e39ef720..0658f6e7d4c7 100644 --- a/xml/impl/src/com/intellij/codeInsight/completion/XmlTagInsertHandler.java +++ b/xml/impl/src/com/intellij/codeInsight/completion/XmlTagInsertHandler.java @@ -230,9 +230,9 @@ public class XmlTagInsertHandler implements InsertHandler { if (shouldBeInserted && (tag == null || tag.getAttributeValue(attributeName) == null)) { if (!notRequiredAttributes.contains(attributeName)) { if (!extension.isIndirectSyntax(attributeDecl)) { - template.addTextSegment(" " + attributeName + "=\""); + template.addTextSegment(" " + attributeName + "=" + XmlAttributeInsertHandler.getAttributeQuote(htmlCode)); template.addVariable(new MacroCallNode(new CompleteMacro()), true); - template.addTextSegment("\""); + template.addTextSegment(XmlAttributeInsertHandler.getAttributeQuote(htmlCode)); } else { if (indirectRequiredAttrs == null) indirectRequiredAttrs = new StringBuilder(); @@ -241,7 +241,8 @@ public class XmlTagInsertHandler implements InsertHandler { } } else if (shouldBeInserted && attributeDecl.isFixed() && attributeDecl.getDefaultValue() != null && !htmlCode) { - template.addTextSegment(" " + attributeName + "=\"" + attributeDecl.getDefaultValue() + "\""); + template.addTextSegment(" " + attributeName + "=" + XmlAttributeInsertHandler.getAttributeQuote(false) + + attributeDecl.getDefaultValue() + XmlAttributeInsertHandler.getAttributeQuote(false)); } } } @@ -254,6 +255,7 @@ public class XmlTagInsertHandler implements InsertHandler { XmlTag tag, Template template, StringBuilder indirectRequiredAttrs) { + boolean htmlCode = HtmlUtil.hasHtml(tag.getContainingFile()) || HtmlUtil.supportsXmlTypedHandlers(tag.getContainingFile()); if (completionChar == '>' || (completionChar == '/' && indirectRequiredAttrs != null)) { template.addTextSegment(">"); @@ -290,7 +292,7 @@ public class XmlTagInsertHandler implements InsertHandler { else if (completionChar == ' ' && template.getSegmentsCount() == 0) { if (WebEditorOptions.getInstance().isAutomaticallyStartAttribute() && (descriptor.getAttributesDescriptors(tag).length > 0 || isTagFromHtml(tag) && !HtmlUtil.isTagWithoutAttributes(tag.getName()))) { - completeAttribute(template); + completeAttribute(template, htmlCode); return true; } } @@ -301,7 +303,7 @@ public class XmlTagInsertHandler implements InsertHandler { else { if (needAlLeastOneAttribute(tag) && WebEditorOptions.getInstance().isAutomaticallyStartAttribute() && tag.getAttributes().length == 0 && template.getSegmentsCount() == 0) { - completeAttribute(template); + completeAttribute(template, htmlCode); return true; } else { @@ -313,12 +315,12 @@ public class XmlTagInsertHandler implements InsertHandler { return false; } - private static void completeAttribute(Template template) { + private static void completeAttribute(Template template, boolean htmlCode) { template.addTextSegment(" "); template.addVariable(new MacroCallNode(new CompleteMacro()), true); - template.addTextSegment("=\""); + template.addTextSegment("=" + XmlAttributeInsertHandler.getAttributeQuote(htmlCode)); template.addEndVariable(); - template.addTextSegment("\""); + template.addTextSegment(XmlAttributeInsertHandler.getAttributeQuote(htmlCode)); } private static boolean needAlLeastOneAttribute(XmlTag tag) { diff --git a/xml/impl/src/com/intellij/codeInsight/editorActions/XmlEqTypedHandler.java b/xml/impl/src/com/intellij/codeInsight/editorActions/XmlEqTypedHandler.java index 6a5287b149c8..a2b988c5aa2a 100644 --- a/xml/impl/src/com/intellij/codeInsight/editorActions/XmlEqTypedHandler.java +++ b/xml/impl/src/com/intellij/codeInsight/editorActions/XmlEqTypedHandler.java @@ -17,12 +17,14 @@ package com.intellij.codeInsight.editorActions; import com.intellij.application.options.editor.WebEditorOptions; import com.intellij.codeInsight.AutoPopupController; +import com.intellij.codeInsight.completion.XmlAttributeInsertHandler; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.xml.XmlAttribute; +import com.intellij.xml.util.HtmlUtil; import org.jetbrains.annotations.NotNull; public class XmlEqTypedHandler extends TypedHandlerDelegate { @@ -50,13 +52,18 @@ public class XmlEqTypedHandler extends TypedHandlerDelegate { if (needToInsertQuotes) { int offset = editor.getCaretModel().getOffset(); PsiElement fileContext = file.getContext(); - String toInsert= "\"\""; + String toInsert = null; if(fileContext != null) { if (fileContext.getText().startsWith("\"")) toInsert = "''"; + if (fileContext.getText().startsWith("\'")) toInsert = "\"\""; + } + if (toInsert == null) { + final String quote = XmlAttributeInsertHandler.getAttributeQuote(HtmlUtil.hasHtml(file) || HtmlUtil.supportsXmlTypedHandlers(file)); + toInsert = quote + quote; } editor.getDocument().insertString(offset, toInsert); - editor.getCaretModel().moveToOffset(offset + 1); + editor.getCaretModel().moveToOffset(offset + toInsert.length() / 2); AutoPopupController.getInstance(project).scheduleAutoPopup(editor); } needToInsertQuotes = false; diff --git a/xml/tests/src/com/intellij/codeInsight/completion/XmlCompletionTest.java b/xml/tests/src/com/intellij/codeInsight/completion/XmlCompletionTest.java index 7fb0761a8019..40cb6c29d392 100644 --- a/xml/tests/src/com/intellij/codeInsight/completion/XmlCompletionTest.java +++ b/xml/tests/src/com/intellij/codeInsight/completion/XmlCompletionTest.java @@ -28,6 +28,8 @@ import com.intellij.javaee.ExternalResourceManagerEx; import com.intellij.javaee.ExternalResourceManagerExImpl; import com.intellij.openapi.application.Result; import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.psi.codeStyle.CodeStyleSchemes; +import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.psi.statistics.StatisticsManager; import com.intellij.psi.statistics.impl.StatisticsManagerImpl; import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase; @@ -342,6 +344,30 @@ public class XmlCompletionTest extends LightCodeInsightFixtureTestCase { checkResultByFile("37_after.xml"); } + public void testInsertExtraRequiredAttributeSingleQuote() throws Exception { + final CodeStyleSettings settings = CodeStyleSchemes.getInstance().getCurrentScheme().getCodeStyleSettings(); + final CodeStyleSettings.QuoteStyle quote = settings.HTML_QUOTE_STYLE; + try { + settings.HTML_QUOTE_STYLE = CodeStyleSettings.QuoteStyle.Single; + configureByFile(getTestName(true) + ".html"); + checkResultByFile(getTestName(true) + "_after.html"); + } finally { + CodeStyleSchemes.getInstance().getCurrentScheme().getCodeStyleSettings().HTML_QUOTE_STYLE = quote; + } + } + + public void testInsertExtraRequiredAttributeNoneQuote() throws Exception { + final CodeStyleSettings settings = CodeStyleSchemes.getInstance().getCurrentScheme().getCodeStyleSettings(); + final CodeStyleSettings.QuoteStyle quote = settings.HTML_QUOTE_STYLE; + try { + settings.HTML_QUOTE_STYLE = CodeStyleSettings.QuoteStyle.None; + configureByFile(getTestName(true) + ".html"); + checkResultByFile(getTestName(true) + "_after.html"); + } finally { + CodeStyleSchemes.getInstance().getCurrentScheme().getCodeStyleSettings().HTML_QUOTE_STYLE = quote; + } + } + public void testBeforeAttributeValue() throws Throwable { configureByFile(getTestName(true) + ".xml"); assertEmpty(myFixture.getLookupElements()); diff --git a/xml/tests/src/com/intellij/codeInsight/completion/XmlTypedHandlersTest.java b/xml/tests/src/com/intellij/codeInsight/completion/XmlTypedHandlersTest.java index 0db89fc934e3..57f4573a5ee0 100644 --- a/xml/tests/src/com/intellij/codeInsight/completion/XmlTypedHandlersTest.java +++ b/xml/tests/src/com/intellij/codeInsight/completion/XmlTypedHandlersTest.java @@ -16,7 +16,10 @@ package com.intellij.codeInsight.completion; import com.intellij.application.options.editor.WebEditorOptions; +import com.intellij.ide.highlighter.HtmlFileType; import com.intellij.ide.highlighter.XmlFileType; +import com.intellij.psi.codeStyle.CodeStyleSchemes; +import com.intellij.psi.codeStyle.CodeStyleSettings; import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase; /** @@ -95,6 +98,32 @@ public class XmlTypedHandlersTest extends LightPlatformCodeInsightFixtureTestCas } } + public void testSingleQuotes() { + final CodeStyleSettings settings = CodeStyleSchemes.getInstance().getCurrentScheme().getCodeStyleSettings(); + final CodeStyleSettings.QuoteStyle quote = settings.HTML_QUOTE_STYLE; + try { + settings.HTML_QUOTE_STYLE = CodeStyleSettings.QuoteStyle.Single; + myFixture.configureByText(HtmlFileType.INSTANCE, ""); + myFixture.type('='); + myFixture.checkResult(">text"); + myFixture.type('='); + myFixture.checkResult(">text"); + } finally { + CodeStyleSchemes.getInstance().getCurrentScheme().getCodeStyleSettings().HTML_QUOTE_STYLE = quote; + } + } + public void testFooBar() throws Exception { doTest("\n" + " \n" + diff --git a/xml/tests/testData/completion/insertExtraRequiredAttributeNoneQuote.html b/xml/tests/testData/completion/insertExtraRequiredAttributeNoneQuote.html new file mode 100644 index 000000000000..3fe294dcef59 --- /dev/null +++ b/xml/tests/testData/completion/insertExtraRequiredAttributeNoneQuote.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/xml/tests/testData/completion/insertExtraRequiredAttributeNoneQuote_after.html b/xml/tests/testData/completion/insertExtraRequiredAttributeNoneQuote_after.html new file mode 100644 index 000000000000..4fb461b54c73 --- /dev/null +++ b/xml/tests/testData/completion/insertExtraRequiredAttributeNoneQuote_after.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/xml/tests/testData/completion/insertExtraRequiredAttributeSingleQuote.html b/xml/tests/testData/completion/insertExtraRequiredAttributeSingleQuote.html new file mode 100644 index 000000000000..3fe294dcef59 --- /dev/null +++ b/xml/tests/testData/completion/insertExtraRequiredAttributeSingleQuote.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/xml/tests/testData/completion/insertExtraRequiredAttributeSingleQuote_after.html b/xml/tests/testData/completion/insertExtraRequiredAttributeSingleQuote_after.html new file mode 100644 index 000000000000..56810380770a --- /dev/null +++ b/xml/tests/testData/completion/insertExtraRequiredAttributeSingleQuote_after.html @@ -0,0 +1 @@ + \ No newline at end of file