BsVSCodeEditor.cpp 14 KB

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