VSCodeScriptEditor.cs 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. using System;
  2. using System.IO;
  3. using System.Linq;
  4. using System.Diagnostics;
  5. using UnityEditor;
  6. using UnityEngine;
  7. using Unity.CodeEditor;
  8. namespace VSCodeEditor
  9. {
  10. [InitializeOnLoad]
  11. public class VSCodeScriptEditor : IExternalCodeEditor
  12. {
  13. const string vscode_argument = "vscode_arguments";
  14. const string vscode_extension = "vscode_userExtensions";
  15. static readonly GUIContent k_ResetArguments = EditorGUIUtility.TrTextContent("Reset argument");
  16. string m_Arguments;
  17. IDiscovery m_Discoverability;
  18. IGenerator m_ProjectGeneration;
  19. static readonly string[] k_SupportedFileNames = { "code.exe", "visualstudiocode.app", "visualstudiocode-insiders.app", "vscode.app", "code.app", "code.cmd", "code-insiders.cmd", "code", "com.visualstudio.code" };
  20. static bool IsOSX => Application.platform == RuntimePlatform.OSXEditor;
  21. static string DefaultApp => EditorPrefs.GetString("kScriptsDefaultApp");
  22. static string DefaultArgument { get; } = "\"$(ProjectPath)\" -g \"$(File)\":$(Line):$(Column)";
  23. string Arguments
  24. {
  25. get => m_Arguments ?? (m_Arguments = EditorPrefs.GetString(vscode_argument, DefaultArgument));
  26. set
  27. {
  28. m_Arguments = value;
  29. EditorPrefs.SetString(vscode_argument, value);
  30. }
  31. }
  32. static string[] defaultExtensions
  33. {
  34. get
  35. {
  36. var customExtensions = new[] { "json", "asmdef", "log" };
  37. return EditorSettings.projectGenerationBuiltinExtensions
  38. .Concat(EditorSettings.projectGenerationUserExtensions)
  39. .Concat(customExtensions)
  40. .Distinct().ToArray();
  41. }
  42. }
  43. static string[] HandledExtensions
  44. {
  45. get
  46. {
  47. return HandledExtensionsString
  48. .Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
  49. .Select(s => s.TrimStart('.', '*'))
  50. .ToArray();
  51. }
  52. }
  53. static string HandledExtensionsString
  54. {
  55. get => EditorPrefs.GetString(vscode_extension, string.Join(";", defaultExtensions));
  56. set => EditorPrefs.SetString(vscode_extension, value);
  57. }
  58. public bool TryGetInstallationForPath(string editorPath, out CodeEditor.Installation installation)
  59. {
  60. var lowerCasePath = editorPath.ToLower();
  61. var filename = Path.GetFileName(lowerCasePath).Replace(" ", "");
  62. var installations = Installations;
  63. if (!k_SupportedFileNames.Contains(filename))
  64. {
  65. installation = default;
  66. return false;
  67. }
  68. if (!installations.Any())
  69. {
  70. installation = new CodeEditor.Installation
  71. {
  72. Name = "Visual Studio Code",
  73. Path = editorPath
  74. };
  75. }
  76. else
  77. {
  78. try
  79. {
  80. installation = installations.First(inst => inst.Path == editorPath);
  81. }
  82. catch (InvalidOperationException)
  83. {
  84. installation = new CodeEditor.Installation
  85. {
  86. Name = "Visual Studio Code",
  87. Path = editorPath
  88. };
  89. }
  90. }
  91. return true;
  92. }
  93. public void OnGUI()
  94. {
  95. Arguments = EditorGUILayout.TextField("External Script Editor Args", Arguments);
  96. if (GUILayout.Button(k_ResetArguments, GUILayout.Width(120)))
  97. {
  98. Arguments = DefaultArgument;
  99. }
  100. EditorGUILayout.LabelField("Generate .csproj files for:");
  101. EditorGUI.indentLevel++;
  102. SettingsButton(ProjectGenerationFlag.Embedded, "Embedded packages", "");
  103. SettingsButton(ProjectGenerationFlag.Local, "Local packages", "");
  104. SettingsButton(ProjectGenerationFlag.Registry, "Registry packages", "");
  105. SettingsButton(ProjectGenerationFlag.Git, "Git packages", "");
  106. SettingsButton(ProjectGenerationFlag.BuiltIn, "Built-in packages", "");
  107. #if UNITY_2019_3_OR_NEWER
  108. SettingsButton(ProjectGenerationFlag.LocalTarBall, "Local tarball", "");
  109. #endif
  110. SettingsButton(ProjectGenerationFlag.Unknown, "Packages from unknown sources", "");
  111. RegenerateProjectFiles();
  112. EditorGUI.indentLevel--;
  113. HandledExtensionsString = EditorGUILayout.TextField(new GUIContent("Extensions handled: "), HandledExtensionsString);
  114. }
  115. void RegenerateProjectFiles()
  116. {
  117. var rect = EditorGUI.IndentedRect(EditorGUILayout.GetControlRect(new GUILayoutOption[] { }));
  118. rect.width = 252;
  119. if (GUI.Button(rect, "Regenerate project files"))
  120. {
  121. m_ProjectGeneration.Sync();
  122. }
  123. }
  124. void SettingsButton(ProjectGenerationFlag preference, string guiMessage, string toolTip)
  125. {
  126. var prevValue = m_ProjectGeneration.AssemblyNameProvider.ProjectGenerationFlag.HasFlag(preference);
  127. var newValue = EditorGUILayout.Toggle(new GUIContent(guiMessage, toolTip), prevValue);
  128. if (newValue != prevValue)
  129. {
  130. m_ProjectGeneration.AssemblyNameProvider.ToggleProjectGeneration(preference);
  131. }
  132. }
  133. public void CreateIfDoesntExist()
  134. {
  135. if (!m_ProjectGeneration.SolutionExists())
  136. {
  137. m_ProjectGeneration.Sync();
  138. }
  139. }
  140. public void SyncIfNeeded(string[] addedFiles, string[] deletedFiles, string[] movedFiles, string[] movedFromFiles, string[] importedFiles)
  141. {
  142. m_ProjectGeneration.SyncIfNeeded(addedFiles.Union(deletedFiles).Union(movedFiles).Union(movedFromFiles).ToList(), importedFiles);
  143. }
  144. public void SyncAll()
  145. {
  146. AssetDatabase.Refresh();
  147. m_ProjectGeneration.Sync();
  148. }
  149. public bool OpenProject(string path, int line, int column)
  150. {
  151. if (path != "" && (!SupportsExtension(path) || !File.Exists(path))) // Assets - Open C# Project passes empty path here
  152. {
  153. return false;
  154. }
  155. if (line == -1)
  156. line = 1;
  157. if (column == -1)
  158. column = 0;
  159. string arguments;
  160. if (Arguments != DefaultArgument)
  161. {
  162. arguments = m_ProjectGeneration.ProjectDirectory != path
  163. ? CodeEditor.ParseArgument(Arguments, path, line, column)
  164. : m_ProjectGeneration.ProjectDirectory;
  165. }
  166. else
  167. {
  168. arguments = $@"""{m_ProjectGeneration.ProjectDirectory}""";
  169. if (m_ProjectGeneration.ProjectDirectory != path && path.Length != 0)
  170. {
  171. arguments += $@" -g ""{path}"":{line}:{column}";
  172. }
  173. }
  174. if (IsOSX)
  175. {
  176. return OpenOSX(arguments);
  177. }
  178. var app = DefaultApp;
  179. var process = new Process
  180. {
  181. StartInfo = new ProcessStartInfo
  182. {
  183. FileName = app,
  184. Arguments = arguments,
  185. WindowStyle = app.EndsWith(".cmd", StringComparison.OrdinalIgnoreCase) ? ProcessWindowStyle.Hidden : ProcessWindowStyle.Normal,
  186. CreateNoWindow = true,
  187. UseShellExecute = true,
  188. }
  189. };
  190. process.Start();
  191. return true;
  192. }
  193. static bool OpenOSX(string arguments)
  194. {
  195. var process = new Process
  196. {
  197. StartInfo = new ProcessStartInfo
  198. {
  199. FileName = "open",
  200. Arguments = $"-n \"{DefaultApp}\" --args {arguments}",
  201. UseShellExecute = true,
  202. }
  203. };
  204. process.Start();
  205. return true;
  206. }
  207. static bool SupportsExtension(string path)
  208. {
  209. var extension = Path.GetExtension(path);
  210. if (string.IsNullOrEmpty(extension))
  211. return false;
  212. return HandledExtensions.Contains(extension.TrimStart('.'));
  213. }
  214. public CodeEditor.Installation[] Installations => m_Discoverability.PathCallback();
  215. public VSCodeScriptEditor(IDiscovery discovery, IGenerator projectGeneration)
  216. {
  217. m_Discoverability = discovery;
  218. m_ProjectGeneration = projectGeneration;
  219. }
  220. static VSCodeScriptEditor()
  221. {
  222. var editor = new VSCodeScriptEditor(new VSCodeDiscovery(), new ProjectGeneration(Directory.GetParent(Application.dataPath).FullName));
  223. CodeEditor.Register(editor);
  224. if (IsVSCodeInstallation(CodeEditor.CurrentEditorInstallation))
  225. {
  226. editor.CreateIfDoesntExist();
  227. }
  228. }
  229. static bool IsVSCodeInstallation(string path)
  230. {
  231. if (string.IsNullOrEmpty(path))
  232. {
  233. return false;
  234. }
  235. var lowerCasePath = path.ToLower();
  236. var filename = Path
  237. .GetFileName(lowerCasePath.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar))
  238. .Replace(" ", "");
  239. return k_SupportedFileNames.Contains(filename);
  240. }
  241. public void Initialize(string editorInstallationPath) { }
  242. }
  243. }