BsVSCodeEditor.cpp 22 KB

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