2
0
Эх сурвалжийг харах

Merge pull request #34181 from van800/rider

Support Rider as external editor for Godot mono version
Rémi Verschelde 5 жил өмнө
parent
commit
cd9d513285

+ 2 - 1
modules/mono/build_scripts/godot_tools_build.py

@@ -82,7 +82,8 @@ def build(env_mono, api_sln_cmd):
 
     target_filenames = [
         'GodotTools.dll', 'GodotTools.IdeConnection.dll', 'GodotTools.BuildLogger.dll',
-        'GodotTools.ProjectEditor.dll', 'DotNet.Glob.dll', 'GodotTools.Core.dll'
+        'GodotTools.ProjectEditor.dll', 'DotNet.Glob.dll', 'GodotTools.Core.dll',
+        'JetBrains.Annotations.dll', 'Newtonsoft.Json.dll'
     ]
 
     if env_mono['target'] == 'debug':

+ 2 - 1
modules/mono/editor/GodotTools/GodotTools/ExternalEditorId.cs

@@ -6,6 +6,7 @@ namespace GodotTools
         VisualStudio, // TODO (Windows-only)
         VisualStudioForMac, // Mac-only
         MonoDevelop,
-        VsCode
+        VsCode,
+        Rider
     }
 }

+ 17 - 3
modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs

@@ -6,8 +6,10 @@ using System.Collections.Generic;
 using System.Diagnostics.CodeAnalysis;
 using System.IO;
 using GodotTools.Ides;
+using GodotTools.Ides.Rider;
 using GodotTools.Internals;
 using GodotTools.ProjectEditor;
+using JetBrains.Annotations;
 using static GodotTools.Internals.Globals;
 using File = GodotTools.Utils.File;
 using OS = GodotTools.Utils.OS;
@@ -189,6 +191,7 @@ namespace GodotTools
             "code", "code-oss", "vscode", "vscode-oss", "visual-studio-code", "visual-studio-code-oss"
         };
 
+        [UsedImplicitly]
         public Error OpenInExternalEditor(Script script, int line, int col)
         {
             var editor = (ExternalEditorId) editorSettings.GetSetting("mono/editor/external_editor");
@@ -202,6 +205,12 @@ namespace GodotTools
                     throw new NotSupportedException();
                 case ExternalEditorId.VisualStudioForMac:
                     goto case ExternalEditorId.MonoDevelop;
+                case ExternalEditorId.Rider:
+                {
+                    string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath);
+                    RiderPathManager.OpenFile(GodotSharpDirs.ProjectSlnPath, scriptPath, line);
+                    return Error.Ok;
+                }        
                 case ExternalEditorId.MonoDevelop:
                 {
                     string scriptPath = ProjectSettings.GlobalizePath(script.ResourcePath);
@@ -306,6 +315,7 @@ namespace GodotTools
             return Error.Ok;
         }
 
+        [UsedImplicitly]
         public bool OverridesExternalEditor()
         {
             return (ExternalEditorId) editorSettings.GetSetting("mono/editor/external_editor") != ExternalEditorId.None;
@@ -419,18 +429,21 @@ namespace GodotTools
             if (OS.IsWindows)
             {
                 settingsHintStr += $",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" +
-                                   $",Visual Studio Code:{(int) ExternalEditorId.VsCode}";
+                                   $",Visual Studio Code:{(int) ExternalEditorId.VsCode}" +
+                                   $",JetBrains Rider:{(int) ExternalEditorId.Rider}";
             }
             else if (OS.IsOSX)
             {
                 settingsHintStr += $",Visual Studio:{(int) ExternalEditorId.VisualStudioForMac}" +
                                    $",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" +
-                                   $",Visual Studio Code:{(int) ExternalEditorId.VsCode}";
+                                   $",Visual Studio Code:{(int) ExternalEditorId.VsCode}" +
+                                   $",JetBrains Rider:{(int) ExternalEditorId.Rider}";
             }
             else if (OS.IsUnixLike())
             {
                 settingsHintStr += $",MonoDevelop:{(int) ExternalEditorId.MonoDevelop}" +
-                                   $",Visual Studio Code:{(int) ExternalEditorId.VsCode}";
+                                   $",Visual Studio Code:{(int) ExternalEditorId.VsCode}" +
+                                   $",JetBrains Rider:{(int) ExternalEditorId.Rider}";
             }
 
             editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary
@@ -448,6 +461,7 @@ namespace GodotTools
             exportPluginWeak = WeakRef(exportPlugin);
 
             BuildManager.Initialize();
+            RiderPathManager.Initialize();
 
             GodotIdeManager = new GodotIdeManager();
             AddChild(GodotIdeManager);

+ 17 - 0
modules/mono/editor/GodotTools/GodotTools/GodotTools.csproj

@@ -30,7 +30,15 @@
     <ConsolePause>false</ConsolePause>
   </PropertyGroup>
   <ItemGroup>
+    <Reference Include="JetBrains.Annotations, Version=2019.1.3.0, Culture=neutral, PublicKeyToken=1010a0d8d6380325">
+      <HintPath>..\packages\JetBrains.Annotations.2019.1.3\lib\net20\JetBrains.Annotations.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
     <Reference Include="Mono.Posix" />
+    <Reference Include="Newtonsoft.Json, Version=12.0.0.0, Culture=neutral, PublicKeyToken=30ad4fe6b2a6aeed">
+      <HintPath>..\packages\Newtonsoft.Json.12.0.3\lib\net45\Newtonsoft.Json.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
     <Reference Include="System" />
     <Reference Include="GodotSharp">
       <HintPath>$(GodotSourceRootPath)/bin/GodotSharp/Api/$(GodotApiConfiguration)/GodotSharp.dll</HintPath>
@@ -47,6 +55,8 @@
     <Compile Include="Ides\GodotIdeServer.cs" />
     <Compile Include="Ides\MonoDevelop\EditorId.cs" />
     <Compile Include="Ides\MonoDevelop\Instance.cs" />
+    <Compile Include="Ides\Rider\RiderPathLocator.cs" />
+    <Compile Include="Ides\Rider\RiderPathManager.cs" />
     <Compile Include="Internals\BindingsGenerator.cs" />
     <Compile Include="Internals\EditorProgress.cs" />
     <Compile Include="Internals\GodotSharpDirs.cs" />
@@ -67,6 +77,7 @@
     <Compile Include="BottomPanel.cs" />
     <Compile Include="CsProjOperations.cs" />
     <Compile Include="Utils\CollectionExtensions.cs" />
+    <Compile Include="Utils\User32Dll.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\GodotTools.BuildLogger\GodotTools.BuildLogger.csproj">
@@ -86,5 +97,11 @@
       <Name>GodotTools.Core</Name>
     </ProjectReference>
   </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <Content Include="Ides\Rider\.editorconfig" />
+  </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
 </Project>

+ 1 - 0
modules/mono/editor/GodotTools/GodotTools/Ides/GodotIdeManager.cs

@@ -72,6 +72,7 @@ namespace GodotTools.Ides
                 case ExternalEditorId.None:
                 case ExternalEditorId.VisualStudio:
                 case ExternalEditorId.VsCode:
+                case ExternalEditorId.Rider:
                     throw new NotSupportedException();
                 case ExternalEditorId.VisualStudioForMac:
                     goto case ExternalEditorId.MonoDevelop;

+ 6 - 0
modules/mono/editor/GodotTools/GodotTools/Ides/Rider/.editorconfig

@@ -0,0 +1,6 @@
+root = true
+
+[*]
+indent_style = space
+indent_size = 2
+end_of_line = lf

+ 416 - 0
modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathLocator.cs

@@ -0,0 +1,416 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Godot;
+using JetBrains.Annotations;
+using Microsoft.Win32;
+using Newtonsoft.Json;
+using Directory = System.IO.Directory;
+using Environment = System.Environment;
+using File = System.IO.File;
+using Path = System.IO.Path;
+using OS = GodotTools.Utils.OS;
+
+namespace GodotTools.Ides.Rider
+{
+  /// <summary>
+  /// This code is a modified version of the JetBrains resharper-unity plugin listed under Apache License 2.0 license:
+  /// https://github.com/JetBrains/resharper-unity/blob/master/unity/JetBrains.Rider.Unity.Editor/EditorPlugin/RiderPathLocator.cs
+  /// </summary>
+  public static class RiderPathLocator
+  {
+    public static RiderInfo[] GetAllRiderPaths()
+    {
+      try
+      {
+        if (OS.IsWindows)
+        {
+          return CollectRiderInfosWindows();
+        }
+        if (OS.IsOSX)
+        {
+          return CollectRiderInfosMac();
+        }
+        if (OS.IsUnixLike())
+        {
+          return CollectAllRiderPathsLinux();
+        }
+        throw new Exception("Unexpected OS.");
+      }
+      catch (Exception e)
+      {
+        GD.PushWarning(e.Message);
+      }
+
+      return new RiderInfo[0];
+    }
+
+    private static RiderInfo[] CollectAllRiderPathsLinux()
+    {
+      var installInfos = new List<RiderInfo>();
+      var home = Environment.GetEnvironmentVariable("HOME");
+      if (!string.IsNullOrEmpty(home))
+      {
+        var toolboxRiderRootPath = GetToolboxBaseDir();
+        installInfos.AddRange(CollectPathsFromToolbox(toolboxRiderRootPath, "bin", "rider.sh", false)
+          .Select(a => new RiderInfo(a, true)).ToList());
+
+        //$Home/.local/share/applications/jetbrains-rider.desktop
+        var shortcut = new FileInfo(Path.Combine(home, @".local/share/applications/jetbrains-rider.desktop"));
+
+        if (shortcut.Exists)
+        {
+          var lines = File.ReadAllLines(shortcut.FullName);
+          foreach (var line in lines)
+          {
+            if (!line.StartsWith("Exec=\""))
+              continue;
+            var path = line.Split('"').Where((item, index) => index == 1).SingleOrDefault();
+            if (string.IsNullOrEmpty(path))
+              continue;
+
+            if (installInfos.Any(a => a.Path == path)) // avoid adding similar build as from toolbox
+              continue;
+            installInfos.Add(new RiderInfo(path, false));
+          }
+        }
+      }
+
+      // snap install
+      var snapInstallPath = "/snap/rider/current/bin/rider.sh";
+      if (new FileInfo(snapInstallPath).Exists)
+        installInfos.Add(new RiderInfo(snapInstallPath, false));
+      
+      return installInfos.ToArray();
+    }
+
+    private static RiderInfo[] CollectRiderInfosMac()
+    {
+      var installInfos = new List<RiderInfo>();
+      // "/Applications/*Rider*.app"
+      var folder = new DirectoryInfo("/Applications");
+      if (folder.Exists)
+      {
+        installInfos.AddRange(folder.GetDirectories("*Rider*.app")
+          .Select(a => new RiderInfo(a.FullName, false))
+          .ToList());
+      }
+
+      // /Users/user/Library/Application Support/JetBrains/Toolbox/apps/Rider/ch-1/181.3870.267/Rider EAP.app
+      var toolboxRiderRootPath = GetToolboxBaseDir();
+      var paths = CollectPathsFromToolbox(toolboxRiderRootPath, "", "Rider*.app", true)
+        .Select(a => new RiderInfo(a, true));
+      installInfos.AddRange(paths);
+
+      return installInfos.ToArray();
+    }
+
+    private static RiderInfo[] CollectRiderInfosWindows()
+    {
+      var installInfos = new List<RiderInfo>();
+      var toolboxRiderRootPath = GetToolboxBaseDir();
+      var installPathsToolbox = CollectPathsFromToolbox(toolboxRiderRootPath, "bin", "rider64.exe", false).ToList();
+      installInfos.AddRange(installPathsToolbox.Select(a => new RiderInfo(a, true)).ToList());
+      
+      var installPaths = new List<string>();
+      const string registryKey = @"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall";
+      CollectPathsFromRegistry(registryKey, installPaths);
+      const string wowRegistryKey = @"SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall";
+      CollectPathsFromRegistry(wowRegistryKey, installPaths);
+      
+      installInfos.AddRange(installPaths.Select(a => new RiderInfo(a, false)).ToList());
+
+      return installInfos.ToArray();
+    }
+
+    private static string GetToolboxBaseDir()
+    {   
+      if (OS.IsWindows)
+      {
+        var localAppData = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
+        return Path.Combine(localAppData, @"JetBrains\Toolbox\apps\Rider");
+      }
+
+      if (OS.IsOSX)
+      {
+        var home = Environment.GetEnvironmentVariable("HOME");
+        if (!string.IsNullOrEmpty(home))
+        {
+          return Path.Combine(home, @"Library/Application Support/JetBrains/Toolbox/apps/Rider");
+        }
+      }
+
+      if (OS.IsUnixLike())
+      {
+        var home = Environment.GetEnvironmentVariable("HOME");
+        if (!string.IsNullOrEmpty(home))
+        {
+          return Path.Combine(home, @".local/share/JetBrains/Toolbox/apps/Rider");
+        }
+      }
+
+      throw new Exception("Unexpected OS.");
+    }
+    
+    internal static ProductInfo GetBuildVersion(string path)
+    {
+      var buildTxtFileInfo = new FileInfo(Path.Combine(path, GetRelativePathToBuildTxt()));
+      var dir = buildTxtFileInfo.DirectoryName;
+      if (!Directory.Exists(dir))
+        return null;
+      var buildVersionFile = new FileInfo(Path.Combine(dir, "product-info.json"));
+      if (!buildVersionFile.Exists) 
+        return null;
+      var json = File.ReadAllText(buildVersionFile.FullName);
+      return ProductInfo.GetProductInfo(json);
+    }
+    
+    internal static Version GetBuildNumber(string path)
+    {
+      var file = new FileInfo(Path.Combine(path, GetRelativePathToBuildTxt()));
+      if (!file.Exists) 
+        return null;
+      var text = File.ReadAllText(file.FullName);
+      if (text.Length <= 3) 
+        return null;
+      
+      var versionText = text.Substring(3);
+      return Version.TryParse(versionText, out var v) ? v : null;
+    }
+
+    internal static bool IsToolbox(string path)
+    {
+      return path.StartsWith(GetToolboxBaseDir());
+    }
+
+    private static string GetRelativePathToBuildTxt()
+    {
+      if (OS.IsWindows || OS.IsUnixLike())
+        return "../../build.txt";
+      if (OS.IsOSX)
+        return "Contents/Resources/build.txt";
+      throw new Exception("Unknown OS.");
+    }
+
+    private static void CollectPathsFromRegistry(string registryKey, List<string> installPaths)
+    {
+      using (var key = Registry.LocalMachine.OpenSubKey(registryKey))
+      {
+        if (key == null) return;
+        foreach (var subkeyName in key.GetSubKeyNames().Where(a => a.Contains("Rider")))
+        {
+          using (var subkey = key.OpenSubKey(subkeyName))
+          {
+            var folderObject = subkey?.GetValue("InstallLocation");
+            if (folderObject == null) continue;
+            var folder = folderObject.ToString();
+            var possiblePath = Path.Combine(folder, @"bin\rider64.exe");
+            if (File.Exists(possiblePath))
+              installPaths.Add(possiblePath);
+          }
+        }
+      }
+    }
+
+    private static string[] CollectPathsFromToolbox(string toolboxRiderRootPath, string dirName, string searchPattern,
+      bool isMac)
+    {
+      if (!Directory.Exists(toolboxRiderRootPath))
+        return new string[0];
+
+      var channelDirs = Directory.GetDirectories(toolboxRiderRootPath);
+      var paths = channelDirs.SelectMany(channelDir =>
+        {
+          try
+          {
+            // use history.json - last entry stands for the active build https://jetbrains.slack.com/archives/C07KNP99D/p1547807024066500?thread_ts=1547731708.057700&cid=C07KNP99D
+            var historyFile = Path.Combine(channelDir, ".history.json");
+            if (File.Exists(historyFile))
+            {
+              var json = File.ReadAllText(historyFile);
+              var build = ToolboxHistory.GetLatestBuildFromJson(json);
+              if (build != null)
+              {
+                var buildDir = Path.Combine(channelDir, build);
+                var executablePaths = GetExecutablePaths(dirName, searchPattern, isMac, buildDir);
+                if (executablePaths.Any())
+                  return executablePaths;
+              }
+            }
+
+            var channelFile = Path.Combine(channelDir, ".channel.settings.json");
+            if (File.Exists(channelFile))
+            {
+              var json = File.ReadAllText(channelFile).Replace("active-application", "active_application");
+              var build = ToolboxInstallData.GetLatestBuildFromJson(json);
+              if (build != null)
+              {
+                var buildDir = Path.Combine(channelDir, build);
+                var executablePaths = GetExecutablePaths(dirName, searchPattern, isMac, buildDir);
+                if (executablePaths.Any())
+                  return executablePaths;
+              }
+            }
+
+            // changes in toolbox json files format may brake the logic above, so return all found Rider installations
+            return Directory.GetDirectories(channelDir)
+              .SelectMany(buildDir => GetExecutablePaths(dirName, searchPattern, isMac, buildDir));
+          }
+          catch (Exception e)
+          {
+            // do not write to Debug.Log, just log it.
+            Logger.Warn($"Failed to get RiderPath from {channelDir}", e);
+          }
+
+          return new string[0];
+        })
+        .Where(c => !string.IsNullOrEmpty(c))
+        .ToArray();
+      return paths;
+    }
+
+    private static string[] GetExecutablePaths(string dirName, string searchPattern, bool isMac, string buildDir)
+    {
+      var folder = new DirectoryInfo(Path.Combine(buildDir, dirName));
+      if (!folder.Exists)
+        return new string[0];
+
+      if (!isMac)
+        return new[] {Path.Combine(folder.FullName, searchPattern)}.Where(File.Exists).ToArray();
+      return folder.GetDirectories(searchPattern).Select(f => f.FullName)
+        .Where(Directory.Exists).ToArray();
+    }
+
+    // Disable the "field is never assigned" compiler warning. We never assign it, but Unity does.
+    // Note that Unity disable this warning in the generated C# projects
+#pragma warning disable 0649
+
+    [Serializable]
+    class ToolboxHistory
+    {
+      public List<ItemNode> history;
+
+      public static string GetLatestBuildFromJson(string json)
+      {
+        try
+        {
+          return JsonConvert.DeserializeObject<ToolboxHistory>(json).history.LastOrDefault()?.item.build;
+        }
+        catch (Exception)
+        {
+          Logger.Warn($"Failed to get latest build from json {json}");
+        }
+
+        return null;
+      }
+    }
+
+    [Serializable]
+    class ItemNode
+    {
+      public BuildNode item;
+    }
+
+    [Serializable]
+    class BuildNode
+    {
+      public string build;
+    }
+
+    [Serializable]
+    public class ProductInfo
+    {
+      public string version;
+      public string versionSuffix;
+
+      [CanBeNull]
+      internal static ProductInfo GetProductInfo(string json)
+      {
+        try
+        {
+          var productInfo = JsonConvert.DeserializeObject<ProductInfo>(json);
+          return productInfo;
+        }
+        catch (Exception)
+        {
+          Logger.Warn($"Failed to get version from json {json}");
+        }
+
+        return null;
+      }
+    }
+
+    // ReSharper disable once ClassNeverInstantiated.Global
+    [Serializable]
+    class ToolboxInstallData
+    {
+      // ReSharper disable once InconsistentNaming
+      public ActiveApplication active_application;
+
+      [CanBeNull]
+      public static string GetLatestBuildFromJson(string json)
+      {
+        try
+        {
+          var toolbox = JsonConvert.DeserializeObject<ToolboxInstallData>(json);
+          var builds = toolbox.active_application.builds;
+          if (builds != null && builds.Any())
+            return builds.First();
+        }
+        catch (Exception)
+        {
+          Logger.Warn($"Failed to get latest build from json {json}");
+        }
+
+        return null;
+      }
+    }
+
+    [Serializable]
+    class ActiveApplication
+    {
+      // ReSharper disable once InconsistentNaming
+      public List<string> builds;
+    }
+
+#pragma warning restore 0649
+
+    public struct RiderInfo
+    {
+      public bool IsToolbox;
+      public string Presentation;
+      public Version BuildNumber;
+      public ProductInfo ProductInfo;
+      public string Path;
+
+      public RiderInfo(string path, bool isToolbox)
+      {
+        BuildNumber = GetBuildNumber(path);
+        ProductInfo = GetBuildVersion(path);
+        Path = new FileInfo(path).FullName; // normalize separators
+        var presentation = $"Rider {BuildNumber}";
+
+        if (ProductInfo != null && !string.IsNullOrEmpty(ProductInfo.version))
+        {
+          var suffix = string.IsNullOrEmpty(ProductInfo.versionSuffix) ? "" : $" {ProductInfo.versionSuffix}";
+          presentation = $"Rider {ProductInfo.version}{suffix}";
+        }
+
+        if (isToolbox)
+          presentation += " (JetBrains Toolbox)";
+
+        Presentation = presentation;
+        IsToolbox = isToolbox;
+      }
+    }
+
+    private static class Logger
+    {
+      internal static void Warn(string message, Exception e = null)
+      { 
+        throw new Exception(message, e);
+      }
+    }
+  }
+}

+ 117 - 0
modules/mono/editor/GodotTools/GodotTools/Ides/Rider/RiderPathManager.cs

@@ -0,0 +1,117 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using Godot;
+using GodotTools.Internals;
+
+namespace GodotTools.Ides.Rider
+{
+  public static class RiderPathManager
+  {
+    private static readonly string editorPathSettingName= "mono/editor/editor_path_optional";
+
+    private static string GetRiderPathFromSettings()
+    {
+      var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
+      if (editorSettings.HasSetting(editorPathSettingName)) 
+        return (string) editorSettings.GetSetting(editorPathSettingName);
+      return null;
+    }
+    
+    public static void Initialize()
+    {
+      var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
+      var editor = (ExternalEditorId) editorSettings.GetSetting("mono/editor/external_editor");
+      if (editor == ExternalEditorId.Rider)
+      {
+        if (!editorSettings.HasSetting(editorPathSettingName))
+        {
+          Globals.EditorDef(editorPathSettingName, "Optional");
+          editorSettings.AddPropertyInfo(new Godot.Collections.Dictionary
+          {
+            ["type"] = Variant.Type.String,
+            ["name"] = editorPathSettingName,
+            ["hint"] = PropertyHint.File,
+            ["hint_string"] = ""
+          });
+        }
+
+        var riderPath = (string) editorSettings.GetSetting(editorPathSettingName);
+        if (IsRiderAndExists(riderPath))
+        {
+          Globals.EditorDef(editorPathSettingName, riderPath);
+          return;
+        }
+
+        var paths = RiderPathLocator.GetAllRiderPaths();
+
+        if (!paths.Any())
+          return;
+
+        var newPath = paths.Last().Path;
+        Globals.EditorDef(editorPathSettingName, newPath);
+        editorSettings.SetSetting(editorPathSettingName, newPath);
+      }
+    }
+
+    private static bool IsRider(string path)
+    {
+      if (string.IsNullOrEmpty(path))
+      {
+        return false;
+      }
+
+      var fileInfo = new FileInfo(path);
+      var filename = fileInfo.Name.ToLowerInvariant();
+      return filename.StartsWith("rider", StringComparison.Ordinal);
+    }
+
+    private static string CheckAndUpdatePath(string riderPath)
+    {
+      if (IsRiderAndExists(riderPath))
+      {
+        return riderPath;
+      }
+
+      var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
+      var paths = RiderPathLocator.GetAllRiderPaths();
+
+      if (!paths.Any())
+        return null;
+        
+      var newPath = paths.Last().Path;
+      editorSettings.SetSetting(editorPathSettingName, newPath);
+      Globals.EditorDef(editorPathSettingName, newPath);
+      return newPath;
+    }
+
+    private static bool IsRiderAndExists(string riderPath)
+    {
+      return !string.IsNullOrEmpty(riderPath) && IsRider(riderPath) && new FileInfo(riderPath).Exists;
+    }
+
+    public static void OpenFile(string slnPath, string scriptPath, int line)
+    {
+      var pathFromSettings = GetRiderPathFromSettings();
+      var path = CheckAndUpdatePath(pathFromSettings);
+      
+      var args = new List<string>();
+      args.Add(slnPath);
+      if (line >= 0)
+      {
+        args.Add("--line");
+        args.Add(line.ToString());
+      }
+      args.Add(scriptPath);
+      try
+      {
+        Utils.OS.RunProcess(path, args);
+      }
+      catch (Exception e)
+      {
+        GD.PushError($"Error when trying to run code editor: JetBrains Rider. Exception message: '{e.Message}'");
+      }
+    }
+  }
+}

+ 2 - 0
modules/mono/editor/GodotTools/GodotTools/Utils/OS.cs

@@ -157,6 +157,8 @@ namespace GodotTools.Utils
 
                 process.BeginOutputReadLine();
                 process.BeginErrorReadLine();
+                if (IsWindows && process.Id>0) 
+                    User32Dll.AllowSetForegroundWindow(process.Id); // allows application to focus itself
             }
         }
     }

+ 10 - 0
modules/mono/editor/GodotTools/GodotTools/Utils/User32Dll.cs

@@ -0,0 +1,10 @@
+using System.Runtime.InteropServices;
+
+namespace GodotTools.Utils
+{
+    public static class User32Dll
+    {
+        [DllImport("user32.dll")]
+        public static extern bool AllowSetForegroundWindow(int dwProcessId);
+    }
+}

+ 5 - 0
modules/mono/editor/GodotTools/GodotTools/packages.config

@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="JetBrains.Annotations" version="2019.1.3" targetFramework="net45" />
+  <package id="Newtonsoft.Json" version="12.0.3" targetFramework="net45" />
+</packages>