Initial commit

This commit is contained in:
Gregory Campbell
2019-09-29 19:40:54 -04:00
commit 463a783c7c
3583 changed files with 101769 additions and 0 deletions
@@ -0,0 +1,34 @@
# Code Editor Package for Visual Studio Code
## [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.
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 4ddcdc3816429494a8bea67e973875f7
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -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
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: fcb9be00baf924c4183fc0313e6185c5
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,4 @@
# Code Editor Package for Visual Studio Code
This package is not intended to be modified by users.
Nor does it provide any api intended to be included in user projects.
@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 58628227479c34542ac8c5193ccced84
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,837 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEditor.Compilation;
using UnityEditor.PackageManager;
using UnityEditorInternal;
using UnityEngine;
using UnityEngine.Profiling;
namespace VSCodeEditor
{
public interface IGenerator {
bool SyncIfNeeded(IEnumerable<string> affectedFiles, IEnumerable<string> reimportedFiles);
void Sync();
bool HasSolutionBeenGenerated();
string SolutionFile();
string ProjectDirectory { get; }
void GenerateAll(bool generateAll);
}
public interface IAssemblyNameProvider
{
string GetAssemblyNameFromScriptPath(string path);
IEnumerable<Assembly> GetAllAssemblies(Func<string, bool> shouldFileBePartOfSolution);
IEnumerable<string> GetAllAssetPaths();
UnityEditor.PackageManager.PackageInfo FindForAssetPath(string assetPath);
}
public struct TestSettings {
public bool ShouldSync;
public Dictionary<string, string> SyncPath;
}
class AssemblyNameProvider : IAssemblyNameProvider
{
public string GetAssemblyNameFromScriptPath(string path)
{
return CompilationPipeline.GetAssemblyNameFromScriptPath(path);
}
public IEnumerable<Assembly> GetAllAssemblies(Func<string, bool> shouldFileBePartOfSolution)
{
return CompilationPipeline.GetAssemblies().Where(i => 0 < i.sourceFiles.Length && i.sourceFiles.Any(shouldFileBePartOfSolution));
}
public IEnumerable<string> GetAllAssetPaths()
{
return AssetDatabase.GetAllAssetPaths();
}
public UnityEditor.PackageManager.PackageInfo FindForAssetPath(string assetPath)
{
return UnityEditor.PackageManager.PackageInfo.FindForAssetPath(assetPath);
}
}
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,
""**/.gitignore"":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 }
};
string m_SolutionProjectEntryTemplate = string.Join("\r\n", @"Project(""{{{0}}}"") = ""{1}"", ""{2}"", ""{{{3}}}""", @"EndProject").Replace(" ", "\t");
string m_SolutionProjectConfigurationTemplate = string.Join("\r\n", @" {{{0}}}.Debug|Any CPU.ActiveCfg = Debug|Any CPU", @" {{{0}}}.Debug|Any CPU.Build.0 = Debug|Any CPU", @" {{{0}}}.Release|Any CPU.ActiveCfg = Release|Any CPU", @" {{{0}}}.Release|Any CPU.Build.0 = Release|Any CPU").Replace(" ", "\t");
static readonly string[] k_ReimportSyncExtensions = { ".dll", ".asmdef" };
/// <summary>
/// Map ScriptingLanguages to project extensions
/// </summary>
/*static readonly Dictionary<ScriptingLanguage, string> k_ProjectExtensions = new Dictionary<ScriptingLanguage, string>
{
{ ScriptingLanguage.CSharp, ".csproj" },
{ ScriptingLanguage.None, ".csproj" },
};*/
static readonly Regex k_ScriptReferenceExpression = new Regex(
@"^Library.ScriptAssemblies.(?<dllname>(?<project>.*)\.dll$)",
RegexOptions.Compiled | RegexOptions.IgnoreCase);
string[] m_ProjectSupportedExtensions = new string[0];
public string ProjectDirectory { get; }
bool m_ShouldGenerateAll;
public void GenerateAll(bool generateAll)
{
m_ShouldGenerateAll = generateAll;
}
public TestSettings Settings { get; set; }
readonly string m_ProjectName;
readonly IAssemblyNameProvider m_AssemblyNameProvider;
const string k_ToolsVersion = "4.0";
const string k_ProductVersion = "10.0.20506";
const string k_BaseDirectory = ".";
const string k_TargetFrameworkVersion = "v4.7.1";
const string k_TargetLanguageVersion = "latest";
public ProjectGeneration() : this(Directory.GetParent(Application.dataPath).FullName, new AssemblyNameProvider())
{
}
public ProjectGeneration(string tempDirectory) : this(tempDirectory, new AssemblyNameProvider()) {
}
public ProjectGeneration(string tempDirectory, IAssemblyNameProvider assemblyNameProvider) {
Settings = new TestSettings { ShouldSync = true };
ProjectDirectory = tempDirectory.Replace('\\', '/');
m_ProjectName = Path.GetFileName(ProjectDirectory);
m_AssemblyNameProvider = assemblyNameProvider;
}
/// <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(IEnumerable<string> affectedFiles, IEnumerable<string> reimportedFiles)
{
Profiler.BeginSample("SolutionSynchronizerSync");
SetupProjectSupportedExtensions();
// Don't sync if we haven't synced before
if (HasSolutionBeenGenerated() && HasFilesBeenModified(affectedFiles, reimportedFiles))
{
Sync();
Profiler.EndSample();
return true;
}
Profiler.EndSample();
return false;
}
bool HasFilesBeenModified(IEnumerable<string> affectedFiles, IEnumerable<string> reimportedFiles)
{
return affectedFiles.Any(ShouldFileBePartOfSolution) || reimportedFiles.Any(ShouldSyncOnReimportedAsset);
}
static bool ShouldSyncOnReimportedAsset(string asset)
{
return k_ReimportSyncExtensions.Contains(new FileInfo(asset).Extension);
}
public void Sync()
{
SetupProjectSupportedExtensions();
GenerateAndWriteSolutionAndProjects();
}
public bool HasSolutionBeenGenerated()
{
return File.Exists(SolutionFile());
}
void SetupProjectSupportedExtensions()
{
m_ProjectSupportedExtensions = EditorSettings.projectGenerationUserExtensions;
}
bool ShouldFileBePartOfSolution(string file)
{
string extension = Path.GetExtension(file);
// Exclude files coming from packages except if they are internalized.
if (!m_ShouldGenerateAll && IsInternalizedPackagePath(file))
{
return false;
}
// 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 island)
{
return ScriptingLanguageFor(GetExtensionOfSourceFiles(island.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 islands 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.GetAllAssemblies(ShouldFileBePartOfSolution);
var allAssetProjectParts = GenerateAllAssetProjectParts();
var monoIslands = assemblies.ToList();
SyncSolution(monoIslands);
var allProjectIslands = RelevantIslandsForMode(monoIslands).ToList();
foreach (Assembly assembly in allProjectIslands)
{
var responseFileData = ParseResponseFileData(assembly);
SyncProject(assembly, allAssetProjectParts, responseFileData, allProjectIslands);
}
if (Settings.ShouldSync)
{
WriteVSCodeSettingsFiles();
}
}
IEnumerable<ResponseFileData> ParseResponseFileData(Assembly assembly)
{
var systemReferenceDirectories = CompilationPipeline.GetSystemAssemblyDirectories(assembly.compilerOptions.ApiCompatibilityLevel);
Dictionary<string, ResponseFileData> responseFilesData = assembly.compilerOptions.ResponseFiles.ToDictionary(x => x, x => CompilationPipeline.ParseResponseFile(
Path.Combine(ProjectDirectory, 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);
}
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_ShouldGenerateAll && 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 + ".cs");
if (string.IsNullOrEmpty(assemblyName))
{
continue;
}
assemblyName = Utility.FileNameWithoutExtension(assemblyName);
if (!stringBuilders.TryGetValue(assemblyName, out var projectBuilder))
{
projectBuilder = new StringBuilder();
stringBuilders[assemblyName] = projectBuilder;
}
projectBuilder.Append(" <None Include=\"").Append(EscapedRelativePathFor(asset)).Append("\" />").Append(k_WindowsNewline);
}
}
var result = new Dictionary<string, string>();
foreach (var entry in stringBuilders)
result[entry.Key] = entry.Value.ToString();
return result;
}
bool IsInternalizedPackagePath(string file)
{
if (string.IsNullOrWhiteSpace(file))
{
return false;
}
var packageInfo = m_AssemblyNameProvider.FindForAssetPath(file);
if (packageInfo == null) {
return false;
}
var packageSource = packageInfo.source;
return packageSource != PackageSource.Embedded && packageSource != PackageSource.Local;
}
void SyncProject(
Assembly island,
Dictionary<string, string> allAssetsProjectParts,
IEnumerable<ResponseFileData> responseFilesData,
List<Assembly> allProjectIslands)
{
SyncProjectFileIfNotChanged(ProjectFile(island), ProjectText(island, allAssetsProjectParts, responseFilesData, allProjectIslands));
}
void SyncProjectFileIfNotChanged(string path, string newContents)
{
if (Path.GetExtension(path) == ".csproj")
{
//newContents = AssetPostprocessingInternal.CallOnGeneratedCSProject(path, newContents); TODO: Call specific code here
}
SyncFileIfNotChanged(path, newContents);
}
void SyncSolutionFileIfNotChanged(string path, string newContents)
{
//newContents = AssetPostprocessingInternal.CallOnGeneratedSlnSolution(path, newContents); TODO: Call specific code here
SyncFileIfNotChanged(path, newContents);
}
void SyncFileIfNotChanged(string filename, string newContents)
{
if (File.Exists(filename) &&
newContents == File.ReadAllText(filename))
{
return;
}
if (Settings.ShouldSync)
{
File.WriteAllText(filename, newContents, Encoding.UTF8);
}
else
{
var utf8 = Encoding.UTF8;
byte[] utfBytes = utf8.GetBytes(newContents);
Settings.SyncPath[filename] = utf8.GetString(utfBytes, 0, utfBytes.Length);
}
}
string ProjectText(Assembly assembly,
Dictionary<string, string> allAssetsProjectParts,
IEnumerable<ResponseFileData> responseFilesData,
List<Assembly> allProjectIslands)
{
var projectBuilder = new StringBuilder(ProjectHeader(assembly, responseFilesData));
var references = new List<string>();
var projectReferences = new List<Match>();
foreach (string file in assembly.sourceFiles)
{
if (!ShouldFileBePartOfSolution(file))
continue;
var extension = Path.GetExtension(file).ToLower();
var fullFile = EscapedRelativePathFor(file);
if (".dll" != extension)
{
projectBuilder.Append(" <Compile Include=\"").Append(fullFile).Append("\" />").Append(k_WindowsNewline);
}
else
{
references.Add(fullFile);
}
}
var assemblyName = Utility.FileNameWithoutExtension(assembly.outputPath);
// Append additional non-script files that should be included in project generation.
if (allAssetsProjectParts.TryGetValue(assemblyName, out var additionalAssetsForProject))
projectBuilder.Append(additionalAssetsForProject);
var islandRefs = references.Union(assembly.allReferences);
foreach (string reference in islandRefs)
{
if (reference.EndsWith("/UnityEditor.dll", StringComparison.Ordinal)
|| reference.EndsWith("/UnityEngine.dll", StringComparison.Ordinal)
|| reference.EndsWith("\\UnityEditor.dll", StringComparison.Ordinal)
|| reference.EndsWith("\\UnityEngine.dll", StringComparison.Ordinal))
continue;
var match = k_ScriptReferenceExpression.Match(reference);
if (match.Success)
{
// assume csharp language
// Add a reference to a project except if it's a reference to a script assembly
// that we are not generating a project for. This will be the case for assemblies
// coming from .assembly.json files in non-internalized packages.
var dllName = match.Groups["dllname"].Value;
if (allProjectIslands.Any(i => Path.GetFileName(i.outputPath) == dllName))
{
projectReferences.Add(match);
continue;
}
}
string fullReference = Path.IsPathRooted(reference) ? reference : Path.Combine(ProjectDirectory, reference);
AppendReference(fullReference, projectBuilder);
}
var responseRefs = responseFilesData.SelectMany(x => x.FullPathReferences.Select(r => r));
foreach (var reference in responseRefs)
{
AppendReference(reference, projectBuilder);
}
if (0 < projectReferences.Count)
{
projectBuilder.AppendLine(" </ItemGroup>");
projectBuilder.AppendLine(" <ItemGroup>");
foreach (Match reference in projectReferences)
{
var referencedProject = reference.Groups["project"].Value;
projectBuilder.Append(" <ProjectReference Include=\"").Append(referencedProject).Append(GetProjectExtension()).Append("\">").Append(k_WindowsNewline);
projectBuilder.Append(" <Project>{").Append(ProjectGuid(Path.Combine("Temp", reference.Groups["project"].Value + ".dll"))).Append("}</Project>").Append(k_WindowsNewline);
projectBuilder.Append(" <Name>").Append(referencedProject).Append("</Name>").Append(k_WindowsNewline);
projectBuilder.AppendLine(" </ProjectReference>");
}
}
projectBuilder.Append(ProjectFooter());
return projectBuilder.ToString();
}
static void AppendReference(string fullReference, StringBuilder projectBuilder)
{
//replace \ with / and \\ with /
var escapedFullPath = SecurityElement.Escape(fullReference);
escapedFullPath = escapedFullPath.Replace("\\", "/");
escapedFullPath = escapedFullPath.Replace("\\\\", "/");
projectBuilder.Append(" <Reference Include=\"").Append(Utility.FileNameWithoutExtension(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)
{
return Path.Combine(ProjectDirectory, $"{Utility.FileNameWithoutExtension(assembly.outputPath)}.csproj");
}
public string SolutionFile()
{
return Path.Combine(ProjectDirectory, $"{m_ProjectName}.sln");
}
string ProjectHeader(
Assembly island,
IEnumerable<ResponseFileData> responseFilesData
)
{
var arguments = new object[]
{
k_ToolsVersion, k_ProductVersion, ProjectGuid(island.outputPath),
InternalEditorUtility.GetEngineAssemblyPath(),
InternalEditorUtility.GetEditorAssemblyPath(),
string.Join(";", new[] { "DEBUG", "TRACE" }.Concat(EditorUserBuildSettings.activeScriptCompilationDefines).Concat(island.defines).Concat(responseFilesData.SelectMany(x => x.Defines)).Distinct().ToArray()),
MSBuildNamespaceUri,
Utility.FileNameWithoutExtension(island.outputPath),
EditorSettings.projectGenerationRootNamespace,
k_TargetFrameworkVersion,
k_TargetLanguageVersion,
k_BaseDirectory,
island.compilerOptions.AllowUnsafeCode | responseFilesData.Any(x => x.Unsafe)
};
try
{
return string.Format(GetProjectHeaderTemplate(), arguments);
}
catch (Exception)
{
throw new NotSupportedException("Failed creating c# project because the c# project header did not have the correct amount of arguments, which is " + arguments.Length);
}
}
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", @" Release|Any CPU = Release|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 string GetProjectHeaderTemplate()
{
var header = new[]
{
@"<?xml version=""1.0"" encoding=""utf-8""?>",
@"<Project ToolsVersion=""{0}"" DefaultTargets=""Build"" xmlns=""{6}"">",
@" <PropertyGroup>",
@" <LangVersion>{10}</LangVersion>",
@" </PropertyGroup>",
@" <PropertyGroup>",
@" <Configuration Condition="" '$(Configuration)' == '' "">Debug</Configuration>",
@" <Platform Condition="" '$(Platform)' == '' "">AnyCPU</Platform>",
@" <ProductVersion>{1}</ProductVersion>",
@" <SchemaVersion>2.0</SchemaVersion>",
@" <RootNamespace>{8}</RootNamespace>",
@" <ProjectGuid>{{{2}}}</ProjectGuid>",
@" <OutputType>Library</OutputType>",
@" <AppDesignerFolder>Properties</AppDesignerFolder>",
@" <AssemblyName>{7}</AssemblyName>",
@" <TargetFrameworkVersion>{9}</TargetFrameworkVersion>",
@" <FileAlignment>512</FileAlignment>",
@" <BaseDirectory>{11}</BaseDirectory>",
@" </PropertyGroup>",
@" <PropertyGroup Condition="" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "">",
@" <DebugSymbols>true</DebugSymbols>",
@" <DebugType>full</DebugType>",
@" <Optimize>false</Optimize>",
@" <OutputPath>Temp\bin\Debug\</OutputPath>",
@" <DefineConstants>{5}</DefineConstants>",
@" <ErrorReport>prompt</ErrorReport>",
@" <WarningLevel>4</WarningLevel>",
@" <NoWarn>0169</NoWarn>",
@" <AllowUnsafeBlocks>{12}</AllowUnsafeBlocks>",
@" </PropertyGroup>",
@" <PropertyGroup Condition="" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "">",
@" <DebugType>pdbonly</DebugType>",
@" <Optimize>true</Optimize>",
@" <OutputPath>Temp\bin\Release\</OutputPath>",
@" <ErrorReport>prompt</ErrorReport>",
@" <WarningLevel>4</WarningLevel>",
@" <NoWarn>0169</NoWarn>",
@" <AllowUnsafeBlocks>{12}</AllowUnsafeBlocks>",
@" </PropertyGroup>"
};
var forceExplicitReferences = new[]
{
@" <PropertyGroup>",
@" <NoConfig>true</NoConfig>",
@" <NoStdLib>true</NoStdLib>",
@" <AddAdditionalExplicitAssemblyReferences>false</AddAdditionalExplicitAssemblyReferences>",
@" <ImplicitlyExpandNETStandardFacades>false</ImplicitlyExpandNETStandardFacades>",
@" <ImplicitlyExpandDesignTimeFacades>false</ImplicitlyExpandDesignTimeFacades>",
@" </PropertyGroup>"
};
var itemGroupStart = new[]
{
@" <ItemGroup>"
};
var footer = new[]
{
@" <Reference Include=""UnityEngine"">",
@" <HintPath>{3}</HintPath>",
@" </Reference>",
@" <Reference Include=""UnityEditor"">",
@" <HintPath>{4}</HintPath>",
@" </Reference>",
@" </ItemGroup>",
@" <ItemGroup>",
@""
};
var text = header.Concat(forceExplicitReferences).Concat(itemGroupStart).Concat(footer).ToArray();
return string.Join("\r\n", text);
}
void SyncSolution(IEnumerable<Assembly> islands)
{
SyncSolutionFileIfNotChanged(SolutionFile(), SolutionText(islands));
}
string SolutionText(IEnumerable<Assembly> islands)
{
var fileversion = "11.00";
var vsversion = "2010";
var relevantIslands = RelevantIslandsForMode(islands);
string projectEntries = GetProjectEntries(relevantIslands);
string projectConfigurations = string.Join(k_WindowsNewline, relevantIslands.Select(i => GetProjectActiveConfigurations(ProjectGuid(i.outputPath))).ToArray());
return string.Format(GetSolutionText(), fileversion, vsversion, projectEntries, projectConfigurations);
}
static IEnumerable<Assembly> RelevantIslandsForMode(IEnumerable<Assembly> islands)
{
IEnumerable<Assembly> relevantIslands = islands.Where(i => ScriptingLanguage.CSharp == ScriptingLanguageFor(i));
return relevantIslands;
}
/// <summary>
/// Get a Project("{guid}") = "MyProject", "MyProject.unityproj", "{projectguid}"
/// entry for each relevant language
/// </summary>
string GetProjectEntries(IEnumerable<Assembly> islands)
{
var projectEntries = islands.Select(i => string.Format(
m_SolutionProjectEntryTemplate,
SolutionGuid(i), Utility.FileNameWithoutExtension(i.outputPath), Path.GetFileName(ProjectFile(i)), ProjectGuid(i.outputPath)
));
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);
}
string EscapedRelativePathFor(string file)
{
var projectDir = ProjectDirectory.Replace('/', '\\');
file = file.Replace('/', '\\');
var path = SkipPathPrefix(file, projectDir);
var packageInfo = m_AssemblyNameProvider.FindForAssetPath(path.Replace('\\', '/'));
if (packageInfo != null) {
// We have to normalize the path, because the PackageManagerRemapper assumes
// dir seperators will be os specific.
var absolutePath = Path.GetFullPath(NormalizePath(path)).Replace('/', '\\');
path = SkipPathPrefix(absolutePath, projectDir);
}
return SecurityElement.Escape(path);
}
static string SkipPathPrefix(string path, string prefix)
{
if (path.Replace("\\","/").StartsWith($"{prefix}/"))
return path.Substring(prefix.Length + 1);
return path;
}
static string NormalizePath(string path)
{
if (Path.DirectorySeparatorChar == '\\')
return path.Replace('/', Path.DirectorySeparatorChar);
return path.Replace('\\', Path.DirectorySeparatorChar);
}
string ProjectGuid(string assembly)
{
return SolutionGuidGenerator.GuidForProject(m_ProjectName + Utility.FileNameWithoutExtension(assembly));
}
string SolutionGuid(Assembly island)
{
return SolutionGuidGenerator.GuidForSolution(m_ProjectName, GetExtensionOfSourceFiles(island.sourceFiles));
}
static string ProjectFooter()
{
return GetProjectFooterTemplate();
}
static string GetProjectExtension()
{
return ".csproj";
}
void WriteVSCodeSettingsFiles()
{
var vsCodeDirectory = Path.Combine(ProjectDirectory, ".vscode");
if (!Directory.Exists(vsCodeDirectory))
Directory.CreateDirectory(vsCodeDirectory);
var vsCodeSettingsJson = Path.Combine(vsCodeDirectory, "settings.json");
if (!File.Exists(vsCodeSettingsJson))
File.WriteAllText(vsCodeSettingsJson, k_SettingsJson);
}
}
public static class SolutionGuidGenerator
{
public static string GuidForProject(string projectName)
{
return ComputeGuidHashFor(projectName + "salt");
}
public static string GuidForSolution(string projectName, string sourceFileExtension)
{
if (sourceFileExtension.ToLower() == "cs")
// GUID for a C# class library: http://www.codeproject.com/Reference/720512/List-of-Visual-Studio-Project-Type-GUIDs
return "FAE04EC0-301F-11D3-BF4B-00C04F79EFBC";
return ComputeGuidHashFor(projectName);
}
static string ComputeGuidHashFor(string input)
{
var hash = MD5.Create().ComputeHash(Encoding.Default.GetBytes(input));
return HashAsGuid(HashToString(hash));
}
static string HashAsGuid(string hash)
{
var guid = hash.Substring(0, 8) + "-" + hash.Substring(8, 4) + "-" + hash.Substring(12, 4) + "-" + hash.Substring(16, 4) + "-" + hash.Substring(20, 12);
return guid.ToUpper();
}
static string HashToString(byte[] bs)
{
var sb = new StringBuilder();
foreach (byte b in bs)
sb.Append(b.ToString("x2"));
return sb.ToString();
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 58eb69fc39be3d040b984b4498a820bb
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,9 @@
{
"name": "Unity.VSCode.Editor",
"references": [],
"optionalUnityReferences": [],
"includePlatforms": [
"Editor"
],
"excludePlatforms": []
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: 8b845b123ab418448a8be2935fa804e0
AssemblyDefinitionImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,35 @@
namespace VSCodeEditor
{
public static class Utility
{
public static string FileNameWithoutExtension(string path)
{
if (string.IsNullOrEmpty(path))
{
return "";
}
var indexOfDot = -1;
var indexOfSlash = 0;
for (var i = path.Length - 1; i >= 0; i--)
{
if (indexOfDot == -1 && path[i] == '.')
{
indexOfDot = i;
}
if (indexOfSlash == 0 && path[i] == '/' || path[i] == '\\')
{
indexOfSlash = i + 1;
break;
}
}
if (indexOfDot == -1)
{
indexOfDot = path.Length - 1;
}
return path.Substring(indexOfSlash, indexOfDot - indexOfSlash);
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 1ac677c5ece15b443b2aaf7fae5842f7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,136 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using UnityEditor;
using UnityEngine;
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"
};
#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
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 380f7372e785c7d408552e2c760d269d
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,253 @@
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_generate_all = "unity_generate_all_csproj";
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 GetDefaultApp => 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;
}
var prevGenerate = EditorPrefs.GetBool(vscode_generate_all, false);
var generateAll = EditorGUILayout.Toggle("Generate all .csproj files.", prevGenerate);
if (generateAll != prevGenerate)
{
EditorPrefs.SetBool(vscode_generate_all, generateAll);
}
m_ProjectGeneration.GenerateAll(generateAll);
HandledExtensionsString = EditorGUILayout.TextField(new GUIContent("Extensions handled: "), HandledExtensionsString);
}
public void CreateIfDoesntExist()
{
if (!m_ProjectGeneration.HasSolutionBeenGenerated())
{
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), importedFiles);
}
public void SyncAll()
{
AssetDatabase.Refresh();
m_ProjectGeneration.Sync();
}
public bool OpenProject(string path, int line, int column)
{
if (path != "" && !SupportsExtension(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 process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = GetDefaultApp,
Arguments = arguments,
WindowStyle = ProcessWindowStyle.Hidden,
CreateNoWindow = true,
UseShellExecute = true,
}
};
process.Start();
return true;
}
static bool OpenOSX(string arguments)
{
var process = new Process
{
StartInfo = new ProcessStartInfo
{
FileName = "open",
Arguments = $"\"{GetDefaultApp}\" --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());
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)
{
}
}
}
@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: ac3f13489022aa34d861a0320a6917b9
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
@@ -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.
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: c9aabac5924106d4790d7b3a924ca34d
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:
@@ -0,0 +1,17 @@
{
"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.1.0",
"unity": "2019.2",
"unityRelease": "0a12",
"dependencies": {},
"relatedPackages": {
"com.unity.ide.vscode.tests": "1.0.4"
},
"repository": {
"type": "git",
"url": "git@github.cds.internal.unity3d.com:unity/com.unity.ide.vscode.git",
"revision": "be12768c09e4711e6d38ba647df27fac67dbfd1f"
}
}
@@ -0,0 +1,7 @@
fileFormatVersion: 2
guid: ffc6271f08270b64ca0aae9c49235d81
TextScriptImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant: