BsVSCodeEditor.cpp 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657
  1. //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
  2. //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
  3. #include "Win32/BsVSCodeEditor.h"
  4. #include <windows.h>
  5. #include <atlbase.h>
  6. #include "FileSystem/BsFileSystem.h"
  7. #include "FileSystem/BsDataStream.h"
  8. // Import EnvDTE
  9. #pragma warning(disable: 4278)
  10. #import "libid:80cc9f66-e7d8-4ddd-85b6-d9e6cd0e93e2" version("8.0") lcid("0") raw_interfaces_only named_guids
  11. #pragma warning(default: 4278)
  12. namespace bs
  13. {
  14. /**
  15. * Reads a string value from the specified key in the registry.
  16. *
  17. * @param[in] key Registry key to read from.
  18. * @param[in] name Identifier of the value to read from.
  19. * @param[in] value Output value read from the key.
  20. * @param[in] defaultValue Default value to return if the key or identifier doesn't exist.
  21. */
  22. LONG getRegistryStringValue(HKEY hKey, const WString& name, WString& value, const WString& defaultValue)
  23. {
  24. value = defaultValue;
  25. wchar_t strBuffer[512];
  26. DWORD strBufferSize = sizeof(strBuffer);
  27. ULONG result = RegQueryValueExW(hKey, name.c_str(), 0, nullptr, (LPBYTE)strBuffer, &strBufferSize);
  28. if (result == ERROR_SUCCESS)
  29. value = strBuffer;
  30. return result;
  31. }
  32. /** Contains data about a Visual Studio project. */
  33. struct VSProjectInfo
  34. {
  35. WString GUID;
  36. WString name;
  37. Path path;
  38. };
  39. /**
  40. * Handles retrying of calls that fail to access Visual Studio. This is due to the weird nature of VS when calling its
  41. * methods from external code. If this message filter isn't registered some calls will just fail silently.
  42. */
  43. class VSMessageFilter : public IMessageFilter
  44. {
  45. DWORD __stdcall HandleInComingCall(DWORD dwCallType, HTASK htaskCaller, DWORD dwTickCount, LPINTERFACEINFO lpInterfaceInfo) override
  46. {
  47. return SERVERCALL_ISHANDLED;
  48. }
  49. DWORD __stdcall RetryRejectedCall(HTASK htaskCallee, DWORD dwTickCount, DWORD dwRejectType) override
  50. {
  51. if (dwRejectType == SERVERCALL_RETRYLATER)
  52. {
  53. // Retry immediatey
  54. return 99;
  55. }
  56. // Cancel the call
  57. return -1;
  58. }
  59. DWORD __stdcall MessagePending(HTASK htaskCallee, DWORD dwTickCount, DWORD dwPendingType) override
  60. {
  61. return PENDINGMSG_WAITDEFPROCESS;
  62. }
  63. /** COM requirement. Returns instance of an interface of provided type. */
  64. HRESULT __stdcall QueryInterface(REFIID iid, void** ppvObject) override
  65. {
  66. if(iid == IID_IDropTarget || iid == IID_IUnknown)
  67. {
  68. AddRef();
  69. *ppvObject = this;
  70. return S_OK;
  71. }
  72. else
  73. {
  74. *ppvObject = nullptr;
  75. return E_NOINTERFACE;
  76. }
  77. }
  78. /** COM requirement. Increments objects reference count. */
  79. ULONG __stdcall AddRef() override
  80. {
  81. return InterlockedIncrement(&mRefCount);
  82. }
  83. /** COM requirement. Decreases the objects reference count and deletes the object if its zero. */
  84. ULONG __stdcall Release() override
  85. {
  86. LONG count = InterlockedDecrement(&mRefCount);
  87. if(count == 0)
  88. {
  89. bs_delete(this);
  90. return 0;
  91. }
  92. else
  93. {
  94. return count;
  95. }
  96. }
  97. private:
  98. LONG mRefCount;
  99. };
  100. /** Contains various helper classes for interacting with a Visual Studio instance running on this machine. */
  101. class VisualStudio
  102. {
  103. private:
  104. static const String SLN_TEMPLATE; /**< Template text used for a solution file. */
  105. static const String PROJ_ENTRY_TEMPLATE; /**< Template text used for a project entry in a solution file. */
  106. static const String PROJ_PLATFORM_TEMPLATE; /**< Template text used for platform specific information for a project entry in a solution file. */
  107. static const String PROJ_TEMPLATE; /**< Template XML used for a project file. */
  108. static const String REFERENCE_ENTRY_TEMPLATE; /**< Template XML used for a reference to another assembly entry by name. */
  109. static const String REFERENCE_PROJECT_ENTRY_TEMPLATE; /**< Template XML used for a reference to another project entry. */
  110. static const String REFERENCE_PATH_ENTRY_TEMPLATE; /**< Template XML used for a reference to another assembly entry by name and path. */
  111. static const String CODE_ENTRY_TEMPLATE; /**< Template XML used for a single code file entry in a project. */
  112. static const String NON_CODE_ENTRY_TEMPLATE; /**< Template XML used for a single non-code file entry in a project. */
  113. public:
  114. /**
  115. * Scans the running processes to find a running Visual Studio instance with the specified version and open solution.
  116. *
  117. * @param[in] clsID Class ID of the specific Visual Studio version we are looking for.
  118. * @param[in] solutionPath Path to the solution the instance needs to have open.
  119. * @return DTE object that may be used to interact with the Visual Studio instance, or null if
  120. * not found.
  121. */
  122. static CComPtr<EnvDTE::_DTE> findRunningInstance(const CLSID& clsID, const Path& solutionPath)
  123. {
  124. CComPtr<IRunningObjectTable> runningObjectTable = nullptr;
  125. if (FAILED(GetRunningObjectTable(0, &runningObjectTable)))
  126. return nullptr;
  127. CComPtr<IEnumMoniker> enumMoniker = nullptr;
  128. if (FAILED(runningObjectTable->EnumRunning(&enumMoniker)))
  129. return nullptr;
  130. CComPtr<IMoniker> dteMoniker = nullptr;
  131. if (FAILED(CreateClassMoniker(clsID, &dteMoniker)))
  132. return nullptr;
  133. CComBSTR bstrSolution(solutionPath.toWString(Path::PathType::Windows).c_str());
  134. CComPtr<IMoniker> moniker;
  135. ULONG count = 0;
  136. while (enumMoniker->Next(1, &moniker, &count) == S_OK)
  137. {
  138. if (moniker->IsEqual(dteMoniker))
  139. {
  140. CComPtr<IUnknown> curObject = nullptr;
  141. HRESULT result = runningObjectTable->GetObject(moniker, &curObject);
  142. moniker = nullptr;
  143. if (result != S_OK)
  144. continue;
  145. CComPtr<EnvDTE::_DTE> dte;
  146. curObject->QueryInterface(__uuidof(EnvDTE::_DTE), (void**)&dte);
  147. if (dte == nullptr)
  148. continue;
  149. CComPtr<EnvDTE::_Solution> solution;
  150. if (FAILED(dte->get_Solution(&solution)))
  151. continue;
  152. CComBSTR fullName;
  153. if (FAILED(solution->get_FullName(&fullName)))
  154. continue;
  155. if (fullName == bstrSolution)
  156. return dte;
  157. }
  158. }
  159. return nullptr;
  160. }
  161. /**
  162. * Opens a new Visual Studio instance of the specified version with the provided solution.
  163. *
  164. * @param[in] clsID Class ID of the specific Visual Studio version to start.
  165. * @param[in] solutionPath Path to the solution the instance needs to open.
  166. */
  167. static CComPtr<EnvDTE::_DTE> openInstance(const CLSID& clsid, const Path& solutionPath)
  168. {
  169. CComPtr<IUnknown> newInstance = nullptr;
  170. if (FAILED(::CoCreateInstance(clsid, nullptr, CLSCTX_LOCAL_SERVER, EnvDTE::IID__DTE, (LPVOID*)&newInstance)))
  171. return nullptr;
  172. CComPtr<EnvDTE::_DTE> dte;
  173. newInstance->QueryInterface(__uuidof(EnvDTE::_DTE), (void**)&dte);
  174. if (dte == nullptr)
  175. return nullptr;
  176. dte->put_UserControl(TRUE);
  177. CComPtr<EnvDTE::_Solution> solution;
  178. if (FAILED(dte->get_Solution(&solution)))
  179. return nullptr;
  180. CComBSTR bstrSolution(solutionPath.toWString(Path::PathType::Windows).c_str());
  181. if (FAILED(solution->Open(bstrSolution)))
  182. return nullptr;
  183. // Wait until VS opens
  184. UINT32 elapsed = 0;
  185. while (elapsed < 10000)
  186. {
  187. EnvDTE::Window* window = nullptr;
  188. if (SUCCEEDED(dte->get_MainWindow(&window)))
  189. return dte;
  190. Sleep(100);
  191. elapsed += 100;
  192. }
  193. return nullptr;
  194. }
  195. /**
  196. * Opens a file on a specific line in a running Visual Studio instance.
  197. *
  198. * @param[in] dte DTE object retrieved from findRunningInstance() or openInstance().
  199. * @param[in] filePath Path of the file to open. File should be a part of the VS solution.
  200. * @param[in] line Line on which to focus Visual Studio after the file is open.
  201. */
  202. static bool openFile(CComPtr<EnvDTE::_DTE> dte, const Path& filePath, UINT32 line)
  203. {
  204. // Open file
  205. CComPtr<EnvDTE::ItemOperations> itemOperations;
  206. if (FAILED(dte->get_ItemOperations(&itemOperations)))
  207. return false;
  208. CComBSTR bstrFilePath(filePath.toWString(Path::PathType::Windows).c_str());
  209. CComBSTR bstrKind(EnvDTE::vsViewKindPrimary);
  210. CComPtr<EnvDTE::Window> window = nullptr;
  211. if (FAILED(itemOperations->OpenFile(bstrFilePath, bstrKind, &window)))
  212. return false;
  213. // Scroll to line
  214. CComPtr<EnvDTE::Document> activeDocument;
  215. if (SUCCEEDED(dte->get_ActiveDocument(&activeDocument)))
  216. {
  217. CComPtr<IDispatch> selection;
  218. if (SUCCEEDED(activeDocument->get_Selection(&selection)))
  219. {
  220. CComPtr<EnvDTE::TextSelection> textSelection;
  221. if (SUCCEEDED(selection->QueryInterface(&textSelection)))
  222. {
  223. textSelection->GotoLine(line, TRUE);
  224. }
  225. }
  226. }
  227. // Bring the window in focus
  228. window = nullptr;
  229. if (SUCCEEDED(dte->get_MainWindow(&window)))
  230. {
  231. window->Activate();
  232. HWND hWnd;
  233. window->get_HWnd((LONG*)&hWnd);
  234. SetForegroundWindow(hWnd);
  235. }
  236. return true;
  237. }
  238. /** Generates a Visual Studio project GUID from the project name. */
  239. static String getProjectGUID(const WString& projectName)
  240. {
  241. static const String guidTemplate = "{0}-{1}-{2}-{3}-{4}";
  242. String hash = md5(projectName);
  243. String output = StringUtil::format(guidTemplate, hash.substr(0, 8),
  244. hash.substr(8, 4), hash.substr(12, 4), hash.substr(16, 4), hash.substr(20, 12));
  245. StringUtil::toUpperCase(output);
  246. return output;
  247. }
  248. /**
  249. * Builds the Visual Studio solution text (.sln) for the provided version, using the provided solution data.
  250. *
  251. * @param[in] version Visual Studio version for which we're generating the solution file.
  252. * @param[in] data Data containing a list of projects and other information required to build the solution text.
  253. * @return Generated text of the solution file.
  254. */
  255. static String writeSolution(VisualStudioVersion version, const CodeSolutionData& data)
  256. {
  257. struct VersionData
  258. {
  259. String formatVersion;
  260. };
  261. Map<VisualStudioVersion, VersionData> versionData =
  262. {
  263. { VisualStudioVersion::VS2008, { "10.00" } },
  264. { VisualStudioVersion::VS2010, { "11.00" } },
  265. { VisualStudioVersion::VS2012, { "12.00" } },
  266. { VisualStudioVersion::VS2013, { "12.00" } },
  267. { VisualStudioVersion::VS2015, { "12.00" } }
  268. };
  269. StringStream projectEntriesStream;
  270. StringStream projectPlatformsStream;
  271. for (auto& project : data.projects)
  272. {
  273. String guid = getProjectGUID(project.name);
  274. String projectName = toString(project.name);
  275. projectEntriesStream << StringUtil::format(PROJ_ENTRY_TEMPLATE, projectName, projectName + ".csproj", guid);
  276. projectPlatformsStream << StringUtil::format(PROJ_PLATFORM_TEMPLATE, guid);
  277. }
  278. String projectEntries = projectEntriesStream.str();
  279. String projectPlatforms = projectPlatformsStream.str();
  280. return StringUtil::format(SLN_TEMPLATE, versionData[version].formatVersion, projectEntries, projectPlatforms);
  281. }
  282. /**
  283. * Builds the Visual Studio project text (.csproj) for the provided version, using the provided project data.
  284. *
  285. * @param[in] version Visual Studio version for which we're generating the project file.
  286. * @param[in] projectData Data containing a list of files, references and other information required to
  287. * build the project text.
  288. * @return Generated text of the project file.
  289. */
  290. static String writeProject(VisualStudioVersion version, const CodeProjectData& projectData)
  291. {
  292. struct VersionData
  293. {
  294. String toolsVersion;
  295. };
  296. Map<VisualStudioVersion, VersionData> versionData =
  297. {
  298. { VisualStudioVersion::VS2008, { "3.5" } },
  299. { VisualStudioVersion::VS2010, { "4.0" } },
  300. { VisualStudioVersion::VS2012, { "4.0" } },
  301. { VisualStudioVersion::VS2013, { "12.0" } },
  302. { VisualStudioVersion::VS2015, { "13.0" } }
  303. };
  304. StringStream tempStream;
  305. for (auto& codeEntry : projectData.codeFiles)
  306. tempStream << StringUtil::format(CODE_ENTRY_TEMPLATE, codeEntry.toString());
  307. String codeEntries = tempStream.str();
  308. tempStream.str("");
  309. tempStream.clear();
  310. for (auto& nonCodeEntry : projectData.nonCodeFiles)
  311. tempStream << StringUtil::format(NON_CODE_ENTRY_TEMPLATE, nonCodeEntry.toString());
  312. String nonCodeEntries = tempStream.str();
  313. tempStream.str("");
  314. tempStream.clear();
  315. for (auto& referenceEntry : projectData.assemblyReferences)
  316. {
  317. String referenceName = toString(referenceEntry.name);
  318. if (referenceEntry.path.isEmpty())
  319. tempStream << StringUtil::format(REFERENCE_ENTRY_TEMPLATE, referenceName);
  320. else
  321. tempStream << StringUtil::format(REFERENCE_PATH_ENTRY_TEMPLATE, referenceName, referenceEntry.path.toString());
  322. }
  323. String referenceEntries = tempStream.str();
  324. tempStream.str("");
  325. tempStream.clear();
  326. for (auto& referenceEntry : projectData.projectReferences)
  327. {
  328. String referenceName = toString(referenceEntry.name);
  329. String projectGUID = getProjectGUID(referenceEntry.name);
  330. tempStream << StringUtil::format(REFERENCE_PROJECT_ENTRY_TEMPLATE, referenceName, projectGUID);
  331. }
  332. String projectReferenceEntries = tempStream.str();
  333. tempStream.str("");
  334. tempStream.clear();
  335. tempStream << toString(projectData.defines);
  336. String defines = tempStream.str();
  337. String projectGUID = getProjectGUID(projectData.name);
  338. return StringUtil::format(PROJ_TEMPLATE, versionData[version].toolsVersion, projectGUID,
  339. toString(projectData.name), defines, referenceEntries, projectReferenceEntries, codeEntries, nonCodeEntries);
  340. }
  341. };
  342. const String VisualStudio::SLN_TEMPLATE =
  343. R"(Microsoft Visual Studio Solution File, Format Version {0}
  344. # Visual Studio 2013
  345. VisualStudioVersion = 12.0.30723.0
  346. MinimumVisualStudioVersion = 10.0.40219.1{1}
  347. Global
  348. GlobalSection(SolutionConfigurationPlatforms) = preSolution
  349. Debug|Any CPU = Debug|Any CPU
  350. Release|Any CPU = Release|Any CPU
  351. EndGlobalSection
  352. GlobalSection(ProjectConfigurationPlatforms) = postSolution{2}
  353. EndGlobalSection
  354. GlobalSection(SolutionProperties) = preSolution
  355. HideSolutionNode = FALSE
  356. EndGlobalSection
  357. EndGlobal
  358. )";
  359. const String VisualStudio::PROJ_ENTRY_TEMPLATE =
  360. R"(
  361. Project("\{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC\}") = "{0}", "{1}", "\{{2}\}"
  362. EndProject)";
  363. const String VisualStudio::PROJ_PLATFORM_TEMPLATE =
  364. R"(
  365. \{{0}\}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
  366. \{{0}\}.Debug|Any CPU.Build.0 = Debug|Any CPU
  367. \{{0}\}.Release|Any CPU.ActiveCfg = Release|Any CPU
  368. \{{0}\}.Release|Any CPU.Build.0 = Release|Any CPU)";
  369. const String VisualStudio::PROJ_TEMPLATE =
  370. R"literal(<?xml version="1.0" encoding="utf-8"?>
  371. <Project ToolsVersion="{0}" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  372. <Import Project="$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props')" />
  373. <PropertyGroup>
  374. <Configuration Condition = " '$(Configuration)' == '' ">Debug</Configuration>
  375. <Platform Condition = " '$(Platform)' == '' ">AnyCPU</Platform>
  376. <ProjectGuid>\{{1}\}</ProjectGuid>
  377. <OutputType>Library</OutputType>
  378. <AppDesignerFolder>Properties</AppDesignerFolder>
  379. <RootNamespace></RootNamespace>
  380. <AssemblyName>{2}</AssemblyName>
  381. <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
  382. <FileAlignment>512</FileAlignment>
  383. <BaseDirectory>Resources</BaseDirectory>
  384. <SchemaVersion>2.0</SchemaVersion>
  385. </PropertyGroup>
  386. <PropertyGroup Condition = " '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
  387. <DebugSymbols>true</DebugSymbols>
  388. <DebugType>full</DebugType>
  389. <Optimize>false</Optimize>
  390. <OutputPath>Internal\\Temp\\Assemblies\\Debug\\</OutputPath>
  391. <BaseIntermediateOutputPath>Internal\\Temp\\Assemblies\\</BaseIntermediateOutputPath>
  392. <DefineConstants>DEBUG;TRACE;{3}</DefineConstants>
  393. <ErrorReport>prompt</ErrorReport>
  394. <WarningLevel>4</WarningLevel >
  395. </PropertyGroup>
  396. <PropertyGroup Condition = " '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
  397. <DebugType>pdbonly</DebugType>
  398. <Optimize>true</Optimize>
  399. <OutputPath>Internal\\Temp\\Assemblies\\Release\\</OutputPath>
  400. <BaseIntermediateOutputPath>Internal\\Temp\\Assemblies\\</BaseIntermediateOutputPath>
  401. <DefineConstants>TRACE;{3}</DefineConstants>
  402. <ErrorReport>prompt</ErrorReport>
  403. <WarningLevel>4</WarningLevel>
  404. </PropertyGroup>
  405. <ItemGroup>{4}
  406. </ItemGroup>
  407. <ItemGroup>{5}
  408. </ItemGroup>
  409. <ItemGroup>{6}
  410. </ItemGroup>
  411. <ItemGroup>{7}
  412. </ItemGroup>
  413. <Import Project = "$(MSBuildToolsPath)\\Microsoft.CSharp.targets"/>
  414. </Project>)literal";
  415. const String VisualStudio::REFERENCE_ENTRY_TEMPLATE =
  416. R"(
  417. <Reference Include="{0}"/>)";
  418. const String VisualStudio::REFERENCE_PATH_ENTRY_TEMPLATE =
  419. R"(
  420. <Reference Include="{0}">
  421. <HintPath>{1}</HintPath>
  422. </Reference>)";
  423. const String VisualStudio::REFERENCE_PROJECT_ENTRY_TEMPLATE =
  424. R"(
  425. <ProjectReference Include="{0}.csproj">
  426. <Project>\{{1}\}</Project>
  427. <Name>{0}</Name>
  428. </ProjectReference>)";
  429. const String VisualStudio::CODE_ENTRY_TEMPLATE =
  430. R"(
  431. <Compile Include="{0}"/>)";
  432. const String VisualStudio::NON_CODE_ENTRY_TEMPLATE =
  433. R"(
  434. <None Include="{0}"/>)";
  435. VSCodeEditor::VSCodeEditor(VisualStudioVersion version, const Path& execPath, const WString& CLSID)
  436. :mVersion(version), mExecPath(execPath), mCLSID(CLSID)
  437. {
  438. }
  439. void VSCodeEditor::openFile(const Path& solutionPath, const Path& filePath, UINT32 lineNumber) const
  440. {
  441. CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
  442. CLSID clsID;
  443. if (FAILED(CLSIDFromString(mCLSID.c_str(), &clsID)))
  444. {
  445. CoUninitialize();
  446. return;
  447. }
  448. CComPtr<EnvDTE::_DTE> dte = VisualStudio::findRunningInstance(clsID, solutionPath);
  449. if (dte == nullptr)
  450. dte = VisualStudio::openInstance(clsID, solutionPath);
  451. if (dte == nullptr)
  452. {
  453. CoUninitialize();
  454. return;
  455. }
  456. VSMessageFilter* newFilter = new VSMessageFilter();
  457. IMessageFilter* oldFilter;
  458. CoRegisterMessageFilter(newFilter, &oldFilter);
  459. EnvDTE::Window* window = nullptr;
  460. if (SUCCEEDED(dte->get_MainWindow(&window)))
  461. window->Activate();
  462. VisualStudio::openFile(dte, filePath, lineNumber);
  463. CoRegisterMessageFilter(oldFilter, nullptr);
  464. CoUninitialize();
  465. }
  466. void VSCodeEditor::syncSolution(const CodeSolutionData& data, const Path& outputPath) const
  467. {
  468. String solutionString = VisualStudio::writeSolution(mVersion, data);
  469. solutionString = StringUtil::replaceAll(solutionString, "\n", "\r\n");
  470. Path solutionPath = outputPath;
  471. solutionPath.append(data.name + L".sln");
  472. for (auto& project : data.projects)
  473. {
  474. String projectString = VisualStudio::writeProject(mVersion, project);
  475. projectString = StringUtil::replaceAll(projectString, "\n", "\r\n");
  476. Path projectPath = outputPath;
  477. projectPath.append(project.name + L".csproj");
  478. SPtr<DataStream> projectStream = FileSystem::createAndOpenFile(projectPath);
  479. projectStream->write(projectString.c_str(), projectString.size() * sizeof(String::value_type));
  480. projectStream->close();
  481. }
  482. SPtr<DataStream> solutionStream = FileSystem::createAndOpenFile(solutionPath);
  483. solutionStream->write(solutionString.c_str(), solutionString.size() * sizeof(String::value_type));
  484. solutionStream->close();
  485. }
  486. VSCodeEditorFactory::VSCodeEditorFactory()
  487. :mAvailableVersions(getAvailableVersions())
  488. {
  489. for (auto& version : mAvailableVersions)
  490. mAvailableEditors.push_back(version.first);
  491. }
  492. Map<CodeEditorType, VSCodeEditorFactory::VSVersionInfo> VSCodeEditorFactory::getAvailableVersions() const
  493. {
  494. #if BS_ARCH_TYPE == BS_ARCHITECTURE_x86_64
  495. BOOL is64bit = 1;
  496. #else
  497. BOOL is64bit = 0;
  498. HANDLE process = GetCurrentProcess();
  499. IsWow64Process(process, (PBOOL)&is64bit);
  500. #endif
  501. WString registryKeyRoot;
  502. if (is64bit)
  503. registryKeyRoot = L"SOFTWARE\\Wow6432Node\\Microsoft";
  504. else
  505. registryKeyRoot = L"SOFTWARE\\Microsoft";
  506. struct VersionData
  507. {
  508. CodeEditorType type;
  509. WString registryKey;
  510. WString name;
  511. WString executable;
  512. };
  513. Map<VisualStudioVersion, VersionData> versionToVersionNumber =
  514. {
  515. { VisualStudioVersion::VS2008, { CodeEditorType::VS2008, L"VisualStudio\\9.0", L"Visual Studio 2008", L"devenv.exe" } },
  516. { VisualStudioVersion::VS2010, { CodeEditorType::VS2010, L"VisualStudio\\10.0", L"Visual Studio 2010", L"devenv.exe" } },
  517. { VisualStudioVersion::VS2012, { CodeEditorType::VS2012, L"VisualStudio\\11.0", L"Visual Studio 2012", L"devenv.exe" } },
  518. { VisualStudioVersion::VS2013, { CodeEditorType::VS2013, L"VisualStudio\\12.0", L"Visual Studio 2013", L"devenv.exe" } },
  519. { VisualStudioVersion::VS2015, { CodeEditorType::VS2015, L"VisualStudio\\14.0", L"Visual Studio 2015", L"devenv.exe" } }
  520. };
  521. Map<CodeEditorType, VSVersionInfo> versionInfo;
  522. for(auto version : versionToVersionNumber)
  523. {
  524. WString registryKey = registryKeyRoot + L"\\" + version.second.registryKey;
  525. HKEY regKey;
  526. LONG result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, registryKey.c_str(), 0, KEY_READ, &regKey);
  527. if (result != ERROR_SUCCESS)
  528. continue;
  529. WString installPath;
  530. getRegistryStringValue(regKey, L"InstallDir", installPath, StringUtil::WBLANK);
  531. if (installPath.empty())
  532. continue;
  533. WString clsID;
  534. getRegistryStringValue(regKey, L"ThisVersionDTECLSID", clsID, StringUtil::WBLANK);
  535. VSVersionInfo info;
  536. info.name = version.second.name;
  537. info.execPath = installPath.append(version.second.executable);
  538. info.CLSID = clsID;
  539. info.version = version.first;
  540. versionInfo[version.second.type] = info;
  541. }
  542. return versionInfo;
  543. }
  544. CodeEditor* VSCodeEditorFactory::create(CodeEditorType type) const
  545. {
  546. auto findIter = mAvailableVersions.find(type);
  547. if (findIter == mAvailableVersions.end())
  548. return nullptr;
  549. // TODO - Also create VSExpress and VSCommunity editors
  550. return bs_new<VSCodeEditor>(findIter->second.version, findIter->second.execPath, findIter->second.CLSID);
  551. }
  552. }