using System; using System.IO; using System.Linq; using System.Diagnostics; using UnityEditor; using UnityEngine; using Unity.CodeEditor; namespace VSCodeEditor { [InitializeOnLoad] public class VSCodeScriptEditor : IExternalCodeEditor { const string vscode_argument = "vscode_arguments"; const string vscode_extension = "vscode_userExtensions"; static readonly GUIContent k_ResetArguments = EditorGUIUtility.TrTextContent("Reset argument"); string m_Arguments; IDiscovery m_Discoverability; IGenerator m_ProjectGeneration; 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" }; static bool IsOSX => Application.platform == RuntimePlatform.OSXEditor; static string DefaultApp => EditorPrefs.GetString("kScriptsDefaultApp"); static string DefaultArgument { get; } = "\"$(ProjectPath)\" -g \"$(File)\":$(Line):$(Column)"; string Arguments { get => m_Arguments ?? (m_Arguments = EditorPrefs.GetString(vscode_argument, DefaultArgument)); set { m_Arguments = value; EditorPrefs.SetString(vscode_argument, value); } } static string[] defaultExtensions { get { var customExtensions = new[] { "json", "asmdef", "log" }; return EditorSettings.projectGenerationBuiltinExtensions .Concat(EditorSettings.projectGenerationUserExtensions) .Concat(customExtensions) .Distinct().ToArray(); } } static string[] HandledExtensions { get { return HandledExtensionsString .Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries) .Select(s => s.TrimStart('.', '*')) .ToArray(); } } static string HandledExtensionsString { get => EditorPrefs.GetString(vscode_extension, string.Join(";", defaultExtensions)); set => EditorPrefs.SetString(vscode_extension, value); } public bool TryGetInstallationForPath(string editorPath, out CodeEditor.Installation installation) { var lowerCasePath = editorPath.ToLower(); var filename = Path.GetFileName(lowerCasePath).Replace(" ", ""); var installations = Installations; if (!k_SupportedFileNames.Contains(filename)) { installation = default; return false; } if (!installations.Any()) { installation = new CodeEditor.Installation { Name = "Visual Studio Code", Path = editorPath }; } else { try { installation = installations.First(inst => inst.Path == editorPath); } catch (InvalidOperationException) { installation = new CodeEditor.Installation { Name = "Visual Studio Code", Path = editorPath }; } } return true; } public void OnGUI() { Arguments = EditorGUILayout.TextField("External Script Editor Args", Arguments); if (GUILayout.Button(k_ResetArguments, GUILayout.Width(120))) { Arguments = DefaultArgument; } EditorGUILayout.LabelField("Generate .csproj files for:"); EditorGUI.indentLevel++; SettingsButton(ProjectGenerationFlag.Embedded, "Embedded packages", ""); SettingsButton(ProjectGenerationFlag.Local, "Local packages", ""); SettingsButton(ProjectGenerationFlag.Registry, "Registry packages", ""); SettingsButton(ProjectGenerationFlag.Git, "Git packages", ""); SettingsButton(ProjectGenerationFlag.BuiltIn, "Built-in packages", ""); #if UNITY_2019_3_OR_NEWER SettingsButton(ProjectGenerationFlag.LocalTarBall, "Local tarball", ""); #endif SettingsButton(ProjectGenerationFlag.Unknown, "Packages from unknown sources", ""); RegenerateProjectFiles(); EditorGUI.indentLevel--; HandledExtensionsString = EditorGUILayout.TextField(new GUIContent("Extensions handled: "), HandledExtensionsString); } void RegenerateProjectFiles() { var rect = EditorGUI.IndentedRect(EditorGUILayout.GetControlRect(new GUILayoutOption[] { })); rect.width = 252; if (GUI.Button(rect, "Regenerate project files")) { m_ProjectGeneration.Sync(); } } void SettingsButton(ProjectGenerationFlag preference, string guiMessage, string toolTip) { var prevValue = m_ProjectGeneration.AssemblyNameProvider.ProjectGenerationFlag.HasFlag(preference); var newValue = EditorGUILayout.Toggle(new GUIContent(guiMessage, toolTip), prevValue); if (newValue != prevValue) { m_ProjectGeneration.AssemblyNameProvider.ToggleProjectGeneration(preference); } } public void CreateIfDoesntExist() { if (!m_ProjectGeneration.SolutionExists()) { m_ProjectGeneration.Sync(); } } public void SyncIfNeeded(string[] addedFiles, string[] deletedFiles, string[] movedFiles, string[] movedFromFiles, string[] importedFiles) { m_ProjectGeneration.SyncIfNeeded(addedFiles.Union(deletedFiles).Union(movedFiles).Union(movedFromFiles).ToList(), importedFiles); } public void SyncAll() { AssetDatabase.Refresh(); m_ProjectGeneration.Sync(); } public bool OpenProject(string path, int line, int column) { if (path != "" && (!SupportsExtension(path) || !File.Exists(path))) // Assets - Open C# Project passes empty path here { return false; } if (line == -1) line = 1; if (column == -1) column = 0; string arguments; if (Arguments != DefaultArgument) { arguments = m_ProjectGeneration.ProjectDirectory != path ? CodeEditor.ParseArgument(Arguments, path, line, column) : m_ProjectGeneration.ProjectDirectory; } else { arguments = $@"""{m_ProjectGeneration.ProjectDirectory}"""; if (m_ProjectGeneration.ProjectDirectory != path && path.Length != 0) { arguments += $@" -g ""{path}"":{line}:{column}"; } } if (IsOSX) { return OpenOSX(arguments); } var app = DefaultApp; var process = new Process { StartInfo = new ProcessStartInfo { FileName = app, Arguments = arguments, WindowStyle = app.EndsWith(".cmd", StringComparison.OrdinalIgnoreCase) ? ProcessWindowStyle.Hidden : ProcessWindowStyle.Normal, CreateNoWindow = true, UseShellExecute = true, } }; process.Start(); return true; } static bool OpenOSX(string arguments) { var process = new Process { StartInfo = new ProcessStartInfo { FileName = "open", Arguments = $"-n \"{DefaultApp}\" --args {arguments}", UseShellExecute = true, } }; process.Start(); return true; } static bool SupportsExtension(string path) { var extension = Path.GetExtension(path); if (string.IsNullOrEmpty(extension)) return false; return HandledExtensions.Contains(extension.TrimStart('.')); } public CodeEditor.Installation[] Installations => m_Discoverability.PathCallback(); public VSCodeScriptEditor(IDiscovery discovery, IGenerator projectGeneration) { m_Discoverability = discovery; m_ProjectGeneration = projectGeneration; } static VSCodeScriptEditor() { var editor = new VSCodeScriptEditor(new VSCodeDiscovery(), new ProjectGeneration(Directory.GetParent(Application.dataPath).FullName)); CodeEditor.Register(editor); if (IsVSCodeInstallation(CodeEditor.CurrentEditorInstallation)) { editor.CreateIfDoesntExist(); } } static bool IsVSCodeInstallation(string path) { if (string.IsNullOrEmpty(path)) { return false; } var lowerCasePath = path.ToLower(); var filename = Path .GetFileName(lowerCasePath.Replace('\\', Path.DirectorySeparatorChar).Replace('/', Path.DirectorySeparatorChar)) .Replace(" ", ""); return k_SupportedFileNames.Contains(filename); } public void Initialize(string editorInstallationPath) { } } }