From 778c760b8d789d84e147cb9d296a003ac6001130 Mon Sep 17 00:00:00 2001 From: Matt Ward <ward.matt@gmail.com> Date: Sun, 19 Jul 2009 17:37:34 +0000 Subject: [PATCH] Added support for code folding of global python methods defined outside of a class. git-svn-id: svn://svn.sharpdevelop.net/sharpdevelop/branches/3.0@4495 1ccf3a8d-04fe-1044-b7c0-cef0b8235c61 --- .../Project/Src/PythonAstWalker.cs | 69 ++++-- .../ParseMethodsWithNoClassTestFixture.cs | 205 ++++++++++++++++++ .../Test/PythonBinding.Tests.csproj | 1 + 3 files changed, 254 insertions(+), 21 deletions(-) create mode 100644 src/AddIns/BackendBindings/Python/PythonBinding/Test/Parsing/ParseMethodsWithNoClassTestFixture.cs diff --git a/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonAstWalker.cs b/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonAstWalker.cs index f12767e7ba..133f4a6cca 100644 --- a/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonAstWalker.cs +++ b/src/AddIns/BackendBindings/Python/PythonBinding/Project/Src/PythonAstWalker.cs @@ -13,6 +13,7 @@ using System.IO; using ICSharpCode.SharpDevelop.Dom; using IronPython.Compiler; using IronPython.Compiler.Ast; +using IronPython.Runtime; namespace ICSharpCode.PythonBinding { @@ -23,6 +24,7 @@ namespace ICSharpCode.PythonBinding { DefaultCompilationUnit compilationUnit; DefaultClass currentClass; + DefaultClass globalClass; string ns; /// <summary> @@ -77,22 +79,30 @@ namespace ICSharpCode.PythonBinding /// </summary> public override bool Walk(FunctionDefinition node) { - if (currentClass != null) { - string methodName = node.Name.ToString(); - DomRegion bodyRegion = GetBodyRegion(node.Body, node.Header); - DomRegion region = GetMethodRegion(node); - - DefaultMethod method; - if (methodName == "__init__") { - method = new Constructor(ModifierEnum.Public, region, bodyRegion, currentClass); - } else { - method = new DefaultMethod(methodName, new DefaultReturnType(currentClass), ModifierEnum.Public, region, bodyRegion, currentClass); - } - foreach (IParameter parameter in ConvertParameters(node.Parameters)) { - method.Parameters.Add(parameter); - } - currentClass.Methods.Add(method); + bool ignoreFirstMethodParameter = true; + IClass c = currentClass; + if (currentClass == null) { + // Walking a global method. + CreateGlobalClass(); + c = globalClass; + ignoreFirstMethodParameter = false; + } + + // Create method. + string methodName = node.Name.ToString(); + DomRegion bodyRegion = GetBodyRegion(node.Body, node.Header); + DomRegion region = GetMethodRegion(node); + + DefaultMethod method; + if (methodName == "__init__") { + method = new Constructor(ModifierEnum.Public, region, bodyRegion, c); + } else { + method = new DefaultMethod(methodName, new DefaultReturnType(c), ModifierEnum.Public, region, bodyRegion, c); } + foreach (IParameter parameter in ConvertParameters(node.Parameters, ignoreFirstMethodParameter)) { + method.Parameters.Add(parameter); + } + c.Methods.Add(method); return true; } @@ -175,16 +185,22 @@ namespace ICSharpCode.PythonBinding /// <summary> /// Converts from Python AST expressions to parameters. /// </summary> - IParameter[] ConvertParameters(Parameter[] parameters) + /// <remarks>If the parameters belong to a class method then the first + /// "self" parameter can be ignored.</remarks> + IParameter[] ConvertParameters(Parameter[] parameters, bool ignoreFirstParameter) { - List<IParameter> convertedTarameters = new List<IParameter>(); + List<IParameter> convertedParameters = new List<IParameter>(); - // Ignore first parameter since this is the "self" parameter. - for (int i = 1; i < parameters.Length; ++i) { + int startingIndex = 0; + if (ignoreFirstParameter) { + startingIndex = 1; + } + + for (int i = startingIndex; i < parameters.Length; ++i) { DefaultParameter parameter = new DefaultParameter(parameters[i].Name.ToString(), null, new DomRegion()); - convertedTarameters.Add(parameter); + convertedParameters.Add(parameter); } - return convertedTarameters.ToArray(); + return convertedParameters.ToArray(); } @@ -195,5 +211,16 @@ namespace ICSharpCode.PythonBinding { return String.Concat(ns, ".", classDef.Name.ToString()); } + + /// <summary> + /// Creates the dummy class that is used to hold global methods. + /// </summary> + void CreateGlobalClass() + { + if (globalClass == null) { + globalClass = new DefaultClass(compilationUnit, ns); + compilationUnit.Classes.Add(globalClass); + } + } } } diff --git a/src/AddIns/BackendBindings/Python/PythonBinding/Test/Parsing/ParseMethodsWithNoClassTestFixture.cs b/src/AddIns/BackendBindings/Python/PythonBinding/Test/Parsing/ParseMethodsWithNoClassTestFixture.cs new file mode 100644 index 0000000000..fa0ac50650 --- /dev/null +++ b/src/AddIns/BackendBindings/Python/PythonBinding/Test/Parsing/ParseMethodsWithNoClassTestFixture.cs @@ -0,0 +1,205 @@ +// <file> +// <copyright see="prj:///doc/copyright.txt"/> +// <license see="prj:///doc/license.txt"/> +// <owner name="Matthew Ward" email="mrward@users.sourceforge.net"/> +// <version>$Revision$</version> +// </file> + +using System; +using System.Collections.Generic; +using ICSharpCode.PythonBinding; +using ICSharpCode.SharpDevelop.DefaultEditor.Gui.Editor; +using ICSharpCode.SharpDevelop.Dom; +using ICSharpCode.TextEditor.Document; +using NUnit.Framework; +using PythonBinding.Tests; + + +namespace PythonBinding.Tests.Parsing +{ + /// <summary> + /// Support folding when no classes are defined. + /// </summary> + [TestFixture] + public class ParseMethodsWithNoClassTestFixture + { + ICompilationUnit compilationUnit; + FoldingRegion fooFoldingRegion; + FoldingRegion barFoldingRegion; + FoldMarker fooMethodMarker; + FoldMarker barMethodMarker; + IClass globalClass; + IMethod fooMethod; + IMethod barMethod; + + [TestFixtureSetUp] + public void SetUpFixture() + { + string python = "def foo():\r\n" + + "\tpass\r\n" + + "\r\n" + + "def bar(i):\r\n" + + "\tpass"; + + DefaultProjectContent projectContent = new DefaultProjectContent(); + PythonParser parser = new PythonParser(); + compilationUnit = parser.Parse(projectContent, @"C:\test.py", python); + + if (compilationUnit.FoldingRegions.Count > 1) { + fooFoldingRegion = compilationUnit.FoldingRegions[0]; + barFoldingRegion = compilationUnit.FoldingRegions[1]; + } + + if (compilationUnit.Classes.Count > 0) { + globalClass = compilationUnit.Classes[0]; + if (globalClass.Methods.Count > 1) { + fooMethod = globalClass.Methods[0]; + barMethod = globalClass.Methods[1]; + } + } + + // Get folds. + ParserFoldingStrategy foldingStrategy = new ParserFoldingStrategy(); + ParseInformation parseInfo = new ParseInformation(); + parseInfo.SetCompilationUnit(compilationUnit); + + DocumentFactory docFactory = new DocumentFactory(); + IDocument doc = docFactory.CreateDocument(); + doc.TextContent = python; + List<FoldMarker> markers = foldingStrategy.GenerateFoldMarkers(doc, @"C:\Temp\test.py", parseInfo); + + if (markers.Count > 1) { + fooMethodMarker = markers[0]; + barMethodMarker = markers[1]; + } + } + + [Test] + public void OneClass() + { + Assert.AreEqual(1, compilationUnit.Classes.Count); + } + + [Test] + public void GlobalClassName() + { + Assert.AreEqual("test", globalClass.Name); + } + + [Test] + public void GlobalClassHasTwoMethods() + { + Assert.AreEqual(2, globalClass.Methods.Count); + } + + [Test] + public void FooMethodName() + { + Assert.AreEqual("foo", fooMethod.Name); + } + + [Test] + public void BarMethodName() + { + Assert.AreEqual("bar", barMethod.Name); + } + + [Test] + public void FooMethodDefaultReturnType() + { + Assert.AreEqual(globalClass, fooMethod.ReturnType.GetUnderlyingClass()); + } + + [Test] + public void BarMethodDefaultReturnType() + { + Assert.AreEqual(globalClass, barMethod.ReturnType.GetUnderlyingClass()); + } + + [Test] + public void FooMethodDeclaringType() + { + Assert.AreEqual(globalClass, fooMethod.DeclaringType); + } + + [Test] + public void FooMethodBodyRegion() + { + int startLine = 1; + int startColumn = 11; + int endLine = 2; + int endColumn = 6; + DomRegion region = new DomRegion(startLine, startColumn, endLine, endColumn); + Assert.AreEqual(region.ToString(), fooMethod.BodyRegion.ToString()); + } + + /// <summary> + /// The method region needs to extend up just after the colon. It does not include the body. + /// </summary> + [Test] + public void FooMethodRegion() + { + int startLine = 1; + int startColumn = 1; + int endLine = 1; + int endColumn = 11; + DomRegion region = new DomRegion(startLine, startColumn, endLine, endColumn); + Assert.AreEqual(region.ToString(), fooMethod.Region.ToString()); + } + + [Test] + public void BarMethodBodyRegion() + { + int startLine = 4; + int startColumn = 12; + int endLine = 5; + int endColumn = 6; + DomRegion region = new DomRegion(startLine, startColumn, endLine, endColumn); + Assert.AreEqual(region.ToString(), barMethod.BodyRegion.ToString()); + } + + /// <summary> + /// The method region needs to extend up just after the colon. It does not include the body. + /// </summary> + [Test] + public void BarMethodRegion() + { + int startLine = 4; + int startColumn = 1; + int endLine = 4; + int endColumn = 12; + DomRegion region = new DomRegion(startLine, startColumn, endLine, endColumn); + Assert.AreEqual(region.ToString(), barMethod.Region.ToString()); + } + + [Test] + public void BarMethodHasOneParameter() + { + Assert.AreEqual(1, barMethod.Parameters.Count); + } + + [Test] + public void FooMethodFoldMarkerInnerText() + { + Assert.AreEqual("\r\n\tpass", fooMethodMarker.InnerText); + } + + [Test] + public void BarMethodFoldMarkerInnerText() + { + Assert.AreEqual("\r\n\tpass", barMethodMarker.InnerText); + } + + [Test] + public void FooMethodCollapsedFoldText() + { + Assert.AreEqual("...", fooMethodMarker.FoldText); + } + + [Test] + public void BarMethodCollapsedFoldText() + { + Assert.AreEqual("...", barMethodMarker.FoldText); + } + } +} diff --git a/src/AddIns/BackendBindings/Python/PythonBinding/Test/PythonBinding.Tests.csproj b/src/AddIns/BackendBindings/Python/PythonBinding/Test/PythonBinding.Tests.csproj index 04c4bbd8cc..1f9bc3f457 100644 --- a/src/AddIns/BackendBindings/Python/PythonBinding/Test/PythonBinding.Tests.csproj +++ b/src/AddIns/BackendBindings/Python/PythonBinding/Test/PythonBinding.Tests.csproj @@ -276,6 +276,7 @@ <Compile Include="Parsing\ParseClassWithMethodTestFixture.cs" /> <Compile Include="Parsing\ParseImportTestFixture.cs" /> <Compile Include="Parsing\ParseInvalidPythonCodeTestFixture.cs" /> + <Compile Include="Parsing\ParseMethodsWithNoClassTestFixture.cs" /> <Compile Include="Parsing\ParserTestFixture.cs" /> <Compile Include="Parsing\ParseSingleClassTestFixture.cs" /> <Compile Include="Parsing\InvalidClassTestFixture.cs" />