No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

VSCodeScriptEditor.cs 9.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282
  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.AssemblyNameProvider as IPackageInfoCache)?.ResetPackageInfoCache();
  143. m_ProjectGeneration.SyncIfNeeded(addedFiles.Union(deletedFiles).Union(movedFiles).Union(movedFromFiles).ToList(), importedFiles);
  144. }
  145. public void SyncAll()
  146. {
  147. (m_ProjectGeneration.AssemblyNameProvider as IPackageInfoCache)?.ResetPackageInfoCache();
  148. AssetDatabase.Refresh();
  149. m_ProjectGeneration.Sync();
  150. }
  151. public bool OpenProject(string path, int line, int column)
  152. {
  153. if (path != "" && (!SupportsExtension(path) || !File.Exists(path))) // Assets - Open C# Project passes empty path here
  154. {
  155. return false;
  156. }
  157. if (line == -1)
  158. line = 1;
  159. if (column == -1)
  160. column = 0;
  161. string arguments;
  162. if (Arguments != DefaultArgument)
  163. {
  164. arguments = m_ProjectGeneration.ProjectDirectory != path
  165. ? CodeEditor.ParseArgument(Arguments, path, line, column)
  166. : m_ProjectGeneration.ProjectDirectory;
  167. }
  168. else
  169. {
  170. arguments = $@"""{m_ProjectGeneration.ProjectDirectory}""";
  171. if (m_ProjectGeneration.ProjectDirectory != path && path.Length != 0)
  172. {
  173. arguments += $@" -g ""{path}"":{line}:{column}";
  174. }
  175. }
  176. if (IsOSX)
  177. {
  178. return OpenOSX(arguments);
  179. }
  180. var app = DefaultApp;
  181. var process = new Process
  182. {
  183. StartInfo = new ProcessStartInfo
  184. {
  185. FileName = app,
  186. Arguments = arguments,
  187. WindowStyle = app.EndsWith(".cmd", StringComparison.OrdinalIgnoreCase) ? ProcessWindowStyle.Hidden : ProcessWindowStyle.Normal,
  188. CreateNoWindow = true,
  189. UseShellExecute = true,
  190. }
  191. };
  192. process.Start();
  193. return true;
  194. }
  195. static bool OpenOSX(string arguments)
  196. {
  197. var process = new Process
  198. {
  199. StartInfo = new ProcessStartInfo
  200. {
  201. FileName = "open",
  202. Arguments = $"-n \"{DefaultApp}\" --args {arguments}",
  203. UseShellExecute = true,
  204. }
  205. };
  206. process.Start();
  207. return true;
  208. }
  209. static bool SupportsExtension(string path)
  210. {
  211. var extension = Path.GetExtension(path);
  212. if (string.IsNullOrEmpty(extension))
  213. return false;
  214. return HandledExtensions.Contains(extension.TrimStart('.'));
  215. }
  216. public CodeEditor.Installation[] Installations => m_Discoverability.PathCallback();
  217. public VSCodeScriptEditor(IDiscovery discovery, IGenerator projectGeneration)
  218. {
  219. m_Discoverability = discovery;
  220. m_ProjectGeneration = projectGeneration;
  221. }
  222. static VSCodeScriptEditor()
  223. {
  224. var editor = new VSCodeScriptEditor(new VSCodeDiscovery(), new ProjectGeneration(Directory.GetParent(Application.dataPath).FullName));
  225. CodeEditor.Register(editor);
  226. if (IsVSCodeInstallation(CodeEditor.CurrentEditorInstallation))
  227. {
  228. editor.CreateIfDoesntExist();
  229. }
  230. }
  231. static bool IsVSCodeInstallation(string path)
  232. {
  233. if (string.IsNullOrEmpty(path))
  234. {
  235. return false;
  236. }
  237. var lowerCasePath = path.ToLower();
  238. var filename = Path
  239. .GetFileName(lowerCasePath.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar))
  240. .Replace(" ", "");
  241. return k_SupportedFileNames.Contains(filename);
  242. }
  243. public void Initialize(string editorInstallationPath) { }
  244. }
  245. }