From 9fed958a9e32f0936ed2d68fc5266a62f4d48643 Mon Sep 17 00:00:00 2001 From: Unity Technologies <@unity> Date: Tue, 5 Sep 2023 00:00:00 +0000 Subject: [PATCH] com.unity.ide.visualstudio@2.0.21 ## [2.0.21] - 2023-09-05 Integration: - Only disable the legacy `com.unity.ide.vscode` package going forward. - Fix json parsing issues with specific non-UTF code pages. Project generation: - Target `netstandard2.1` instead of `netstandard2.0`. - Set `defaultSolution` in `settings.json`. - Remove `files.exclude` entries for root `csproj` and `sln` files in `settings.json` when needed. - Add `vstuc` launch configuration to `launch.json` when needed. - Add `visualstudiotoolsforunity.vstuc` entry to `extensions.json` when needed. - You can prevent the package from patching those configuration files by creating a `.vscode/.vstupatchdisable` file. --- CHANGELOG.md | 29 +- .../COMIntegration/Release/COMIntegration.exe | Bin 294400 -> 294400 bytes .../Contents/MacOS/AppleEventIntegration | Bin 153664 -> 153664 bytes .../SdkStyleProjectGeneration.cs | 4 +- Editor/SimpleJSON.cs | 1434 +++++++++++++++++ Editor/SimpleJSON.cs.meta | 11 + Editor/VisualStudioCodeInstallation.cs | 318 +++- Editor/VisualStudioEditor.cs | 6 + Editor/VisualStudioForWindowsInstallation.cs | 19 +- package.json | 10 +- 10 files changed, 1727 insertions(+), 104 deletions(-) create mode 100644 Editor/SimpleJSON.cs create mode 100644 Editor/SimpleJSON.cs.meta diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a694c9..898f78c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,25 +1,34 @@ # Code Editor Package for Visual Studio +## [2.0.21] - 2023-09-05 + +Integration: + +- Only disable the legacy `com.unity.ide.vscode` package going forward. +- Fix json parsing issues with specific non-UTF code pages. + +Project generation: + +- Target `netstandard2.1` instead of `netstandard2.0`. +- Set `defaultSolution` in `settings.json`. +- Remove `files.exclude` entries for root `csproj` and `sln` files in `settings.json` when needed. +- Add `vstuc` launch configuration to `launch.json` when needed. +- Add `visualstudiotoolsforunity.vstuc` entry to `extensions.json` when needed. +- You can prevent the package from patching those configuration files by creating a `.vscode/.vstupatchdisable` file. + + ## [2.0.20] - 2023-06-27 Integration: - Internal API refactoring. - - - -## [2.0.19] - 2023-06-14 - -Integration: - - Add support for Visual Studio Code. - + Project generation: -- Add support for Sdk Style poject generation. +- Add support for Sdk Style project generation. - Fix an issue related to missing properties with 2021.3. - ## [2.0.18] - 2023-03-17 Integration: diff --git a/Editor/COMIntegration/Release/COMIntegration.exe b/Editor/COMIntegration/Release/COMIntegration.exe index 4622082f65ba68b348530d845daa0b8609c11c6e..57883379438fa0fc493aa5eebd9808c426810f3a 100644 GIT binary patch delta 40 rcmZqpBiQgqaDxCNGo$wRW?{y5VMY*U+Ahqt^fc4 diff --git a/Editor/Plugins/AppleEventIntegration.bundle/Contents/MacOS/AppleEventIntegration b/Editor/Plugins/AppleEventIntegration.bundle/Contents/MacOS/AppleEventIntegration index 038318eb1b9c419690cb8f3c03f0ca6b642312fd..6965b8c3b771a0024470de076b17312d2c9c4222 100644 GIT binary patch delta 108 zcmV-y0F(c~unEAh39yjF0~adyvy;Sb3?N1~3ke?VluiKi8+(AhW&qvl9%A9~5Q`8# zuH>dOp+bWp(YGPd0cSG<7b^F+iv$7&4IqX(!svzmJ(4@VdJCkEj>&DydCk&NQSzsp Ob+R`@n{%@v(T_$Y-!W4F delta 108 zcmV-y0F(c~unEAh39yjF1D2$kvy;Sb3?NdY^Rif`9op_P^gh)w(|xm=>yyV9E3>9> zl=kUsI@N@(;|v*NV&+@F0^2g*B1*429r*0Properties").Append(k_WindowsNewline); headerBuilder.Append(@" ").Append(properties.AssemblyName).Append(@"").Append(k_WindowsNewline); // In the end, given we use NoConfig/NoStdLib (see below), hardcoding the target framework version will have no impact, even when targeting netstandard/net48 from Unity. - // But with SDK style we use netstandard2.0 (net471 for legacy), so 3rd party tools will not fail to work when .NETFW reference assemblies are not installed. + // But with SDK style we use netstandard2.1 (net471 for legacy), so 3rd party tools will not fail to work when .NETFW reference assemblies are not installed. // Unity already selected proper API surface through referenced DLLs for us. - headerBuilder.Append(@" netstandard2.0").Append(k_WindowsNewline); + headerBuilder.Append(@" netstandard2.1").Append(k_WindowsNewline); headerBuilder.Append(@" .").Append(k_WindowsNewline); headerBuilder.Append(@" ").Append(k_WindowsNewline); diff --git a/Editor/SimpleJSON.cs b/Editor/SimpleJSON.cs new file mode 100644 index 0000000..7451961 --- /dev/null +++ b/Editor/SimpleJSON.cs @@ -0,0 +1,1434 @@ +/* * * * * + * A simple JSON Parser / builder + * ------------------------------ + * + * It mainly has been written as a simple JSON parser. It can build a JSON string + * from the node-tree, or generate a node tree from any valid JSON string. + * + * Written by Bunny83 + * 2012-06-09 + * + * Changelog now external. See Changelog.txt + * + * The MIT License (MIT) + * + * Copyright (c) 2012-2022 Markus Göbel (Bunny83) + * + * 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. + * + * * * * */ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; + +namespace SimpleJSON +{ + internal enum JSONNodeType + { + Array = 1, + Object = 2, + String = 3, + Number = 4, + NullValue = 5, + Boolean = 6, + None = 7, + Custom = 0xFF, + } + internal enum JSONTextMode + { + Compact, + Indent + } + + internal abstract partial class JSONNode + { + #region Enumerators + internal struct Enumerator + { + private enum Type { None, Array, Object } + private Type type; + private Dictionary.Enumerator m_Object; + private List.Enumerator m_Array; + public bool IsValid { get { return type != Type.None; } } + public Enumerator(List.Enumerator aArrayEnum) + { + type = Type.Array; + m_Object = default(Dictionary.Enumerator); + m_Array = aArrayEnum; + } + public Enumerator(Dictionary.Enumerator aDictEnum) + { + type = Type.Object; + m_Object = aDictEnum; + m_Array = default(List.Enumerator); + } + public KeyValuePair Current + { + get + { + if (type == Type.Array) + return new KeyValuePair(string.Empty, m_Array.Current); + else if (type == Type.Object) + return m_Object.Current; + return new KeyValuePair(string.Empty, null); + } + } + public bool MoveNext() + { + if (type == Type.Array) + return m_Array.MoveNext(); + else if (type == Type.Object) + return m_Object.MoveNext(); + return false; + } + } + internal struct ValueEnumerator + { + private Enumerator m_Enumerator; + public ValueEnumerator(List.Enumerator aArrayEnum) : this(new Enumerator(aArrayEnum)) { } + public ValueEnumerator(Dictionary.Enumerator aDictEnum) : this(new Enumerator(aDictEnum)) { } + public ValueEnumerator(Enumerator aEnumerator) { m_Enumerator = aEnumerator; } + public JSONNode Current { get { return m_Enumerator.Current.Value; } } + public bool MoveNext() { return m_Enumerator.MoveNext(); } + public ValueEnumerator GetEnumerator() { return this; } + } + internal struct KeyEnumerator + { + private Enumerator m_Enumerator; + public KeyEnumerator(List.Enumerator aArrayEnum) : this(new Enumerator(aArrayEnum)) { } + public KeyEnumerator(Dictionary.Enumerator aDictEnum) : this(new Enumerator(aDictEnum)) { } + public KeyEnumerator(Enumerator aEnumerator) { m_Enumerator = aEnumerator; } + public string Current { get { return m_Enumerator.Current.Key; } } + public bool MoveNext() { return m_Enumerator.MoveNext(); } + public KeyEnumerator GetEnumerator() { return this; } + } + + internal class LinqEnumerator : IEnumerator>, IEnumerable> + { + private JSONNode m_Node; + private Enumerator m_Enumerator; + internal LinqEnumerator(JSONNode aNode) + { + m_Node = aNode; + if (m_Node != null) + m_Enumerator = m_Node.GetEnumerator(); + } + public KeyValuePair Current { get { return m_Enumerator.Current; } } + object IEnumerator.Current { get { return m_Enumerator.Current; } } + public bool MoveNext() { return m_Enumerator.MoveNext(); } + + public void Dispose() + { + m_Node = null; + m_Enumerator = new Enumerator(); + } + + public IEnumerator> GetEnumerator() + { + return new LinqEnumerator(m_Node); + } + + public void Reset() + { + if (m_Node != null) + m_Enumerator = m_Node.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new LinqEnumerator(m_Node); + } + } + + #endregion Enumerators + + #region common interface + + public static bool forceASCII = false; // Use Unicode by default + public static bool longAsString = false; // lazy creator creates a JSONString instead of JSONNumber + public static bool allowLineComments = true; // allow "//"-style comments at the end of a line + + public abstract JSONNodeType Tag { get; } + + public virtual JSONNode this[int aIndex] { get { return null; } set { } } + + public virtual JSONNode this[string aKey] { get { return null; } set { } } + + public virtual string Value { get { return ""; } set { } } + + public virtual int Count { get { return 0; } } + + public virtual bool IsNumber { get { return false; } } + public virtual bool IsString { get { return false; } } + public virtual bool IsBoolean { get { return false; } } + public virtual bool IsNull { get { return false; } } + public virtual bool IsArray { get { return false; } } + public virtual bool IsObject { get { return false; } } + + public virtual bool Inline { get { return false; } set { } } + + public virtual void Add(string aKey, JSONNode aItem) + { + } + public virtual void Add(JSONNode aItem) + { + Add("", aItem); + } + + public virtual JSONNode Remove(string aKey) + { + return null; + } + + public virtual JSONNode Remove(int aIndex) + { + return null; + } + + public virtual JSONNode Remove(JSONNode aNode) + { + return aNode; + } + public virtual void Clear() { } + + public virtual JSONNode Clone() + { + return null; + } + + public virtual IEnumerable Children + { + get + { + yield break; + } + } + + public IEnumerable DeepChildren + { + get + { + foreach (var C in Children) + foreach (var D in C.DeepChildren) + yield return D; + } + } + + public virtual bool HasKey(string aKey) + { + return false; + } + + public virtual JSONNode GetValueOrDefault(string aKey, JSONNode aDefault) + { + return aDefault; + } + + public override string ToString() + { + StringBuilder sb = new StringBuilder(); + WriteToStringBuilder(sb, 0, 0, JSONTextMode.Compact); + return sb.ToString(); + } + + public virtual string ToString(int aIndent) + { + StringBuilder sb = new StringBuilder(); + WriteToStringBuilder(sb, 0, aIndent, JSONTextMode.Indent); + return sb.ToString(); + } + internal abstract void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode); + + public abstract Enumerator GetEnumerator(); + public IEnumerable> Linq { get { return new LinqEnumerator(this); } } + public KeyEnumerator Keys { get { return new KeyEnumerator(GetEnumerator()); } } + public ValueEnumerator Values { get { return new ValueEnumerator(GetEnumerator()); } } + + #endregion common interface + + #region typecasting properties + + + public virtual double AsDouble + { + get + { + double v = 0.0; + if (double.TryParse(Value, NumberStyles.Float, CultureInfo.InvariantCulture, out v)) + return v; + return 0.0; + } + set + { + Value = value.ToString(CultureInfo.InvariantCulture); + } + } + + public virtual int AsInt + { + get { return (int)AsDouble; } + set { AsDouble = value; } + } + + public virtual float AsFloat + { + get { return (float)AsDouble; } + set { AsDouble = value; } + } + + public virtual bool AsBool + { + get + { + bool v = false; + if (bool.TryParse(Value, out v)) + return v; + return !string.IsNullOrEmpty(Value); + } + set + { + Value = (value) ? "true" : "false"; + } + } + + public virtual long AsLong + { + get + { + long val = 0; + if (long.TryParse(Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out val)) + return val; + return 0L; + } + set + { + Value = value.ToString(CultureInfo.InvariantCulture); + } + } + + public virtual ulong AsULong + { + get + { + ulong val = 0; + if (ulong.TryParse(Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out val)) + return val; + return 0; + } + set + { + Value = value.ToString(CultureInfo.InvariantCulture); + } + } + + public virtual JSONArray AsArray + { + get + { + return this as JSONArray; + } + } + + public virtual JSONObject AsObject + { + get + { + return this as JSONObject; + } + } + + + #endregion typecasting properties + + #region operators + + public static implicit operator JSONNode(string s) + { + return (s == null) ? (JSONNode)JSONNull.CreateOrGet() : new JSONString(s); + } + public static implicit operator string(JSONNode d) + { + return (d == null) ? null : d.Value; + } + + public static implicit operator JSONNode(double n) + { + return new JSONNumber(n); + } + public static implicit operator double(JSONNode d) + { + return (d == null) ? 0 : d.AsDouble; + } + + public static implicit operator JSONNode(float n) + { + return new JSONNumber(n); + } + public static implicit operator float(JSONNode d) + { + return (d == null) ? 0 : d.AsFloat; + } + + public static implicit operator JSONNode(int n) + { + return new JSONNumber(n); + } + public static implicit operator int(JSONNode d) + { + return (d == null) ? 0 : d.AsInt; + } + + public static implicit operator JSONNode(long n) + { + if (longAsString) + return new JSONString(n.ToString(CultureInfo.InvariantCulture)); + return new JSONNumber(n); + } + public static implicit operator long(JSONNode d) + { + return (d == null) ? 0L : d.AsLong; + } + + public static implicit operator JSONNode(ulong n) + { + if (longAsString) + return new JSONString(n.ToString(CultureInfo.InvariantCulture)); + return new JSONNumber(n); + } + public static implicit operator ulong(JSONNode d) + { + return (d == null) ? 0 : d.AsULong; + } + + public static implicit operator JSONNode(bool b) + { + return new JSONBool(b); + } + public static implicit operator bool(JSONNode d) + { + return (d == null) ? false : d.AsBool; + } + + public static implicit operator JSONNode(KeyValuePair aKeyValue) + { + return aKeyValue.Value; + } + + public static bool operator ==(JSONNode a, object b) + { + if (ReferenceEquals(a, b)) + return true; + bool aIsNull = a is JSONNull || ReferenceEquals(a, null) || a is JSONLazyCreator; + bool bIsNull = b is JSONNull || ReferenceEquals(b, null) || b is JSONLazyCreator; + if (aIsNull && bIsNull) + return true; + return !aIsNull && a.Equals(b); + } + + public static bool operator !=(JSONNode a, object b) + { + return !(a == b); + } + + public override bool Equals(object obj) + { + return ReferenceEquals(this, obj); + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + + #endregion operators + + [ThreadStatic] + private static StringBuilder m_EscapeBuilder; + internal static StringBuilder EscapeBuilder + { + get + { + if (m_EscapeBuilder == null) + m_EscapeBuilder = new StringBuilder(); + return m_EscapeBuilder; + } + } + internal static string Escape(string aText) + { + var sb = EscapeBuilder; + sb.Length = 0; + if (sb.Capacity < aText.Length + aText.Length / 10) + sb.Capacity = aText.Length + aText.Length / 10; + foreach (char c in aText) + { + switch (c) + { + case '\\': + sb.Append("\\\\"); + break; + case '\"': + sb.Append("\\\""); + break; + case '\n': + sb.Append("\\n"); + break; + case '\r': + sb.Append("\\r"); + break; + case '\t': + sb.Append("\\t"); + break; + case '\b': + sb.Append("\\b"); + break; + case '\f': + sb.Append("\\f"); + break; + default: + if (c < ' ' || (forceASCII && c > 127)) + { + ushort val = c; + sb.Append("\\u").Append(val.ToString("X4")); + } + else + sb.Append(c); + break; + } + } + string result = sb.ToString(); + sb.Length = 0; + return result; + } + + private static JSONNode ParseElement(string token, bool quoted) + { + if (quoted) + return token; + if (token.Length <= 5) + { + string tmp = token.ToLower(); + if (tmp == "false" || tmp == "true") + return tmp == "true"; + if (tmp == "null") + return JSONNull.CreateOrGet(); + } + double val; + if (double.TryParse(token, NumberStyles.Float, CultureInfo.InvariantCulture, out val)) + return val; + else + return token; + } + + public static JSONNode Parse(string aJSON) + { + Stack stack = new Stack(); + JSONNode ctx = null; + int i = 0; + StringBuilder Token = new StringBuilder(); + string TokenName = ""; + bool QuoteMode = false; + bool TokenIsQuoted = false; + bool HasNewlineChar = false; + while (i < aJSON.Length) + { + switch (aJSON[i]) + { + case '{': + if (QuoteMode) + { + Token.Append(aJSON[i]); + break; + } + stack.Push(new JSONObject()); + if (ctx != null) + { + ctx.Add(TokenName, stack.Peek()); + } + TokenName = ""; + Token.Length = 0; + ctx = stack.Peek(); + HasNewlineChar = false; + break; + + case '[': + if (QuoteMode) + { + Token.Append(aJSON[i]); + break; + } + + stack.Push(new JSONArray()); + if (ctx != null) + { + ctx.Add(TokenName, stack.Peek()); + } + TokenName = ""; + Token.Length = 0; + ctx = stack.Peek(); + HasNewlineChar = false; + break; + + case '}': + case ']': + if (QuoteMode) + { + + Token.Append(aJSON[i]); + break; + } + if (stack.Count == 0) + throw new Exception("JSON Parse: Too many closing brackets"); + + stack.Pop(); + if (Token.Length > 0 || TokenIsQuoted) + ctx.Add(TokenName, ParseElement(Token.ToString(), TokenIsQuoted)); + if (ctx != null) + ctx.Inline = !HasNewlineChar; + TokenIsQuoted = false; + TokenName = ""; + Token.Length = 0; + if (stack.Count > 0) + ctx = stack.Peek(); + break; + + case ':': + if (QuoteMode) + { + Token.Append(aJSON[i]); + break; + } + TokenName = Token.ToString(); + Token.Length = 0; + TokenIsQuoted = false; + break; + + case '"': + QuoteMode ^= true; + TokenIsQuoted |= QuoteMode; + break; + + case ',': + if (QuoteMode) + { + Token.Append(aJSON[i]); + break; + } + if (Token.Length > 0 || TokenIsQuoted) + ctx.Add(TokenName, ParseElement(Token.ToString(), TokenIsQuoted)); + TokenIsQuoted = false; + TokenName = ""; + Token.Length = 0; + TokenIsQuoted = false; + break; + + case '\r': + case '\n': + HasNewlineChar = true; + break; + + case ' ': + case '\t': + if (QuoteMode) + Token.Append(aJSON[i]); + break; + + case '\\': + ++i; + if (QuoteMode) + { + char C = aJSON[i]; + switch (C) + { + case 't': + Token.Append('\t'); + break; + case 'r': + Token.Append('\r'); + break; + case 'n': + Token.Append('\n'); + break; + case 'b': + Token.Append('\b'); + break; + case 'f': + Token.Append('\f'); + break; + case 'u': + { + string s = aJSON.Substring(i + 1, 4); + Token.Append((char)int.Parse( + s, + System.Globalization.NumberStyles.AllowHexSpecifier)); + i += 4; + break; + } + default: + Token.Append(C); + break; + } + } + break; + case '/': + if (allowLineComments && !QuoteMode && i + 1 < aJSON.Length && aJSON[i + 1] == '/') + { + while (++i < aJSON.Length && aJSON[i] != '\n' && aJSON[i] != '\r') ; + break; + } + Token.Append(aJSON[i]); + break; + case '\uFEFF': // remove / ignore BOM (Byte Order Mark) + break; + + default: + Token.Append(aJSON[i]); + break; + } + ++i; + } + if (QuoteMode) + { + throw new Exception("JSON Parse: Quotation marks seems to be messed up."); + } + if (ctx == null) + return ParseElement(Token.ToString(), TokenIsQuoted); + return ctx; + } + + } + // End of JSONNode + + internal partial class JSONArray : JSONNode + { + private List m_List = new List(); + private bool inline = false; + public override bool Inline + { + get { return inline; } + set { inline = value; } + } + + public override JSONNodeType Tag { get { return JSONNodeType.Array; } } + public override bool IsArray { get { return true; } } + public override Enumerator GetEnumerator() { return new Enumerator(m_List.GetEnumerator()); } + + public override JSONNode this[int aIndex] + { + get + { + if (aIndex < 0 || aIndex >= m_List.Count) + return new JSONLazyCreator(this); + return m_List[aIndex]; + } + set + { + if (value == null) + value = JSONNull.CreateOrGet(); + if (aIndex < 0 || aIndex >= m_List.Count) + m_List.Add(value); + else + m_List[aIndex] = value; + } + } + + public override JSONNode this[string aKey] + { + get { return new JSONLazyCreator(this); } + set + { + if (value == null) + value = JSONNull.CreateOrGet(); + m_List.Add(value); + } + } + + public override int Count + { + get { return m_List.Count; } + } + + public override void Add(string aKey, JSONNode aItem) + { + if (aItem == null) + aItem = JSONNull.CreateOrGet(); + m_List.Add(aItem); + } + + public override JSONNode Remove(int aIndex) + { + if (aIndex < 0 || aIndex >= m_List.Count) + return null; + JSONNode tmp = m_List[aIndex]; + m_List.RemoveAt(aIndex); + return tmp; + } + + public override JSONNode Remove(JSONNode aNode) + { + m_List.Remove(aNode); + return aNode; + } + + public override void Clear() + { + m_List.Clear(); + } + + public override JSONNode Clone() + { + var node = new JSONArray(); + node.m_List.Capacity = m_List.Capacity; + foreach (var n in m_List) + { + if (n != null) + node.Add(n.Clone()); + else + node.Add(null); + } + return node; + } + + public override IEnumerable Children + { + get + { + foreach (JSONNode N in m_List) + yield return N; + } + } + + + internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) + { + aSB.Append('['); + int count = m_List.Count; + if (inline) + aMode = JSONTextMode.Compact; + for (int i = 0; i < count; i++) + { + if (i > 0) + aSB.Append(','); + if (aMode == JSONTextMode.Indent) + aSB.AppendLine(); + + if (aMode == JSONTextMode.Indent) + aSB.Append(' ', aIndent + aIndentInc); + m_List[i].WriteToStringBuilder(aSB, aIndent + aIndentInc, aIndentInc, aMode); + } + if (aMode == JSONTextMode.Indent) + aSB.AppendLine().Append(' ', aIndent); + aSB.Append(']'); + } + } + // End of JSONArray + + internal partial class JSONObject : JSONNode + { + private Dictionary m_Dict = new Dictionary(); + + private bool inline = false; + public override bool Inline + { + get { return inline; } + set { inline = value; } + } + + public override JSONNodeType Tag { get { return JSONNodeType.Object; } } + public override bool IsObject { get { return true; } } + + public override Enumerator GetEnumerator() { return new Enumerator(m_Dict.GetEnumerator()); } + + + public override JSONNode this[string aKey] + { + get + { + if (m_Dict.ContainsKey(aKey)) + return m_Dict[aKey]; + else + return new JSONLazyCreator(this, aKey); + } + set + { + if (value == null) + value = JSONNull.CreateOrGet(); + if (m_Dict.ContainsKey(aKey)) + m_Dict[aKey] = value; + else + m_Dict.Add(aKey, value); + } + } + + public override JSONNode this[int aIndex] + { + get + { + if (aIndex < 0 || aIndex >= m_Dict.Count) + return null; + return m_Dict.ElementAt(aIndex).Value; + } + set + { + if (value == null) + value = JSONNull.CreateOrGet(); + if (aIndex < 0 || aIndex >= m_Dict.Count) + return; + string key = m_Dict.ElementAt(aIndex).Key; + m_Dict[key] = value; + } + } + + public override int Count + { + get { return m_Dict.Count; } + } + + public override void Add(string aKey, JSONNode aItem) + { + if (aItem == null) + aItem = JSONNull.CreateOrGet(); + + if (aKey != null) + { + if (m_Dict.ContainsKey(aKey)) + m_Dict[aKey] = aItem; + else + m_Dict.Add(aKey, aItem); + } + else + m_Dict.Add(Guid.NewGuid().ToString(), aItem); + } + + public override JSONNode Remove(string aKey) + { + if (!m_Dict.ContainsKey(aKey)) + return null; + JSONNode tmp = m_Dict[aKey]; + m_Dict.Remove(aKey); + return tmp; + } + + public override JSONNode Remove(int aIndex) + { + if (aIndex < 0 || aIndex >= m_Dict.Count) + return null; + var item = m_Dict.ElementAt(aIndex); + m_Dict.Remove(item.Key); + return item.Value; + } + + public override JSONNode Remove(JSONNode aNode) + { + try + { + var item = m_Dict.Where(k => k.Value == aNode).First(); + m_Dict.Remove(item.Key); + return aNode; + } + catch + { + return null; + } + } + + public override void Clear() + { + m_Dict.Clear(); + } + + public override JSONNode Clone() + { + var node = new JSONObject(); + foreach (var n in m_Dict) + { + node.Add(n.Key, n.Value.Clone()); + } + return node; + } + + public override bool HasKey(string aKey) + { + return m_Dict.ContainsKey(aKey); + } + + public override JSONNode GetValueOrDefault(string aKey, JSONNode aDefault) + { + JSONNode res; + if (m_Dict.TryGetValue(aKey, out res)) + return res; + return aDefault; + } + + public override IEnumerable Children + { + get + { + foreach (KeyValuePair N in m_Dict) + yield return N.Value; + } + } + + internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) + { + aSB.Append('{'); + bool first = true; + if (inline) + aMode = JSONTextMode.Compact; + foreach (var k in m_Dict) + { + if (!first) + aSB.Append(','); + first = false; + if (aMode == JSONTextMode.Indent) + aSB.AppendLine(); + if (aMode == JSONTextMode.Indent) + aSB.Append(' ', aIndent + aIndentInc); + aSB.Append('\"').Append(Escape(k.Key)).Append('\"'); + if (aMode == JSONTextMode.Compact) + aSB.Append(':'); + else + aSB.Append(": "); + k.Value.WriteToStringBuilder(aSB, aIndent + aIndentInc, aIndentInc, aMode); + } + if (aMode == JSONTextMode.Indent) + aSB.AppendLine().Append(' ', aIndent); + aSB.Append('}'); + } + + } + // End of JSONObject + + internal partial class JSONString : JSONNode + { + private string m_Data; + + public override JSONNodeType Tag { get { return JSONNodeType.String; } } + public override bool IsString { get { return true; } } + + public override Enumerator GetEnumerator() { return new Enumerator(); } + + + public override string Value + { + get { return m_Data; } + set + { + m_Data = value; + } + } + + public JSONString(string aData) + { + m_Data = aData; + } + public override JSONNode Clone() + { + return new JSONString(m_Data); + } + + internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) + { + aSB.Append('\"').Append(Escape(m_Data)).Append('\"'); + } + public override bool Equals(object obj) + { + if (base.Equals(obj)) + return true; + string s = obj as string; + if (s != null) + return m_Data == s; + JSONString s2 = obj as JSONString; + if (s2 != null) + return m_Data == s2.m_Data; + return false; + } + public override int GetHashCode() + { + return m_Data.GetHashCode(); + } + public override void Clear() + { + m_Data = ""; + } + } + // End of JSONString + + internal partial class JSONNumber : JSONNode + { + private double m_Data; + + public override JSONNodeType Tag { get { return JSONNodeType.Number; } } + public override bool IsNumber { get { return true; } } + public override Enumerator GetEnumerator() { return new Enumerator(); } + + public override string Value + { + get { return m_Data.ToString(CultureInfo.InvariantCulture); } + set + { + double v; + if (double.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out v)) + m_Data = v; + } + } + + public override double AsDouble + { + get { return m_Data; } + set { m_Data = value; } + } + public override long AsLong + { + get { return (long)m_Data; } + set { m_Data = value; } + } + public override ulong AsULong + { + get { return (ulong)m_Data; } + set { m_Data = value; } + } + + public JSONNumber(double aData) + { + m_Data = aData; + } + + public JSONNumber(string aData) + { + Value = aData; + } + + public override JSONNode Clone() + { + return new JSONNumber(m_Data); + } + + internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) + { + aSB.Append(Value.ToString(CultureInfo.InvariantCulture)); + } + private static bool IsNumeric(object value) + { + return value is int || value is uint + || value is float || value is double + || value is decimal + || value is long || value is ulong + || value is short || value is ushort + || value is sbyte || value is byte; + } + public override bool Equals(object obj) + { + if (obj == null) + return false; + if (base.Equals(obj)) + return true; + JSONNumber s2 = obj as JSONNumber; + if (s2 != null) + return m_Data == s2.m_Data; + if (IsNumeric(obj)) + return Convert.ToDouble(obj) == m_Data; + return false; + } + public override int GetHashCode() + { + return m_Data.GetHashCode(); + } + public override void Clear() + { + m_Data = 0; + } + } + // End of JSONNumber + + internal partial class JSONBool : JSONNode + { + private bool m_Data; + + public override JSONNodeType Tag { get { return JSONNodeType.Boolean; } } + public override bool IsBoolean { get { return true; } } + public override Enumerator GetEnumerator() { return new Enumerator(); } + + public override string Value + { + get { return m_Data.ToString(); } + set + { + bool v; + if (bool.TryParse(value, out v)) + m_Data = v; + } + } + public override bool AsBool + { + get { return m_Data; } + set { m_Data = value; } + } + + public JSONBool(bool aData) + { + m_Data = aData; + } + + public JSONBool(string aData) + { + Value = aData; + } + + public override JSONNode Clone() + { + return new JSONBool(m_Data); + } + + internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) + { + aSB.Append((m_Data) ? "true" : "false"); + } + public override bool Equals(object obj) + { + if (obj == null) + return false; + if (obj is bool) + return m_Data == (bool)obj; + return false; + } + public override int GetHashCode() + { + return m_Data.GetHashCode(); + } + public override void Clear() + { + m_Data = false; + } + } + // End of JSONBool + + internal partial class JSONNull : JSONNode + { + static JSONNull m_StaticInstance = new JSONNull(); + public static bool reuseSameInstance = true; + public static JSONNull CreateOrGet() + { + if (reuseSameInstance) + return m_StaticInstance; + return new JSONNull(); + } + private JSONNull() { } + + public override JSONNodeType Tag { get { return JSONNodeType.NullValue; } } + public override bool IsNull { get { return true; } } + public override Enumerator GetEnumerator() { return new Enumerator(); } + + public override string Value + { + get { return "null"; } + set { } + } + public override bool AsBool + { + get { return false; } + set { } + } + + public override JSONNode Clone() + { + return CreateOrGet(); + } + + public override bool Equals(object obj) + { + if (object.ReferenceEquals(this, obj)) + return true; + return (obj is JSONNull); + } + public override int GetHashCode() + { + return 0; + } + + internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) + { + aSB.Append("null"); + } + } + // End of JSONNull + + internal partial class JSONLazyCreator : JSONNode + { + private JSONNode m_Node = null; + private string m_Key = null; + public override JSONNodeType Tag { get { return JSONNodeType.None; } } + public override Enumerator GetEnumerator() { return new Enumerator(); } + + public JSONLazyCreator(JSONNode aNode) + { + m_Node = aNode; + m_Key = null; + } + + public JSONLazyCreator(JSONNode aNode, string aKey) + { + m_Node = aNode; + m_Key = aKey; + } + + private T Set(T aVal) where T : JSONNode + { + if (m_Key == null) + m_Node.Add(aVal); + else + m_Node.Add(m_Key, aVal); + m_Node = null; // Be GC friendly. + return aVal; + } + + public override JSONNode this[int aIndex] + { + get { return new JSONLazyCreator(this); } + set { Set(new JSONArray()).Add(value); } + } + + public override JSONNode this[string aKey] + { + get { return new JSONLazyCreator(this, aKey); } + set { Set(new JSONObject()).Add(aKey, value); } + } + + public override void Add(JSONNode aItem) + { + Set(new JSONArray()).Add(aItem); + } + + public override void Add(string aKey, JSONNode aItem) + { + Set(new JSONObject()).Add(aKey, aItem); + } + + public static bool operator ==(JSONLazyCreator a, object b) + { + if (b == null) + return true; + return System.Object.ReferenceEquals(a, b); + } + + public static bool operator !=(JSONLazyCreator a, object b) + { + return !(a == b); + } + + public override bool Equals(object obj) + { + if (obj == null) + return true; + return System.Object.ReferenceEquals(this, obj); + } + + public override int GetHashCode() + { + return 0; + } + + public override int AsInt + { + get { Set(new JSONNumber(0)); return 0; } + set { Set(new JSONNumber(value)); } + } + + public override float AsFloat + { + get { Set(new JSONNumber(0.0f)); return 0.0f; } + set { Set(new JSONNumber(value)); } + } + + public override double AsDouble + { + get { Set(new JSONNumber(0.0)); return 0.0; } + set { Set(new JSONNumber(value)); } + } + + public override long AsLong + { + get + { + if (longAsString) + Set(new JSONString("0")); + else + Set(new JSONNumber(0.0)); + return 0L; + } + set + { + if (longAsString) + Set(new JSONString(value.ToString(CultureInfo.InvariantCulture))); + else + Set(new JSONNumber(value)); + } + } + + public override ulong AsULong + { + get + { + if (longAsString) + Set(new JSONString("0")); + else + Set(new JSONNumber(0.0)); + return 0L; + } + set + { + if (longAsString) + Set(new JSONString(value.ToString(CultureInfo.InvariantCulture))); + else + Set(new JSONNumber(value)); + } + } + + public override bool AsBool + { + get { Set(new JSONBool(false)); return false; } + set { Set(new JSONBool(value)); } + } + + public override JSONArray AsArray + { + get { return Set(new JSONArray()); } + } + + public override JSONObject AsObject + { + get { return Set(new JSONObject()); } + } + internal override void WriteToStringBuilder(StringBuilder aSB, int aIndent, int aIndentInc, JSONTextMode aMode) + { + aSB.Append("null"); + } + } + // End of JSONLazyCreator + + internal static class JSON + { + public static JSONNode Parse(string aJSON) + { + return JSONNode.Parse(aJSON); + } + } +} \ No newline at end of file diff --git a/Editor/SimpleJSON.cs.meta b/Editor/SimpleJSON.cs.meta new file mode 100644 index 0000000..ac83825 --- /dev/null +++ b/Editor/SimpleJSON.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ed8e7c93c781e48df93d063a3c0d2972 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Editor/VisualStudioCodeInstallation.cs b/Editor/VisualStudioCodeInstallation.cs index 55c6102..7a1b5a5 100644 --- a/Editor/VisualStudioCodeInstallation.cs +++ b/Editor/VisualStudioCodeInstallation.cs @@ -10,9 +10,8 @@ using System.IO; using System.Linq; using System.Text.RegularExpressions; using UnityEngine; +using SimpleJSON; using IOPath = System.IO.Path; -using System.Runtime.InteropServices; -using System.Text; namespace Microsoft.Unity.VisualStudio.Editor { @@ -44,7 +43,7 @@ namespace Microsoft.Unity.VisualStudio.Editor return null; return Directory - .EnumerateDirectories(extensionsPath, "visualstudiotoolsforunity.vstuc*") // publisherid.extensionid + .EnumerateDirectories(extensionsPath, $"{MicrosoftUnityExtensionId}*") // publisherid.extensionid .OrderByDescending(n => n) .FirstOrDefault(); } @@ -170,7 +169,7 @@ namespace Microsoft.Unity.VisualStudio.Editor } #if UNITY_EDITOR_LINUX - [DllImport ("libc")] + [System.Runtime.InteropServices.DllImport ("libc")] private static extern int readlink(string path, byte[] buffer, int buflen); internal static string GetRealPath(string path) @@ -193,123 +192,270 @@ namespace Microsoft.Unity.VisualStudio.Editor { try { - // see https://tattoocoder.com/recommending-vscode-extensions-within-your-open-source-projects/ var vscodeDirectory = IOPath.Combine(projectDirectory.NormalizePathSeparators(), ".vscode"); Directory.CreateDirectory(vscodeDirectory); - CreateRecommendedExtensionsFile(vscodeDirectory); - CreateSettingsFile(vscodeDirectory); - CreateLaunchFile(vscodeDirectory); + var enablePatch = !File.Exists(IOPath.Combine(vscodeDirectory, ".vstupatchdisable")); + + CreateRecommendedExtensionsFile(vscodeDirectory, enablePatch); + CreateSettingsFile(vscodeDirectory, enablePatch); + CreateLaunchFile(vscodeDirectory, enablePatch); } catch (IOException) { } } - private static void CreateLaunchFile(string vscodeDirectory) - { - var launchFile = IOPath.Combine(vscodeDirectory, "launch.json"); - if (File.Exists(launchFile)) - return; - - const string content = @"{ + private const string DefaultLaunchFileContent = @"{ ""version"": ""0.2.0"", ""configurations"": [ { ""name"": ""Attach to Unity"", ""type"": ""vstuc"", - ""request"": ""attach"", + ""request"": ""attach"" } ] }"; - File.WriteAllText(launchFile, content); + private static void CreateLaunchFile(string vscodeDirectory, bool enablePatch) + { + var launchFile = IOPath.Combine(vscodeDirectory, "launch.json"); + if (File.Exists(launchFile)) + { + if (enablePatch) + PatchLaunchFile(launchFile); + + return; + } + + File.WriteAllText(launchFile, DefaultLaunchFileContent); } - private static void CreateSettingsFile(string vscodeDirectory) + private static void PatchLaunchFile(string launchFile) + { + try + { + const string configurationsKey = "configurations"; + const string typeKey = "type"; + + var content = File.ReadAllText(launchFile); + var launch = JSONNode.Parse(content); + + var configurations = launch[configurationsKey] as JSONArray; + if (configurations == null) + { + configurations = new JSONArray(); + launch.Add(configurationsKey, configurations); + } + + if (configurations.Linq.Any(entry => entry.Value[typeKey].Value == "vstuc")) + return; + + var defaultContent = JSONNode.Parse(DefaultLaunchFileContent); + configurations.Add(defaultContent[configurationsKey][0]); + + WriteAllTextFromJObject(launchFile, launch); + } + catch (Exception) + { + // do not fail if we cannot patch the launch.json file + } + } + + private void CreateSettingsFile(string vscodeDirectory, bool enablePatch) { var settingsFile = IOPath.Combine(vscodeDirectory, "settings.json"); if (File.Exists(settingsFile)) - return; + { + if (enablePatch) + PatchSettingsFile(settingsFile); - const string content = @"{ - ""files.exclude"": - { - ""**/.DS_Store"":true, - ""**/.git"":true, - ""**/.vs"":true, - ""**/.gitmodules"":true, - ""**/.vsconfig"":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, - ""Logs/"":true, - ""logs/"":true, - ""ProjectSettings/"":true, - ""UserSettings/"":true, - ""temp/"":true, - ""Temp/"":true - }, - ""omnisharp.enableRoslynAnalyzers"": true + return; + } + + const string excludes = @" ""files.exclude"": { + ""**/.DS_Store"": true, + ""**/.git"": true, + ""**/.vs"": true, + ""**/.gitmodules"": true, + ""**/.vsconfig"": 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, + ""Logs/"": true, + ""logs/"": true, + ""ProjectSettings/"": true, + ""UserSettings/"": true, + ""temp/"": true, + ""Temp/"": true + }"; + + var content = @"{ +" + excludes + @", + ""dotnet.defaultSolution"": """ + IOPath.GetFileName(ProjectGenerator.SolutionFile()) + @""" }"; File.WriteAllText(settingsFile, content); } - private static void CreateRecommendedExtensionsFile(string vscodeDirectory) + private void PatchSettingsFile(string settingsFile) { - var extensionFile = IOPath.Combine(vscodeDirectory, "extensions.json"); - if (File.Exists(extensionFile)) - return; + try + { + const string excludesKey = "files.exclude"; + const string solutionKey = "dotnet.defaultSolution"; - const string content = @"{ + var content = File.ReadAllText(settingsFile); + var settings = JSONNode.Parse(content); + + var excludes = settings[excludesKey] as JSONObject; + if (excludes == null) + return; + + var patchList = new List(); + var patched = false; + + // Remove files.exclude for solution+project files in the project root + foreach (var exclude in excludes) + { + if (!bool.TryParse(exclude.Value, out var exc) || !exc) + continue; + + var key = exclude.Key; + + if (!key.EndsWith(".sln") && !key.EndsWith(".csproj")) + continue; + + if (!Regex.IsMatch(key, "^(\\*\\*[\\\\\\/])?\\*\\.(sln|csproj)$")) + continue; + + patchList.Add(key); + patched = true; + } + + // Check default solution + var defaultSolution = settings[solutionKey]; + var solutionFile = IOPath.GetFileName(ProjectGenerator.SolutionFile()); + if (defaultSolution == null || defaultSolution.Value != solutionFile) + { + settings[solutionKey] = solutionFile; + patched = true; + } + + if (!patched) + return; + + foreach (var patch in patchList) + excludes.Remove(patch); + + WriteAllTextFromJObject(settingsFile, settings); + } + catch (Exception) + { + // do not fail if we cannot patch the settings.json file + } + } + + private const string MicrosoftUnityExtensionId = "visualstudiotoolsforunity.vstuc"; + private const string DefaultRecommendedExtensionsContent = @"{ ""recommendations"": [ - ""visualstudiotoolsforunity.vstuc"" + """+ MicrosoftUnityExtensionId + @""" ] } "; - File.WriteAllText(extensionFile, content); + + private static void CreateRecommendedExtensionsFile(string vscodeDirectory, bool enablePatch) + { + // see https://tattoocoder.com/recommending-vscode-extensions-within-your-open-source-projects/ + var extensionFile = IOPath.Combine(vscodeDirectory, "extensions.json"); + if (File.Exists(extensionFile)) + { + if (enablePatch) + PatchRecommendedExtensionsFile(extensionFile); + + return; + } + + File.WriteAllText(extensionFile, DefaultRecommendedExtensionsContent); + } + + private static void PatchRecommendedExtensionsFile(string extensionFile) + { + try + { + const string recommendationsKey = "recommendations"; + + var content = File.ReadAllText(extensionFile); + var extensions = JSONNode.Parse(content); + + var recommendations = extensions[recommendationsKey] as JSONArray; + if (recommendations == null) + { + recommendations = new JSONArray(); + extensions.Add(recommendationsKey, recommendations); + } + + if (recommendations.Linq.Any(entry => entry.Value.Value == MicrosoftUnityExtensionId)) + return; + + recommendations.Add(MicrosoftUnityExtensionId); + WriteAllTextFromJObject(extensionFile, extensions); + } + catch (Exception) + { + // do not fail if we cannot patch the extensions.json file + } + } + + private static void WriteAllTextFromJObject(string file, JSONNode node) + { + using (var fs = File.Open(file, FileMode.Create)) + using (var sw = new StreamWriter(fs)) + { + // Keep formatting/indent in sync with default contents + sw.Write(node.ToString(aIndent: 4)); + } } public override bool Open(string path, int line, int column, string solution) diff --git a/Editor/VisualStudioEditor.cs b/Editor/VisualStudioEditor.cs index a956699..a4f93ea 100644 --- a/Editor/VisualStudioEditor.cs +++ b/Editor/VisualStudioEditor.cs @@ -55,6 +55,12 @@ namespace Microsoft.Unity.VisualStudio.Editor if (editor is VisualStudioEditor) return; + // only disable the com.unity.ide.vscode package + var assembly = editor.GetType().Assembly; + var assemblyName = assembly.GetName().Name; + if (assemblyName != "Unity.VSCode.Editor") + return; + CodeEditor.Unregister(editor); } #endif diff --git a/Editor/VisualStudioForWindowsInstallation.cs b/Editor/VisualStudioForWindowsInstallation.cs index c193c17..6138df2 100644 --- a/Editor/VisualStudioForWindowsInstallation.cs +++ b/Editor/VisualStudioForWindowsInstallation.cs @@ -228,7 +228,24 @@ namespace Microsoft.Unity.VisualStudio.Editor if (string.IsNullOrWhiteSpace(progpath)) return Enumerable.Empty(); - var result = ProcessRunner.StartAndWaitForExit(progpath, "-prerelease -format json -utf8"); + const string arguments = "-prerelease -format json"; + + // We've seen issues with json parsing in utf8 mode and with specific non-UTF code pages like 949 (Korea) + // So try with utf8 first, then fallback to non utf8 in case of an issue + // See https://github.com/microsoft/vswhere/issues/264 + try + { + return QueryVsWhere(progpath, $"{arguments} -utf8"); + } + catch + { + return QueryVsWhere(progpath, $"{arguments}"); + } + } + + private static IEnumerable QueryVsWhere(string progpath, string arguments) + { + var result = ProcessRunner.StartAndWaitForExit(progpath, arguments); if (!result.Success) throw new Exception($"Failure while running vswhere: {result.Error}"); diff --git a/package.json b/package.json index b6a3971..a7d9aaa 100644 --- a/package.json +++ b/package.json @@ -2,25 +2,25 @@ "name": "com.unity.ide.visualstudio", "displayName": "Visual Studio Editor", "description": "Code editor integration for supporting Visual Studio as code editor for unity. Adds support for generating csproj files for intellisense purposes, auto discovery of installations, etc.", - "version": "2.0.20", + "version": "2.0.21", "unity": "2019.4", "unityRelease": "25f1", "dependencies": { "com.unity.test-framework": "1.1.9" }, "relatedPackages": { - "com.unity.ide.visualstudio.tests": "2.0.20" + "com.unity.ide.visualstudio.tests": "2.0.21" }, "_upm": { - "changelog": "Integration:\n\n- Internal API refactoring." + "changelog": "Integration:\n\n- Only disable the legacy `com.unity.ide.vscode` package going forward.\n- Fix json parsing issues with specific non-UTF code pages.\n\nProject generation:\n\n- Target `netstandard2.1` instead of `netstandard2.0`.\n- Set `defaultSolution` in `settings.json`.\n- Remove `files.exclude` entries for root `csproj` and `sln` files in `settings.json` when needed.\n- Add `vstuc` launch configuration to `launch.json` when needed.\n- Add `visualstudiotoolsforunity.vstuc` entry to `extensions.json` when needed.\n- You can prevent the package from patching those configuration files by creating a `.vscode/.vstupatchdisable` file." }, "upmCi": { - "footprint": "7d769a8558c7768417b16fc2ac8477cf69234049" + "footprint": "33ecd838a074080a82176ca86b21c837ba396a28" }, "documentationUrl": "https://docs.unity3d.com/Packages/com.unity.ide.visualstudio@2.0/manual/index.html", "repository": { "url": "https://github.cds.internal.unity3d.com/unity/com.unity.ide.visualstudio.git", "type": "git", - "revision": "b7bf23d23806ac75645bfa12acadcfc11468a383" + "revision": "eb2b500b99f6429d3d9dcb8acbcbd366dfbb42be" } }