mirror of
				https://github.com/godotengine/godot.git
				synced 2025-10-30 21:21:10 +00:00 
			
		
		
		
	
		
			
	
	
		
			271 lines
		
	
	
	
		
			9.5 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
		
		
			
		
	
	
			271 lines
		
	
	
	
		
			9.5 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 | |||
|  | 
 | |||
|  |                 var visualStudioDteType = Type.GetTypeFromProgID("VisualStudio.DTE.16.0", throwOnError: true); | |||
|  |                 dte = (DTE)Activator.CreateInstance(visualStudioDteType); | |||
|  | 
 | |||
|  |                 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(new IntPtr(mainWindow.HWnd)); | |||
|  | 
 | |||
|  |                 MessageFilter.Revoke(); | |||
|  |             } | |||
|  | 
 | |||
|  |             return 0; | |||
|  |         } | |||
|  | 
 | |||
|  |         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.16.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) | |||
|  |             { | |||
|  |                 if (dwRejectType == 2) | |||
|  |                     // flag = SERVERCALL_RETRYLATER | |||
|  |                 { | |||
|  |                     // 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 | |||
|  |     } | |||
|  | } |