BsVSCodeEditor.cpp 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283
  1. #include "Win32/BsVSCodeEditor.h"
  2. #include <windows.h>
  3. #include <atlbase.h>
  4. // Import EnvDTE
  5. #import "libid:80cc9f66-e7d8-4ddd-85b6-d9e6cd0e93e2" version("8.0") lcid("0") raw_interfaces_only named_guids
  6. namespace BansheeEngine
  7. {
  8. enum class VisualStudioVersion
  9. {
  10. VS2008,
  11. VS2010,
  12. VS2012,
  13. VS2013,
  14. VS2015
  15. };
  16. LONG getRegistryStringValue(HKEY hKey, const WString& name, WString& value, const WString& defaultValue)
  17. {
  18. value = defaultValue;
  19. wchar_t strBuffer[512];
  20. DWORD strBufferSize = sizeof(strBuffer);
  21. ULONG result = RegQueryValueExW(hKey, name.c_str(), 0, nullptr, (LPBYTE)strBuffer, &strBufferSize);
  22. if (result == ERROR_SUCCESS)
  23. value = strBuffer;
  24. return result;
  25. }
  26. struct VSProjectInfo
  27. {
  28. WString GUID;
  29. WString name;
  30. Path path;
  31. }
  32. class VisualStudio
  33. {
  34. public:
  35. static CComPtr<EnvDTE::_DTE> findRunningInstance(const CLSID& clsID, const Path& solutionPath)
  36. {
  37. CComPtr<IRunningObjectTable> runningObjectTable = nullptr;
  38. if (FAILED(GetRunningObjectTable(0, &runningObjectTable)))
  39. return nullptr;
  40. CComPtr<IEnumMoniker> enumMoniker = nullptr;
  41. if (FAILED(runningObjectTable->EnumRunning(&enumMoniker)))
  42. return nullptr;
  43. CComPtr<IMoniker> dteMoniker = nullptr;
  44. if (FAILED(CreateClassMoniker(clsID, &dteMoniker)))
  45. return nullptr;
  46. CComBSTR bstrSolution(solutionPath.toWString(Path::PathType::Windows).c_str());
  47. CComPtr<IMoniker> moniker;
  48. ULONG count = 0;
  49. while (enumMoniker->Next(1, &moniker, &count) == S_OK)
  50. {
  51. if (moniker->IsEqual(dteMoniker))
  52. {
  53. CComPtr<IUnknown> curObject = nullptr;
  54. HRESULT result = runningObjectTable->GetObject(moniker, &curObject);
  55. moniker = nullptr;
  56. if (result != S_OK)
  57. continue;
  58. CComPtr<EnvDTE::_DTE> dte = curObject;
  59. if (dte == nullptr)
  60. return nullptr;
  61. CComPtr<EnvDTE::_Solution> solution;
  62. if (FAILED(dte->get_Solution(&solution)))
  63. continue;
  64. CComBSTR fullName;
  65. if (FAILED(solution->get_FullName(&fullName)))
  66. continue;
  67. if (fullName == bstrSolution)
  68. return dte;
  69. }
  70. }
  71. return nullptr;
  72. }
  73. static CComPtr<EnvDTE::_DTE> openInstance(const CLSID& clsid, const Path& solutionPath)
  74. {
  75. CComPtr<IUnknown> newInstance = nullptr;
  76. if (FAILED(::CoCreateInstance(clsid, nullptr, CLSCTX_LOCAL_SERVER, EnvDTE::IID__DTE, (LPVOID*)&newInstance)))
  77. return nullptr;
  78. CComPtr<EnvDTE::_DTE> dte = newInstance;
  79. if (dte == nullptr)
  80. return nullptr;
  81. dte->put_UserControl(TRUE);
  82. CComPtr<EnvDTE::_Solution> solution;
  83. if (FAILED(dte->get_Solution(&solution)))
  84. return nullptr;
  85. CComBSTR bstrSolution(solutionPath.toWString(Path::PathType::Windows).c_str());
  86. if (FAILED(solution->Open(bstrSolution)))
  87. return nullptr;
  88. // Wait until VS opens
  89. UINT32 elapsed = 0;
  90. while (elapsed < 10000)
  91. {
  92. EnvDTE::Window* window = nullptr;
  93. if (SUCCEEDED(dte->get_MainWindow(&window)))
  94. return dte;
  95. Sleep(100);
  96. elapsed += 100;
  97. }
  98. return nullptr;
  99. }
  100. static bool openFile(CComPtr<EnvDTE::_DTE> dte, const Path& filePath, UINT32 line)
  101. {
  102. // Open file
  103. CComPtr<EnvDTE::ItemOperations> itemOperations;
  104. if (FAILED(dte->get_ItemOperations(&itemOperations)))
  105. return false;
  106. CComBSTR bstrFilePath(filePath.toWString(Path::PathType::Windows).c_str());
  107. CComBSTR bstrKind(EnvDTE::vsViewKindPrimary);
  108. CComPtr<EnvDTE::Window> window = nullptr;
  109. if (FAILED(itemOperations->OpenFile(bstrFilePath, bstrKind, &window)))
  110. return false;
  111. // Scroll to line
  112. CComPtr<EnvDTE::Document> activeDocument;
  113. if (SUCCEEDED(dte->get_ActiveDocument(&activeDocument)))
  114. {
  115. CComPtr<IDispatch> selection;
  116. if (SUCCEEDED(activeDocument->get_Selection(&selection)))
  117. {
  118. CComPtr<EnvDTE::TextSelection> textSelection;
  119. if (SUCCEEDED(selection->QueryInterface(&textSelection)))
  120. {
  121. textSelection->GotoLine(line, TRUE);
  122. }
  123. }
  124. }
  125. // Bring the window in focus
  126. window = nullptr;
  127. if (SUCCEEDED(dte->get_MainWindow(&window)))
  128. {
  129. window->Activate();
  130. HWND hWnd;
  131. window->get_HWnd((LONG*)&hWnd);
  132. SetForegroundWindow(hWnd);
  133. }
  134. return true;
  135. }
  136. static WString writeSolution(VisualStudioVersion version, const WString& solutionGUID, const Vector<VSProjectInfo>& projects)
  137. {
  138. WStringStream stream;
  139. const WString header;
  140. }
  141. };
  142. VSCodeEditor::VSCodeEditor(const Path& execPath, const WString& CLSID)
  143. :mCLSID(CLSID), mExecPath(execPath)
  144. {
  145. }
  146. void VSCodeEditor::openFile(const Path& solutionPath, const Path& filePath, UINT32 lineNumber) const
  147. {
  148. CLSID clsID;
  149. if (FAILED(CLSIDFromString(mCLSID.toWString().c_str(), &clsID)))
  150. return;
  151. CComPtr<EnvDTE::_DTE> dte = VisualStudio::findRunningInstance(clsID, solutionPath);
  152. if (dte == nullptr)
  153. dte = VisualStudio::openInstance(clsID, solutionPath);
  154. if (dte == nullptr)
  155. return;
  156. VisualStudio::openFile(dte, filePath, lineNumber);
  157. }
  158. void VSCodeEditor::syncSolution(const CodeSolutionData& data, const Path& outputPath) const
  159. {
  160. // TODO
  161. }
  162. VSCodeEditorFactory::VSCodeEditorFactory()
  163. :mAvailableVersions(getAvailableVersions())
  164. {
  165. for (auto& version : mAvailableVersions)
  166. mAvailableEditors.push_back(version.first);
  167. }
  168. Map<WString, VSCodeEditorFactory::VSVersionInfo> VSCodeEditorFactory::getAvailableVersions() const
  169. {
  170. #if BS_ARCH_TYPE == BS_ARCHITECTURE_x86_64
  171. bool is64bit = true;
  172. #else
  173. bool is64bit = false;
  174. IsWow64Process(GetCurrentProcess(), &is64bit);
  175. #endif
  176. WString registryKeyRoot;
  177. if (is64bit)
  178. registryKeyRoot = L"SOFTWARE\\Microsoft";
  179. else
  180. registryKeyRoot = L"SOFTWARE\\Wow6432Node\\Microsoft";
  181. struct VersionData
  182. {
  183. WString registryKey;
  184. WString name;
  185. WString executable;
  186. };
  187. Map<VisualStudioVersion, VersionData> versionToVersionNumber =
  188. {
  189. { VisualStudioVersion::VS2008, { L"VisualStudio\\9.0", L"Visual Studio 2008", L"devenv.exe" } },
  190. { VisualStudioVersion::VS2010, { L"VisualStudio\\10.0", L"Visual Studio 2010", L"devenv.exe" } },
  191. { VisualStudioVersion::VS2012, { L"VisualStudio\\11.0", L"Visual Studio 2012", L"devenv.exe" } },
  192. { VisualStudioVersion::VS2013, { L"VisualStudio\\12.0", L"Visual Studio 2013", L"devenv.exe" } },
  193. { VisualStudioVersion::VS2015, { L"VisualStudio\\13.0", L"Visual Studio 2015", L"devenv.exe" } }
  194. };
  195. Map<WString, VSVersionInfo> versionInfo;
  196. for(auto version : versionToVersionNumber)
  197. {
  198. WString registryKey = registryKeyRoot + L"\\" + version.second.registryKey;
  199. HKEY regKey;
  200. LONG result = RegOpenKeyExW(HKEY_LOCAL_MACHINE, registryKeyRoot.c_str(), 0, KEY_READ, &regKey);
  201. if (result != ERROR_SUCCESS)
  202. continue;
  203. WString installPath;
  204. getRegistryStringValue(regKey, L"InstallDir", installPath, StringUtil::WBLANK);
  205. if (installPath.empty())
  206. continue;
  207. WString clsID;
  208. getRegistryStringValue(regKey, L"ThisVersionDTECLSID", clsID, StringUtil::WBLANK);
  209. VSVersionInfo info;
  210. info.name = version.second.name;
  211. info.execPath = installPath.append(version.second.executable);
  212. info.CLSID = clsID;
  213. versionInfo[info.name] = info;
  214. }
  215. // TODO - Also query for VSExpress and VSCommunity (their registry keys are different)
  216. return versionInfo;
  217. }
  218. CodeEditor* VSCodeEditorFactory::create(const WString& editor) const
  219. {
  220. auto findIter = mAvailableVersions.find(editor);
  221. if (findIter == mAvailableVersions.end())
  222. return nullptr;
  223. // TODO - Also create VSExpress and VSCommunity editors
  224. return bs_new<VSCodeEditor>(findIter->second.execPath, findIter->second.CLSID);
  225. }
  226. }