| 
									
										
										
										
											2020-07-13 14:13:38 -04:00
										 |  |  | using System; | 
					
						
							| 
									
										
										
										
											2020-06-15 21:29:16 +02:00
										 |  |  | using System.IO; | 
					
						
							|  |  |  | using System.Runtime.InteropServices; | 
					
						
							|  |  |  | using System.Runtime.InteropServices.ComTypes; | 
					
						
							|  |  |  | using System.Text.RegularExpressions; | 
					
						
							|  |  |  | using EnvDTE; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | namespace GodotTools.OpenVisualStudio | 
					
						
							|  |  |  | { | 
					
						
							|  |  |  |     internal static class Program | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |         [DllImport("ole32.dll")] | 
					
						
							|  |  |  |         private static extern int GetRunningObjectTable(int reserved, out IRunningObjectTable pprot); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         [DllImport("ole32.dll")] | 
					
						
							|  |  |  |         private static extern void CreateBindCtx(int reserved, out IBindCtx ppbc); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         [DllImport("user32.dll")] | 
					
						
							|  |  |  |         private static extern bool SetForegroundWindow(IntPtr hWnd); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         private static void ShowHelp() | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             Console.WriteLine("Opens the file(s) in a Visual Studio instance that is editing the specified solution."); | 
					
						
							|  |  |  |             Console.WriteLine("If an existing instance for the solution is not found, a new one is created."); | 
					
						
							|  |  |  |             Console.WriteLine(); | 
					
						
							|  |  |  |             Console.WriteLine("Usage:"); | 
					
						
							|  |  |  |             Console.WriteLine(@"  GodotTools.OpenVisualStudio.exe solution [file[;line[;col]]...]"); | 
					
						
							|  |  |  |             Console.WriteLine(); | 
					
						
							|  |  |  |             Console.WriteLine("Lines and columns begin at one. Zero or lower will result in an error."); | 
					
						
							|  |  |  |             Console.WriteLine("If a line is specified but a column is not, the line is selected in the text editor."); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         // STAThread needed, otherwise CoRegisterMessageFilter may return CO_E_NOT_SUPPORTED. | 
					
						
							|  |  |  |         [STAThread] | 
					
						
							|  |  |  |         private static int Main(string[] args) | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             if (args.Length == 0 || args[0] == "--help" || args[0] == "-h") | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 ShowHelp(); | 
					
						
							|  |  |  |                 return 0; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             string solutionFile = NormalizePath(args[0]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             var dte = FindInstanceEditingSolution(solutionFile); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             if (dte == null) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 // Open a new instance | 
					
						
							| 
									
										
										
										
											2022-02-04 00:06:53 +01:00
										 |  |  |                 dte = TryVisualStudioLaunch("VisualStudio.DTE.17.0"); | 
					
						
							| 
									
										
										
										
											2020-06-15 21:29:16 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-02-04 00:06:53 +01:00
										 |  |  |                 if (dte == null) | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     // Launch of VS 2022 failed, fallback to 2019 | 
					
						
							|  |  |  |                     dte = TryVisualStudioLaunch("VisualStudio.DTE.16.0"); | 
					
						
							| 
									
										
										
										
											2024-01-12 22:24:12 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |                     if (dte == null) | 
					
						
							|  |  |  |                     { | 
					
						
							|  |  |  |                         Console.Error.WriteLine("Visual Studio not found"); | 
					
						
							|  |  |  |                         return 1; | 
					
						
							|  |  |  |                     } | 
					
						
							| 
									
										
										
										
											2022-02-04 00:06:53 +01:00
										 |  |  |                 } | 
					
						
							| 
									
										
										
										
											2020-06-15 21:29:16 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 dte.UserControl = true; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 try | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     dte.Solution.Open(solutionFile); | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |                 catch (ArgumentException) | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     Console.Error.WriteLine("Solution.Open: Invalid path or file not found"); | 
					
						
							|  |  |  |                     return 1; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 dte.MainWindow.Visible = true; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             MessageFilter.Register(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             try | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 // Open files | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 for (int i = 1; i < args.Length; i++) | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     // Both the line number and the column begin at one | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     string[] fileArgumentParts = args[i].Split(';'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     string filePath = NormalizePath(fileArgumentParts[0]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     try | 
					
						
							|  |  |  |                     { | 
					
						
							|  |  |  |                         dte.ItemOperations.OpenFile(filePath); | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     catch (ArgumentException) | 
					
						
							|  |  |  |                     { | 
					
						
							|  |  |  |                         Console.Error.WriteLine("ItemOperations.OpenFile: Invalid path or file not found"); | 
					
						
							|  |  |  |                         return 1; | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     if (fileArgumentParts.Length > 1) | 
					
						
							|  |  |  |                     { | 
					
						
							|  |  |  |                         if (int.TryParse(fileArgumentParts[1], out int line)) | 
					
						
							|  |  |  |                         { | 
					
						
							|  |  |  |                             var textSelection = (TextSelection)dte.ActiveDocument.Selection; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                             if (fileArgumentParts.Length > 2) | 
					
						
							|  |  |  |                             { | 
					
						
							|  |  |  |                                 if (int.TryParse(fileArgumentParts[2], out int column)) | 
					
						
							|  |  |  |                                 { | 
					
						
							|  |  |  |                                     textSelection.MoveToLineAndOffset(line, column); | 
					
						
							|  |  |  |                                 } | 
					
						
							|  |  |  |                                 else | 
					
						
							|  |  |  |                                 { | 
					
						
							|  |  |  |                                     Console.Error.WriteLine("The column part of the argument must be a valid integer"); | 
					
						
							|  |  |  |                                     return 1; | 
					
						
							|  |  |  |                                 } | 
					
						
							|  |  |  |                             } | 
					
						
							|  |  |  |                             else | 
					
						
							|  |  |  |                             { | 
					
						
							|  |  |  |                                 textSelection.GotoLine(line, Select: true); | 
					
						
							|  |  |  |                             } | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                         else | 
					
						
							|  |  |  |                         { | 
					
						
							|  |  |  |                             Console.Error.WriteLine("The line part of the argument must be a valid integer"); | 
					
						
							|  |  |  |                             return 1; | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             finally | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 var mainWindow = dte.MainWindow; | 
					
						
							|  |  |  |                 mainWindow.Activate(); | 
					
						
							| 
									
										
										
										
											2023-12-22 13:24:57 -06:00
										 |  |  |                 SetForegroundWindow(mainWindow.HWnd); | 
					
						
							| 
									
										
										
										
											2020-06-15 21:29:16 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 MessageFilter.Revoke(); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return 0; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-12 22:24:12 -06:00
										 |  |  |         private static DTE? TryVisualStudioLaunch(string version) | 
					
						
							| 
									
										
										
										
											2022-02-04 00:06:53 +01:00
										 |  |  |         { | 
					
						
							|  |  |  |             try | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 var visualStudioDteType = Type.GetTypeFromProgID(version, throwOnError: true); | 
					
						
							| 
									
										
										
										
											2024-01-12 22:24:12 -06:00
										 |  |  |                 var dte = (DTE?)Activator.CreateInstance(visualStudioDteType!); | 
					
						
							| 
									
										
										
										
											2022-02-04 00:06:53 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  |                 return dte; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             catch (COMException) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 return null; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-12 22:24:12 -06:00
										 |  |  |         private static DTE? FindInstanceEditingSolution(string solutionPath) | 
					
						
							| 
									
										
										
										
											2020-06-15 21:29:16 +02:00
										 |  |  |         { | 
					
						
							|  |  |  |             if (GetRunningObjectTable(0, out IRunningObjectTable pprot) != 0) | 
					
						
							|  |  |  |                 return null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             try | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 pprot.EnumRunning(out IEnumMoniker ppenumMoniker); | 
					
						
							|  |  |  |                 ppenumMoniker.Reset(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 var moniker = new IMoniker[1]; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 while (ppenumMoniker.Next(1, moniker, IntPtr.Zero) == 0) | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     string ppszDisplayName; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     CreateBindCtx(0, out IBindCtx ppbc); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     try | 
					
						
							|  |  |  |                     { | 
					
						
							|  |  |  |                         moniker[0].GetDisplayName(ppbc, null, out ppszDisplayName); | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                     finally | 
					
						
							|  |  |  |                     { | 
					
						
							|  |  |  |                         Marshal.ReleaseComObject(ppbc); | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     if (ppszDisplayName == null) | 
					
						
							|  |  |  |                         continue; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     // The digits after the colon are the process ID | 
					
						
							| 
									
										
										
										
											2022-02-04 10:38:28 +01:00
										 |  |  |                     if (!Regex.IsMatch(ppszDisplayName, "!VisualStudio.DTE.1[6-7].0:[0-9]")) | 
					
						
							| 
									
										
										
										
											2020-06-15 21:29:16 +02:00
										 |  |  |                         continue; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                     if (pprot.GetObject(moniker[0], out object ppunkObject) == 0) | 
					
						
							|  |  |  |                     { | 
					
						
							|  |  |  |                         if (ppunkObject is DTE dte && dte.Solution.FullName.Length > 0) | 
					
						
							|  |  |  |                         { | 
					
						
							|  |  |  |                             if (NormalizePath(dte.Solution.FullName) == solutionPath) | 
					
						
							|  |  |  |                                 return dte; | 
					
						
							|  |  |  |                         } | 
					
						
							|  |  |  |                     } | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  |             finally | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 Marshal.ReleaseComObject(pprot); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             return null; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-19 22:15:37 +01:00
										 |  |  |         private static string NormalizePath(string path) | 
					
						
							| 
									
										
										
										
											2020-06-15 21:29:16 +02:00
										 |  |  |         { | 
					
						
							|  |  |  |             return new Uri(Path.GetFullPath(path)).LocalPath | 
					
						
							|  |  |  |                 .TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar) | 
					
						
							|  |  |  |                 .ToUpperInvariant(); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         #region MessageFilter. See: http: //msdn.microsoft.com/en-us/library/ms228772.aspx | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         private class MessageFilter : IOleMessageFilter | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             // Class containing the IOleMessageFilter | 
					
						
							|  |  |  |             // thread error-handling functions | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-01-12 22:24:12 -06:00
										 |  |  |             private static IOleMessageFilter? _oldFilter; | 
					
						
							| 
									
										
										
										
											2020-06-15 21:29:16 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |             // Start the filter | 
					
						
							|  |  |  |             public static void Register() | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 IOleMessageFilter newFilter = new MessageFilter(); | 
					
						
							|  |  |  |                 int ret = CoRegisterMessageFilter(newFilter, out _oldFilter); | 
					
						
							|  |  |  |                 if (ret != 0) | 
					
						
							|  |  |  |                     Console.Error.WriteLine($"CoRegisterMessageFilter failed with error code: {ret}"); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Done with the filter, close it | 
					
						
							|  |  |  |             public static void Revoke() | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 int ret = CoRegisterMessageFilter(_oldFilter, out _); | 
					
						
							|  |  |  |                 if (ret != 0) | 
					
						
							|  |  |  |                     Console.Error.WriteLine($"CoRegisterMessageFilter failed with error code: {ret}"); | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // | 
					
						
							|  |  |  |             // IOleMessageFilter functions | 
					
						
							|  |  |  |             // Handle incoming thread requests | 
					
						
							|  |  |  |             int IOleMessageFilter.HandleInComingCall(int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 // Return the flag SERVERCALL_ISHANDLED | 
					
						
							|  |  |  |                 return 0; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Thread call was rejected, so try again. | 
					
						
							|  |  |  |             int IOleMessageFilter.RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType) | 
					
						
							|  |  |  |             { | 
					
						
							| 
									
										
										
										
											2022-08-27 03:22:23 +02:00
										 |  |  |                 // flag = SERVERCALL_RETRYLATER | 
					
						
							| 
									
										
										
										
											2020-06-15 21:29:16 +02:00
										 |  |  |                 if (dwRejectType == 2) | 
					
						
							|  |  |  |                 { | 
					
						
							|  |  |  |                     // Retry the thread call immediately if return >= 0 & < 100 | 
					
						
							|  |  |  |                     return 99; | 
					
						
							|  |  |  |                 } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |                 // Too busy; cancel call | 
					
						
							|  |  |  |                 return -1; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             int IOleMessageFilter.MessagePending(IntPtr hTaskCallee, int dwTickCount, int dwPendingType) | 
					
						
							|  |  |  |             { | 
					
						
							|  |  |  |                 // Return the flag PENDINGMSG_WAITDEFPROCESS | 
					
						
							|  |  |  |                 return 2; | 
					
						
							|  |  |  |             } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             // Implement the IOleMessageFilter interface | 
					
						
							|  |  |  |             [DllImport("ole32.dll")] | 
					
						
							| 
									
										
										
										
											2024-01-12 22:24:12 -06:00
										 |  |  |             private static extern int CoRegisterMessageFilter(IOleMessageFilter? newFilter, out IOleMessageFilter? oldFilter); | 
					
						
							| 
									
										
										
										
											2020-06-15 21:29:16 +02:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         [ComImport(), Guid("00000016-0000-0000-C000-000000000046"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] | 
					
						
							|  |  |  |         private interface IOleMessageFilter | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |             [PreserveSig] | 
					
						
							|  |  |  |             int HandleInComingCall(int dwCallType, IntPtr hTaskCaller, int dwTickCount, IntPtr lpInterfaceInfo); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             [PreserveSig] | 
					
						
							|  |  |  |             int RetryRejectedCall(IntPtr hTaskCallee, int dwTickCount, int dwRejectType); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             [PreserveSig] | 
					
						
							|  |  |  |             int MessagePending(IntPtr hTaskCallee, int dwTickCount, int dwPendingType); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         #endregion | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | } |