From 1e22646923cc9f57790a429864c9ea3700713523 Mon Sep 17 00:00:00 2001 From: Matt Ward Date: Sat, 26 Sep 2009 20:41:47 +0000 Subject: [PATCH] Added smart indenting for Python. git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/branches/3.0@5007 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61 --- .../PythonBinding/Project/PythonBinding.addin | 4 + .../Project/PythonBinding.csproj | 1 + .../Project/Src/PythonFormattingStrategy.cs | 76 +++++++ .../Test/AddInFileTestFixture.cs | 8 + .../Test/PythonBinding.Tests.csproj | 1 + .../Test/PythonIndentationTests.cs | 205 ++++++++++++++++++ .../Test/Utils/MockTextEditorProperties.cs | 1 + 7 files changed, 296 insertions(+) create mode 100644 src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonFormattingStrategy.cs create mode 100644 src/AddIns/BackendBindings/Python/PythonBinding/Test/PythonIndentationTests.cs diff --git a/src/AddIns/BackendBindings/Python/PythonBinding/Project/PythonBinding.addin b/src/AddIns/BackendBindings/Python/PythonBinding/Project/PythonBinding.addin index 6d476b688c..2e7a56c9a7 100644 --- a/src/AddIns/BackendBindings/Python/PythonBinding/Project/PythonBinding.addin +++ b/src/AddIns/BackendBindings/Python/PythonBinding/Project/PythonBinding.addin @@ -24,6 +24,10 @@ resource="ICSharpCode.PythonBinding.Resources.Python.xshd"/> + + + + + diff --git a/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonFormattingStrategy.cs b/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonFormattingStrategy.cs new file mode 100644 index 0000000000..55ad526f35 --- /dev/null +++ b/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonFormattingStrategy.cs @@ -0,0 +1,76 @@ +// +// +// +// +// $Revision$ +// + +using System; +using ICSharpCode.TextEditor; +using ICSharpCode.TextEditor.Actions; +using ICSharpCode.TextEditor.Document; + +namespace ICSharpCode.PythonBinding +{ + public class PythonFormattingStrategy : DefaultFormattingStrategy + { + public PythonFormattingStrategy() + { + } + + protected override int SmartIndentLine(TextArea textArea, int line) + { + IDocument document = textArea.Document; + LineSegment previousLine = document.GetLineSegment(line - 1); + string previousLineText = document.GetText(previousLine).Trim(); + + if (previousLineText.EndsWith(":")) { + return IncreaseLineIndent(textArea, line); + } else if (previousLineText == "pass") { + return DecreaseLineIndent(textArea, line); + } else if ((previousLineText == "return") || (previousLineText.StartsWith("return "))) { + return DecreaseLineIndent(textArea, line); + } else if ((previousLineText == "raise") || (previousLineText.StartsWith("raise "))) { + return DecreaseLineIndent(textArea, line); + } else if (previousLineText == "break") { + return DecreaseLineIndent(textArea, line); + } + return base.SmartIndentLine(textArea, line); + } + + int IncreaseLineIndent(TextArea textArea, int line) + { + return ModifyLineIndent(textArea, line, true); + } + + int DecreaseLineIndent(TextArea textArea, int line) + { + return ModifyLineIndent(textArea, line, false); + } + + int ModifyLineIndent(TextArea textArea, int line, bool increaseIndent) + { + IDocument document = textArea.Document; + LineSegment currentLine = document.GetLineSegment(line); + string indentation = GetIndentation(textArea, line - 1); + indentation = GetNewLineIndentation(indentation, Tab.GetIndentationString(document), increaseIndent); + string newIndentedText = indentation + document.GetText(currentLine); + SmartReplaceLine(document, currentLine, newIndentedText); + return indentation.Length; + } + + string GetNewLineIndentation(string previousLineIndentation, string singleIndent, bool increaseIndent) + { + if (increaseIndent) { + return previousLineIndentation + singleIndent; + } + + // Decrease the new line indentation. + int decreaselength = previousLineIndentation.Length - singleIndent.Length; + if (decreaselength < 0) { + decreaselength = 0; + } + return previousLineIndentation.Substring(0, decreaselength); + } + } +} diff --git a/src/AddIns/BackendBindings/Python/PythonBinding/Test/AddInFileTestFixture.cs b/src/AddIns/BackendBindings/Python/PythonBinding/Test/AddInFileTestFixture.cs index 88e0c37860..7722d4b3c0 100644 --- a/src/AddIns/BackendBindings/Python/PythonBinding/Test/AddInFileTestFixture.cs +++ b/src/AddIns/BackendBindings/Python/PythonBinding/Test/AddInFileTestFixture.cs @@ -51,6 +51,7 @@ namespace PythonBinding.Tests Codon pythonProjectIconCodon; Codon convertCSharpProjectCodon; Codon convertVBNetProjectCodon; + Codon formattingStrategyCodon; [TestFixtureSetUp] public void SetupFixture() @@ -83,6 +84,7 @@ namespace PythonBinding.Tests pythonProjectIconCodon = GetCodon("/Workspace/Icons", "PythonProjectIcon"); convertCSharpProjectCodon = GetCodon("/SharpDevelop/Pads/ProjectBrowser/ContextMenu/ProjectActions/Convert", "CSharpProjectToPythonProjectConverter"); convertVBNetProjectCodon = GetCodon("/SharpDevelop/Pads/ProjectBrowser/ContextMenu/ProjectActions/Convert", "VBNetProjectToPythonProjectConverter"); + formattingStrategyCodon = GetCodon("/AddIns/DefaultTextEditor/Formatter/Python", "PythonFormatter"); // Get the PythonBinding runtime. foreach (Runtime runtime in addin.Runtimes) { @@ -749,6 +751,12 @@ namespace PythonBinding.Tests { Assert.AreEqual(pythonWithoutDebuggerRunMenuItemCodon.Conditions[0], pythonRunMenuItemCodon.Conditions[0]); } + + [Test] + public void PythonFormatterClass() + { + Assert.AreEqual("ICSharpCode.PythonBinding.PythonFormattingStrategy", formattingStrategyCodon["class"]); + } Codon GetCodon(string name, string extensionPath) { diff --git a/src/AddIns/BackendBindings/Python/PythonBinding/Test/PythonBinding.Tests.csproj b/src/AddIns/BackendBindings/Python/PythonBinding/Test/PythonBinding.Tests.csproj index cbba547df8..a40cb01b0c 100644 --- a/src/AddIns/BackendBindings/Python/PythonBinding/Test/PythonBinding.Tests.csproj +++ b/src/AddIns/BackendBindings/Python/PythonBinding/Test/PythonBinding.Tests.csproj @@ -306,6 +306,7 @@ + diff --git a/src/AddIns/BackendBindings/Python/PythonBinding/Test/PythonIndentationTests.cs b/src/AddIns/BackendBindings/Python/PythonBinding/Test/PythonIndentationTests.cs new file mode 100644 index 0000000000..9d15beef43 --- /dev/null +++ b/src/AddIns/BackendBindings/Python/PythonBinding/Test/PythonIndentationTests.cs @@ -0,0 +1,205 @@ +// +// +// +// +// $Revision$ +// + +using System; +using ICSharpCode.PythonBinding; +using ICSharpCode.TextEditor; +using ICSharpCode.TextEditor.Document; +using ICSharpCode.TextEditor.Actions; +using NUnit.Framework; +using PythonBinding.Tests.Utils; + +namespace PythonBinding.Tests.Indentation +{ + /// + /// Tests that the PythonFormattingStrategy indents the new line added after pressing the ':' character. + /// + [TestFixture] + public class PythonNewMethodIndentationTestFixture + { + TextEditorControl textEditor; + PythonFormattingStrategy formattingStrategy; + + [SetUp] + public void Init() + { + MockTextEditorProperties textEditorProperties = new MockTextEditorProperties(); + textEditorProperties.IndentStyle = IndentStyle.Smart; + textEditorProperties.TabIndent = 4; + textEditor = new TextEditorControl(); + textEditor.TextEditorProperties = textEditorProperties; + formattingStrategy = new PythonFormattingStrategy(); + } + + [TearDown] + public void TearDown() + { + textEditor.Dispose(); + } + + [Test] + public void NewMethodDefinition() + { + textEditor.Text = "def newMethod:\r\n" + + ""; + int indentResult = formattingStrategy.IndentLine(textEditor.ActiveTextAreaControl.TextArea, 1); + string expectedText = "def newMethod:\r\n" + + "\t"; + + Assert.AreEqual(expectedText, textEditor.Text); + Assert.AreEqual(1, indentResult); + } + + [Test] + public void NoExtraIndentationRequired() + { + textEditor.Text = "\tprint 'abc'\r\n" + + ""; + int indentResult = formattingStrategy.IndentLine(textEditor.ActiveTextAreaControl.TextArea, 1); + string expectedText = "\tprint 'abc'\r\n" + + "\t"; + + Assert.AreEqual(expectedText, textEditor.Text); + Assert.AreEqual(1, indentResult); + } + + [Test] + public void PassStatementDecreasesIndentOnThirdLine() + { + textEditor.Text = "def method1:\r\n" + + "\tpass\r\n" + + ""; + int indentResult = formattingStrategy.IndentLine(textEditor.ActiveTextAreaControl.TextArea, 2); + string expectedText = "def method1:\r\n" + + "\tpass\r\n" + + ""; + + Assert.AreEqual(expectedText, textEditor.Text); + } + + [Test] + public void ReturnValueStatementDecreasesIndentOnThirdLine() + { + textEditor.Text = "def method1:\r\n" + + "\treturn 0\r\n" + + ""; + int indentResult = formattingStrategy.IndentLine(textEditor.ActiveTextAreaControl.TextArea, 2); + string expectedText = "def method1:\r\n" + + "\treturn 0\r\n" + + ""; + + Assert.AreEqual(expectedText, textEditor.Text); + } + + [Test] + public void ReturnStatementDecreasesIndentOnThirdLine() + { + textEditor.Text = "def method1:\r\n" + + "\treturn\r\n" + + ""; + int indentResult = formattingStrategy.IndentLine(textEditor.ActiveTextAreaControl.TextArea, 2); + string expectedText = "def method1:\r\n" + + "\treturn\r\n" + + ""; + + Assert.AreEqual(expectedText, textEditor.Text); + } + [Test] + public void ReturnStatementWithNoIndentOnPreviousLine() + { + textEditor.Text = "return\r\n" + + ""; + int indentResult = formattingStrategy.IndentLine(textEditor.ActiveTextAreaControl.TextArea, 1); + string expectedText = "return\r\n" + + ""; + + Assert.AreEqual(expectedText, textEditor.Text); + } + + [Test] + public void StatementIsNotAReturnOnPreviousLine() + { + textEditor.Text = "\treturnValue\r\n" + + ""; + int indentResult = formattingStrategy.IndentLine(textEditor.ActiveTextAreaControl.TextArea, 1); + string expectedText = "\treturnValue\r\n" + + "\t"; + + Assert.AreEqual(expectedText, textEditor.Text); + } + + [Test] + public void RaiseStatementWithObjectDecreasesIndentOnThirdLine() + { + textEditor.Text = "def method1:\r\n" + + "\traise 'a'\r\n" + + ""; + int indentResult = formattingStrategy.IndentLine(textEditor.ActiveTextAreaControl.TextArea, 2); + string expectedText = "def method1:\r\n" + + "\traise 'a'\r\n" + + ""; + + Assert.AreEqual(expectedText, textEditor.Text); + } + + [Test] + public void RaiseStatementDecreasesIndentOnThirdLine() + { + textEditor.Text = "def method1:\r\n" + + "\traise\r\n" + + ""; + int indentResult = formattingStrategy.IndentLine(textEditor.ActiveTextAreaControl.TextArea, 2); + string expectedText = "def method1:\r\n" + + "\traise\r\n" + + ""; + + Assert.AreEqual(expectedText, textEditor.Text); + } + + [Test] + public void StatementIsNotARaiseStatementOnPreviousLine() + { + textEditor.Text = "def method1:\r\n" + + "\traiseThis\r\n" + + ""; + int indentResult = formattingStrategy.IndentLine(textEditor.ActiveTextAreaControl.TextArea, 2); + string expectedText = "def method1:\r\n" + + "\traiseThis\r\n" + + "\t"; + + Assert.AreEqual(expectedText, textEditor.Text); + } + + [Test] + public void BreakStatementDecreasesIndentOnThirdLine() + { + textEditor.Text = "def method1:\r\n" + + "\tbreak\r\n" + + ""; + int indentResult = formattingStrategy.IndentLine(textEditor.ActiveTextAreaControl.TextArea, 2); + string expectedText = "def method1:\r\n" + + "\tbreak\r\n" + + ""; + + Assert.AreEqual(expectedText, textEditor.Text); + } + + [Test] + public void StatementIsNotABreakStatementOnPreviousLine() + { + textEditor.Text = "def method1:\r\n" + + "\tbreakThis\r\n" + + ""; + int indentResult = formattingStrategy.IndentLine(textEditor.ActiveTextAreaControl.TextArea, 2); + string expectedText = "def method1:\r\n" + + "\tbreakThis\r\n" + + "\t"; + + Assert.AreEqual(expectedText, textEditor.Text); + } + } +} diff --git a/src/AddIns/BackendBindings/Python/PythonBinding/Test/Utils/MockTextEditorProperties.cs b/src/AddIns/BackendBindings/Python/PythonBinding/Test/Utils/MockTextEditorProperties.cs index e54df5268f..bee4d59345 100644 --- a/src/AddIns/BackendBindings/Python/PythonBinding/Test/Utils/MockTextEditorProperties.cs +++ b/src/AddIns/BackendBindings/Python/PythonBinding/Test/Utils/MockTextEditorProperties.cs @@ -17,6 +17,7 @@ namespace PythonBinding.Tests.Utils { public MockTextEditorProperties() { + FontContainer = new FontContainer(SystemFonts.MenuFont); } public bool CaretLine { get; set; }