From 5806d84c64b6146b43bdd95334c0308659ae86f0 Mon Sep 17 00:00:00 2001 From: Willem Date: Sun, 27 May 2018 10:54:43 +0200 Subject: [PATCH 01/20] Allow for individual formatting settings per XML tag. - Use class for all formatting settings access. - Reworked the line break logic. - Options window now does live preview. --- .../Formatting/CommentFormatHelper.cs | 40 +- .../Formatting/FormatWithPrefixTests.cs | 9 +- .../Formatting/HeaderFormattingTests.cs | 16 +- .../Formatting/IgnorePrefixesTests.cs | 9 +- .../Formatting/ListFormattingTests.cs | 19 +- .../Formatting/SimpleFormattingTests.cs | 22 +- .../Formatting/XmlFormattingTests.cs | 463 ++++++++--------- CodeMaid/CodeMaid.csproj | 11 +- CodeMaid/Helpers/CodeCommentHelper.cs | 77 +-- .../Logic/Formatting/CommentFormatLogic.cs | 19 +- CodeMaid/Model/Comments/CodeComment.cs | 35 +- CodeMaid/Model/Comments/CommentFormatter.cs | 466 ++++++++---------- CodeMaid/Model/Comments/CommentLineXml.cs | 157 +++--- CodeMaid/Model/Comments/CommentMatch.cs | 29 +- CodeMaid/Model/Comments/FormatterOptions.cs | 15 - .../Comments/{ => Options}/CommentOptions.cs | 4 +- .../Comments/Options/FormatterOptions.cs | 43 ++ .../Comments/Options/FormatterOptionsXml.cs | 60 +++ .../Options/FormatterOptionsXmlTag.cs | 31 ++ .../Model/Comments/Options/ICommentOptions.cs | 11 + .../Model/Comments/Options/IXmlTagOptions.cs | 19 + CodeMaid/Model/Comments/Options/XmlTagCase.cs | 14 + .../Model/Comments/Options/XmlTagNewLine.cs | 34 ++ .../Model/Comments/Options/XmlTagOptions.cs | 50 ++ CodeMaid/Properties/Resources.Designer.cs | 6 +- CodeMaid/Properties/Resources.en-US.resx | 4 +- CodeMaid/Properties/Resources.resx | 4 +- CodeMaid/Properties/Resources.zh-Hans.resx | 4 +- .../Formatting/FormattingDataTemplate.xaml | 2 +- .../Options/Formatting/FormattingViewModel.cs | 21 +- 30 files changed, 937 insertions(+), 757 deletions(-) delete mode 100644 CodeMaid/Model/Comments/FormatterOptions.cs rename CodeMaid/Model/Comments/{ => Options}/CommentOptions.cs (69%) create mode 100644 CodeMaid/Model/Comments/Options/FormatterOptions.cs create mode 100644 CodeMaid/Model/Comments/Options/FormatterOptionsXml.cs create mode 100644 CodeMaid/Model/Comments/Options/FormatterOptionsXmlTag.cs create mode 100644 CodeMaid/Model/Comments/Options/ICommentOptions.cs create mode 100644 CodeMaid/Model/Comments/Options/IXmlTagOptions.cs create mode 100644 CodeMaid/Model/Comments/Options/XmlTagCase.cs create mode 100644 CodeMaid/Model/Comments/Options/XmlTagNewLine.cs create mode 100644 CodeMaid/Model/Comments/Options/XmlTagOptions.cs diff --git a/CodeMaid.UnitTests/Formatting/CommentFormatHelper.cs b/CodeMaid.UnitTests/Formatting/CommentFormatHelper.cs index 60e553c89..7f6d1b9ad 100644 --- a/CodeMaid.UnitTests/Formatting/CommentFormatHelper.cs +++ b/CodeMaid.UnitTests/Formatting/CommentFormatHelper.cs @@ -1,42 +1,36 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using SteveCadwallader.CodeMaid.Model.Comments; +using SteveCadwallader.CodeMaid.Model.Comments.Options; using System; -using System.Collections.Generic; namespace SteveCadwallader.CodeMaid.UnitTests.Formatting { internal class CommentFormatHelper { - public static string AssertEqualAfterFormat(string input) + public static string AssertEqualAfterFormat( + string text, + Action options = null) { - return AssertEqualAfterFormat(input, input); + return AssertEqualAfterFormat(text, null, null, options); } - public static string AssertEqualAfterFormat(string input, string expected) + public static string AssertEqualAfterFormat( + string text, + string expected, + Action options = null) { - return AssertEqualAfterFormat(input, expected, null); + return AssertEqualAfterFormat(text, expected, null, options); } - public static string AssertEqualAfterFormat(string input, string expected, string prefix) + public static string AssertEqualAfterFormat( + string text, + string expected, + string prefix, + Action options = null) { - var result = Format(input, prefix); - Assert.AreEqual(expected, result); + var result = CodeComment.Format(text, prefix, options); + Assert.AreEqual(expected ?? text, result); return result; } - - public static string Format(IEnumerable text) - { - return Format(string.Join(Environment.NewLine, text)); - } - - public static string Format(string text) - { - return Format(text, null); - } - - public static string Format(string text, string prefix) - { - return CodeComment.FormatXml(text, prefix); - } } } \ No newline at end of file diff --git a/CodeMaid.UnitTests/Formatting/FormatWithPrefixTests.cs b/CodeMaid.UnitTests/Formatting/FormatWithPrefixTests.cs index 141648be1..8348e688e 100644 --- a/CodeMaid.UnitTests/Formatting/FormatWithPrefixTests.cs +++ b/CodeMaid.UnitTests/Formatting/FormatWithPrefixTests.cs @@ -19,12 +19,11 @@ public void TestInitialize() [TestCategory("Formatting UnitTests")] public void SimpleFormatWithPrefixTests_KeepsPrefix() { - Settings.Default.Formatting_CommentWrapColumn = 40; var input = "// Lorem ipsum dolor sit amet, consectetur adipiscing elit."; var expected = "// Lorem ipsum dolor sit amet," + Environment.NewLine + "// consectetur adipiscing elit."; - CommentFormatHelper.AssertEqualAfterFormat(input, expected, "//"); + CommentFormatHelper.AssertEqualAfterFormat(input, expected, "//", o => o.WrapColumn = 40); } [TestMethod] @@ -48,26 +47,24 @@ public void SimpleFormatWithPrefixTests_KeepsLeadingSpace() [TestCategory("Formatting UnitTests")] public void SimpleFormatWithPrefixTests_AlignsToFirstPrefix() { - Settings.Default.Formatting_CommentWrapColumn = 40; var input = " // Lorem ipsum dolor sit amet, consectetur" + Environment.NewLine + " // adipiscing elit."; var expected = " // Lorem ipsum dolor sit amet," + Environment.NewLine + " // consectetur adipiscing elit."; - CommentFormatHelper.AssertEqualAfterFormat(input, expected, " //"); + CommentFormatHelper.AssertEqualAfterFormat(input, expected, " //", o => o.WrapColumn = 40); } [TestMethod] [TestCategory("Formatting UnitTests")] public void SimpleFormatWithPrefixTests_NoTrailingWhitespaceOnEmptyLine() { - Settings.Default.Formatting_CommentWrapColumn = 40; var input = "// Lorem ipsum dolor sit amet." + Environment.NewLine + "//" + Environment.NewLine + "// Consectetur adipiscing elit."; - CommentFormatHelper.AssertEqualAfterFormat(input, input, "//"); + CommentFormatHelper.AssertEqualAfterFormat(input, input, "//", o => o.WrapColumn = 40); } } } \ No newline at end of file diff --git a/CodeMaid.UnitTests/Formatting/HeaderFormattingTests.cs b/CodeMaid.UnitTests/Formatting/HeaderFormattingTests.cs index 6a5b07f83..613007bc2 100644 --- a/CodeMaid.UnitTests/Formatting/HeaderFormattingTests.cs +++ b/CodeMaid.UnitTests/Formatting/HeaderFormattingTests.cs @@ -22,24 +22,19 @@ public void TestInitialize() /// [TestMethod] [TestCategory("Formatting UnitTests")] - public void HeaderFormattingTests_IndentsXML() + public void HeaderFormattingTests_Copyright_Indenting() { - Settings.Default.Formatting_CommentXmlValueIndent = 0; - var input = @"" + Environment.NewLine + @"Company copyright tag." + Environment.NewLine + @""; - // It's not fair, but even though we are formatting without a prefix, the forced header - // indenting adds one extra space (to separate the prefix from the text) to the - // indenting, making it 5 total. var expected = @"" + Environment.NewLine + - @" Company copyright tag." + Environment.NewLine + + @" Company copyright tag." + Environment.NewLine + @""; - CommentFormatHelper.AssertEqualAfterFormat(input, expected); + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => o.Xml.Default.Indent = 0); } [TestMethod] @@ -58,13 +53,12 @@ public void HeaderFormattingTests_PreservesHyphenLinesWithoutXML() [TestMethod] [TestCategory("Formatting UnitTests")] - public void HeaderFormattingTests_PreservesHyphenLinesWithXML() + public void HeaderFormattingTests_Copyright_PreservesHyphenLinesWithXML() { - // Same as above, indenting without a prefix sneaks in an extra space. var input = @"-----------------------------------------------------------------------" + Environment.NewLine + @"" + Environment.NewLine + - @" Company copyright tag." + Environment.NewLine + + @" Company copyright tag." + Environment.NewLine + @"" + Environment.NewLine + @"-----------------------------------------------------------------------"; diff --git a/CodeMaid.UnitTests/Formatting/IgnorePrefixesTests.cs b/CodeMaid.UnitTests/Formatting/IgnorePrefixesTests.cs index 79a570f35..5757a163f 100644 --- a/CodeMaid.UnitTests/Formatting/IgnorePrefixesTests.cs +++ b/CodeMaid.UnitTests/Formatting/IgnorePrefixesTests.cs @@ -20,8 +20,7 @@ public void TestInitialize() [TestCategory("Formatting UnitTests")] public void IgnorePrefixesTests_DoesNotWrapSingleLine() { - Settings.Default.Formatting_CommentWrapColumn = 30; - CommentFormatHelper.AssertEqualAfterFormat(@"TODO: Lorem ipsum dolor sit amet, consectetur adipiscing elit."); + CommentFormatHelper.AssertEqualAfterFormat(@"TODO: Lorem ipsum dolor sit amet, consectetur adipiscing elit.", o => o.WrapColumn = 30); } [TestMethod] @@ -41,8 +40,7 @@ public void IgnorePrefixesTests_DoesNotWrapLineInsideComment() "Lorem ipsum dolor sit amet," + Environment.NewLine + "consectetur adipiscing elit."; - Settings.Default.Formatting_CommentWrapColumn = 30; - CommentFormatHelper.AssertEqualAfterFormat(input, expected); + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => o.WrapColumn = 30); } [TestMethod] @@ -64,8 +62,7 @@ public void IgnorePrefixesTests_DoesNotCombineSubsequentLines() "Lorem ipsum dolor sit amet," + Environment.NewLine + "consectetur adipiscing elit."; - Settings.Default.Formatting_CommentWrapColumn = 30; - CommentFormatHelper.AssertEqualAfterFormat(input, expected); + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => o.WrapColumn = 30); } } } \ No newline at end of file diff --git a/CodeMaid.UnitTests/Formatting/ListFormattingTests.cs b/CodeMaid.UnitTests/Formatting/ListFormattingTests.cs index 3014793fa..f44c0952d 100644 --- a/CodeMaid.UnitTests/Formatting/ListFormattingTests.cs +++ b/CodeMaid.UnitTests/Formatting/ListFormattingTests.cs @@ -5,8 +5,8 @@ namespace SteveCadwallader.CodeMaid.UnitTests.Formatting { /// - /// Class with list oriented unit tests for formatting. This calls the formatter directly, - /// rather than invoking it through the UI as with the integration tests. + /// Class with list oriented unit tests for formatting. This calls the formatter directly, rather + /// than invoking it through the UI as with the integration tests. /// [TestClass] public class ListFormattingTests @@ -35,9 +35,7 @@ public void ListFormattingTests_DashedList() @" words to require wrapping." + Environment.NewLine + @"Some trailing text."; - Settings.Default.Formatting_CommentWrapColumn = 30; - - CommentFormatHelper.AssertEqualAfterFormat(input, expected); + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => o.WrapColumn = 30); } [TestMethod] @@ -58,9 +56,7 @@ public void ListFormattingTests_NumberedList() @" words to require wrapping." + Environment.NewLine + @"Some trailing text."; - Settings.Default.Formatting_CommentWrapColumn = 30; - - CommentFormatHelper.AssertEqualAfterFormat(input, expected); + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => o.WrapColumn = 30); } [TestMethod] @@ -81,9 +77,7 @@ public void ListFormattingTests_WordList() @" words to require wrapping." + Environment.NewLine + @"Some trailing text."; - Settings.Default.Formatting_CommentWrapColumn = 35; - - CommentFormatHelper.AssertEqualAfterFormat(input, expected); + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => o.WrapColumn = 35); } [TestMethod] @@ -153,8 +147,7 @@ public void ListFormattingTests_XmlListWithHeaderAndIndent() "" + Environment.NewLine + "Some trailing text."; - Settings.Default.Formatting_CommentXmlValueIndent = 2; - CommentFormatHelper.AssertEqualAfterFormat(input, expected); + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => o.Xml.Default.Indent = 2); } } } \ No newline at end of file diff --git a/CodeMaid.UnitTests/Formatting/SimpleFormattingTests.cs b/CodeMaid.UnitTests/Formatting/SimpleFormattingTests.cs index 307472389..8185f56df 100644 --- a/CodeMaid.UnitTests/Formatting/SimpleFormattingTests.cs +++ b/CodeMaid.UnitTests/Formatting/SimpleFormattingTests.cs @@ -105,10 +105,11 @@ public void SimpleFormattingTests_SkipWrapOnLastWord() var input = "Lorem ipsum dolor sit amet."; var expected = "Lorem ipsum\r\ndolor sit amet."; - Settings.Default.Formatting_CommentWrapColumn = 12; - Settings.Default.Formatting_CommentSkipWrapOnLastWord = true; - - CommentFormatHelper.AssertEqualAfterFormat(input, expected); + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => + { + o.WrapColumn = 12; + o.SkipWrapOnLastWord = true; + }); } [TestMethod] @@ -118,10 +119,11 @@ public void SimpleFormattingTests_WrapOnLastWord() var input = "Lorem ipsum dolor sit amet."; var expected = "Lorem ipsum\r\ndolor sit\r\namet."; - Settings.Default.Formatting_CommentWrapColumn = 12; - Settings.Default.Formatting_CommentSkipWrapOnLastWord = false; - - CommentFormatHelper.AssertEqualAfterFormat(input, expected); + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => + { + o.WrapColumn = 12; + o.SkipWrapOnLastWord = false; + }); } [TestMethod] @@ -147,9 +149,7 @@ public void SimpleFormattingTests_WrapsLinesAsExpected() var input = "Lorem ipsum dolor sit."; var expected = "Lorem ipsum\r\ndolor sit."; - Settings.Default.Formatting_CommentWrapColumn = 12; - - CommentFormatHelper.AssertEqualAfterFormat(input, expected); + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => o.WrapColumn = 12); } [TestMethod] diff --git a/CodeMaid.UnitTests/Formatting/XmlFormattingTests.cs b/CodeMaid.UnitTests/Formatting/XmlFormattingTests.cs index 1876d812d..69d73132f 100644 --- a/CodeMaid.UnitTests/Formatting/XmlFormattingTests.cs +++ b/CodeMaid.UnitTests/Formatting/XmlFormattingTests.cs @@ -1,4 +1,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; +using SteveCadwallader.CodeMaid.Model.Comments; +using SteveCadwallader.CodeMaid.Model.Comments.Options; using SteveCadwallader.CodeMaid.Properties; using System; @@ -21,83 +23,88 @@ public void TestInitialize() [TestCategory("Formatting UnitTests")] public void XmlFormattingTests_AddSpaceToInsideTags() { - var input = ""; - var expected = ""; + var input = ""; + var expected = ""; - Settings.Default.Formatting_CommentXmlSplitSummaryTagToMultipleLines = false; - Settings.Default.Formatting_CommentXmlSpaceSingleTags = true; - - CommentFormatHelper.AssertEqualAfterFormat(input, expected); + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => + { + o.Xml.Default.SpaceSelfClosing = true; + }); } [TestMethod] [TestCategory("Formatting UnitTests")] public void XmlFormattingTests_AddSpaceToTagContent() { - var input = "test"; - var expected = " test "; + var input = "test"; + var expected = " test "; - Settings.Default.Formatting_CommentXmlSplitSummaryTagToMultipleLines = false; - Settings.Default.Formatting_CommentXmlSpaceTags = true; - - CommentFormatHelper.AssertEqualAfterFormat(input, expected); + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => + { + o.Xml.Default.SpaceContent = true; + }); } [TestMethod] [TestCategory("Formatting UnitTests")] - public void XmlFormattingTests_AddSpaceToTagContentWithSelfClosingTag() + public void XmlFormattingTests_AddSpaceToTagContentShouldLeaveNoTrailingWhitespace() { - var input = ""; - var expected = " "; - - Settings.Default.Formatting_CommentXmlSplitSummaryTagToMultipleLines = false; - Settings.Default.Formatting_CommentXmlSpaceTags = true; - - CommentFormatHelper.AssertEqualAfterFormat(input, expected); + var input = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; + var expected = + "" + Environment.NewLine + + "Lorem ipsum dolor sit amet," + Environment.NewLine + + "consectetur adipiscing elit." + Environment.NewLine + + ""; + + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => + { + o.WrapColumn = 30; + o.Xml.Default.Split = XmlTagNewLine.Always; + o.Xml.Default.SpaceContent = true; + }); } [TestMethod] [TestCategory("Formatting UnitTests")] - public void XmlFormattingTests_AddSpaceToTagContentWithSelfClosingTagMultiline() + public void XmlFormattingTests_AddSpaceToTagContentWithSelfClosingTag() { - var input = ""; - var expected = - "" + Environment.NewLine + - "" + Environment.NewLine + - ""; - - Settings.Default.Formatting_CommentXmlSplitSummaryTagToMultipleLines = true; - Settings.Default.Formatting_CommentXmlSpaceTags = true; - - CommentFormatHelper.AssertEqualAfterFormat(input, expected); + var input = ""; + var expected = " "; + + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => + { + o.Xml.Default.SpaceContent = true; + o.Xml.Default.SpaceSelfClosing = false; + }); } [TestMethod] [TestCategory("Formatting UnitTests")] - public void XmlFormattingTests_AddSpaceToTagContentShouldLeaveNoTrailingWhitespace() + public void XmlFormattingTests_AddSpaceToTagContentWithSelfClosingTagMultiline() { - var input = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; + // Add space to content should not add a space when tag content is on it's own line. + var input = ""; var expected = - "" + Environment.NewLine + - "Lorem ipsum dolor sit amet," + Environment.NewLine + - "consectetur adipiscing elit." + Environment.NewLine + - ""; - - Settings.Default.Formatting_CommentWrapColumn = 30; - Settings.Default.Formatting_CommentXmlSplitSummaryTagToMultipleLines = true; - Settings.Default.Formatting_CommentXmlSpaceTags = true; - - CommentFormatHelper.AssertEqualAfterFormat(input, expected); + "" + Environment.NewLine + + "" + Environment.NewLine + + ""; + + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => + { + o.Xml.Default.Split = XmlTagNewLine.Always; + o.Xml.Default.SpaceContent = true; + o.Xml.Default.SpaceSelfClosing = false; + }); } [TestMethod] [TestCategory("Formatting UnitTests")] public void XmlFormattingTests_AllRootLevelTagsOnNewLine() { - var input = "abcabc"; - var expected = "abc" + Environment.NewLine + "abc"; - - Settings.Default.Formatting_CommentXmlSplitSummaryTagToMultipleLines = false; + var input = "abcabc"; + var expected = + "abc" + Environment.NewLine + + "abc"; CommentFormatHelper.AssertEqualAfterFormat(input, expected); } @@ -106,14 +113,18 @@ public void XmlFormattingTests_AllRootLevelTagsOnNewLine() [TestCategory("Formatting UnitTests")] public void XmlFormattingTests_BreakAllTags() { - var input = ""; - var expected = "" + Environment.NewLine + "" + Environment.NewLine + "" + Environment.NewLine + ""; - - Settings.Default.Formatting_CommentXmlValueIndent = 0; - Settings.Default.Formatting_CommentXmlSplitSummaryTagToMultipleLines = false; - Settings.Default.Formatting_CommentXmlSplitAllTags = true; - - CommentFormatHelper.AssertEqualAfterFormat(input, expected); + var input = ""; + var expected = + "" + Environment.NewLine + + "" + Environment.NewLine + + "" + Environment.NewLine + + ""; + + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => + { + o.Xml.Default.Indent = 0; + o.Xml.Default.Split = XmlTagNewLine.Always; + }); } [TestMethod] @@ -129,19 +140,7 @@ public void XmlFormattingTests_BreakLongParagraphs() "" + Environment.NewLine + ""; - Settings.Default.Formatting_CommentWrapColumn = 60; - CommentFormatHelper.AssertEqualAfterFormat(input, expected); - } - - [TestMethod] - [TestCategory("Formatting UnitTests")] - public void XmlFormattingTests_BreakSummaryTags() - { - var input = ""; - var expected = "" + Environment.NewLine + "" + Environment.NewLine + ""; - - Settings.Default.Formatting_CommentXmlValueIndent = 0; - CommentFormatHelper.AssertEqualAfterFormat(input, expected); + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => o.WrapColumn = 60); } [TestMethod] @@ -157,115 +156,38 @@ public void XmlFormattingTests_BreakTagsWhenContainsParagraphs() CommentFormatHelper.AssertEqualAfterFormat(input, expected); } - /// - /// If XML tag indenting is set, this should not affect any literal content. however, - /// content after the literal should be indented as normal. - /// - [TestMethod] - [TestCategory("Formatting UnitTests")] - public void XmlFormattingTests_DoesIndentAfterLiteralContent() - { - var input = - "" + Environment.NewLine + - "Example usage :" + Environment.NewLine + - "" + Environment.NewLine + - "Example usage with a location parameter and a location function:" + Environment.NewLine + - "" + Environment.NewLine + - "And some final text that should also be formatted." + Environment.NewLine + - ""; - - var expected = - "" + Environment.NewLine + - " Example usage :" + Environment.NewLine + - " " + Environment.NewLine + - " Example usage with a location parameter and a location function:" + Environment.NewLine + - " " + Environment.NewLine + - " And some final text that should also be formatted." + Environment.NewLine + - ""; - - Settings.Default.Formatting_CommentXmlValueIndent = 4; - Settings.Default.Formatting_CommentXmlKeepTagsTogether = true; - - // First pass. - var result = CommentFormatHelper.AssertEqualAfterFormat(input, expected); - - // Second pass. - CommentFormatHelper.AssertEqualAfterFormat(result, expected); - } - [TestMethod] [TestCategory("Formatting UnitTests")] public void XmlFormattingTests_DoesNotIndentCloseTag() { - var input = ""; - var expected = "" + Environment.NewLine + "" + Environment.NewLine + ""; - - Settings.Default.Formatting_CommentXmlValueIndent = 4; - Settings.Default.Formatting_CommentXmlSplitSummaryTagToMultipleLines = true; - Settings.Default.Formatting_CommentXmlSplitAllTags = false; - - CommentFormatHelper.AssertEqualAfterFormat(input, expected); - } - - /// - /// If XML tag indenting is set, this should not affect any literal content. Since - /// whitespace is preserved on literals, this would increase the indenting with every pass. - /// - [TestMethod] - [TestCategory("Formatting UnitTests")] - public void XmlFormattingTests_DoesNotIndentLiteralContent() - { - var input = - "" + Environment.NewLine + - "" + Environment.NewLine + - " Some code with." + Environment.NewLine + - " funny indenting" + Environment.NewLine + - "" + Environment.NewLine + - " and a white line" + Environment.NewLine + - "that should not change." + Environment.NewLine + - "" + Environment.NewLine + - ""; - + var input = ""; var expected = - "" + Environment.NewLine + - " " + Environment.NewLine + - " Some code with." + Environment.NewLine + - " funny indenting" + Environment.NewLine + - "" + Environment.NewLine + - " and a white line" + Environment.NewLine + - "that should not change." + Environment.NewLine + - " " + Environment.NewLine + - ""; - - Settings.Default.Formatting_CommentXmlValueIndent = 4; - - // First pass. - var result = CommentFormatHelper.AssertEqualAfterFormat(input, expected); - - // Second pass. - CommentFormatHelper.AssertEqualAfterFormat(result, expected); + "" + Environment.NewLine + + "" + Environment.NewLine + + ""; + + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => + { + o.Xml.Default.Indent = 4; + o.Xml.Default.KeepTogether = true; + + o.Xml.Tags.Clear(); + o.Xml.Tags["tag1"] = new FormatterOptionsXmlTag { Split = XmlTagNewLine.Always }; + }); } [TestMethod] [TestCategory("Formatting UnitTests")] public void XmlFormattingTests_DoNotAutoCollapseTags() { - var input = ""; - - Settings.Default.Formatting_CommentXmlSplitSummaryTagToMultipleLines = false; - - CommentFormatHelper.AssertEqualAfterFormat(input); + CommentFormatHelper.AssertEqualAfterFormat(""); } [TestMethod] [TestCategory("Formatting UnitTests")] public void XmlFormattingTests_DoNotAutoExpandTags() { - var input = ""; - - Settings.Default.Formatting_CommentXmlSplitSummaryTagToMultipleLines = false; - - CommentFormatHelper.AssertEqualAfterFormat(input); + CommentFormatHelper.AssertEqualAfterFormat("", o => o.Xml.Default.SpaceSelfClosing = false); } [TestMethod] @@ -288,16 +210,17 @@ public void XmlFormattingTests_HyperlinkOnNewLine() [TestCategory("Formatting UnitTests")] public void XmlFormattingTests_IndentsXml() { - var input = "Lorem ipsum dolor sit amet."; + var input = "Lorem ipsum dolor sit amet."; var expected = - "" + Environment.NewLine + + "" + Environment.NewLine + " Lorem ipsum dolor sit amet." + Environment.NewLine + - ""; - - Settings.Default.Formatting_CommentXmlSplitSummaryTagToMultipleLines = true; - Settings.Default.Formatting_CommentXmlValueIndent = 4; + ""; - CommentFormatHelper.AssertEqualAfterFormat(input, expected); + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => + { + o.Xml.Default.Indent = 4; + o.Xml.Tags["xml"] = new FormatterOptionsXmlTag { Split = XmlTagNewLine.Always }; + }); } [TestMethod] @@ -315,11 +238,11 @@ public void XmlFormattingTests_IndentsXmlMultiLevel() " sit amet." + Environment.NewLine + ""; - Settings.Default.Formatting_CommentWrapColumn = 60; - Settings.Default.Formatting_CommentXmlSplitSummaryTagToMultipleLines = true; - Settings.Default.Formatting_CommentXmlValueIndent = 4; - - CommentFormatHelper.AssertEqualAfterFormat(input, expected); + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => + { + o.WrapColumn = 60; + o.Xml.Default.Indent = 4; + }); } [TestMethod] @@ -334,10 +257,11 @@ public void XmlFormattingTests_IndentsXmlSingleLevel() " sit amet." + Environment.NewLine + ""; - Settings.Default.Formatting_CommentXmlSplitSummaryTagToMultipleLines = true; - Settings.Default.Formatting_CommentXmlValueIndent = 4; - - CommentFormatHelper.AssertEqualAfterFormat(input, expected); + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => + { + o.Xml.Default.Indent = 4; + o.Xml.Tags["summary"] = new FormatterOptionsXmlTag { Split = XmlTagNewLine.Always }; + }); } /// @@ -350,12 +274,111 @@ public void XmlFormattingTests_InterpunctionNoSpacing() { var input = "Line with ."; - CommentFormatHelper.AssertEqualAfterFormat(input); + CommentFormatHelper.AssertEqualAfterFormat(input, o => o.Xml.Default.SpaceSelfClosing = false); + } + + [TestMethod] + [TestCategory("Formatting UnitTests")] + public void XmlFormattingTests_KeepShortParagraphs() + { + var input = + "" + Environment.NewLine + + "" + Environment.NewLine + + "Lorem ipsum dolor sit amet." + Environment.NewLine + + "" + Environment.NewLine + + ""; + + var expected = + "" + Environment.NewLine + + "Lorem ipsum dolor sit amet." + Environment.NewLine + + ""; + + CommentFormatHelper.AssertEqualAfterFormat(input, expected); } + /// + /// If XML tag indenting is set, this should not affect any literal content. Since whitespace + /// is preserved on literals, this would increase the indenting with every pass. + /// [TestMethod] [TestCategory("Formatting UnitTests")] - public void XmlFormattingTests_KeepCodeFormatting() + public void XmlFormattingTests_Literal_DoesNotIndent() + { + var input = + "" + Environment.NewLine + + "" + Environment.NewLine + + " Some code with." + Environment.NewLine + + " funny indenting" + Environment.NewLine + + "" + Environment.NewLine + + " and a white line" + Environment.NewLine + + "that should not change." + Environment.NewLine + + "" + Environment.NewLine + + ""; + + var expected = + "" + Environment.NewLine + + " " + Environment.NewLine + + " Some code with." + Environment.NewLine + + " funny indenting" + Environment.NewLine + + "" + Environment.NewLine + + " and a white line" + Environment.NewLine + + "that should not change." + Environment.NewLine + + " " + Environment.NewLine + + ""; + + // First pass. + var result = CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => o.Xml.Default.Indent = 4); + + // Second pass. + CommentFormatHelper.AssertEqualAfterFormat(result, expected, o => o.Xml.Default.Indent = 4); + } + + /// + /// If XML tag indenting is set, this should not affect any literal content. however, content + /// after the literal should be indented as normal. + /// + [TestMethod] + [TestCategory("Formatting UnitTests")] + public void XmlFormattingTests_Literal_IndentsAfterContent() + { + var input = + "" + Environment.NewLine + + "Example usage :" + Environment.NewLine + + "" + Environment.NewLine + + "Example usage with a location parameter and a location function:" + Environment.NewLine + + "" + Environment.NewLine + + "And some final text that should also be formatted." + Environment.NewLine + + ""; + + var expected = + "" + Environment.NewLine + + " Example usage :" + Environment.NewLine + + " " + Environment.NewLine + + " Example usage with a location parameter and a location function:" + Environment.NewLine + + " " + Environment.NewLine + + " And some final text that should also be formatted." + Environment.NewLine + + ""; + + // First pass. + var result = CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => + { + o.Xml.Default.Indent = 4; + o.Xml.Default.KeepTogether = true; + o.Xml.Default.SpaceSelfClosing = false; + }); + + // Second pass. + CommentFormatHelper.AssertEqualAfterFormat(result, expected, o => + { + o.Xml.Default.Indent = 4; + o.Xml.Default.KeepTogether = true; + o.Xml.Default.SpaceSelfClosing = false; + }); + } + + [TestMethod] + [TestCategory("Formatting UnitTests")] + public void XmlFormattingTests_Literal_KeepFormatting() { var input = "before " + Environment.NewLine + @@ -380,45 +403,40 @@ public void XmlFormattingTests_KeepCodeFormatting() [TestMethod] [TestCategory("Formatting UnitTests")] - public void XmlFormattingTests_KeepShortParagraphs() + public void XmlFormattingTests_RemoveSpaceFromInsideTags() { - var input = - "" + Environment.NewLine + - "" + Environment.NewLine + - "Lorem ipsum dolor sit amet." + Environment.NewLine + - "" + Environment.NewLine + - ""; + var input = ""; + var expected = ""; - var expected = - "" + Environment.NewLine + - "Lorem ipsum dolor sit amet." + Environment.NewLine + - ""; - - CommentFormatHelper.AssertEqualAfterFormat(input, expected); + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => o.Xml.Default.SpaceSelfClosing = false); } [TestMethod] [TestCategory("Formatting UnitTests")] - public void XmlFormattingTests_RemoveSpaceFromInsideTags() + public void XmlFormattingTests_RemoveSpaceFromTagContent() { - var input = ""; - var expected = ""; + var input = " test "; + var expected = "test"; - Settings.Default.Formatting_CommentXmlSplitSummaryTagToMultipleLines = false; - - CommentFormatHelper.AssertEqualAfterFormat(input, expected); + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => o.Xml.Default.SpaceContent = false); } [TestMethod] [TestCategory("Formatting UnitTests")] - public void XmlFormattingTests_RemoveSpaceFromTagContent() + public void XmlFormattingTests_SplitAlwaysOnSingleTag() { - var input = " test "; - var expected = "test"; - - Settings.Default.Formatting_CommentXmlSplitSummaryTagToMultipleLines = false; - - CommentFormatHelper.AssertEqualAfterFormat(input, expected); + var input = ""; + var expected = + "" + Environment.NewLine + + "" + Environment.NewLine + + ""; + + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => + { + o.Xml.Default.Indent = 0; + o.Xml.Tags.Clear(); + o.Xml.Tags["tag1"] = new FormatterOptionsXmlTag { Split = XmlTagNewLine.Always }; + }); } [TestMethod] @@ -431,35 +449,38 @@ public void XmlFormattingTests_SplitsTagsWhenLineDoesNotFit() "elit. Vivamus nisi neque, placerat sed neque vitae" + Environment.NewLine + ""; - Settings.Default.Formatting_CommentWrapColumn = 50; - Settings.Default.Formatting_CommentSkipWrapOnLastWord = false; - - CommentFormatHelper.AssertEqualAfterFormat(input, expected); + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => + { + o.WrapColumn = 50; + o.SkipWrapOnLastWord = false; + }); } [TestMethod] [TestCategory("Formatting UnitTests")] - public void XmlFormattingTests_TagNameKeepCase() + public void XmlFormattingTests_TagCase_Keep() { - var input = ""; - - Settings.Default.Formatting_CommentXmlSplitSummaryTagToMultipleLines = false; - Settings.Default.Formatting_CommentXmlTagsToLowerCase = false; + var input = ""; - CommentFormatHelper.AssertEqualAfterFormat(input); + CommentFormatHelper.AssertEqualAfterFormat(input, o => o.Xml.Default.Case = XmlTagCase.Keep); } [TestMethod] [TestCategory("Formatting UnitTests")] - public void XmlFormattingTests_TagNameToLowerCase() + public void XmlFormattingTests_TagCase_Lower() { - var input = ""; - var expected = ""; - - Settings.Default.Formatting_CommentXmlSplitSummaryTagToMultipleLines = false; - Settings.Default.Formatting_CommentXmlTagsToLowerCase = true; + var input = ""; + var expected = ""; + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => o.Xml.Default.Case = XmlTagCase.LowerCase); + } - CommentFormatHelper.AssertEqualAfterFormat(input, expected); + [TestMethod] + [TestCategory("Formatting UnitTests")] + public void XmlFormattingTests_TagCase_Upper() + { + var input = ""; + var expected = ""; + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => o.Xml.Default.Case = XmlTagCase.UpperCase); } } } \ No newline at end of file diff --git a/CodeMaid/CodeMaid.csproj b/CodeMaid/CodeMaid.csproj index b0374fd7c..6337fc970 100644 --- a/CodeMaid/CodeMaid.csproj +++ b/CodeMaid/CodeMaid.csproj @@ -191,10 +191,12 @@ - - + + + + @@ -291,6 +293,11 @@ + + + + + True diff --git a/CodeMaid/Helpers/CodeCommentHelper.cs b/CodeMaid/Helpers/CodeCommentHelper.cs index 3587d4345..2572c53a5 100644 --- a/CodeMaid/Helpers/CodeCommentHelper.cs +++ b/CodeMaid/Helpers/CodeCommentHelper.cs @@ -1,4 +1,5 @@ using EnvDTE; +using SteveCadwallader.CodeMaid.Model.Comments; using SteveCadwallader.CodeMaid.Properties; using System; using System.Collections.Generic; @@ -17,65 +18,6 @@ internal static class CodeCommentHelper public const char KeepTogetherSpacer = '\a'; public const char Spacer = ' '; - /// - /// Creates the XML close tag string for an XElement. - /// - /// The element. - /// - /// The XML close tag, or null if the element has no value and is a self-closing tag. - /// - internal static string CreateXmlCloseTag(System.Xml.Linq.XElement element) - { - if (element.IsEmpty) - { - return null; - } - - var name = element.Name.LocalName; - - var result = string.Format("", Settings.Default.Formatting_CommentXmlTagsToLowerCase ? name.ToLowerInvariant() : name); - - return Settings.Default.Formatting_CommentXmlKeepTagsTogether ? SpaceToFake(result) : result; - } - - /// - /// Creates the XML open tag string for an XElement. - /// - /// The element. - /// The XML open tag. In case of an element without value, the tag is self-closing. - internal static string CreateXmlOpenTag(System.Xml.Linq.XElement element) - { - var builder = new System.Text.StringBuilder(); - builder.Append("<"); - var name = element.Name.LocalName; - builder.Append(Settings.Default.Formatting_CommentXmlTagsToLowerCase ? name.ToLowerInvariant() : name); - - if (element.HasAttributes) - { - foreach (var attr in element.Attributes()) - { - builder.Append(Spacer); - builder.Append(attr); - } - } - - if (element.IsEmpty) - { - if (Settings.Default.Formatting_CommentXmlSpaceSingleTags) - { - builder.Append(Spacer); - } - - builder.Append("/"); - } - - builder.Append(">"); - - var result = builder.ToString(); - - return Settings.Default.Formatting_CommentXmlKeepTagsTogether ? SpaceToFake(result) : result; - } - internal static string FakeToSpace(string value) { return value.Replace(KeepTogetherSpacer, Spacer); @@ -146,23 +88,6 @@ internal static Regex GetCommentRegex(CodeLanguage codeLanguage, bool includePre return new Regex(pattern, RegexOptions.ExplicitCapture | RegexOptions.Multiline); } - internal static int GetTabSize(CodeMaidPackage package, TextDocument document) - { - const int fallbackTabSize = 4; - - try - { - var settings = package.IDE.Properties["TextEditor", document.Language]; - var tabsize = settings.Item("TabSize").Value as int? ?? fallbackTabSize; - return tabsize; - } - catch (Exception) - { - // Some languages (e.g. F#, PowerShell) may not correctly resolve tab settings. - return fallbackTabSize; - } - } - /// /// Gets the list of tokens defined in Tools > Options > Environment > Task List. /// diff --git a/CodeMaid/Logic/Formatting/CommentFormatLogic.cs b/CodeMaid/Logic/Formatting/CommentFormatLogic.cs index fda0112a3..318037f88 100644 --- a/CodeMaid/Logic/Formatting/CommentFormatLogic.cs +++ b/CodeMaid/Logic/Formatting/CommentFormatLogic.cs @@ -1,6 +1,7 @@ using EnvDTE; using SteveCadwallader.CodeMaid.Helpers; using SteveCadwallader.CodeMaid.Model.Comments; +using SteveCadwallader.CodeMaid.Model.Comments.Options; using SteveCadwallader.CodeMaid.Properties; using System.Linq; @@ -59,14 +60,16 @@ public bool FormatComments(TextDocument textDocument, EditPoint start, EditPoint { bool foundComments = false; - var options = new FormatterOptions - { - TabSize = CodeCommentHelper.GetTabSize(_package, textDocument), - IgnoreTokens = CodeCommentHelper - .GetTaskListTokens(_package) - .Concat(Settings.Default.Formatting_IgnoreLinesStartingWith.Cast()) - .ToArray() - }; + var options = FormatterOptions + .FromSettings(Settings.Default) + .Set(o => + { + o.TabSize = textDocument.TabSize; + o.IgnoreTokens = CodeCommentHelper + .GetTaskListTokens(_package) + .Concat(Settings.Default.Formatting_IgnoreLinesStartingWith.Cast()) + .ToArray(); + }); while (start.Line <= end.Line) { diff --git a/CodeMaid/Model/Comments/CodeComment.cs b/CodeMaid/Model/Comments/CodeComment.cs index f42dea946..3ffa18ac8 100644 --- a/CodeMaid/Model/Comments/CodeComment.cs +++ b/CodeMaid/Model/Comments/CodeComment.cs @@ -1,5 +1,6 @@ using EnvDTE; using SteveCadwallader.CodeMaid.Helpers; +using SteveCadwallader.CodeMaid.Model.Comments.Options; using System; using System.Linq; using System.Text.RegularExpressions; @@ -60,22 +61,26 @@ public CodeComment(TextPoint point, FormatterOptions options) /// /// Helper function to generate the preview in the options menu. /// - public static string FormatXml(string text, string prefix = "///") + public static string Format(string text, string prefix = null, Action options = null) { var xml = XElement.Parse($"{text}"); + var formatterOptions = FormatterOptions + .FromSettings(Properties.Settings.Default) + .Set(o => o.IgnoreTokens = new[] { "TODO: " }); + + options?.Invoke(formatterOptions); + + var commentOptions = new CommentOptions + { + Prefix = prefix, + Regex = CodeCommentHelper.GetCommentRegex(CodeLanguage.CSharp, !string.IsNullOrWhiteSpace(prefix)) + }; + var formatter = new CommentFormatter( - new CommentLineXml(xml), - new FormatterOptions - { - IgnoreTokens = new[] { "TODO: " }, - TabSize = 4 - }, - new CommentOptions - { - Prefix = prefix, - Regex = CodeCommentHelper.GetCommentRegex(CodeLanguage.CSharp, !string.IsNullOrWhiteSpace(prefix)) - }); + new CommentLineXml(xml, formatterOptions), + formatterOptions, + commentOptions); return formatter.ToString(); } @@ -109,7 +114,7 @@ public TextPoint Format() try { var xml = XElement.Parse($"{commentText}"); - line = new CommentLineXml(xml); + line = new CommentLineXml(xml, _formatterOptions); } catch (System.Xml.XmlException) { @@ -203,8 +208,8 @@ private EditPoint Expand(TextPoint point, Action foundAction) prefix = currentPrefix; } - // The initial spacer is required, otherwise we assume this is commented out - // code and do not format. + // The initial spacer is required, otherwise we assume this is commented out code + // and do not format. if (match.Groups["initialspacer"].Success) { result = current.CreateEditPoint(); diff --git a/CodeMaid/Model/Comments/CommentFormatter.cs b/CodeMaid/Model/Comments/CommentFormatter.cs index 68a98edbc..0817d47ae 100644 --- a/CodeMaid/Model/Comments/CommentFormatter.cs +++ b/CodeMaid/Model/Comments/CommentFormatter.cs @@ -1,5 +1,5 @@ using SteveCadwallader.CodeMaid.Helpers; -using SteveCadwallader.CodeMaid.Properties; +using SteveCadwallader.CodeMaid.Model.Comments.Options; using System; using System.Linq; using System.Text; @@ -12,22 +12,16 @@ namespace SteveCadwallader.CodeMaid.Model.Comments /// internal class CommentFormatter : IEquatable { - #region Fields - - private readonly FormatterOptions _formatterOptions; private readonly CommentOptions _commentOptions; + private readonly FormatterOptions _formatterOptions; - private StringBuilder _builder; + private readonly StringBuilder _builder; private int _commentPrefixLength; private int _currentPosition; private bool _isAfterCommentPrefix; private bool _isFirstWord; private bool _isIndented; - #endregion Fields - - #region Constructors - public CommentFormatter(ICommentLine line, FormatterOptions formatterOptions, CommentOptions commentOptions) { _formatterOptions = formatterOptions; @@ -41,55 +35,41 @@ public CommentFormatter(ICommentLine line, FormatterOptions formatterOptions, Co // Special handling for the root XML line, it should not output it's surrounding xml // tags, only it's child lines. - var xml = line as CommentLineXml; - if (xml != null) + if (line is CommentLineXml xml) { - // On the content of the root, fix the optional alignment of param tags. This is not - // important if all tags will be broken onto seperate lines anyway. - if (!Settings.Default.Formatting_CommentXmlSplitAllTags && Settings.Default.Formatting_CommentXmlAlignParamTags) + // On the content of the root, fix the optional alignment of param tags. + if (_formatterOptions.Xml.AlignParamTags) { - var paramPhrases = xml.Lines.OfType().Where(p => string.Equals(p.TagName, "param", StringComparison.OrdinalIgnoreCase)); - if (paramPhrases.Count() > 1) - { - var longestParam = paramPhrases.Max(p => p.OpenTag.Length); - foreach (var phrase in paramPhrases) - { - phrase.OpenTag = phrase.OpenTag.PadRight(longestParam); - } - } + AlignParamTags(xml); } // Process all the lines inside the root XML line. foreach (var l in xml.Lines) { NewLine(); - Parse(l); + Format(l); } } else { // Normal comment line has no child-lines and can be processed normally. NewLine(); - Parse(line); + Format(line); } } - #endregion Constructors - - #region Methods - public bool Equals(string other) { return string.Equals(ToString(), other); } - public void Indent(int indentLevel) + public void Indent(int amount) { if (!_isIndented) { - if (indentLevel > 0 && Settings.Default.Formatting_CommentXmlValueIndent > 0) + if (amount > 0) { - Append(string.Empty.PadLeft(indentLevel * Settings.Default.Formatting_CommentXmlValueIndent)); + Append(string.Empty.PadLeft(amount)); } _isIndented = true; _isFirstWord = true; @@ -101,6 +81,24 @@ public override string ToString() return _builder.ToString().TrimEnd(); } + private static void AlignParamTags(CommentLineXml xml) + { + var paramPhrases = xml.Lines.OfType().Where(p => string.Equals(p.TagName, "param", StringComparison.OrdinalIgnoreCase)); + if (paramPhrases.Count() > 1) + { + // If param tags are broken into seperate lines there is nothing to align. + var paramSplit = paramPhrases.First().TagOptions.Split.HasFlag(XmlTagNewLine.AfterOpen); + if (!paramSplit) + { + var longestParam = paramPhrases.Max(p => p.OpenTag.Length); + foreach (var phrase in paramPhrases) + { + phrase.OpenTag = phrase.OpenTag.PadRight(longestParam); + } + } + } + } + private void Append(char value) { Append(value.ToString()); @@ -117,33 +115,16 @@ private void Append(string value) _builder.Append(CodeCommentHelper.Spacer); _isAfterCommentPrefix = false; } - _builder.Append(Settings.Default.Formatting_CommentXmlKeepTagsTogether ? CodeCommentHelper.FakeToSpace(value) : value); + _builder.Append(value); _currentPosition += WordLength(value); _isFirstWord = false; } - private void NewLine(bool force = false) - { - if (!_isFirstWord || force) - { - _builder.AppendLine(); - _currentPosition = 0; - } - - _builder.Append(_commentOptions.Prefix); - _currentPosition += _commentPrefixLength; - _isFirstWord = true; - _isIndented = false; - - // Cannot simply be true, because an empty prefix also means no initial spacing. - _isAfterCommentPrefix = _commentPrefixLength > 0; - } - /// /// Parse a code comment line into a string and write it to the buffer. /// /// The comment line. - /// The level of indenting for the content of this tag. + /// The amount of indenting for the content of this tag. /// /// The length of the enclosing XML tags, this is needed to calculate the line length for /// single line XML comments. @@ -151,308 +132,279 @@ private void NewLine(bool force = false) /// /// true if line fitted on single line, false if it wrapped on multiple lines. /// - private bool Parse(ICommentLine line, int indentLevel = 0, int xmlTagLength = 0) + private bool Format(ICommentLine line, int indentAmount = 0, int xmlTagLength = 0) { - var xml = line as CommentLineXml; - if (xml != null) + if (line is CommentLineXml xml) { - return ParseXml(xml, indentLevel); + return FormatXml(xml, indentAmount); } - else if (line.Content != null) + + if (line.Content == null) + return true; + + var matches = _commentOptions.Regex.Matches(line.Content).OfType().Select(x => new CodeCommentMatch(x, _formatterOptions)).ToList(); + + // Remove empty matches from the start and end of the comment. + CodeCommentMatch m; + while (((m = matches.FirstOrDefault()) != null && m.IsEmpty) || ((m = matches.LastOrDefault()) != null && m.IsEmpty)) { - var matches = _commentOptions.Regex.Matches(line.Content).OfType().Select(x => new CodeCommentMatch(x, _formatterOptions)).ToList(); + matches.Remove(m); + } - // Remove empty matches from the start and end of the comment. - CodeCommentMatch m; - while (((m = matches.FirstOrDefault()) != null && m.IsEmpty) || ((m = matches.LastOrDefault()) != null && m.IsEmpty)) + // Join the comment matches into single lines where possible. + if (matches.Count > 1) + { + int i = 0; + do { - matches.Remove(m); - } + m = matches[i]; + if (m.TryAppend(matches[i + 1])) + { + matches.RemoveAt(i + 1); + } + else + { + i++; + } + } while (i < matches.Count - 1); + } + + // Extended logic for line breaks. + // - Break if there is more than 1 line match (eg. due to a list or child xml tags). + // - Break if the content does not fit on a single line. + var matchCount = matches.Count; + var forceBreak = matchCount > 1; + var fittedOnLine = true; + + if (!forceBreak && matchCount == 1 && matches[0].Words.Any()) + { + // Calculate the length of the first line. + var firstLineLength = _commentPrefixLength + xmlTagLength + matches[0].Length + indentAmount; - // Join the comment matches into single lines where possible. - if (matches.Count > 1) + // If set to skip wrapping on the last word, the last word's length does not matter. + if (_formatterOptions.SkipWrapOnLastWord) { - int i = 0; - do - { - m = matches[i]; - if (m.TryAppend(matches[i + 1])) - { - matches.RemoveAt(i + 1); - } - else - { - i++; - } - } while (i < matches.Count - 1); + firstLineLength -= WordLength(matches[0].Words.Last()) + 1; } - // Extended logic for line breaks. - // - Break if there is more than 1 line match (eg. due to a list or child xml tags). - // - Break if the content does not fit on a single line. - var matchCount = matches.Count; - var forceBreak = matchCount > 1; - if (!forceBreak && matchCount == 1 && matches[0].Words.Any()) - { - // Calculate the length of the first line. - var firstLineLength = _commentPrefixLength + xmlTagLength + matches[0].Length + (indentLevel * Settings.Default.Formatting_CommentXmlValueIndent); + forceBreak = firstLineLength > _formatterOptions.WrapColumn; + } - // Tag spacing adds a space before and after. - if (Settings.Default.Formatting_CommentXmlSpaceTags) - { - firstLineLength += 2; - } + if (_currentPosition == 0 || (!_isFirstWord && forceBreak)) + { + NewLine(); + fittedOnLine = false; + } + + // Always consider the word after the opening tag as the first word to prevent an extra + // space before. + _isFirstWord = true; - // If set to skip wrapping on the last word, the last word's length does not matter. - if (Settings.Default.Formatting_CommentSkipWrapOnLastWord) + foreach (var match in matches) + { + if (match.IsLiteral || match.IsList) + { + if (!_isFirstWord) { - firstLineLength -= WordLength(matches[0].Words.Last()) + 1; + NewLine(); + fittedOnLine = false; } - forceBreak = firstLineLength > Settings.Default.Formatting_CommentWrapColumn; + Indent(indentAmount); } - if (_currentPosition == 0 || !_isFirstWord && forceBreak) + if (match.IsList) { - NewLine(); - } + Append(match.ListPrefix); - // Always consider the word after the opening tag as the first word to prevent an - // extra space before. - _isFirstWord = true; + // List items include their spacing and do not require additional space, thus we + // are logically still on the first word. + _isFirstWord = true; + } - foreach (var match in matches) + if (!match.IsEmpty) { - if (match.IsLiteral || match.IsList) + Indent(indentAmount); + var wordCount = match.Words.Count - 1; + + for (int i = 0; i <= wordCount; i++) { - if (!_isFirstWord) + var word = match.Words[i]; + var length = WordLength(word); + var wrap = false; + + // If current position plus word length exceeds the maximum comment length, + // wrap to the next line. Take care not to wrap on the first word, otherwise + // a word that never fits a line (ie. too long) would cause endless linewrapping. + if (!_isFirstWord && _currentPosition + length + 1 > _formatterOptions.WrapColumn) { - NewLine(); + wrap = true; } - Indent(indentLevel); - } - - if (match.IsList) - { - Append(match.ListPrefix); - - // List items include their spacing and do not require additional space, thus - // we are logically still on the first word. - _isFirstWord = true; - } - - if (match.Words != null) - { - Indent(indentLevel); - var wordCount = match.Words.Count - 1; + // If this is the last word and user selected to not wrap on the last word, + // don't wrap. + if (wrap && i == wordCount && _formatterOptions.SkipWrapOnLastWord) + { + wrap = false; + } - for (int i = 0; i <= wordCount; i++) + if (wrap) { - var word = match.Words[i]; - var length = WordLength(word); - var wrap = false; - - // If current position plus word length exceeds the maximum comment - // length, wrap to the next line. Take care not to wrap on the first - // word, otherwise a word that never fits a line (ie. too long) would - // cause endless linewrapping. - if (!_isFirstWord && _currentPosition + length + 1 > Settings.Default.Formatting_CommentWrapColumn) - { - wrap = true; - } + NewLine(); + Indent(indentAmount); + fittedOnLine = false; - // If this is the last word and user selected to not wrap on the last - // word, don't wrap. - if (wrap && i == wordCount && Settings.Default.Formatting_CommentSkipWrapOnLastWord) + // If linewrap is on a list item, add extra spacing to align the text + // with the previous line. + if (match.IsList) { - wrap = false; - } + Append(string.Empty.PadLeft(WordLength(match.ListPrefix), CodeCommentHelper.Spacer)); - if (wrap) - { - NewLine(); - Indent(indentLevel); - - // If linewrap is on a list item, add extra spacing to align the text - // with the previous line. - if (match.IsList) - { - Append(string.Empty.PadLeft(WordLength(match.ListPrefix), CodeCommentHelper.Spacer)); - - // Unset the first-word flag, because this is just padding and - // not a proper word. - _isFirstWord = true; - } + // Unset the first-word flag, because this is just padding and not a + // proper word. + _isFirstWord = true; } - else if (!_isFirstWord) - { - Append(CodeCommentHelper.Spacer); - } - - Append(word); } - } - else - { - // Line without words, create a blank line. - if (!_isFirstWord) + else if (!_isFirstWord) { - NewLine(); + Append(CodeCommentHelper.Spacer); } - NewLine(true); + Append(word); } } - - if (_currentPosition == 0 || _currentPosition > _commentPrefixLength && forceBreak) + else { - // This comment fitted on a single line. - return true; + // Line without words, create a blank line. + if (!_isFirstWord) + { + NewLine(); + } + + NewLine(true); + fittedOnLine = false; } } - // This comment did not fit on a single line. - return false; + return fittedOnLine; } /// /// Returns true if the line requests a break afterwards (did not fit on a single /// line), otherwise false. /// - private bool ParseXml(CommentLineXml line, int indentLevel = 0) + private bool FormatXml(CommentLineXml xml, int indentAmount) { - // All XML lines start on a new line. - if (!_isFirstWord) + var isLiteralContent = !string.IsNullOrEmpty(xml.Content); + var split = xml.TagOptions.Split; + + if (isLiteralContent) + { + // Tags containing literal content with multiple with should always be on their own line. + if (xml.Content.Contains('\n')) + split = XmlTagNewLine.Always; + } + else if ((split == XmlTagNewLine.Default || split == XmlTagNewLine.Content) && xml.Lines.Count > 1) + { + // Split always if there is more than one child line. + split = XmlTagNewLine.Always; + } + + if (split.HasFlag(XmlTagNewLine.BeforeOpen) && !_isFirstWord) { NewLine(); } - Indent(indentLevel); - Append(line.OpenTag); + Indent(indentAmount); + Append(xml.TagOptions.KeepTogether ? CodeCommentHelper.FakeToSpace(xml.OpenTag) : xml.OpenTag); // Self closing tags have no content, skip all further logic and just output. - var tagOnOwnLine = TagsOnOwnLine(line, indentLevel); - if (line.IsSelfClosing) + if (xml.IsSelfClosing) { - if (tagOnOwnLine) + if (split.HasFlag(XmlTagNewLine.AfterClose)) { NewLine(); return false; } + return true; } - // If this is the StyleCop SA1633 header tag, the content should ALWAYS be - // indented. So if no indenting is set, fake it. This is done by adding the indenting to - // the comment prefix, otherwise it would indent recursively. - var isCopyrightTag = indentLevel == 0 && string.Equals(line.TagName, "copyright", StringComparison.OrdinalIgnoreCase); - if (isCopyrightTag && Settings.Default.Formatting_CommentXmlValueIndent < 1) + if (split.HasFlag(XmlTagNewLine.AfterOpen)) { - _commentPrefixLength += CodeCommentHelper.CopyrightExtraIndent; - _commentOptions.Prefix += string.Empty.PadLeft(CodeCommentHelper.CopyrightExtraIndent); + NewLine(); } - // Increase the indent level. - indentLevel++; - - var isLiteralContent = !string.IsNullOrEmpty(line.Content); + // Increase the indenting. + indentAmount += xml.TagOptions.Indent; - // If true the tag should be alone on it's own line. - tagOnOwnLine |= isLiteralContent; - - if (!tagOnOwnLine && Settings.Default.Formatting_CommentXmlSpaceTags) - { - Append(CodeCommentHelper.Spacer); - } - - // If the literal content of an XML tag is set, output that content without formatting. if (isLiteralContent) { - var literals = line.Content.Trim('\r', '\n').TrimEnd('\r', '\n', '\t', ' ').Split('\n'); + // If the literal content of an XML tag is set, output that content without formatting. + var literals = xml.Content.Trim('\r', '\n').TrimEnd('\r', '\n', '\t', ' ').Split('\n'); for (int i = 0; i < literals.Length; i++) { - NewLine(true); + if (i > 0) NewLine(true); Append(literals[i].TrimEnd()); } } else { - // If the tag has any child lines and should be on it's own line, put another break. - if (tagOnOwnLine && line.Lines.Count > 0) + // Else output the child lines. + if (!_isFirstWord && xml.TagOptions.SpaceContent) { - NewLine(); + Append(CodeCommentHelper.Spacer); } - // Loop and parse all content lines, with a little hacky solution for allowing the - // parser to know the XML tag length. - var xmlTagLength = tagOnOwnLine ? 0 : WordLength(line.OpenTag) + WordLength(line.CloseTag); - var needBreakBefore = false; - foreach (var l in line.Lines) - { - if (needBreakBefore) NewLine(); + var xmlTagLength = WordLength(xml.OpenTag) + WordLength(xml.CloseTag) + (xml.TagOptions.SpaceContent ? 2 : 0); - // Parse function returns true if it had to wrap lines. If so, we need to force a - // newline before the closing tag. - needBreakBefore = Parse(l, indentLevel, xmlTagLength); - tagOnOwnLine |= needBreakBefore; + foreach (var line in xml.Lines) + { + if (!Format(line, indentAmount, xmlTagLength)) + split |= XmlTagNewLine.BeforeClose | XmlTagNewLine.AfterClose; } } - indentLevel--; - - // Undo the indenting hack done for copyright tags. - if (isCopyrightTag && Settings.Default.Formatting_CommentXmlValueIndent < 1) - { - _commentPrefixLength -= CodeCommentHelper.CopyrightExtraIndent; - _commentOptions.Prefix = _commentOptions.Prefix.Substring(0, _commentPrefixLength); - } + // Remove the indenting. + indentAmount -= xml.TagOptions.Indent; // If opening tag was on own line, do the same for the closing tag. - if (tagOnOwnLine && !_isFirstWord) + if (split.HasFlag(XmlTagNewLine.BeforeClose)) { NewLine(); - Indent(indentLevel); + Indent(indentAmount); } - else if (Settings.Default.Formatting_CommentXmlSpaceTags) + else if (xml.TagOptions.SpaceContent) { Append(CodeCommentHelper.Spacer); } - Append(line.CloseTag); + Append(xml.CloseTag); - return tagOnOwnLine || CommentLineXml.SingleLineElementNames.Contains(line.TagName, StringComparer.OrdinalIgnoreCase); + if (split.HasFlag(XmlTagNewLine.AfterClose)) + { + NewLine(); + return false; + } + + return true; } - /// - /// Check if the open and close tags for an XML line should be on their own lines or not. - /// - /// - /// - /// true if the tags should be split, otherwise false. - private bool TagsOnOwnLine(CommentLineXml line, int indentlevel) + private void NewLine(bool force = false) { - // Check for splitting all root level tags. - if (Settings.Default.Formatting_CommentXmlSplitAllTags && indentlevel <= 1) - return true; - - // Split if there is more than one child line. - if (line.Lines.Count > 1) - return true; - - // Split if there is literal content (eg. a code tag). - if (!string.IsNullOrEmpty(line.Content)) - return true; - - // Split if this is a summary tag and option to split is set. - if (Settings.Default.Formatting_CommentXmlSplitSummaryTagToMultipleLines && string.Equals(line.TagName, "summary", StringComparison.OrdinalIgnoreCase)) - return true; + if (!_isFirstWord || force) + { + _builder.AppendLine(); + _currentPosition = 0; + } - // Always split on StyleCop SA1633 copyright tag. - if (Settings.Default.Formatting_CommentXmlSplitSummaryTagToMultipleLines && string.Equals(line.TagName, "copyright", StringComparison.OrdinalIgnoreCase)) - return true; + _builder.Append(_commentOptions.Prefix); + _currentPosition += _commentPrefixLength; + _isFirstWord = true; + _isIndented = false; - return false; + // Cannot simply be true, because an empty prefix also means no initial spacing. + _isAfterCommentPrefix = _commentPrefixLength > 0; } /// @@ -464,7 +416,5 @@ private int WordLength(string word) { return word == null ? 0 : word.Length + word.Count(c => c == '\t') * (_formatterOptions.TabSize - 1); } - - #endregion Methods } } \ No newline at end of file diff --git a/CodeMaid/Model/Comments/CommentLineXml.cs b/CodeMaid/Model/Comments/CommentLineXml.cs index 6bd8da971..fb5aedaf6 100644 --- a/CodeMaid/Model/Comments/CommentLineXml.cs +++ b/CodeMaid/Model/Comments/CommentLineXml.cs @@ -1,8 +1,6 @@ using SteveCadwallader.CodeMaid.Helpers; -using SteveCadwallader.CodeMaid.Properties; -using System; +using SteveCadwallader.CodeMaid.Model.Comments.Options; using System.Collections.Generic; -using System.Linq; using System.Text; using System.Text.RegularExpressions; using System.Xml; @@ -12,60 +10,111 @@ namespace SteveCadwallader.CodeMaid.Model.Comments { internal class CommentLineXml : CommentLine { - #region Fields - - // Elements with these names always start and end on their own line, and are only split - // based on content. - internal static string[] SingleLineElementNames = { "p", "para", "list", "listheader", "item", "term", "description", "code" }; - private static Regex InterpunctionRegex = new Regex(@"^[^\w]", RegexOptions.Compiled); - private StringBuilder _innerText; - #endregion Fields + private readonly FormatterOptions _formatterOptions; + private readonly StringBuilder _innerText; - #region Constructors - - public CommentLineXml(XElement xml) + public CommentLineXml(XElement xml, FormatterOptions formatterOptions) : base(null) { + _formatterOptions = formatterOptions; + _innerText = new StringBuilder(); + TagName = xml.Name.LocalName; + TagOptions = _formatterOptions.Xml.GetTagOptions(TagName); - // Tags that are forced to be their own line should never be self closing. This prevents - // empty tags from getting collapsed. - OpenTag = CodeCommentHelper.CreateXmlOpenTag(xml); - CloseTag = CodeCommentHelper.CreateXmlCloseTag(xml); + OpenTag = CreateXmlOpenTag(xml, TagOptions); + CloseTag = CreateXmlCloseTag(xml, TagOptions); IsSelfClosing = CloseTag == null; Lines = new List(); - _innerText = new StringBuilder(); ParseChildNodes(xml); CloseInnerText(); } - #endregion Constructors + public string CloseTag { get; } + + public bool IsSelfClosing { get; } + + public ICollection Lines { get; } - #region Properties + public string OpenTag { get; internal set; } - public string CloseTag { get; private set; } + public string TagName { get; } - public bool IsSelfClosing { get; private set; } + public IXmlTagOptions TagOptions { get; } - public ICollection Lines { get; private set; } + /// + /// Creates the XML close tag string for an XElement. + /// + /// The element. + /// + /// The XML close tag, or null if the element has no value and is a self-closing tag. + /// + private static string CreateXmlCloseTag(XElement element, IXmlTagOptions options) + { + if (element.IsEmpty) + { + return null; + } + + return $""; + } + + /// + /// Creates the XML open tag string for an XElement. + /// + /// The element. + /// The XML open tag. In case of an element without value, the tag is self-closing. + private static string CreateXmlOpenTag(XElement element, IXmlTagOptions options) + { + var builder = new StringBuilder(); + var name = element.Name.LocalName; + + builder.Append("<"); + + builder.Append(TagCase(name, options.Case)); + + if (element.HasAttributes) + { + foreach (var attr in element.Attributes()) + { + builder.Append(CodeCommentHelper.Spacer); + builder.Append(attr); + } + } + + if (element.IsEmpty) + { + if (options.SpaceSelfClosing) + { + builder.Append(CodeCommentHelper.Spacer); + } - public string OpenTag { get; set; } + builder.Append("/"); + } - public string TagName { get; private set; } + builder.Append(">"); - #endregion Properties + var result = builder.ToString(); - #region Methods + return options.KeepTogether ? CodeCommentHelper.SpaceToFake(result) : result; + } private static bool StartsWithInterpunction(string value) { return InterpunctionRegex.IsMatch(value); } + private static string TagCase(string tag, XmlTagCase tagCase) + { + return tagCase == XmlTagCase.LowerCase ? tag.ToLowerInvariant() : + tagCase == XmlTagCase.UpperCase ? tag.ToUpperInvariant() : + tag; + } + /// /// If there is text left in the buffer, parse and append it as a comment line. /// @@ -78,11 +127,24 @@ private void CloseInnerText() } } + private bool NeedsXmlHandling(XElement e) + { + // All root level elements are always on their own line. + if (e.Parent == null || e.Parent.Parent == null) + return true; + + // Some special tags should also always be their own line. + if (_formatterOptions.Xml.Tags.ContainsKey(e.Name.LocalName)) + return true; + + return false; + } + private void ParseChildNodes(XElement xml) { - if (string.Equals(TagName, "code", StringComparison.OrdinalIgnoreCase)) + if (TagOptions.Literal) { - // Content of code element should be read literally and preserve whitespace. + // Read content literally and preserve all formatting. using (var reader = xml.CreateReader()) { reader.MoveToContent(); @@ -98,29 +160,29 @@ private void ParseChildNodes(XElement xml) // If the node is a sub-element, it needs to be handled seperately. if (node.NodeType == XmlNodeType.Element) { - var e = (XElement)node; + var element = (XElement)node; - if (ShouldBeNewLine(e)) + if (NeedsXmlHandling(element)) { CloseInnerText(); - Lines.Add(new CommentLineXml(e)); + Lines.Add(new CommentLineXml(element, _formatterOptions)); } else { // If the tag is not forced to be on it's own line, append it to the // current content as string. - _innerText.Append(CodeCommentHelper.CreateXmlOpenTag(e)); + _innerText.Append(CreateXmlOpenTag(element, TagOptions)); - if (!e.IsEmpty) + if (!element.IsEmpty) { - if (Settings.Default.Formatting_CommentXmlSpaceTags) + if (TagOptions.SpaceContent) { _innerText.Append(CodeCommentHelper.Spacer); } - ParseChildNodes(e); + ParseChildNodes(element); - _innerText.Append(CodeCommentHelper.CreateXmlCloseTag(e)); + _innerText.Append(CreateXmlCloseTag(element, TagOptions)); } } } @@ -130,7 +192,7 @@ private void ParseChildNodes(XElement xml) var value = node.ToString().TrimEnd(CodeCommentHelper.Spacer); // If the parent is an element, trim the starting spaces. - if (node.PreviousNode == null && node.Parent.NodeType == XmlNodeType.Element && !Settings.Default.Formatting_CommentXmlSpaceTags) + if (node.PreviousNode == null && node.Parent.NodeType == XmlNodeType.Element && !TagOptions.SpaceContent) { value = value.TrimStart(CodeCommentHelper.Spacer); } @@ -148,7 +210,7 @@ private void ParseChildNodes(XElement xml) _innerText.Append(value); // Add spacing after (almost) each word. - if (node.NextNode != null || node.Parent.NodeType != XmlNodeType.Element || Settings.Default.Formatting_CommentXmlSpaceTags) + if (node.NextNode != null || node.Parent.NodeType != XmlNodeType.Element || TagOptions.SpaceContent) { _innerText.Append(CodeCommentHelper.Spacer); } @@ -158,20 +220,5 @@ private void ParseChildNodes(XElement xml) } } } - - private bool ShouldBeNewLine(XElement e) - { - // All root level elements are always on their own line. - if (e.Parent == null || e.Parent.Parent == null) - return true; - - // Some special tags should also always be their own line. - if (SingleLineElementNames.Contains(e.Name.LocalName, StringComparer.OrdinalIgnoreCase)) - return true; - - return false; - } - - #endregion Methods } } \ No newline at end of file diff --git a/CodeMaid/Model/Comments/CommentMatch.cs b/CodeMaid/Model/Comments/CommentMatch.cs index fbe4af41c..a1f8a3c29 100644 --- a/CodeMaid/Model/Comments/CommentMatch.cs +++ b/CodeMaid/Model/Comments/CommentMatch.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using SteveCadwallader.CodeMaid.Model.Comments.Options; +using System.Collections.Generic; using System.Linq; using System.Text.RegularExpressions; @@ -6,8 +7,6 @@ namespace SteveCadwallader.CodeMaid.Model.Comments { internal class CodeCommentMatch { - #region Constructors - public CodeCommentMatch(Match match, FormatterOptions formatterOptions) { if (!match.Success) @@ -44,17 +43,13 @@ public CodeCommentMatch(Match match, FormatterOptions formatterOptions) } } - #endregion Constructors - - #region Properties - - public int Indent { get; private set; } + public int Indent { get; } public bool IsEmpty { get; private set; } - public bool IsList { get; private set; } + public bool IsList { get; } - public bool IsLiteral { get; private set; } + public bool IsLiteral { get; } public int Length { @@ -66,13 +61,9 @@ public int Length } } - public string ListPrefix { get; private set; } - - public IList Words { get; private set; } + public string ListPrefix { get; } - #endregion Properties - - #region Methods + public List Words { get; } /// /// Attempt to combine another match with this match. If possible, all words from the other @@ -97,14 +88,10 @@ public bool TryAppend(CodeCommentMatch other) if (IsLiteral || other.IsLiteral) return false; - foreach (var word in other.Words) - Words.Add(word); - + Words.AddRange(other.Words); IsEmpty = Words.Count < 1; return true; } - - #endregion Methods } } \ No newline at end of file diff --git a/CodeMaid/Model/Comments/FormatterOptions.cs b/CodeMaid/Model/Comments/FormatterOptions.cs deleted file mode 100644 index 2ff24770b..000000000 --- a/CodeMaid/Model/Comments/FormatterOptions.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace SteveCadwallader.CodeMaid.Model.Comments -{ - /// - /// Document wide options for the comment formatter. - /// - internal class FormatterOptions - { - public int TabSize { get; internal set; } - - /// - /// The list of comment prefix tokens to ignore while formatting the comment. - /// - public string[] IgnoreTokens { get; internal set; } - } -} \ No newline at end of file diff --git a/CodeMaid/Model/Comments/CommentOptions.cs b/CodeMaid/Model/Comments/Options/CommentOptions.cs similarity index 69% rename from CodeMaid/Model/Comments/CommentOptions.cs rename to CodeMaid/Model/Comments/Options/CommentOptions.cs index 126b415a8..8ae575359 100644 --- a/CodeMaid/Model/Comments/CommentOptions.cs +++ b/CodeMaid/Model/Comments/Options/CommentOptions.cs @@ -1,11 +1,11 @@ using System.Text.RegularExpressions; -namespace SteveCadwallader.CodeMaid.Model.Comments +namespace SteveCadwallader.CodeMaid.Model.Comments.Options { /// /// Comment specific options for the formatter. /// - internal class CommentOptions + internal class CommentOptions : ICommentOptions { public string Prefix { get; internal set; } diff --git a/CodeMaid/Model/Comments/Options/FormatterOptions.cs b/CodeMaid/Model/Comments/Options/FormatterOptions.cs new file mode 100644 index 000000000..d13baea15 --- /dev/null +++ b/CodeMaid/Model/Comments/Options/FormatterOptions.cs @@ -0,0 +1,43 @@ +using SteveCadwallader.CodeMaid.Properties; +using System; + +namespace SteveCadwallader.CodeMaid.Model.Comments.Options +{ + /// + /// Document wide options for the comment formatter. + /// + public class FormatterOptions + { + /// + /// The list of comment prefix tokens to ignore while formatting the comment. + /// + public string[] IgnoreTokens { get; set; } + + /// + /// Do not wrap to a newline for only a single word. + /// + public bool SkipWrapOnLastWord { get; set; } + + public int TabSize { get; set; } = 4; + + public int WrapColumn { get; set; } + + public FormatterOptionsXml Xml { get; set; } + + internal static FormatterOptions FromSettings(Settings settings) + { + return new FormatterOptions + { + WrapColumn = settings.Formatting_CommentWrapColumn, + SkipWrapOnLastWord = settings.Formatting_CommentSkipWrapOnLastWord, + Xml = FormatterOptionsXml.FromSettings(settings) + }; + } + + internal FormatterOptions Set(Action action) + { + action?.Invoke(this); + return this; + } + } +} \ No newline at end of file diff --git a/CodeMaid/Model/Comments/Options/FormatterOptionsXml.cs b/CodeMaid/Model/Comments/Options/FormatterOptionsXml.cs new file mode 100644 index 000000000..e3551762c --- /dev/null +++ b/CodeMaid/Model/Comments/Options/FormatterOptionsXml.cs @@ -0,0 +1,60 @@ +using SteveCadwallader.CodeMaid.Helpers; +using SteveCadwallader.CodeMaid.Properties; +using System; +using System.Collections.Generic; + +namespace SteveCadwallader.CodeMaid.Model.Comments.Options +{ + public class FormatterOptionsXml + { + private readonly static FormatterOptionsXmlTag FormatterOptionsXmlTagOverrideSplitBeforeAfter = new FormatterOptionsXmlTag + { + Split = XmlTagNewLine.BeforeAndAfter + }; + + public FormatterOptionsXml() + { + Tags = new Dictionary(StringComparer.OrdinalIgnoreCase); + Default = new XmlTagOptions(); + } + + /// + /// Whether `param` tags should be all made the same length. + /// + public bool AlignParamTags { get; set; } + + public XmlTagOptions Default { get; set; } + + /// + /// Settings for individual tags. + /// + public Dictionary Tags { get; set; } + + public IXmlTagOptions GetTagOptions(string tagName) + { + return !Tags.TryGetValue(tagName, out var tag) ? Default : new XmlTagOptions(tag, Default); + } + + internal static FormatterOptionsXml FromSettings(Settings settings) + { + return new FormatterOptionsXml + { + AlignParamTags = settings.Formatting_CommentXmlAlignParamTags, + Default = XmlTagOptions.FromSettings(settings), + Tags = new Dictionary + { + ["summary"] = new FormatterOptionsXmlTag { Split = settings.Formatting_CommentXmlSplitSummaryTagToMultipleLines ? XmlTagNewLine.Always : XmlTagNewLine.Default }, + ["copyright"] = new FormatterOptionsXmlTag { Split = XmlTagNewLine.Always, Indent = CodeCommentHelper.CopyrightExtraIndent }, + ["code"] = new FormatterOptionsXmlTag { Split = XmlTagNewLine.BeforeAndAfter, Literal = true }, + ["p"] = FormatterOptionsXmlTagOverrideSplitBeforeAfter, + ["para"] = FormatterOptionsXmlTagOverrideSplitBeforeAfter, + ["list"] = FormatterOptionsXmlTagOverrideSplitBeforeAfter, + ["listheader"] = FormatterOptionsXmlTagOverrideSplitBeforeAfter, + ["item"] = FormatterOptionsXmlTagOverrideSplitBeforeAfter, + ["term"] = FormatterOptionsXmlTagOverrideSplitBeforeAfter, + ["description"] = FormatterOptionsXmlTagOverrideSplitBeforeAfter, + } + }; + } + }; +} \ No newline at end of file diff --git a/CodeMaid/Model/Comments/Options/FormatterOptionsXmlTag.cs b/CodeMaid/Model/Comments/Options/FormatterOptionsXmlTag.cs new file mode 100644 index 000000000..3692ec9f7 --- /dev/null +++ b/CodeMaid/Model/Comments/Options/FormatterOptionsXmlTag.cs @@ -0,0 +1,31 @@ +namespace SteveCadwallader.CodeMaid.Model.Comments.Options +{ + public class FormatterOptionsXmlTag + { + /// + /// If not , overrides the default tag case setting. + /// + public XmlTagCase Case { get; set; } + + /// + /// If not null, overrides the default tag indentation. + /// + public int? Indent { get; set; } + + public bool? KeepTogether { get; set; } + + /// + /// Whether the content should be kept literally and not formatted. + /// + public bool? Literal { get; set; } + + public bool? SpaceContent { get; set; } + + public bool? SpaceSelfClosing { get; set; } + + /// + /// If not , overrides the default tag split setting. + /// + public XmlTagNewLine Split { get; set; } + } +} \ No newline at end of file diff --git a/CodeMaid/Model/Comments/Options/ICommentOptions.cs b/CodeMaid/Model/Comments/Options/ICommentOptions.cs new file mode 100644 index 000000000..f7179c939 --- /dev/null +++ b/CodeMaid/Model/Comments/Options/ICommentOptions.cs @@ -0,0 +1,11 @@ +using System.Text.RegularExpressions; + +namespace SteveCadwallader.CodeMaid.Model.Comments.Options +{ + internal interface ICommentOptions + { + string Prefix { get; } + + Regex Regex { get; } + } +} \ No newline at end of file diff --git a/CodeMaid/Model/Comments/Options/IXmlTagOptions.cs b/CodeMaid/Model/Comments/Options/IXmlTagOptions.cs new file mode 100644 index 000000000..c42edb9cf --- /dev/null +++ b/CodeMaid/Model/Comments/Options/IXmlTagOptions.cs @@ -0,0 +1,19 @@ +namespace SteveCadwallader.CodeMaid.Model.Comments.Options +{ + public interface IXmlTagOptions + { + XmlTagCase Case { get; } + + int Indent { get; } + + bool KeepTogether { get; } + + bool Literal { get; } + + bool SpaceContent { get; } + + bool SpaceSelfClosing { get; } + + XmlTagNewLine Split { get; } + } +} \ No newline at end of file diff --git a/CodeMaid/Model/Comments/Options/XmlTagCase.cs b/CodeMaid/Model/Comments/Options/XmlTagCase.cs new file mode 100644 index 000000000..124955ef1 --- /dev/null +++ b/CodeMaid/Model/Comments/Options/XmlTagCase.cs @@ -0,0 +1,14 @@ +namespace SteveCadwallader.CodeMaid.Model.Comments.Options +{ + public enum XmlTagCase + { + /// + /// Use formatter default settings. + /// + Default = 0, + + Keep, + LowerCase, + UpperCase + } +} \ No newline at end of file diff --git a/CodeMaid/Model/Comments/Options/XmlTagNewLine.cs b/CodeMaid/Model/Comments/Options/XmlTagNewLine.cs new file mode 100644 index 000000000..7f8a97cc8 --- /dev/null +++ b/CodeMaid/Model/Comments/Options/XmlTagNewLine.cs @@ -0,0 +1,34 @@ +using System; + +namespace SteveCadwallader.CodeMaid.Model.Comments.Options +{ + [Flags] + public enum XmlTagNewLine + { + /// + /// Use formatter default settings. + /// + Default = 0, + + /// + /// Put tags on their own line when content is too long to fit on a single line. + /// + Content = 1 << 0, + + BeforeOpen = 1 << 1, + AfterOpen = 1 << 2, + + BeforeClose = 1 << 3, + AfterClose = 1 << 4, + + /// + /// Force a break before the open tag and after the close tag, regardless of content length. + /// + BeforeAndAfter = BeforeOpen | AfterClose, + + /// + /// Force a break before and after the open and close tags, ie put tags on their own lines. + /// + Always = BeforeOpen | AfterOpen | BeforeClose | AfterClose + } +} \ No newline at end of file diff --git a/CodeMaid/Model/Comments/Options/XmlTagOptions.cs b/CodeMaid/Model/Comments/Options/XmlTagOptions.cs new file mode 100644 index 000000000..4f60fc85c --- /dev/null +++ b/CodeMaid/Model/Comments/Options/XmlTagOptions.cs @@ -0,0 +1,50 @@ +using SteveCadwallader.CodeMaid.Properties; + +namespace SteveCadwallader.CodeMaid.Model.Comments.Options +{ + public class XmlTagOptions : IXmlTagOptions + { + public XmlTagOptions() + { + } + + public XmlTagOptions(FormatterOptionsXmlTag tag, IXmlTagOptions fallback) + { + Case = tag.Case != XmlTagCase.Default ? tag.Case : fallback.Case != XmlTagCase.Default ? fallback.Case : XmlTagCase.Keep; + Indent = tag.Indent ?? fallback.Indent; + KeepTogether = tag.KeepTogether ?? fallback.KeepTogether; + Literal = tag.Literal ?? false; + SpaceContent = tag.SpaceContent ?? fallback.SpaceContent; + SpaceSelfClosing = tag.SpaceSelfClosing ?? fallback.SpaceSelfClosing; + Split = tag.Split != XmlTagNewLine.Default ? tag.Split : fallback.Split != XmlTagNewLine.Default ? fallback.Split : XmlTagNewLine.Content; + } + + public XmlTagCase Case { get; set; } + + public int Indent { get; set; } + + public bool KeepTogether { get; set; } + + public bool Literal { get; set; } + + public bool SpaceContent { get; set; } + + public bool SpaceSelfClosing { get; set; } + + public XmlTagNewLine Split { get; set; } + + internal static XmlTagOptions FromSettings(Settings settings) + { + return new XmlTagOptions + { + Case = settings.Formatting_CommentXmlTagsToLowerCase ? XmlTagCase.LowerCase : XmlTagCase.Keep, + Indent = settings.Formatting_CommentXmlValueIndent, + KeepTogether = settings.Formatting_CommentXmlKeepTagsTogether, + Literal = false, + SpaceContent = settings.Formatting_CommentXmlSpaceTags, + SpaceSelfClosing = settings.Formatting_CommentXmlSpaceSingleTags, + Split = settings.Formatting_CommentXmlSplitAllTags ? XmlTagNewLine.Always : XmlTagNewLine.Content + }; + } + } +} \ No newline at end of file diff --git a/CodeMaid/Properties/Resources.Designer.cs b/CodeMaid/Properties/Resources.Designer.cs index 0cc8f7bf0..8f34da4c1 100644 --- a/CodeMaid/Properties/Resources.Designer.cs +++ b/CodeMaid/Properties/Resources.Designer.cs @@ -1610,11 +1610,11 @@ public static string PlaceExplicitInterfaceMembersAtTheEndOfTheirGroup { } /// - /// Looks up a localized string similar to Preview (reopen to refresh). + /// Looks up a localized string similar to Preview. /// - public static string PreviewReopenToRefresh { + public static string Preview { get { - return ResourceManager.GetString("PreviewReopenToRefresh", resourceCulture); + return ResourceManager.GetString("Preview", resourceCulture); } } diff --git a/CodeMaid/Properties/Resources.en-US.resx b/CodeMaid/Properties/Resources.en-US.resx index c51b42157..76117d432 100644 --- a/CodeMaid/Properties/Resources.en-US.resx +++ b/CodeMaid/Properties/Resources.en-US.resx @@ -633,8 +633,8 @@ Place explicit interface members at the end of their group - - Preview (reopen to refresh) + + Preview Primary ordering should be by diff --git a/CodeMaid/Properties/Resources.resx b/CodeMaid/Properties/Resources.resx index c51b42157..76117d432 100644 --- a/CodeMaid/Properties/Resources.resx +++ b/CodeMaid/Properties/Resources.resx @@ -633,8 +633,8 @@ Place explicit interface members at the end of their group - - Preview (reopen to refresh) + + Preview Primary ordering should be by diff --git a/CodeMaid/Properties/Resources.zh-Hans.resx b/CodeMaid/Properties/Resources.zh-Hans.resx index c8d9cfd1e..1b6896306 100644 --- a/CodeMaid/Properties/Resources.zh-Hans.resx +++ b/CodeMaid/Properties/Resources.zh-Hans.resx @@ -633,8 +633,8 @@ 在其组的末尾放置显式接口成员 - - 预览 (重新打开以刷新) + + 预览 主要排序应由 diff --git a/CodeMaid/UI/Dialogs/Options/Formatting/FormattingDataTemplate.xaml b/CodeMaid/UI/Dialogs/Options/Formatting/FormattingDataTemplate.xaml index 8890aeef2..48a64aa3a 100644 --- a/CodeMaid/UI/Dialogs/Options/Formatting/FormattingDataTemplate.xaml +++ b/CodeMaid/UI/Dialogs/Options/Formatting/FormattingDataTemplate.xaml @@ -31,7 +31,7 @@ - + + { + o.WrapColumn = CommentWrapColumn; + o.SkipWrapOnLastWord = CommentSkipWrapOnLastWord; + + o.Xml.AlignParamTags = CommentXmlAlignParamTags; + + o.Xml.Default.Case = CommentXmlTagsToLowerCase ? XmlTagCase.LowerCase : XmlTagCase.Keep; + o.Xml.Default.Indent = CommentXmlValueIndent; + o.Xml.Default.KeepTogether = CommentXmlKeepTagsTogether; + o.Xml.Default.SpaceContent = CommentXmlSpaceTags; + o.Xml.Default.SpaceSelfClosing = CommentXmlSpaceSingleTags; + + o.Xml.Default.Split = CommentXmlSplitAllTags ? XmlTagNewLine.Always : XmlTagNewLine.Content; + o.Xml.Tags["summary"] = new FormatterOptionsXmlTag { Split = CommentXmlSplitSummaryTagToMultipleLines ? XmlTagNewLine.Always : XmlTagNewLine.Content }; + }); } #endregion Preview Text and Helpers From d7c0c8179b50906d8d785e418f41463224645dfa Mon Sep 17 00:00:00 2001 From: Willem Date: Tue, 29 May 2018 15:16:57 +0200 Subject: [PATCH 02/20] Fix missing call to keep XML tags together --- CodeMaid/Model/Comments/CommentFormatter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodeMaid/Model/Comments/CommentFormatter.cs b/CodeMaid/Model/Comments/CommentFormatter.cs index 0817d47ae..9ac70b376 100644 --- a/CodeMaid/Model/Comments/CommentFormatter.cs +++ b/CodeMaid/Model/Comments/CommentFormatter.cs @@ -270,7 +270,7 @@ private bool Format(ICommentLine line, int indentAmount = 0, int xmlTagLength = Append(CodeCommentHelper.Spacer); } - Append(word); + Append(CodeCommentHelper.FakeToSpace(word)); } } else From 6fd26c983ec103726ceb3f1e80c3c5ae946255e0 Mon Sep 17 00:00:00 2001 From: Willem Date: Mon, 11 Feb 2019 08:45:25 +0100 Subject: [PATCH 03/20] Update `XmlFormattingTests` in preparation of merger --- .../Formatting/XmlFormattingTests.cs | 316 ++++++++++++------ 1 file changed, 207 insertions(+), 109 deletions(-) diff --git a/CodeMaid.UnitTests/Formatting/XmlFormattingTests.cs b/CodeMaid.UnitTests/Formatting/XmlFormattingTests.cs index 69d73132f..41d8434b8 100644 --- a/CodeMaid.UnitTests/Formatting/XmlFormattingTests.cs +++ b/CodeMaid.UnitTests/Formatting/XmlFormattingTests.cs @@ -1,5 +1,4 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; -using SteveCadwallader.CodeMaid.Model.Comments; using SteveCadwallader.CodeMaid.Model.Comments.Options; using SteveCadwallader.CodeMaid.Properties; using System; @@ -47,32 +46,32 @@ public void XmlFormattingTests_AddSpaceToTagContent() [TestMethod] [TestCategory("Formatting UnitTests")] - public void XmlFormattingTests_AddSpaceToTagContentShouldLeaveNoTrailingWhitespace() + public void XmlFormattingTests_AddSpaceToTagContentWithSelfClosingTag() { - var input = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; - var expected = - "" + Environment.NewLine + - "Lorem ipsum dolor sit amet," + Environment.NewLine + - "consectetur adipiscing elit." + Environment.NewLine + - ""; + var input = ""; + var expected = " "; CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => { - o.WrapColumn = 30; - o.Xml.Default.Split = XmlTagNewLine.Always; o.Xml.Default.SpaceContent = true; + o.Xml.Default.SpaceSelfClosing = false; }); } [TestMethod] [TestCategory("Formatting UnitTests")] - public void XmlFormattingTests_AddSpaceToTagContentWithSelfClosingTag() + public void XmlFormattingTests_AddSpaceToTagContentWithSelfClosingTagMultiline() { + // Add space to content should not add a space when tag content is on it's own line. var input = ""; - var expected = " "; + var expected = + "" + Environment.NewLine + + "" + Environment.NewLine + + ""; CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => { + o.Xml.Default.Split = XmlTagNewLine.Always; o.Xml.Default.SpaceContent = true; o.Xml.Default.SpaceSelfClosing = false; }); @@ -80,20 +79,44 @@ public void XmlFormattingTests_AddSpaceToTagContentWithSelfClosingTag() [TestMethod] [TestCategory("Formatting UnitTests")] - public void XmlFormattingTests_AddSpaceToTagContentWithSelfClosingTagMultiline() + public void XmlFormattingTests_AddSpaceToTagContentShouldLeaveNoTrailingWhitespace1() { - // Add space to content should not add a space when tag content is on it's own line. - var input = ""; + var input = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."; var expected = - "" + Environment.NewLine + - "" + Environment.NewLine + - ""; + "" + Environment.NewLine + + "Lorem ipsum dolor sit amet," + Environment.NewLine + + "consectetur adipiscing elit." + Environment.NewLine + + ""; CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => { + o.WrapColumn = 30; o.Xml.Default.Split = XmlTagNewLine.Always; o.Xml.Default.SpaceContent = true; - o.Xml.Default.SpaceSelfClosing = false; + }); + } + + [TestMethod] + [TestCategory("Formatting UnitTests")] + [Ignore] // This is temporarily ignored until a better fix for #564 is found. + public void XmlFormattingTests_AddSpaceToTagContentShouldLeaveNoTrailingWhitespace2() + { + var input = + "" + Environment.NewLine + + "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + Environment.NewLine + + ""; + + var expected = + "" + Environment.NewLine + + " Lorem ipsum dolor sit amet, consectetur" + Environment.NewLine + + " adipiscing elit." + Environment.NewLine + + ""; + + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => + { + o.WrapColumn = 50; + o.Xml.Default.Indent = 4; + o.Xml.Default.SpaceContent = true; }); } @@ -156,6 +179,43 @@ public void XmlFormattingTests_BreakTagsWhenContainsParagraphs() CommentFormatHelper.AssertEqualAfterFormat(input, expected); } + /// + /// If XML tag indenting is set, this should not affect any literal content. However, content + /// after the literal should be indented as normal. + /// + [TestMethod] + [TestCategory("Formatting UnitTests")] + public void XmlFormattingTests_DoesIndentAfterLiteralContent() + { + var input = + "" + Environment.NewLine + + "Example usage :" + Environment.NewLine + + "" + Environment.NewLine + + "Example usage with a location parameter and a location function:" + Environment.NewLine + + "" + Environment.NewLine + + "And some final text that should also be formatted." + Environment.NewLine + + ""; + + var expected = + "" + Environment.NewLine + + " Example usage :" + Environment.NewLine + + " " + Environment.NewLine + + " Example usage with a location parameter and a location function:" + Environment.NewLine + + " " + Environment.NewLine + + " And some final text that should also be formatted." + Environment.NewLine + + ""; + + Settings.Default.Formatting_CommentXmlValueIndent = 4; + Settings.Default.Formatting_CommentXmlKeepTagsTogether = true; + Settings.Default.Formatting_CommentXmlSpaceSingleTags = false; + + // First pass. + var result = CommentFormatHelper.AssertEqualAfterFormat(input, expected); + + // Second pass. + CommentFormatHelper.AssertEqualAfterFormat(result, expected); + } + [TestMethod] [TestCategory("Formatting UnitTests")] public void XmlFormattingTests_DoesNotIndentCloseTag() @@ -176,6 +236,45 @@ public void XmlFormattingTests_DoesNotIndentCloseTag() }); } + /// + /// If XML tag indenting is set, this should not affect any literal content. Since whitespace + /// is preserved on literals, this would increase the indenting with every pass. + /// + [TestMethod] + [TestCategory("Formatting UnitTests")] + public void XmlFormattingTests_DoesNotIndentLiteralContent() + { + var input = + "" + Environment.NewLine + + "" + Environment.NewLine + + " Some code with." + Environment.NewLine + + " funny indenting" + Environment.NewLine + + "" + Environment.NewLine + + " and a white line" + Environment.NewLine + + "that should not change." + Environment.NewLine + + "" + Environment.NewLine + + ""; + + var expected = + "" + Environment.NewLine + + " " + Environment.NewLine + + " Some code with." + Environment.NewLine + + " funny indenting" + Environment.NewLine + + "" + Environment.NewLine + + " and a white line" + Environment.NewLine + + "that should not change." + Environment.NewLine + + " " + Environment.NewLine + + ""; + + Settings.Default.Formatting_CommentXmlValueIndent = 4; + + // First pass. + var result = CommentFormatHelper.AssertEqualAfterFormat(input, expected); + + // Second pass. + CommentFormatHelper.AssertEqualAfterFormat(result, expected); + } + [TestMethod] [TestCategory("Formatting UnitTests")] public void XmlFormattingTests_DoNotAutoCollapseTags() @@ -210,17 +309,16 @@ public void XmlFormattingTests_HyperlinkOnNewLine() [TestCategory("Formatting UnitTests")] public void XmlFormattingTests_IndentsXml() { - var input = "Lorem ipsum dolor sit amet."; + var input = "Lorem ipsum dolor sit amet."; var expected = - "" + Environment.NewLine + + "" + Environment.NewLine + " Lorem ipsum dolor sit amet." + Environment.NewLine + - ""; + ""; - CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => - { - o.Xml.Default.Indent = 4; - o.Xml.Tags["xml"] = new FormatterOptionsXmlTag { Split = XmlTagNewLine.Always }; - }); + Settings.Default.Formatting_CommentXmlSplitSummaryTagToMultipleLines = true; + Settings.Default.Formatting_CommentXmlValueIndent = 4; + + CommentFormatHelper.AssertEqualAfterFormat(input, expected); } [TestMethod] @@ -296,6 +394,88 @@ public void XmlFormattingTests_KeepShortParagraphs() CommentFormatHelper.AssertEqualAfterFormat(input, expected); } + [TestMethod] + [TestCategory("Formatting UnitTests")] + public void XmlFormattingTests_RemoveSpaceFromInsideTags() + { + var input = ""; + var expected = ""; + + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => o.Xml.Default.SpaceSelfClosing = false); + } + + [TestMethod] + [TestCategory("Formatting UnitTests")] + public void XmlFormattingTests_RemoveSpaceFromTagContent() + { + var input = " test "; + var expected = "test"; + + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => o.Xml.Default.SpaceContent = false); + } + + [TestMethod] + [TestCategory("Formatting UnitTests")] + public void XmlFormattingTests_SplitAlwaysOnSingleTag() + { + var input = ""; + var expected = + "" + Environment.NewLine + + "" + Environment.NewLine + + ""; + + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => + { + o.Xml.Default.Indent = 0; + o.Xml.Tags.Clear(); + o.Xml.Tags["tag1"] = new FormatterOptionsXmlTag { Split = XmlTagNewLine.Always }; + }); + } + + [TestMethod] + [TestCategory("Formatting UnitTests")] + public void XmlFormattingTests_SplitsTagsWhenLineDoesNotFit() + { + var input = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus nisi neque, placerat sed neque vitae"; + var expected = "" + Environment.NewLine + + "Lorem ipsum dolor sit amet, consectetur adipiscing" + Environment.NewLine + + "elit. Vivamus nisi neque, placerat sed neque vitae" + Environment.NewLine + + ""; + + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => + { + o.WrapColumn = 50; + o.SkipWrapOnLastWord = false; + }); + } + + [TestMethod] + [TestCategory("Formatting UnitTests")] + public void XmlFormattingTests_TagCase_Keep() + { + var input = ""; + + CommentFormatHelper.AssertEqualAfterFormat(input, o => o.Xml.Default.Case = XmlTagCase.Keep); + } + + [TestMethod] + [TestCategory("Formatting UnitTests")] + public void XmlFormattingTests_TagCase_Lower() + { + var input = ""; + var expected = ""; + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => o.Xml.Default.Case = XmlTagCase.LowerCase); + } + + [TestMethod] + [TestCategory("Formatting UnitTests")] + public void XmlFormattingTests_TagCase_Upper() + { + var input = ""; + var expected = ""; + CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => o.Xml.Default.Case = XmlTagCase.UpperCase); + } + /// /// If XML tag indenting is set, this should not affect any literal content. Since whitespace /// is preserved on literals, this would increase the indenting with every pass. @@ -400,87 +580,5 @@ public void XmlFormattingTests_Literal_KeepFormatting() CommentFormatHelper.AssertEqualAfterFormat(input, expected); } - - [TestMethod] - [TestCategory("Formatting UnitTests")] - public void XmlFormattingTests_RemoveSpaceFromInsideTags() - { - var input = ""; - var expected = ""; - - CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => o.Xml.Default.SpaceSelfClosing = false); - } - - [TestMethod] - [TestCategory("Formatting UnitTests")] - public void XmlFormattingTests_RemoveSpaceFromTagContent() - { - var input = " test "; - var expected = "test"; - - CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => o.Xml.Default.SpaceContent = false); - } - - [TestMethod] - [TestCategory("Formatting UnitTests")] - public void XmlFormattingTests_SplitAlwaysOnSingleTag() - { - var input = ""; - var expected = - "" + Environment.NewLine + - "" + Environment.NewLine + - ""; - - CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => - { - o.Xml.Default.Indent = 0; - o.Xml.Tags.Clear(); - o.Xml.Tags["tag1"] = new FormatterOptionsXmlTag { Split = XmlTagNewLine.Always }; - }); - } - - [TestMethod] - [TestCategory("Formatting UnitTests")] - public void XmlFormattingTests_SplitsTagsWhenLineDoesNotFit() - { - var input = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus nisi neque, placerat sed neque vitae"; - var expected = "" + Environment.NewLine + - "Lorem ipsum dolor sit amet, consectetur adipiscing" + Environment.NewLine + - "elit. Vivamus nisi neque, placerat sed neque vitae" + Environment.NewLine + - ""; - - CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => - { - o.WrapColumn = 50; - o.SkipWrapOnLastWord = false; - }); - } - - [TestMethod] - [TestCategory("Formatting UnitTests")] - public void XmlFormattingTests_TagCase_Keep() - { - var input = ""; - - CommentFormatHelper.AssertEqualAfterFormat(input, o => o.Xml.Default.Case = XmlTagCase.Keep); - } - - [TestMethod] - [TestCategory("Formatting UnitTests")] - public void XmlFormattingTests_TagCase_Lower() - { - var input = ""; - var expected = ""; - CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => o.Xml.Default.Case = XmlTagCase.LowerCase); - } - - [TestMethod] - [TestCategory("Formatting UnitTests")] - public void XmlFormattingTests_TagCase_Upper() - { - var input = ""; - var expected = ""; - CommentFormatHelper.AssertEqualAfterFormat(input, expected, o => o.Xml.Default.Case = XmlTagCase.UpperCase); - } } } \ No newline at end of file From 6712e31f03df6d85dc9eb9b950bb4c48fe25cc53 Mon Sep 17 00:00:00 2001 From: Willem Date: Mon, 11 Feb 2019 08:59:38 +0100 Subject: [PATCH 04/20] Unignore test --- CodeMaid.UnitTests/Formatting/XmlFormattingTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/CodeMaid.UnitTests/Formatting/XmlFormattingTests.cs b/CodeMaid.UnitTests/Formatting/XmlFormattingTests.cs index 41d8434b8..9936aeaae 100644 --- a/CodeMaid.UnitTests/Formatting/XmlFormattingTests.cs +++ b/CodeMaid.UnitTests/Formatting/XmlFormattingTests.cs @@ -98,7 +98,6 @@ public void XmlFormattingTests_AddSpaceToTagContentShouldLeaveNoTrailingWhitespa [TestMethod] [TestCategory("Formatting UnitTests")] - [Ignore] // This is temporarily ignored until a better fix for #564 is found. public void XmlFormattingTests_AddSpaceToTagContentShouldLeaveNoTrailingWhitespace2() { var input = From 8b9f84f7eb101e4682f323c487c18b755d7f7446 Mon Sep 17 00:00:00 2001 From: Willem Date: Mon, 11 Feb 2019 09:29:04 +0100 Subject: [PATCH 05/20] No newline after last line, fixes #599 --- CodeMaid/Model/Comments/CodeComment.cs | 2 +- CodeMaid/Model/Comments/CommentFormatter.cs | 8 +++++--- CodeMaid/Model/Comments/CommentLine.cs | 8 +------- CodeMaid/Model/Comments/CommentLineXml.cs | 10 +++++----- CodeMaid/Model/Comments/ICommentLine.cs | 4 +--- 5 files changed, 13 insertions(+), 19 deletions(-) diff --git a/CodeMaid/Model/Comments/CodeComment.cs b/CodeMaid/Model/Comments/CodeComment.cs index 3ffa18ac8..708af41ca 100644 --- a/CodeMaid/Model/Comments/CodeComment.cs +++ b/CodeMaid/Model/Comments/CodeComment.cs @@ -16,8 +16,8 @@ internal class CodeComment { #region Fields - private readonly TextDocument _document; private readonly Regex _commentLineRegex; + private readonly TextDocument _document; private readonly FormatterOptions _formatterOptions; private EditPoint _endPoint; diff --git a/CodeMaid/Model/Comments/CommentFormatter.cs b/CodeMaid/Model/Comments/CommentFormatter.cs index 9ac70b376..68282563e 100644 --- a/CodeMaid/Model/Comments/CommentFormatter.cs +++ b/CodeMaid/Model/Comments/CommentFormatter.cs @@ -12,10 +12,9 @@ namespace SteveCadwallader.CodeMaid.Model.Comments /// internal class CommentFormatter : IEquatable { + private readonly StringBuilder _builder; private readonly CommentOptions _commentOptions; private readonly FormatterOptions _formatterOptions; - - private readonly StringBuilder _builder; private int _commentPrefixLength; private int _currentPosition; private bool _isAfterCommentPrefix; @@ -383,7 +382,10 @@ private bool FormatXml(CommentLineXml xml, int indentAmount) if (split.HasFlag(XmlTagNewLine.AfterClose)) { - NewLine(); + if (!xml.IsLast) + { + NewLine(); + } return false; } diff --git a/CodeMaid/Model/Comments/CommentLine.cs b/CodeMaid/Model/Comments/CommentLine.cs index d0085399a..e1fd1f82a 100644 --- a/CodeMaid/Model/Comments/CommentLine.cs +++ b/CodeMaid/Model/Comments/CommentLine.cs @@ -2,8 +2,6 @@ { internal class CommentLine : ICommentLine { - #region Constructors - public CommentLine(string content) { if (!string.IsNullOrWhiteSpace(content)) @@ -12,12 +10,8 @@ public CommentLine(string content) } } - #endregion Constructors - - #region Properties - public string Content { get; protected set; } - #endregion Properties + public bool IsLast { get; internal set; } } } \ No newline at end of file diff --git a/CodeMaid/Model/Comments/CommentLineXml.cs b/CodeMaid/Model/Comments/CommentLineXml.cs index fb5aedaf6..c2cc59c6f 100644 --- a/CodeMaid/Model/Comments/CommentLineXml.cs +++ b/CodeMaid/Model/Comments/CommentLineXml.cs @@ -29,9 +29,9 @@ public CommentLineXml(XElement xml, FormatterOptions formatterOptions) IsSelfClosing = CloseTag == null; Lines = new List(); - ParseChildNodes(xml); - CloseInnerText(); + CloseInnerText(true); + IsLast = xml.NextNode == null; } public string CloseTag { get; } @@ -118,11 +118,11 @@ private static string TagCase(string tag, XmlTagCase tagCase) /// /// If there is text left in the buffer, parse and append it as a comment line. /// - private void CloseInnerText() + private void CloseInnerText(bool isLast) { if (_innerText.Length > 0) { - Lines.Add(new CommentLine(_innerText.ToString())); + Lines.Add(new CommentLine(_innerText.ToString()) { IsLast = isLast }); _innerText.Clear(); } } @@ -164,7 +164,7 @@ private void ParseChildNodes(XElement xml) if (NeedsXmlHandling(element)) { - CloseInnerText(); + CloseInnerText(false); Lines.Add(new CommentLineXml(element, _formatterOptions)); } else diff --git a/CodeMaid/Model/Comments/ICommentLine.cs b/CodeMaid/Model/Comments/ICommentLine.cs index a9f7291d6..5d685c293 100644 --- a/CodeMaid/Model/Comments/ICommentLine.cs +++ b/CodeMaid/Model/Comments/ICommentLine.cs @@ -2,10 +2,8 @@ { internal interface ICommentLine { - #region Properties - string Content { get; } - #endregion Properties + bool IsLast { get; } } } \ No newline at end of file From 33c7a4d7ed170c5015e2f23b150e0aa6250ae22e Mon Sep 17 00:00:00 2001 From: Willem Date: Tue, 12 Feb 2019 18:58:24 +0100 Subject: [PATCH 06/20] Move tag spacing code around to fix trailing whitespace issue --- CodeMaid/Model/Comments/CommentFormatter.cs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/CodeMaid/Model/Comments/CommentFormatter.cs b/CodeMaid/Model/Comments/CommentFormatter.cs index 68282563e..65b772a69 100644 --- a/CodeMaid/Model/Comments/CommentFormatter.cs +++ b/CodeMaid/Model/Comments/CommentFormatter.cs @@ -128,10 +128,13 @@ private void Append(string value) /// The length of the enclosing XML tags, this is needed to calculate the line length for /// single line XML comments. /// + /// + /// Set to true when parent is an XML tag and wants space between tags and content. + /// /// /// true if line fitted on single line, false if it wrapped on multiple lines. /// - private bool Format(ICommentLine line, int indentAmount = 0, int xmlTagLength = 0) + private bool Format(ICommentLine line, int indentAmount = 0, int xmlTagLength = 0, bool xmlSpaceParentTagContent = false) { if (line is CommentLineXml xml) { @@ -194,6 +197,11 @@ private bool Format(ICommentLine line, int indentAmount = 0, int xmlTagLength = NewLine(); fittedOnLine = false; } + else if (!_isFirstWord && xmlSpaceParentTagContent) + { + // Parent is XML tag and wants space between tags and content. + Append(CodeCommentHelper.Spacer); + } // Always consider the word after the opening tag as the first word to prevent an extra // space before. @@ -350,16 +358,11 @@ private bool FormatXml(CommentLineXml xml, int indentAmount) else { // Else output the child lines. - if (!_isFirstWord && xml.TagOptions.SpaceContent) - { - Append(CodeCommentHelper.Spacer); - } - var xmlTagLength = WordLength(xml.OpenTag) + WordLength(xml.CloseTag) + (xml.TagOptions.SpaceContent ? 2 : 0); foreach (var line in xml.Lines) { - if (!Format(line, indentAmount, xmlTagLength)) + if (!Format(line, indentAmount, xmlTagLength, xml.TagOptions.SpaceContent)) split |= XmlTagNewLine.BeforeClose | XmlTagNewLine.AfterClose; } } @@ -386,6 +389,7 @@ private bool FormatXml(CommentLineXml xml, int indentAmount) { NewLine(); } + return false; } From 4687121b306211a0a5efa2033ee2e3e3905e94be Mon Sep 17 00:00:00 2001 From: Willem Date: Wed, 13 Feb 2019 15:41:31 +0100 Subject: [PATCH 07/20] Remove unused `ICommentOptions` interface --- CodeMaid/CodeMaid.csproj | 1 - CodeMaid/Model/Comments/Options/CommentOptions.cs | 2 +- CodeMaid/Model/Comments/Options/ICommentOptions.cs | 11 ----------- 3 files changed, 1 insertion(+), 13 deletions(-) delete mode 100644 CodeMaid/Model/Comments/Options/ICommentOptions.cs diff --git a/CodeMaid/CodeMaid.csproj b/CodeMaid/CodeMaid.csproj index 6337fc970..abc4ed8da 100644 --- a/CodeMaid/CodeMaid.csproj +++ b/CodeMaid/CodeMaid.csproj @@ -293,7 +293,6 @@ - diff --git a/CodeMaid/Model/Comments/Options/CommentOptions.cs b/CodeMaid/Model/Comments/Options/CommentOptions.cs index 8ae575359..b2b55e161 100644 --- a/CodeMaid/Model/Comments/Options/CommentOptions.cs +++ b/CodeMaid/Model/Comments/Options/CommentOptions.cs @@ -5,7 +5,7 @@ namespace SteveCadwallader.CodeMaid.Model.Comments.Options /// /// Comment specific options for the formatter. /// - internal class CommentOptions : ICommentOptions + internal class CommentOptions { public string Prefix { get; internal set; } diff --git a/CodeMaid/Model/Comments/Options/ICommentOptions.cs b/CodeMaid/Model/Comments/Options/ICommentOptions.cs deleted file mode 100644 index f7179c939..000000000 --- a/CodeMaid/Model/Comments/Options/ICommentOptions.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Text.RegularExpressions; - -namespace SteveCadwallader.CodeMaid.Model.Comments.Options -{ - internal interface ICommentOptions - { - string Prefix { get; } - - Regex Regex { get; } - } -} \ No newline at end of file From e4befb4848fb2cb909a18cafdcb3d790c5d4a602 Mon Sep 17 00:00:00 2001 From: Steve Cadwallader Date: Sat, 13 Apr 2019 11:18:58 +0000 Subject: [PATCH 08/20] Use an open-ended version range for CoreEditor per https://devblogs.microsoft.com/visualstudio/visual-studio-extensions-and-version-ranges-demystified/ recommendation. --- CodeMaid/source.extension.vsixmanifest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodeMaid/source.extension.vsixmanifest b/CodeMaid/source.extension.vsixmanifest index a4252abc1..bc1dbeb84 100644 --- a/CodeMaid/source.extension.vsixmanifest +++ b/CodeMaid/source.extension.vsixmanifest @@ -17,7 +17,7 @@ - + From 0cafd9433db0b45e060e4b7f06658d62b9fdf870 Mon Sep 17 00:00:00 2001 From: Steve Cadwallader Date: Sat, 13 Apr 2019 11:23:29 +0000 Subject: [PATCH 09/20] Clarify the minimum required version of VS2017 as v15.9 since we have NuGet references expecting it. --- CodeMaid/source.extension.vsixmanifest | 44 +++++++++++++------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/CodeMaid/source.extension.vsixmanifest b/CodeMaid/source.extension.vsixmanifest index bc1dbeb84..5278fd425 100644 --- a/CodeMaid/source.extension.vsixmanifest +++ b/CodeMaid/source.extension.vsixmanifest @@ -1,25 +1,25 @@  - - - CodeMaid - CodeMaid is an open source Visual Studio extension to cleanup and simplify our C#, C++, F#, VB, PHP, PowerShell, R, JSON, XAML, XML, ASP, HTML, CSS, LESS, SCSS, JavaScript and TypeScript coding. - http://www.codemaid.net/ - LICENSE.txt - CodeMaid.png - CodeMaid_Large.png - build, code, c#, beautify, cleanup, cleaning, digging, reorganizing, formatting - - - - - - - - - - - - - + + + CodeMaid + CodeMaid is an open source Visual Studio extension to cleanup and simplify our C#, C++, F#, VB, PHP, PowerShell, R, JSON, XAML, XML, ASP, HTML, CSS, LESS, SCSS, JavaScript and TypeScript coding. + http://www.codemaid.net/ + LICENSE.txt + CodeMaid.png + CodeMaid_Large.png + build, code, c#, beautify, cleanup, cleaning, digging, reorganizing, formatting + + + + + + + + + + + + + \ No newline at end of file From 5787387dc855eb49ed8d8e35a59bb7228196b7ba Mon Sep 17 00:00:00 2001 From: Steve Cadwallader Date: Sat, 13 Apr 2019 11:37:18 +0000 Subject: [PATCH 10/20] Add back a missing comma for the version range. --- CodeMaid/source.extension.vsixmanifest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodeMaid/source.extension.vsixmanifest b/CodeMaid/source.extension.vsixmanifest index 5278fd425..d7e48ef7d 100644 --- a/CodeMaid/source.extension.vsixmanifest +++ b/CodeMaid/source.extension.vsixmanifest @@ -17,7 +17,7 @@ - + From cc6d67280280518ce53170d01827b0ddfe601e9d Mon Sep 17 00:00:00 2001 From: Steve Cadwallader Date: Sat, 13 Apr 2019 23:01:08 +0000 Subject: [PATCH 11/20] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index dbd83f8b5..a91666d88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ These changes have not been released to the Visual Studio marketplace, but (if checked) are available in preview within the [CI build](http://vsixgallery.com/extension/4c82e17d-927e-42d2-8460-b473ac7df316/). - [ ] Features + - [x] [#620](https://github.com/codecadwallader/codemaid/pull/620) - Formatting: Allow for individual tag formatting rules - thanks [willemduncan](https://github.com/willemduncan)! - [ ] Fixes From e3beb66f5b65d02e4d91497cf766d826ae3e6791 Mon Sep 17 00:00:00 2001 From: Willem Date: Thu, 18 Apr 2019 12:57:24 +0200 Subject: [PATCH 12/20] Reworked newline and indent logic to prevent multiple prefixes fixes #646 --- CodeMaid/Model/Comments/CommentFormatter.cs | 121 ++++++++++++-------- 1 file changed, 71 insertions(+), 50 deletions(-) diff --git a/CodeMaid/Model/Comments/CommentFormatter.cs b/CodeMaid/Model/Comments/CommentFormatter.cs index ba4c4a6c7..6f22b9bb2 100644 --- a/CodeMaid/Model/Comments/CommentFormatter.cs +++ b/CodeMaid/Model/Comments/CommentFormatter.cs @@ -17,9 +17,10 @@ internal class CommentFormatter : IEquatable private readonly FormatterOptions _formatterOptions; private int _commentPrefixLength; private int _currentPosition; - private bool _isAfterCommentPrefix; + private int _indentAmount; private bool _isFirstWord; private bool _isIndented; + private bool _isPrefixWritten; public CommentFormatter(ICommentLine line, FormatterOptions formatterOptions, CommentOptions commentOptions) { @@ -29,7 +30,9 @@ public CommentFormatter(ICommentLine line, FormatterOptions formatterOptions, Co _builder = new StringBuilder(); _currentPosition = 0; _isFirstWord = true; + _isPrefixWritten = false; _isIndented = false; + _indentAmount = 0; _commentPrefixLength = WordLength(commentOptions.Prefix); // Special handling for the root XML line, it should not output it's surrounding xml @@ -52,7 +55,6 @@ public CommentFormatter(ICommentLine line, FormatterOptions formatterOptions, Co else { // Normal comment line has no child-lines and can be processed normally. - NewLine(); Format(line); } } @@ -62,19 +64,6 @@ public bool Equals(string other) return string.Equals(ToString(), other); } - public void Indent(int amount) - { - if (!_isIndented) - { - if (amount > 0) - { - Append(string.Empty.PadLeft(amount)); - } - _isIndented = true; - _isFirstWord = true; - } - } - public override string ToString() { return _builder.ToString().TrimEnd(); @@ -103,27 +92,60 @@ private void Append(char value) Append(value.ToString()); } - private void Append(string value) + /// + /// Append value to current line. When line is still empty, this first writes the comment + /// prefix, indenting and initial spacer before appending the given value. Empty values are + /// ignored, but the comment prefix will be added if required. + /// + /// The string to append to the writer. + /// + /// true if value should not be indented, eg for literal content. + /// + private void Append(string value, bool noIdenting = false) { + if (!_isPrefixWritten) + { + _builder.Append(_commentOptions.Prefix); + _currentPosition += _commentPrefixLength; + _isPrefixWritten = true; + _isIndented = false; + } + if (string.IsNullOrEmpty(value)) { return; } - if (_isAfterCommentPrefix) + + if (!_isIndented) { - _builder.Append(CodeCommentHelper.Spacer); - _isAfterCommentPrefix = false; + if (!noIdenting) + { + // Empty prefix also means no initial spacing. + if (_commentPrefixLength > 0) + { + _builder.Append(CodeCommentHelper.Spacer); + _currentPosition += 1; + } + + if (_indentAmount > 0) + { + _builder.Append(string.Empty.PadLeft(_indentAmount)); + _currentPosition += _indentAmount; + } + + _isIndented = true; + } } + _builder.Append(value); _currentPosition += WordLength(value); _isFirstWord = false; } /// - /// Parse a code comment line into a string and write it to the buffer. + /// Parse a comment line into individual words and write it to the buffer. /// /// The comment line. - /// The amount of indenting for the content of this tag. /// /// The length of the enclosing XML tags, this is needed to calculate the line length for /// single line XML comments. @@ -134,11 +156,11 @@ private void Append(string value) /// /// true if line fitted on single line, false if it wrapped on multiple lines. /// - private bool Format(ICommentLine line, int indentAmount = 0, int xmlTagLength = 0, bool xmlSpaceParentTagContent = false) + private bool Format(ICommentLine line, int xmlTagLength = 0, bool xmlSpaceParentTagContent = false) { if (line is CommentLineXml xml) { - return FormatXml(xml, indentAmount); + return FormatXml(xml); } if (line.Content == null) @@ -181,7 +203,7 @@ private bool Format(ICommentLine line, int indentAmount = 0, int xmlTagLength = if (!forceBreak && matchCount == 1 && matches[0].Words.Any()) { // Calculate the length of the first line. - var firstLineLength = _commentPrefixLength + xmlTagLength + matches[0].Length + indentAmount; + var firstLineLength = _commentPrefixLength + xmlTagLength + matches[0].Length + _indentAmount; // If set to skip wrapping on the last word, the last word's length does not matter. if (_formatterOptions.SkipWrapOnLastWord) @@ -216,8 +238,6 @@ private bool Format(ICommentLine line, int indentAmount = 0, int xmlTagLength = NewLine(); fittedOnLine = false; } - - Indent(indentAmount); } if (match.IsList) @@ -231,7 +251,6 @@ private bool Format(ICommentLine line, int indentAmount = 0, int xmlTagLength = if (!match.IsEmpty) { - Indent(indentAmount); var wordCount = match.Words.Count - 1; for (int i = 0; i <= wordCount; i++) @@ -258,7 +277,6 @@ private bool Format(ICommentLine line, int indentAmount = 0, int xmlTagLength = if (wrap) { NewLine(); - Indent(indentAmount); fittedOnLine = false; // If linewrap is on a list item, add extra spacing to align the text @@ -282,12 +300,10 @@ private bool Format(ICommentLine line, int indentAmount = 0, int xmlTagLength = } else { - // Line without words, create a blank line. - if (!_isFirstWord) - { - NewLine(); - } + // Line without words, create a blank line. First end the current line. + NewLine(); + // And then force a newline creating an empty one. NewLine(true); fittedOnLine = false; } @@ -300,7 +316,7 @@ private bool Format(ICommentLine line, int indentAmount = 0, int xmlTagLength = /// Returns true if the line requests a break afterwards (did not fit on a single /// line), otherwise false. /// - private bool FormatXml(CommentLineXml xml, int indentAmount) + private bool FormatXml(CommentLineXml xml) { var isLiteralContent = !string.IsNullOrEmpty(xml.Content); var split = xml.TagOptions.Split; @@ -322,7 +338,6 @@ private bool FormatXml(CommentLineXml xml, int indentAmount) NewLine(); } - Indent(indentAmount); Append(xml.TagOptions.KeepTogether ? CodeCommentHelper.FakeToSpace(xml.OpenTag) : xml.OpenTag); // Self closing tags have no content, skip all further logic and just output. @@ -346,7 +361,7 @@ private bool FormatXml(CommentLineXml xml, int indentAmount) } // Increase the indenting. - indentAmount += xml.TagOptions.Indent; + _indentAmount += xml.TagOptions.Indent; if (isLiteralContent) { @@ -354,8 +369,9 @@ private bool FormatXml(CommentLineXml xml, int indentAmount) var literals = xml.Content.Trim('\r', '\n').TrimEnd('\r', '\n', '\t', ' ').Split('\n'); for (int i = 0; i < literals.Length; i++) { - if (i > 0) NewLine(true); - Append(literals[i].TrimEnd()); + if (i > 0) + NewLine(true); + Append(literals[i].TrimEnd(), true); } } else @@ -365,19 +381,18 @@ private bool FormatXml(CommentLineXml xml, int indentAmount) foreach (var line in xml.Lines) { - if (!Format(line, indentAmount, xmlTagLength, xml.TagOptions.SpaceContent)) + if (!Format(line, xmlTagLength, xml.TagOptions.SpaceContent)) split |= XmlTagNewLine.BeforeClose | XmlTagNewLine.AfterClose; } } // Remove the indenting. - indentAmount -= xml.TagOptions.Indent; + _indentAmount -= xml.TagOptions.Indent; // If opening tag was on own line, do the same for the closing tag. if (split.HasFlag(XmlTagNewLine.BeforeClose)) { NewLine(); - Indent(indentAmount); } else if (xml.TagOptions.SpaceContent) { @@ -388,7 +403,7 @@ private bool FormatXml(CommentLineXml xml, int indentAmount) if (split.HasFlag(XmlTagNewLine.AfterClose)) { - if (!xml.IsLast) + //if (!xml.IsLast) { NewLine(); } @@ -399,21 +414,27 @@ private bool FormatXml(CommentLineXml xml, int indentAmount) return true; } + /// + /// Appends a new line to the buffer, unless buffer already is on an empty new line. + /// + /// + /// If true, creates a new line even if the current line is empty. + /// private void NewLine(bool force = false) { + if (_isFirstWord && force) + { + Append(string.Empty); + } + if (!_isFirstWord || force) { _builder.AppendLine(); _currentPosition = 0; - } - _builder.Append(_commentOptions.Prefix); - _currentPosition += _commentPrefixLength; - _isFirstWord = true; - _isIndented = false; - - // Cannot simply be true, because an empty prefix also means no initial spacing. - _isAfterCommentPrefix = _commentPrefixLength > 0; + _isPrefixWritten = false; + _isFirstWord = true; + } } /// From 7fe5ff6f2d8d4d7c5270f9f3c2b814be2b3b002e Mon Sep 17 00:00:00 2001 From: Willem Date: Thu, 18 Apr 2019 12:57:55 +0200 Subject: [PATCH 13/20] Added unit tests for leading and trailing blank line behavior. --- .../Formatting/FormatWithPrefixTests.cs | 35 ++++++++++++++++--- CodeMaid/.editorconfig | 8 +++++ 2 files changed, 39 insertions(+), 4 deletions(-) create mode 100644 CodeMaid/.editorconfig diff --git a/CodeMaid.UnitTests/Formatting/FormatWithPrefixTests.cs b/CodeMaid.UnitTests/Formatting/FormatWithPrefixTests.cs index 8348e688e..a15896ffa 100644 --- a/CodeMaid.UnitTests/Formatting/FormatWithPrefixTests.cs +++ b/CodeMaid.UnitTests/Formatting/FormatWithPrefixTests.cs @@ -1,6 +1,6 @@ -using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using Microsoft.VisualStudio.TestTools.UnitTesting; using SteveCadwallader.CodeMaid.Properties; -using System; namespace SteveCadwallader.CodeMaid.UnitTests.Formatting { @@ -30,8 +30,35 @@ public void SimpleFormatWithPrefixTests_KeepsPrefix() [TestCategory("Formatting UnitTests")] public void SimpleFormatWithPrefixTests_TrimsTrailingSpace() { - var input = "// "; - var expected = "//"; + var input = "// Trailing space "; + var expected = "// Trailing space"; + CommentFormatHelper.AssertEqualAfterFormat(input, expected, "//"); + } + + [TestMethod] + [TestCategory("Formatting UnitTests")] + public void SimpleFormatWithPrefixTests_TrimsTrailingLines() + { + var input = + "// Comment with some trailing lines" + Environment.NewLine + + "//" + Environment.NewLine + + "//"; + var expected = + "// Comment with some trailing lines"; + CommentFormatHelper.AssertEqualAfterFormat(input, expected, "//"); + } + + [TestMethod] + [TestCategory("Formatting UnitTests")] + public void SimpleFormatWithPrefixTests_TrimsLeadingLines() + { + var input = + "//" + Environment.NewLine + + "//" + Environment.NewLine + + "// Comment with some leading lines"; + var expected = + "// Comment with some leading lines"; + CommentFormatHelper.AssertEqualAfterFormat(input, expected, "//"); } diff --git a/CodeMaid/.editorconfig b/CodeMaid/.editorconfig new file mode 100644 index 000000000..fd61c4992 --- /dev/null +++ b/CodeMaid/.editorconfig @@ -0,0 +1,8 @@ +; Top-most EditorConfig file +root = true + +[*.{cs,vb}] +dotnet_style_qualification_for_field = false:suggestion +dotnet_style_qualification_for_property = false:suggestion +dotnet_style_qualification_for_method = false:suggestion +dotnet_style_qualification_for_event = false:suggestion \ No newline at end of file From f0a01407b2050aaa263a4870c33ef289a073a9d6 Mon Sep 17 00:00:00 2001 From: Steve Cadwallader Date: Wed, 5 Jun 2019 11:53:14 +0000 Subject: [PATCH 14/20] Add missing explicit package. --- CodeMaid/CodeMaid.csproj | 1 - CodeMaid/packages.config | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/CodeMaid/CodeMaid.csproj b/CodeMaid/CodeMaid.csproj index 1396a4fba..d5349217b 100644 --- a/CodeMaid/CodeMaid.csproj +++ b/CodeMaid/CodeMaid.csproj @@ -187,7 +187,6 @@ ..\packages\Expression.Blend.Sdk.1.0.2\lib\net45\System.Windows.Interactivity.dll - True diff --git a/CodeMaid/packages.config b/CodeMaid/packages.config index 3745b4dd9..026f4cd22 100644 --- a/CodeMaid/packages.config +++ b/CodeMaid/packages.config @@ -1,5 +1,6 @@  + From 26975e2f87a7316b337dccb6f7f4a48c152ddb6d Mon Sep 17 00:00:00 2001 From: diermeier Date: Thu, 25 Jul 2019 19:04:44 +0200 Subject: [PATCH 15/20] Add icons to ToolWindows (using ImageMoniker) #482 --- CodeMaid/CodeMaid.csproj | 5 +++ CodeMaid/CodeMaid.imagemanifest | 31 ++++++++++++++++++ .../Images/IDE/progressToolWindow.png | Bin 0 -> 2895 bytes .../Images/IDE/spadeToolWindow.png | Bin 0 -> 3064 bytes .../BuildProgress/BuildProgressToolWindow.cs | 10 ++++-- .../UI/ToolWindows/Spade/SpadeToolWindow.cs | 10 ++++-- 6 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 CodeMaid/CodeMaid.imagemanifest create mode 100644 CodeMaid/Integration/Images/IDE/progressToolWindow.png create mode 100644 CodeMaid/Integration/Images/IDE/spadeToolWindow.png diff --git a/CodeMaid/CodeMaid.csproj b/CodeMaid/CodeMaid.csproj index d5349217b..7d8a3a86c 100644 --- a/CodeMaid/CodeMaid.csproj +++ b/CodeMaid/CodeMaid.csproj @@ -460,6 +460,8 @@ + + true Always @@ -648,6 +650,9 @@ CodeMaid.vsct Designer + + true + Designer CodeMaid.cs diff --git a/CodeMaid/CodeMaid.imagemanifest b/CodeMaid/CodeMaid.imagemanifest new file mode 100644 index 000000000..8b256afd8 --- /dev/null +++ b/CodeMaid/CodeMaid.imagemanifest @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CodeMaid/Integration/Images/IDE/progressToolWindow.png b/CodeMaid/Integration/Images/IDE/progressToolWindow.png new file mode 100644 index 0000000000000000000000000000000000000000..bebbba37fe2c25ad5df5363f6e39d0978616fef2 GIT binary patch literal 2895 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 literal 0 HcmV?d00001 diff --git a/CodeMaid/Integration/Images/IDE/spadeToolWindow.png b/CodeMaid/Integration/Images/IDE/spadeToolWindow.png new file mode 100644 index 0000000000000000000000000000000000000000..99bb37f9006b402f62b07326f88a5ff3a5760234 GIT binary patch literal 3064 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 Date: Sun, 11 Aug 2019 15:35:08 +0000 Subject: [PATCH 16/20] Small code cleanups for #665 --- CodeMaid/CodeMaid.cs | 2 ++ CodeMaid/CodeMaid.imagemanifest | 7 +++---- .../ToolWindows/BuildProgress/BuildProgressToolWindow.cs | 8 ++++---- CodeMaid/UI/ToolWindows/Spade/SpadeToolWindow.cs | 6 +++--- 4 files changed, 12 insertions(+), 11 deletions(-) diff --git a/CodeMaid/CodeMaid.cs b/CodeMaid/CodeMaid.cs index a7887daf6..1742d002b 100644 --- a/CodeMaid/CodeMaid.cs +++ b/CodeMaid/CodeMaid.cs @@ -18,6 +18,7 @@ internal sealed partial class PackageGuids public const string GuidCodeMaidToolWindowSpadeString = "75d09b86-471e-4b30-8720-362d13ad0a45"; public const string GuidCodeMaidOutputPaneString = "4e7ba904-9311-4dd0-abe5-a61c1739780f"; public const string GuidCodeMaidMenuSetString = "369b04df-0688-4074-911d-d0d6c6a31632"; + public const string GuidCodeMaidImageMonikerString = "e4c3c7f4-7250-4f02-b07b-575a320898ab"; public const string GuidImageCleanupString = "54aa78c8-285a-4166-9eba-369dde5d787e"; public const string GuidImageCleanupAllString = "e3d926ae-03d7-4c69-8226-440eeddab292"; public const string GuidImageCloseLockedString = "02ee894b-084c-4ccb-8a67-5f236b5df68b"; @@ -49,6 +50,7 @@ internal sealed partial class PackageGuids public static Guid GuidCodeMaidToolWindowSpade = new Guid(GuidCodeMaidToolWindowSpadeString); public static Guid GuidCodeMaidOutputPane = new Guid(GuidCodeMaidOutputPaneString); public static Guid GuidCodeMaidMenuSet = new Guid(GuidCodeMaidMenuSetString); + public static Guid GuidCodeMaidImageMoniker = new Guid(GuidCodeMaidImageMonikerString); public static Guid GuidImageCleanup = new Guid(GuidImageCleanupString); public static Guid GuidImageCleanupAll = new Guid(GuidImageCleanupAllString); public static Guid GuidImageCloseLocked = new Guid(GuidImageCloseLockedString); diff --git a/CodeMaid/CodeMaid.imagemanifest b/CodeMaid/CodeMaid.imagemanifest index 8b256afd8..1f27760b4 100644 --- a/CodeMaid/CodeMaid.imagemanifest +++ b/CodeMaid/CodeMaid.imagemanifest @@ -6,19 +6,19 @@ - + - + - + @@ -27,5 +27,4 @@ - \ No newline at end of file diff --git a/CodeMaid/UI/ToolWindows/BuildProgress/BuildProgressToolWindow.cs b/CodeMaid/UI/ToolWindows/BuildProgress/BuildProgressToolWindow.cs index 30e05ad47..875c9eeda 100644 --- a/CodeMaid/UI/ToolWindows/BuildProgress/BuildProgressToolWindow.cs +++ b/CodeMaid/UI/ToolWindows/BuildProgress/BuildProgressToolWindow.cs @@ -1,4 +1,5 @@ using EnvDTE; +using Microsoft.VisualStudio.Imaging.Interop; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using SteveCadwallader.CodeMaid.Properties; @@ -8,7 +9,6 @@ using System.Linq; using System.Runtime.InteropServices; using System.Text; -using Microsoft.VisualStudio.Imaging.Interop; namespace SteveCadwallader.CodeMaid.UI.ToolWindows.BuildProgress { @@ -31,10 +31,10 @@ public BuildProgressToolWindow() Caption = DefaultCaption; // Set the tool window image from moniker. - BitmapImageMoniker = new ImageMoniker() + BitmapImageMoniker = new ImageMoniker { - Guid = new Guid("{E4C3C7F4-7250-4F02-B07B-575A320898AB}"), - Id = 1, + Guid = PackageGuids.GuidCodeMaidImageMoniker, + Id = 1 }; // Create the view model. diff --git a/CodeMaid/UI/ToolWindows/Spade/SpadeToolWindow.cs b/CodeMaid/UI/ToolWindows/Spade/SpadeToolWindow.cs index d9cc00871..9b69fc7f8 100644 --- a/CodeMaid/UI/ToolWindows/Spade/SpadeToolWindow.cs +++ b/CodeMaid/UI/ToolWindows/Spade/SpadeToolWindow.cs @@ -1,6 +1,7 @@ using EnvDTE; using Microsoft.Internal.VisualStudio.PlatformUI; using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Imaging.Interop; using Microsoft.VisualStudio.PlatformUI; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; @@ -17,7 +18,6 @@ using System.Windows.Threading; using CodeModel = SteveCadwallader.CodeMaid.Model.CodeModel; using Task = System.Threading.Tasks.Task; -using Microsoft.VisualStudio.Imaging.Interop; namespace SteveCadwallader.CodeMaid.UI.ToolWindows.Spade { @@ -43,9 +43,9 @@ public SpadeToolWindow() Caption = Resources.CodeMaidSpade; // Set the tool window image from moniker. - BitmapImageMoniker = new ImageMoniker() + BitmapImageMoniker = new ImageMoniker { - Guid = new Guid("{E4C3C7F4-7250-4F02-B07B-575A320898AB}"), + Guid = PackageGuids.GuidCodeMaidImageMoniker, Id = 2, }; From 2213552f833fde0b5e66f225d26d95cf173900e8 Mon Sep 17 00:00:00 2001 From: Steve Cadwallader Date: Sun, 11 Aug 2019 15:41:53 +0000 Subject: [PATCH 17/20] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a91666d88..7751b1c7f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,8 +6,10 @@ These changes have not been released to the Visual Studio marketplace, but (if c - [ ] Features - [x] [#620](https://github.com/codecadwallader/codemaid/pull/620) - Formatting: Allow for individual tag formatting rules - thanks [willemduncan](https://github.com/willemduncan)! + - [x] [#665](https://github.com/codecadwallader/codemaid/pull/665) - Use image monikors so icons show up again when tool windows are small - thanks [Diermeier](https://github.com/Diermeier)! - [ ] Fixes + - [x] [#647](https://github.com/codecadwallader/codemaid/pull/647) - Formatting: Fix magically added slashes - thanks [willemduncan](https://github.com/willemduncan)! ## Previous Releases From 6b4fa0b5eb7a7f85f90ff062b518c24ef185b7d9 Mon Sep 17 00:00:00 2001 From: Matthias Weingaertner Date: Mon, 12 Aug 2019 20:52:19 +0200 Subject: [PATCH 18/20] Ensure imported Config File is not read-only --- CodeMaid/UI/Dialogs/Options/OptionsViewModel.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/CodeMaid/UI/Dialogs/Options/OptionsViewModel.cs b/CodeMaid/UI/Dialogs/Options/OptionsViewModel.cs index f49029706..0019f6ac7 100644 --- a/CodeMaid/UI/Dialogs/Options/OptionsViewModel.cs +++ b/CodeMaid/UI/Dialogs/Options/OptionsViewModel.cs @@ -283,6 +283,7 @@ private void OnImportCommandExecuted(object parameter) try { File.Copy(dialog.FileName, ActiveSettingsPath, true); + File.SetAttributes(ActiveSettingsPath, File.GetAttributes(ActiveSettingsPath) & ~FileAttributes.ReadOnly); RefreshPackageSettings(); From b20d806f9f2c8e9d26937c34d187e7a523966ed4 Mon Sep 17 00:00:00 2001 From: Steve Cadwallader Date: Thu, 29 Aug 2019 11:23:53 +0000 Subject: [PATCH 19/20] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7751b1c7f..56035fb63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ These changes have not been released to the Visual Studio marketplace, but (if c - [ ] Fixes - [x] [#647](https://github.com/codecadwallader/codemaid/pull/647) - Formatting: Fix magically added slashes - thanks [willemduncan](https://github.com/willemduncan)! + - [x] [#670](https://github.com/codecadwallader/codemaid/pull/670) - Options: Fix importing read-only config - thanks [Smartis2812](https://github.com/Smartis2812)! ## Previous Releases From 8b9668c11a75ec864f48edf2040042afdf6c967c Mon Sep 17 00:00:00 2001 From: Steve Cadwallader Date: Sun, 3 Nov 2019 14:46:32 +0000 Subject: [PATCH 20/20] Update to v11.1 label. --- CHANGELOG.md | 19 +++++++++++++------ CodeMaid/Integration/Images/about.png | Bin 50748 -> 50323 bytes CodeMaid/Integration/Images/about.xcf | Bin 93926 -> 93533 bytes CodeMaid/source.extension.cs | 2 +- CodeMaid/source.extension.vsixmanifest | 2 +- README.md | 2 +- appveyor.yml | 2 +- 7 files changed, 17 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 56035fb63..01bac50ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,21 +1,28 @@ # Changelog -## vNext (11.1) +## vNext (11.2) These changes have not been released to the Visual Studio marketplace, but (if checked) are available in preview within the [CI build](http://vsixgallery.com/extension/4c82e17d-927e-42d2-8460-b473ac7df316/). - [ ] Features - - [x] [#620](https://github.com/codecadwallader/codemaid/pull/620) - Formatting: Allow for individual tag formatting rules - thanks [willemduncan](https://github.com/willemduncan)! - - [x] [#665](https://github.com/codecadwallader/codemaid/pull/665) - Use image monikors so icons show up again when tool windows are small - thanks [Diermeier](https://github.com/Diermeier)! - - [ ] Fixes - - [x] [#647](https://github.com/codecadwallader/codemaid/pull/647) - Formatting: Fix magically added slashes - thanks [willemduncan](https://github.com/willemduncan)! - - [x] [#670](https://github.com/codecadwallader/codemaid/pull/670) - Options: Fix importing read-only config - thanks [Smartis2812](https://github.com/Smartis2812)! ## Previous Releases These are the changes to each version that has been released to the Visual Studio marketplace. +## 11.1 + +**2019-11-03** + +- [x] Features + - [x] [#620](https://github.com/codecadwallader/codemaid/pull/620) - Formatting: Allow for individual tag formatting rules - thanks [willemduncan](https://github.com/willemduncan)! + - [x] [#665](https://github.com/codecadwallader/codemaid/pull/665) - Use image monikors so icons show up again when tool windows are small - thanks [Diermeier](https://github.com/Diermeier)! + +- [x] Fixes + - [x] [#647](https://github.com/codecadwallader/codemaid/pull/647) - Formatting: Fix magically added slashes - thanks [willemduncan](https://github.com/willemduncan)! + - [x] [#670](https://github.com/codecadwallader/codemaid/pull/670) - Options: Fix importing read-only config - thanks [Smartis2812](https://github.com/Smartis2812)! + ## 11.0 **2019-03-23** diff --git a/CodeMaid/Integration/Images/about.png b/CodeMaid/Integration/Images/about.png index 95ed68493d21b4b418b733c50157db9601ce9a34..5ade57e920ff403a41f170f19ec7991a54f46378 100644 GIT binary patch literal 50323 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%0D{;I_V6cO2hZ>-+KZu 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 CXf}!f diff --git a/CodeMaid/Integration/Images/about.xcf b/CodeMaid/Integration/Images/about.xcf index d6910313a8004deae73125114353f65487b19912..cb92c6e123647af93730be213c6fdc44d88591be 100644 GIT binary patch delta 2080 zcmchYT}TvB6vywKnY+s>yNi~#=B5^!2_>1?$3uZoQV7+m6%iq|qvj{s&Pt>?g^&^W z5CcAhL4nXylo%8dB}6_IBt2ya31v@0ei4$*?aZ8$SkaeB!rb3E|2ub_`OWOi*oYVz z7I!XlUpjODQU@)Im-LVFbVX5Z%U07Pn{X!Z5gAm8L8LSC^)Rf!UaLcT`jEa%WMT=@ zf3Uw!8%nTVamyMEw($Zho@QIcN!d#31FI$})3k6u#!TMHK6``+xMP+(s>~K1RU{%- zAr!_vQd30&F--I_E`qSFhV74Q^KTa{V*db}(}d6h3(m{Wq*oQv>o5E<^!OoDnAChi zYJ%v)WB8yI)&WM9uP(q%u?lL_Vu2iGpu%orE1M3Jl3xc1wYT$$;r0Bx99F>rp#8Q%=Let z%N&T;xiLIlhV7NMeOk;{ptyZ-Vnsgnr0R}&It&EvaX$BH*Q59B-bAU4EJ(vw! z621`CrvCC!mZF5Ft-h7Z@-$4-HoD}E8;IM9yNMClJJn1-<+d#2Au>0Qc#L=jnKz2e zA4e9(k(*12&yhu(SVXKPUPErXf!tZ{(qt9%TGugWMkf}Ro+7(xqW2T_cPo)kXx)nt z_QS7{@6I8|>9h2K*3GOQZ3P12qK6tH26}|hM=h51jSFGL6Fk=p*3(-Lu_ux|L!xn&4Gn_u=9KV z&oeVS`@fytotYL_X2jZYk3c3C$*!M;c00;b_89 zrsGDIFA-*>xF|1Fd@B@gFs2blRQbQLFU)9gk;un|wRrZ*aPela%*9j%Qzfn;R7t$( z3W7AURwB%rFd{CLDKz6WPdQUYOt`UO=JMCrLZ4&X6@(VH;ACw(#{5HB(@qeY8MpK3 z6Jzsevsnj)Fme&XzyaIA%50bc#cixGjA0-uk>a)*j+AdZxNzOkx3f!xb6bLE%iU-K z$6h+Y5|Mw=F081M#cY+uRYl8!U;T!y2H~P(qeLr0)THi#mddq2>}M$|mL~C7cQc zTujNXNO2ju7}nfI=BFesT=(>4G7@}A;g)ggzxx->norBZYT%$$DcBTd)&{GES4l&v zE;xv*2}(M90NHc`1N@#&k1wyaV3|xwuW5OmyK^#^gAvu;U(YKw3L|vX%x(_kkKa*) z+R>|@^lusqYC3{RZTTL!`N3#a(7>i|FJgCV38Pxojv}4>1N%3s`^PSnlXQ`UccW*t zCOLNU_9cI>sPFM}MY8kqQkid7_eJZ#UOwEG%MIZ|Cf<$GuyK{4)N0uH&_5fat3>`T zeP|u!+%2AEBU{okd@4F3f2= z2=P$BQ`lCBkqA#BF=qe-#8d4Z4%*f!~T1vWcp15K=am_i+E5x<+#Jdj?*AEgO@QIs#3F)%UdnvJbL~~Lz zM|`lA*tU+i?GkZsBQf;`ac~)NM8Bj@T_^qFtHdvUCw`~L{d6|aE@PJ@6|P#u*=>3yA^wnr?(R^BS jNb`NoDb3%A8T#Jh8T!k3Mmvx>c(l9WtL_H=*Ped?KA=Ec diff --git a/CodeMaid/source.extension.cs b/CodeMaid/source.extension.cs index 4627a248b..8de7e1e2a 100644 --- a/CodeMaid/source.extension.cs +++ b/CodeMaid/source.extension.cs @@ -6,7 +6,7 @@ static class Vsix public const string Name = "CodeMaid"; public const string Description = "CodeMaid is an open source Visual Studio extension to cleanup and simplify our C#, C++, F#, VB, PHP, PowerShell, R, JSON, XAML, XML, ASP, HTML, CSS, LESS, SCSS, JavaScript and TypeScript coding."; public const string Language = "en-US"; - public const string Version = "11.0"; + public const string Version = "11.1"; public const string Author = "Steve Cadwallader"; public const string Tags = "build, code, c#, beautify, cleanup, cleaning, digging, reorganizing, formatting"; } diff --git a/CodeMaid/source.extension.vsixmanifest b/CodeMaid/source.extension.vsixmanifest index d7e48ef7d..8c826f599 100644 --- a/CodeMaid/source.extension.vsixmanifest +++ b/CodeMaid/source.extension.vsixmanifest @@ -1,7 +1,7 @@  - + CodeMaid CodeMaid is an open source Visual Studio extension to cleanup and simplify our C#, C++, F#, VB, PHP, PowerShell, R, JSON, XAML, XML, ASP, HTML, CSS, LESS, SCSS, JavaScript and TypeScript coding. http://www.codemaid.net/ diff --git a/README.md b/README.md index 9cbb9c53d..125aae566 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -
codemaid.net
+
codemaid.net
For more details, please visit: http://www.codemaid.net

Currently supports VS2017 and VS2019.

For Visual Studio 2012/2013/2015, the last supported version is v10.6.

diff --git a/appveyor.yml b/appveyor.yml index 782dce395..a6157b79a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -version: 11.0.{build} +version: 11.1.{build} image: Visual Studio 2017 install: