Browse Source

WIP: Linux port
- MonoDevelop as an external code editor

BearishSun 8 years ago
parent
commit
c236f47084

+ 1 - 0
Source/BansheeEditor/BsEditorPrerequisites.h

@@ -214,6 +214,7 @@ namespace bs
 		VS2013,
 		VS2015,
 		VS2017,
+		MonoDevelop,
 		None
 	};
 

+ 4 - 0
Source/BansheeEditor/CMakeSources.cmake

@@ -225,10 +225,12 @@ set(BS_BANSHEEEDITOR_INC_TESTING
 
 set(BS_BANSHEEEDITOR_INC_CODEEDITOR
 	"CodeEditor/BsCodeEditor.h"
+	"CodeEditor/BsMDCodeEditor.h"
 )
 
 set(BS_BANSHEEEDITOR_SRC_CODEEDITOR
 	"CodeEditor/BsCodeEditor.cpp"
+	"CodeEditor/BsMDCodeEditor.cpp"
 )
 
 set(BS_BANSHEEEDITOR_INC_WIN32
@@ -280,6 +282,8 @@ source_group("Header Files\\Build" FILES ${BS_BANSHEEEDITOR_INC_BUILD})
 source_group("Source Files\\Build" FILES ${BS_BANSHEEEDITOR_SRC_BUILD})
 source_group("Source Files\\Handles" FILES ${BS_BANSHEEEDITOR_SRC_HANDLES})
 source_group("Header Files\\Testing" FILES ${BS_BANSHEEEDITOR_INC_TESTING})
+source_group("Header Files\\Win32" FILES ${BS_BANSHEEEDITOR_INC_WIN32})
+source_group("Source Files\\Win32" FILES ${BS_BANSHEEEDITOR_SRC_WIN32})
 
 set(BS_BANSHEEEDITOR_SRC
 	${BS_BANSHEEEDITOR_INC_SETTINGS}

+ 230 - 0
Source/BansheeEditor/CodeEditor/BsCodeEditor.cpp

@@ -6,6 +6,7 @@
 #include "Library/BsProjectResourceMeta.h"
 #include "Resources/BsScriptCodeImportOptions.h"
 #include "Build/BsBuildManager.h"
+#include "CodeEditor/BsMDCodeEditor.h"
 
 #if BS_PLATFORM == BS_PLATFORM_WIN32
 #include "Win32/BsVSCodeEditor.h"
@@ -31,6 +32,17 @@ namespace bs
 #else
 		// Add implementations for code editors on other platforms.
 #endif
+
+		MDCodeEditorFactory* mdCodeEditorFactory = bs_new<MDCodeEditorFactory>();
+		Vector<CodeEditorType> mdEditors = mdCodeEditorFactory->getAvailableEditors();
+
+		for(auto& editor : mdEditors)
+		{
+			mFactoryPerEditor[editor] = mdCodeEditorFactory;
+			mEditors.push_back(editor);
+		}
+
+		mFactories.push_back(mdCodeEditorFactory);
 	}
 
 	CodeEditorManager::~CodeEditorManager()
@@ -147,4 +159,222 @@ namespace bs
 
 		return path;
 	}
+
+	const String CSProject::SLN_TEMPLATE =
+		R"(Microsoft Visual Studio Solution File, Format Version {0}
+# Visual Studio 2013
+VisualStudioVersion = 12.0.30723.0
+MinimumVisualStudioVersion = 10.0.40219.1{1}
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|Any CPU = Debug|Any CPU
+		Release|Any CPU = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution{2}
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+EndGlobal
+)";
+
+	const String CSProject::PROJ_ENTRY_TEMPLATE =
+		R"(
+Project("\{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC\}") = "{0}", "{1}", "\{{2}\}"
+EndProject)";
+
+	const String CSProject::PROJ_PLATFORM_TEMPLATE =
+		R"(
+		\{{0}\}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		\{{0}\}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		\{{0}\}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		\{{0}\}.Release|Any CPU.Build.0 = Release|Any CPU)";
+
+	const String CSProject::PROJ_TEMPLATE =
+		R"literal(<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="{0}" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <Import Project="$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props')" />
+  <PropertyGroup>
+	<Configuration Condition = " '$(Configuration)' == '' ">Debug</Configuration>
+	<Platform Condition = " '$(Platform)' == '' ">AnyCPU</Platform>
+	<ProjectGuid>\{{1}\}</ProjectGuid>
+	<OutputType>Library</OutputType>
+	<AppDesignerFolder>Properties</AppDesignerFolder>
+	<RootNamespace></RootNamespace>
+	<AssemblyName>{2}</AssemblyName>
+	<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+	<FileAlignment>512</FileAlignment>
+	<BaseDirectory>Resources</BaseDirectory>
+	<SchemaVersion>2.0</SchemaVersion>
+  </PropertyGroup>
+  <PropertyGroup Condition = " '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+	<DebugSymbols>true</DebugSymbols>
+	<DebugType>full</DebugType>
+	<Optimize>false</Optimize>
+	<OutputPath>Internal\\Temp\\Assemblies\\Debug\\</OutputPath>
+	<BaseIntermediateOutputPath>Internal\\Temp\\Assemblies\\</BaseIntermediateOutputPath>
+	<DefineConstants>DEBUG;TRACE;{3}</DefineConstants>
+	<ErrorReport>prompt</ErrorReport>
+	<WarningLevel>4</WarningLevel >
+  </PropertyGroup>
+  <PropertyGroup Condition = " '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+	<DebugType>pdbonly</DebugType>
+	<Optimize>true</Optimize>
+	<OutputPath>Internal\\Temp\\Assemblies\\Release\\</OutputPath>
+	<BaseIntermediateOutputPath>Internal\\Temp\\Assemblies\\</BaseIntermediateOutputPath>
+	<DefineConstants>TRACE;{3}</DefineConstants>
+	<ErrorReport>prompt</ErrorReport>
+	<WarningLevel>4</WarningLevel>
+  </PropertyGroup>
+  <ItemGroup>{4}
+  </ItemGroup>
+  <ItemGroup>{5}
+  </ItemGroup>
+  <ItemGroup>{6}
+  </ItemGroup>
+  <ItemGroup>{7}
+  </ItemGroup>
+  <Import Project = "$(MSBuildToolsPath)\\Microsoft.CSharp.targets"/>
+</Project>)literal";
+
+	const String CSProject::REFERENCE_ENTRY_TEMPLATE =
+		R"(
+	<Reference Include="{0}"/>)";
+
+	const String CSProject::REFERENCE_PATH_ENTRY_TEMPLATE =
+		R"(
+	<Reference Include="{0}">
+	  <HintPath>{1}</HintPath>
+	</Reference>)";
+
+	const String CSProject::REFERENCE_PROJECT_ENTRY_TEMPLATE =
+		R"(
+	<ProjectReference Include="{0}.csproj">
+	  <Project>\{{1}\}</Project>
+	  <Name>{0}</Name>
+	</ProjectReference>)";
+
+	const String CSProject::CODE_ENTRY_TEMPLATE =
+		R"(
+	<Compile Include="{0}"/>)";
+
+	const String CSProject::NON_CODE_ENTRY_TEMPLATE =
+		R"(
+	<None Include="{0}"/>)";
+
+	/**	Generates a C# project GUID from the project name. */
+	String getProjectGUID(const WString& projectName)
+	{
+		static const String guidTemplate = "{0}-{1}-{2}-{3}-{4}";
+		String hash = md5(projectName);
+
+		String output = StringUtil::format(guidTemplate, hash.substr(0, 8),
+			hash.substr(8, 4), hash.substr(12, 4), hash.substr(16, 4), hash.substr(20, 12));
+		StringUtil::toUpperCase(output);
+
+		return output;
+	}
+
+	String CSProject::writeSolution(CSProjectVersion version, const CodeSolutionData& data)
+	{
+		struct VersionData
+		{
+			String formatVersion;
+		};
+
+		Map<CSProjectVersion, VersionData> versionData =
+		{
+			{ CSProjectVersion::VS2008, { "10.00" } },
+			{ CSProjectVersion::VS2010, { "11.00" } },
+			{ CSProjectVersion::VS2012, { "12.00" } },
+			{ CSProjectVersion::VS2013, { "12.00" } },
+			{ CSProjectVersion::VS2015, { "12.00" } },
+			{ CSProjectVersion::VS2017, { "12.00" } },
+			{ CSProjectVersion::MonoDevelop, { "12.00" } }
+		};
+
+		StringStream projectEntriesStream;
+		StringStream projectPlatformsStream;
+		for (auto& project : data.projects)
+		{
+			String guid = getProjectGUID(project.name);
+			String projectName = toString(project.name);
+
+			projectEntriesStream << StringUtil::format(PROJ_ENTRY_TEMPLATE, projectName, projectName + ".csproj", guid);
+			projectPlatformsStream << StringUtil::format(PROJ_PLATFORM_TEMPLATE, guid);
+		}
+
+		String projectEntries = projectEntriesStream.str();
+		String projectPlatforms = projectPlatformsStream.str();
+
+		return StringUtil::format(SLN_TEMPLATE, versionData[version].formatVersion, projectEntries, projectPlatforms);
+	}
+
+	String CSProject::writeProject(CSProjectVersion version, const CodeProjectData& projectData)
+	{
+		struct VersionData
+		{
+			String toolsVersion;
+		};
+
+		Map<CSProjectVersion, VersionData> versionData =
+		{
+			{ CSProjectVersion::VS2008, { "3.5" } },
+			{ CSProjectVersion::VS2010, { "4.0" } },
+			{ CSProjectVersion::VS2012, { "4.0" } },
+			{ CSProjectVersion::VS2013, { "12.0" } },
+			{ CSProjectVersion::VS2015, { "13.0" } },
+			{ CSProjectVersion::VS2017, { "15.0" } },
+			{ CSProjectVersion::MonoDevelop, { "14.0" } }
+		};
+
+		StringStream tempStream;
+		for (auto& codeEntry : projectData.codeFiles)
+			tempStream << StringUtil::format(CODE_ENTRY_TEMPLATE, codeEntry.toString());
+
+		String codeEntries = tempStream.str();
+		tempStream.str("");
+		tempStream.clear();
+
+		for (auto& nonCodeEntry : projectData.nonCodeFiles)
+			tempStream << StringUtil::format(NON_CODE_ENTRY_TEMPLATE, nonCodeEntry.toString());
+
+		String nonCodeEntries = tempStream.str();
+		tempStream.str("");
+		tempStream.clear();
+
+		for (auto& referenceEntry : projectData.assemblyReferences)
+		{
+			String referenceName = toString(referenceEntry.name);
+
+			if (referenceEntry.path.isEmpty())
+				tempStream << StringUtil::format(REFERENCE_ENTRY_TEMPLATE, referenceName);
+			else
+				tempStream << StringUtil::format(REFERENCE_PATH_ENTRY_TEMPLATE, referenceName, referenceEntry.path.toString());
+		}
+
+		String referenceEntries = tempStream.str();
+		tempStream.str("");
+		tempStream.clear();
+
+		for (auto& referenceEntry : projectData.projectReferences)
+		{
+			String referenceName = toString(referenceEntry.name);
+			String projectGUID = getProjectGUID(referenceEntry.name);
+
+			tempStream << StringUtil::format(REFERENCE_PROJECT_ENTRY_TEMPLATE, referenceName, projectGUID);
+		}
+
+		String projectReferenceEntries = tempStream.str();
+		tempStream.str("");
+		tempStream.clear();
+
+		tempStream << toString(projectData.defines);
+
+		String defines = tempStream.str();
+		String projectGUID = getProjectGUID(projectData.name);
+
+		return StringUtil::format(PROJ_TEMPLATE, versionData[version].toolsVersion, projectGUID, 
+			toString(projectData.name), defines, referenceEntries, projectReferenceEntries, codeEntries, nonCodeEntries);
+	}
 }

+ 51 - 0
Source/BansheeEditor/CodeEditor/BsCodeEditor.h

@@ -145,5 +145,56 @@ namespace bs
 		virtual CodeEditor* create(CodeEditorType editor) const = 0;
 	};
 
+	/** Different versions of .csproj files, depending on which Microsoft Visual Studio version generated them. */
+	enum class CSProjectVersion
+	{
+		VS2008,
+		VS2010,
+		VS2012,
+		VS2013,
+		VS2015,
+		VS2017,
+		MonoDevelop
+	};
+
+	/** 
+	 * Contains helper functionality for the generation of .csproj files, as well as the .sln file. Those are used by C# IDE's like Visual Studio
+	 * and MonoDevelop, and build systems like msbuild or xbuild.
+	 */
+	class CSProject
+	{
+	public:
+		/**
+		 * Builds the .sln text for the provided version, using the provided solution data.
+		 *
+		 * @param[in]	version	Visual Studio version for which we're generating the solution file.
+		 * @param[in]	data	Data containing a list of projects and other information required to build the solution text.
+		 * @return				Generated text of the solution file.
+		 */
+		static String writeSolution(CSProjectVersion version, const CodeSolutionData& data);
+
+		/**
+		 * Builds the .csproj text for the provided version, using the provided project data.
+		 *
+		 * @param[in]	version		Visual Studio version for which we're generating the project file.
+		 * @param[in]	projectData	Data containing a list of files, references and other information required to 
+		 *							build the project text.
+		 * @return					Generated text of the project file.
+		 */
+		static String writeProject(CSProjectVersion version, const CodeProjectData& projectData);
+
+	private:
+		static const String SLN_TEMPLATE; /**< Template text used for a solution file. */
+		static const String PROJ_ENTRY_TEMPLATE; /**< Template text used for a project entry in a solution file. */
+		static const String PROJ_PLATFORM_TEMPLATE; /**< Template text used for platform specific information for a project entry in a solution file. */
+
+		static const String PROJ_TEMPLATE; /**< Template XML used for a project file. */
+		static const String REFERENCE_ENTRY_TEMPLATE; /**< Template XML used for a reference to another assembly entry by name. */
+		static const String REFERENCE_PROJECT_ENTRY_TEMPLATE; /**< Template XML used for a reference to another project entry. */
+		static const String REFERENCE_PATH_ENTRY_TEMPLATE; /**< Template XML used for a reference to another assembly entry by name and path. */
+		static const String CODE_ENTRY_TEMPLATE; /**< Template XML used for a single code file entry in a project. */
+		static const String NON_CODE_ENTRY_TEMPLATE; /**< Template XML used for a single non-code file entry in a project. */
+	};
+
 	/** @} */
 }

+ 138 - 0
Source/BansheeEditor/CodeEditor/BsMDCodeEditor.cpp

@@ -0,0 +1,138 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#include "CodeEditor/BsMDCodeEditor.h"
+#include "FileSystem/BsFileSystem.h"
+#include "FileSystem/BsDataStream.h"
+#include "String/BsUnicode.h"
+
+#if BS_PLATFORM == BS_PLATFORM_WIN32
+#include <Windows.h>
+#endif
+
+namespace bs
+{
+#if BS_PLATFORM == BS_PLATFORM_WIN32
+	/**
+	 * Reads a string value from the specified key in the registry.
+	 * 
+	 * @param[in]	key				Registry key to read from.
+	 * @param[in]	name			Identifier of the value to read from.
+	 * @param[in]	value			Output value read from the key.
+	 * @param[in]	defaultValue	Default value to return if the key or identifier doesn't exist.
+	 */
+	static LONG getRegistryStringValue(HKEY hKey, const WString& name, WString& value, const WString& defaultValue)
+	{
+		value = defaultValue;
+
+		wchar_t strBuffer[512];
+		DWORD strBufferSize = sizeof(strBuffer);
+		ULONG result = RegQueryValueExW(hKey, name.c_str(), 0, nullptr, (LPBYTE)strBuffer, &strBufferSize);
+		if (result == ERROR_SUCCESS)
+			value = strBuffer;
+
+		return result;
+	}
+#else
+
+
+#endif
+
+	MDCodeEditor::MDCodeEditor(const Path& execPath)
+		:mExecPath(execPath)
+	{
+	}
+
+	void MDCodeEditor::openFile(const Path& solutionPath, const Path& filePath, UINT32 lineNumber) const
+	{
+		WString args = L"--no-splash \"" + solutionPath.toWString() + L"\" \"" + filePath.toWString() + L";" + toWString(lineNumber) + L"\"";
+
+#if BS_PLATFORM == BS_PLATFORM_WIN32
+		WString pathStr = mExecPath.toWString();
+		ShellExecuteW(0, L"open", pathStr.c_str(), args.c_str(), NULL, SW_HIDE);
+#elif BS_PLATFORM == BS_PLATFORM_LINUX
+		String narrowArgs = UTF8::fromWide(args);
+		const char* commandPattern = "flatpak run com.xamarin.MonoDevelop %s";
+
+		char* commandStr = (char*)bs_stack_alloc((UINT32)narrowArgs.size() + (UINT32)strlen(commandPattern) + 1);
+		sprintf(commandStr, commandPattern, narrowArgs.c_str());
+
+		system(commandStr);
+		bs_stack_free(commandStr);
+#endif
+	}
+
+	void MDCodeEditor::syncSolution(const CodeSolutionData& data, const Path& outputPath) const
+	{
+		String solutionString = CSProject::writeSolution(CSProjectVersion::MonoDevelop, data);
+		solutionString = StringUtil::replaceAll(solutionString, "\n", "\r\n");
+		Path solutionPath = outputPath;
+		solutionPath.append(data.name + L".sln");
+
+		for (auto& project : data.projects)
+		{
+			String projectString = CSProject::writeProject(CSProjectVersion::MonoDevelop, project);
+			projectString = StringUtil::replaceAll(projectString, "\n", "\r\n");
+
+			Path projectPath = outputPath;
+			projectPath.append(project.name + L".csproj");
+
+			SPtr<DataStream> projectStream = FileSystem::createAndOpenFile(projectPath);
+			projectStream->write(projectString.c_str(), projectString.size() * sizeof(String::value_type));
+			projectStream->close();
+		}
+
+		SPtr<DataStream> solutionStream = FileSystem::createAndOpenFile(solutionPath);
+		solutionStream->write(solutionString.c_str(), solutionString.size() * sizeof(String::value_type));
+		solutionStream->close();
+	}
+
+	MDCodeEditorFactory::MDCodeEditorFactory()
+	{ 
+#if BS_PLATFORM == BS_PLATFORM_WIN32
+
+#if BS_ARCH_TYPE == BS_ARCHITECTURE_x86_64
+		BOOL is64bit = 1;
+#else
+		BOOL is64bit = 0;
+		
+		HANDLE process = GetCurrentProcess();
+		IsWow64Process(process, (PBOOL)&is64bit);
+#endif
+		WString registryKeyRoot;
+		if (is64bit)
+			registryKeyRoot = L"SOFTWARE\\Wow6432Node\\"; 
+		else
+			registryKeyRoot = L"SOFTWARE\\";
+
+		WString registryKey = registryKeyRoot + L"\\Xamarin\\XamarinStudio";
+
+		HKEY regKey;
+		LONG result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, registryKey.c_str(), 0, KEY_READ, &regKey);
+		if (result != ERROR_SUCCESS)
+			return;
+
+		WString installPath;
+		getRegistryStringValue(regKey, L"Path", installPath, StringUtil::WBLANK);
+		if (installPath.empty())
+			return;
+
+		mInstallPath = Path(installPath) + Path("XamarinStudio.exe");
+#elif BS_PLATFORM == BS_PLATFORM_LINUX
+		static_assert(false);
+#endif
+
+		if (!mInstallPath.isEmpty())
+			mSupportedEditors.push_back(CodeEditorType::MonoDevelop);
+	}
+
+	CodeEditor* MDCodeEditorFactory::create(CodeEditorType type) const
+	{
+		if (mInstallPath.isEmpty())
+			return nullptr;
+
+		if (type != CodeEditorType::MonoDevelop)
+			return nullptr;
+
+		return bs_new<MDCodeEditor>(mInstallPath);
+	}
+}

+ 48 - 0
Source/BansheeEditor/CodeEditor/BsMDCodeEditor.h

@@ -0,0 +1,48 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#pragma once
+
+#include "BsEditorPrerequisites.h"
+#include "CodeEditor/BsCodeEditor.h"
+
+namespace bs
+{
+	/** @addtogroup CodeEditor-Internal
+	 *  @{
+	 */
+
+	/**	Code editor implementation that handles interacting with MonoDevelop. */
+	class BS_ED_EXPORT MDCodeEditor : public CodeEditor
+	{
+	public:
+		MDCodeEditor(const Path& execPath);
+
+		/** @copydoc CodeEditor::openFile */
+		void openFile(const Path& solutionPath, const Path& path, UINT32 lineNumber) const override;
+
+		/** @copydoc CodeEditor::syncSolution */
+		void syncSolution(const CodeSolutionData& data, const Path& outputPath) const override;
+
+	private:
+		Path mExecPath;
+	};
+
+	/** Code editor factory used for creating specific instances of the MonoDevelop code editor object. */
+	class BS_ED_EXPORT MDCodeEditorFactory : public CodeEditorFactory
+	{
+	public:
+		MDCodeEditorFactory();
+
+		/** @copydoc CodeEditorFactory::getAvailableEditors */
+		const Vector<CodeEditorType>& getAvailableEditors() const override { return mSupportedEditors; }
+
+		/** @copydoc CodeEditorFactory::create */
+		CodeEditor* create(CodeEditorType editor) const override;
+
+	private:
+		Path mInstallPath;
+		Vector<CodeEditorType> mSupportedEditors;
+	};
+
+	/** @} */
+}

+ 15 - 255
Source/BansheeEditor/Win32/BsVSCodeEditor.cpp

@@ -36,7 +36,7 @@ namespace bs
 	 * @param[in]	value			Output value read from the key.
 	 * @param[in]	defaultValue	Default value to return if the key or identifier doesn't exist.
 	 */
-	LONG getRegistryStringValue(HKEY hKey, const WString& name, WString& value, const WString& defaultValue)
+	static LONG getRegistryStringValue(HKEY hKey, const WString& name, WString& value, const WString& defaultValue)
 	{
 		value = defaultValue;
 
@@ -126,155 +126,6 @@ namespace bs
 		LONG mRefCount;
 	};
 
-	/** 
-	 * Contains helper functionality for the generation of .csproj files, as well as the .sln file. Those are used by C# IDE's like Visual Studio
-	 * and MonoDevelop, and build systems like msbuild or xbuild.
-	 */
-	class CSProject
-	{
-	private:
-		static const String SLN_TEMPLATE; /**< Template text used for a solution file. */
-		static const String PROJ_ENTRY_TEMPLATE; /**< Template text used for a project entry in a solution file. */
-		static const String PROJ_PLATFORM_TEMPLATE; /**< Template text used for platform specific information for a project entry in a solution file. */
-
-		static const String PROJ_TEMPLATE; /**< Template XML used for a project file. */
-		static const String REFERENCE_ENTRY_TEMPLATE; /**< Template XML used for a reference to another assembly entry by name. */
-		static const String REFERENCE_PROJECT_ENTRY_TEMPLATE; /**< Template XML used for a reference to another project entry. */
-		static const String REFERENCE_PATH_ENTRY_TEMPLATE; /**< Template XML used for a reference to another assembly entry by name and path. */
-		static const String CODE_ENTRY_TEMPLATE; /**< Template XML used for a single code file entry in a project. */
-		static const String NON_CODE_ENTRY_TEMPLATE; /**< Template XML used for a single non-code file entry in a project. */
-
-	public:
-		/**	Generates a C# project GUID from the project name. */
-		static String getProjectGUID(const WString& projectName)
-		{
-			static const String guidTemplate = "{0}-{1}-{2}-{3}-{4}";
-			String hash = md5(projectName);
-
-			String output = StringUtil::format(guidTemplate, hash.substr(0, 8),
-				hash.substr(8, 4), hash.substr(12, 4), hash.substr(16, 4), hash.substr(20, 12));
-			StringUtil::toUpperCase(output);
-
-			return output;
-		}
-
-		/**
-		 * Builds the .sln text for the provided version, using the provided solution data.
-		 *
-		 * @param[in]	version	Visual Studio version for which we're generating the solution file.
-		 * @param[in]	data	Data containing a list of projects and other information required to build the solution text.
-		 * @return				Generated text of the solution file.
-		 */
-		static String writeSolution(VisualStudioVersion version, const CodeSolutionData& data)
-		{
-			struct VersionData
-			{
-				String formatVersion;
-			};
-
-			Map<VisualStudioVersion, VersionData> versionData =
-			{
-				{ VisualStudioVersion::VS2008, { "10.00" } },
-				{ VisualStudioVersion::VS2010, { "11.00" } },
-				{ VisualStudioVersion::VS2012, { "12.00" } },
-				{ VisualStudioVersion::VS2013, { "12.00" } },
-				{ VisualStudioVersion::VS2015, { "12.00" } },
-				{ VisualStudioVersion::VS2017, { "12.00" } }
-			};
-
-			StringStream projectEntriesStream;
-			StringStream projectPlatformsStream;
-			for (auto& project : data.projects)
-			{
-				String guid = getProjectGUID(project.name);
-				String projectName = toString(project.name);
-
-				projectEntriesStream << StringUtil::format(PROJ_ENTRY_TEMPLATE, projectName, projectName + ".csproj", guid);
-				projectPlatformsStream << StringUtil::format(PROJ_PLATFORM_TEMPLATE, guid);
-			}
-
-			String projectEntries = projectEntriesStream.str();
-			String projectPlatforms = projectPlatformsStream.str();
-
-			return StringUtil::format(SLN_TEMPLATE, versionData[version].formatVersion, projectEntries, projectPlatforms);
-		}
-
-		/**
-		 * Builds the .csproj text for the provided version, using the provided project data.
-		 *
-		 * @param[in]	version		Visual Studio version for which we're generating the project file.
-		 * @param[in]	projectData	Data containing a list of files, references and other information required to 
-		 *							build the project text.
-		 * @return					Generated text of the project file.
-		 */
-		static String writeProject(VisualStudioVersion version, const CodeProjectData& projectData)
-		{
-			struct VersionData
-			{
-				String toolsVersion;
-			};
-
-			Map<VisualStudioVersion, VersionData> versionData =
-			{
-				{ VisualStudioVersion::VS2008, { "3.5" } },
-				{ VisualStudioVersion::VS2010, { "4.0" } },
-				{ VisualStudioVersion::VS2012, { "4.0" } },
-				{ VisualStudioVersion::VS2013, { "12.0" } },
-				{ VisualStudioVersion::VS2015, { "13.0" } },
-				{ VisualStudioVersion::VS2017, { "15.0" } }
-			};
-
-			StringStream tempStream;
-			for (auto& codeEntry : projectData.codeFiles)
-				tempStream << StringUtil::format(CODE_ENTRY_TEMPLATE, codeEntry.toString());
-
-			String codeEntries = tempStream.str();
-			tempStream.str("");
-			tempStream.clear();
-
-			for (auto& nonCodeEntry : projectData.nonCodeFiles)
-				tempStream << StringUtil::format(NON_CODE_ENTRY_TEMPLATE, nonCodeEntry.toString());
-
-			String nonCodeEntries = tempStream.str();
-			tempStream.str("");
-			tempStream.clear();
-
-			for (auto& referenceEntry : projectData.assemblyReferences)
-			{
-				String referenceName = toString(referenceEntry.name);
-
-				if (referenceEntry.path.isEmpty())
-					tempStream << StringUtil::format(REFERENCE_ENTRY_TEMPLATE, referenceName);
-				else
-					tempStream << StringUtil::format(REFERENCE_PATH_ENTRY_TEMPLATE, referenceName, referenceEntry.path.toString());
-			}
-
-			String referenceEntries = tempStream.str();
-			tempStream.str("");
-			tempStream.clear();
-
-			for (auto& referenceEntry : projectData.projectReferences)
-			{
-				String referenceName = toString(referenceEntry.name);
-				String projectGUID = getProjectGUID(referenceEntry.name);
-
-				tempStream << StringUtil::format(REFERENCE_PROJECT_ENTRY_TEMPLATE, referenceName, projectGUID);
-			}
-
-			String projectReferenceEntries = tempStream.str();
-			tempStream.str("");
-			tempStream.clear();
-
-			tempStream << toString(projectData.defines);
-
-			String defines = tempStream.str();
-			String projectGUID = getProjectGUID(projectData.name);
-
-			return StringUtil::format(PROJ_TEMPLATE, versionData[version].toolsVersion, projectGUID, 
-				toString(projectData.name), defines, referenceEntries, projectReferenceEntries, codeEntries, nonCodeEntries);
-		}
-	};
-
 	/** Contains various helper functionality for interacting with a Visual Studio instance running on this machine. */
 	class VisualStudio
 	{
@@ -428,111 +279,8 @@ namespace bs
 
 			return true;
 		}
-
 	};
 
-	const String CSProject::SLN_TEMPLATE =
-		R"(Microsoft Visual Studio Solution File, Format Version {0}
-# Visual Studio 2013
-VisualStudioVersion = 12.0.30723.0
-MinimumVisualStudioVersion = 10.0.40219.1{1}
-Global
-	GlobalSection(SolutionConfigurationPlatforms) = preSolution
-		Debug|Any CPU = Debug|Any CPU
-		Release|Any CPU = Release|Any CPU
-	EndGlobalSection
-	GlobalSection(ProjectConfigurationPlatforms) = postSolution{2}
-	EndGlobalSection
-	GlobalSection(SolutionProperties) = preSolution
-		HideSolutionNode = FALSE
-	EndGlobalSection
-EndGlobal
-)";
-
-	const String CSProject::PROJ_ENTRY_TEMPLATE =
-		R"(
-Project("\{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC\}") = "{0}", "{1}", "\{{2}\}"
-EndProject)";
-
-	const String CSProject::PROJ_PLATFORM_TEMPLATE =
-		R"(
-		\{{0}\}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		\{{0}\}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		\{{0}\}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		\{{0}\}.Release|Any CPU.Build.0 = Release|Any CPU)";
-
-	const String CSProject::PROJ_TEMPLATE =
-		R"literal(<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="{0}" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <Import Project="$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props')" />
-  <PropertyGroup>
-	<Configuration Condition = " '$(Configuration)' == '' ">Debug</Configuration>
-	<Platform Condition = " '$(Platform)' == '' ">AnyCPU</Platform>
-	<ProjectGuid>\{{1}\}</ProjectGuid>
-	<OutputType>Library</OutputType>
-	<AppDesignerFolder>Properties</AppDesignerFolder>
-	<RootNamespace></RootNamespace>
-	<AssemblyName>{2}</AssemblyName>
-	<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
-	<FileAlignment>512</FileAlignment>
-	<BaseDirectory>Resources</BaseDirectory>
-	<SchemaVersion>2.0</SchemaVersion>
-  </PropertyGroup>
-  <PropertyGroup Condition = " '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-	<DebugSymbols>true</DebugSymbols>
-	<DebugType>full</DebugType>
-	<Optimize>false</Optimize>
-	<OutputPath>Internal\\Temp\\Assemblies\\Debug\\</OutputPath>
-	<BaseIntermediateOutputPath>Internal\\Temp\\Assemblies\\</BaseIntermediateOutputPath>
-	<DefineConstants>DEBUG;TRACE;{3}</DefineConstants>
-	<ErrorReport>prompt</ErrorReport>
-	<WarningLevel>4</WarningLevel >
-  </PropertyGroup>
-  <PropertyGroup Condition = " '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
-	<DebugType>pdbonly</DebugType>
-	<Optimize>true</Optimize>
-	<OutputPath>Internal\\Temp\\Assemblies\\Release\\</OutputPath>
-	<BaseIntermediateOutputPath>Internal\\Temp\\Assemblies\\</BaseIntermediateOutputPath>
-	<DefineConstants>TRACE;{3}</DefineConstants>
-	<ErrorReport>prompt</ErrorReport>
-	<WarningLevel>4</WarningLevel>
-  </PropertyGroup>
-  <ItemGroup>{4}
-  </ItemGroup>
-  <ItemGroup>{5}
-  </ItemGroup>
-  <ItemGroup>{6}
-  </ItemGroup>
-  <ItemGroup>{7}
-  </ItemGroup>
-  <Import Project = "$(MSBuildToolsPath)\\Microsoft.CSharp.targets"/>
-</Project>)literal";
-
-	const String CSProject::REFERENCE_ENTRY_TEMPLATE =
-		R"(
-	<Reference Include="{0}"/>)";
-
-	const String CSProject::REFERENCE_PATH_ENTRY_TEMPLATE =
-		R"(
-	<Reference Include="{0}">
-	  <HintPath>{1}</HintPath>
-	</Reference>)";
-
-	const String CSProject::REFERENCE_PROJECT_ENTRY_TEMPLATE =
-		R"(
-	<ProjectReference Include="{0}.csproj">
-	  <Project>\{{1}\}</Project>
-	  <Name>{0}</Name>
-	</ProjectReference>)";
-
-	const String CSProject::CODE_ENTRY_TEMPLATE =
-		R"(
-	<Compile Include="{0}"/>)";
-
-	const String CSProject::NON_CODE_ENTRY_TEMPLATE =
-		R"(
-	<None Include="{0}"/>)";
-
 	VSCodeEditor::VSCodeEditor(VisualStudioVersion version, const Path& execPath, const WString& CLSID)
 		:mVersion(version), mExecPath(execPath), mCLSID(CLSID)
 	{
@@ -576,14 +324,26 @@ EndProject)";
 
 	void VSCodeEditor::syncSolution(const CodeSolutionData& data, const Path& outputPath) const
 	{
-		String solutionString = CSProject::writeSolution(mVersion, data);
+		CSProjectVersion csProjVer;
+		switch(mVersion)
+		{
+		case VisualStudioVersion::VS2008: csProjVer = CSProjectVersion::VS2008; break;
+		case VisualStudioVersion::VS2010: csProjVer = CSProjectVersion::VS2010; break;
+		case VisualStudioVersion::VS2012: csProjVer = CSProjectVersion::VS2012; break;
+		case VisualStudioVersion::VS2013: csProjVer = CSProjectVersion::VS2013; break;
+		case VisualStudioVersion::VS2015: csProjVer = CSProjectVersion::VS2015; break;
+		default:
+		case VisualStudioVersion::VS2017: csProjVer = CSProjectVersion::VS2017;  break;
+		}
+
+		String solutionString = CSProject::writeSolution(csProjVer, data);
 		solutionString = StringUtil::replaceAll(solutionString, "\n", "\r\n");
 		Path solutionPath = outputPath;
 		solutionPath.append(data.name + L".sln");
 
 		for (auto& project : data.projects)
 		{
-			String projectString = CSProject::writeProject(mVersion, project);
+			String projectString = CSProject::writeProject(csProjVer, project);
 			projectString = StringUtil::replaceAll(projectString, "\n", "\r\n");
 
 			Path projectPath = outputPath;

+ 2 - 0
Source/MBansheeEditor/Script/CodeEditor.cs

@@ -20,6 +20,8 @@ namespace BansheeEditor
         VS2012,
         VS2013,
         VS2015,
+        VS2017,
+        MonoDevelop,
         None
     }