diff --git a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs index 7a4641dbbc94..e7dbcdb65f24 100644 --- a/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs +++ b/modules/mono/editor/GodotTools/GodotTools.OpenVisualStudio/Program.cs @@ -1,289 +1,239 @@ +using EnvDTE; 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) - { - 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 - } +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) { + 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 +} }