VSCodeScriptEditor.cs 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  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. [InitializeOnLoad]
  10. public class VSCodeScriptEditor : IExternalCodeEditor
  11. {
  12. const string vscode_argument = "vscode_arguments";
  13. const string vscode_generate_all = "unity_generate_all_csproj";
  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 GetDefaultApp => 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. var prevGenerate = EditorPrefs.GetBool(vscode_generate_all, false);
  101. var generateAll = EditorGUILayout.Toggle("Generate all .csproj files.", prevGenerate);
  102. if (generateAll != prevGenerate)
  103. {
  104. EditorPrefs.SetBool(vscode_generate_all, generateAll);
  105. }
  106. m_ProjectGeneration.GenerateAll(generateAll);
  107. HandledExtensionsString = EditorGUILayout.TextField(new GUIContent("Extensions handled: "), HandledExtensionsString);
  108. }
  109. public void CreateIfDoesntExist()
  110. {
  111. if (!m_ProjectGeneration.HasSolutionBeenGenerated())
  112. {
  113. m_ProjectGeneration.Sync();
  114. }
  115. }
  116. public void SyncIfNeeded(string[] addedFiles, string[] deletedFiles, string[] movedFiles, string[] movedFromFiles, string[] importedFiles)
  117. {
  118. m_ProjectGeneration.SyncIfNeeded(addedFiles.Union(deletedFiles).Union(movedFiles).Union(movedFromFiles), importedFiles);
  119. }
  120. public void SyncAll()
  121. {
  122. AssetDatabase.Refresh();
  123. m_ProjectGeneration.Sync();
  124. }
  125. public bool OpenProject(string path, int line, int column)
  126. {
  127. if (path != "" && !SupportsExtension(path)) // Assets - Open C# Project passes empty path here
  128. {
  129. return false;
  130. }
  131. if (line == -1)
  132. line = 1;
  133. if (column == -1)
  134. column = 0;
  135. string arguments;
  136. if (Arguments != DefaultArgument)
  137. {
  138. arguments = m_ProjectGeneration.ProjectDirectory != path
  139. ? CodeEditor.ParseArgument(Arguments, path, line, column)
  140. : m_ProjectGeneration.ProjectDirectory;
  141. }
  142. else
  143. {
  144. arguments = $@"""{m_ProjectGeneration.ProjectDirectory}""";
  145. if (m_ProjectGeneration.ProjectDirectory != path && path.Length != 0)
  146. {
  147. arguments += $@" -g ""{path}"":{line}:{column}";
  148. }
  149. }
  150. if (IsOSX)
  151. {
  152. return OpenOSX(arguments);
  153. }
  154. var process = new Process
  155. {
  156. StartInfo = new ProcessStartInfo
  157. {
  158. FileName = GetDefaultApp,
  159. Arguments = arguments,
  160. WindowStyle = ProcessWindowStyle.Hidden,
  161. CreateNoWindow = true,
  162. UseShellExecute = true,
  163. }
  164. };
  165. process.Start();
  166. return true;
  167. }
  168. static bool OpenOSX(string arguments)
  169. {
  170. var process = new Process
  171. {
  172. StartInfo = new ProcessStartInfo
  173. {
  174. FileName = "open",
  175. Arguments = $"\"{GetDefaultApp}\" --args {arguments}",
  176. UseShellExecute = true,
  177. }
  178. };
  179. process.Start();
  180. return true;
  181. }
  182. static bool SupportsExtension(string path)
  183. {
  184. var extension = Path.GetExtension(path);
  185. if (string.IsNullOrEmpty(extension))
  186. return false;
  187. return HandledExtensions.Contains(extension.TrimStart('.'));
  188. }
  189. public CodeEditor.Installation[] Installations => m_Discoverability.PathCallback();
  190. public VSCodeScriptEditor(IDiscovery discovery, IGenerator projectGeneration)
  191. {
  192. m_Discoverability = discovery;
  193. m_ProjectGeneration = projectGeneration;
  194. }
  195. static VSCodeScriptEditor()
  196. {
  197. var editor = new VSCodeScriptEditor(new VSCodeDiscovery(), new ProjectGeneration());
  198. CodeEditor.Register(editor);
  199. if (IsVSCodeInstallation(CodeEditor.CurrentEditorInstallation))
  200. {
  201. editor.CreateIfDoesntExist();
  202. }
  203. }
  204. static bool IsVSCodeInstallation(string path)
  205. {
  206. if (string.IsNullOrEmpty(path))
  207. {
  208. return false;
  209. }
  210. var lowerCasePath = path.ToLower();
  211. var filename = Path
  212. .GetFileName(lowerCasePath.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar))
  213. .Replace(" ", "");
  214. return k_SupportedFileNames.Contains(filename);
  215. }
  216. public void Initialize(string editorInstallationPath)
  217. {
  218. }
  219. }
  220. }