BsVSCodeEditor.cpp 21 KB

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