123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289 |
- 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.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
- }
- }
|