mirror of
				https://github.com/godotengine/godot.git
				synced 2025-10-31 13:41:03 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			295 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			295 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
| using System;
 | |
| 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
 | |
|                 dte = TryVisualStudioLaunch("VisualStudio.DTE.17.0");
 | |
| 
 | |
|                 if (dte == null)
 | |
|                 {
 | |
|                     // Launch of VS 2022 failed, fallback to 2019
 | |
|                     dte = TryVisualStudioLaunch("VisualStudio.DTE.16.0");
 | |
| 
 | |
|                     if (dte == null)
 | |
|                     {
 | |
|                         Console.Error.WriteLine("Visual Studio not found");
 | |
|                         return 1;
 | |
|                     }
 | |
|                 }
 | |
| 
 | |
|                 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();
 | |
|                 SetForegroundWindow(mainWindow.HWnd);
 | |
| 
 | |
|                 MessageFilter.Revoke();
 | |
|             }
 | |
| 
 | |
|             return 0;
 | |
|         }
 | |
| 
 | |
|         private static DTE? TryVisualStudioLaunch(string version)
 | |
|         {
 | |
|             try
 | |
|             {
 | |
|                 var visualStudioDteType = Type.GetTypeFromProgID(version, throwOnError: true);
 | |
|                 var dte = (DTE?)Activator.CreateInstance(visualStudioDteType!);
 | |
| 
 | |
|                 return dte;
 | |
|             }
 | |
|             catch (COMException)
 | |
|             {
 | |
|                 return null;
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         private static DTE? FindInstanceEditingSolution(string solutionPath)
 | |
|         {
 | |
|             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
 | |
|                     if (!Regex.IsMatch(ppszDisplayName, "!VisualStudio.DTE.1[6-7].0:[0-9]"))
 | |
|                         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;
 | |
|         }
 | |
| 
 | |
|         static string NormalizePath(string path)
 | |
|         {
 | |
|             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
 | |
| 
 | |
|             private static IOleMessageFilter? _oldFilter;
 | |
| 
 | |
|             // 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)
 | |
|             {
 | |
|                 // flag = SERVERCALL_RETRYLATER
 | |
|                 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")]
 | |
|             private static extern int CoRegisterMessageFilter(IOleMessageFilter? newFilter, out IOleMessageFilter? oldFilter);
 | |
|         }
 | |
| 
 | |
|         [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
 | |
|     }
 | |
| }
 | 
