Add example Unity Project

This commit is contained in:
Michał Gdula 2023-04-26 01:55:33 +01:00
parent fda7ff28dd
commit e3acdb9d6b
7122 changed files with 505543 additions and 2 deletions

View file

@ -0,0 +1,90 @@
# Code Editor Package for Visual Studio Code
## [1.2.5] - 2022-02-07
- Introduce OnGeneratedCSProjectFiles, OnGeneratedCSProject and OnGeneratedSlnSolution callbacks.
- Always use forward slash in source paths
- Analyzers use absolute paths
- Ruleset files for roslyn analyzers
- Extra snap search paths on Ubuntu
- Specific c# language version for specific unity versions
- No longer hide .gitignore in VSCode file explorer
## [1.2.3] - 2020-10-23
Remove workaround for VSCode omnisharp (as of https://github.com/OmniSharp/omnisharp-vscode/issues/4113 we no longer need to disable the referenceoutputassemblies).
## [1.2.2] - 2020-09-04
VSC-14 - synchronize solution file when adding new assembly
## [1.2.1] - 2020-05-15
Source filtering adds support for asmref
## [1.2.0] - 2020-03-04
Do not reference projects that has not been generated (case 1211057)
Only open files that exists (case 1188394)
Add individual toggle buttons for generating csprojects for packages
Add support for Roslyn analyzers in project generation through csc.rsp and compiled assembly references
Remove Release build target from csproj and sln
## [1.1.4] - 2020-01-02
Delta project generation, only recompute the csproj files whose script modified.
## [1.1.3] - 2019-10-22
Exe version of vscode will use Normal ProcessWindowStyle while cmd will use Hidden
## [1.1.2] - 2019-08-30
Fixing OSX open command arguments
## [1.1.1] - 2019-08-19
Support for Player Project. Generates specific csproj files containing files, reference, defines,
etc. that will show how the assembly will be compiled for a target platform.
## [1.1.0] - 2019-08-07
Adds support for choosing extensions to be opened with VSCode. This can be done through the GUI in Preferences.
Avoids opening all extensions after the change in core unity.
## [1.0.7] - 2019-05-15
Fix various OSX specific issues.
Generate project on load if they are not generated.
Fix path recognition.
## [1.0.6] - 2019-04-30
Ensure asset database is refreshed when generating csproj and solution files.
## [1.0.5] - 2019-04-27
Add support for generating all csproj files.
## [1.0.4] - 2019-04-18
Fix relative package paths.
Fix opening editor on mac.
Add %LOCALAPPDATA%/Programs to the path of install paths.
## [1.0.3] - 2019-01-01
### This is the first release of *Unity Package vscode_editor*.
Using the newly created api to integrate Visual Studio Code with Unity.

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 4ddcdc3816429494a8bea67e973875f7
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,6 @@
# Contributing
## All contributions are subject to the [Unity Contribution Agreement(UCA)](https://unity3d.com/legal/licenses/Unity_Contribution_Agreement)
By making a pull request, you are confirming agreement to the terms and conditions of the UCA, including that your Contributions are your original creation and that you have complete right and authority to make your Contributions.
## Once you have a change ready following these ground rules. Simply make a pull request

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: fcb9be00baf924c4183fc0313e6185c5
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 58628227479c34542ac8c5193ccced84
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c779d3735d950f341ba35154e8b3234b
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,165 @@
using System;
using System.Collections.Generic;
using System.Linq;
using UnityEditor;
using UnityEditor.Compilation;
using UnityEditor.PackageManager;
namespace VSCodeEditor
{
public interface IAssemblyNameProvider
{
string[] ProjectSupportedExtensions { get; }
ProjectGenerationFlag ProjectGenerationFlag { get; }
string GetAssemblyNameFromScriptPath(string path);
IEnumerable<Assembly> GetAssemblies(Func<string, bool> shouldFileBePartOfSolution);
IEnumerable<string> GetAllAssetPaths();
IEnumerable<string> GetRoslynAnalyzerPaths();
UnityEditor.PackageManager.PackageInfo FindForAssetPath(string assetPath);
ResponseFileData ParseResponseFile(string responseFilePath, string projectDirectory, string[] systemReferenceDirectories);
bool IsInternalizedPackagePath(string path);
void ToggleProjectGeneration(ProjectGenerationFlag preference);
}
internal interface IPackageInfoCache{
void ResetPackageInfoCache();
}
internal class AssemblyNameProvider : IAssemblyNameProvider, IPackageInfoCache
{
private readonly Dictionary<string, UnityEditor.PackageManager.PackageInfo> m_PackageInfoCache = new Dictionary<string, UnityEditor.PackageManager.PackageInfo>();
ProjectGenerationFlag m_ProjectGenerationFlag = (ProjectGenerationFlag)EditorPrefs.GetInt("unity_project_generation_flag", 0);
public string[] ProjectSupportedExtensions => EditorSettings.projectGenerationUserExtensions;
public ProjectGenerationFlag ProjectGenerationFlag
{
get => m_ProjectGenerationFlag;
private set
{
EditorPrefs.SetInt("unity_project_generation_flag", (int)value);
m_ProjectGenerationFlag = value;
}
}
public string GetAssemblyNameFromScriptPath(string path)
{
return CompilationPipeline.GetAssemblyNameFromScriptPath(path);
}
public IEnumerable<Assembly> GetAssemblies(Func<string, bool> shouldFileBePartOfSolution)
{
return CompilationPipeline.GetAssemblies()
.Where(i => 0 < i.sourceFiles.Length && i.sourceFiles.Any(shouldFileBePartOfSolution));
}
public IEnumerable<string> GetAllAssetPaths()
{
return AssetDatabase.GetAllAssetPaths();
}
private static string ResolvePotentialParentPackageAssetPath(string assetPath)
{
const string packagesPrefix = "packages/";
if (!assetPath.StartsWith(packagesPrefix, StringComparison.OrdinalIgnoreCase))
{
return null;
}
var followupSeparator = assetPath.IndexOf('/', packagesPrefix.Length);
if (followupSeparator == -1)
{
return assetPath.ToLowerInvariant();
}
return assetPath.Substring(0, followupSeparator).ToLowerInvariant();
}
public void ResetPackageInfoCache()
{
m_PackageInfoCache.Clear();
}
public UnityEditor.PackageManager.PackageInfo FindForAssetPath(string assetPath)
{
var parentPackageAssetPath = ResolvePotentialParentPackageAssetPath(assetPath);
if (parentPackageAssetPath == null)
{
return null;
}
if (m_PackageInfoCache.TryGetValue(parentPackageAssetPath, out var cachedPackageInfo))
{
return cachedPackageInfo;
}
var result = UnityEditor.PackageManager.PackageInfo.FindForAssetPath(parentPackageAssetPath);
m_PackageInfoCache[parentPackageAssetPath] = result;
return result;
}
public ResponseFileData ParseResponseFile(string responseFilePath, string projectDirectory, string[] systemReferenceDirectories)
{
return CompilationPipeline.ParseResponseFile(
responseFilePath,
projectDirectory,
systemReferenceDirectories
);
}
public bool IsInternalizedPackagePath(string path)
{
if (string.IsNullOrWhiteSpace(path))
{
return false;
}
var packageInfo = FindForAssetPath(path);
if (packageInfo == null)
{
return false;
}
var packageSource = packageInfo.source;
switch (packageSource)
{
case PackageSource.Embedded:
return !ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.Embedded);
case PackageSource.Registry:
return !ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.Registry);
case PackageSource.BuiltIn:
return !ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.BuiltIn);
case PackageSource.Unknown:
return !ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.Unknown);
case PackageSource.Local:
return !ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.Local);
case PackageSource.Git:
return !ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.Git);
#if UNITY_2019_3_OR_NEWER
case PackageSource.LocalTarball:
return !ProjectGenerationFlag.HasFlag(ProjectGenerationFlag.LocalTarBall);
#endif
}
return false;
}
public void ToggleProjectGeneration(ProjectGenerationFlag preference)
{
if (ProjectGenerationFlag.HasFlag(preference))
{
ProjectGenerationFlag ^= preference;
}
else
{
ProjectGenerationFlag |= preference;
}
}
public IEnumerable<string> GetRoslynAnalyzerPaths()
{
return PluginImporter.GetAllImporters()
.Where(i => !i.isNativePlugin && AssetDatabase.GetLabels(i).SingleOrDefault(l => l == "RoslynAnalyzer") != null)
.Select(i => i.assetPath);
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1d93ffb668978f7488211a331977b73b
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,60 @@
using System;
using System.IO;
using System.Security;
using System.Text;
namespace VSCodeEditor
{
public interface IFileIO
{
bool Exists(string fileName);
string ReadAllText(string fileName);
void WriteAllText(string fileName, string content);
void CreateDirectory(string pathName);
string EscapedRelativePathFor(string file, string projectDirectory);
}
class FileIOProvider : IFileIO
{
public bool Exists(string fileName)
{
return File.Exists(fileName);
}
public string ReadAllText(string fileName)
{
return File.ReadAllText(fileName);
}
public void WriteAllText(string fileName, string content)
{
File.WriteAllText(fileName, content, Encoding.UTF8);
}
public void CreateDirectory(string pathName)
{
Directory.CreateDirectory(pathName);
}
public string EscapedRelativePathFor(string file, string projectDirectory)
{
var projectDir = Path.GetFullPath(projectDirectory);
// We have to normalize the path, because the PackageManagerRemapper assumes
// dir seperators will be os specific.
var absolutePath = Path.GetFullPath(file.NormalizePath());
var path = SkipPathPrefix(absolutePath, projectDir);
return SecurityElement.Escape(path);
}
private static string SkipPathPrefix(string path, string prefix)
{
return path.StartsWith($@"{prefix}{Path.DirectorySeparatorChar}", StringComparison.Ordinal)
? path.Substring(prefix.Length + 1)
: path;
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: eb221cf55b3544646b0c3b6bc790080f
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,21 @@
namespace VSCodeEditor
{
public interface IGUIDGenerator
{
string ProjectGuid(string projectName, string assemblyName);
string SolutionGuid(string projectName, string extension);
}
class GUIDProvider : IGUIDGenerator
{
public string ProjectGuid(string projectName, string assemblyName)
{
return SolutionGuidGenerator.GuidForProject(projectName + assemblyName);
}
public string SolutionGuid(string projectName, string extension)
{
return SolutionGuidGenerator.GuidForSolution(projectName, extension); // GetExtensionOfSourceFiles(assembly.sourceFiles)
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: e58bd3cca6475e54b93632bb6837aeea
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,829 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security;
using System.Security.Cryptography;
using SR = System.Reflection;
using System.Text;
using UnityEditor;
using UnityEditor.Compilation;
using UnityEngine;
using UnityEngine.Profiling;
namespace VSCodeEditor
{
public interface IGenerator
{
bool SyncIfNeeded(List<string> affectedFiles, string[] reimportedFiles);
void Sync();
string SolutionFile();
string ProjectDirectory { get; }
IAssemblyNameProvider AssemblyNameProvider { get; }
void GenerateAll(bool generateAll);
bool SolutionExists();
}
public class ProjectGeneration : IGenerator
{
enum ScriptingLanguage
{
None,
CSharp
}
public static readonly string MSBuildNamespaceUri = "http://schemas.microsoft.com/developer/msbuild/2003";
const string k_WindowsNewline = "\r\n";
const string k_SettingsJson = @"{
""files.exclude"":
{
""**/.DS_Store"":true,
""**/.git"":true,
""**/.gitmodules"":true,
""**/*.booproj"":true,
""**/*.pidb"":true,
""**/*.suo"":true,
""**/*.user"":true,
""**/*.userprefs"":true,
""**/*.unityproj"":true,
""**/*.dll"":true,
""**/*.exe"":true,
""**/*.pdf"":true,
""**/*.mid"":true,
""**/*.midi"":true,
""**/*.wav"":true,
""**/*.gif"":true,
""**/*.ico"":true,
""**/*.jpg"":true,
""**/*.jpeg"":true,
""**/*.png"":true,
""**/*.psd"":true,
""**/*.tga"":true,
""**/*.tif"":true,
""**/*.tiff"":true,
""**/*.3ds"":true,
""**/*.3DS"":true,
""**/*.fbx"":true,
""**/*.FBX"":true,
""**/*.lxo"":true,
""**/*.LXO"":true,
""**/*.ma"":true,
""**/*.MA"":true,
""**/*.obj"":true,
""**/*.OBJ"":true,
""**/*.asset"":true,
""**/*.cubemap"":true,
""**/*.flare"":true,
""**/*.mat"":true,
""**/*.meta"":true,
""**/*.prefab"":true,
""**/*.unity"":true,
""build/"":true,
""Build/"":true,
""Library/"":true,
""library/"":true,
""obj/"":true,
""Obj/"":true,
""ProjectSettings/"":true,
""temp/"":true,
""Temp/"":true
}
}";
/// <summary>
/// Map source extensions to ScriptingLanguages
/// </summary>
static readonly Dictionary<string, ScriptingLanguage> k_BuiltinSupportedExtensions = new Dictionary<string, ScriptingLanguage>
{
{ "cs", ScriptingLanguage.CSharp },
{ "uxml", ScriptingLanguage.None },
{ "uss", ScriptingLanguage.None },
{ "shader", ScriptingLanguage.None },
{ "compute", ScriptingLanguage.None },
{ "cginc", ScriptingLanguage.None },
{ "hlsl", ScriptingLanguage.None },
{ "glslinc", ScriptingLanguage.None },
{ "template", ScriptingLanguage.None },
{ "raytrace", ScriptingLanguage.None }
};
readonly string m_SolutionProjectEntryTemplate = string.Join("\r\n", @"Project(""{{{0}}}"") = ""{1}"", ""{2}"", ""{{{3}}}""", @"EndProject").Replace(" ", "\t");
readonly string m_SolutionProjectConfigurationTemplate = string.Join("\r\n", @" {{{0}}}.Debug|Any CPU.ActiveCfg = Debug|Any CPU", @" {{{0}}}.Debug|Any CPU.Build.0 = Debug|Any CPU").Replace(" ", "\t");
static readonly string[] k_ReimportSyncExtensions = { ".dll", ".asmdef" };
string[] m_ProjectSupportedExtensions = Array.Empty<string>();
const string k_TargetLanguageVersion = "latest";
public string ProjectDirectory { get; }
IAssemblyNameProvider IGenerator.AssemblyNameProvider => m_AssemblyNameProvider;
public void GenerateAll(bool generateAll)
{
m_AssemblyNameProvider.ToggleProjectGeneration(
ProjectGenerationFlag.BuiltIn
| ProjectGenerationFlag.Embedded
| ProjectGenerationFlag.Git
| ProjectGenerationFlag.Local
#if UNITY_2019_3_OR_NEWER
| ProjectGenerationFlag.LocalTarBall
#endif
| ProjectGenerationFlag.PlayerAssemblies
| ProjectGenerationFlag.Registry
| ProjectGenerationFlag.Unknown);
}
readonly string m_ProjectName;
readonly IAssemblyNameProvider m_AssemblyNameProvider;
readonly IFileIO m_FileIOProvider;
readonly IGUIDGenerator m_GUIDProvider;
const string k_ToolsVersion = "4.0";
const string k_ProductVersion = "10.0.20506";
const string k_BaseDirectory = ".";
const string k_TargetFrameworkVersion = "v4.7.1";
public ProjectGeneration(string tempDirectory)
: this(tempDirectory, new AssemblyNameProvider(), new FileIOProvider(), new GUIDProvider()) { }
public ProjectGeneration(string tempDirectory, IAssemblyNameProvider assemblyNameProvider, IFileIO fileIO, IGUIDGenerator guidGenerator)
{
ProjectDirectory = tempDirectory.NormalizePath();
m_ProjectName = Path.GetFileName(ProjectDirectory);
m_AssemblyNameProvider = assemblyNameProvider;
m_FileIOProvider = fileIO;
m_GUIDProvider = guidGenerator;
}
/// <summary>
/// Syncs the scripting solution if any affected files are relevant.
/// </summary>
/// <returns>
/// Whether the solution was synced.
/// </returns>
/// <param name='affectedFiles'>
/// A set of files whose status has changed
/// </param>
/// <param name="reimportedFiles">
/// A set of files that got reimported
/// </param>
public bool SyncIfNeeded(List<string> affectedFiles, string[] reimportedFiles)
{
Profiler.BeginSample("SolutionSynchronizerSync");
SetupProjectSupportedExtensions();
if (!HasFilesBeenModified(affectedFiles, reimportedFiles))
{
Profiler.EndSample();
return false;
}
var assemblies = m_AssemblyNameProvider.GetAssemblies(ShouldFileBePartOfSolution);
var allProjectAssemblies = RelevantAssembliesForMode(assemblies).ToList();
SyncSolution(allProjectAssemblies);
var allAssetProjectParts = GenerateAllAssetProjectParts();
var affectedNames = affectedFiles.Select(asset => m_AssemblyNameProvider.GetAssemblyNameFromScriptPath(asset)).Where(name => !string.IsNullOrWhiteSpace(name)).Select(name => name.Split(new [] {".dll"}, StringSplitOptions.RemoveEmptyEntries)[0]);
var reimportedNames = reimportedFiles.Select(asset => m_AssemblyNameProvider.GetAssemblyNameFromScriptPath(asset)).Where(name => !string.IsNullOrWhiteSpace(name)).Select(name => name.Split(new [] {".dll"}, StringSplitOptions.RemoveEmptyEntries)[0]);
var affectedAndReimported = new HashSet<string>(affectedNames.Concat(reimportedNames));
foreach (var assembly in allProjectAssemblies)
{
if (!affectedAndReimported.Contains(assembly.name))
continue;
SyncProject(assembly, allAssetProjectParts, ParseResponseFileData(assembly));
}
Profiler.EndSample();
return true;
}
bool HasFilesBeenModified(List<string> affectedFiles, string[] reimportedFiles)
{
return affectedFiles.Any(ShouldFileBePartOfSolution) || reimportedFiles.Any(ShouldSyncOnReimportedAsset);
}
static bool ShouldSyncOnReimportedAsset(string asset)
{
return k_ReimportSyncExtensions.Contains(new FileInfo(asset).Extension);
}
private static IEnumerable<SR.MethodInfo> GetPostProcessorCallbacks(string name)
{
return TypeCache
.GetTypesDerivedFrom<AssetPostprocessor>()
.Select(t => t.GetMethod(name, SR.BindingFlags.Public | SR.BindingFlags.NonPublic | SR.BindingFlags.Static))
.Where(m => m != null);
}
static void OnGeneratedCSProjectFiles()
{
foreach (var method in GetPostProcessorCallbacks(nameof(OnGeneratedCSProjectFiles)))
{
method.Invoke(null, Array.Empty<object>());
}
}
private static string InvokeAssetPostProcessorGenerationCallbacks(string name, string path, string content)
{
foreach (var method in GetPostProcessorCallbacks(name))
{
var args = new[] { path, content };
var returnValue = method.Invoke(null, args);
if (method.ReturnType == typeof(string))
{
// We want to chain content update between invocations
content = (string)returnValue;
}
}
return content;
}
private static string OnGeneratedCSProject(string path, string content)
{
return InvokeAssetPostProcessorGenerationCallbacks(nameof(OnGeneratedCSProject), path, content);
}
private static string OnGeneratedSlnSolution(string path, string content)
{
return InvokeAssetPostProcessorGenerationCallbacks(nameof(OnGeneratedSlnSolution), path, content);
}
public void Sync()
{
SetupProjectSupportedExtensions();
GenerateAndWriteSolutionAndProjects();
OnGeneratedCSProjectFiles();
}
public bool SolutionExists()
{
return m_FileIOProvider.Exists(SolutionFile());
}
void SetupProjectSupportedExtensions()
{
m_ProjectSupportedExtensions = m_AssemblyNameProvider.ProjectSupportedExtensions;
}
bool ShouldFileBePartOfSolution(string file)
{
// Exclude files coming from packages except if they are internalized.
if (m_AssemblyNameProvider.IsInternalizedPackagePath(file))
{
return false;
}
return HasValidExtension(file);
}
bool HasValidExtension(string file)
{
string extension = Path.GetExtension(file);
// Dll's are not scripts but still need to be included..
if (extension == ".dll")
return true;
if (file.ToLower().EndsWith(".asmdef"))
return true;
return IsSupportedExtension(extension);
}
bool IsSupportedExtension(string extension)
{
extension = extension.TrimStart('.');
if (k_BuiltinSupportedExtensions.ContainsKey(extension))
return true;
if (m_ProjectSupportedExtensions.Contains(extension))
return true;
return false;
}
static ScriptingLanguage ScriptingLanguageFor(Assembly assembly)
{
return ScriptingLanguageFor(GetExtensionOfSourceFiles(assembly.sourceFiles));
}
static string GetExtensionOfSourceFiles(string[] files)
{
return files.Length > 0 ? GetExtensionOfSourceFile(files[0]) : "NA";
}
static string GetExtensionOfSourceFile(string file)
{
var ext = Path.GetExtension(file).ToLower();
ext = ext.Substring(1); //strip dot
return ext;
}
static ScriptingLanguage ScriptingLanguageFor(string extension)
{
return k_BuiltinSupportedExtensions.TryGetValue(extension.TrimStart('.'), out var result)
? result
: ScriptingLanguage.None;
}
public void GenerateAndWriteSolutionAndProjects()
{
// Only synchronize assemblies that have associated source files and ones that we actually want in the project.
// This also filters out DLLs coming from .asmdef files in packages.
var assemblies = m_AssemblyNameProvider.GetAssemblies(ShouldFileBePartOfSolution).ToArray();
var allAssetProjectParts = GenerateAllAssetProjectParts();
SyncSolution(assemblies);
var allProjectAssemblies = RelevantAssembliesForMode(assemblies).ToList();
foreach (Assembly assembly in allProjectAssemblies)
{
var responseFileData = ParseResponseFileData(assembly);
SyncProject(assembly, allAssetProjectParts, responseFileData);
}
WriteVSCodeSettingsFiles();
}
List<ResponseFileData> ParseResponseFileData(Assembly assembly)
{
var systemReferenceDirectories = CompilationPipeline.GetSystemAssemblyDirectories(assembly.compilerOptions.ApiCompatibilityLevel);
Dictionary<string, ResponseFileData> responseFilesData = assembly.compilerOptions.ResponseFiles.ToDictionary(x => x, x => m_AssemblyNameProvider.ParseResponseFile(
x,
ProjectDirectory,
systemReferenceDirectories
));
Dictionary<string, ResponseFileData> responseFilesWithErrors = responseFilesData.Where(x => x.Value.Errors.Any())
.ToDictionary(x => x.Key, x => x.Value);
if (responseFilesWithErrors.Any())
{
foreach (var error in responseFilesWithErrors)
foreach (var valueError in error.Value.Errors)
{
Debug.LogError($"{error.Key} Parse Error : {valueError}");
}
}
return responseFilesData.Select(x => x.Value).ToList();
}
Dictionary<string, string> GenerateAllAssetProjectParts()
{
Dictionary<string, StringBuilder> stringBuilders = new Dictionary<string, StringBuilder>();
foreach (string asset in m_AssemblyNameProvider.GetAllAssetPaths())
{
// Exclude files coming from packages except if they are internalized.
// TODO: We need assets from the assembly API
if (m_AssemblyNameProvider.IsInternalizedPackagePath(asset))
{
continue;
}
string extension = Path.GetExtension(asset);
if (IsSupportedExtension(extension) && ScriptingLanguage.None == ScriptingLanguageFor(extension))
{
// Find assembly the asset belongs to by adding script extension and using compilation pipeline.
var assemblyName = m_AssemblyNameProvider.GetAssemblyNameFromScriptPath(asset);
if (string.IsNullOrEmpty(assemblyName))
{
continue;
}
assemblyName = Path.GetFileNameWithoutExtension(assemblyName);
if (!stringBuilders.TryGetValue(assemblyName, out var projectBuilder))
{
projectBuilder = new StringBuilder();
stringBuilders[assemblyName] = projectBuilder;
}
projectBuilder.Append(" <None Include=\"").Append(m_FileIOProvider.EscapedRelativePathFor(asset, ProjectDirectory)).Append("\" />").Append(k_WindowsNewline);
}
}
var result = new Dictionary<string, string>();
foreach (var entry in stringBuilders)
result[entry.Key] = entry.Value.ToString();
return result;
}
void SyncProject(
Assembly assembly,
Dictionary<string, string> allAssetsProjectParts,
List<ResponseFileData> responseFilesData)
{
SyncProjectFileIfNotChanged(ProjectFile(assembly), ProjectText(assembly, allAssetsProjectParts, responseFilesData));
}
void SyncProjectFileIfNotChanged(string path, string newContents)
{
if (Path.GetExtension(path) == ".csproj")
{
newContents = OnGeneratedCSProject(path, newContents);
}
SyncFileIfNotChanged(path, newContents);
}
void SyncSolutionFileIfNotChanged(string path, string newContents)
{
newContents = OnGeneratedSlnSolution(path, newContents);
SyncFileIfNotChanged(path, newContents);
}
void SyncFileIfNotChanged(string filename, string newContents)
{
try
{
if (m_FileIOProvider.Exists(filename) && newContents == m_FileIOProvider.ReadAllText(filename))
{
return;
}
}
catch (Exception exception)
{
Debug.LogException(exception);
}
m_FileIOProvider.WriteAllText(filename, newContents);
}
string ProjectText(
Assembly assembly,
Dictionary<string, string> allAssetsProjectParts,
List<ResponseFileData> responseFilesData)
{
var projectBuilder = new StringBuilder();
ProjectHeader(assembly, responseFilesData, projectBuilder);
var references = new List<string>();
foreach (string file in assembly.sourceFiles)
{
var fullFile = m_FileIOProvider.EscapedRelativePathFor(file, ProjectDirectory);
projectBuilder.Append(" <Compile Include=\"").Append(fullFile).Append("\" />").Append(k_WindowsNewline);
}
// Append additional non-script files that should be included in project generation.
if (allAssetsProjectParts.TryGetValue(assembly.name, out var additionalAssetsForProject))
projectBuilder.Append(additionalAssetsForProject);
var responseRefs = responseFilesData.SelectMany(x => x.FullPathReferences.Select(r => r));
var internalAssemblyReferences = assembly.assemblyReferences
.Where(i => !i.sourceFiles.Any(ShouldFileBePartOfSolution)).Select(i => i.outputPath);
var allReferences =
assembly.compiledAssemblyReferences
.Union(responseRefs)
.Union(references)
.Union(internalAssemblyReferences);
foreach (var reference in allReferences)
{
string fullReference = Path.IsPathRooted(reference) ? reference : Path.Combine(ProjectDirectory, reference);
AppendReference(fullReference, projectBuilder);
}
if (0 < assembly.assemblyReferences.Length)
{
projectBuilder.Append(" </ItemGroup>").Append(k_WindowsNewline);
projectBuilder.Append(" <ItemGroup>").Append(k_WindowsNewline);
foreach (Assembly reference in assembly.assemblyReferences.Where(i => i.sourceFiles.Any(ShouldFileBePartOfSolution)))
{
projectBuilder.Append(" <ProjectReference Include=\"").Append(reference.name).Append(GetProjectExtension()).Append("\">").Append(k_WindowsNewline);
projectBuilder.Append(" <Project>{").Append(ProjectGuid(reference.name)).Append("}</Project>").Append(k_WindowsNewline);
projectBuilder.Append(" <Name>").Append(reference.name).Append("</Name>").Append(k_WindowsNewline);
projectBuilder.Append(" </ProjectReference>").Append(k_WindowsNewline);
}
}
projectBuilder.Append(ProjectFooter());
return projectBuilder.ToString();
}
static void AppendReference(string fullReference, StringBuilder projectBuilder)
{
var escapedFullPath = SecurityElement.Escape(fullReference);
escapedFullPath = escapedFullPath.NormalizePath();
projectBuilder.Append(" <Reference Include=\"").Append(Path.GetFileNameWithoutExtension(escapedFullPath)).Append("\">").Append(k_WindowsNewline);
projectBuilder.Append(" <HintPath>").Append(escapedFullPath).Append("</HintPath>").Append(k_WindowsNewline);
projectBuilder.Append(" </Reference>").Append(k_WindowsNewline);
}
public string ProjectFile(Assembly assembly)
{
var fileBuilder = new StringBuilder(assembly.name);
fileBuilder.Append(".csproj");
return Path.Combine(ProjectDirectory, fileBuilder.ToString());
}
public string SolutionFile()
{
return Path.Combine(ProjectDirectory, $"{m_ProjectName}.sln");
}
private void ProjectHeader(
Assembly assembly,
List<ResponseFileData> responseFilesData,
StringBuilder builder
)
{
var otherArguments = GetOtherArgumentsFromResponseFilesData(responseFilesData);
GetProjectHeaderTemplate(
builder,
ProjectGuid(assembly.name),
assembly.name,
string.Join(";", new[] { "DEBUG", "TRACE" }.Concat(assembly.defines).Concat(responseFilesData.SelectMany(x => x.Defines)).Concat(EditorUserBuildSettings.activeScriptCompilationDefines).Distinct().ToArray()),
GenerateLangVersion(otherArguments["langversion"], assembly),
assembly.compilerOptions.AllowUnsafeCode | responseFilesData.Any(x => x.Unsafe),
GenerateAnalyserItemGroup(RetrieveRoslynAnalyzers(assembly, otherArguments)),
GenerateRoslynAnalyzerRulesetPath(assembly, otherArguments)
);
}
private static string GenerateLangVersion(IEnumerable<string> langVersionList, Assembly assembly)
{
var langVersion = langVersionList.FirstOrDefault();
if (!string.IsNullOrWhiteSpace(langVersion))
return langVersion;
#if UNITY_2020_2_OR_NEWER
return assembly.compilerOptions.LanguageVersion;
#else
return k_TargetLanguageVersion;
#endif
}
private static string GenerateRoslynAnalyzerRulesetPath(Assembly assembly, ILookup<string, string> otherResponseFilesData)
{
#if UNITY_2020_2_OR_NEWER
return GenerateAnalyserRuleSet(otherResponseFilesData["ruleset"].Append(assembly.compilerOptions.RoslynAnalyzerRulesetPath).Where(a => !string.IsNullOrEmpty(a)).Distinct().Select(x => MakeAbsolutePath(x).NormalizePath()).ToArray());
#else
return GenerateAnalyserRuleSet(otherResponseFilesData["ruleset"].Distinct().Select(x => MakeAbsolutePath(x).NormalizePath()).ToArray());
#endif
}
private static string GenerateAnalyserRuleSet(string[] paths)
{
return paths.Length == 0
? string.Empty
: $"{Environment.NewLine}{string.Join(Environment.NewLine, paths.Select(a => $" <CodeAnalysisRuleSet>{a}</CodeAnalysisRuleSet>"))}";
}
private static string MakeAbsolutePath(string path)
{
return Path.IsPathRooted(path) ? path : Path.GetFullPath(path);
}
private static ILookup<string, string> GetOtherArgumentsFromResponseFilesData(List<ResponseFileData> responseFilesData)
{
var paths = responseFilesData.SelectMany(x =>
{
return x.OtherArguments.Where(a => a.StartsWith("/") || a.StartsWith("-"))
.Select(b =>
{
var index = b.IndexOf(":", StringComparison.Ordinal);
if (index > 0 && b.Length > index)
{
var key = b.Substring(1, index - 1);
return new KeyValuePair<string, string>(key, b.Substring(index + 1));
}
const string warnaserror = "warnaserror";
return b.Substring(1).StartsWith(warnaserror)
? new KeyValuePair<string, string>(warnaserror, b.Substring(warnaserror.Length + 1))
: default;
});
})
.Distinct()
.ToLookup(o => o.Key, pair => pair.Value);
return paths;
}
string[] RetrieveRoslynAnalyzers(Assembly assembly, ILookup<string, string> otherArguments)
{
#if UNITY_2020_2_OR_NEWER
return otherArguments["analyzer"].Concat(otherArguments["a"])
.SelectMany(x=>x.Split(';'))
#if !ROSLYN_ANALYZER_FIX
.Concat(m_AssemblyNameProvider.GetRoslynAnalyzerPaths())
#else
.Concat(assembly.compilerOptions.RoslynAnalyzerDllPaths)
#endif
.Select(MakeAbsolutePath)
.Distinct()
.ToArray();
#else
return otherArguments["analyzer"].Concat(otherArguments["a"])
.SelectMany(x=>x.Split(';'))
.Distinct()
.Select(MakeAbsolutePath)
.ToArray();
#endif
}
static string GenerateAnalyserItemGroup(string[] paths)
{
// <ItemGroup>
// <Analyzer Include="..\packages\Comments_analyser.1.0.6626.21356\analyzers\dotnet\cs\Comments_analyser.dll" />
// <Analyzer Include="..\packages\UnityEngineAnalyzer.1.0.0.0\analyzers\dotnet\cs\UnityEngineAnalyzer.dll" />
// </ItemGroup>
if (paths.Length == 0)
{
return string.Empty;
}
var analyserBuilder = new StringBuilder();
analyserBuilder.Append(" <ItemGroup>").Append(k_WindowsNewline);
foreach (var path in paths)
{
analyserBuilder.Append($" <Analyzer Include=\"{path.NormalizePath()}\" />").Append(k_WindowsNewline);
}
analyserBuilder.Append(" </ItemGroup>").Append(k_WindowsNewline);
return analyserBuilder.ToString();
}
static string GetSolutionText()
{
return string.Join("\r\n", @"", @"Microsoft Visual Studio Solution File, Format Version {0}", @"# Visual Studio {1}", @"{2}", @"Global", @" GlobalSection(SolutionConfigurationPlatforms) = preSolution", @" Debug|Any CPU = Debug|Any CPU", @" EndGlobalSection", @" GlobalSection(ProjectConfigurationPlatforms) = postSolution", @"{3}", @" EndGlobalSection", @" GlobalSection(SolutionProperties) = preSolution", @" HideSolutionNode = FALSE", @" EndGlobalSection", @"EndGlobal", @"").Replace(" ", "\t");
}
static string GetProjectFooterTemplate()
{
return string.Join("\r\n", @" </ItemGroup>", @" <Import Project=""$(MSBuildToolsPath)\Microsoft.CSharp.targets"" />", @" <!-- To modify your build process, add your task inside one of the targets below and uncomment it.", @" Other similar extension points exist, see Microsoft.Common.targets.", @" <Target Name=""BeforeBuild"">", @" </Target>", @" <Target Name=""AfterBuild"">", @" </Target>", @" -->", @"</Project>", @"");
}
static void GetProjectHeaderTemplate(
StringBuilder builder,
string assemblyGUID,
string assemblyName,
string defines,
string langVersion,
bool allowUnsafe,
string analyzerBlock,
string rulesetBlock
)
{
builder.Append(@"<?xml version=""1.0"" encoding=""utf-8""?>").Append(k_WindowsNewline);
builder.Append(@"<Project ToolsVersion=""").Append(k_ToolsVersion).Append(@""" DefaultTargets=""Build"" xmlns=""").Append(MSBuildNamespaceUri).Append(@""">").Append(k_WindowsNewline);
builder.Append(@" <PropertyGroup>").Append(k_WindowsNewline);
builder.Append(@" <LangVersion>").Append(langVersion).Append("</LangVersion>").Append(k_WindowsNewline);
builder.Append(@" </PropertyGroup>").Append(k_WindowsNewline);
builder.Append(@" <PropertyGroup>").Append(k_WindowsNewline);
builder.Append(@" <Configuration Condition="" '$(Configuration)' == '' "">Debug</Configuration>").Append(k_WindowsNewline);
builder.Append(@" <Platform Condition="" '$(Platform)' == '' "">AnyCPU</Platform>").Append(k_WindowsNewline);
builder.Append(@" <ProductVersion>").Append(k_ProductVersion).Append("</ProductVersion>").Append(k_WindowsNewline);
builder.Append(@" <SchemaVersion>2.0</SchemaVersion>").Append(k_WindowsNewline);
builder.Append(@" <RootNamespace>").Append(EditorSettings.projectGenerationRootNamespace).Append("</RootNamespace>").Append(k_WindowsNewline);
builder.Append(@" <ProjectGuid>{").Append(assemblyGUID).Append("}</ProjectGuid>").Append(k_WindowsNewline);
builder.Append(@" <OutputType>Library</OutputType>").Append(k_WindowsNewline);
builder.Append(@" <AppDesignerFolder>Properties</AppDesignerFolder>").Append(k_WindowsNewline);
builder.Append(@" <AssemblyName>").Append(assemblyName).Append("</AssemblyName>").Append(k_WindowsNewline);
builder.Append(@" <TargetFrameworkVersion>").Append(k_TargetFrameworkVersion).Append("</TargetFrameworkVersion>").Append(k_WindowsNewline);
builder.Append(@" <FileAlignment>512</FileAlignment>").Append(k_WindowsNewline);
builder.Append(@" <BaseDirectory>").Append(k_BaseDirectory).Append("</BaseDirectory>").Append(k_WindowsNewline);
builder.Append(@" </PropertyGroup>").Append(k_WindowsNewline);
builder.Append(@" <PropertyGroup Condition="" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "">").Append(k_WindowsNewline);
builder.Append(@" <DebugSymbols>true</DebugSymbols>").Append(k_WindowsNewline);
builder.Append(@" <DebugType>full</DebugType>").Append(k_WindowsNewline);
builder.Append(@" <Optimize>false</Optimize>").Append(k_WindowsNewline);
builder.Append(@" <OutputPath>Temp\bin\Debug\</OutputPath>").Append(k_WindowsNewline);
builder.Append(@" <DefineConstants>").Append(defines).Append("</DefineConstants>").Append(k_WindowsNewline);
builder.Append(@" <ErrorReport>prompt</ErrorReport>").Append(k_WindowsNewline);
builder.Append(@" <WarningLevel>4</WarningLevel>").Append(k_WindowsNewline);
builder.Append(@" <NoWarn>0169</NoWarn>").Append(k_WindowsNewline);
builder.Append(@" <AllowUnsafeBlocks>").Append(allowUnsafe).Append("</AllowUnsafeBlocks>").Append(k_WindowsNewline);
builder.Append(@" </PropertyGroup>").Append(k_WindowsNewline);
builder.Append(@" <PropertyGroup>").Append(k_WindowsNewline);
builder.Append(@" <NoConfig>true</NoConfig>").Append(k_WindowsNewline);
builder.Append(@" <NoStdLib>true</NoStdLib>").Append(k_WindowsNewline);
builder.Append(@" <AddAdditionalExplicitAssemblyReferences>false</AddAdditionalExplicitAssemblyReferences>").Append(k_WindowsNewline);
builder.Append(@" <ImplicitlyExpandNETStandardFacades>false</ImplicitlyExpandNETStandardFacades>").Append(k_WindowsNewline);
builder.Append(@" <ImplicitlyExpandDesignTimeFacades>false</ImplicitlyExpandDesignTimeFacades>").Append(k_WindowsNewline);
builder.Append(rulesetBlock);
builder.Append(@" </PropertyGroup>").Append(k_WindowsNewline);
builder.Append(analyzerBlock);
builder.Append(@" <ItemGroup>").Append(k_WindowsNewline);
}
void SyncSolution(IEnumerable<Assembly> assemblies)
{
SyncSolutionFileIfNotChanged(SolutionFile(), SolutionText(assemblies));
}
string SolutionText(IEnumerable<Assembly> assemblies)
{
var fileversion = "11.00";
var vsversion = "2010";
var relevantAssemblies = RelevantAssembliesForMode(assemblies);
string projectEntries = GetProjectEntries(relevantAssemblies);
string projectConfigurations = string.Join(k_WindowsNewline, relevantAssemblies.Select(i => GetProjectActiveConfigurations(ProjectGuid(i.name))).ToArray());
return string.Format(GetSolutionText(), fileversion, vsversion, projectEntries, projectConfigurations);
}
static IEnumerable<Assembly> RelevantAssembliesForMode(IEnumerable<Assembly> assemblies)
{
return assemblies.Where(i => ScriptingLanguage.CSharp == ScriptingLanguageFor(i));
}
/// <summary>
/// Get a Project("{guid}") = "MyProject", "MyProject.csproj", "{projectguid}"
/// entry for each relevant language
/// </summary>
string GetProjectEntries(IEnumerable<Assembly> assemblies)
{
var projectEntries = assemblies.Select(i => string.Format(
m_SolutionProjectEntryTemplate,
SolutionGuid(i),
i.name,
Path.GetFileName(ProjectFile(i)),
ProjectGuid(i.name)
));
return string.Join(k_WindowsNewline, projectEntries.ToArray());
}
/// <summary>
/// Generate the active configuration string for a given project guid
/// </summary>
string GetProjectActiveConfigurations(string projectGuid)
{
return string.Format(
m_SolutionProjectConfigurationTemplate,
projectGuid);
}
static string SkipPathPrefix(string path, string prefix)
{
if (path.StartsWith($@"{prefix}{Path.DirectorySeparatorChar}"))
return path.Substring(prefix.Length + 1);
return path;
}
string ProjectGuid(string assembly)
{
return m_GUIDProvider.ProjectGuid(m_ProjectName, assembly);
}
string SolutionGuid(Assembly assembly)
{
return m_GUIDProvider.SolutionGuid(m_ProjectName, GetExtensionOfSourceFiles(assembly.sourceFiles));
}
static string ProjectFooter()
{
return GetProjectFooterTemplate();
}
static string GetProjectExtension()
{
return ".csproj";
}
void WriteVSCodeSettingsFiles()
{
var vsCodeDirectory = Path.Combine(ProjectDirectory, ".vscode");
if (!m_FileIOProvider.Exists(vsCodeDirectory))
m_FileIOProvider.CreateDirectory(vsCodeDirectory);
var vsCodeSettingsJson = Path.Combine(vsCodeDirectory, "settings.json");
if (!m_FileIOProvider.Exists(vsCodeSettingsJson))
m_FileIOProvider.WriteAllText(vsCodeSettingsJson, k_SettingsJson);
}
}
public static class SolutionGuidGenerator
{
static MD5 mD5 = MD5CryptoServiceProvider.Create();
public static string GuidForProject(string projectName)
{
return ComputeGuidHashFor(projectName + "salt");
}
public static string GuidForSolution(string projectName, string sourceFileExtension)
{
return "FAE04EC0-301F-11D3-BF4B-00C04F79EFBC";
}
static string ComputeGuidHashFor(string input)
{
var hash = mD5.ComputeHash(Encoding.Default.GetBytes(input));
return new Guid(hash).ToString();
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 97d6c87381e3e51488b49f5891490b70
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,18 @@
using System;
namespace VSCodeEditor
{
[Flags]
public enum ProjectGenerationFlag
{
None = 0,
Embedded = 1,
Local = 2,
Registry = 4,
Git = 8,
BuiltIn = 16,
Unknown = 32,
PlayerAssemblies = 64,
LocalTarBall = 128,
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: f239f506223a98f4e9b5dd3a9f80edea
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,26 @@
using System.IO;
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Unity.VSCode.EditorTests")]
namespace VSCodeEditor
{
internal static class StringUtils
{
private const char WinSeparator = '\\';
private const char UnixSeparator = '/';
public static string NormalizePath(this string path)
{
if (string.IsNullOrEmpty(path))
return path;
if (Path.DirectorySeparatorChar == WinSeparator)
path = path.Replace(UnixSeparator, WinSeparator);
if (Path.DirectorySeparatorChar == UnixSeparator)
path = path.Replace(WinSeparator, UnixSeparator);
return path.Replace(string.Concat(WinSeparator, WinSeparator), WinSeparator.ToString());
}
}
}

View file

@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: ee022071da7e4040bf2f949e15926ba1
timeCreated: 1624968755

View file

@ -0,0 +1,26 @@
{
"name": "Unity.VSCode.Editor",
"references": [],
"optionalUnityReferences": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": [],
"versionDefines": [
{
"name": "Unity",
"expression": "2021.2.0a9",
"define": "ROSLYN_ANALYZER_FIX"
},
{
"name": "Unity",
"expression": "[2021.1.2f1,2021.2.0a1]",
"define": "ROSLYN_ANALYZER_FIX"
},
{
"name": "Unity",
"expression": "[2020.3.6f1,2021.0]",
"define": "ROSLYN_ANALYZER_FIX"
}
]
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 8b845b123ab418448a8be2935fa804e0
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,138 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Unity.CodeEditor;
namespace VSCodeEditor
{
public interface IDiscovery
{
CodeEditor.Installation[] PathCallback();
}
public class VSCodeDiscovery : IDiscovery
{
List<CodeEditor.Installation> m_Installations;
public CodeEditor.Installation[] PathCallback()
{
if (m_Installations == null)
{
m_Installations = new List<CodeEditor.Installation>();
FindInstallationPaths();
}
return m_Installations.ToArray();
}
void FindInstallationPaths()
{
string[] possiblePaths =
#if UNITY_EDITOR_OSX
{
"/Applications/Visual Studio Code.app",
"/Applications/Visual Studio Code - Insiders.app"
};
#elif UNITY_EDITOR_WIN
{
GetProgramFiles() + @"/Microsoft VS Code/bin/code.cmd",
GetProgramFiles() + @"/Microsoft VS Code/Code.exe",
GetProgramFiles() + @"/Microsoft VS Code Insiders/bin/code-insiders.cmd",
GetProgramFiles() + @"/Microsoft VS Code Insiders/Code.exe",
GetLocalAppData() + @"/Programs/Microsoft VS Code/bin/code.cmd",
GetLocalAppData() + @"/Programs/Microsoft VS Code/Code.exe",
GetLocalAppData() + @"/Programs/Microsoft VS Code Insiders/bin/code-insiders.cmd",
GetLocalAppData() + @"/Programs/Microsoft VS Code Insiders/Code.exe",
};
#else
{
"/usr/bin/code",
"/bin/code",
"/usr/local/bin/code",
"/var/lib/flatpak/exports/bin/com.visualstudio.code",
"/snap/current/bin/code",
"/snap/bin/code"
};
#endif
var existingPaths = possiblePaths.Where(VSCodeExists).ToList();
if (!existingPaths.Any())
{
return;
}
var lcp = GetLongestCommonPrefix(existingPaths);
switch (existingPaths.Count)
{
case 1:
{
var path = existingPaths.First();
m_Installations = new List<CodeEditor.Installation>
{
new CodeEditor.Installation
{
Path = path,
Name = path.Contains("Insiders")
? "Visual Studio Code Insiders"
: "Visual Studio Code"
}
};
break;
}
case 2 when existingPaths.Any(path => !(path.Substring(lcp.Length).Contains("/") || path.Substring(lcp.Length).Contains("\\"))):
{
goto case 1;
}
default:
{
m_Installations = existingPaths.Select(path => new CodeEditor.Installation
{
Name = $"Visual Studio Code Insiders ({path.Substring(lcp.Length)})",
Path = path
}).ToList();
break;
}
}
}
#if UNITY_EDITOR_WIN
static string GetProgramFiles()
{
return Environment.GetEnvironmentVariable("ProgramFiles")?.Replace("\\", "/");
}
static string GetLocalAppData()
{
return Environment.GetEnvironmentVariable("LOCALAPPDATA")?.Replace("\\", "/");
}
#endif
static string GetLongestCommonPrefix(List<string> paths)
{
var baseLength = paths.First().Length;
for (var pathIndex = 1; pathIndex < paths.Count; pathIndex++)
{
baseLength = Math.Min(baseLength, paths[pathIndex].Length);
for (var i = 0; i < baseLength; i++)
{
if (paths[pathIndex][i] == paths[0][i]) continue;
baseLength = i;
break;
}
}
return paths[0].Substring(0, baseLength);
}
static bool VSCodeExists(string path)
{
#if UNITY_EDITOR_OSX
return System.IO.Directory.Exists(path);
#else
return new FileInfo(path).Exists;
#endif
}
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 380f7372e785c7d408552e2c760d269d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,282 @@
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.AssemblyNameProvider as IPackageInfoCache)?.ResetPackageInfoCache();
m_ProjectGeneration.SyncIfNeeded(addedFiles.Union(deletedFiles).Union(movedFiles).Union(movedFromFiles).ToList(), importedFiles);
}
public void SyncAll()
{
(m_ProjectGeneration.AssemblyNameProvider as IPackageInfoCache)?.ResetPackageInfoCache();
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) { }
}
}

View file

@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ac3f13489022aa34d861a0320a6917b9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2019 Unity Technologies
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: c9aabac5924106d4790d7b3a924ca34d
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,10 @@
{
"ErrorExceptions": [
{
"ValidationTest": "API Validation",
"ExceptionMessage": "Additions require a new minor or major version.",
"PackageVersion": "1.2.3"
}
],
"WarningExceptions": []
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: fc368c32d6432ff4bb374d520000749c
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View file

@ -0,0 +1,19 @@
{
"name": "com.unity.ide.vscode",
"displayName": "Visual Studio Code Editor",
"description": "Code editor integration for supporting Visual Studio Code as code editor for unity. Adds support for generating csproj files for intellisense purposes, auto discovery of installations, etc.",
"version": "1.2.5",
"unity": "2019.2",
"unityRelease": "0a12",
"relatedPackages": {
"com.unity.ide.vscode.tests": "1.2.5"
},
"upmCi": {
"footprint": "bc03b7bb076199fbe68649a647d0d32d98af6a0f"
},
"repository": {
"url": "https://github.com/Unity-Technologies/com.unity.ide.vscode.git",
"type": "git",
"revision": "b0740c80bfc2440527c317109f7c3d9100132722"
}
}

View file

@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: ffc6271f08270b64ca0aae9c49235d81
PackageManifestImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: