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" />