BsVSCodeEditor.cpp 24 KB

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