Program.cs 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. using System;
  2. using System.IO;
  3. using System.Runtime.InteropServices;
  4. using System.Runtime.InteropServices.ComTypes;
  5. using System.Text.RegularExpressions;
  6. using EnvDTE;
  7. namespace GodotTools.OpenVisualStudio
  8. {
  9. internal static class Program
  10. {
  11. [DllImport("ole32.dll")]
  12. private static extern int GetRunningObjectTable(int reserved, out IRunningObjectTable pprot);
  13. [DllImport("ole32.dll")]
  14. private static extern void CreateBindCtx(int reserved, out IBindCtx ppbc);
  15. [DllImport("user32.dll")]
  16. private static extern bool SetForegroundWindow(IntPtr hWnd);
  17. private static void ShowHelp()
  18. {
  19. Console.WriteLine("Opens the file(s) in a Visual Studio instance that is editing the specified solution.");
  20. Console.WriteLine("If an existing instance for the solution is not found, a new one is created.");
  21. Console.WriteLine();
  22. Console.WriteLine("Usage:");
  23. Console.WriteLine(@" GodotTools.OpenVisualStudio.exe solution [file[;line[;col]]...]");
  24. Console.WriteLine();
  25. Console.WriteLine("Lines and columns begin at one. Zero or lower will result in an error.");
  26. Console.WriteLine("If a line is specified but a column is not, the line is selected in the text editor.");
  27. }
  28. // STAThread needed, otherwise CoRegisterMessageFilter may return CO_E_NOT_SUPPORTED.
  29. [STAThread]
  30. private static int Main(string[] args)
  31. {
  32. if (args.Length == 0 || args[0] == "--help" || args[0] == "-h")
  33. {
  34. ShowHelp();
  35. return 0;
  36. }
  37. string solutionFile = NormalizePath(args[0]);
  38. var dte = FindInstanceEditingSolution(solutionFile);
  39. if (dte == null)
  40. {
  41. // Open a new instance
  42. var visualStudioDteType = Type.GetTypeFromProgID("VisualStudio.DTE.16.0", throwOnError: true);
  43. dte = (DTE)Activator.CreateInstance(visualStudioDteType);
  44. dte.UserControl = true;
  45. try
  46. {
  47. dte.Solution.Open(solutionFile);
  48. }
  49. catch (ArgumentException)
  50. {
  51. Console.Error.WriteLine("Solution.Open: Invalid path or file not found");
  52. return 1;
  53. }
  54. dte.MainWindow.Visible = true;
  55. }
  56. MessageFilter.Register();
  57. try
  58. {
  59. // Open files
  60. for (int i = 1; i < args.Length; i++)
  61. {
  62. // Both the line number and the column begin at one
  63. string[] fileArgumentParts = args[i].Split(';');
  64. string filePath = NormalizePath(fileArgumentParts[0]);
  65. try
  66. {
  67. dte.ItemOperations.OpenFile(filePath);
  68. }
  69. catch (ArgumentException)
  70. {
  71. Console.Error.WriteLine("ItemOperations.OpenFile: Invalid path or file not found");
  72. return 1;
  73. }
  74. if (fileArgumentParts.Length > 1)
  75. {
  76. if (int.TryParse(fileArgumentParts[1], out int line))
  77. {
  78. var textSelection = (TextSelection)dte.ActiveDocument.Selection;
  79. if (fileArgumentParts.Length > 2)
  80. {
  81. if (int.TryParse(fileArgumentParts[2], out int column))
  82. {
  83. textSelection.MoveToLineAndOffset(line, column);
  84. }
  85. else
  86. {
  87. Console.Error.WriteLine("The column part of the argument must be a valid integer");
  88. return 1;
  89. }
  90. }
  91. else
  92. {
  93. textSelection.GotoLine(line, Select: true);
  94. }
  95. }
  96. else
  97. {
  98. Console.Error.WriteLine("The line part of the argument must be a valid integer");
  99. return 1;
  100. }
  101. }
  102. }
  103. }
  104. finally
  105. {
  106. var mainWindow = dte.MainWindow;
  107. mainWindow.Activate();
  108. SetForegroundWindow(new IntPtr(mainWindow.HWnd));
  109. MessageFilter.Revoke();
  110. }
  111. return 0;
  112. }
  113. private static DTE FindInstanceEditingSolution(string solutionPath)
  114. {
  115. if (GetRunningObjectTable(0, out IRunningObjectTable pprot) != 0)
  116. return null;
  117. try
  118. {
  119. pprot.EnumRunning(out IEnumMoniker ppenumMoniker);
  120. ppenumMoniker.Reset();
  121. var moniker = new IMoniker[1];
  122. while (ppenumMoniker.Next(1, moniker, IntPtr.Zero) == 0)
  123. {
  124. string ppszDisplayName;
  125. CreateBindCtx(0, out IBindCtx ppbc);
  126. try
  127. {
  128. moniker[0].GetDisplayName(ppbc, null, out ppszDisplayName);
  129. }
  130. finally
  131. {
  132. Marshal.ReleaseComObject(ppbc);
  133. }
  134. if (ppszDisplayName == null)
  135. continue;
  136. // The digits after the colon are the process ID
  137. if (!Regex.IsMatch(ppszDisplayName, "!VisualStudio.DTE.16.0:[0-9]"))
  138. continue;
  139. if (pprot.GetObject(moniker[0], out object ppunkObject) == 0)
  140. {
  141. if (ppunkObject is DTE dte && dte.Solution.FullName.Length > 0)
  142. {
  143. if (NormalizePath(dte.Solution.FullName) == solutionPath)
  144. return dte;
  145. }
  146. }
  147. }
  148. }
  149. finally
  150. {
  151. Marshal.ReleaseComObject(pprot);
  152. }
  153. return null;
  154. }
  155. static string NormalizePath(string path)
  156. {
  157. return new Uri(Path.GetFullPath(path)).LocalPath
  158. .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar)
  159. .ToUpperInvariant();
  160. }
  161. #region MessageFilter. See: http: //msdn.microsoft.com/en-us/library/ms228772.aspx
  162. private class MessageFilter : IOleMessageFilter
  163. {
  164. // Class containing the IOleMessageFilter
  165. // thread error-handling functions
  166. private static IOleMessageFilter _oldFilter;
  167. // Start the filter
  168. public static void Register()
  169. {
  170. IOleMessageFilter newFilter = new MessageFilter();
  171. int ret = CoRegisterMessageFilter(newFilter, out _oldFilter);
  172. if (ret != 0)
  173. Console.Error.WriteLine($"CoRegisterMessageFilter failed with error code: {ret}");
  174. }
  175. // Done with the filter, close it
  176. public static void Revoke()
  177. {
  178. int ret = CoRegisterMessageFilter(_oldFilter, out _);
  179. if (ret != 0)
  180. Console.Error.WriteLine($"CoRegisterMessageFilter failed with error code: {ret}");
  181. }
  182. //
  183. // IOleMessageFilter functions
  184. // Handle incoming thread requests
  185. int IOleMessageFilter.HandleInComingCall(int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo)
  186. {
  187. // Return the flag SERVERCALL_ISHANDLED
  188. return 0;
  189. }
  190. // Thread call was rejected, so try again.
  191. int IOleMessageFilter.RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType)
  192. {
  193. if (dwRejectType == 2)
  194. // flag = SERVERCALL_RETRYLATER
  195. {
  196. // Retry the thread call immediately if return >= 0 & < 100
  197. return 99;
  198. }
  199. // Too busy; cancel call
  200. return -1;
  201. }
  202. int IOleMessageFilter.MessagePending(IntPtr hTaskCallee, int dwTickCount, int dwPendingType)
  203. {
  204. // Return the flag PENDINGMSG_WAITDEFPROCESS
  205. return 2;
  206. }
  207. // Implement the IOleMessageFilter interface
  208. [DllImport("ole32.dll")]
  209. private static extern int CoRegisterMessageFilter(IOleMessageFilter newFilter, out IOleMessageFilter oldFilter);
  210. }
  211. [ComImport(), Guid("00000016-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
  212. private interface IOleMessageFilter
  213. {
  214. [PreserveSig]
  215. int HandleInComingCall(int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo);
  216. [PreserveSig]
  217. int RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType);
  218. [PreserveSig]
  219. int MessagePending(IntPtr hTaskCallee, int dwTickCount, int dwPendingType);
  220. }
  221. #endregion
  222. }
  223. }