RiderScriptEditor.cs 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. using System;
  2. using System.Diagnostics;
  3. using System.IO;
  4. using System.Linq;
  5. using Packages.Rider.Editor.Util;
  6. using Unity.CodeEditor;
  7. using UnityEditor;
  8. using UnityEngine;
  9. using Debug = UnityEngine.Debug;
  10. namespace Packages.Rider.Editor
  11. {
  12. [InitializeOnLoad]
  13. public class RiderScriptEditor : IExternalCodeEditor
  14. {
  15. IDiscovery m_Discoverability;
  16. IGenerator m_ProjectGeneration;
  17. RiderInitializer m_Initiliazer = new RiderInitializer();
  18. static RiderScriptEditor()
  19. {
  20. try
  21. {
  22. var projectGeneration = new ProjectGeneration();
  23. var editor = new RiderScriptEditor(new Discovery(), projectGeneration);
  24. CodeEditor.Register(editor);
  25. var path = GetEditorRealPath(CodeEditor.CurrentEditorInstallation);
  26. if (IsRiderInstallation(path))
  27. {
  28. if (!FileSystemUtil.EditorPathExists(path)) // previously used rider was removed
  29. {
  30. var newEditor = editor.Installations.Last().Path;
  31. CodeEditor.SetExternalScriptEditor(newEditor);
  32. path = newEditor;
  33. }
  34. editor.CreateSolutionIfDoesntExist();
  35. if (ShouldLoadEditorPlugin(path))
  36. {
  37. editor.m_Initiliazer.Initialize(path);
  38. }
  39. InitProjectFilesWatcher();
  40. }
  41. }
  42. catch (Exception e)
  43. {
  44. Debug.LogException(e);
  45. }
  46. }
  47. private static void InitProjectFilesWatcher()
  48. {
  49. var watcher = new FileSystemWatcher();
  50. watcher.Path = Directory.GetCurrentDirectory();
  51. watcher.NotifyFilter = NotifyFilters.LastWrite; //Watch for changes in LastWrite times
  52. watcher.Filter = "*.*";
  53. // Add event handlers.
  54. watcher.Changed += OnChanged;
  55. watcher.Created += OnChanged;
  56. watcher.EnableRaisingEvents = true; // Begin watching.
  57. AppDomain.CurrentDomain.DomainUnload += (EventHandler) ((_, __) =>
  58. {
  59. watcher.Dispose();
  60. });
  61. }
  62. private static void OnChanged(object sender, FileSystemEventArgs e)
  63. {
  64. var extension = Path.GetExtension(e.FullPath);
  65. if (extension == ".sln" || extension == ".csproj")
  66. RiderScriptEditorData.instance.HasChanges = true;
  67. }
  68. private static string GetEditorRealPath(string path)
  69. {
  70. if (string.IsNullOrEmpty(path))
  71. {
  72. return path;
  73. }
  74. if (!FileSystemUtil.EditorPathExists(path))
  75. return path;
  76. if (SystemInfo.operatingSystemFamily != OperatingSystemFamily.Windows)
  77. {
  78. var realPath = FileSystemUtil.GetFinalPathName(path);
  79. // case of snap installation
  80. if (SystemInfo.operatingSystemFamily == OperatingSystemFamily.Linux)
  81. {
  82. if (new FileInfo(path).Name.ToLowerInvariant() == "rider" &&
  83. new FileInfo(realPath).Name.ToLowerInvariant() == "snap")
  84. {
  85. var snapInstallPath = "/snap/rider/current/bin/rider.sh";
  86. if (new FileInfo(snapInstallPath).Exists)
  87. return snapInstallPath;
  88. }
  89. }
  90. // in case of symlink
  91. return realPath;
  92. }
  93. return path;
  94. }
  95. const string unity_generate_all = "unity_generate_all_csproj";
  96. public RiderScriptEditor(IDiscovery discovery, IGenerator projectGeneration)
  97. {
  98. m_Discoverability = discovery;
  99. m_ProjectGeneration = projectGeneration;
  100. }
  101. private static string[] defaultExtensions
  102. {
  103. get
  104. {
  105. var customExtensions = new[] {"json", "asmdef", "log"};
  106. return EditorSettings.projectGenerationBuiltinExtensions.Concat(EditorSettings.projectGenerationUserExtensions)
  107. .Concat(customExtensions).Distinct().ToArray();
  108. }
  109. }
  110. private static string[] HandledExtensions
  111. {
  112. get
  113. {
  114. return HandledExtensionsString.Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries).Select(s => s.TrimStart('.', '*'))
  115. .ToArray();
  116. }
  117. }
  118. private static string HandledExtensionsString
  119. {
  120. get { return EditorPrefs.GetString("Rider_UserExtensions", string.Join(";", defaultExtensions));}
  121. set { EditorPrefs.SetString("Rider_UserExtensions", value); }
  122. }
  123. private static bool SupportsExtension(string path)
  124. {
  125. var extension = Path.GetExtension(path);
  126. if (string.IsNullOrEmpty(extension))
  127. return false;
  128. return HandledExtensions.Contains(extension.TrimStart('.'));
  129. }
  130. public void OnGUI()
  131. {
  132. var prevGenerate = EditorPrefs.GetBool(unity_generate_all, false);
  133. var generateAll = EditorGUILayout.Toggle("Generate all .csproj files.", prevGenerate);
  134. if (generateAll != prevGenerate)
  135. {
  136. EditorPrefs.SetBool(unity_generate_all, generateAll);
  137. }
  138. m_ProjectGeneration.GenerateAll(generateAll);
  139. if (ShouldLoadEditorPlugin(CurrentEditor))
  140. {
  141. HandledExtensionsString = EditorGUILayout.TextField(new GUIContent("Extensions handled: "), HandledExtensionsString);
  142. }
  143. }
  144. public void SyncIfNeeded(string[] addedFiles, string[] deletedFiles, string[] movedFiles, string[] movedFromFiles,
  145. string[] importedFiles)
  146. {
  147. m_ProjectGeneration.SyncIfNeeded(addedFiles.Union(deletedFiles).Union(movedFiles).Union(movedFromFiles),
  148. importedFiles);
  149. }
  150. public void SyncAll()
  151. {
  152. AssetDatabase.Refresh();
  153. if (RiderScriptEditorData.instance.HasChanges)
  154. {
  155. m_ProjectGeneration.Sync();
  156. RiderScriptEditorData.instance.HasChanges = false;
  157. }
  158. }
  159. public void Initialize(string editorInstallationPath) // is called each time ExternalEditor is changed
  160. {
  161. m_ProjectGeneration.Sync(); // regenerate csproj and sln for new editor
  162. }
  163. public bool OpenProject(string path, int line, int column)
  164. {
  165. if (path != "" && !SupportsExtension(path)) // Assets - Open C# Project passes empty path here
  166. {
  167. return false;
  168. }
  169. if (path == "" && SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX)
  170. {
  171. // there is a bug in DllImplementation - use package implementation here instead https://github.cds.internal.unity3d.com/unity/com.unity.ide.rider/issues/21
  172. return OpenOSXApp(path, line, column);
  173. }
  174. if (!IsUnityScript(path))
  175. {
  176. var fastOpenResult = EditorPluginInterop.OpenFileDllImplementation(path, line, column);
  177. if (fastOpenResult)
  178. return true;
  179. }
  180. if (SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX)
  181. {
  182. return OpenOSXApp(path, line, column);
  183. }
  184. var solution = GetSolutionFile(path); // TODO: If solution file doesn't exist resync.
  185. solution = solution == "" ? "" : $"\"{solution}\"";
  186. var process = new Process
  187. {
  188. StartInfo = new ProcessStartInfo
  189. {
  190. FileName = CodeEditor.CurrentEditorInstallation,
  191. Arguments = $"{solution} -l {line} \"{path}\"",
  192. UseShellExecute = true,
  193. }
  194. };
  195. process.Start();
  196. return true;
  197. }
  198. private bool OpenOSXApp(string path, int line, int column)
  199. {
  200. var solution = GetSolutionFile(path); // TODO: If solution file doesn't exist resync.
  201. solution = solution == "" ? "" : $"\"{solution}\"";
  202. var pathArguments = path == "" ? "" : $"-l {line} \"{path}\"";
  203. var process = new Process
  204. {
  205. StartInfo = new ProcessStartInfo
  206. {
  207. FileName = "open",
  208. Arguments = $"-n \"{CodeEditor.CurrentEditorInstallation}\" --args {solution} {pathArguments}",
  209. CreateNoWindow = true,
  210. UseShellExecute = true,
  211. }
  212. };
  213. process.Start();
  214. return true;
  215. }
  216. private string GetSolutionFile(string path)
  217. {
  218. if (IsUnityScript(path))
  219. {
  220. return Path.Combine(GetBaseUnityDeveloperFolder(), "Projects/CSharp/Unity.CSharpProjects.gen.sln");
  221. }
  222. var solutionFile = m_ProjectGeneration.SolutionFile();
  223. if (File.Exists(solutionFile))
  224. {
  225. return solutionFile;
  226. }
  227. return "";
  228. }
  229. static bool IsUnityScript(string path)
  230. {
  231. if (UnityEditor.Unsupported.IsDeveloperBuild())
  232. {
  233. var baseFolder = GetBaseUnityDeveloperFolder().Replace("\\", "/");
  234. var lowerPath = path.ToLowerInvariant().Replace("\\", "/");
  235. if (lowerPath.Contains((baseFolder + "/Runtime").ToLowerInvariant())
  236. || lowerPath.Contains((baseFolder + "/Editor").ToLowerInvariant()))
  237. {
  238. return true;
  239. }
  240. }
  241. return false;
  242. }
  243. static string GetBaseUnityDeveloperFolder()
  244. {
  245. return Directory.GetParent(EditorApplication.applicationPath).Parent.Parent.FullName;
  246. }
  247. public bool TryGetInstallationForPath(string editorPath, out CodeEditor.Installation installation)
  248. {
  249. if (FileSystemUtil.EditorPathExists(editorPath) && IsRiderInstallation(editorPath))
  250. {
  251. var info = new RiderPathLocator.RiderInfo(editorPath, false);
  252. installation = new CodeEditor.Installation
  253. {
  254. Name = info.Presentation,
  255. Path = info.Path
  256. };
  257. return true;
  258. }
  259. installation = default;
  260. return false;
  261. }
  262. public static bool IsRiderInstallation(string path)
  263. {
  264. if (string.IsNullOrEmpty(path))
  265. {
  266. return false;
  267. }
  268. var fileInfo = new FileInfo(path);
  269. var filename = fileInfo.Name.ToLowerInvariant();
  270. return filename.StartsWith("rider", StringComparison.Ordinal);
  271. }
  272. public static string CurrentEditor // works fast, doesn't validate if executable really exists
  273. => EditorPrefs.GetString("kScriptsDefaultApp");
  274. public static bool ShouldLoadEditorPlugin(string path)
  275. {
  276. var ver = RiderPathLocator.GetBuildNumber(path);
  277. if (!Version.TryParse(ver, out var version))
  278. return false;
  279. return version >= new Version("191.7141.156");
  280. }
  281. public CodeEditor.Installation[] Installations => m_Discoverability.PathCallback();
  282. public void CreateSolutionIfDoesntExist()
  283. {
  284. if (!m_ProjectGeneration.HasSolutionBeenGenerated())
  285. {
  286. m_ProjectGeneration.Sync();
  287. }
  288. }
  289. }
  290. }