mirror of
				https://github.com/godotengine/godot.git
				synced 2025-11-03 23:21:15 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			289 lines
		
	
	
	
		
			10 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			289 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");
 | 
						|
                }
 | 
						|
 | 
						|
                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 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
 | 
						|
    }
 | 
						|
}
 |