404 lines
13 KiB
C#
404 lines
13 KiB
C#
|
using System;
|
||
|
using System.Diagnostics;
|
||
|
using System.IO;
|
||
|
using System.Linq;
|
||
|
using Packages.Rider.Editor.Util;
|
||
|
using Unity.CodeEditor;
|
||
|
using UnityEditor;
|
||
|
using UnityEngine;
|
||
|
using Debug = UnityEngine.Debug;
|
||
|
|
||
|
namespace Packages.Rider.Editor
|
||
|
{
|
||
|
[InitializeOnLoad]
|
||
|
public class RiderScriptEditor : IExternalCodeEditor
|
||
|
{
|
||
|
IDiscovery m_Discoverability;
|
||
|
IGenerator m_ProjectGeneration;
|
||
|
RiderInitializer m_Initiliazer = new RiderInitializer();
|
||
|
|
||
|
static RiderScriptEditor()
|
||
|
{
|
||
|
try
|
||
|
{
|
||
|
var projectGeneration = new ProjectGeneration();
|
||
|
var editor = new RiderScriptEditor(new Discovery(), projectGeneration);
|
||
|
CodeEditor.Register(editor);
|
||
|
var path = GetEditorRealPath(CodeEditor.CurrentEditorInstallation);
|
||
|
|
||
|
if (IsRiderInstallation(path))
|
||
|
{
|
||
|
if (!RiderScriptEditorData.instance.InitializedOnce)
|
||
|
{
|
||
|
var installations = editor.Installations;
|
||
|
// is toolbox and outdated - update
|
||
|
if (installations.Any() && RiderPathLocator.IsToolbox(path) && installations.All(a => a.Path != path))
|
||
|
{
|
||
|
var toolboxInstallations = installations.Where(a => a.Name.Contains("(JetBrains Toolbox)")).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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// exists, is non toolbox and outdated - notify
|
||
|
if (installations.Any() && FileSystemUtil.EditorPathExists(path) && installations.All(a => a.Path != path))
|
||
|
{
|
||
|
var newEditorName = installations.Last().Name;
|
||
|
Debug.LogWarning($"Consider updating External Editor in Unity to Rider {newEditorName}.");
|
||
|
}
|
||
|
|
||
|
ShowWarningOnUnexpectedScriptEditor(path);
|
||
|
RiderScriptEditorData.instance.InitializedOnce = true;
|
||
|
}
|
||
|
|
||
|
if (!FileSystemUtil.EditorPathExists(path)) // previously used rider was removed
|
||
|
{
|
||
|
var installations = editor.Installations;
|
||
|
if (installations.Any())
|
||
|
{
|
||
|
var newEditor = installations.Last().Path;
|
||
|
CodeEditor.SetExternalScriptEditor(newEditor);
|
||
|
path = newEditor;
|
||
|
}
|
||
|
}
|
||
|
RiderScriptEditorData.instance.Init();
|
||
|
|
||
|
editor.CreateSolutionIfDoesntExist();
|
||
|
if (RiderScriptEditorData.instance.shouldLoadEditorPlugin)
|
||
|
{
|
||
|
editor.m_Initiliazer.Initialize(path);
|
||
|
}
|
||
|
|
||
|
InitProjectFilesWatcher();
|
||
|
}
|
||
|
}
|
||
|
catch (Exception e)
|
||
|
{
|
||
|
Debug.LogException(e);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static void ShowWarningOnUnexpectedScriptEditor(string path)
|
||
|
{
|
||
|
// Show warning, when Unity was started from Rider, but external editor is different https://github.com/JetBrains/resharper-unity/issues/1127
|
||
|
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 != string.Empty && 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}");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static void InitProjectFilesWatcher()
|
||
|
{
|
||
|
var watcher = new FileSystemWatcher();
|
||
|
watcher.Path = Directory.GetCurrentDirectory();
|
||
|
watcher.NotifyFilter = NotifyFilters.LastWrite; //Watch for changes in LastWrite times
|
||
|
watcher.Filter = "*.*";
|
||
|
|
||
|
// Add event handlers.
|
||
|
watcher.Changed += OnChanged;
|
||
|
watcher.Created += OnChanged;
|
||
|
|
||
|
watcher.EnableRaisingEvents = true; // Begin watching.
|
||
|
|
||
|
AppDomain.CurrentDomain.DomainUnload += (EventHandler) ((_, __) =>
|
||
|
{
|
||
|
watcher.Dispose();
|
||
|
});
|
||
|
}
|
||
|
|
||
|
private static void OnChanged(object sender, FileSystemEventArgs e)
|
||
|
{
|
||
|
var extension = Path.GetExtension(e.FullPath);
|
||
|
if (extension == ".sln" || extension == ".csproj")
|
||
|
RiderScriptEditorData.instance.HasChanges = true;
|
||
|
}
|
||
|
|
||
|
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);
|
||
|
|
||
|
// case of snap installation
|
||
|
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;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// in case of symlink
|
||
|
return realPath;
|
||
|
}
|
||
|
|
||
|
return path;
|
||
|
}
|
||
|
|
||
|
const string unity_generate_all = "unity_generate_all_csproj";
|
||
|
|
||
|
public RiderScriptEditor(IDiscovery discovery, IGenerator projectGeneration)
|
||
|
{
|
||
|
m_Discoverability = discovery;
|
||
|
m_ProjectGeneration = projectGeneration;
|
||
|
}
|
||
|
|
||
|
private static string[] defaultExtensions
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
var customExtensions = new[] {"json", "asmdef", "log", "xaml"};
|
||
|
return EditorSettings.projectGenerationBuiltinExtensions.Concat(EditorSettings.projectGenerationUserExtensions)
|
||
|
.Concat(customExtensions).Distinct().ToArray();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static string[] HandledExtensions
|
||
|
{
|
||
|
get
|
||
|
{
|
||
|
return HandledExtensionsString.Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries).Select(s => s.TrimStart('.', '*'))
|
||
|
.ToArray();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private static string HandledExtensionsString
|
||
|
{
|
||
|
get { return EditorPrefs.GetString("Rider_UserExtensions", string.Join(";", defaultExtensions));}
|
||
|
set { EditorPrefs.SetString("Rider_UserExtensions", value); }
|
||
|
}
|
||
|
|
||
|
private static bool SupportsExtension(string path)
|
||
|
{
|
||
|
var extension = Path.GetExtension(path);
|
||
|
if (string.IsNullOrEmpty(extension))
|
||
|
return false;
|
||
|
return HandledExtensions.Contains(extension.TrimStart('.'));
|
||
|
}
|
||
|
|
||
|
public void OnGUI()
|
||
|
{
|
||
|
var prevGenerate = EditorPrefs.GetBool(unity_generate_all, false);
|
||
|
var generateAll = EditorGUILayout.Toggle("Generate all .csproj files.", prevGenerate);
|
||
|
if (generateAll != prevGenerate)
|
||
|
{
|
||
|
EditorPrefs.SetBool(unity_generate_all, generateAll);
|
||
|
}
|
||
|
|
||
|
m_ProjectGeneration.GenerateAll(generateAll);
|
||
|
|
||
|
if (RiderScriptEditorData.instance.shouldLoadEditorPlugin)
|
||
|
{
|
||
|
HandledExtensionsString = EditorGUILayout.TextField(new GUIContent("Extensions handled: "), HandledExtensionsString);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
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();
|
||
|
if (RiderScriptEditorData.instance.HasChanges)
|
||
|
{
|
||
|
m_ProjectGeneration.Sync();
|
||
|
RiderScriptEditorData.instance.HasChanges = false;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public void Initialize(string editorInstallationPath) // is called each time ExternalEditor is changed
|
||
|
{
|
||
|
RiderScriptEditorData.instance.Invalidate(editorInstallationPath);
|
||
|
m_ProjectGeneration.Sync(); // regenerate csproj and sln for new editor
|
||
|
}
|
||
|
|
||
|
public bool OpenProject(string path, int line, int column)
|
||
|
{
|
||
|
if (path != "" && !SupportsExtension(path)) // Assets - Open C# Project passes empty path here
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
if (path == "" && SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX)
|
||
|
{
|
||
|
// there is a bug in DllImplementation - use package implementation here instead https://github.cds.internal.unity3d.com/unity/com.unity.ide.rider/issues/21
|
||
|
return OpenOSXApp(path, line, column);
|
||
|
}
|
||
|
|
||
|
if (!IsUnityScript(path))
|
||
|
{
|
||
|
var fastOpenResult = EditorPluginInterop.OpenFileDllImplementation(path, line, column);
|
||
|
if (fastOpenResult)
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
if (SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX)
|
||
|
{
|
||
|
return OpenOSXApp(path, line, column);
|
||
|
}
|
||
|
|
||
|
var solution = GetSolutionFile(path); // TODO: If solution file doesn't exist resync.
|
||
|
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 path, int line, int column)
|
||
|
{
|
||
|
var solution = GetSolutionFile(path); // TODO: If solution file doesn't exist resync.
|
||
|
solution = solution == "" ? "" : $"\"{solution}\"";
|
||
|
var pathArguments = path == "" ? "" : $"-l {line} \"{path}\"";
|
||
|
var process = new Process
|
||
|
{
|
||
|
StartInfo = new ProcessStartInfo
|
||
|
{
|
||
|
FileName = "open",
|
||
|
Arguments = $"-n \"{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) && IsRiderInstallation(editorPath))
|
||
|
{
|
||
|
var info = new RiderPathLocator.RiderInfo(editorPath, false);
|
||
|
installation = new CodeEditor.Installation
|
||
|
{
|
||
|
Name = info.Presentation,
|
||
|
Path = info.Path
|
||
|
};
|
||
|
return true;
|
||
|
}
|
||
|
|
||
|
installation = default;
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
public static bool IsRiderInstallation(string path)
|
||
|
{
|
||
|
if (IsAssetImportWorkerProcess())
|
||
|
return false;
|
||
|
|
||
|
if (string.IsNullOrEmpty(path))
|
||
|
{
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
var fileInfo = new FileInfo(path);
|
||
|
var filename = fileInfo.Name.ToLowerInvariant();
|
||
|
return filename.StartsWith("rider", StringComparison.Ordinal);
|
||
|
}
|
||
|
|
||
|
private static bool IsAssetImportWorkerProcess()
|
||
|
{
|
||
|
#if UNITY_2019_3_OR_NEWER
|
||
|
return UnityEditor.Experimental.AssetDatabaseExperimental.IsAssetImportWorkerProcess();
|
||
|
#else
|
||
|
return false;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
public static string CurrentEditor // works fast, doesn't validate if executable really exists
|
||
|
=> EditorPrefs.GetString("kScriptsDefaultApp");
|
||
|
|
||
|
public CodeEditor.Installation[] Installations => m_Discoverability.PathCallback();
|
||
|
|
||
|
public void CreateSolutionIfDoesntExist()
|
||
|
{
|
||
|
if (!m_ProjectGeneration.HasSolutionBeenGenerated())
|
||
|
{
|
||
|
m_ProjectGeneration.Sync();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|