| using System; |
| using System.Diagnostics; |
| using System.IO; |
| using System.Linq; |
| using JetBrains.Annotations; |
| using Packages.Rider.Editor.ProjectGeneration; |
| using Packages.Rider.Editor.Util; |
| using Unity.CodeEditor; |
| using UnityEditor; |
| using UnityEngine; |
| using Debug = UnityEngine.Debug; |
|
|
| namespace Packages.Rider.Editor |
| { |
| [InitializeOnLoad] |
| internal class RiderScriptEditor : IExternalCodeEditor |
| { |
| IDiscovery m_Discoverability; |
| static IGenerator m_ProjectGeneration; |
| RiderInitializer m_Initiliazer = new RiderInitializer(); |
| static RiderScriptEditor m_RiderScriptEditor; |
|
|
| static RiderScriptEditor() |
| { |
| try |
| { |
| |
| var projectGeneration = new ProjectGeneration.ProjectGeneration(); |
| m_RiderScriptEditor = new RiderScriptEditor(new Discovery(), projectGeneration); |
| CodeEditor.Register(m_RiderScriptEditor); |
|
|
| InitializeInternal(CurrentEditor); |
| } |
| catch (Exception e) |
| { |
| Debug.LogException(e); |
| } |
| } |
|
|
| private static void ShowWarningOnUnexpectedScriptEditor(string path) |
| { |
| |
| try |
| { |
| var args = Environment.GetCommandLineArgs(); |
| var commandlineParser = new CommandLineParser(args); |
| if (commandlineParser.Options.ContainsKey("-riderPath")) |
| { |
| var originRiderPath = commandlineParser.Options["-riderPath"]; |
| var originRealPath = GetEditorRealPath(originRiderPath); |
| var originVersion = RiderPathLocator.GetBuildNumber(originRealPath); |
| var version = RiderPathLocator.GetBuildNumber(path); |
| if (originVersion != null && originVersion != version) |
| { |
| Debug.LogWarning("Unity was started by a version of Rider that is not the current default external editor. Advanced integration features cannot be enabled."); |
| Debug.Log($"Unity was started by Rider {originVersion}, but external editor is set to: {path}"); |
| } |
| } |
| } |
| catch (Exception e) |
| { |
| Debug.LogException(e); |
| } |
| } |
|
|
| internal static string GetEditorRealPath(string path) |
| { |
| if (string.IsNullOrEmpty(path)) |
| { |
| return path; |
| } |
|
|
| if (!FileSystemUtil.EditorPathExists(path)) |
| return path; |
|
|
| if (SystemInfo.operatingSystemFamily != OperatingSystemFamily.Windows) |
| { |
| var realPath = FileSystemUtil.GetFinalPathName(path); |
|
|
| |
| if (SystemInfo.operatingSystemFamily == OperatingSystemFamily.Linux) |
| { |
| if (new FileInfo(path).Name.ToLowerInvariant() == "rider" && |
| new FileInfo(realPath).Name.ToLowerInvariant() == "snap") |
| { |
| var snapInstallPath = "/snap/rider/current/bin/rider.sh"; |
| if (new FileInfo(snapInstallPath).Exists) |
| return snapInstallPath; |
| } |
| } |
|
|
| |
| return realPath; |
| } |
|
|
| return path; |
| } |
|
|
| public RiderScriptEditor(IDiscovery discovery, IGenerator projectGeneration) |
| { |
| m_Discoverability = discovery; |
| m_ProjectGeneration = projectGeneration; |
| } |
|
|
| public void OnGUI() |
| { |
| GUILayout.BeginHorizontal(); |
|
|
| var style = GUI.skin.label; |
| var text = "Customize handled extensions in"; |
| EditorGUILayout.LabelField(text, style, GUILayout.Width(style.CalcSize(new GUIContent(text)).x)); |
|
|
| if (PluginSettings.LinkButton("Project Settings | Editor | Additional extensions to include")) |
| { |
| SettingsService.OpenProjectSettings("Project/Editor"); |
| } |
|
|
| GUILayout.EndHorizontal(); |
|
|
| EditorGUILayout.LabelField("Generate .csproj files for:"); |
| EditorGUI.indentLevel++; |
| SettingsButton(ProjectGenerationFlag.Embedded, "Embedded packages", ""); |
| SettingsButton(ProjectGenerationFlag.Local, "Local packages", ""); |
| SettingsButton(ProjectGenerationFlag.Registry, "Registry packages", ""); |
| SettingsButton(ProjectGenerationFlag.Git, "Git packages", ""); |
| SettingsButton(ProjectGenerationFlag.BuiltIn, "Built-in packages", ""); |
| #if UNITY_2019_3_OR_NEWER |
| SettingsButton(ProjectGenerationFlag.LocalTarBall, "Local tarball", ""); |
| #endif |
| SettingsButton(ProjectGenerationFlag.Unknown, "Packages from unknown sources", ""); |
| SettingsButton(ProjectGenerationFlag.PlayerAssemblies, "Player projects", "For each player project generate an additional csproj with the name 'project-player.csproj'"); |
| RegenerateProjectFiles(); |
| EditorGUI.indentLevel--; |
| } |
|
|
| void RegenerateProjectFiles() |
| { |
| var rect = EditorGUI.IndentedRect(EditorGUILayout.GetControlRect(new GUILayoutOption[] {})); |
| rect.width = 252; |
| if (GUI.Button(rect, "Regenerate project files")) |
| { |
| m_ProjectGeneration.Sync(); |
| } |
| } |
|
|
| void SettingsButton(ProjectGenerationFlag preference, string guiMessage, string toolTip) |
| { |
| var prevValue = m_ProjectGeneration.AssemblyNameProvider.ProjectGenerationFlag.HasFlag(preference); |
| var newValue = EditorGUILayout.Toggle(new GUIContent(guiMessage, toolTip), prevValue); |
| if (newValue != prevValue) |
| { |
| m_ProjectGeneration.AssemblyNameProvider.ToggleProjectGeneration(preference); |
| } |
| } |
|
|
| public void SyncIfNeeded(string[] addedFiles, string[] deletedFiles, string[] movedFiles, string[] movedFromFiles, |
| string[] importedFiles) |
| { |
| m_ProjectGeneration.SyncIfNeeded(addedFiles.Union(deletedFiles).Union(movedFiles).Union(movedFromFiles), |
| importedFiles); |
| } |
| |
| public void SyncAll() |
| { |
| AssetDatabase.Refresh(); |
| m_ProjectGeneration.SyncIfNeeded(new string[] { }, new string[] { }); |
| } |
| |
| [UsedImplicitly] |
| public static void SyncSolution() |
| { |
| m_ProjectGeneration.Sync(); |
| } |
| |
| [UsedImplicitly] |
| public static void SyncIfNeeded(bool checkProjectFiles) |
| { |
| AssetDatabase.Refresh(); |
| m_ProjectGeneration.SyncIfNeeded(new string[] { }, new string[] { }, checkProjectFiles); |
| } |
| |
| [UsedImplicitly] |
| public static void SyncSolutionAndOpenExternalEditor() |
| { |
| m_ProjectGeneration.Sync(); |
| CodeEditor.CurrentEditor.OpenProject(); |
| } |
|
|
| |
| |
| |
| |
| |
| public void Initialize(string editorInstallationPath) |
| { |
| var prevEditorBuildNumber = RiderScriptEditorData.instance.prevEditorBuildNumber; |
| |
| RiderScriptEditorData.instance.Invalidate(editorInstallationPath, true); |
|
|
| if (EditorPluginInterop.EditorPluginAssembly == null) |
| { |
| InitializeInternal(editorInstallationPath); |
| return; |
| } |
| |
| if (prevEditorBuildNumber.ToVersion() != RiderScriptEditorData.instance.editorBuildNumber.ToVersion()) |
| { |
| m_ProjectGeneration.Sync(); |
| #if UNITY_2019_3_OR_NEWER |
| EditorUtility.RequestScriptReload(); |
| #else |
| UnityEditorInternal.InternalEditorUtility.RequestScriptReload(); |
| #endif |
| |
| } |
| } |
|
|
| private static void InitializeInternal(string currentEditorPath) |
| { |
| var path = GetEditorRealPath(currentEditorPath); |
|
|
| if (IsRiderOrFleetInstallation(path)) |
| { |
| RiderPathLocator.RiderInfo[] installations = null; |
|
|
| if (!RiderScriptEditorData.instance.initializedOnce) |
| { |
| installations = RiderPathLocator.GetAllRiderPaths().OrderBy(a => a.BuildNumber).ToArray(); |
| |
| if (installations.Any() && installations.All(a => GetEditorRealPath(a.Path) != path)) |
| { |
| if (RiderPathLocator.GetIsToolbox(path)) |
| { |
| var toolboxInstallations = installations.Where(a => a.IsToolbox).ToArray(); |
| if (toolboxInstallations.Any()) |
| { |
| var newEditor = toolboxInstallations.Last().Path; |
| CodeEditor.SetExternalScriptEditor(newEditor); |
| path = newEditor; |
| } |
| else |
| { |
| var newEditor = installations.Last().Path; |
| CodeEditor.SetExternalScriptEditor(newEditor); |
| path = newEditor; |
| } |
| } |
| else |
| { |
| var newEditorName = installations.Last().Presentation; |
| Debug.LogWarning($"Consider updating External Editor in Unity to {newEditorName}."); |
| } |
| } |
|
|
| ShowWarningOnUnexpectedScriptEditor(path); |
| RiderScriptEditorData.instance.initializedOnce = true; |
| } |
|
|
| if (!FileSystemUtil.EditorPathExists(path)) |
| { |
| if (installations == null) |
| installations = RiderPathLocator.GetAllRiderPaths().OrderBy(a => a.BuildNumber).ToArray(); |
| if (installations.Any()) |
| { |
| var newEditor = installations.Last().Path; |
| CodeEditor.SetExternalScriptEditor(newEditor); |
| path = newEditor; |
| } |
| } |
|
|
| RiderScriptEditorData.instance.Init(); |
|
|
| m_RiderScriptEditor.CreateSolutionIfDoesntExist(); |
| if (RiderScriptEditorData.instance.shouldLoadEditorPlugin) |
| { |
| m_RiderScriptEditor.m_Initiliazer.Initialize(path); |
| } |
|
|
| |
| #pragma warning disable 618 |
| EditorUserBuildSettings.activeBuildTargetChanged += () => |
| #pragma warning restore 618 |
| { |
| RiderScriptEditorData.instance.hasChanges = true; |
| }; |
| } |
| } |
|
|
| public bool OpenProject(string path, int line, int column) |
| { |
| var projectGeneration = (ProjectGeneration.ProjectGeneration) m_ProjectGeneration; |
| |
| if (path != "" && !projectGeneration.HasValidExtension(path)) |
| { |
| return false; |
| } |
| |
| if (!IsUnityScript(path)) |
| { |
| m_ProjectGeneration.SyncIfNeeded(affectedFiles: new string[] { }, new string[] { }); |
| var fastOpenResult = EditorPluginInterop.OpenFileDllImplementation(path, line, column); |
| if (fastOpenResult) |
| return true; |
| } |
|
|
| var solution = GetSolutionFile(path); |
| if (ExecutableStartsWith(CodeEditor.CurrentEditorInstallation, "fleet")) |
| solution = new FileInfo(solution).Directory.FullName; |
| if (SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX) |
| { |
| return OpenOSXApp(solution, path, line, column); |
| } |
|
|
| solution = solution == "" ? "" : $"\"{solution}\""; |
| var process = new Process |
| { |
| StartInfo = new ProcessStartInfo |
| { |
| FileName = CodeEditor.CurrentEditorInstallation, |
| Arguments = $"{solution} -l {line} \"{path}\"", |
| UseShellExecute = true, |
| } |
| }; |
|
|
| process.Start(); |
|
|
| return true; |
| } |
|
|
| private bool OpenOSXApp(string solution, string path, int line, int column) |
| { |
| solution = solution == "" ? "" : $"\"{solution}\""; |
| var pathArguments = path == "" ? "" : $"-l {line} \"{path}\""; |
| var process = new Process |
| { |
| StartInfo = new ProcessStartInfo |
| { |
| FileName = "open", |
| Arguments = $"-n -j \"{CodeEditor.CurrentEditorInstallation}\" --args {solution} {pathArguments}", |
| CreateNoWindow = true, |
| UseShellExecute = true, |
| } |
| }; |
|
|
| process.Start(); |
|
|
| return true; |
| } |
|
|
| private string GetSolutionFile(string path) |
| { |
| if (IsUnityScript(path)) |
| { |
| return Path.Combine(GetBaseUnityDeveloperFolder(), "Projects/CSharp/Unity.CSharpProjects.gen.sln"); |
| } |
|
|
| var solutionFile = m_ProjectGeneration.SolutionFile(); |
| if (File.Exists(solutionFile)) |
| { |
| return solutionFile; |
| } |
|
|
| return ""; |
| } |
|
|
| static bool IsUnityScript(string path) |
| { |
| if (UnityEditor.Unsupported.IsDeveloperBuild()) |
| { |
| var baseFolder = GetBaseUnityDeveloperFolder().Replace("\\", "/"); |
| var lowerPath = path.ToLowerInvariant().Replace("\\", "/"); |
|
|
| if (lowerPath.Contains((baseFolder + "/Runtime").ToLowerInvariant()) |
| || lowerPath.Contains((baseFolder + "/Editor").ToLowerInvariant())) |
| { |
| return true; |
| } |
| } |
|
|
| return false; |
| } |
|
|
| static string GetBaseUnityDeveloperFolder() |
| { |
| return Directory.GetParent(EditorApplication.applicationPath).Parent.Parent.FullName; |
| } |
|
|
| public bool TryGetInstallationForPath(string editorPath, out CodeEditor.Installation installation) |
| { |
| if (FileSystemUtil.EditorPathExists(editorPath) && IsRiderOrFleetInstallation(editorPath)) |
| { |
| var info = new RiderPathLocator.RiderInfo(editorPath, RiderPathLocator.GetIsToolbox(editorPath)); |
| installation = new CodeEditor.Installation |
| { |
| Name = info.Presentation, |
| Path = info.Path |
| }; |
| return true; |
| } |
|
|
| installation = default; |
| return false; |
| } |
|
|
| public static bool IsRiderOrFleetInstallation(string path) |
| { |
| if (IsAssetImportWorkerProcess()) |
| return false; |
|
|
| #if UNITY_2021_1_OR_NEWER |
| if (UnityEditor.MPE.ProcessService.level == UnityEditor.MPE.ProcessLevel.Secondary) |
| return false; |
| #elif UNITY_2020_2_OR_NEWER |
| if (UnityEditor.MPE.ProcessService.level == UnityEditor.MPE.ProcessLevel.Slave) |
| return false; |
| #elif UNITY_2020_1_OR_NEWER |
| if (Unity.MPE.ProcessService.level == Unity.MPE.ProcessLevel.UMP_SLAVE) |
| return false; |
| #endif |
| |
| if (string.IsNullOrEmpty(path)) |
| return false; |
|
|
| return ExecutableStartsWith(path, "rider") || ExecutableStartsWith(path, "fleet"); |
| } |
|
|
| public static bool ExecutableStartsWith(string path, string input) |
| { |
| var fileInfo = new FileInfo(path); |
| var filename = fileInfo.Name; |
| return filename.StartsWith(input, StringComparison.OrdinalIgnoreCase); |
| } |
|
|
| public static string GetProductNameForPresentation(FileInfo path) |
| { |
| var filename = path.Name; |
| if (filename.StartsWith("rider", StringComparison.OrdinalIgnoreCase)) |
| return "Rider"; |
| if (filename.StartsWith("fleet", StringComparison.OrdinalIgnoreCase)) |
| return "Fleet"; |
| return filename; |
| } |
|
|
| private static bool IsAssetImportWorkerProcess() |
| { |
| #if UNITY_2020_2_OR_NEWER |
| return UnityEditor.AssetDatabase.IsAssetImportWorkerProcess(); |
| #elif UNITY_2019_3_OR_NEWER |
| return UnityEditor.Experimental.AssetDatabaseExperimental.IsAssetImportWorkerProcess(); |
| #else |
| return false; |
| #endif |
| } |
|
|
| public static string CurrentEditor |
| => EditorPrefs.GetString("kScriptsDefaultApp"); |
|
|
| public CodeEditor.Installation[] Installations => m_Discoverability.PathCallback(); |
|
|
| private void CreateSolutionIfDoesntExist() |
| { |
| if (!m_ProjectGeneration.HasSolutionBeenGenerated()) |
| { |
| m_ProjectGeneration.Sync(); |
| } |
| } |
| } |
| } |