BsVSCodeEditor.cpp 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518
  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 String SLN_TEMPLATE;
  30. static const String PROJ_ENTRY_TEMPLATE;
  31. static const String PROJ_PLATFORM_TEMPLATE;
  32. static const String PROJ_TEMPLATE;
  33. static const String REFERENCE_ENTRY_TEMPLATE;
  34. static const String REFERENCE_PROJECT_ENTRY_TEMPLATE;
  35. static const String REFERENCE_PATH_ENTRY_TEMPLATE;
  36. static const String CODE_ENTRY_TEMPLATE;
  37. static const String 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 getProjectGUID(const WString& projectName)
  143. {
  144. static const String guidTemplate = "{0}-{1}-{2}-{3}-{4}";
  145. String hash = md5(projectName);
  146. String output = StringUtil::format(guidTemplate, hash.substr(0, 8),
  147. hash.substr(8, 4), hash.substr(12, 4), hash.substr(16, 4), hash.substr(20, 12));
  148. StringUtil::toUpperCase(output);
  149. return output;
  150. }
  151. static String writeSolution(VisualStudioVersion version, const CodeSolutionData& data)
  152. {
  153. struct VersionData
  154. {
  155. String formatVersion;
  156. };
  157. Map<VisualStudioVersion, VersionData> versionData =
  158. {
  159. { VisualStudioVersion::VS2008, { "10.00" } },
  160. { VisualStudioVersion::VS2010, { "11.00" } },
  161. { VisualStudioVersion::VS2012, { "12.00" } },
  162. { VisualStudioVersion::VS2013, { "12.00" } },
  163. { VisualStudioVersion::VS2015, { "12.00" } }
  164. };
  165. StringStream projectEntriesStream;
  166. StringStream projectPlatformsStream;
  167. for (auto& project : data.projects)
  168. {
  169. String guid = getProjectGUID(project.name);
  170. String projectName = toString(project.name);
  171. projectEntriesStream << StringUtil::format(PROJ_ENTRY_TEMPLATE, projectName, projectName + ".csproj", guid);
  172. projectPlatformsStream << StringUtil::format(PROJ_PLATFORM_TEMPLATE, guid);
  173. }
  174. String projectEntries = projectEntriesStream.str();
  175. String projectPlatforms = projectPlatformsStream.str();
  176. return StringUtil::format(SLN_TEMPLATE, versionData[version].formatVersion, projectEntries, projectPlatforms);
  177. }
  178. static String writeProject(VisualStudioVersion version, const CodeProjectData& projectData)
  179. {
  180. struct VersionData
  181. {
  182. String toolsVersion;
  183. };
  184. Map<VisualStudioVersion, VersionData> versionData =
  185. {
  186. { VisualStudioVersion::VS2008, { "3.5" } },
  187. { VisualStudioVersion::VS2010, { "4.0" } },
  188. { VisualStudioVersion::VS2012, { "4.0" } },
  189. { VisualStudioVersion::VS2013, { "12.0" } },
  190. { VisualStudioVersion::VS2015, { "13.0" } }
  191. };
  192. StringStream tempStream;
  193. for (auto& codeEntry : projectData.codeFiles)
  194. tempStream << StringUtil::format(CODE_ENTRY_TEMPLATE, codeEntry.toString());
  195. String codeEntries = tempStream.str();
  196. tempStream.str("");
  197. tempStream.clear();
  198. for (auto& nonCodeEntry : projectData.nonCodeFiles)
  199. tempStream << StringUtil::format(NON_CODE_ENTRY_TEMPLATE, nonCodeEntry.toString());
  200. String nonCodeEntries = tempStream.str();
  201. tempStream.str("");
  202. tempStream.clear();
  203. for (auto& referenceEntry : projectData.assemblyReferences)
  204. {
  205. String referenceName = toString(referenceEntry.name);
  206. if (referenceEntry.path.isEmpty())
  207. tempStream << StringUtil::format(REFERENCE_ENTRY_TEMPLATE, referenceName);
  208. else
  209. tempStream << StringUtil::format(REFERENCE_PATH_ENTRY_TEMPLATE, referenceName, referenceEntry.path.toString());
  210. }
  211. String referenceEntries = tempStream.str();
  212. tempStream.str("");
  213. tempStream.clear();
  214. for (auto& referenceEntry : projectData.projectReferences)
  215. {
  216. String referenceName = toString(referenceEntry.name);
  217. String projectGUID = getProjectGUID(referenceEntry.name);
  218. tempStream << StringUtil::format(REFERENCE_PROJECT_ENTRY_TEMPLATE, referenceName, projectGUID);
  219. }
  220. String projectReferenceEntries = tempStream.str();
  221. tempStream.str("");
  222. tempStream.clear();
  223. tempStream << toString(projectData.defines);
  224. String defines = tempStream.str();
  225. String projectGUID = getProjectGUID(projectData.name);
  226. return StringUtil::format(PROJ_TEMPLATE, versionData[version].toolsVersion, projectGUID,
  227. toString(projectData.name), defines, referenceEntries, projectReferenceEntries, codeEntries, nonCodeEntries);
  228. }
  229. };
  230. const String VisualStudio::SLN_TEMPLATE =
  231. R"(Microsoft Visual Studio Solution File, Format Version {0}
  232. # Visual Studio 2013
  233. VisualStudioVersion = 12.0.30723.0
  234. MinimumVisualStudioVersion = 10.0.40219.1{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{2}
  241. EndGlobalSection
  242. GlobalSection(SolutionProperties) = preSolution
  243. HideSolutionNode = FALSE
  244. EndGlobalSection
  245. EndGlobal
  246. )";
  247. const String VisualStudio::PROJ_ENTRY_TEMPLATE =
  248. R"(
  249. Project("\{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC\}") = "{0}", "{1}", "\{{2}\}"
  250. EndProject)";
  251. const String VisualStudio::PROJ_PLATFORM_TEMPLATE =
  252. R"(
  253. \{{0}\}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
  254. \{{0}\}.Debug|Any CPU.Build.0 = Debug|Any CPU
  255. \{{0}\}.Release|Any CPU.ActiveCfg = Release|Any CPU
  256. \{{0}\}.Release|Any CPU.Build.0 = Release|Any CPU)";
  257. const String VisualStudio::PROJ_TEMPLATE =
  258. R"literal(<?xml version="1.0" encoding="utf-8"?>
  259. <Project ToolsVersion="{0}" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
  260. <Import Project="$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\\$(MSBuildToolsVersion)\\Microsoft.Common.props')" />
  261. <PropertyGroup>
  262. <Configuration Condition = " '$(Configuration)' == '' ">Debug</Configuration>
  263. <Platform Condition = " '$(Platform)' == '' ">AnyCPU</Platform>
  264. <ProjectGuid>\{{1}\}</ProjectGuid>
  265. <OutputType>Library</OutputType>
  266. <AppDesignerFolder>Properties</AppDesignerFolder>
  267. <RootNamespace></RootNamespace>
  268. <AssemblyName>{2}</AssemblyName>
  269. <TargetFrameworkVersion>v4.0</TargetFrameworkVersion>
  270. <FileAlignment>512</FileAlignment>
  271. <BaseDirectory>Resources</BaseDirectory>
  272. <SchemaVersion>2.0</SchemaVersion>
  273. </PropertyGroup>
  274. <PropertyGroup Condition = " '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
  275. <DebugSymbols>true</DebugSymbols>
  276. <DebugType>full</DebugType>
  277. <Optimize>false</Optimize>
  278. <OutputPath>Internal\\Temp\\Assemblies\\Debug\\</OutputPath>
  279. <BaseIntermediateOutputPath>Internal\\Temp\\Assemblies\\</BaseIntermediateOutputPath>
  280. <DefineConstants>DEBUG;TRACE;{3}</DefineConstants>
  281. <ErrorReport>prompt</ErrorReport>
  282. <WarningLevel>4</WarningLevel >
  283. </PropertyGroup>
  284. <PropertyGroup Condition = " '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
  285. <DebugType>pdbonly</DebugType>
  286. <Optimize>true</Optimize>
  287. <OutputPath>Internal\\Temp\\Assemblies\\Release\\</OutputPath>
  288. <BaseIntermediateOutputPath>Internal\\Temp\\Assemblies\\</BaseIntermediateOutputPath>
  289. <DefineConstants>TRACE;{3}</DefineConstants>
  290. <ErrorReport>prompt</ErrorReport>
  291. <WarningLevel>4</WarningLevel>
  292. </PropertyGroup>
  293. <ItemGroup>{4}
  294. </ItemGroup>
  295. <ItemGroup>{5}
  296. </ItemGroup>
  297. <ItemGroup>{6}
  298. </ItemGroup>
  299. <ItemGroup>{7}
  300. </ItemGroup>
  301. <Import Project = "$(MSBuildToolsPath)\\Microsoft.CSharp.targets"/>
  302. </Project>)literal";
  303. const String VisualStudio::REFERENCE_ENTRY_TEMPLATE =
  304. R"(
  305. <Reference Include="{0}"/>)";
  306. const String VisualStudio::REFERENCE_PATH_ENTRY_TEMPLATE =
  307. R"(
  308. <Reference Include="{0}">
  309. <HintPath>{1}</HintPath>
  310. </Reference>)";
  311. const String VisualStudio::REFERENCE_PROJECT_ENTRY_TEMPLATE =
  312. R"(
  313. <ProjectReference Include="{0}.csproj">
  314. <Project>\{{1}\}</Project>
  315. <Name>{0}</Name>
  316. </ProjectReference>)";
  317. const String VisualStudio::CODE_ENTRY_TEMPLATE =
  318. R"(
  319. <Compile Include="{0}"/>)";
  320. const String VisualStudio::NON_CODE_ENTRY_TEMPLATE =
  321. R"(
  322. <None Include="{0}"/>)";
  323. VSCodeEditor::VSCodeEditor(VisualStudioVersion version, const Path& execPath, const WString& CLSID)
  324. :mCLSID(CLSID), mExecPath(execPath), mVersion(version)
  325. {
  326. }
  327. void VSCodeEditor::openFile(const Path& solutionPath, const Path& filePath, UINT32 lineNumber) const
  328. {
  329. CLSID clsID;
  330. if (FAILED(CLSIDFromString(mCLSID.c_str(), &clsID)))
  331. return;
  332. CComPtr<EnvDTE::_DTE> dte = VisualStudio::findRunningInstance(clsID, solutionPath);
  333. if (dte == nullptr)
  334. dte = VisualStudio::openInstance(clsID, solutionPath);
  335. if (dte == nullptr)
  336. return;
  337. VisualStudio::openFile(dte, filePath, lineNumber);
  338. }
  339. void VSCodeEditor::syncSolution(const CodeSolutionData& data, const Path& outputPath) const
  340. {
  341. String solutionString = VisualStudio::writeSolution(mVersion, data);
  342. solutionString = StringUtil::replaceAll(solutionString, "\n", "\r\n");
  343. Path solutionPath = outputPath;
  344. solutionPath.append(data.name + L".sln");
  345. for (auto& project : data.projects)
  346. {
  347. String projectString = VisualStudio::writeProject(mVersion, project);
  348. projectString = StringUtil::replaceAll(projectString, "\n", "\r\n");
  349. Path projectPath = outputPath;
  350. projectPath.append(project.name + L".csproj");
  351. DataStreamPtr projectStream = FileSystem::createAndOpenFile(projectPath);
  352. projectStream->write(projectString.c_str(), projectString.size() * sizeof(String::value_type));
  353. projectStream->close();
  354. }
  355. DataStreamPtr solutionStream = FileSystem::createAndOpenFile(solutionPath);
  356. solutionStream->write(solutionString.c_str(), solutionString.size() * sizeof(String::value_type));
  357. solutionStream->close();
  358. }
  359. VSCodeEditorFactory::VSCodeEditorFactory()
  360. :mAvailableVersions(getAvailableVersions())
  361. {
  362. for (auto& version : mAvailableVersions)
  363. mAvailableEditors.push_back(version.first);
  364. }
  365. Map<CodeEditorType, VSCodeEditorFactory::VSVersionInfo> VSCodeEditorFactory::getAvailableVersions() const
  366. {
  367. #if BS_ARCH_TYPE == BS_ARCHITECTURE_x86_64
  368. bool is64bit = true;
  369. #else
  370. bool is64bit = false;
  371. IsWow64Process(GetCurrentProcess(), &is64bit);
  372. #endif
  373. WString registryKeyRoot;
  374. if (is64bit)
  375. registryKeyRoot = L"SOFTWARE\\Wow6432Node\\Microsoft";
  376. else
  377. registryKeyRoot = L"SOFTWARE\\Microsoft";
  378. struct VersionData
  379. {
  380. CodeEditorType type;
  381. WString registryKey;
  382. WString name;
  383. WString executable;
  384. };
  385. Map<VisualStudioVersion, VersionData> versionToVersionNumber =
  386. {
  387. { VisualStudioVersion::VS2008, { CodeEditorType::VS2008, L"VisualStudio\\9.0", L"Visual Studio 2008", L"devenv.exe" } },
  388. { VisualStudioVersion::VS2010, { CodeEditorType::VS2010, L"VisualStudio\\10.0", L"Visual Studio 2010", L"devenv.exe" } },
  389. { VisualStudioVersion::VS2012, { CodeEditorType::VS2012, L"VisualStudio\\11.0", L"Visual Studio 2012", L"devenv.exe" } },
  390. { VisualStudioVersion::VS2013, { CodeEditorType::VS2013, L"VisualStudio\\12.0", L"Visual Studio 2013", L"devenv.exe" } },
  391. { VisualStudioVersion::VS2015, { CodeEditorType::VS2015, L"VisualStudio\\13.0", L"Visual Studio 2015", L"devenv.exe" } }
  392. };
  393. Map<CodeEditorType, VSVersionInfo> versionInfo;
  394. for(auto version : versionToVersionNumber)
  395. {
  396. WString registryKey = registryKeyRoot + L"\\" + version.second.registryKey;
  397. HKEY regKey;
  398. LONG result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, registryKey.c_str(), 0, KEY_READ, &regKey);
  399. if (result != ERROR_SUCCESS)
  400. continue;
  401. WString installPath;
  402. getRegistryStringValue(regKey, L"InstallDir", installPath, StringUtil::WBLANK);
  403. if (installPath.empty())
  404. continue;
  405. WString clsID;
  406. getRegistryStringValue(regKey, L"ThisVersionDTECLSID", clsID, StringUtil::WBLANK);
  407. VSVersionInfo info;
  408. info.name = version.second.name;
  409. info.execPath = installPath.append(version.second.executable);
  410. info.CLSID = clsID;
  411. info.version = version.first;
  412. versionInfo[version.second.type] = info;
  413. }
  414. // TODO - Also query for VSExpress and VSCommunity (their registry keys are different)
  415. return versionInfo;
  416. }
  417. CodeEditor* VSCodeEditorFactory::create(CodeEditorType type) const
  418. {
  419. auto findIter = mAvailableVersions.find(type);
  420. if (findIter == mAvailableVersions.end())
  421. return nullptr;
  422. // TODO - Also create VSExpress and VSCommunity editors
  423. return bs_new<VSCodeEditor>(findIter->second.version, findIter->second.execPath, findIter->second.CLSID);
  424. }
  425. }