BsVSCodeEditor.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509
  1. #include "Win32/BsVSCodeEditor.h"
  2. #include <windows.h>
  3. #include <atlbase.h>
  4. #include "BsFileSystem.h"
  5. #include "BsDataStream.h"
  6. // Import EnvDTE
  7. #import "libid:80cc9f66-e7d8-4ddd-85b6-d9e6cd0e93e2" version("8.0") lcid("0") raw_interfaces_only named_guids
  8. namespace BansheeEngine
  9. {
  10. LONG getRegistryStringValue(HKEY hKey, const WString& name, WString& value, const WString& defaultValue)
  11. {
  12. value = defaultValue;
  13. wchar_t strBuffer[512];
  14. DWORD strBufferSize = sizeof(strBuffer);
  15. ULONG result = RegQueryValueExW(hKey, name.c_str(), 0, nullptr, (LPBYTE)strBuffer, &strBufferSize);
  16. if (result == ERROR_SUCCESS)
  17. value = strBuffer;
  18. return result;
  19. }
  20. struct VSProjectInfo
  21. {
  22. WString GUID;
  23. WString name;
  24. Path path;
  25. };
  26. class VisualStudio
  27. {
  28. private:
  29. static const WString SLN_TEMPLATE;
  30. static const WString PROJ_ENTRY_TEMPLATE;
  31. static const WString PROJ_PLATFORM_TEMPLATE;
  32. static const WString PROJ_TEMPLATE;
  33. static const WString REFERENCE_ENTRY_TEMPLATE;
  34. static const WString REFERENCE_PROJECT_ENTRY_TEMPLATE;
  35. static const WString REFERENCE_PATH_ENTRY_TEMPLATE;
  36. static const WString CODE_ENTRY_TEMPLATE;
  37. static const WString NON_CODE_ENTRY_TEMPLATE;
  38. public:
  39. static CComPtr<EnvDTE::_DTE> findRunningInstance(const CLSID& clsID, const Path& solutionPath)
  40. {
  41. CComPtr<IRunningObjectTable> runningObjectTable = nullptr;
  42. if (FAILED(GetRunningObjectTable(0, &runningObjectTable)))
  43. return nullptr;
  44. CComPtr<IEnumMoniker> enumMoniker = nullptr;
  45. if (FAILED(runningObjectTable->EnumRunning(&enumMoniker)))
  46. return nullptr;
  47. CComPtr<IMoniker> dteMoniker = nullptr;
  48. if (FAILED(CreateClassMoniker(clsID, &dteMoniker)))
  49. return nullptr;
  50. CComBSTR bstrSolution(solutionPath.toWString(Path::PathType::Windows).c_str());
  51. CComPtr<IMoniker> moniker;
  52. ULONG count = 0;
  53. while (enumMoniker->Next(1, &moniker, &count) == S_OK)
  54. {
  55. if (moniker->IsEqual(dteMoniker))
  56. {
  57. CComPtr<IUnknown> curObject = nullptr;
  58. HRESULT result = runningObjectTable->GetObject(moniker, &curObject);
  59. moniker = nullptr;
  60. if (result != S_OK)
  61. continue;
  62. CComPtr<EnvDTE::_DTE> dte;
  63. curObject->QueryInterface(__uuidof(EnvDTE::_DTE), (void**)&dte);
  64. if (dte == nullptr)
  65. return nullptr;
  66. CComPtr<EnvDTE::_Solution> solution;
  67. if (FAILED(dte->get_Solution(&solution)))
  68. continue;
  69. CComBSTR fullName;
  70. if (FAILED(solution->get_FullName(&fullName)))
  71. continue;
  72. if (fullName == bstrSolution)
  73. return dte;
  74. }
  75. }
  76. return nullptr;
  77. }
  78. static CComPtr<EnvDTE::_DTE> openInstance(const CLSID& clsid, const Path& solutionPath)
  79. {
  80. CComPtr<IUnknown> newInstance = nullptr;
  81. if (FAILED(::CoCreateInstance(clsid, nullptr, CLSCTX_LOCAL_SERVER, EnvDTE::IID__DTE, (LPVOID*)&newInstance)))
  82. return nullptr;
  83. CComPtr<EnvDTE::_DTE> dte;
  84. newInstance->QueryInterface(__uuidof(EnvDTE::_DTE), (void**)&dte);
  85. if (dte == nullptr)
  86. return nullptr;
  87. dte->put_UserControl(TRUE);
  88. CComPtr<EnvDTE::_Solution> solution;
  89. if (FAILED(dte->get_Solution(&solution)))
  90. return nullptr;
  91. CComBSTR bstrSolution(solutionPath.toWString(Path::PathType::Windows).c_str());
  92. if (FAILED(solution->Open(bstrSolution)))
  93. return nullptr;
  94. // Wait until VS opens
  95. UINT32 elapsed = 0;
  96. while (elapsed < 10000)
  97. {
  98. EnvDTE::Window* window = nullptr;
  99. if (SUCCEEDED(dte->get_MainWindow(&window)))
  100. return dte;
  101. Sleep(100);
  102. elapsed += 100;
  103. }
  104. return nullptr;
  105. }
  106. static bool openFile(CComPtr<EnvDTE::_DTE> dte, const Path& filePath, UINT32 line)
  107. {
  108. // Open file
  109. CComPtr<EnvDTE::ItemOperations> itemOperations;
  110. if (FAILED(dte->get_ItemOperations(&itemOperations)))
  111. return false;
  112. CComBSTR bstrFilePath(filePath.toWString(Path::PathType::Windows).c_str());
  113. CComBSTR bstrKind(EnvDTE::vsViewKindPrimary);
  114. CComPtr<EnvDTE::Window> window = nullptr;
  115. if (FAILED(itemOperations->OpenFile(bstrFilePath, bstrKind, &window)))
  116. return false;
  117. // Scroll to line
  118. CComPtr<EnvDTE::Document> activeDocument;
  119. if (SUCCEEDED(dte->get_ActiveDocument(&activeDocument)))
  120. {
  121. CComPtr<IDispatch> selection;
  122. if (SUCCEEDED(activeDocument->get_Selection(&selection)))
  123. {
  124. CComPtr<EnvDTE::TextSelection> textSelection;
  125. if (SUCCEEDED(selection->QueryInterface(&textSelection)))
  126. {
  127. textSelection->GotoLine(line, TRUE);
  128. }
  129. }
  130. }
  131. // Bring the window in focus
  132. window = nullptr;
  133. if (SUCCEEDED(dte->get_MainWindow(&window)))
  134. {
  135. window->Activate();
  136. HWND hWnd;
  137. window->get_HWnd((LONG*)&hWnd);
  138. SetForegroundWindow(hWnd);
  139. }
  140. return true;
  141. }
  142. static String getSolutionGUID(const WString& solutionName)
  143. {
  144. static const String guidTemplate = "{0}-{1}-{2}-{3}-{4}";
  145. String hash = md5(L"SLN_" + solutionName);
  146. return StringUtil::format(guidTemplate, hash.substr(0, 8), hash.substr(8, 4), hash.substr(12, 4), hash.substr(16, 4), hash.substr(20, 12));
  147. }
  148. static String getProjectGUID(const WString& projectName)
  149. {
  150. static const String guidTemplate = "{0}-{1}-{2}-{3}-{4}";
  151. String hash = md5(L"PRJ_" + projectName);
  152. return StringUtil::format(guidTemplate, hash.substr(0, 8), hash.substr(8, 4), hash.substr(12, 4), hash.substr(16, 4), hash.substr(20, 12));
  153. }
  154. static WString writeSolution(VisualStudioVersion version, const CodeSolutionData& data)
  155. {
  156. struct VersionData
  157. {
  158. WString formatVersion;
  159. };
  160. Map<VisualStudioVersion, VersionData> versionData =
  161. {
  162. { VisualStudioVersion::VS2008, { L"10.0" } },
  163. { VisualStudioVersion::VS2010, { L"11.0" } },
  164. { VisualStudioVersion::VS2012, { L"12.0" } },
  165. { VisualStudioVersion::VS2013, { L"12.0" } },
  166. { VisualStudioVersion::VS2015, { L"12.0" } }
  167. };
  168. WString solutionGUID = toWString(getSolutionGUID(data.name));
  169. WStringStream projectEntriesStream;
  170. WStringStream projectPlatformsStream;
  171. for (auto& project : data.projects)
  172. {
  173. WString guid = toWString(getProjectGUID(project.name));
  174. projectEntriesStream << StringUtil::format(PROJ_ENTRY_TEMPLATE, solutionGUID, project.name, project.name + L".csproj", guid) << std::endl;
  175. projectPlatformsStream << StringUtil::format(PROJ_PLATFORM_TEMPLATE, guid) << std::endl;
  176. }
  177. WString projectEntries = projectEntriesStream.str();
  178. WString projectPlatforms = projectPlatformsStream.str();
  179. return StringUtil::format(SLN_TEMPLATE, versionData[version].formatVersion, projectEntries, projectPlatforms);
  180. }
  181. static WString writeProject(VisualStudioVersion version, const CodeProjectData& projectData)
  182. {
  183. struct VersionData
  184. {
  185. WString toolsVersion;
  186. };
  187. Map<VisualStudioVersion, VersionData> versionData =
  188. {
  189. { VisualStudioVersion::VS2008, { L"3.5" } },
  190. { VisualStudioVersion::VS2010, { L"4.0" } },
  191. { VisualStudioVersion::VS2012, { L"4.0" } },
  192. { VisualStudioVersion::VS2013, { L"12.0" } },
  193. { VisualStudioVersion::VS2015, { L"13.0" } }
  194. };
  195. WStringStream tempStream;
  196. for (auto& codeEntry : projectData.codeFiles)
  197. tempStream << StringUtil::format(CODE_ENTRY_TEMPLATE, codeEntry.toWString()) << std::endl;
  198. WString codeEntries = tempStream.str();
  199. tempStream.str(L"");
  200. tempStream.clear();
  201. for (auto& nonCodeEntry : projectData.nonCodeFiles)
  202. tempStream << StringUtil::format(NON_CODE_ENTRY_TEMPLATE, nonCodeEntry.toWString()) << std::endl;
  203. WString nonCodeEntries = tempStream.str();
  204. tempStream.str(L"");
  205. tempStream.clear();
  206. for (auto& referenceEntry : projectData.assemblyReferences)
  207. {
  208. if (referenceEntry.path.isEmpty())
  209. tempStream << StringUtil::format(REFERENCE_ENTRY_TEMPLATE, referenceEntry.name) << std::endl;
  210. else
  211. tempStream << StringUtil::format(REFERENCE_PATH_ENTRY_TEMPLATE, referenceEntry.name, referenceEntry.path.toWString()) << std::endl;
  212. }
  213. WString referenceEntries = tempStream.str();
  214. tempStream.str(L"");
  215. tempStream.clear();
  216. for (auto& referenceEntry : projectData.projectReferences)
  217. {
  218. WString projectGUID = toWString(getProjectGUID(referenceEntry.name));
  219. tempStream << StringUtil::format(REFERENCE_PROJECT_ENTRY_TEMPLATE, referenceEntry.name, projectGUID) << std::endl;
  220. }
  221. WString projectReferenceEntries = tempStream.str();
  222. tempStream.str(L"");
  223. tempStream.clear();
  224. for (auto& define : projectData.defines)
  225. tempStream << define << L";";
  226. WString defines = tempStream.str();
  227. WString projectGUID = toWString(getProjectGUID(projectData.name));
  228. return StringUtil::format(PROJ_ENTRY_TEMPLATE, versionData[version].toolsVersion, projectGUID,
  229. projectData.name, defines, referenceEntries, projectReferenceEntries, codeEntries, nonCodeEntries);
  230. }
  231. };
  232. const WString VisualStudio::SLN_TEMPLATE =
  233. LR"(Microsoft Visual Studio Solution File, Format Version {0}
  234. {1}
  235. Global
  236. GlobalSection(SolutionConfigurationPlatforms) = preSolution
  237. Debug|Any CPU = Debug|Any CPU
  238. Release|Any CPU = Release|Any CPU
  239. EndGlobalSection
  240. GlobalSection(ProjectConfigurationPlatforms) = postSolution
  241. {2}
  242. EndGlobalSection
  243. GlobalSection(SolutionProperties) = preSolution
  244. HideSolutionNode = FALSE
  245. EndGlobalSection
  246. EndGlobal)";
  247. const WString VisualStudio::PROJ_ENTRY_TEMPLATE =
  248. LR"(Project("\{{0}\}") = "{1}", "{2}", "\{{3}\}"
  249. EndProject)";
  250. const WString VisualStudio::PROJ_PLATFORM_TEMPLATE =
  251. LR"(\{{0}\}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
  252. \{{0}\}.Debug|Any CPU.Build.0 = Debug|Any CPU
  253. \{{0}\}.Release|Any CPU.ActiveCfg = Release|Any CPU
  254. \{{0}\}.Release|Any CPU.Build.0 = Release|Any CPU)";
  255. const WString VisualStudio::PROJ_TEMPLATE =
  256. LR"literal(<?xml version="1.0" encoding="utf-8"?>
  257. <Project ToolsVersion="{0}" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  258. <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
  259. <PropertyGroup>
  260. <Configuration Condition = " '$(Configuration)' == '' ">Debug</Configuration>
  261. <Platform Condition = " '$(Platform)' == '' ">AnyCPU</Platform>
  262. <ProjectGuid>\{{1}\}</ProjectGuid>
  263. <OutputType>Library</OutputType>
  264. <AppDesignerFolder>Properties</AppDesignerFolder>
  265. <RootNamespace></RootNamespace>
  266. <AssemblyName>{2}</AssemblyName>
  267. <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
  268. <FileAlignment>512</FileAlignment>
  269. <BaseDirectory>Resources</BaseDirectory>
  270. <SchemaVersion>2.0</SchemaVersion>
  271. </PropertyGroup>
  272. <PropertyGroup Condition = " '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
  273. <DebugSymbols>true</DebugSymbols>
  274. <DebugType>full</DebugType>
  275. <Optimize>false</Optimize>
  276. <OutputPath>Internal\Temp\Assemblies\Debug\</OutputPath>
  277. <DefineConstants>DEBUG;TRACE;{3}</DefineConstants>
  278. <ErrorReport>prompt</ErrorReport>
  279. <WarningLevel>4</WarningLevel >
  280. </PropertyGroup>
  281. <PropertyGroup Condition = " '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
  282. <DebugType>pdbonly</DebugType>
  283. <Optimize>true</Optimize>
  284. <OutputPath>Internal\Temp\Assemblies\Release\</OutputPath>
  285. <DefineConstants>TRACE;{3}</DefineConstants>
  286. <ErrorReport>prompt</ErrorReport>
  287. <WarningLevel>4</WarningLevel>
  288. </PropertyGroup>
  289. <ItemGroup>
  290. {4}
  291. </ItemGroup>
  292. <ItemGroup>
  293. {5}
  294. </ItemGroup>
  295. <ItemGroup>
  296. {6}
  297. </ItemGroup>
  298. <ItemGroup>
  299. {7}
  300. </ItemGroup>
  301. <Import Project = "$(MSBuildToolsPath)\Microsoft.CSharp.targets"/>
  302. </Project>)literal";
  303. const WString VisualStudio::REFERENCE_ENTRY_TEMPLATE =
  304. LR"( <Reference Include="{0}"/>)";
  305. const WString VisualStudio::REFERENCE_PATH_ENTRY_TEMPLATE =
  306. LR"( <Reference Include="{0}">
  307. <HintPath>{1}</HintPath>
  308. </Reference>)";
  309. const WString VisualStudio::REFERENCE_PROJECT_ENTRY_TEMPLATE =
  310. LR"( <ProjectReference Include="{0}.csproj">
  311. <Project>\{{1}\}</Project>
  312. <Name>{0}</Name>
  313. </ProjectReference>)";
  314. const WString VisualStudio::CODE_ENTRY_TEMPLATE =
  315. LR"( <Compile Include="{0}"/>)";
  316. const WString VisualStudio::NON_CODE_ENTRY_TEMPLATE =
  317. LR"( <None Include="{0}"/>)";
  318. VSCodeEditor::VSCodeEditor(VisualStudioVersion version, const Path& execPath, const WString& CLSID)
  319. :mCLSID(CLSID), mExecPath(execPath), mVersion(version)
  320. {
  321. }
  322. void VSCodeEditor::openFile(const Path& solutionPath, const Path& filePath, UINT32 lineNumber) const
  323. {
  324. CLSID clsID;
  325. if (FAILED(CLSIDFromString(mCLSID.c_str(), &clsID)))
  326. return;
  327. CComPtr<EnvDTE::_DTE> dte = VisualStudio::findRunningInstance(clsID, solutionPath);
  328. if (dte == nullptr)
  329. dte = VisualStudio::openInstance(clsID, solutionPath);
  330. if (dte == nullptr)
  331. return;
  332. VisualStudio::openFile(dte, filePath, lineNumber);
  333. }
  334. void VSCodeEditor::syncSolution(const CodeSolutionData& data, const Path& outputPath) const
  335. {
  336. WString solutionString = VisualStudio::writeSolution(mVersion, data);
  337. Path solutionPath = outputPath;
  338. solutionPath.append(data.name + L".sln");
  339. for (auto& project : data.projects)
  340. {
  341. WString projectString = VisualStudio::writeProject(mVersion, project);
  342. Path projectPath = outputPath;
  343. projectPath.append(project.name + L".csproj");
  344. DataStreamPtr projectStream = FileSystem::createAndOpenFile(projectPath);
  345. projectStream->write(projectString.c_str(), projectString.size());
  346. projectStream->close();
  347. }
  348. DataStreamPtr solutionStream = FileSystem::createAndOpenFile(solutionPath);
  349. solutionStream->write(solutionString.c_str(), solutionString.size());
  350. solutionStream->close();
  351. }
  352. VSCodeEditorFactory::VSCodeEditorFactory()
  353. :mAvailableVersions(getAvailableVersions())
  354. {
  355. for (auto& version : mAvailableVersions)
  356. mAvailableEditors.push_back(version.first);
  357. }
  358. Map<WString, VSCodeEditorFactory::VSVersionInfo> VSCodeEditorFactory::getAvailableVersions() const
  359. {
  360. #if BS_ARCH_TYPE == BS_ARCHITECTURE_x86_64
  361. bool is64bit = true;
  362. #else
  363. bool is64bit = false;
  364. IsWow64Process(GetCurrentProcess(), &is64bit);
  365. #endif
  366. WString registryKeyRoot;
  367. if (is64bit)
  368. registryKeyRoot = L"SOFTWARE\\Microsoft";
  369. else
  370. registryKeyRoot = L"SOFTWARE\\Wow6432Node\\Microsoft";
  371. struct VersionData
  372. {
  373. WString registryKey;
  374. WString name;
  375. WString executable;
  376. };
  377. Map<VisualStudioVersion, VersionData> versionToVersionNumber =
  378. {
  379. { VisualStudioVersion::VS2008, { L"VisualStudio\\9.0", L"Visual Studio 2008", L"devenv.exe" } },
  380. { VisualStudioVersion::VS2010, { L"VisualStudio\\10.0", L"Visual Studio 2010", L"devenv.exe" } },
  381. { VisualStudioVersion::VS2012, { L"VisualStudio\\11.0", L"Visual Studio 2012", L"devenv.exe" } },
  382. { VisualStudioVersion::VS2013, { L"VisualStudio\\12.0", L"Visual Studio 2013", L"devenv.exe" } },
  383. { VisualStudioVersion::VS2015, { L"VisualStudio\\13.0", L"Visual Studio 2015", L"devenv.exe" } }
  384. };
  385. Map<WString, VSVersionInfo> versionInfo;
  386. for(auto version : versionToVersionNumber)
  387. {
  388. WString registryKey = registryKeyRoot + L"\\" + version.second.registryKey;
  389. HKEY regKey;
  390. LONG result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, registryKeyRoot.c_str(), 0, KEY_READ, &regKey);
  391. if (result != ERROR_SUCCESS)
  392. continue;
  393. WString installPath;
  394. getRegistryStringValue(regKey, L"InstallDir", installPath, StringUtil::WBLANK);
  395. if (installPath.empty())
  396. continue;
  397. WString clsID;
  398. getRegistryStringValue(regKey, L"ThisVersionDTECLSID", clsID, StringUtil::WBLANK);
  399. VSVersionInfo info;
  400. info.name = version.second.name;
  401. info.execPath = installPath.append(version.second.executable);
  402. info.CLSID = clsID;
  403. info.version = version.first;
  404. versionInfo[info.name] = info;
  405. }
  406. // TODO - Also query for VSExpress and VSCommunity (their registry keys are different)
  407. return versionInfo;
  408. }
  409. CodeEditor* VSCodeEditorFactory::create(const WString& editor) const
  410. {
  411. auto findIter = mAvailableVersions.find(editor);
  412. if (findIter == mAvailableVersions.end())
  413. return nullptr;
  414. // TODO - Also create VSExpress and VSCommunity editors
  415. return bs_new<VSCodeEditor>(findIter->second.version, findIter->second.execPath, findIter->second.CLSID);
  416. }
  417. }