diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..1ca17e4a9 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,24 @@ +# Set the default behavior (used when a rule below doesn't match) +* text=auto +*.cs text +*.R text +*.*proj text +*.targets text +*.settings text +*.vssettings text + +*.dll -text +*.lib -text +*.sln -text +*.ico -text +*.bmp -text +*.png -text +*.snk -text +*.mht -text +*.pickle -text +*.Rdata -text +*.Rhistory -text + +# Some Windows-specific files should always be CRLF +*.bat eol=crlf +*.cmd eol=crlf diff --git a/lib/Microsoft.Office.Interop.Excel.dll b/lib/Microsoft.Office.Interop.Excel.dll new file mode 100644 index 000000000..08b401411 Binary files /dev/null and b/lib/Microsoft.Office.Interop.Excel.dll differ diff --git a/src/CodeCoverage.runsettings b/src/CodeCoverage.runsettings index a55fe141b..711c639a3 100644 --- a/src/CodeCoverage.runsettings +++ b/src/CodeCoverage.runsettings @@ -25,6 +25,8 @@ ^boost::.* ^websocketpp::.* ^__sc.* + .*Resources.* + .*ClassificationFormat.* diff --git a/src/Common/Core/Impl/Extensions/StringExtensions.cs b/src/Common/Core/Impl/Extensions/StringExtensions.cs index 0e8ece546..c9ec1746c 100644 --- a/src/Common/Core/Impl/Extensions/StringExtensions.cs +++ b/src/Common/Core/Impl/Extensions/StringExtensions.cs @@ -46,5 +46,35 @@ public static string Replace(this string s, string oldValue, string newValue, in .Replace(oldValue, newValue, start, length) .ToString(); } + + public static string RemoveWhiteSpaceLines(this string text) { + var sb = new StringBuilder(text); + var lineBreakIndex = sb.Length; + var isWhiteSpaceOnly = true; + for (var i = sb.Length - 1; i >= 0; i--) { + var ch = sb[i]; + if (ch == '\r' || ch == '\n') { + if (ch == '\n' && i > 0 && sb[i - 1] == '\r') { + i--; + } + + if (isWhiteSpaceOnly) { + sb.Remove(i, lineBreakIndex - i); + } else if (i == 0) { + var rn = sb.Length > 1 && sb[0] == '\r' && sb[1] == '\n'; + sb.Remove(0, rn ? 2 : 1); + break; + } + + lineBreakIndex = i; + isWhiteSpaceOnly = true; + } + + isWhiteSpaceOnly = isWhiteSpaceOnly && char.IsWhiteSpace(ch); + } + + return sb.ToString(); + } + } } diff --git a/src/Common/Core/Impl/Microsoft.Common.Core.csproj b/src/Common/Core/Impl/Microsoft.Common.Core.csproj index 82d51b5f4..a160aca6e 100644 --- a/src/Common/Core/Impl/Microsoft.Common.Core.csproj +++ b/src/Common/Core/Impl/Microsoft.Common.Core.csproj @@ -36,6 +36,7 @@ Properties\GlobalAssemblyInfo.cs + diff --git a/src/Common/Core/Impl/Properties/AssemblyInfo.cs b/src/Common/Core/Impl/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..1b7d2618b --- /dev/null +++ b/src/Common/Core/Impl/Properties/AssemblyInfo.cs @@ -0,0 +1,11 @@ +using System.Runtime.CompilerServices; + +#if SIGN +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.R.Package.Test, PublicKey=002400000480000094000000060200000024000052534131000400000100010007D1FA57C4AED9F0A32E84AA0FAEFD0DE9E8FD6AEC8F87FB03766C834C99921EB23BE79AD9D5DCC1DD9AD236132102900B723CF980957FC4E177108FC607774F29E8320E92EA05ECE4E821C0A5EFE8F1645C4C0C93C1AB99285D622CAA652C1DFAD63D745D6F2DE5F17E5EAF0FC4963D261C8A12436518206DC093344D5AD293")] +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.R.Interactive.Test, PublicKey=002400000480000094000000060200000024000052534131000400000100010007D1FA57C4AED9F0A32E84AA0FAEFD0DE9E8FD6AEC8F87FB03766C834C99921EB23BE79AD9D5DCC1DD9AD236132102900B723CF980957FC4E177108FC607774F29E8320E92EA05ECE4E821C0A5EFE8F1645C4C0C93C1AB99285D622CAA652C1DFAD63D745D6F2DE5F17E5EAF0FC4963D261C8A12436518206DC093344D5AD293")] +[assembly: InternalsVisibleTo("Microsoft.R.Common.Core.Test, PublicKey=002400000480000094000000060200000024000052534131000400000100010007D1FA57C4AED9F0A32E84AA0FAEFD0DE9E8FD6AEC8F87FB03766C834C99921EB23BE79AD9D5DCC1DD9AD236132102900B723CF980957FC4E177108FC607774F29E8320E92EA05ECE4E821C0A5EFE8F1645C4C0C93C1AB99285D622CAA652C1DFAD63D745D6F2DE5F17E5EAF0FC4963D261C8A12436518206DC093344D5AD293")] +#else +[assembly: InternalsVisibleTo("Microsoft.R.Common.Core.Test")] +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.R.Package.Test")] +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.R.Interactive.Test")] +#endif \ No newline at end of file diff --git a/src/Common/Core/Impl/Shell/ICoreShell.cs b/src/Common/Core/Impl/Shell/ICoreShell.cs index c661c430e..4e6c88ea0 100644 --- a/src/Common/Core/Impl/Shell/ICoreShell.cs +++ b/src/Common/Core/Impl/Shell/ICoreShell.cs @@ -51,6 +51,15 @@ public interface ICoreShell: ICompositionCatalog { /// void ShowErrorMessage(string message); + /// + /// Shows the context menu with the specified command ID at the specified location + /// + /// + /// + /// + /// + void ShowContextMenu(Guid contextMenuGroup, int contextMenuId, int x, int y); + /// /// Displays message with specified buttons in a host-specific UI /// diff --git a/src/Common/Core/Impl/TaskUtilities.cs b/src/Common/Core/Impl/TaskUtilities.cs index abfec6a9a..bb24b28ca 100644 --- a/src/Common/Core/Impl/TaskUtilities.cs +++ b/src/Common/Core/Impl/TaskUtilities.cs @@ -6,6 +6,8 @@ namespace Microsoft.Common.Core { public static class TaskUtilities { + public static Task CompletedTask = Task.FromResult(0); + public static bool IsOnBackgroundThread() { var taskScheduler = TaskScheduler.Current; var syncContext = SynchronizationContext.Current; diff --git a/src/Common/Core/Impl/Telemetry/ITelemetryRecorder.cs b/src/Common/Core/Impl/Telemetry/ITelemetryRecorder.cs index 69f469cfb..840448a07 100644 --- a/src/Common/Core/Impl/Telemetry/ITelemetryRecorder.cs +++ b/src/Common/Core/Impl/Telemetry/ITelemetryRecorder.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System; namespace Microsoft.Common.Core.Telemetry { /// @@ -6,7 +6,7 @@ namespace Microsoft.Common.Core.Telemetry { /// the telemetry service. In Visual Studio environment maps to IVsTelemetryService /// whereas in tests can be replaced by an object that writes events to a string. /// - public interface ITelemetryRecorder { + public interface ITelemetryRecorder : IDisposable { /// /// True if telemetry is actually being recorded /// diff --git a/src/Common/Core/Impl/Telemetry/TelemetryArea.cs b/src/Common/Core/Impl/Telemetry/TelemetryArea.cs index 467f74fd8..6e22cfc27 100644 --- a/src/Common/Core/Impl/Telemetry/TelemetryArea.cs +++ b/src/Common/Core/Impl/Telemetry/TelemetryArea.cs @@ -8,6 +8,7 @@ namespace Microsoft.Common.Core.Telemetry { public enum TelemetryArea { // Keep these sorted Build, + Configuration, Debugger, Editor, History, diff --git a/src/Common/Core/Impl/Telemetry/TelemetryServiceBase.cs b/src/Common/Core/Impl/Telemetry/TelemetryServiceBase.cs index 939a6e01a..66a190e45 100644 --- a/src/Common/Core/Impl/Telemetry/TelemetryServiceBase.cs +++ b/src/Common/Core/Impl/Telemetry/TelemetryServiceBase.cs @@ -1,11 +1,12 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Microsoft.Common.Core.Diagnostics; namespace Microsoft.Common.Core.Telemetry { /// /// Base telemetry service implementation, common to production code and test cases. /// - public abstract class TelemetryServiceBase : ITelemetryService { + public abstract class TelemetryServiceBase : ITelemetryService, IDisposable { public string EventNamePrefix { get; private set; } public string PropertyNamePrefix { get; private set; } @@ -51,17 +52,21 @@ public bool CanCollectPrivateInformation { public void ReportEvent(TelemetryArea area, string eventName, object parameters = null) { Check.ArgumentStringNullOrEmpty("eventName", eventName); - IDictionary dict = DictionaryExtension.FromAnonymousObject(parameters); - IDictionary dictWithPrefix = new Dictionary(); + string completeEventName = MakeEventName(area, eventName); + if (parameters == null) { + this.TelemetryRecorder.RecordEvent(completeEventName); + } else if (parameters is string) { + this.TelemetryRecorder.RecordEvent(completeEventName, parameters as string); + } else { + IDictionary dict = DictionaryExtension.FromAnonymousObject(parameters); + IDictionary dictWithPrefix = new Dictionary(); - foreach (KeyValuePair kvp in dict) { - Check.ArgumentStringNullOrEmpty("parameterName", kvp.Key); - Check.ArgumentNull("parameterValue", kvp.Value); - - dictWithPrefix[this.PropertyNamePrefix + area.ToString() + "." + kvp.Key] = kvp.Value; + foreach (KeyValuePair kvp in dict) { + Check.ArgumentStringNullOrEmpty("parameterName", kvp.Key); + dictWithPrefix[this.PropertyNamePrefix + area.ToString() + "." + kvp.Key] = kvp.Value ?? string.Empty; + } + this.TelemetryRecorder.RecordEvent(completeEventName, dictWithPrefix); } - - this.TelemetryRecorder.RecordEvent(this.EventNamePrefix + area.ToString() + "/" + eventName, dictWithPrefix); } /// @@ -69,5 +74,17 @@ public void ReportEvent(TelemetryArea area, string eventName, object parameters /// public abstract ITelemetryActivity StartActivity(TelemetryArea area, string eventName); #endregion + + private string MakeEventName(TelemetryArea area, string eventName) { + return this.EventNamePrefix + area.ToString() + "/" + eventName; + } + + protected virtual void Dispose(bool disposing) { } + + public void Dispose() { + Dispose(true); + TelemetryRecorder?.Dispose(); + TelemetryRecorder = null; + } } } diff --git a/src/Common/Core/Test/Controls/ControlTestScript.cs b/src/Common/Core/Test/Controls/ControlTestScript.cs index d388fb8b2..085f37c42 100644 --- a/src/Common/Core/Test/Controls/ControlTestScript.cs +++ b/src/Common/Core/Test/Controls/ControlTestScript.cs @@ -1,5 +1,7 @@ using System; using System.Diagnostics.CodeAnalysis; +using System.Windows; +using System.Xml.Serialization; using Microsoft.Common.Core.Test.Script; using Microsoft.Common.Core.Test.Utility; using Microsoft.UnitTests.Core.Threading; @@ -21,6 +23,12 @@ public void Dispose() { ControlWindow.Close(); } + public DependencyObject Control { + get { + return ControlWindow.Control; + } + } + public string WriteVisualTree(bool writeProperties = true) { VisualTreeWriter w = new VisualTreeWriter(); string tree = null; diff --git a/src/Common/Core/Test/Controls/ControlWindow.cs b/src/Common/Core/Test/Controls/ControlWindow.cs index 50feda0a6..03667c9fc 100644 --- a/src/Common/Core/Test/Controls/ControlWindow.cs +++ b/src/Common/Core/Test/Controls/ControlWindow.cs @@ -13,7 +13,7 @@ namespace Microsoft.Common.Core.Test.Controls { /// Control window /// [ExcludeFromCodeCoverage] - internal static class ControlWindow { + public static class ControlWindow { [ExcludeFromCodeCoverage] class ControlTestRequest { public Type ControlType { get; } @@ -23,10 +23,17 @@ public ControlTestRequest(Type controlType) { } } + /// + /// Component that is being tested. May be same as + /// or may be different if component is + /// + public static object Component { get; private set; } + /// /// Control that is being tested /// public static Control Control { get; private set; } + /// /// WPF window that contains the control /// @@ -59,7 +66,14 @@ private static void CreateWindowInstance(ControlTestRequest request, ManualReset Window.Width = 800; Window.Height = 600; - Control = Activator.CreateInstance(request.ControlType) as Control; + Component = Activator.CreateInstance(request.ControlType); + if(Component is Control) { + Control = Component as Control; + } + else { + Control = Component.GetType().GetProperty("Control").GetValue(Component) as Control; + } + Window.Title = "Control - " + request.ControlType.ToString(); Window.Content = Control; @@ -76,6 +90,10 @@ public static void Close() { var action = new Action(() => { IDisposable disp = Window.Content as IDisposable; disp?.Dispose(); + + disp = Component as IDisposable; + disp?.Dispose(); + Window.Close(); }); diff --git a/src/Common/Core/Test/Microsoft.Common.Core.Test.csproj b/src/Common/Core/Test/Microsoft.Common.Core.Test.csproj index ccae4dba1..fc2b3ae1b 100644 --- a/src/Common/Core/Test/Microsoft.Common.Core.Test.csproj +++ b/src/Common/Core/Test/Microsoft.Common.Core.Test.csproj @@ -98,6 +98,7 @@ + @@ -111,7 +112,7 @@ - + diff --git a/src/Common/Core/Test/Script/EventsPump.cs b/src/Common/Core/Test/Script/EventsPump.cs new file mode 100644 index 000000000..7d0c11903 --- /dev/null +++ b/src/Common/Core/Test/Script/EventsPump.cs @@ -0,0 +1,17 @@ +using System.Threading; +using Microsoft.UnitTests.Core.Threading; + +namespace Microsoft.Common.Core.Test.Script { + public static class EventsPump { + public static void DoEvents(int ms) { + UIThreadHelper.Instance.Invoke(() => { + int time = 0; + while (time < ms) { + TestScript.DoEvents(); + Thread.Sleep(20); + time += 20; + } + }); + } + } +} diff --git a/src/Common/Core/Test/StringExtensionsTest.cs b/src/Common/Core/Test/StringExtensionsTest.cs index ef4865496..ca5578064 100644 --- a/src/Common/Core/Test/StringExtensionsTest.cs +++ b/src/Common/Core/Test/StringExtensionsTest.cs @@ -17,5 +17,32 @@ public class StringExtensionsTest { public void Replace(string s, string oldValue, string newValue, int start, int length, string expected) { s.Replace(oldValue, newValue, start, length).Should().Be(expected); } + + [CompositeTest] + [InlineData("\n", "")] + [InlineData("\r\n", "")] + [InlineData("\r\r\n", "")] + [InlineData("aa", "aa")] + [InlineData("aa\n", "aa")] + [InlineData("aa\r", "aa")] + [InlineData("aa\r\n", "aa")] + [InlineData("\raa", "aa")] + [InlineData("\naa", "aa")] + [InlineData("\r\naa", "aa")] + [InlineData(" aa\r\n", " aa")] + [InlineData("\r\n aa\r\n", " aa")] + [InlineData("aa\nbb", "aa\nbb")] + [InlineData("aa\rbb", "aa\rbb")] + [InlineData("aa\r\nbb", "aa\r\nbb")] + [InlineData("aa\n\r\nbb", "aa\r\nbb")] + [InlineData("aa\r\n\r\nbb", "aa\r\nbb")] + [InlineData("aa\r\nbb\n", "aa\r\nbb")] + [InlineData("aa\n\r\nbb\r\n", "aa\r\nbb")] + [InlineData("aa\r\n\r\nbb\r", "aa\r\nbb")] + [InlineData("aa\r\n \r\nbb\r\n", "aa\r\nbb")] + [InlineData("aa\r\n \r\nbb\r\n \r\n cc \r\n", "aa\r\nbb\r\n cc ")] + public void RemoveWhiteSpaceLines(string s, string expected) { + s.RemoveWhiteSpaceLines().Should().Be(expected); + } } } \ No newline at end of file diff --git a/src/Common/Core/Test/Telemetry/FileTelemetryRecorder.cs b/src/Common/Core/Test/Telemetry/FileTelemetryRecorder.cs index da79966af..990b71b84 100644 --- a/src/Common/Core/Test/Telemetry/FileTelemetryRecorder.cs +++ b/src/Common/Core/Test/Telemetry/FileTelemetryRecorder.cs @@ -65,5 +65,7 @@ public void RecordActivity(object telemetryActivity) { } } } + + public void Dispose() { } } } diff --git a/src/Common/Core/Test/Telemetry/StringTelemetryRecorderTests.cs b/src/Common/Core/Test/Telemetry/StringTelemetryRecorderTests.cs index 6794f3c9d..27fccc4ed 100644 --- a/src/Common/Core/Test/Telemetry/StringTelemetryRecorderTests.cs +++ b/src/Common/Core/Test/Telemetry/StringTelemetryRecorderTests.cs @@ -10,7 +10,7 @@ public class StringTelemetryRecorderTests { [CompositeTest] [InlineData("event")] public void StringTelemetryRecorder_SimpleEventTest(string eventName) { - var telemetryRecorder = new StringTelemetryRecorder(); + var telemetryRecorder = new TestTelemetryRecorder(); telemetryRecorder.RecordEvent(eventName); string log = telemetryRecorder.SessionLog; @@ -20,7 +20,7 @@ public void StringTelemetryRecorder_SimpleEventTest(string eventName) { [CompositeTest] [InlineData("event", "parameter1", "value1", "parameter2", "value2")] public void StringTelemetryRecorder_EventWithDictionaryTest(string eventName, string parameter1, string value1, string parameter2, string value2) { - var telemetryRecorder = new StringTelemetryRecorder(); + var telemetryRecorder = new TestTelemetryRecorder(); telemetryRecorder.RecordEvent(eventName, new Dictionary() { { parameter1, value1 }, { parameter2, value2 } }); string log = telemetryRecorder.SessionLog; @@ -30,7 +30,7 @@ public void StringTelemetryRecorder_EventWithDictionaryTest(string eventName, st [CompositeTest] [InlineData("event")] public void StringTelemetryRecorder_EventWithAnonymousCollectionTest(string eventName) { - var telemetryRecorder = new StringTelemetryRecorder(); + var telemetryRecorder = new TestTelemetryRecorder(); telemetryRecorder.RecordEvent(eventName, new { parameter1 = "value1", parameter2 = "value2" }); string log = telemetryRecorder.SessionLog; diff --git a/src/Common/Core/Test/Telemetry/TelemetryTestService.cs b/src/Common/Core/Test/Telemetry/TelemetryTestService.cs index 31f8964b7..3fdb0d3fe 100644 --- a/src/Common/Core/Test/Telemetry/TelemetryTestService.cs +++ b/src/Common/Core/Test/Telemetry/TelemetryTestService.cs @@ -9,7 +9,7 @@ public sealed class TelemetryTestService : TelemetryServiceBase, ITelemetryTestS public static readonly string PropertyNamePrefixString = "Test.RTVS."; public TelemetryTestService(string eventNamePrefix, string propertyNamePrefix) : - base(eventNamePrefix, propertyNamePrefix, new StringTelemetryRecorder()) { + base(eventNamePrefix, propertyNamePrefix, new TestTelemetryRecorder()) { } diff --git a/src/Common/Core/Test/Telemetry/StringTelemetryRecorder.cs b/src/Common/Core/Test/Telemetry/TestTelemetryRecorder.cs similarity index 83% rename from src/Common/Core/Test/Telemetry/StringTelemetryRecorder.cs rename to src/Common/Core/Test/Telemetry/TestTelemetryRecorder.cs index e285c710c..e3b1bc853 100644 --- a/src/Common/Core/Test/Telemetry/StringTelemetryRecorder.cs +++ b/src/Common/Core/Test/Telemetry/TestTelemetryRecorder.cs @@ -7,7 +7,7 @@ namespace Microsoft.Common.Core.Test.Telemetry { [ExcludeFromCodeCoverage] - public sealed class StringTelemetryRecorder : ITelemetryRecorder, ITelemetryTestSupport { + public sealed class TestTelemetryRecorder : ITelemetryRecorder, ITelemetryTestSupport { private StringBuilder stringBuilder = new StringBuilder(); #region ITelemetryRecorder @@ -22,7 +22,11 @@ public bool CanCollectPrivateInformation { public void RecordEvent(string eventName, object parameters = null) { this.stringBuilder.AppendLine(eventName); if (parameters != null) { - WriteDictionary(DictionaryExtension.FromAnonymousObject(parameters)); + if (parameters is string) { + WriteProperty("Value", parameters as string); + } else { + WriteDictionary(DictionaryExtension.FromAnonymousObject(parameters)); + } } } @@ -33,13 +37,6 @@ public void RecordActivity(object telemetryActivity) { WriteDictionary(activity.Properties); } } - - public string SerializeSession() { - return string.Empty; - } - - public void Dispose() { - } #endregion #region ITelemetryTestSupport @@ -52,6 +49,8 @@ public string SessionLog { } #endregion + public void Dispose() { } + private void WriteDictionary(IDictionary dict) { foreach (KeyValuePair kvp in dict) { WriteProperty(kvp.Key, kvp.Value); diff --git a/src/Common/Core/Test/Utility/SupportedWpfProperties.cs b/src/Common/Core/Test/Utility/SupportedWpfProperties.cs index bb7815e60..9195976dc 100644 --- a/src/Common/Core/Test/Utility/SupportedWpfProperties.cs +++ b/src/Common/Core/Test/Utility/SupportedWpfProperties.cs @@ -6,7 +6,7 @@ namespace Microsoft.Common.Core.Test.Utility { /// Properties written by the /// [ExcludeFromCodeCoverage] - internal static class SupportedWpfProperties { + public static class SupportedWpfProperties { private static HashSet _hashset; public static bool IsSupported(string name) { diff --git a/src/Debugger/Engine/Impl/AD7BoundBreakpoint.cs b/src/Debugger/Engine/Impl/AD7BoundBreakpoint.cs index 526021bf9..70af4ec41 100644 --- a/src/Debugger/Engine/Impl/AD7BoundBreakpoint.cs +++ b/src/Debugger/Engine/Impl/AD7BoundBreakpoint.cs @@ -1,5 +1,7 @@ using System; using System.Diagnostics; +using Microsoft.Common.Core; +using Microsoft.R.Host.Client; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Debugger.Interop; using static System.FormattableString; @@ -20,8 +22,7 @@ public AD7BoundBreakpoint(AD7PendingBreakpoint pendingBreakpoint, DebugBreakpoin } int IDebugBoundBreakpoint2.Delete() { - SetState(enum_BP_STATE.BPS_DELETED); - return VSConstants.S_OK; + return SetState(enum_BP_STATE.BPS_DELETED); } int IDebugBoundBreakpoint2.Enable(int fEnable) { @@ -30,8 +31,7 @@ int IDebugBoundBreakpoint2.Enable(int fEnable) { return VSConstants.E_FAIL; } - SetState(_state = fEnable == 0 ? enum_BP_STATE.BPS_DISABLED : enum_BP_STATE.BPS_ENABLED); - return VSConstants.S_OK; + return SetState(_state = fEnable == 0 ? enum_BP_STATE.BPS_DISABLED : enum_BP_STATE.BPS_ENABLED); } int IDebugBoundBreakpoint2.GetBreakpointResolution(out IDebugBreakpointResolution2 ppBPResolution) { @@ -66,24 +66,41 @@ int IDebugBoundBreakpoint2.SetPassCount(BP_PASSCOUNT bpPassCount) { return VSConstants.E_NOTIMPL; } - private void SetState(enum_BP_STATE state) { + private int SetState(enum_BP_STATE state) { if (_state == enum_BP_STATE.BPS_ENABLED) { if (state == enum_BP_STATE.BPS_DISABLED || state == enum_BP_STATE.BPS_DELETED) { if (DebugBreakpoint != null) { DebugBreakpoint.BreakpointHit -= DebugBreakpoint_BreakpointHit; if (Engine.IsConnected) { - DebugBreakpoint.DeleteAsync().GetResultOnUIThread(); + if (Engine.IsProgramDestroyed) { + // If engine is shutting down, do not wait for the delete eval to complete, to avoid + // blocking debugger detach if a long-running operation is in progress. This way the + // engine can just report successful detach right away, and breakpoints are deleted + // later, but as soon as it's actually possible. + DebugBreakpoint.DeleteAsync() + .SilenceException() + .SilenceException() + .DoNotWait(); + } else { + TaskExtensions.RunSynchronouslyOnUIThread(ct => DebugBreakpoint.DeleteAsync(ct)); + } } } } } else { if (state == enum_BP_STATE.BPS_ENABLED) { - DebugBreakpoint = Engine.DebugSession.CreateBreakpointAsync(Location).GetResultOnUIThread(); + if (Engine.IsProgramDestroyed) { + // Do not allow enabling breakpoints when engine is shutting down. + return VSConstants.E_ABORT; + } + + DebugBreakpoint = TaskExtensions.RunSynchronouslyOnUIThread(ct => Engine.DebugSession.CreateBreakpointAsync(Location, ct)); DebugBreakpoint.BreakpointHit += DebugBreakpoint_BreakpointHit; } } _state = state; + return VSConstants.S_OK; } private void DebugBreakpoint_BreakpointHit(object sender, EventArgs e) { diff --git a/src/Debugger/Engine/Impl/AD7CustomViewer.cs b/src/Debugger/Engine/Impl/AD7CustomViewer.cs new file mode 100644 index 000000000..6025c8893 --- /dev/null +++ b/src/Debugger/Engine/Impl/AD7CustomViewer.cs @@ -0,0 +1,23 @@ +using System; +using System.Runtime.InteropServices; +using Microsoft.VisualStudio; +using Microsoft.VisualStudio.Debugger.Interop; + +namespace Microsoft.R.Debugger.Engine { + [ComVisible(true)] + [Guid(DebuggerGuids.CustomViewerString)] + public class AD7CustomViewer : IDebugCustomViewer { + public AD7CustomViewer() { + } + + public int DisplayValue(IntPtr hwnd, uint dwID, object pHostServices, IDebugProperty3 pDebugProperty) { + var property = pDebugProperty as AD7Property; + if (property == null || property.StackFrame.Engine.GridViewProvider == null) { + return VSConstants.E_FAIL; + } + + property.StackFrame.Engine.GridViewProvider.ShowDataGrid(property.EvaluationResult); + return VSConstants.S_OK; + } + } +} diff --git a/src/Debugger/Engine/Impl/AD7Engine.cs b/src/Debugger/Engine/Impl/AD7Engine.cs index 782c41ec7..69d86cf4a 100644 --- a/src/Debugger/Engine/Impl/AD7Engine.cs +++ b/src/Debugger/Engine/Impl/AD7Engine.cs @@ -2,6 +2,8 @@ using System.ComponentModel.Composition; using System.Diagnostics; using System.Runtime.InteropServices; +using System.Threading; +using System.Threading.Tasks; using Microsoft.Common.Core; using Microsoft.R.Debugger.Engine.PortSupplier; using Microsoft.R.Host.Client; @@ -12,7 +14,7 @@ using Microsoft.VisualStudio.Shell.Interop; using static System.FormattableString; using Task = System.Threading.Tasks.Task; - + namespace Microsoft.R.Debugger.Engine { [ComVisible(true)] [Guid(DebuggerGuids.DebugEngineCLSIDString)] @@ -30,6 +32,7 @@ public sealed class AD7Engine : IDebugEngine2, IDebugEngineLaunch2, IDebugProgra internal bool IsDisposed { get; private set; } internal bool IsConnected { get; private set; } + internal bool IsProgramDestroyed { get; private set; } internal DebugSession DebugSession { get; private set; } internal AD7Thread MainThread { get; private set; } @@ -39,6 +42,9 @@ public sealed class AD7Engine : IDebugEngine2, IDebugEngineLaunch2, IDebugProgra [Import] private IDebugSessionProvider DebugSessionProvider { get; set; } + [Import] + internal IDebugGridViewProvider GridViewProvider { get; set; } + public AD7Engine() { var compModel = (IComponentModel)Package.GetGlobalService(typeof(SComponentModel)); if (compModel == null) { @@ -70,7 +76,10 @@ public void Dispose() { IsDisposed = true; - ExitBrowserAsync(rSession).SilenceException().DoNotWait(); + ExitBrowserAsync(rSession) + .SilenceException() + .SilenceException() + .DoNotWait(); } private void ThrowIfDisposed() { @@ -79,19 +88,32 @@ private void ThrowIfDisposed() { } } - private async Task ExitBrowserAsync(IRSession session) { - using (var inter = await session.BeginInteractionAsync(isVisible: true)) { - // Check if this is still the same prompt as the last Browse prompt that we've seen. - // If it isn't, then session has moved on already, and there's nothing for us to exit. - DebugBrowseEventArgs currentBrowseDebugEventArgs; - lock (_browseLock) { - currentBrowseDebugEventArgs = _currentBrowseEventArgs; - } - - if (currentBrowseDebugEventArgs != null && currentBrowseDebugEventArgs.Contexts == inter.Contexts) { - await inter.RespondAsync("Q\n"); - } - } + private async Task ExitBrowserAsync(IRSession session) { + await TaskUtilities.SwitchToBackgroundThread(); + + var cts = new CancellationTokenSource(1000); + IRSessionInteraction inter; + try { + inter = await session.BeginInteractionAsync(true, cts.Token); + } catch (OperationCanceledException) { + // If we couldn't get a prompt to put "Q" into within a reasonable timeframe, just + // abort the current interaction; + await session.CancelAllAsync(); + return; + } + + using (inter) { + // Check if this is still the same prompt as the last Browse prompt that we've seen. + // If it isn't, then session has moved on already, and there's nothing for us to exit. + DebugBrowseEventArgs currentBrowseDebugEventArgs; + lock (_browseLock) { + currentBrowseDebugEventArgs = _currentBrowseEventArgs; + } + + if (currentBrowseDebugEventArgs != null && currentBrowseDebugEventArgs.Contexts == inter.Contexts) { + await inter.RespondAsync("Q\n"); + } + } } internal void Send(IDebugEvent2 eventObject, string iidEvent, IDebugProgram2 program, IDebugThread2 thread) { @@ -134,12 +156,12 @@ int IDebugEngine2.Attach(IDebugProgram2[] rgpPrograms, IDebugProgramNode2[] rgpP Marshal.ThrowExceptionForHR(_program.GetProgramId(out _programId)); _events = pCallback; - DebugSession = DebugSessionProvider.GetDebugSessionAsync(_program.Session).GetResultOnUIThread(); + DebugSession = TaskExtensions.RunSynchronouslyOnUIThread(ct => DebugSessionProvider.GetDebugSessionAsync(_program.Session, ct)); MainThread = new AD7Thread(this); IsConnected = true; // Enable breakpoint instrumentation. - DebugSession.EnableBreakpoints(true).GetResultOnUIThread(); + TaskExtensions.RunSynchronouslyOnUIThread(ct => DebugSession.EnableBreakpointsAsync(true, ct)); // Send notification after acquiring the session - we need it in case there were any breakpoints pending before // the attach, in which case we'll immediately get breakpoint creation requests as soon as we send these, and @@ -159,8 +181,8 @@ int IDebugEngine2.Attach(IDebugProgram2[] rgpPrograms, IDebugProgramNode2[] rgpP // If we're already at the Browse prompt, registering the handler will result in its immediate invocation. // We want to handle that fully before we process any following AfterRequest event to avoid concurrency issues // where we pause and never resume, so hold the lock while adding the handler. - lock (_browseLock) { - DebugSession.Browse += Session_Browse; + lock (_browseLock) { + DebugSession.Browse += Session_Browse; } return VSConstants.S_OK; @@ -238,7 +260,7 @@ int IDebugEngine2.SetRegistryRoot(string pszRegistryRoot) { int IDebugEngineLaunch2.CanTerminateProcess(IDebugProcess2 pProcess) { ThrowIfDisposed(); - return VSConstants.S_FALSE; + return VSConstants.S_OK; } int IDebugEngineLaunch2.LaunchSuspended(string pszServer, IDebugPort2 pPort, string pszExe, string pszArgs, string pszDir, string bstrEnv, string pszOptions, enum_LAUNCH_FLAGS dwLaunchFlags, uint hStdInput, uint hStdOutput, uint hStdError, IDebugEventCallback2 pCallback, out IDebugProcess2 ppProcess) { @@ -251,7 +273,7 @@ int IDebugEngineLaunch2.ResumeProcess(IDebugProcess2 pProcess) { } int IDebugEngineLaunch2.TerminateProcess(IDebugProcess2 pProcess) { - return VSConstants.E_NOTIMPL; + return IDebugProgram2.Detach(); } int IDebugProgram2.Attach(IDebugEventCallback2 pCallback) { @@ -268,15 +290,25 @@ int IDebugProgram2.CauseBreak() { return IDebugEngine2.CauseBreak(); } + private void DestroyProgram() { + IsProgramDestroyed = true; + Send(new AD7ProgramDestroyEvent(0), AD7ProgramDestroyEvent.IID); + } + int IDebugProgram2.Detach() { ThrowIfDisposed(); try { - // Disable breakpoint instrumentation. - DebugSession.EnableBreakpoints(false).GetResultOnUIThread(); + // Queue disabling breakpoint instrumentation, but do not wait for it to complete - + // if there's currently some code running, this may take a while, so just detach and + // let breakpoints be taken care of later. + DebugSession.EnableBreakpointsAsync(false) + .SilenceException() + .SilenceException() + .DoNotWait(); } finally { // Detach should never fail, even if something above didn't work. - Send(new AD7ProgramDestroyEvent(0), AD7ProgramDestroyEvent.IID); + DestroyProgram(); } return VSConstants.S_OK; @@ -292,16 +324,16 @@ private int Continue(IDebugThread2 pThread) { // debugger that user has explicitly entered something at the Browse prompt, and // we don't actually need to issue the command to R debugger. - Task continueTask = null; - lock (_browseLock) { - if (_sentContinue != true) { - _sentContinue = true; - continueTask = DebugSession.Continue(); + Func continueMethod = null; + lock (_browseLock) { + if (_sentContinue != true) { + _sentContinue = true; + continueMethod = ct => DebugSession.Continue(ct); } } - if (continueTask != null) { - continueTask.GetResultOnUIThread(); + if (continueMethod != null) { + TaskExtensions.RunSynchronouslyOnUIThread(continueMethod); } } @@ -396,7 +428,7 @@ int IDebugProgram2.GetProgramId(out Guid pguidProgramId) { int IDebugProgram2.Step(IDebugThread2 pThread, enum_STEPKIND sk, enum_STEPUNIT Step) { ThrowIfDisposed(); - Task step; + Task step; switch (sk) { case enum_STEPKIND.STEP_OVER: step = DebugSession.StepOverAsync(); @@ -412,7 +444,20 @@ int IDebugProgram2.Step(IDebugThread2 pThread, enum_STEPKIND sk, enum_STEPUNIT S } step.ContinueWith(t => { - Send(new AD7SteppingCompleteEvent(), AD7SteppingCompleteEvent.IID); + // If step was interrupted midway (e.g. by a breakpoint), we have already reported breakpoint + // hit event, and so we must not report step complete. Note that interrupting is not the same + // as canceling, and if step was canceled, we must report step completion. + + bool completed = true; + try { + completed = t.GetAwaiter().GetResult(); + } catch (OperationCanceledException) { + } catch (MessageTransportException) { + } + + if (completed) { + Send(new AD7SteppingCompleteEvent(), AD7SteppingCompleteEvent.IID); + } }); return VSConstants.S_OK; @@ -523,9 +568,9 @@ int IDebugSymbolSettings100.SetSymbolLoadState(int bIsManual, int bLoadAdjacentS } private void Session_Browse(object sender, DebugBrowseEventArgs e) { - lock (_browseLock) { - _currentBrowseEventArgs = e; - _sentContinue = false; + lock (_browseLock) { + _currentBrowseEventArgs = e; + _sentContinue = false; } // If we hit a breakpoint or completed a step, we have already reported the stop from the corresponding handlers. @@ -539,17 +584,17 @@ private void Session_Browse(object sender, DebugBrowseEventArgs e) { private void RSession_AfterRequest(object sender, RRequestEventArgs e) { bool? sentContinue; - lock (_browseLock) { - var browseEventArgs = _currentBrowseEventArgs; - if (browseEventArgs == null || browseEventArgs.Contexts != e.Contexts) { - // This AfterRequest does not correspond to a Browse prompt, or at least not one - // that we have seen before (and paused on), so there's nothing to do. - return; - } - - _currentBrowseEventArgs = null; - sentContinue = _sentContinue; - _sentContinue = true; + lock (_browseLock) { + var browseEventArgs = _currentBrowseEventArgs; + if (browseEventArgs == null || browseEventArgs.Contexts != e.Contexts) { + // This AfterRequest does not correspond to a Browse prompt, or at least not one + // that we have seen before (and paused on), so there's nothing to do. + return; + } + + _currentBrowseEventArgs = null; + sentContinue = _sentContinue; + _sentContinue = true; } if (sentContinue == false) { @@ -565,7 +610,7 @@ private void RSession_AfterRequest(object sender, RRequestEventArgs e) { private void RSession_Disconnected(object sender, EventArgs e) { IsConnected = false; - Send(new AD7ProgramDestroyEvent(0), AD7ProgramDestroyEvent.IID); + DestroyProgram(); } } } diff --git a/src/Debugger/Engine/Impl/AD7Expression.cs b/src/Debugger/Engine/Impl/AD7Expression.cs index 1da69b203..5efed91f9 100644 --- a/src/Debugger/Engine/Impl/AD7Expression.cs +++ b/src/Debugger/Engine/Impl/AD7Expression.cs @@ -45,7 +45,7 @@ int IDebugExpression2.EvaluateAsync(enum_EVALFLAGS dwFlags, IDebugEventCallback2 } int IDebugExpression2.EvaluateSync(enum_EVALFLAGS dwFlags, uint dwTimeout, IDebugEventCallback2 pExprCallback, out IDebugProperty2 ppResult) { - var res = StackFrame.StackFrame.EvaluateAsync(_expression, reprMaxLength: AD7Property.ReprMaxLength).GetResultOnUIThread(); + var res = TaskExtensions.RunSynchronouslyOnUIThread(ct => StackFrame.StackFrame.EvaluateAsync(_expression, reprMaxLength: AD7Property.ReprMaxLength, cancellationToken:ct)); ppResult = new AD7Property(StackFrame, res); return VSConstants.S_OK; } diff --git a/src/Debugger/Engine/Impl/AD7PendingBreakpoint.cs b/src/Debugger/Engine/Impl/AD7PendingBreakpoint.cs index 0e42c8bbc..71d9d13dc 100644 --- a/src/Debugger/Engine/Impl/AD7PendingBreakpoint.cs +++ b/src/Debugger/Engine/Impl/AD7PendingBreakpoint.cs @@ -79,6 +79,10 @@ int IDebugPendingBreakpoint2.Enable(int fEnable) { return VSConstants.E_FAIL; } + if (_boundBreakpoint != null) { + Marshal.ThrowExceptionForHR(((IDebugBoundBreakpoint2)_boundBreakpoint).Enable(fEnable)); + } + _state = fEnable == 0 ? enum_PENDING_BP_STATE.PBPS_DISABLED : enum_PENDING_BP_STATE.PBPS_ENABLED; return VSConstants.S_OK; } diff --git a/src/Debugger/Engine/Impl/AD7Property.cs b/src/Debugger/Engine/Impl/AD7Property.cs index 3eabe0c89..d10e5b418 100644 --- a/src/Debugger/Engine/Impl/AD7Property.cs +++ b/src/Debugger/Engine/Impl/AD7Property.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Common.Core; +using Microsoft.R.Support.Settings; using Microsoft.VisualStudio; using Microsoft.VisualStudio.Debugger.Interop; using static System.FormattableString; @@ -11,10 +12,24 @@ internal sealed class AD7Property : IDebugProperty3 { internal const int ChildrenMaxLength = 100; internal const int ReprMaxLength = 100; + private const DebugEvaluationResultFields _prefetchedFields = + DebugEvaluationResultFields.Expression | + DebugEvaluationResultFields.Kind | + DebugEvaluationResultFields.Repr | + DebugEvaluationResultFields.ReprDeparse | + DebugEvaluationResultFields.TypeName | + DebugEvaluationResultFields.Classes | + DebugEvaluationResultFields.Length | + DebugEvaluationResultFields.SlotCount | + DebugEvaluationResultFields.AttrCount | + DebugEvaluationResultFields.Dim | + DebugEvaluationResultFields.Flags; + private IDebugProperty2 IDebugProperty2 => this; private IDebugProperty3 IDebugProperty3 => this; private Lazy> _children; + private Lazy _reprToString; public AD7Property Parent { get; } public AD7StackFrame StackFrame { get; } @@ -33,15 +48,27 @@ public AD7Property(AD7StackFrame stackFrame, DebugEvaluationResult result, bool IsSynthetic = isSynthetic; IsFrameEnvironment = isFrameEnvironment; - _children = Lazy.Create(() => - (EvaluationResult as DebugValueEvaluationResult) - ?.GetChildrenAsync(maxLength: ChildrenMaxLength, reprMaxLength: ReprMaxLength) - ?.GetResultOnUIThread() - ?? new DebugEvaluationResult[0]); + _children = Lazy.Create(CreateChildren); + _reprToString = Lazy.Create(CreateReprToString); + } + + private IReadOnlyList CreateChildren() { + return TaskExtensions.RunSynchronouslyOnUIThread(ct => (EvaluationResult as DebugValueEvaluationResult)?.GetChildrenAsync(_prefetchedFields, ChildrenMaxLength, ReprMaxLength, ct)) + ?? new DebugEvaluationResult[0]; + } + + private string CreateReprToString() { + var ev = TaskExtensions.RunSynchronouslyOnUIThread(ct => EvaluationResult.EvaluateAsync(DebugEvaluationResultFields.Repr | DebugEvaluationResultFields.ReprToString, cancellationToken: ct)); + return (ev as DebugValueEvaluationResult)?.Representation.ToString; } int IDebugProperty2.EnumChildren(enum_DEBUGPROP_INFO_FLAGS dwFields, uint dwRadix, ref Guid guidFilter, enum_DBG_ATTRIB_FLAGS dwAttribFilter, string pszNameFilter, uint dwTimeout, out IEnumDebugPropertyInfo2 ppEnum) { IEnumerable children = _children.Value; + + if (!RToolsSettings.Current.ShowDotPrefixedVariables) { + children = children.Where(v => v.Name != null && !v.Name.StartsWith(".")); + } + if (IsFrameEnvironment) { children = children.OrderBy(v => v.Name); } @@ -50,8 +77,8 @@ int IDebugProperty2.EnumChildren(enum_DEBUGPROP_INFO_FLAGS dwFields, uint dwRadi var valueResult = EvaluationResult as DebugValueEvaluationResult; if (valueResult != null && valueResult.HasAttributes == true) { - string attrExpr = Invariant($"attributes({valueResult.Expression})"); - var attrResult = StackFrame.StackFrame.EvaluateAsync(attrExpr, "attributes()", reprMaxLength: ReprMaxLength).GetResultOnUIThread(); + string attrExpr = Invariant($"base::attributes({valueResult.Expression})"); + var attrResult = TaskExtensions.RunSynchronouslyOnUIThread(ct => StackFrame.StackFrame.EvaluateAsync(attrExpr, "attributes()", reprMaxLength: ReprMaxLength, cancellationToken:ct)); if (!(attrResult is DebugErrorEvaluationResult)) { var attrInfo = new AD7Property(this, attrResult, isSynthetic: true).GetDebugPropertyInfo(dwRadix, dwFields); infos = new[] { attrInfo }.Concat(infos); @@ -128,12 +155,23 @@ int IDebugProperty3.EnumChildren(enum_DEBUGPROP_INFO_FLAGS dwFields, uint dwRadi } int IDebugProperty3.GetCustomViewerCount(out uint pcelt) { - pcelt = 0; + pcelt = StackFrame.Engine.GridViewProvider?.CanShowDataGrid(EvaluationResult) == true ? 1u : 0u; return VSConstants.S_OK; } int IDebugProperty3.GetCustomViewerList(uint celtSkip, uint celtRequested, DEBUG_CUSTOM_VIEWER[] rgViewers, out uint pceltFetched) { - pceltFetched = 0; + if (celtSkip > 0 || celtRequested == 0) { + pceltFetched = 0; + } else { + pceltFetched = 1; + rgViewers[0] = new DEBUG_CUSTOM_VIEWER { + bstrMenuName = "Grid Visualizer", + bstrMetric = "CustomViewerCLSID", + guidLang = DebuggerGuids.LanguageGuid, + guidVendor = DebuggerGuids.VendorGuid, + dwID = 0 + }; + } return VSConstants.S_OK; } @@ -168,25 +206,23 @@ int IDebugProperty3.GetSize(out uint pdwSize) { int IDebugProperty3.GetStringCharLength(out uint pLen) { pLen = 0; - var valueResult = EvaluationResult as DebugValueEvaluationResult; - if (valueResult == null || valueResult.Representation.ToString == null) { + if (_reprToString.Value == null) { return VSConstants.E_FAIL; } - pLen = (uint)valueResult.Representation.ToString.Length; + pLen = (uint)_reprToString.Value.Length; return VSConstants.S_OK; } int IDebugProperty3.GetStringChars(uint buflen, ushort[] rgString, out uint pceltFetched) { pceltFetched = 0; - var valueResult = EvaluationResult as DebugValueEvaluationResult; - if (valueResult == null || valueResult.Representation.ToString == null) { + if (_reprToString.Value == null) { return VSConstants.E_FAIL; } for (int i = 0; i < buflen; ++i) { - rgString[i] = valueResult.Representation.ToString[i]; + rgString[i] = _reprToString.Value[i]; } return VSConstants.S_OK; } @@ -203,7 +239,7 @@ int IDebugProperty3.SetValueAsStringWithError(string pszValue, uint dwRadix, uin errorString = null; // TODO: dwRadix - var setResult = EvaluationResult.SetValueAsync(pszValue).GetResultOnUIThread() as DebugErrorEvaluationResult; + var setResult = TaskExtensions.RunSynchronouslyOnUIThread(ct => EvaluationResult.SetValueAsync(pszValue, ct)) as DebugErrorEvaluationResult; if (setResult != null) { errorString = setResult.ErrorText; return VSConstants.E_FAIL; @@ -239,7 +275,7 @@ internal DEBUG_PROPERTY_INFO GetDebugPropertyInfo(uint radix, enum_DEBUGPROP_INF if (fields.HasFlag(enum_DEBUGPROP_INFO_FLAGS.DEBUGPROP_INFO_TYPE)) { if (valueResult != null) { dpi.bstrType = valueResult.TypeName; - if (valueResult.Classes.Count > 0) { + if (valueResult.Classes != null && valueResult.Classes.Count > 0) { dpi.bstrType += " (" + string.Join(", ", valueResult.Classes) + ")"; } dpi.dwFields |= enum_DEBUGPROP_INFO_FLAGS.DEBUGPROP_INFO_TYPE; @@ -255,7 +291,7 @@ internal DEBUG_PROPERTY_INFO GetDebugPropertyInfo(uint radix, enum_DEBUGPROP_INF if (fields.HasFlag(enum_DEBUGPROP_INFO_FLAGS.DEBUGPROP_INFO_VALUE)) { if (valueResult != null) { // TODO: handle radix - dpi.bstrValue = valueResult.Representation.DPut; + dpi.bstrValue = valueResult.Representation.Deparse; dpi.dwFields |= enum_DEBUGPROP_INFO_FLAGS.DEBUGPROP_INFO_VALUE; } else if (promiseResult != null) { dpi.bstrValue = promiseResult.Code; @@ -273,22 +309,31 @@ internal DEBUG_PROPERTY_INFO GetDebugPropertyInfo(uint radix, enum_DEBUGPROP_INF dpi.dwAttrib |= enum_DBG_ATTRIB_FLAGS.DBG_ATTRIB_METHOD | enum_DBG_ATTRIB_FLAGS.DBG_ATTRIB_TYPE_VIRTUAL; } + if (StackFrame.Engine.GridViewProvider?.CanShowDataGrid(EvaluationResult) == true) { + dpi.dwAttrib |= enum_DBG_ATTRIB_FLAGS.DBG_ATTRIB_VALUE_CUSTOM_VIEWER; + } + if (valueResult?.HasChildren == true || valueResult?.HasAttributes == true) { dpi.dwAttrib |= enum_DBG_ATTRIB_FLAGS.DBG_ATTRIB_OBJ_IS_EXPANDABLE; } if (valueResult != null) { - dpi.dwAttrib |= enum_DBG_ATTRIB_FLAGS.DBG_ATTRIB_VALUE_RAW_STRING; switch (valueResult.TypeName) { case "logical": dpi.dwAttrib |= enum_DBG_ATTRIB_FLAGS.DBG_ATTRIB_VALUE_BOOLEAN; - if (valueResult.Representation.DPut == "TRUE") { + if (valueResult.Representation.Deparse == "TRUE") { dpi.dwAttrib |= enum_DBG_ATTRIB_FLAGS.DBG_ATTRIB_VALUE_BOOLEAN_TRUE; } break; case "closure": dpi.dwAttrib |= enum_DBG_ATTRIB_FLAGS.DBG_ATTRIB_METHOD; break; + case "character": + case "symbol": + if (valueResult.Length == 1) { + dpi.dwAttrib |= enum_DBG_ATTRIB_FLAGS.DBG_ATTRIB_VALUE_RAW_STRING; + } + break; } } else if (errorResult != null) { dpi.dwAttrib |= enum_DBG_ATTRIB_FLAGS.DBG_ATTRIB_VALUE_ERROR; diff --git a/src/Debugger/Engine/Impl/AD7StackFrame.cs b/src/Debugger/Engine/Impl/AD7StackFrame.cs index 6cf583019..7325e16bf 100644 --- a/src/Debugger/Engine/Impl/AD7StackFrame.cs +++ b/src/Debugger/Engine/Impl/AD7StackFrame.cs @@ -16,7 +16,7 @@ public AD7StackFrame(AD7Engine engine, DebugStackFrame stackFrame) { Engine = engine; StackFrame = stackFrame; - _property = Lazy.Create(() => new AD7Property(this, StackFrame.GetEnvironmentAsync().GetResultOnUIThread(), isFrameEnvironment: true)); + _property = Lazy.Create(() => new AD7Property(this, TaskExtensions.RunSynchronouslyOnUIThread(ct => StackFrame.GetEnvironmentAsync(cancellationToken:ct)), isFrameEnvironment: true)); } int IDebugStackFrame2.EnumProperties(enum_DEBUGPROP_INFO_FLAGS dwFields, uint nRadix, ref Guid guidFilter, uint dwTimeout, out uint pcelt, out IEnumDebugPropertyInfo2 ppEnum) { diff --git a/src/Debugger/Engine/Impl/AD7Thread.cs b/src/Debugger/Engine/Impl/AD7Thread.cs index 6c97035ef..a8e0d21a8 100644 --- a/src/Debugger/Engine/Impl/AD7Thread.cs +++ b/src/Debugger/Engine/Impl/AD7Thread.cs @@ -34,7 +34,7 @@ private void ThrowIfDisposed() { int IDebugThread100.CanDoFuncEval() { ThrowIfDisposed(); - return VSConstants.S_FALSE; + return VSConstants.S_OK; } int IDebugThread2.CanSetNextStatement(IDebugStackFrame2 pStackFrame, IDebugCodeContext2 pCodeContext) { @@ -156,8 +156,7 @@ int IDebugThread100.SetThreadDisplayName(string bstrDisplayName) { private void ResetStackFrames() { _stackFrames = Lazy.Create(() => (IReadOnlyList) - Engine.DebugSession.GetStackFramesAsync() - .GetResultOnUIThread() + TaskExtensions.RunSynchronouslyOnUIThread(ct => Engine.DebugSession.GetStackFramesAsync(ct)) .Reverse() .ToArray()); } diff --git a/src/Debugger/Engine/Impl/DTEDebuggerExtensions.cs b/src/Debugger/Engine/Impl/DTEDebuggerExtensions.cs new file mode 100644 index 000000000..da2b9a1d5 --- /dev/null +++ b/src/Debugger/Engine/Impl/DTEDebuggerExtensions.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.R.Debugger.Engine { + public static class DTEDebuggerExtensions { + /// + /// Forces debugger to refresh its variable views (Locals, Autos etc) by re-querying the debug engine. + /// + /// + public static void RefreshVariableViews(this EnvDTE.Debugger debugger) { + // There's no proper way to do this, so just "change" a property that would invalidate the view. + debugger.HexDisplayMode = debugger.HexDisplayMode; + } + } +} diff --git a/src/Debugger/Engine/Impl/DebuggerGuids.cs b/src/Debugger/Engine/Impl/DebuggerGuids.cs index 57fba56e9..927d661d3 100644 --- a/src/Debugger/Engine/Impl/DebuggerGuids.cs +++ b/src/Debugger/Engine/Impl/DebuggerGuids.cs @@ -1,35 +1,38 @@ -using System; - -namespace Microsoft.R.Debugger.Engine { - public static class DebuggerGuids { - public const string VendorGuidString = "994B45C4-E6E9-11D2-903F-00C04FA302A1"; - public static readonly Guid VendorGuid = new Guid(VendorGuidString); - - public const string RuntimeTypeGuidString = "3940F2FC-5CD0-446F-81B0-6641C05F76D4"; - public static readonly Guid RuntimeTypeGuid = new Guid(RuntimeTypeGuidString); - - public const string SymbolProviderGuidString = "366C0C4E-27B0-4847-9BCB-480B139E5CCF"; - public static readonly Guid SymbolProviderGuid = new Guid(SymbolProviderGuidString); - - public const string LanguageGuidString = "652D96EE-B796-4BD7-AD7F-EDEA65528946"; - public static readonly Guid LanguageGuid = new Guid(LanguageGuidString); - - public const string ExceptionCategoryGuidString = "4717209D-0829-4DA2-899B-4F885D627BBF"; - public static readonly Guid ExceptionCategoryGuid = new Guid(ExceptionCategoryGuidString); - - public const string ProgramProviderCLSIDString = "6FA14708-3963-46AF-ADAF-7CD7E3EF57FE"; - public static readonly Guid ProgramProviderCLSID = new Guid(ProgramProviderCLSIDString); - - public const string DebugEngineCLSIDString = "F839D71F-EEF4-4123-B6E7-BE0FC7E6F2A3"; - public static readonly Guid DebugEngineCLSID = new Guid(DebugEngineCLSIDString); - - public const string DebugEngineString = "BC67335F-8EC6-4AA8-AF59-72AEC95947EA"; - public static readonly Guid DebugEngine = new Guid(DebugEngineString); - - public const string PortSupplierCLSIDString = "B89C17B4-320D-44D0-B95C-5D3468644207"; - public static readonly Guid PortSupplierCLSID = new Guid(PortSupplierCLSIDString); - - public const string PortSupplierString = "B3B6414F-D6F8-43A3-BFF4-93F5DD84CB86"; - public static readonly Guid PortSupplier = new Guid(PortSupplierString); - }; -} +using System; + +namespace Microsoft.R.Debugger.Engine { + public static class DebuggerGuids { + public const string VendorGuidString = "994B45C4-E6E9-11D2-903F-00C04FA302A1"; + public static readonly Guid VendorGuid = new Guid(VendorGuidString); + + public const string RuntimeTypeGuidString = "3940F2FC-5CD0-446F-81B0-6641C05F76D4"; + public static readonly Guid RuntimeTypeGuid = new Guid(RuntimeTypeGuidString); + + public const string SymbolProviderGuidString = "366C0C4E-27B0-4847-9BCB-480B139E5CCF"; + public static readonly Guid SymbolProviderGuid = new Guid(SymbolProviderGuidString); + + public const string LanguageGuidString = "652D96EE-B796-4BD7-AD7F-EDEA65528946"; + public static readonly Guid LanguageGuid = new Guid(LanguageGuidString); + + public const string ExceptionCategoryGuidString = "4717209D-0829-4DA2-899B-4F885D627BBF"; + public static readonly Guid ExceptionCategoryGuid = new Guid(ExceptionCategoryGuidString); + + public const string ProgramProviderCLSIDString = "6FA14708-3963-46AF-ADAF-7CD7E3EF57FE"; + public static readonly Guid ProgramProviderCLSID = new Guid(ProgramProviderCLSIDString); + + public const string DebugEngineCLSIDString = "F839D71F-EEF4-4123-B6E7-BE0FC7E6F2A3"; + public static readonly Guid DebugEngineCLSID = new Guid(DebugEngineCLSIDString); + + public const string DebugEngineString = "BC67335F-8EC6-4AA8-AF59-72AEC95947EA"; + public static readonly Guid DebugEngine = new Guid(DebugEngineString); + + public const string PortSupplierCLSIDString = "B89C17B4-320D-44D0-B95C-5D3468644207"; + public static readonly Guid PortSupplierCLSID = new Guid(PortSupplierCLSIDString); + + public const string PortSupplierString = "B3B6414F-D6F8-43A3-BFF4-93F5DD84CB86"; + public static readonly Guid PortSupplier = new Guid(PortSupplierString); + + public const string CustomViewerString = "8FBE2C99-E300-4079-A702-410FC60996EA"; + public static readonly Guid CustomViewer = new Guid(CustomViewerString); + }; +} diff --git a/src/Debugger/Engine/Impl/IDebugGridViewProvider.cs b/src/Debugger/Engine/Impl/IDebugGridViewProvider.cs new file mode 100644 index 000000000..4feb7e7be --- /dev/null +++ b/src/Debugger/Engine/Impl/IDebugGridViewProvider.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Microsoft.R.Debugger.Engine { + public interface IDebugGridViewProvider { + bool CanShowDataGrid(DebugEvaluationResult evaluationResult); + void ShowDataGrid(DebugEvaluationResult evaluationResult); + } +} diff --git a/src/Debugger/Engine/Impl/Microsoft.R.Debugger.Engine.csproj b/src/Debugger/Engine/Impl/Microsoft.R.Debugger.Engine.csproj index daf19c17d..7ea3abe38 100644 --- a/src/Debugger/Engine/Impl/Microsoft.R.Debugger.Engine.csproj +++ b/src/Debugger/Engine/Impl/Microsoft.R.Debugger.Engine.csproj @@ -28,6 +28,14 @@ ..\..\..\..\NugetPackages\Microsoft.VisualStudio.OLE.Interop.7.10.6070\lib\Microsoft.VisualStudio.OLE.Interop.dll True + + ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Shell.Immutable.11.0.11.0.50727\lib\net45\Microsoft.VisualStudio.Shell.Immutable.11.0.dll + True + + + ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Shell.Immutable.14.0.14.1.24720\lib\net45\Microsoft.VisualStudio.Shell.Immutable.14.0.dll + True + ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Shell.Interop.7.10.6071\lib\Microsoft.VisualStudio.Shell.Interop.dll True @@ -54,6 +62,14 @@ ..\..\..\..\NugetPackages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll True + + ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Threading.14.1.114\lib\net45\Microsoft.VisualStudio.Threading.dll + True + + + ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Validation.14.1.111\lib\net45\Microsoft.VisualStudio.Validation.dll + True + @@ -71,6 +87,7 @@ True + @@ -78,6 +95,7 @@ + @@ -91,12 +109,19 @@ + + + + True + True + Resources.resx + @@ -112,6 +137,10 @@ {d6eeef87-ce3a-4804-a409-39966b96c850} Microsoft.R.Editor + + {c1957d47-b0b4-42e0-bc08-0d5e96e47fe4} + Microsoft.R.Support + {17e155bf-351c-4253-b9b1-36eeea35fe3c} Microsoft.R.Debugger @@ -127,6 +156,13 @@ StrongName + + + PublicResXFileCodeGenerator + Designer + Resources.Designer.cs + + diff --git a/src/Debugger/Engine/Impl/Resources.Designer.cs b/src/Debugger/Engine/Impl/Resources.Designer.cs new file mode 100644 index 000000000..38f289663 --- /dev/null +++ b/src/Debugger/Engine/Impl/Resources.Designer.cs @@ -0,0 +1,72 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.R.Debugger.Engine { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.R.Debugger.Engine.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Debugger operation is in progress.... + /// + public static string DebuggerInProgress { + get { + return ResourceManager.GetString("DebuggerInProgress", resourceCulture); + } + } + } +} diff --git a/src/Debugger/Engine/Impl/Resources.resx b/src/Debugger/Engine/Impl/Resources.resx new file mode 100644 index 000000000..23a71d142 --- /dev/null +++ b/src/Debugger/Engine/Impl/Resources.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Debugger operation is in progress... + + + \ No newline at end of file diff --git a/src/Debugger/Engine/Impl/TaskExtensions.cs b/src/Debugger/Engine/Impl/TaskExtensions.cs index dbaebc331..8d63f4d2b 100644 --- a/src/Debugger/Engine/Impl/TaskExtensions.cs +++ b/src/Debugger/Engine/Impl/TaskExtensions.cs @@ -20,28 +20,28 @@ internal static class TaskExtensions { return twdf; }); - public static void GetResultOnUIThread(this Task task, int delay = 1) { - var syncContext = SynchronizationContext.Current; - if (syncContext == null) { - var err = Invariant($"{nameof(GetResultOnUIThread)} called from a background thread"); - Trace.Fail(err); - throw new InvalidOperationException(err); + public static void RunSynchronouslyOnUIThread(Func method, double delayToShowDialog = 2) { + ThreadHelper.ThrowIfNotOnUIThread(); + + using (var session = StartWaitDialog(delayToShowDialog)) { + var ct = session.UserCancellationToken; + ThreadHelper.JoinableTaskFactory.Run(() => method(ct)); } + } - var twdf = _twdf.Value; - IVsThreadedWaitDialog2 twd; - Marshal.ThrowExceptionForHR(twdf.CreateInstance(out twd)); - - // Post EndWaitDialog rather than invoking directly, so that it is guaranteed to be invoked after StartWaitDialog. - task.ContinueWith(delegate { syncContext.Post(delegate { twd.EndWaitDialog(); }, null); }); - twd.StartWaitDialog(null, "Debugger operation is in progress...", null, null, null, delay, false, true); + public static T RunSynchronouslyOnUIThread(Func> method, double delayToShowDialog = 2) { + T result; + using (var session = StartWaitDialog(delayToShowDialog)) { + var ct = session.UserCancellationToken; + result = ThreadHelper.JoinableTaskFactory.Run(() => method(ct)); + } - task.GetAwaiter().GetResult(); + return result; } - public static TResult GetResultOnUIThread(this Task task, int delay = 1) { - GetResultOnUIThread((Task)task, delay); - return task.GetAwaiter().GetResult(); + private static ThreadedWaitDialogHelper.Session StartWaitDialog(double delayToShowDialog) { + var initialProgress = new ThreadedWaitDialogProgressData(Resources.DebuggerInProgress, isCancelable: true); + return _twdf.Value.StartWaitDialog(null, initialProgress, TimeSpan.FromSeconds(delayToShowDialog)); } } } \ No newline at end of file diff --git a/src/Debugger/Engine/Impl/packages.config b/src/Debugger/Engine/Impl/packages.config index c00a9bec8..1aa6e1688 100644 --- a/src/Debugger/Engine/Impl/packages.config +++ b/src/Debugger/Engine/Impl/packages.config @@ -2,10 +2,14 @@ + + + + \ No newline at end of file diff --git a/src/Debugger/Impl/DebugBreakpoint.cs b/src/Debugger/Impl/DebugBreakpoint.cs index 4ab9b5337..6ba77fc49 100644 --- a/src/Debugger/Impl/DebugBreakpoint.cs +++ b/src/Debugger/Impl/DebugBreakpoint.cs @@ -1,91 +1,92 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Threading.Tasks; -using Microsoft.Common.Core; -using Microsoft.R.Host.Client; -using static System.FormattableString; - -namespace Microsoft.R.Debugger { - public struct DebugBreakpointLocation : IEquatable { - public string FileName { get; } - public int LineNumber { get; } - - public DebugBreakpointLocation(string fileName, int lineNumber) { - FileName = fileName; - LineNumber = lineNumber; - } - - public override int GetHashCode() { - return new { FileName, LineNumber }.GetHashCode(); - } - - public override bool Equals(object obj) { - return (obj as DebugBreakpointLocation?)?.Equals(this) ?? false; - } - - public bool Equals(DebugBreakpointLocation other) { - return FileName == other.FileName && LineNumber == other.LineNumber; - } - - public override string ToString() { - return Invariant($"{FileName}:{LineNumber}"); - } - } - - public sealed class DebugBreakpoint { - public DebugSession Session { get; } - public DebugBreakpointLocation Location { get; } - internal int UseCount { get; private set; } - - public event EventHandler BreakpointHit; - - internal DebugBreakpoint(DebugSession session, DebugBreakpointLocation location) { - Session = session; - Location = location; - } - - internal string GetAddBreakpointExpression(bool reapply) { - string fileName = null; - try { - fileName = Path.GetFileName(Location.FileName); - } catch (ArgumentException) { - return null; - } - - return Invariant($"rtvs:::add_breakpoint({fileName.ToRStringLiteral()}, {Location.LineNumber}, {(reapply ? "TRUE" : "FALSE")})"); - } - - internal async Task SetBreakpointAsync() { - TaskUtilities.AssertIsOnBackgroundThread(); - await Session.InvokeDebugHelperAsync(GetAddBreakpointExpression(true)); - ++UseCount; - } - - public async Task DeleteAsync() { - Trace.Assert(UseCount > 0); - await TaskUtilities.SwitchToBackgroundThread(); - await Session.InitializeAsync(); - - string fileName = null; - try { - fileName = Path.GetFileName(Location.FileName); - } catch (ArgumentException) { - return; - } - - if (--UseCount == 0) { - Session.RemoveBreakpoint(this); - - var res = await Session.EvaluateAsync(Invariant($"rtvs:::remove_breakpoint({fileName.ToRStringLiteral()}, {Location.LineNumber})")); - if (res is DebugErrorEvaluationResult) { - throw new InvalidOperationException(Invariant($"{res.Expression}: {res}")); - } - } - } - - internal void RaiseBreakpointHit() { - BreakpointHit?.Invoke(this, EventArgs.Empty); - } - } -} +using System; +using System.Diagnostics; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Common.Core; +using Microsoft.R.Host.Client; +using static System.FormattableString; + +namespace Microsoft.R.Debugger { + public struct DebugBreakpointLocation : IEquatable { + public string FileName { get; } + public int LineNumber { get; } + + public DebugBreakpointLocation(string fileName, int lineNumber) { + FileName = fileName; + LineNumber = lineNumber; + } + + public override int GetHashCode() { + return new { FileName, LineNumber }.GetHashCode(); + } + + public override bool Equals(object obj) { + return (obj as DebugBreakpointLocation?)?.Equals(this) ?? false; + } + + public bool Equals(DebugBreakpointLocation other) { + return FileName == other.FileName && LineNumber == other.LineNumber; + } + + public override string ToString() { + return Invariant($"{FileName}:{LineNumber}"); + } + } + + public sealed class DebugBreakpoint { + public DebugSession Session { get; } + public DebugBreakpointLocation Location { get; } + internal int UseCount { get; private set; } + + public event EventHandler BreakpointHit; + + internal DebugBreakpoint(DebugSession session, DebugBreakpointLocation location) { + Session = session; + Location = location; + } + + internal string GetAddBreakpointExpression(bool reapply) { + string fileName = null; + try { + fileName = Path.GetFileName(Location.FileName); + } catch (ArgumentException) { + return null; + } + + return Invariant($"rtvs:::add_breakpoint({fileName.ToRStringLiteral()}, {Location.LineNumber}, {(reapply ? "TRUE" : "FALSE")})"); + } + + internal async Task SetBreakpointAsync(CancellationToken cancellationToken = default(CancellationToken)) { + TaskUtilities.AssertIsOnBackgroundThread(); + await Session.InvokeDebugHelperAsync(GetAddBreakpointExpression(true), cancellationToken); + ++UseCount; + } + + public async Task DeleteAsync(CancellationToken cancellationToken = default(CancellationToken)) { + Trace.Assert(UseCount > 0); + await TaskUtilities.SwitchToBackgroundThread(); + await Session.InitializeAsync(cancellationToken); + + string fileName = null; + try { + fileName = Path.GetFileName(Location.FileName); + } catch (ArgumentException) { + return; + } + + if (--UseCount == 0) { + Session.RemoveBreakpoint(this); + + var res = await Session.EvaluateAsync(Invariant($"rtvs:::remove_breakpoint({fileName.ToRStringLiteral()}, {Location.LineNumber})"), cancellationToken); + if (res is DebugErrorEvaluationResult) { + throw new InvalidOperationException(Invariant($"{res.Expression}: {res}")); + } + } + } + + internal void RaiseBreakpointHit() { + BreakpointHit?.Invoke(this, EventArgs.Empty); + } + } +} diff --git a/src/Debugger/Impl/DebugEvaluationResult.cs b/src/Debugger/Impl/DebugEvaluationResult.cs index ca0547b74..5db38e92d 100644 --- a/src/Debugger/Impl/DebugEvaluationResult.cs +++ b/src/Debugger/Impl/DebugEvaluationResult.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.IO; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Microsoft.Common.Core; using Microsoft.R.Host.Client; @@ -16,10 +17,10 @@ public enum DebugEvaluationResultFields : ulong { Expression = 1 << 1, Kind = 1 << 2, Repr = 1 << 3, - ReprDPut = 1 << 4, + ReprDeparse = 1 << 4, ReprToString = 1 << 5, ReprStr = 1 << 6, - ReprAll = Repr | ReprDPut | ReprStr | ReprToString, + ReprAll = Repr | ReprDeparse | ReprStr | ReprToString, TypeName = 1 << 7, Classes = 1 << 8, Length = 1 << 9, @@ -37,7 +38,7 @@ internal static class DebugEvaluationResultFieldsExtensions { [DebugEvaluationResultFields.Expression] = "expression", [DebugEvaluationResultFields.Kind] = "kind", [DebugEvaluationResultFields.Repr] = "repr", - [DebugEvaluationResultFields.ReprDPut] = "repr.dput", + [DebugEvaluationResultFields.ReprDeparse] = "repr.deparse", [DebugEvaluationResultFields.ReprToString] = "repr.toString", [DebugEvaluationResultFields.ReprStr] = "repr.str", [DebugEvaluationResultFields.TypeName] = "type", @@ -57,7 +58,7 @@ public static string ToRVector(this DebugEvaluationResultFields fields) { } var fieldNames = _mapping.Where(kv => fields.HasFlag(kv.Key)).Select(kv => "'" + kv.Value + "'"); - return Invariant($"c({string.Join(", ", fieldNames)})"); + return Invariant($"base::c({string.Join(", ", fieldNames)})"); } } @@ -93,12 +94,27 @@ internal static DebugEvaluationResult Parse(DebugStackFrame stackFrame, string n return new DebugValueEvaluationResult(stackFrame, expression, name, json); } - public Task SetValueAsync(string value) { + public Task SetValueAsync(string value, CancellationToken cancellationToken = default(CancellationToken)) { if (string.IsNullOrEmpty(Expression)) { throw new InvalidOperationException(Invariant($"{nameof(SetValueAsync)} is not supported for this {nameof(DebugEvaluationResult)} because it doesn't have an associated {nameof(Expression)}.")); } - return StackFrame.EvaluateAsync(Invariant($"{Expression} <- {value}"), reprMaxLength: 0); + return StackFrame.EvaluateAsync(Invariant($"{Expression} <- {value}"), reprMaxLength: 0, cancellationToken: cancellationToken); + } + + public Task EvaluateAsync( + DebugEvaluationResultFields fields = DebugEvaluationResultFields.All, + int? reprMaxLength = null, + CancellationToken cancellationToken = default(CancellationToken) + ) { + if (StackFrame == null) { + throw new InvalidOperationException("Cannot re-evaluate an evaluation result that is not tied to a frame."); + } + if (Expression == null) { + throw new InvalidOperationException("Cannot re-evaluate an evaluation result that does not have an associated expression."); + } + + return StackFrame.EvaluateAsync(Expression, Name, fields, reprMaxLength, cancellationToken); } } @@ -130,12 +146,12 @@ public enum DebugValueEvaluationResultFlags { } public struct DebugValueEvaluationResultRepresentation { - public readonly string DPut; + public readonly string Deparse; public readonly new string ToString; public readonly string Str; public DebugValueEvaluationResultRepresentation(JObject repr) { - DPut = repr.Value("dput"); + Deparse = repr.Value("deparse"); ToString = repr.Value("toString"); Str = repr.Value("str"); } @@ -155,9 +171,9 @@ public class DebugValueEvaluationResult : DebugEvaluationResult { public bool IsAtomic => Flags.HasFlag(DebugValueEvaluationResultFlags.Atomic); public bool IsRecursive => Flags.HasFlag(DebugValueEvaluationResultFlags.Recursive); - public bool HasAttributes => AttributeCount != 0; - public bool HasSlots => SlotCount != 0; - public bool HasChildren => HasSlots || Length > (IsAtomic || TypeName == "closure" ? 1 : 0); + public bool HasAttributes => AttributeCount != null && AttributeCount != 0; + public bool HasSlots => SlotCount != null && SlotCount != 0; + public bool HasChildren => HasSlots || (Length != null && (Length > (IsAtomic || TypeName == "closure" ? 1 : 0))); internal DebugValueEvaluationResult(DebugStackFrame stackFrame, string expression, string name, JObject json) : base(stackFrame, expression, name) { @@ -203,20 +219,22 @@ internal DebugValueEvaluationResult(DebugStackFrame stackFrame, string expressio throw new InvalidDataException(Invariant($"Invalid kind '{kind}' in:\n\n{json}")); } - var flags = json.Value("flags").Select(v => v.Value()); - foreach (var flag in flags) { - switch (flag) { - case "atomic": - Flags |= DebugValueEvaluationResultFlags.Atomic; - break; - case "recursive": - Flags |= DebugValueEvaluationResultFlags.Recursive; - break; - case "has_parent_env": - Flags |= DebugValueEvaluationResultFlags.HasParentEnvironment; - break; - default: - throw new InvalidDataException(Invariant($"Unrecognized flag '{flag}' in:\n\n{json}")); + var flags = json.Value("flags")?.Select(v => v.Value()); + if (flags != null) { + foreach (var flag in flags) { + switch (flag) { + case "atomic": + Flags |= DebugValueEvaluationResultFlags.Atomic; + break; + case "recursive": + Flags |= DebugValueEvaluationResultFlags.Recursive; + break; + case "has_parent_env": + Flags |= DebugValueEvaluationResultFlags.HasParentEnvironment; + break; + default: + throw new InvalidDataException(Invariant($"Unrecognized flag '{flag}' in:\n\n{json}")); + } } } } @@ -224,18 +242,22 @@ internal DebugValueEvaluationResult(DebugStackFrame stackFrame, string expressio public async Task> GetChildrenAsync( DebugEvaluationResultFields fields = DebugEvaluationResultFields.All, int? maxLength = null, - int? reprMaxLength = null + int? reprMaxLength = null, + CancellationToken cancellationToken = default(CancellationToken) ) { await TaskUtilities.SwitchToBackgroundThread(); if (StackFrame == null) { throw new InvalidOperationException("Cannot retrieve children of an evaluation result that is not tied to a frame."); } + if (Expression == null) { + throw new InvalidOperationException("Cannot retrieve children of an evaluation result that does not have an associated expression."); + } var call = Invariant($@"rtvs:::toJSON(rtvs:::describe_children( {Expression.ToRStringLiteral()}, {StackFrame.SysFrame}, {fields.ToRVector()}, {maxLength}, {reprMaxLength}))"); - var jChildren = await StackFrame.Session.InvokeDebugHelperAsync(call); + var jChildren = await StackFrame.Session.InvokeDebugHelperAsync(call, cancellationToken); Trace.Assert( jChildren.Children().All(t => t is JObject), Invariant($"rtvs:::describe_children(): object of objects expected.\n\n{jChildren}")); @@ -258,7 +280,7 @@ public async Task> GetChildrenAsync( } public override string ToString() { - return Invariant($"VALUE: {TypeName} {Representation.DPut}"); + return Invariant($"VALUE: {TypeName} {Representation.Deparse}"); } } diff --git a/src/Debugger/Impl/DebugSession.cs b/src/Debugger/Impl/DebugSession.cs index 4f94433e1..589908517 100644 --- a/src/Debugger/Impl/DebugSession.cs +++ b/src/Debugger/Impl/DebugSession.cs @@ -1,406 +1,413 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Text.RegularExpressions; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Common.Core; -using Microsoft.R.Host.Client; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using static System.FormattableString; - -namespace Microsoft.R.Debugger { - public sealed class DebugSession : IDisposable { - private Task _initializeTask; - private readonly object _initializeLock = new object(); - - private CancellationTokenSource _initialPromptCts = new CancellationTokenSource(); - private TaskCompletionSource _stepTcs; - private DebugStackFrame _bpHitFrame; - private volatile EventHandler _browse; - private volatile DebugBrowseEventArgs _currentBrowseEventArgs; - private readonly object _browseLock = new object(); - - private Dictionary _breakpoints = new Dictionary(); - - public IReadOnlyCollection Breakpoints => _breakpoints.Values; - - public IRSession RSession { get; private set; } - - public bool IsBrowsing => _currentBrowseEventArgs != null; - - public event EventHandler Browse { - add { - var eventArgs = _currentBrowseEventArgs; - if (eventArgs != null) { - value?.Invoke(this, eventArgs); - } - - lock (_browseLock) { - _browse += value; - } - } - remove { - lock (_browseLock) { - _browse -= value; - } - } - } - - public DebugSession(IRSession session) { - RSession = session; - RSession.Connected += RSession_Connected; - RSession.BeforeRequest += RSession_BeforeRequest; - RSession.AfterRequest += RSession_AfterRequest; - } - - public void Dispose() { - RSession.Connected -= RSession_Connected; - RSession.BeforeRequest -= RSession_BeforeRequest; - RSession.AfterRequest -= RSession_AfterRequest; - RSession = null; - } - - private void ThrowIfDisposed() { - if (RSession == null) { - throw new ObjectDisposedException(nameof(DebugSession)); - } - } - - public Task InitializeAsync() { - lock (_initializeLock) { - if (_initializeTask == null) { - _initializeTask = InitializeWorkerAsync(); - } - return _initializeTask; - } - } - - private async Task InitializeWorkerAsync() { - ThrowIfDisposed(); - - await TaskUtilities.SwitchToBackgroundThread(); - - using (var eval = await RSession.BeginEvaluationAsync()) { - // Re-initialize the breakpoint table. - foreach (var bp in _breakpoints.Values) { - await eval.EvaluateAsync(bp.GetAddBreakpointExpression(false)); // TODO: mark breakpoint as invalid if this fails. - } - - await eval.EvaluateAsync("rtvs:::reapply_breakpoints()"); // TODO: mark all breakpoints as invalid if this fails. - } - - // Attach might happen when session is already at the Browse prompt, in which case we have - // missed the corresponding BeginRequest event, but we want to raise Browse anyway. So - // grab an interaction and check the prompt. - RSession.BeginInteractionAsync().ContinueWith(async t => { - using (var inter = await t) { - // If we got AfterRequest before we got here, then that has already taken care of - // the prompt; or if it's not a Browse prompt, will do so in a future event. Bail out.' - if (_initialPromptCts.IsCancellationRequested) { - return; - } - - // Otherwise, treat it the same as if AfterRequest just happened. - ProcessBrowsePrompt(inter.Contexts); - } - }).DoNotWait(); - } - - public async Task ExecuteBrowserCommandAsync(string command) { - ThrowIfDisposed(); - await TaskUtilities.SwitchToBackgroundThread(); - - using (var inter = await RSession.BeginInteractionAsync(isVisible: true)) { - if (IsBrowserContext(inter.Contexts)) { - await inter.RespondAsync(command + "\n"); - } - } - } - - internal async Task InvokeDebugHelperAsync(string expression, bool json = false) { - TaskUtilities.AssertIsOnBackgroundThread(); - ThrowIfDisposed(); - - REvaluationResult res; - using (var eval = await RSession.BeginEvaluationAsync(false)) { - res = await eval.EvaluateAsync(expression, json ? REvaluationKind.Json : REvaluationKind.Normal); - if (res.ParseStatus != RParseStatus.OK || res.Error != null || (json && res.JsonResult == null)) { - Trace.Fail(Invariant($"Internal debugger evaluation {expression} failed: {res}")); - throw new REvaluationException(res); - } - } - - return res; - } - - internal async Task InvokeDebugHelperAsync(string expression) - where TToken : JToken { - - var res = await InvokeDebugHelperAsync(expression, json: true); - - var token = res.JsonResult as TToken; - if (token == null) { - var err = Invariant($"Expected to receive {typeof(TToken).Name} in response to {expression}, but got {res.JsonResult?.GetType().Name}"); - Trace.Fail(err); - throw new JsonException(err); - } - - return token; - } - - public Task EvaluateAsync(string expression) { - return EvaluateAsync(null, expression); - } - - public async Task EvaluateAsync( - DebugStackFrame stackFrame, - string expression, - string name = null, - string env = null, - DebugEvaluationResultFields fields = DebugEvaluationResultFields.All, - int? reprMaxLength = null - ) { - ThrowIfDisposed(); - - await TaskUtilities.SwitchToBackgroundThread(); - await InitializeAsync(); - - env = env ?? stackFrame?.SysFrame ?? "NULL"; - var code = Invariant($"rtvs:::toJSON(rtvs:::eval_and_describe({expression.ToRStringLiteral()}, {env},, {fields.ToRVector()},, {reprMaxLength}))"); - var jEvalResult = await InvokeDebugHelperAsync(code); - return DebugEvaluationResult.Parse(stackFrame, name, jEvalResult); - } - - public async Task Break() { - await TaskUtilities.SwitchToBackgroundThread(); - using (var inter = await RSession.BeginInteractionAsync(isVisible: true)) { - await inter.RespondAsync("browser()\n"); - } - } - - public async Task Continue() { - await TaskUtilities.SwitchToBackgroundThread(); - ExecuteBrowserCommandAsync("c") - .SilenceException() - .SilenceException() - .DoNotWait(); - } - - public Task StepIntoAsync() { - return StepAsync("s"); - } - - public Task StepOverAsync() { - return StepAsync("n"); - } - - public Task StepOutAsync() { - return StepAsync("browserSetDebug()", "c"); - } - - private async Task StepAsync(params string[] commands) { - Trace.Assert(commands.Length > 0); - ThrowIfDisposed(); - - await TaskUtilities.SwitchToBackgroundThread(); - - _stepTcs = new TaskCompletionSource(); - for (int i = 0; i < commands.Length - 1; ++i) { - await ExecuteBrowserCommandAsync(commands[i]); - } - - // If RException happens, it means that the expression we just stepped over caused an error. - // The step is still considered successful and complete in that case, so we just ignore it. - ExecuteBrowserCommandAsync(commands.Last()) - .SilenceException() - .SilenceException() - .DoNotWait(); - - await _stepTcs.Task; - } - - public bool CancelStep() { - ThrowIfDisposed(); - - if (_stepTcs == null) { - return false; - } - - _stepTcs.TrySetCanceled(); - _stepTcs = null; - return true; - } - - public async Task> GetStackFramesAsync() { - ThrowIfDisposed(); - - await TaskUtilities.SwitchToBackgroundThread(); - await InitializeAsync(); - - var jFrames = await InvokeDebugHelperAsync("rtvs:::describe_traceback()"); - Trace.Assert(jFrames.All(t => t is JObject), "rtvs:::describe_traceback(): array of objects expected.\n\n" + jFrames); - - var stackFrames = new List(); - - DebugStackFrame lastFrame = null; - int i = 0; - foreach (JObject jFrame in jFrames) { - var fallbackFrame = (_bpHitFrame != null && _bpHitFrame.Index == i) ? _bpHitFrame : null; - lastFrame = new DebugStackFrame(this, i, lastFrame, jFrame, fallbackFrame); - stackFrames.Add(lastFrame); - ++i; - } - - return stackFrames; - } - - public async Task EnableBreakpoints(bool enable) { - ThrowIfDisposed(); - await TaskUtilities.SwitchToBackgroundThread(); - await InvokeDebugHelperAsync(Invariant($"rtvs:::enable_breakpoints({(enable ? "TRUE" : "FALSE")})")); - } - - public async Task CreateBreakpointAsync(DebugBreakpointLocation location) { - ThrowIfDisposed(); - - await TaskUtilities.SwitchToBackgroundThread(); - await InitializeAsync(); - - DebugBreakpoint bp; - if (!_breakpoints.TryGetValue(location, out bp)) { - bp = new DebugBreakpoint(this, location); - _breakpoints.Add(location, bp); - } - - await bp.SetBreakpointAsync(); - return bp; - } - - internal void RemoveBreakpoint(DebugBreakpoint breakpoint) { - Trace.Assert(breakpoint.Session == this); - _breakpoints.Remove(breakpoint.Location); - } - - private bool IsBrowserContext(IReadOnlyList contexts) { - return contexts.SkipWhile(context => context.CallFlag.HasFlag(RContextType.Restart)) - .FirstOrDefault()?.CallFlag.HasFlag(RContextType.Browser) == true; - } - - private void InterruptBreakpointHitProcessing() { - _bpHitFrame = null; - } - - private void ProcessBrowsePrompt(IReadOnlyList contexts) { - if (!IsBrowserContext(contexts)) { - InterruptBreakpointHitProcessing(); - return; - } - - RSession.BeginInteractionAsync().ContinueWith(async t => { - using (var inter = await t) { - if (inter.Contexts != contexts) { - // Someone else has already responded to this interaction. - InterruptBreakpointHitProcessing(); - return; - } else { - await ProcessBrowsePromptWorker(inter); - } - } - }).DoNotWait(); - } - - private async Task ProcessBrowsePromptWorker(IRSessionInteraction inter) { - var frames = await GetStackFramesAsync(); - - // If there's .doTrace(rtvs:::breakpoint) anywhere on the stack, we're inside the internal machinery - // that triggered Browse> prompt when hitting a breakpoint. We need to step out of it until we're - // back at the frame where the breakpoint was actually set, so that those internal frames do not show - // on the call stack, and further stepping does not try to step through them. - // Since browserSetDebug-based step out is not reliable in the presence of loops, we'll just keep - // stepping over with "n" until we're all the way out. Every step will trigger a new prompt, and - // we will come back to this method again. - var doTraceFrame = frames.FirstOrDefault(frame => frame.FrameKind == DebugStackFrameKind.DoTrace); - if (doTraceFrame != null) { - // Save the .doTrace frame so that we can report file / line number info correctly later, once we're fully stepped out. - // TODO: remove this hack when injected breakpoints get proper source info (#570). - _bpHitFrame = doTraceFrame; - - await inter.RespondAsync(Invariant($"n\n")); - return; - } - - IReadOnlyCollection breakpointsHit = null; - var lastFrame = frames.LastOrDefault(); - if (lastFrame != null) { - // Report breakpoints first, so that by the time step completion is reported, all actions associated - // with breakpoints (e.g. printing messages for tracepoints) have already been completed. - if (lastFrame.FileName != null && lastFrame.LineNumber != null) { - var location = new DebugBreakpointLocation(lastFrame.FileName, lastFrame.LineNumber.Value); - DebugBreakpoint bp; - if (_breakpoints.TryGetValue(location, out bp)) { - bp.RaiseBreakpointHit(); - breakpointsHit = Enumerable.Repeat(bp, bp.UseCount).ToArray(); - } - } - } - - bool isStepCompleted = false; - if (_stepTcs != null) { - var stepTcs = _stepTcs; - _stepTcs = null; - stepTcs.TrySetResult(null); - isStepCompleted = true; - } - - EventHandler browse; - lock (_browseLock) { - browse = _browse; - } - - var eventArgs = new DebugBrowseEventArgs(inter.Contexts, isStepCompleted, breakpointsHit); - _currentBrowseEventArgs = eventArgs; - browse?.Invoke(this, eventArgs); - } - - private void RSession_Connected(object sender, EventArgs e) { - lock (_initializeLock) { - _initializeTask = null; - } - - InitializeAsync().DoNotWait(); - } - - private void RSession_BeforeRequest(object sender, RRequestEventArgs e) { - _initialPromptCts.Cancel(); - ProcessBrowsePrompt(e.Contexts); - } - - private void RSession_AfterRequest(object sender, RRequestEventArgs e) { - _currentBrowseEventArgs = null; - } - } - - public class REvaluationException : Exception { - public REvaluationResult Result { get; } - - public REvaluationException(REvaluationResult result) { - Result = result; - } - } - - public class DebugBrowseEventArgs : EventArgs { - public IReadOnlyList Contexts { get; } - public bool IsStepCompleted { get; } - public IReadOnlyCollection BreakpointsHit { get; } - - public DebugBrowseEventArgs(IReadOnlyList contexts, bool isStepCompleted, IReadOnlyCollection breakpointsHit) { - Contexts = contexts; - IsStepCompleted = isStepCompleted; - BreakpointsHit = breakpointsHit ?? new DebugBreakpoint[0]; - } - } -} +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Common.Core; +using Microsoft.R.Host.Client; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using static System.FormattableString; + +namespace Microsoft.R.Debugger { + public sealed class DebugSession : IDisposable { + private Task _initializeTask; + private readonly object _initializeLock = new object(); + + private CancellationTokenSource _initialPromptCts = new CancellationTokenSource(); + private TaskCompletionSource _stepTcs; + private DebugStackFrame _bpHitFrame; + private volatile EventHandler _browse; + private volatile DebugBrowseEventArgs _currentBrowseEventArgs; + private readonly object _browseLock = new object(); + + private Dictionary _breakpoints = new Dictionary(); + + public IReadOnlyCollection Breakpoints => _breakpoints.Values; + + public IRSession RSession { get; private set; } + + public bool IsBrowsing => _currentBrowseEventArgs != null; + + public event EventHandler Browse { + add { + var eventArgs = _currentBrowseEventArgs; + if (eventArgs != null) { + value?.Invoke(this, eventArgs); + } + + lock (_browseLock) { + _browse += value; + } + } + remove { + lock (_browseLock) { + _browse -= value; + } + } + } + + public DebugSession(IRSession session) { + RSession = session; + RSession.Connected += RSession_Connected; + RSession.BeforeRequest += RSession_BeforeRequest; + RSession.AfterRequest += RSession_AfterRequest; + } + + public void Dispose() { + RSession.Connected -= RSession_Connected; + RSession.BeforeRequest -= RSession_BeforeRequest; + RSession.AfterRequest -= RSession_AfterRequest; + RSession = null; + } + + private void ThrowIfDisposed() { + if (RSession == null) { + throw new ObjectDisposedException(nameof(DebugSession)); + } + } + + public Task InitializeAsync(CancellationToken cancellationToken = default(CancellationToken)) { + lock (_initializeLock) { + if (_initializeTask == null) { + _initializeTask = InitializeWorkerAsync(cancellationToken); + } + + return _initializeTask; + } + } + + private async Task InitializeWorkerAsync(CancellationToken cancellationToken = default(CancellationToken)) { + ThrowIfDisposed(); + await TaskUtilities.SwitchToBackgroundThread(); + try { + using (var eval = await RSession.BeginEvaluationAsync(cancellationToken: cancellationToken)) { + // Re-initialize the breakpoint table. + foreach (var bp in _breakpoints.Values) { + await eval.EvaluateAsync(bp.GetAddBreakpointExpression(false)); // TODO: mark breakpoint as invalid if this fails. + } + + await eval.EvaluateAsync("rtvs:::reapply_breakpoints()"); // TODO: mark all breakpoints as invalid if this fails. + } + + // Attach might happen when session is already at the Browse prompt, in which case we have + // missed the corresponding BeginRequest event, but we want to raise Browse anyway. So + // grab an interaction and check the prompt. + RSession.BeginInteractionAsync(cancellationToken: cancellationToken).ContinueWith(async t => { + using (var inter = await t) { + // If we got AfterRequest before we got here, then that has already taken care of + // the prompt; or if it's not a Browse prompt, will do so in a future event. Bail out.' + if (_initialPromptCts.IsCancellationRequested) { + return; + } + + // Otherwise, treat it the same as if AfterRequest just happened. + ProcessBrowsePrompt(inter.Contexts); + } + }, cancellationToken).DoNotWait(); + } catch (Exception ex) when (!ex.IsCriticalException()) { + Dispose(); + throw; + } + } + + public async Task ExecuteBrowserCommandAsync(string command, CancellationToken cancellationToken = default(CancellationToken)) { + ThrowIfDisposed(); + await TaskUtilities.SwitchToBackgroundThread(); + + using (var inter = await RSession.BeginInteractionAsync(isVisible: true, cancellationToken: cancellationToken)) { + if (IsBrowserContext(inter.Contexts)) { + await inter.RespondAsync(command + "\n"); + } + } + } + + internal async Task InvokeDebugHelperAsync(string expression, CancellationToken cancellationToken, bool json = false) { + TaskUtilities.AssertIsOnBackgroundThread(); + ThrowIfDisposed(); + + REvaluationResult res; + using (var eval = await RSession.BeginEvaluationAsync(false, cancellationToken)) { + res = await eval.EvaluateAsync(expression, json ? REvaluationKind.Json : REvaluationKind.Normal); + if (res.ParseStatus != RParseStatus.OK || res.Error != null || (json && res.JsonResult == null)) { + Trace.Fail(Invariant($"Internal debugger evaluation {expression} failed: {res}")); + throw new REvaluationException(res); + } + } + + return res; + } + + internal async Task InvokeDebugHelperAsync(string expression, CancellationToken cancellationToken) + where TToken : JToken { + + var res = await InvokeDebugHelperAsync(expression, cancellationToken, json: true); + + var token = res.JsonResult as TToken; + if (token == null) { + var err = Invariant($"Expected to receive {typeof(TToken).Name} in response to {expression}, but got {res.JsonResult?.GetType().Name}"); + Trace.Fail(err); + throw new JsonException(err); + } + + return token; + } + + public Task EvaluateAsync(string expression, CancellationToken cancellationToken = default(CancellationToken)) { + return EvaluateAsync(null, expression, cancellationToken: cancellationToken); + } + + public async Task EvaluateAsync( + DebugStackFrame stackFrame, + string expression, + string name = null, + string env = null, + DebugEvaluationResultFields fields = DebugEvaluationResultFields.All, + int? reprMaxLength = null, + CancellationToken cancellationToken = default(CancellationToken) + ) { + ThrowIfDisposed(); + + await TaskUtilities.SwitchToBackgroundThread(); + await InitializeAsync(cancellationToken); + + env = env ?? stackFrame?.SysFrame ?? "NULL"; + var code = Invariant($"rtvs:::toJSON(rtvs:::eval_and_describe({expression.ToRStringLiteral()}, {env},, {fields.ToRVector()},, {reprMaxLength}))"); + var jEvalResult = await InvokeDebugHelperAsync(code, cancellationToken); + return DebugEvaluationResult.Parse(stackFrame, name, jEvalResult); + } + + public async Task Break(CancellationToken ct = default(CancellationToken)) { + await TaskUtilities.SwitchToBackgroundThread(); + using (var inter = await RSession.BeginInteractionAsync(true, ct)) { + await inter.RespondAsync("browser()\n"); + } + } + + public async Task Continue(CancellationToken cancellationToken = default(CancellationToken)) { + await TaskUtilities.SwitchToBackgroundThread(); + ExecuteBrowserCommandAsync("c", cancellationToken) + .SilenceException() + .SilenceException() + .DoNotWait(); + } + + public Task StepIntoAsync(CancellationToken cancellationToken = default(CancellationToken)) { + return StepAsync(cancellationToken, "s"); + } + + public Task StepOverAsync(CancellationToken cancellationToken = default(CancellationToken)) { + return StepAsync(cancellationToken, "n"); + } + + public Task StepOutAsync(CancellationToken cancellationToken = default(CancellationToken)) { + return StepAsync(cancellationToken, "rtvs:::browser_set_debug()", "c"); + } + + /// + /// true if step completed successfully, and false if it was interrupted midway by something + /// else pausing the process, such as a breakpoint. + /// + private async Task StepAsync(CancellationToken cancellationToken, params string[] commands) { + Trace.Assert(commands.Length > 0); + ThrowIfDisposed(); + + await TaskUtilities.SwitchToBackgroundThread(); + + _stepTcs = new TaskCompletionSource(); + for (int i = 0; i < commands.Length - 1; ++i) { + await ExecuteBrowserCommandAsync(commands[i], cancellationToken); + } + + // If RException happens, it means that the expression we just stepped over caused an error. + // The step is still considered successful and complete in that case, so we just ignore it. + ExecuteBrowserCommandAsync(commands.Last(), cancellationToken) + .SilenceException() + .SilenceException() + .DoNotWait(); + + return await _stepTcs.Task; + } + + public bool CancelStep() { + ThrowIfDisposed(); + + if (_stepTcs == null) { + return false; + } + + _stepTcs.TrySetCanceled(); + _stepTcs = null; + return true; + } + + public async Task> GetStackFramesAsync(CancellationToken cancellationToken = default(CancellationToken)) { + ThrowIfDisposed(); + + await TaskUtilities.SwitchToBackgroundThread(); + await InitializeAsync(cancellationToken); + + var jFrames = await InvokeDebugHelperAsync("rtvs:::describe_traceback()", cancellationToken); + Trace.Assert(jFrames.All(t => t is JObject), "rtvs:::describe_traceback(): array of objects expected.\n\n" + jFrames); + + var stackFrames = new List(); + + DebugStackFrame lastFrame = null; + int i = 0; + foreach (JObject jFrame in jFrames) { + var fallbackFrame = (_bpHitFrame != null && _bpHitFrame.Index == i) ? _bpHitFrame : null; + lastFrame = new DebugStackFrame(this, i, lastFrame, jFrame, fallbackFrame); + stackFrames.Add(lastFrame); + ++i; + } + + return stackFrames; + } + + public async Task EnableBreakpointsAsync(bool enable, CancellationToken ct = default(CancellationToken)) { + ThrowIfDisposed(); + await TaskUtilities.SwitchToBackgroundThread(); + await InvokeDebugHelperAsync(Invariant($"rtvs:::enable_breakpoints({(enable ? "TRUE" : "FALSE")})"), ct); + } + + public async Task CreateBreakpointAsync(DebugBreakpointLocation location, CancellationToken cancellationToken = default(CancellationToken)) { + ThrowIfDisposed(); + + await TaskUtilities.SwitchToBackgroundThread(); + await InitializeAsync(cancellationToken); + + DebugBreakpoint bp; + if (!_breakpoints.TryGetValue(location, out bp)) { + bp = new DebugBreakpoint(this, location); + _breakpoints.Add(location, bp); + } + + await bp.SetBreakpointAsync(cancellationToken); + return bp; + } + + internal void RemoveBreakpoint(DebugBreakpoint breakpoint) { + Trace.Assert(breakpoint.Session == this); + _breakpoints.Remove(breakpoint.Location); + } + + private bool IsBrowserContext(IReadOnlyList contexts) { + return contexts.SkipWhile(context => context.CallFlag.HasFlag(RContextType.Restart)) + .FirstOrDefault()?.CallFlag.HasFlag(RContextType.Browser) == true; + } + + private void InterruptBreakpointHitProcessing() { + _bpHitFrame = null; + } + + private void ProcessBrowsePrompt(IReadOnlyList contexts) { + if (!IsBrowserContext(contexts)) { + InterruptBreakpointHitProcessing(); + return; + } + + RSession.BeginInteractionAsync().ContinueWith(async t => { + using (var inter = await t) { + if (inter.Contexts != contexts) { + // Someone else has already responded to this interaction. + InterruptBreakpointHitProcessing(); + return; + } else { + await ProcessBrowsePromptWorker(inter); + } + } + }).DoNotWait(); + } + + private async Task ProcessBrowsePromptWorker(IRSessionInteraction inter) { + var frames = await GetStackFramesAsync(); + + // If there's .doTrace(rtvs:::breakpoint) anywhere on the stack, we're inside the internal machinery + // that triggered Browse> prompt when hitting a breakpoint. We need to step out of it until we're + // back at the frame where the breakpoint was actually set, so that those internal frames do not show + // on the call stack, and further stepping does not try to step through them. + // Since browserSetDebug-based step out is not reliable in the presence of loops, we'll just keep + // stepping over with "n" until we're all the way out. Every step will trigger a new prompt, and + // we will come back to this method again. + var doTraceFrame = frames.FirstOrDefault(frame => frame.FrameKind == DebugStackFrameKind.DoTrace); + if (doTraceFrame != null) { + // Save the .doTrace frame so that we can report file / line number info correctly later, once we're fully stepped out. + // TODO: remove this hack when injected breakpoints get proper source info (#570). + _bpHitFrame = doTraceFrame; + + await inter.RespondAsync(Invariant($"n\n")); + return; + } + + IReadOnlyCollection breakpointsHit = null; + var lastFrame = frames.LastOrDefault(); + if (lastFrame != null) { + // Report breakpoints first, so that by the time step completion is reported, all actions associated + // with breakpoints (e.g. printing messages for tracepoints) have already been completed. + if (lastFrame.FileName != null && lastFrame.LineNumber != null) { + var location = new DebugBreakpointLocation(lastFrame.FileName, lastFrame.LineNumber.Value); + DebugBreakpoint bp; + if (_breakpoints.TryGetValue(location, out bp)) { + bp.RaiseBreakpointHit(); + breakpointsHit = Enumerable.Repeat(bp, bp.UseCount).ToArray(); + } + } + } + + bool isStepCompleted = false; + if (_stepTcs != null) { + var stepTcs = _stepTcs; + _stepTcs = null; + stepTcs.TrySetResult(breakpointsHit == null || breakpointsHit.Count == 0); + isStepCompleted = true; + } + + EventHandler browse; + lock (_browseLock) { + browse = _browse; + } + + var eventArgs = new DebugBrowseEventArgs(inter.Contexts, isStepCompleted, breakpointsHit); + _currentBrowseEventArgs = eventArgs; + browse?.Invoke(this, eventArgs); + } + + private void RSession_Connected(object sender, EventArgs e) { + lock (_initializeLock) { + _initializeTask = null; + } + + InitializeAsync().DoNotWait(); + } + + private void RSession_BeforeRequest(object sender, RRequestEventArgs e) { + _initialPromptCts.Cancel(); + ProcessBrowsePrompt(e.Contexts); + } + + private void RSession_AfterRequest(object sender, RRequestEventArgs e) { + _currentBrowseEventArgs = null; + } + } + + public class REvaluationException : Exception { + public REvaluationResult Result { get; } + + public REvaluationException(REvaluationResult result) { + Result = result; + } + } + + public class DebugBrowseEventArgs : EventArgs { + public IReadOnlyList Contexts { get; } + public bool IsStepCompleted { get; } + public IReadOnlyCollection BreakpointsHit { get; } + + public DebugBrowseEventArgs(IReadOnlyList contexts, bool isStepCompleted, IReadOnlyCollection breakpointsHit) { + Contexts = contexts; + IsStepCompleted = isStepCompleted; + BreakpointsHit = breakpointsHit ?? new DebugBreakpoint[0]; + } + } +} diff --git a/src/Debugger/Impl/DebugStackFrame.cs b/src/Debugger/Impl/DebugStackFrame.cs index 40b1498a6..312f78442 100644 --- a/src/Debugger/Impl/DebugStackFrame.cs +++ b/src/Debugger/Impl/DebugStackFrame.cs @@ -1,8 +1,6 @@ -using System; -using System.Diagnostics; -using System.Text.RegularExpressions; +using System.Text.RegularExpressions; +using System.Threading; using System.Threading.Tasks; -using Microsoft.R.Host.Client; using Newtonsoft.Json.Linq; using static System.FormattableString; @@ -21,7 +19,7 @@ public class DebugStackFrame { public int Index { get; } - internal string SysFrame => Invariant($"sys.frame({Index})"); + internal string SysFrame => Invariant($"base::sys.frame({Index})"); public DebugStackFrame CallingFrame { get; } @@ -48,24 +46,7 @@ internal DebugStackFrame(DebugSession session, int index, DebugStackFrame callin var match = _doTraceRegex.Match(Call); if (match.Success) { FrameKind = DebugStackFrameKind.DoTrace; - try { - // When setBreakpoint injects .doTrace calls, it does not inject source information for them. - // Consequently, then such a call is on the stack - i.e. when a breakpoint is hit - there is - // no information about which filename and line number we're on in the call object. - // To work around that, we use our own special wrapper function for tracer instead of just - // plain browser(), and we pass this data as arguments to that function. The function itself - // does not actually use them, but they appear as part of the calling expression, and we can - // extract them from here. - // In case setBreakpoint is changed in the future to correctly adjust the source info for the - // function, only make use of the parsed data if the values couldn't be properly obtained. - FileName = FileName ?? match.Groups["filename"].Value.FromRStringLiteral(); - LineNumber = LineNumber ?? int.Parse(match.Groups["line_number"].Value); - } catch (FormatException) { - // This should never happen with .doTrace calls that we insert, but the user can always manually - // insert one. Assert in Debug to detect code changes that break our inserted .doTrace. - Debug.Fail(Invariant($"Couldn't parse RTVS .doTrace call: {Call}")); - } - } + } if (fallbackFrame != null) { // If we still don't have the filename and line number, use those from the fallback frame. @@ -82,13 +63,17 @@ public Task EvaluateAsync( string expression, string name = null, DebugEvaluationResultFields fields = DebugEvaluationResultFields.All, - int? reprMaxLength = null + int? reprMaxLength = null, + CancellationToken cancellationToken = default(CancellationToken) ) { - return Session.EvaluateAsync(this, expression, name, null, fields, reprMaxLength); + return Session.EvaluateAsync(this, expression, name, null, fields, reprMaxLength, cancellationToken); } - public Task GetEnvironmentAsync() { - return EvaluateAsync("environment()"); + public Task GetEnvironmentAsync( + DebugEvaluationResultFields fields = DebugEvaluationResultFields.Expression | DebugEvaluationResultFields.Length | DebugEvaluationResultFields.AttrCount, + CancellationToken cancellationToken = default(CancellationToken) + ) { + return EvaluateAsync("base::environment()", fields: fields, cancellationToken: cancellationToken); } } } diff --git a/src/Debugger/Impl/IDebugSessionProvider.cs b/src/Debugger/Impl/IDebugSessionProvider.cs index c71c20e52..a557d3875 100644 --- a/src/Debugger/Impl/IDebugSessionProvider.cs +++ b/src/Debugger/Impl/IDebugSessionProvider.cs @@ -1,8 +1,9 @@ -using System.Threading.Tasks; +using System.Threading; +using System.Threading.Tasks; using Microsoft.R.Host.Client; namespace Microsoft.R.Debugger { public interface IDebugSessionProvider { - Task GetDebugSessionAsync(IRSession session); + Task GetDebugSessionAsync(IRSession session, CancellationToken cancellationToken = default(CancellationToken)); } } diff --git a/src/Debugger/Impl/Microsoft.R.Debugger.csproj b/src/Debugger/Impl/Microsoft.R.Debugger.csproj index b2b859547..57b6c8910 100644 --- a/src/Debugger/Impl/Microsoft.R.Debugger.csproj +++ b/src/Debugger/Impl/Microsoft.R.Debugger.csproj @@ -91,6 +91,15 @@ PreserveNewest + True + + + PreserveNewest + True + + + PreserveNewest + True PreserveNewest diff --git a/src/Debugger/Impl/rtvs/R/breakpoints.R b/src/Debugger/Impl/rtvs/R/breakpoints.R index dee5f141e..2fe7125a9 100644 --- a/src/Debugger/Impl/rtvs/R/breakpoints.R +++ b/src/Debugger/Impl/rtvs/R/breakpoints.R @@ -1,203 +1,255 @@ -locals <- as.environment(list(breakpoints_enabled = FALSE)); - -# List of all active breakpoints. -# Names are filenames, values are vectors of line numbers. -breakpoints <- new.env(parent = emptyenv()) - -# Used to check whether a breakpoint is still valid at this location. -is_breakpoint <- function(filename, line_number) { - if (locals$breakpoints_enabled) { - line_numbers <- breakpoints[[filename]]; - return(line_number %in% line_numbers); - } -} - -# Adds a new breakpoint to the list of active breakpoints. If reapply=TRUE, will -# automatically reapply the new list. -add_breakpoint <- function(filename, line_number, reapply = TRUE) { - breakpoints[[filename]] <- c(breakpoints[[filename]], line_number); - if (reapply) { - reapply_breakpoints(); - } -} - -# Removes the breakpoint from the list. -remove_breakpoint <- function(filename, line_number) { - bps <- setdiff(breakpoints[[filename]], line_number); - if (length(bps) == 0) { - rm(list = filename, envir = breakpoints); - } else { - breakpoints[[filename]] <- bps; - } - - # TODO: implement reverse reapply: walk all environments looking for injected breakpoints, - # and remove those that are no longer in the list using attr('rtvs::original_expr'). -} - -# Walks environments, starting from .GlobalEnv up, and injects active breakpoints -# into all functions in those environments. -reapply_breakpoints <- function() { - if (!locals$breakpoints_enabled) { - return(); - } - - (function(env, visited = NULL) { - if (!identical(env, emptyenv()) && !any(sapply(visited, function(x) identical(x, env)))) { - visited <- c(visited, env); - - for (name in names(env)) { - x <- tryCatch({ - env[[name]] - }, error = function(e) { - NULL - }); - - if (!missing(x)) { - if (is.function(x)) { - bp_body <- inject_breakpoints(body(x)); - if (!is.null(bp_body)) { - tryCatch({ - body(env[[name]]) <- bp_body; - }, error = function(e) { - }); - } - # TODO: also walk parent.env of the function, in case it was defined - # elsewhere and then copied here? - } else if (is.environment(x)) { - # TODO: also walk nested environments? - # Recall(x, visited); - } - } - } - - Recall(parent.env(env), visited); - } - })(.GlobalEnv); - - # TODO: also walk environment chains for all loaded namespaces. - - # TODO: this is expensive already. If a deeper walk is performed, this might - # have to be rewritten in native code to get acceptable perf. -} - -# Given an expression or a language object, and a line number, return the steps that -# must be taken from the root of that object to get to the instruction that is located -# at that line number, or NULL if there is no such instruction. The returned value -# is an integer vector that can be directly used as an index for [[ ]]. -steps_for_line_num <- function(expr, line, have_srcrefs = FALSE) { - is_brace <- function(expr) - typeof(expr) == 'symbol' && identical(as.character(expr), '{') - - if (typeof(expr) == 'language' || typeof(expr) == 'expression') { - srcrefs <- attr(expr, 'srcref') - for (i in seq_along(expr)) { - srcref <- srcrefs[[i]] - - # Check for non-matching range. - if (!is.null(srcref) && (srcref[1] > line || line > srcref[3])) { - next - } - - # We're in range. See if there's a finer division, and add it as a substep if so. - finer <- steps_for_line_num(expr[[i]], line, have_srcrefs || !is.null(srcrefs)) - if (!is.null(finer)) { - return(c(i, finer)) - } - - # If there was no subdivision, then this is the exact instruction, but only if - # there was an srcref. However, if this is an opening curly brace for a block, - # and parent had an srcref, then match the parent (which will be the whole block). - if (!is.null(srcref) && (!have_srcrefs || !is_brace(expr[[i]]))) { - return(i) - } - } - } - - NULL -} - -# Given an expression or language object, return that object with all active -# breakpoints injected into it, or NULL if no breakpoints were injected. -inject_breakpoints <- function(expr) { - if (length(breakpoints) == 0) { - return(NULL); - } - - filename <- getSrcFilename(expr); - if (is.null(filename) || !is.character(filename) || length(filename) != 1 || is.na(filename) || identical(filename, '')) { - return(NULL); - } - - line_numbers <- breakpoints[[filename]]; - if (is.null(line_numbers)) { - return(NULL); - } - - changed <- FALSE; - for (line_num in line_numbers) { - step <- steps_for_line_num(expr, line_num); - if (length(step) > 0) { - bp_expr <- expr[[step]]; - original_expr <- attr(bp_expr, 'rtvs::original_expr'); - if (is.null(original_expr)) { - original_expr <- bp_expr; - } - - expr[[step]] <- substitute({.doTrace(if (rtvs:::is_breakpoint(FILENAME, LINE_NUMBER)) browser()); EXPR}, - list(FILENAME = filename, LINE_NUMBER = line_num, EXPR = original_expr)); - attr(expr[[step]], 'rtvs::original_expr') <- original_expr; - attr(expr[[step]], 'srcref') <- attr(original_expr, 'srcref'); - changed <- TRUE; - } - } - - if (!changed) { - return(NULL); - } - - expr -} - -# Enables or disables instrumentation that makes breakpoints work. -enable_breakpoints <- function(enable) { - if (locals$breakpoints_enabled != enable) { - locals$breakpoints_enabled <- enable; - if (enable) { - call_embedded('set_instrumentation_callback', inject_breakpoints); - reapply_breakpoints(); - } else { - call_embedded('set_instrumentation_callback', NULL); - } - } -} - -# Like parse, but returns a single `{` call object wrapping the content of the file, rather than -# an expression object containing separate calls. Consequently, when the returned object is eval'd, -# it is possible to use debug stepping commands to execute expressions sequentially. -debug_parse <- function(filename, encoding = getOption('encoding')) { - exprs <- parse(filename, encoding = encoding); - - # Create a `{` call wrapping all expressions in the file. - result <- quote({}); - for (i in 1:length(exprs)) { - result[[i + 1]] <- exprs[[i]]; - } - - # Copy top-level source info. - attr(result, 'srcfile') <- attr(exprs, 'srcfile'); - - # Since the result has indices shifted by 1 due to the addition of `{` at the beginning, - # per-line source info needs to be adjusted accordingly before copying. - old_srcref <- attr(exprs, 'srcref'); - new_srcref <- list(attr(exprs, 'srcref')[[1]]); - for (i in 1:length(exprs)) { - new_srcref[[i + 1]] <- old_srcref[[i]]; - } - attr(result, 'srcref') <- new_srcref; - - result -} - -debug_source <- function(file, encoding = getOption('encoding')) { - eval.parent(debug_parse(file, encoding)) -} +locals <- as.environment(list(breakpoints_enabled = FALSE)); + +# List of all active breakpoints. +# Names are filenames, values are vectors of line numbers. +breakpoints <- new.env(parent = emptyenv()) + +# Used to check whether a breakpoint is still valid at this location. +is_breakpoint <- function(filename, line_number) { + if (locals$breakpoints_enabled) { + line_numbers <- breakpoints[[filename]]; + return(line_number %in% line_numbers); + } +} + +# Adds a new breakpoint to the list of active breakpoints. If reapply=TRUE, will +# automatically reapply the new list. +add_breakpoint <- function(filename, line_number, reapply = TRUE) { + breakpoints[[filename]] <- c(breakpoints[[filename]], line_number); + if (reapply) { + reapply_breakpoints(); + } +} + +# Removes the breakpoint from the list. +remove_breakpoint <- function(filename, line_number) { + bps <- setdiff(breakpoints[[filename]], line_number); + if (length(bps) == 0) { + rm(list = filename, envir = breakpoints); + } else { + breakpoints[[filename]] <- bps; + } + + # TODO: implement reverse reapply: walk all environments looking for injected breakpoints, + # and remove those that are no longer in the list using attr('rtvs::original_expr'). +} + +# Walks environments, starting from .GlobalEnv up, and injects active breakpoints +# into all functions in those environments. +reapply_breakpoints <- function() { + if (!locals$breakpoints_enabled) { + return(); + } + + (function(env, visited = NULL) { + if (!identical(env, emptyenv()) && !any(sapply(visited, function(x) identical(x, env)))) { + visited <- c(visited, env); + + for (name in names(env)) { + x <- tryCatch({ + env[[name]] + }, error = function(e) { + NULL + }); + + if (!missing(x)) { + if (is.function(x)) { + bp_body <- inject_breakpoints(body(x)); + if (!is.null(bp_body)) { + tryCatch({ + body(env[[name]]) <- bp_body; + }, error = function(e) { + }); + } + # TODO: also walk parent.env of the function, in case it was defined + # elsewhere and then copied here? + } else if (is.environment(x)) { + # TODO: also walk nested environments? + # Recall(x, visited); + } + } + } + + Recall(parent.env(env), visited); + } + })(.GlobalEnv); + + # TODO: also walk environment chains for all loaded namespaces. + + # TODO: this is expensive already. If a deeper walk is performed, this might + # have to be rewritten in native code to get acceptable perf. +} + +# Given an expression or a language object, and a line number, return the steps that +# must be taken from the root of that object to get to the instruction that is located +# at that line number, or NULL if there is no such instruction. The returned value +# is an integer vector that can be directly used as an index for [[ ]]. +steps_for_line_num <- function(expr, line, have_srcrefs = FALSE) { + is_brace <- function(expr) + typeof(expr) == 'symbol' && identical(as.character(expr), '{') + + if (typeof(expr) == 'language' || typeof(expr) == 'expression') { + srcrefs <- attr(expr, 'srcref') + for (i in seq_along(expr)) { + srcref <- srcrefs[[i]] + + # Check for non-matching range. + if (!is.null(srcref) && (srcref[1] > line || line > srcref[3])) { + next + } + + # We're in range. See if there's a finer division, and add it as a substep if so. + finer <- steps_for_line_num(expr[[i]], line, have_srcrefs || !is.null(srcrefs)) + if (!is.null(finer)) { + return(c(i, finer)) + } + + # If there was no subdivision, then this is the exact instruction, but only if there was an srcref, + # and this is not a breakpoint. However, if this is an opening curly brace for a block, and parent + # had an srcref, then match the parent (which will be the whole block). + if (!is.null(srcref) && !isTRUE(attr(expr[[i]], 'rtvs::is_breakpoint')) && (!have_srcrefs || !is_brace(expr[[i]]))) { + return(i) + } + } + } + + NULL +} + +# Given an expression or language object, return that object with all active +# breakpoints injected into it, or NULL if no breakpoints were injected. +inject_breakpoints <- function(expr) { + if (length(breakpoints) == 0 || length(expr) == 0) { + return(NULL); + } + + filename <- getSrcFilename(expr); + if (is.null(filename) || !is.character(filename) || length(filename) != 1 || is.na(filename) || identical(filename, '')) { + return(NULL); + } + + line_numbers <- breakpoints[[filename]]; + if (is.null(line_numbers)) { + return(NULL); + } + + original_expr <- expr; + changed <- FALSE; + + for (line_num in sort(line_numbers)) { + step <- steps_for_line_num(expr, line_num); + if (length(step) > 0) { + new_expr <- expr; + target_expr <- expr[[step]]; + + # If there's already an injected breakpoint there, nothing to do for this line. + if (isTRUE(attr(target_expr, 'rtvs::at_breakpoint')) || !is.null(attr(target_expr, 'rtvs::original_expr'))) { + next; + } + + new_expr[[step]] <- substitute({ + .doTrace(if (rtvs:::is_breakpoint(FILENAME, LINE_NUMBER)) browser()); + EXPR + }, list( + FILENAME = filename, + LINE_NUMBER = line_num, + EXPR = target_expr + )); + + attr(new_expr[[step]], 'rtvs::original_expr') <- target_expr; + attr(new_expr[[step]][[2]], 'rtvs::is_breakpoint') <- TRUE; + attr(new_expr[[step]][[3]], 'rtvs::at_breakpoint') <- TRUE; + + expr <- new_expr; + changed <- TRUE; + } + } + + if (!changed) { + return(NULL); + } + + # Recursively copy srcrefs from the original expression to the new one with injected breakpoints, + # synthesizing them for injected breakpoint expressions from original expressions that they replace + expr <- (function(before, after) { + if (is.symbol(before) || is.symbol(after)) { + return(after); + } + + before_srcref <- attr(before, 'srcref'); + attr(after, 'srcref') <- before_srcref; + + for (i in 1:length(after)) { + if (is.null(attr(before[[i]], 'rtvs::original_expr')) && !is.null(attr(after[[i]], 'rtvs::original_expr'))) { + # If it has the original_expr attribute that wasn't there before, it's an breakpoint expression that + # was freshly injected, replacing the original expression at the point where the breakpoint was set. + # It looks like this: + # + # {.doTrace(...); } + # + # Copy srcrefs from the original, replicating them such that they apply to the entirety of + # the replacement expression, so that the same line is considered current for all of it. + attr(after[[i]], 'srcref') <- rep(list(before_srcref[[i]]), length(after[[i]])); + + # Auto-step over '{' and '.doTrace', so that stepping skips to the original expression. + attr(attr(after, 'srcref')[[i]], 'Microsoft.R.Host::auto_step_over') <- TRUE; + attr(attr(after[[i]], 'srcref')[[2]], 'Microsoft.R.Host::auto_step_over') <- TRUE; + + # Recurse into the original expression, in case it has more breakpoints inside. + after[[i]][[3]] <- Recall(before[[i]], after[[i]][[3]]); + } else if (is.language(after[[i]])) { + # Otherwise, if this is not a literal, keep recursing down in case injected breakpoints + # are in the subexpressions of this expression. + after[[i]] <- Recall(before[[i]], after[[i]]); + } + } + + after + })(original_expr, expr); + + expr +} + +# Enables or disables instrumentation that makes breakpoints work. +enable_breakpoints <- function(enable) { + if (locals$breakpoints_enabled != enable) { + locals$breakpoints_enabled <- enable; + if (enable) { + call_embedded('set_instrumentation_callback', inject_breakpoints); + reapply_breakpoints(); + } else { + call_embedded('set_instrumentation_callback', NULL); + } + } +} + +# Like parse, but returns a single `{` call object wrapping the content of the file, rather than +# an expression object containing separate calls. Consequently, when the returned object is eval'd, +# it is possible to use debug stepping commands to execute expressions sequentially. +debug_parse <- function(filename, encoding = getOption('encoding')) { + exprs <- parse(filename, encoding = encoding); + + # Create a `{` call wrapping all expressions in the file. + result <- quote({}); + for (i in 1:length(exprs)) { + result[[i + 1]] <- exprs[[i]]; + } + + # Copy top-level source info. + attr(result, 'srcfile') <- attr(exprs, 'srcfile'); + + # Since the result has indices shifted by 1 due to the addition of `{` at the beginning, + # per-line source info needs to be adjusted accordingly before copying. + old_srcref <- attr(exprs, 'srcref'); + new_srcref <- list(attr(exprs, 'srcref')[[1]]); + for (i in 1:length(exprs)) { + new_srcref[[i + 1]] <- old_srcref[[i]]; + } + attr(result, 'srcref') <- new_srcref; + + result +} + +debug_source <- function(file, encoding = getOption('encoding')) { + safe_eval(debug_parse(file, encoding), parent.frame(1)) +} diff --git a/src/Debugger/Impl/rtvs/R/completions.R b/src/Debugger/Impl/rtvs/R/completions.R new file mode 100644 index 000000000..42315bdcb --- /dev/null +++ b/src/Debugger/Impl/rtvs/R/completions.R @@ -0,0 +1,11 @@ +signature.help2 <- function(f, p) { + x <- help(paste(f), paste(p)) + y <- utils:::.getHelpFile(x) + paste0(y, collapse = '') +} + +signature.help1 <- function(f) { + x <- help(paste(f)) + y <- utils:::.getHelpFile(x) + paste0(y, collapse = '') +} diff --git a/src/Debugger/Impl/rtvs/R/eval.R b/src/Debugger/Impl/rtvs/R/eval.R index a538ee7b1..53ed240b1 100644 --- a/src/Debugger/Impl/rtvs/R/eval.R +++ b/src/Debugger/Impl/rtvs/R/eval.R @@ -23,8 +23,15 @@ describe_object <- function(obj, res, fields, repr_max_length = NA) { if (field('repr')) { repr <- new.env(); - if (field('repr.dput')) { - repr$dput <- NA_if_error(dput_str(obj, repr_max_length, 0x100)); + if (field('repr.deparse')) { + repr$deparse <- paste0(collapse = '', NA_if_error( + if (is.na(repr_max_length)) { + deparse(obj) + } else { + # Force max length into range permitted by deparse + cutoff <- min(max(repr_max_length, 20), 500); + deparse(obj, width.cutoff = cutoff, nlines = 1) + })) } if (field('repr.toString')) { @@ -68,7 +75,7 @@ describe_object <- function(obj, res, fields, repr_max_length = NA) { if (field('dim')) { dim <- NA_if_error(dim(obj)); if (is.integer(dim) && !anyNA(dim)) { - res$dim <- dim; + res$dim <- as.list(dim); } } @@ -81,7 +88,7 @@ describe_object <- function(obj, res, fields, repr_max_length = NA) { } if (field('flags')) { - res$flags <- c( + res$flags <- list( if (is.atomic(obj)) "atomic" else NA, if (is.recursive(obj)) "recursive" else NA, if (has_parent_env) "has_parent_env" else NA @@ -179,15 +186,15 @@ describe_children <- function(obj, env, fields, count = NULL, repr_max_length = }); item_expr <- - if (expr == 'environment()') { - dput_symbol(name) + if (expr == 'base::environment()') { + deparse_symbol(name) } else { - paste0(expr, '$', dput_symbol(name), collapse = '') + paste0(expr, '$', deparse_symbol(name), collapse = '') }; if (!is.null(code)) { # It's a promise - we don't want to force it as it could affect the debugged code. - value <- list(promise = dput_str(code), expression = item_expr); + value <- list(promise = deparse(code), expression = item_expr); } else if (bindingIsActive(name, obj)) { # It's an active binding - we don't want to read it to avoid inadvertently changing program state. value <- list(active_binding = TRUE, expression = item_expr); @@ -237,11 +244,11 @@ describe_children <- function(obj, env, fields, count = NULL, repr_max_length = # For S4 objects, slots can be accessed with '@'. For other objects, we have to # use slot(). Still, always use '@' as accessor name to show to the user. - accessor <- paste0('@', dput_symbol(name), collapse = ''); + accessor <- paste0('@', deparse_symbol(name), collapse = ''); if (is_S4) { slot_expr <- paste0('(', expr, ')', accessor, collapse = '') } else { - slot_expr <- paste0('methods::slot((', expr, '), ', dput_str(name), ')', collapse = '') + slot_expr <- paste0('methods::slot((', expr, '), ', deparse(name), ')', collapse = '') } value <- eval_and_describe(slot_expr, environment(), '@', fields, slot(obj, name), repr_max_length); @@ -270,7 +277,7 @@ describe_children <- function(obj, env, fields, count = NULL, repr_max_length = # avoid presenting an infinitely recursive model (a vector of size 1 has the only child, # which is another vector of size 1 etc). However, we do want to show the only child if # it is named, so that the name is exposed. - if (n != 1 || !is.atomic(obj) || !(is.null(names[[1]]) || is.na(names[[1]]) || names[[1]] == '')) { + if (n != 1 || !is.atomic(obj) || (length(names) >= 1 && !(is.null(names[[1]]) || is.na(names[[1]]) || names[[1]] == ''))) { if (!is.null(count)) { n <- min(n, count); count <<- count - n; @@ -285,14 +292,19 @@ describe_children <- function(obj, env, fields, count = NULL, repr_max_length = # if this item corresponds to the first mention of that name - i.e. if we have # c(1,2,3), and names() is c('x','y','x'), then c[[1]] is named 'x', but c[[3]] # is effectively unnamed, because there's no way to address it by name. - name <- force_toString(names[[i]]); + name <- tryCatch({ + names[[i]] + }, error = function(e) { + NULL + }); + name <- force_toString(name); if (name != '' && match(name, names, -1) == i) { kind <- '$'; # Named items can be accessed with '$' in lists, but other types require brackets. if (is.list(obj)) { - accessor <- paste0('$', dput_symbol(name), collapse = ''); + accessor <- paste0('$', deparse_symbol(name), collapse = ''); } else { - accessor <- paste0('[[', dput_str(name), ']]', collapse = ''); + accessor <- paste0('[[', deparse(name), ']]', collapse = ''); } } diff --git a/src/Debugger/Impl/rtvs/R/graphics.R b/src/Debugger/Impl/rtvs/R/graphics.R new file mode 100644 index 000000000..eda412d46 --- /dev/null +++ b/src/Debugger/Impl/rtvs/R/graphics.R @@ -0,0 +1,33 @@ +graphics.xaml <- function(filename, width, height) { + invisible(external_embedded('xaml_graphicsdevice_new', filename, width, height)) +} + +graphics.ide.resize <- function(width, height) { + invisible(external_embedded('ide_graphicsdevice_resize', width, height)) +} + +graphics.ide.new <- function() { + invisible(external_embedded('ide_graphicsdevice_new')) +} + +graphics.ide.exportimage <- function(filename, device, width, height) { + dev.copy(device=device,filename=filename,width=width,height=height) + dev.off() +} + +graphics.ide.exportpdf <- function(filename, width, height, paper) { + dev.copy(device=pdf,file=filename,width=width,height=height,paper=paper) + dev.off() +} + +graphics.ide.nextplot <- function() { + invisible(external_embedded('ide_graphicsdevice_next_plot')) +} + +graphics.ide.previousplot <- function() { + invisible(external_embedded('ide_graphicsdevice_previous_plot')) +} + +graphics.ide.historyinfo <- function() { + external_embedded('ide_graphicsdevice_history_info') +} diff --git a/src/Debugger/Impl/rtvs/R/grid.R b/src/Debugger/Impl/rtvs/R/grid.R index 73da05892..64f4140ac 100644 --- a/src/Debugger/Impl/rtvs/R/grid.R +++ b/src/Debugger/Impl/rtvs/R/grid.R @@ -1,17 +1,13 @@ -grid.header <- function(obj, range, isRow) { - vp <- list(); - - dn <- dimnames(obj); - if (!is.null(dn) && (length(dn)==2)) { - if (isRow) { - vp$headers<-grid.str.vector(dn[[1]][range]); - } else { - vp$headers<-grid.str.vector(dn[[2]][range]) - } +grid.trim <- function(str, max_length = 100) { + if (nchar(str) > (100 - 3)) { + paste(substr(str, 1, 97), '...', sep=''); } else { - vp$headers<-list(); + str; } - vp; +} + +grid.format <- function(x) { + sapply(format(x, trim = TRUE), grid.trim); } grid.data <- function(x, rows, cols) { @@ -20,18 +16,35 @@ grid.data <- function(x, rows, cols) { stop('grid.data requires two dimensional object'); } - x0 <- as.data.frame(x[rows, cols]); - x1 <- apply(x0, 2, format); + if ((length(rows) == 1) || (length(cols) == 1)) { + x1 <- grid.format(x[rows, cols]); + } else { + x0 <- as.data.frame(x[rows, cols]); + x1 <- sapply(x0, grid.format, USE.NAMES=FALSE); + } vp<-list(); dn <- dimnames(x); if (!is.null(dn) && (length(dn)==2)) { - vp$dimnames <- 'true'; - vp$row.names <- sapply(row.names(x)[rows], format, USE.NAMES = FALSE); - vp$col.names <- sapply(colnames(x)[cols], format, USE.NAMES = FALSE); + dnvalue <- 0; + vp$dimnames <- dnvalue; + if (!is.null(dn[[1]])) { + vp$row.names <- sapply(row.names(x)[rows], format, USE.NAMES = FALSE); + dnvalue <- dnvalue + 1; + } else { + vp$row.names <- 'dummy'; + } + + if (!is.null(dn[[2]])) { + vp$col.names <- sapply(colnames(x)[cols], format, USE.NAMES = FALSE); + dnvalue <- dnvalue + 2; + } else { + vp$col.names <- 'dummy'; + } + vp$dimnames <- format(dnvalue); } else { - vp$dimnames <- 'false'; + vp$dimnames <- '0'; vp$row.names <- 'dummy'; # dummy required for parser vp$col.names <- 'dummy'; } @@ -41,24 +54,6 @@ grid.data <- function(x, rows, cols) { vp; } -grid.str.vector<-function(v) { - vr <- list(); - index<-1; - for (item in v) { - if (is.character(item)){ - vr[[index]]<-item; - } else { - vr[[index]]<-capture.output(str(item, give.head = FALSE)); - } - index<-index+1; - } - vr; -}; - -grid.dput2 <- function(obj) { - capture.output(cat(capture.output(dput(obj)))); -} - grid.dput <- function(obj) { conn <- memory_connection(NA, 0x10000); json <- "{}"; @@ -71,15 +66,3 @@ grid.dput <- function(obj) { json; } -grid.toJSON <- function(obj) { - conn <- textConnection(NULL, open = "w"); - json <- "{}"; - tryCatch({ - rtvs:::toJSON(obj, conn); - cat('\n', file = conn, sep = ''); - json <- textConnectionValue(conn); - }, finally = { - close(conn); - }); - json; -} diff --git a/src/Debugger/Impl/rtvs/R/json.R b/src/Debugger/Impl/rtvs/R/json.R index adacb7194..afff375d6 100644 --- a/src/Debugger/Impl/rtvs/R/json.R +++ b/src/Debugger/Impl/rtvs/R/json.R @@ -1,15 +1,15 @@ # Produces JSON from an R object according to the following mappings: # -# NULL -> null +# NULL, NA, empty vector -> null # TRUE -> true # FALSE -> false -# Vector of a single non-NA number -> numeric literal +# Vector of a single non-NA integer or double -> numeric literal # Vector of a single non-NA string -> string literal -# Empty or multiple-element vector -> array (recursively) # List with all elements unnamed -> array (recursively) # List with all elements named, or environment -> object (recursively) # -# If any element of a vector, list or environment is NA, that element is skipped. +# If any element of a list or environment is NA, that element is skipped. +# Any input not covered by the rules above is considered invalid. toJSON <- function(data, con) { if (missing(con)) { con <- memory_connection(NA, 0x10000); @@ -21,18 +21,21 @@ toJSON <- function(data, con) { to_literal_or_array <- function() { if (length(data) == 0) { cat('null', file = con, sep = ''); - } else if (length(data) == 1 && !is.na(data)) { - # Atomic vector of length 1 is a boolean, number, or string literal. - if (is.logical(data)) { + } else if (length(data) == 1) { + # Atomic vector of length 1 is NA, boolean, number, or string literal. + if (is.na(data)) { + cat('null', file = con, sep = ''); + } else if (is.logical(data)) { cat((if (data) 'true' else 'false'), file = con, sep = ''); + } else if (is.double(data)) { + dput(data, con); } else if (is.integer(data)) { dput(as.double(data), con); } else { dput(data, con); } } else { - # If it's 0 or more than 1 element, treat it as an array. - toJSON(as.list(data), con); + stop("vector must have 0 or 1 element to be convertible to JSON"); } } diff --git a/src/Debugger/Impl/rtvs/R/util.R b/src/Debugger/Impl/rtvs/R/util.R index cb4522a37..66befb117 100644 --- a/src/Debugger/Impl/rtvs/R/util.R +++ b/src/Debugger/Impl/rtvs/R/util.R @@ -2,6 +2,10 @@ call_embedded <- function(name, ...) { .Call(paste0('Microsoft.R.Host::Call.', name, collapse = ''), ..., PACKAGE = '(embedding)') } +external_embedded <- function(name, ...) { + .External(paste0('Microsoft.R.Host::External.', name, collapse = ''), ..., PACKAGE = '(embedding)') +} + memory_connection <- function(max_length = NA, expected_length = NA, overflow_suffix = '', eof_marker = '') { call_embedded('memory_connection', max_length, expected_length, overflow_suffix, eof_marker) } @@ -30,6 +34,10 @@ set_rdebug <- function(obj, debug) { call_embedded("set_rdebug", obj, debug) } +browser_set_debug <- function(n = 1) { + call_embedded("browser_set_debug", n) +} + NA_if_error <- function(expr) { tryCatch(expr, error = function(e) { NA }) } @@ -64,12 +72,12 @@ dput_str <- function(obj, max_length = NA, expected_length = NA, overflow_suffix gsub("^\\s+|\\s+$", "", memory_connection_tochar(con)) } -# A wrapper for dput_str that will first make name a symbol if it can be a legitimate one. -dput_symbol <- function(name) { +# A wrapper for deparse that will first make name a symbol if it can be a legitimate one. +deparse_symbol <- function(name) { if (is.character(name) && length(name) == 1 && !is.na(name) && nchar(name) > 0) { name <- as.symbol(name); } - dput_str(name) + deparse(name) } # Like str(...)[[1]], but special-cases some common types to provide a more descriptive diff --git a/src/Debugger/Test/BreakpointsTest.cs b/src/Debugger/Test/BreakpointsTest.cs new file mode 100644 index 000000000..12f54e0d6 --- /dev/null +++ b/src/Debugger/Test/BreakpointsTest.cs @@ -0,0 +1,86 @@ +using System.Diagnostics.CodeAnalysis; +using System.Threading; +using System.Threading.Tasks; +using FluentAssertions; +using Microsoft.Common.Core.Test.Script; +using Microsoft.Languages.Editor.Shell; +using Microsoft.R.Host.Client; +using Microsoft.R.Host.Client.Test.Script; +using Microsoft.UnitTests.Core.XUnit; +using Xunit; + +namespace Microsoft.R.Debugger.Test { + [ExcludeFromCodeCoverage] + [Collection(CollectionNames.NonParallel)] + public class BreakpointsTest { + [Test] + [Category.R.Debugger] + public async Task SetRemoveBreakpoint() { + var sessionProvider = EditorShell.Current.ExportProvider.GetExportedValue(); + using (new RHostScript(sessionProvider)) { + IRSession session = sessionProvider.GetOrCreate(GuidList.InteractiveWindowRSessionGuid, new RHostClientTestApp()); + using (var debugSession = new DebugSession(session)) { + string content = +@"x <- 1 + y <- 2 + z <- 3 +"; + using (var sf = new SourceFile(content)) { + var bpl1 = new DebugBreakpointLocation(sf.FilePath, 1); + DebugBreakpoint bp1 = await debugSession.CreateBreakpointAsync(bpl1, default(CancellationToken)); + + bp1.Location.Should().Be(bpl1); + bp1.Session.Should().Be(debugSession); + + debugSession.Breakpoints.Count.Should().Be(1); + + var bpl2 = new DebugBreakpointLocation(sf.FilePath, 3); + DebugBreakpoint bp2 = await debugSession.CreateBreakpointAsync(bpl2, default(CancellationToken)); + + bp2.Location.Should().Be(bpl2); + bp2.Session.Should().Be(debugSession); + + debugSession.Breakpoints.Count.Should().Be(2); + + await bp1.DeleteAsync(default(CancellationToken)); + debugSession.Breakpoints.Count.Should().Be(1); + } + } + } + } + + [Test] + [Category.R.Debugger] + public async Task HitBreakpoint() { + var sessionProvider = EditorShell.Current.ExportProvider.GetExportedValue(); + using (new RHostScript(sessionProvider)) { + IRSession session = sessionProvider.GetOrCreate(GuidList.InteractiveWindowRSessionGuid, new RHostClientTestApp()); + using (var debugSession = new DebugSession(session)) { + string content = +@"x <- 1 + y <- 2 + z <- 3 +"; + using (var sf = new SourceFile(content)) { + await debugSession.EnableBreakpointsAsync(true, default(CancellationToken)); + + var bpl = new DebugBreakpointLocation(sf.FilePath, 2); + DebugBreakpoint bp = await debugSession.CreateBreakpointAsync(bpl, default(CancellationToken)); + + int eventCount = 0; + bp.BreakpointHit += (s, e) => { + eventCount++; + }; + + await sf.Source(session); + + // Allow pending thread transitions and async/awaits to complete + EventsPump.DoEvents(3000); + + eventCount.Should().Be(1); + } + } + } + } + } +} diff --git a/src/Debugger/Test/Microsoft.R.Debugger.Test.csproj b/src/Debugger/Test/Microsoft.R.Debugger.Test.csproj new file mode 100644 index 000000000..7e1b9e985 --- /dev/null +++ b/src/Debugger/Test/Microsoft.R.Debugger.Test.csproj @@ -0,0 +1,113 @@ + + + + + Debug + AnyCPU + {6D3AC84F-D53F-4494-8142-DD2BC8FD9CFD} + Library + Properties + Microsoft.R.Debugger.Test + Microsoft.R.Debugger.Test + v4.6 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + UnitTest + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + ..\..\..\obj\ + ..\..\..\bin\ + $(BaseIntermediateOutputPath)\$(Configuration)\$(AssemblyName)\ + $(BaseOutputPath)\$(Configuration)\ + + + + $(RootDirectory)\obj\ + $(RootDirectory)\bin\ + $(BaseIntermediateOutputPath)\$(Configuration)\$(AssemblyName)\ + $(BaseOutputPath)\$(Configuration)\ + + + + ..\..\..\NugetPackages\FluentAssertions.4.1.1\lib\net45\FluentAssertions.dll + True + + + ..\..\..\NugetPackages\FluentAssertions.4.1.1\lib\net45\FluentAssertions.Core.dll + True + + + + + + + + + ..\..\..\NugetPackages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll + True + + + ..\..\..\NugetPackages\xunit.assert.2.2.0-beta1-build3239\lib\dotnet\xunit.assert.dll + True + + + ..\..\..\NugetPackages\xunit.extensibility.core.2.2.0-beta1-build3239\lib\dotnet\xunit.core.dll + True + + + ..\..\..\NugetPackages\xunit.extensibility.execution.2.2.0-beta1-build3239\lib\net45\xunit.execution.desktop.dll + True + + + + + Properties\GlobalAssemblyInfo.cs + + + + + + + + + {8d408909-459f-4853-a36c-745118f99869} + Microsoft.Common.Core + + + {fc4aad0a-13b9-49ee-a59c-f03142958170} + Microsoft.Common.Core.Test + + + {e09d3bda-2e6b-47b5-87ac-b6fc2d33dfab} + Microsoft.R.Host.Client + + + {e1b1909e-3193-499b-91db-1e13e6836929} + Microsoft.R.Host.Client.Test + + + {62857e49-e586-4baa-ae4d-1232093e7378} + Microsoft.Languages.Editor + + + {5ef2ad64-d6fe-446b-b350-8c7f0df0834d} + Microsoft.UnitTests.Core + + + {17e155bf-351c-4253-b9b1-36eeea35fe3c} + Microsoft.R.Debugger + + + + + + + + + + \ No newline at end of file diff --git a/src/Debugger/Test/Properties/AssemblyInfo.cs b/src/Debugger/Test/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..5f282702b --- /dev/null +++ b/src/Debugger/Test/Properties/AssemblyInfo.cs @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/Debugger/Test/SourceFile.cs b/src/Debugger/Test/SourceFile.cs new file mode 100644 index 000000000..b12a64f7b --- /dev/null +++ b/src/Debugger/Test/SourceFile.cs @@ -0,0 +1,29 @@ +using System; +using System.IO; +using System.Threading.Tasks; +using Microsoft.R.Host.Client; + +namespace Microsoft.R.Debugger.Test { + sealed class SourceFile : IDisposable { + public string FilePath { get; } + + public SourceFile(string content) { + FilePath = Path.GetTempFileName(); + using (var sw = new StreamWriter(FilePath)) { + sw.Write(content); + } + } + + public async Task Source(IRSession session) { + using (IRSessionInteraction eval = await session.BeginInteractionAsync()) { + await eval.RespondAsync($"rtvs::debug_source({FilePath.ToRStringLiteral()})" + Environment.NewLine); + } + } + + public void Dispose() { + try { + File.Delete(FilePath); + } catch (IOException) { } + } + } +} diff --git a/src/Debugger/Test/SteppingTest.cs b/src/Debugger/Test/SteppingTest.cs new file mode 100644 index 000000000..41753d68f --- /dev/null +++ b/src/Debugger/Test/SteppingTest.cs @@ -0,0 +1,132 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using FluentAssertions; +using Microsoft.Common.Core.Test.Script; +using Microsoft.Languages.Editor.Shell; +using Microsoft.R.Host.Client; +using Microsoft.R.Host.Client.Test.Script; +using Microsoft.UnitTests.Core.XUnit; +using Xunit; + +namespace Microsoft.R.Debugger.Test { + [ExcludeFromCodeCoverage] + [Collection(CollectionNames.NonParallel)] + public class SteppingTest { + private const string code = +@"f <- function(x) { + x + 1 +} +x <- 1 +y <- f(x) +z <- x + y"; + + [Test] + [Category.R.Debugger] + public async Task StepOver() { + var sessionProvider = EditorShell.Current.ExportProvider.GetExportedValue(); + using (new RHostScript(sessionProvider)) { + IRSession session = sessionProvider.GetOrCreate(GuidList.InteractiveWindowRSessionGuid, new RHostClientTestApp()); + using (var debugSession = new DebugSession(session)) { + using (var sf = new SourceFile(code)) { + await debugSession.EnableBreakpointsAsync(true); + + var bp = await debugSession.CreateBreakpointAsync(new DebugBreakpointLocation(sf.FilePath, 1)); + var bpHit = new TaskCompletionSource(); + bp.BreakpointHit += (s, e) => { + bpHit.SetResult(true); + }; + + await sf.Source(session); + await bpHit.Task; + + var stackFrames = (await debugSession.GetStackFramesAsync()).Reverse().ToArray(); + stackFrames.Should().NotBeEmpty(); + stackFrames[0].LineNumber.Should().Be(1); + + bool stepCompleted = await debugSession.StepOverAsync(); + stepCompleted.Should().Be(true); + + stackFrames = (await debugSession.GetStackFramesAsync()).Reverse().ToArray(); + stackFrames.Should().NotBeEmpty(); + stackFrames[0].LineNumber.Should().Be(2); + } + } + } + } + + [Test] + [Category.R.Debugger] + public async Task StepInto() { + var sessionProvider = EditorShell.Current.ExportProvider.GetExportedValue(); + using (new RHostScript(sessionProvider)) { + IRSession session = sessionProvider.GetOrCreate(GuidList.InteractiveWindowRSessionGuid, new RHostClientTestApp()); + using (var debugSession = new DebugSession(session)) { + using (var sf = new SourceFile(code)) { + await debugSession.EnableBreakpointsAsync(true); + + var bp = await debugSession.CreateBreakpointAsync(new DebugBreakpointLocation(sf.FilePath, 5)); + var bpHit = new TaskCompletionSource(); + bp.BreakpointHit += (s, e) => { + bpHit.SetResult(true); + }; + + await sf.Source(session); + await bpHit.Task; + + var stackFrames = (await debugSession.GetStackFramesAsync()).Reverse().ToArray(); + stackFrames.Should().NotBeEmpty(); + stackFrames[0].LineNumber.Should().Be(5); + + bool stepCompleted = await debugSession.StepIntoAsync(); + stepCompleted.Should().Be(true); + + stackFrames = (await debugSession.GetStackFramesAsync()).Reverse().ToArray(); + stackFrames.Should().HaveCount(n => n >= 2); + stackFrames[0].LineNumber.Should().Be(1); + stackFrames[1].Call.Should().Be("f(x)"); + } + } + } + } + + [Test] + [Category.R.Debugger] + public async Task StepOut() { + var sessionProvider = EditorShell.Current.ExportProvider.GetExportedValue(); + using (new RHostScript(sessionProvider)) { + IRSession session = sessionProvider.GetOrCreate(GuidList.InteractiveWindowRSessionGuid, new RHostClientTestApp()); + using (var debugSession = new DebugSession(session)) { + using (var sf = new SourceFile(code)) { + await debugSession.EnableBreakpointsAsync(true); + + var bp = await debugSession.CreateBreakpointAsync(new DebugBreakpointLocation(sf.FilePath, 2)); + var bpHit = new TaskCompletionSource(); + bp.BreakpointHit += (s, e) => { + bpHit.SetResult(true); + }; + + await sf.Source(session); + await bpHit.Task; + + var stackFrames = (await debugSession.GetStackFramesAsync()).Reverse().ToArray(); + stackFrames.Should().HaveCount(n => n >= 2); + stackFrames[0].LineNumber.Should().Be(bp.Location.LineNumber); + stackFrames[1].Call.Should().Be("f(x)"); + + bool stepCompleted = await debugSession.StepOutAsync(); + stepCompleted.Should().Be(true); + + stackFrames = (await debugSession.GetStackFramesAsync()).Reverse().ToArray(); + stackFrames.Should().HaveCount(n => n >= 2); + stackFrames[0].LineNumber.Should().Be(6); + stackFrames[1].Call.Should().NotBe("f(x)"); + } + } + } + } + } +} diff --git a/src/Debugger/Test/packages.config b/src/Debugger/Test/packages.config new file mode 100644 index 000000000..25216f6ea --- /dev/null +++ b/src/Debugger/Test/packages.config @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/src/Host/Client/Impl/Definitions/IRExpressionEvaluator.cs b/src/Host/Client/Impl/Definitions/IRExpressionEvaluator.cs index 4e1186657..c00c07a7a 100644 --- a/src/Host/Client/Impl/Definitions/IRExpressionEvaluator.cs +++ b/src/Host/Client/Impl/Definitions/IRExpressionEvaluator.cs @@ -14,6 +14,7 @@ public enum REvaluationKind { BaseEnv = 1 << 3, EmptyEnv = 1 << 4, Cancelable = 1 << 5, + NewEnv = 1 << 6, } public interface IRExpressionEvaluator { diff --git a/src/Host/Client/Impl/Definitions/IRSession.cs b/src/Host/Client/Impl/Definitions/IRSession.cs index 24756877e..3c20b7897 100644 --- a/src/Host/Client/Impl/Definitions/IRSession.cs +++ b/src/Host/Client/Impl/Definitions/IRSession.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; using System.Threading.Tasks; namespace Microsoft.R.Host.Client { @@ -16,8 +17,8 @@ public interface IRSession : IDisposable { string Prompt { get; } bool IsHostRunning { get; } - Task BeginInteractionAsync(bool isVisible = true); - Task BeginEvaluationAsync(bool isMutating = true); + Task BeginInteractionAsync(bool isVisible = true, CancellationToken cancellationToken = default(CancellationToken)); + Task BeginEvaluationAsync(bool isMutating = true, CancellationToken cancellationToken = default(CancellationToken)); Task CancelAllAsync(); Task StartHostAsync(RHostStartupInfo startupInfo, int timeout = 3000); Task StopHostAsync(); diff --git a/src/Host/Client/Impl/Host/RHost.cs b/src/Host/Client/Impl/Host/RHost.cs index 9dc402a6a..352032a80 100644 --- a/src/Host/Client/Impl/Host/RHost.cs +++ b/src/Host/Client/Impl/Host/RHost.cs @@ -1,594 +1,600 @@ -using System; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net.Sockets; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Common.Core; -using Microsoft.Common.Core.Shell; -using Microsoft.R.Actions.Logging; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; -using WebSocketSharp; -using WebSocketSharp.Server; -using static System.FormattableString; - -namespace Microsoft.R.Host.Client { - public sealed partial class RHost : IDisposable, IRExpressionEvaluator { - private readonly string[] parseStatusNames = { "NULL", "OK", "INCOMPLETE", "ERROR", "EOF" }; - - public const int DefaultPort = 5118; - public const string RHostExe = "Microsoft.R.Host.exe"; - public const string RBinPathX64 = @"bin\x64"; - - public static IRContext TopLevelContext { get; } = new RContext(RContextType.TopLevel); - - private static bool showConsole = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("RTVS_HOST_CONSOLE")); - - private IMessageTransport _transport; - private readonly object _transportLock = new object(); - private readonly TaskCompletionSource _transportTcs = new TaskCompletionSource(); - - private readonly CancellationTokenSource _cts = new CancellationTokenSource(); - private readonly string _name; - private readonly IRCallbacks _callbacks; - private readonly LinesLog _log; - private readonly FileLogWriter _fileLogWriter; - private Process _process; - private volatile Task _runTask; - private bool _canEval; - private int _rLoopDepth; - private long _nextMessageId = 1; - - private TaskCompletionSource _cancelAllTcs; - private CancellationTokenSource _cancelAllCts = new CancellationTokenSource(); - - public RHost(string name, IRCallbacks callbacks) { - _callbacks = callbacks; - _name = name; - - _fileLogWriter = FileLogWriter.InTempFolder("Microsoft.R.Host.Client" + (name != null ? "_" + name : "")); - _log = new LinesLog(_fileLogWriter); - } - - public void Dispose() { - _cts.Cancel(); - } - - public void FlushLog() { - _fileLogWriter?.Flush(); - } - - private static Exception ProtocolError(FormattableString fs, object message = null) { - var s = Invariant(fs); - if (message != null) { - s += "\n\n" + message; - } - Trace.Fail(s); - return new InvalidDataException(s); - } - - private async Task ReceiveMessageAsync(CancellationToken ct) { - var sb = new StringBuilder(); - - string json; - try { - json = await _transport.ReceiveAsync(ct); - } catch (MessageTransportException ex) when (ct.IsCancellationRequested) { - // Network errors during cancellation are expected, but should not be exposed to clients. - throw new OperationCanceledException(new OperationCanceledException().Message, ex); - } - - _log.Response(json, _rLoopDepth); - - var token = JToken.Parse(json); - - var value = token as JValue; - if (value != null && value.Value == null) { - return null; - } - - return new Message(token); - } - - private JArray CreateMessage(CancellationToken ct, out string id, string name, params object[] args) { - id = "#" + _nextMessageId + "#"; - _nextMessageId += 2; - return new JArray(id, name, args); - } - - private async Task SendAsync(JToken token, CancellationToken ct) { - TaskUtilities.AssertIsOnBackgroundThread(); - - var json = JsonConvert.SerializeObject(token); - _log.Request(json, _rLoopDepth); - - try { - await _transport.SendAsync(json, ct); - } catch (MessageTransportException ex) when (ct.IsCancellationRequested) { - // Network errors during cancellation are expected, but should not be exposed to clients. - throw new OperationCanceledException(new OperationCanceledException().Message, ex); - } - } - - private async Task SendAsync(string name, CancellationToken ct, params object[] args) { - string id; - var message = CreateMessage(ct, out id, name, args); - await SendAsync(message, ct); - return id; - } - - private async Task RespondAsync(Message request, CancellationToken ct, params object[] args) { - TaskUtilities.AssertIsOnBackgroundThread(); - - string id; - var message = CreateMessage(ct, out id, ":", request.Id, request.Name, args); - await SendAsync(message, ct); - return id; - } - - private static RContext[] GetContexts(Message message) { - var contexts = message.GetArgument(0, "contexts", JTokenType.Array) - .Select((token, i) => { - if (token.Type != JTokenType.Integer) { - throw ProtocolError($"Element #{i} of context array must be an integer:", message); - } - return new RContext((RContextType)(int)token); - }); - return contexts.ToArray(); - } - - private void CancelAll() { - var tcs = Volatile.Read(ref _cancelAllTcs); - if (tcs != null) { - Volatile.Write(ref _cancelAllCts, new CancellationTokenSource()); - tcs.TrySetResult(true); - } - } - - private async Task ShowDialog(Message request, bool allowEval, MessageButtons buttons, CancellationToken ct) { - TaskUtilities.AssertIsOnBackgroundThread(); - - request.ExpectArguments(2); - var contexts = GetContexts(request); - var s = request.GetString(1, "s", allowNull: true); - - MessageButtons input; - try { - _canEval = allowEval; - input = await _callbacks.ShowDialog(contexts, s, _canEval, buttons, ct); - } finally { - _canEval = false; - } - - ct.ThrowIfCancellationRequested(); - - string response; - switch (input) { - case MessageButtons.No: - response = "N"; - break; - case MessageButtons.Cancel: - response = "C"; - break; - case MessageButtons.Yes: - response = "Y"; - break; - default: { - FormattableString error = $"YesNoCancel: callback returned an invalid value: {input}"; - Trace.Fail(Invariant(error)); - throw new InvalidOperationException(Invariant(error)); - } - } - - await RespondAsync(request, ct, response); - } - - private async Task ReadConsole(Message request, bool allowEval, CancellationToken ct) { - TaskUtilities.AssertIsOnBackgroundThread(); - - request.ExpectArguments(5); - - var contexts = GetContexts(request); - var len = request.GetInt32(1, "len"); - var addToHistory = request.GetBoolean(2, "addToHistory"); - var retryReason = request.GetString(3, "retry_reason", allowNull: true); - var prompt = request.GetString(4, "prompt", allowNull: true); - - string input; - try { - _canEval = allowEval; - input = await _callbacks.ReadConsole(contexts, prompt, len, addToHistory, _canEval, ct); - } finally { - _canEval = false; - } - - ct.ThrowIfCancellationRequested(); - - input = input.Replace("\r\n", "\n"); - await RespondAsync(request, ct, input); - } - - public async Task EvaluateAsync(string expression, REvaluationKind kind, CancellationToken ct) { - await TaskUtilities.SwitchToBackgroundThread(); - - if (!_canEval) { - throw new InvalidOperationException("EvaluateAsync can only be called while ReadConsole or YesNoCancel is pending."); - } - - bool reentrant = false, jsonResult = false; - - var nameBuilder = new StringBuilder("="); - if (kind.HasFlag(REvaluationKind.Reentrant)) { - nameBuilder.Append('@'); - reentrant = true; - } - if (kind.HasFlag(REvaluationKind.Cancelable)) { - nameBuilder.Append('/'); - reentrant = true; - } - if (kind.HasFlag(REvaluationKind.Json)) { - nameBuilder.Append('j'); - jsonResult = true; - } - if (kind.HasFlag(REvaluationKind.BaseEnv)) { - nameBuilder.Append('B'); - } - if (kind.HasFlag(REvaluationKind.EmptyEnv)) { - nameBuilder.Append('E'); - } - var name = nameBuilder.ToString(); - - _canEval = false; - try { - expression = expression.Replace("\r\n", "\n"); - var id = await SendAsync(name, ct, expression); - - var response = await RunLoop(ct, reentrant); - if (response == null) { - throw new OperationCanceledException("Evaluation canceled because host process has been terminated."); - } - - if (response.RequestId != id || response.Name != name) { - throw ProtocolError($"Mismatched host response ['{response.Id}',':','{response.Name}',...] to evaluation request ['{id}','{name}','{expression}']"); - } - - response.ExpectArguments(1, 3); - var firstArg = response[0] as JValue; - if (firstArg != null && firstArg.Value == null) { - throw new OperationCanceledException(Invariant($"Evaluation canceled: {expression}")); - } - - response.ExpectArguments(3); - var parseStatus = response.GetEnum(0, "parseStatus", parseStatusNames); - var error = response.GetString(1, "error", allowNull: true); - - if (jsonResult) { - return new REvaluationResult(response[2], error, parseStatus); - } else { - return new REvaluationResult(response.GetString(2, "value", allowNull: true), error, parseStatus); - } - } finally { - _canEval = true; - } - } - - /// - /// Cancels any ongoing evaluations or interaction processing. - /// - public async Task CancelAllAsync() { - if (_runTask == null) { - throw new InvalidOperationException("Not connected to host."); - } - - await TaskUtilities.SwitchToBackgroundThread(); - - var tcs = new TaskCompletionSource(); - if (Interlocked.CompareExchange(ref _cancelAllTcs, tcs, null) != null) { - // Cancellation is already in progress - do nothing. - return; - } - - try { - // Cancel any pending callbacks - _cancelAllCts.Cancel(); - - try { - await SendAsync("/", _cts.Token, null); - } catch (OperationCanceledException) { - return; - } catch (MessageTransportException) { - return; - } - - await tcs.Task; - } finally { - Volatile.Write(ref _cancelAllTcs, null); - } - } - - public async Task DisconnectAsync() { - if (_runTask == null) { - throw new InvalidOperationException("Not connected to host."); - } - - await TaskUtilities.SwitchToBackgroundThread(); - - // We may get MessageTransportException from any concurrent SendAsync or ReceiveAsync when the host - // drops connection after we request it to do so. To ensure that those don't bubble up to the - // client, cancel this token to indicate that we're shutting down the host - SendAsync and - // ReceiveAsync will take care of wrapping any WSE into OperationCanceledException. - _cts.Cancel(); - - try { - // Don't use _cts, since it's already cancelled. We want to try to send this message in - // any case, and we'll catch MessageTransportException if no-one is on the other end anymore. - await SendAsync(JValue.CreateNull(), new CancellationToken()); - } catch (OperationCanceledException) { - } catch (MessageTransportException) { - } - - try { - await _runTask; - } catch (OperationCanceledException) { - // Expected during disconnect. - } catch (MessageTransportException) { - // Possible and valid during disconnect. - } - } - - private async Task RunLoop(CancellationToken ct, bool allowEval) { - TaskUtilities.AssertIsOnBackgroundThread(); - - try { - _log.EnterRLoop(_rLoopDepth++); - while (!ct.IsCancellationRequested) { - var message = await ReceiveMessageAsync(ct); - if (message == null) { - return null; - } else if (message.RequestId != null) { - return message; - } - - try { - switch (message.Name) { - case "\\": - CancelAll(); - break; - - case "?": - await ShowDialog(message, allowEval, MessageButtons.YesNoCancel, CancellationTokenSource.CreateLinkedTokenSource(ct, _cancelAllCts.Token).Token); - break; - - case "??": - await ShowDialog(message, allowEval, MessageButtons.YesNo, CancellationTokenSource.CreateLinkedTokenSource(ct, _cancelAllCts.Token).Token); - break; - - case "???": - await ShowDialog(message, allowEval, MessageButtons.OKCancel, CancellationTokenSource.CreateLinkedTokenSource(ct, _cancelAllCts.Token).Token); - break; - - case ">": - await ReadConsole(message, allowEval, CancellationTokenSource.CreateLinkedTokenSource(ct, _cancelAllCts.Token).Token); - break; - - case "!": - case "!!": - message.ExpectArguments(1); - await _callbacks.WriteConsoleEx( - message.GetString(0, "buf", allowNull: true), - message.Name.Length == 1 ? OutputType.Output : OutputType.Error, - ct); - break; - - case "![]": - message.ExpectArguments(1); - await _callbacks.ShowMessage(message.GetString(0, "s", allowNull: true), ct); - break; - - case "~+": - await _callbacks.Busy(true, ct); - break; - case "~-": - await _callbacks.Busy(false, ct); - break; - - case "~/": - _callbacks.DirectoryChanged(); - break; - - case "Plot": - await _callbacks.Plot(message.GetString(0, "xaml_file_path"), ct); - break; - - case "Browser": - await _callbacks.Browser(message.GetString(0, "help_url")); - break; - - default: - throw ProtocolError($"Unrecognized host message name:", message); - } - } catch (OperationCanceledException) when (!ct.IsCancellationRequested) { - // Cancelled via _cancelAllCts - just move onto the next message. - } - } - } finally { - _log.ExitRLoop(--_rLoopDepth); - } - - return null; - } - - private async Task RunWorker(CancellationToken ct) { - TaskUtilities.AssertIsOnBackgroundThread(); - - try { - var message = await ReceiveMessageAsync(ct); - if (message.Name != "Microsoft.R.Host" || message.RequestId != null) { - throw ProtocolError($"Microsoft.R.Host handshake expected:", message); - } - - var protocolVersion = message.GetInt32(0, "protocol_version"); - if (protocolVersion != 1) { - throw ProtocolError($"Unsupported RHost protocol version:", message); - } - - var rVersion = message.GetString(1, "R_version"); - await _callbacks.Connected(rVersion); - - message = await RunLoop(ct, allowEval: true); - if (message != null) { - throw ProtocolError($"Unexpected host response message:", message); - } - } finally { - await _callbacks.Disconnected(); - } - } - - public async Task Run(IMessageTransport transport, CancellationToken ct) { - TaskUtilities.AssertIsOnBackgroundThread(); - - if (_runTask != null) { - throw new InvalidOperationException("This host is already running."); - } - - if (transport != null) { - lock (_transportLock) { - _transport = transport; - } - } else if (_transport == null) { - throw new ArgumentNullException("transport"); - } - - try { - await (_runTask = RunWorker(ct)); - } catch (OperationCanceledException) when (ct.IsCancellationRequested) { - // Expected cancellation, do not propagate, just exit process - } catch (MessageTransportException ex) when (ct.IsCancellationRequested) { - // Network errors during cancellation are expected, but should not be exposed to clients. - throw new OperationCanceledException(new OperationCanceledException().Message, ex); - } catch (Exception ex) { - Trace.Fail("Exception in RHost run loop:\n" + ex); - throw; - } - } - - private WebSocketMessageTransport CreateWebSocketMessageTransport() { - lock (_transportLock) { - if (_transport != null) { - throw new MessageTransportException("More than one incoming connection."); - } - - var transport = new WebSocketMessageTransport(); - _transportTcs.SetResult(_transport = transport); - return transport; - } - } - - public async Task CreateAndRun(string rHome, string rCommandLineArguments, int timeout = 3000, CancellationToken ct = default(CancellationToken)) { - await TaskUtilities.SwitchToBackgroundThread(); - - string rhostExe = Path.Combine(Path.GetDirectoryName(typeof(RHost).Assembly.GetAssemblyPath()), RHostExe); - string rBinPath = Path.Combine(rHome, RBinPathX64); - - if (!File.Exists(rhostExe)) { - throw new RHostBinaryMissingException(); - } - - // Grab an available port from the ephemeral port range (per RFC 6335 8.1.2) for the server socket. - - WebSocketServer server = null; - var rnd = new Random(); - const int ephemeralRangeStart = 49152; - var ports = - from port in Enumerable.Range(ephemeralRangeStart, 0x10000 - ephemeralRangeStart) - let pos = rnd.NextDouble() - orderby pos - select port; - - foreach (var port in ports) { - ct.ThrowIfCancellationRequested(); - - server = new WebSocketServer(port) { ReuseAddress = false }; - server.AddWebSocketService("/", CreateWebSocketMessageTransport); - - try { - server.Start(); - break; - } catch (SocketException ex) { - if (ex.SocketErrorCode == SocketError.AddressAlreadyInUse) { - server = null; - } else { - throw new MessageTransportException(ex); - } - } catch (WebSocketException ex) { - throw new MessageTransportException(ex); - } - } - - if (server == null) { - throw new MessageTransportException(new SocketException((int)SocketError.AddressAlreadyInUse)); - } - - var psi = new ProcessStartInfo { - FileName = rhostExe, - UseShellExecute = false - }; - - psi.EnvironmentVariables["R_HOME"] = rHome; - psi.EnvironmentVariables["PATH"] = Environment.GetEnvironmentVariable("PATH") + ";" + rBinPath; - - if (_name != null) { - psi.Arguments += " --rhost-name " + _name; - } - - psi.Arguments += Invariant($" --rhost-connect ws://127.0.0.1:{server.Port}"); - - if (!showConsole) { - psi.CreateNoWindow = true; - } - - if (!string.IsNullOrWhiteSpace(rCommandLineArguments)) { - psi.Arguments += Invariant($" {rCommandLineArguments}"); - } - - using (this) - using (_process = Process.Start(psi)) { - _log.RHostProcessStarted(psi); - _process.EnableRaisingEvents = true; - _process.Exited += delegate { Dispose(); }; - - try { - ct = CancellationTokenSource.CreateLinkedTokenSource(ct, _cts.Token).Token; - - // Timeout increased to allow more time in test and code coverage runs. - await Task.WhenAny(_transportTcs.Task, Task.Delay(timeout)).Unwrap(); - if (!_transportTcs.Task.IsCompleted) { - _log.FailedToConnectToRHost(); - throw new RHostTimeoutException("Timed out waiting for R host process to connect"); - } - - await Run(null, ct); - } catch (Exception) { - // TODO: delete when we figure out why host occasionally times out in code coverage runs. - //await _log.WriteFormatAsync(MessageCategory.Error, "Exception running R Host: {0}", ex.Message); - throw; - } finally { - if (!_process.HasExited) { - try { - _process.WaitForExit(500); - if (!_process.HasExited) { - _process.Kill(); - _process.WaitForExit(); - } - } catch (InvalidOperationException) { - } - } - _log.RHostProcessExited(); - } - } - } - } -} +using System; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net.Sockets; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Common.Core; +using Microsoft.Common.Core.Diagnostics; +using Microsoft.Common.Core.Shell; +using Microsoft.R.Actions.Logging; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using WebSocketSharp; +using WebSocketSharp.Server; +using static System.FormattableString; + +namespace Microsoft.R.Host.Client { + public sealed partial class RHost : IDisposable, IRExpressionEvaluator { + private readonly string[] parseStatusNames = { "NULL", "OK", "INCOMPLETE", "ERROR", "EOF" }; + + public const int DefaultPort = 5118; + public const string RHostExe = "Microsoft.R.Host.exe"; + public const string RBinPathX64 = @"bin\x64"; + + public static IRContext TopLevelContext { get; } = new RContext(RContextType.TopLevel); + + private static bool showConsole = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("RTVS_HOST_CONSOLE")); + + private IMessageTransport _transport; + private readonly object _transportLock = new object(); + private readonly TaskCompletionSource _transportTcs = new TaskCompletionSource(); + + private readonly CancellationTokenSource _cts = new CancellationTokenSource(); + private readonly string _name; + private readonly IRCallbacks _callbacks; + private readonly LinesLog _log; + private readonly FileLogWriter _fileLogWriter; + private Process _process; + private volatile Task _runTask; + private bool _canEval; + private int _rLoopDepth; + private long _nextMessageId = 1; + + private TaskCompletionSource _cancelAllTcs; + private CancellationTokenSource _cancelAllCts = new CancellationTokenSource(); + + public RHost(string name, IRCallbacks callbacks) { + Check.ArgumentStringNullOrEmpty(nameof(name), name); + + _callbacks = callbacks; + _name = name; + + _fileLogWriter = FileLogWriter.InTempFolder("Microsoft.R.Host.Client" + "_" + name); + _log = new LinesLog(_fileLogWriter); + } + + public void Dispose() { + _cts.Cancel(); + } + + public void FlushLog() { + _fileLogWriter?.Flush(); + } + + private static Exception ProtocolError(FormattableString fs, object message = null) { + var s = Invariant(fs); + if (message != null) { + s += "\n\n" + message; + } + Trace.Fail(s); + return new InvalidDataException(s); + } + + private async Task ReceiveMessageAsync(CancellationToken ct) { + var sb = new StringBuilder(); + + string json; + try { + json = await _transport.ReceiveAsync(ct); + } catch (MessageTransportException ex) when (ct.IsCancellationRequested) { + // Network errors during cancellation are expected, but should not be exposed to clients. + throw new OperationCanceledException(new OperationCanceledException().Message, ex); + } + + _log.Response(json, _rLoopDepth); + + var token = JToken.Parse(json); + + var value = token as JValue; + if (value != null && value.Value == null) { + return null; + } + + return new Message(token); + } + + private JArray CreateMessage(CancellationToken ct, out string id, string name, params object[] args) { + id = "#" + _nextMessageId + "#"; + _nextMessageId += 2; + return new JArray(id, name, args); + } + + private async Task SendAsync(JToken token, CancellationToken ct) { + TaskUtilities.AssertIsOnBackgroundThread(); + + var json = JsonConvert.SerializeObject(token); + _log.Request(json, _rLoopDepth); + + try { + await _transport.SendAsync(json, ct); + } catch (MessageTransportException ex) when (ct.IsCancellationRequested) { + // Network errors during cancellation are expected, but should not be exposed to clients. + throw new OperationCanceledException(new OperationCanceledException().Message, ex); + } + } + + private async Task SendAsync(string name, CancellationToken ct, params object[] args) { + string id; + var message = CreateMessage(ct, out id, name, args); + await SendAsync(message, ct); + return id; + } + + private async Task RespondAsync(Message request, CancellationToken ct, params object[] args) { + TaskUtilities.AssertIsOnBackgroundThread(); + + string id; + var message = CreateMessage(ct, out id, ":", request.Id, request.Name, args); + await SendAsync(message, ct); + return id; + } + + private static RContext[] GetContexts(Message message) { + var contexts = message.GetArgument(0, "contexts", JTokenType.Array) + .Select((token, i) => { + if (token.Type != JTokenType.Integer) { + throw ProtocolError($"Element #{i} of context array must be an integer:", message); + } + return new RContext((RContextType)(int)token); + }); + return contexts.ToArray(); + } + + private void CancelAll() { + var tcs = Volatile.Read(ref _cancelAllTcs); + if (tcs != null) { + Volatile.Write(ref _cancelAllCts, new CancellationTokenSource()); + tcs.TrySetResult(true); + } + } + + private async Task ShowDialog(Message request, bool allowEval, MessageButtons buttons, CancellationToken ct) { + TaskUtilities.AssertIsOnBackgroundThread(); + + request.ExpectArguments(2); + var contexts = GetContexts(request); + var s = request.GetString(1, "s", allowNull: true); + + MessageButtons input; + try { + _canEval = allowEval; + input = await _callbacks.ShowDialog(contexts, s, _canEval, buttons, ct); + } finally { + _canEval = false; + } + + ct.ThrowIfCancellationRequested(); + + string response; + switch (input) { + case MessageButtons.No: + response = "N"; + break; + case MessageButtons.Cancel: + response = "C"; + break; + case MessageButtons.Yes: + response = "Y"; + break; + default: { + FormattableString error = $"YesNoCancel: callback returned an invalid value: {input}"; + Trace.Fail(Invariant(error)); + throw new InvalidOperationException(Invariant(error)); + } + } + + await RespondAsync(request, ct, response); + } + + private async Task ReadConsole(Message request, bool allowEval, CancellationToken ct) { + TaskUtilities.AssertIsOnBackgroundThread(); + + request.ExpectArguments(5); + + var contexts = GetContexts(request); + var len = request.GetInt32(1, "len"); + var addToHistory = request.GetBoolean(2, "addToHistory"); + var retryReason = request.GetString(3, "retry_reason", allowNull: true); + var prompt = request.GetString(4, "prompt", allowNull: true); + + string input; + try { + _canEval = allowEval; + input = await _callbacks.ReadConsole(contexts, prompt, len, addToHistory, _canEval, ct); + } finally { + _canEval = false; + } + + ct.ThrowIfCancellationRequested(); + + input = input.Replace("\r\n", "\n"); + await RespondAsync(request, ct, input); + } + + public async Task EvaluateAsync(string expression, REvaluationKind kind, CancellationToken ct) { + await TaskUtilities.SwitchToBackgroundThread(); + + if (!_canEval) { + throw new InvalidOperationException("EvaluateAsync can only be called while ReadConsole or YesNoCancel is pending."); + } + + bool reentrant = false, jsonResult = false; + + var nameBuilder = new StringBuilder("="); + if (kind.HasFlag(REvaluationKind.Reentrant)) { + nameBuilder.Append('@'); + reentrant = true; + } + if (kind.HasFlag(REvaluationKind.Cancelable)) { + nameBuilder.Append('/'); + reentrant = true; + } + if (kind.HasFlag(REvaluationKind.Json)) { + nameBuilder.Append('j'); + jsonResult = true; + } + if (kind.HasFlag(REvaluationKind.BaseEnv)) { + nameBuilder.Append('B'); + } + if (kind.HasFlag(REvaluationKind.EmptyEnv)) { + nameBuilder.Append('E'); + } + if (kind.HasFlag(REvaluationKind.NewEnv)) { + nameBuilder.Append("N"); + } + var name = nameBuilder.ToString(); + + _canEval = false; + try { + expression = expression.Replace("\r\n", "\n"); + var id = await SendAsync(name, ct, expression); + + var response = await RunLoop(ct, reentrant); + if (response == null) { + throw new OperationCanceledException("Evaluation canceled because host process has been terminated."); + } + + if (response.RequestId != id || response.Name != name) { + throw ProtocolError($"Mismatched host response ['{response.Id}',':','{response.Name}',...] to evaluation request ['{id}','{name}','{expression}']"); + } + + response.ExpectArguments(1, 3); + var firstArg = response[0] as JValue; + if (firstArg != null && firstArg.Value == null) { + throw new OperationCanceledException(Invariant($"Evaluation canceled: {expression}")); + } + + response.ExpectArguments(3); + var parseStatus = response.GetEnum(0, "parseStatus", parseStatusNames); + var error = response.GetString(1, "error", allowNull: true); + + if (jsonResult) { + return new REvaluationResult(response[2], error, parseStatus); + } else { + return new REvaluationResult(response.GetString(2, "value", allowNull: true), error, parseStatus); + } + } finally { + _canEval = true; + } + } + + /// + /// Cancels any ongoing evaluations or interaction processing. + /// + public async Task CancelAllAsync() { + if (_runTask == null) { + throw new InvalidOperationException("Not connected to host."); + } + + await TaskUtilities.SwitchToBackgroundThread(); + + var tcs = new TaskCompletionSource(); + if (Interlocked.CompareExchange(ref _cancelAllTcs, tcs, null) != null) { + // Cancellation is already in progress - do nothing. + return; + } + + try { + // Cancel any pending callbacks + _cancelAllCts.Cancel(); + + try { + await SendAsync("/", _cts.Token, null); + } catch (OperationCanceledException) { + return; + } catch (MessageTransportException) { + return; + } + + await tcs.Task; + } finally { + Volatile.Write(ref _cancelAllTcs, null); + } + } + + public async Task DisconnectAsync() { + if (_runTask == null) { + throw new InvalidOperationException("Not connected to host."); + } + + await TaskUtilities.SwitchToBackgroundThread(); + + // We may get MessageTransportException from any concurrent SendAsync or ReceiveAsync when the host + // drops connection after we request it to do so. To ensure that those don't bubble up to the + // client, cancel this token to indicate that we're shutting down the host - SendAsync and + // ReceiveAsync will take care of wrapping any WSE into OperationCanceledException. + _cts.Cancel(); + + try { + // Don't use _cts, since it's already cancelled. We want to try to send this message in + // any case, and we'll catch MessageTransportException if no-one is on the other end anymore. + await SendAsync(JValue.CreateNull(), new CancellationToken()); + } catch (OperationCanceledException) { + } catch (MessageTransportException) { + } + + try { + await _runTask; + } catch (OperationCanceledException) { + // Expected during disconnect. + } catch (MessageTransportException) { + // Possible and valid during disconnect. + } + } + + private async Task RunLoop(CancellationToken ct, bool allowEval) { + TaskUtilities.AssertIsOnBackgroundThread(); + + try { + _log.EnterRLoop(_rLoopDepth++); + while (!ct.IsCancellationRequested) { + var message = await ReceiveMessageAsync(ct); + if (message == null) { + return null; + } else if (message.RequestId != null) { + return message; + } + + try { + switch (message.Name) { + case "\\": + CancelAll(); + break; + + case "?": + await ShowDialog(message, allowEval, MessageButtons.YesNoCancel, CancellationTokenSource.CreateLinkedTokenSource(ct, _cancelAllCts.Token).Token); + break; + + case "??": + await ShowDialog(message, allowEval, MessageButtons.YesNo, CancellationTokenSource.CreateLinkedTokenSource(ct, _cancelAllCts.Token).Token); + break; + + case "???": + await ShowDialog(message, allowEval, MessageButtons.OKCancel, CancellationTokenSource.CreateLinkedTokenSource(ct, _cancelAllCts.Token).Token); + break; + + case ">": + await ReadConsole(message, allowEval, CancellationTokenSource.CreateLinkedTokenSource(ct, _cancelAllCts.Token).Token); + break; + + case "!": + case "!!": + message.ExpectArguments(1); + await _callbacks.WriteConsoleEx( + message.GetString(0, "buf", allowNull: true), + message.Name.Length == 1 ? OutputType.Output : OutputType.Error, + ct); + break; + + case "![]": + message.ExpectArguments(1); + await _callbacks.ShowMessage(message.GetString(0, "s", allowNull: true), ct); + break; + + case "~+": + await _callbacks.Busy(true, ct); + break; + case "~-": + await _callbacks.Busy(false, ct); + break; + + case "~/": + _callbacks.DirectoryChanged(); + break; + + case "Plot": + await _callbacks.Plot(message.GetString(0, "xaml_file_path"), ct); + break; + + case "Browser": + await _callbacks.Browser(message.GetString(0, "help_url")); + break; + + default: + throw ProtocolError($"Unrecognized host message name:", message); + } + } catch (OperationCanceledException) when (!ct.IsCancellationRequested) { + // Cancelled via _cancelAllCts - just move onto the next message. + } + } + } finally { + _log.ExitRLoop(--_rLoopDepth); + } + + return null; + } + + private async Task RunWorker(CancellationToken ct) { + TaskUtilities.AssertIsOnBackgroundThread(); + + try { + var message = await ReceiveMessageAsync(ct); + if (message.Name != "Microsoft.R.Host" || message.RequestId != null) { + throw ProtocolError($"Microsoft.R.Host handshake expected:", message); + } + + var protocolVersion = message.GetInt32(0, "protocol_version"); + if (protocolVersion != 1) { + throw ProtocolError($"Unsupported RHost protocol version:", message); + } + + var rVersion = message.GetString(1, "R_version"); + await _callbacks.Connected(rVersion); + + message = await RunLoop(ct, allowEval: true); + if (message != null) { + throw ProtocolError($"Unexpected host response message:", message); + } + } finally { + await _callbacks.Disconnected(); + } + } + + public async Task Run(IMessageTransport transport, CancellationToken ct) { + TaskUtilities.AssertIsOnBackgroundThread(); + + if (_runTask != null) { + throw new InvalidOperationException("This host is already running."); + } + + if (transport != null) { + lock (_transportLock) { + _transport = transport; + } + } else if (_transport == null) { + throw new ArgumentNullException("transport"); + } + + try { + await (_runTask = RunWorker(ct)); + } catch (OperationCanceledException) when (ct.IsCancellationRequested) { + // Expected cancellation, do not propagate, just exit process + } catch (MessageTransportException ex) when (ct.IsCancellationRequested) { + // Network errors during cancellation are expected, but should not be exposed to clients. + throw new OperationCanceledException(new OperationCanceledException().Message, ex); + } catch (Exception ex) { + Trace.Fail("Exception in RHost run loop:\n" + ex); + throw; + } + } + + private WebSocketMessageTransport CreateWebSocketMessageTransport() { + lock (_transportLock) { + if (_transport != null) { + throw new MessageTransportException("More than one incoming connection."); + } + + var transport = new WebSocketMessageTransport(); + _transportTcs.SetResult(_transport = transport); + return transport; + } + } + + public async Task CreateAndRun(string rHome, string rCommandLineArguments, int timeout = 3000, CancellationToken ct = default(CancellationToken)) { + await TaskUtilities.SwitchToBackgroundThread(); + + string rhostExe = Path.Combine(Path.GetDirectoryName(typeof(RHost).Assembly.GetAssemblyPath()), RHostExe); + string rBinPath = Path.Combine(rHome, RBinPathX64); + + if (!File.Exists(rhostExe)) { + throw new RHostBinaryMissingException(); + } + + // Grab an available port from the ephemeral port range (per RFC 6335 8.1.2) for the server socket. + + WebSocketServer server = null; + var rnd = new Random(); + const int ephemeralRangeStart = 49152; + var ports = + from port in Enumerable.Range(ephemeralRangeStart, 0x10000 - ephemeralRangeStart) + let pos = rnd.NextDouble() + orderby pos + select port; + + foreach (var port in ports) { + ct.ThrowIfCancellationRequested(); + + server = new WebSocketServer(port) { ReuseAddress = false }; + server.AddWebSocketService("/", CreateWebSocketMessageTransport); + + try { + server.Start(); + break; + } catch (SocketException ex) { + if (ex.SocketErrorCode == SocketError.AddressAlreadyInUse) { + server = null; + } else { + throw new MessageTransportException(ex); + } + } catch (WebSocketException ex) { + throw new MessageTransportException(ex); + } + } + + if (server == null) { + throw new MessageTransportException(new SocketException((int)SocketError.AddressAlreadyInUse)); + } + + var psi = new ProcessStartInfo { + FileName = rhostExe, + UseShellExecute = false + }; + + psi.EnvironmentVariables["R_HOME"] = rHome; + psi.EnvironmentVariables["PATH"] = Environment.GetEnvironmentVariable("PATH") + ";" + rBinPath; + + if (_name != null) { + psi.Arguments += " --rhost-name " + _name; + } + + psi.Arguments += Invariant($" --rhost-connect ws://127.0.0.1:{server.Port}"); + + if (!showConsole) { + psi.CreateNoWindow = true; + } + + if (!string.IsNullOrWhiteSpace(rCommandLineArguments)) { + psi.Arguments += Invariant($" {rCommandLineArguments}"); + } + + using (this) + using (_process = Process.Start(psi)) { + _log.RHostProcessStarted(psi); + _process.EnableRaisingEvents = true; + _process.Exited += delegate { Dispose(); }; + + try { + ct = CancellationTokenSource.CreateLinkedTokenSource(ct, _cts.Token).Token; + + // Timeout increased to allow more time in test and code coverage runs. + await Task.WhenAny(_transportTcs.Task, Task.Delay(timeout)).Unwrap(); + if (!_transportTcs.Task.IsCompleted) { + _log.FailedToConnectToRHost(); + throw new RHostTimeoutException("Timed out waiting for R host process to connect"); + } + + await Run(null, ct); + } catch (Exception) { + // TODO: delete when we figure out why host occasionally times out in code coverage runs. + //await _log.WriteFormatAsync(MessageCategory.Error, "Exception running R Host: {0}", ex.Message); + throw; + } finally { + if (!_process.HasExited) { + try { + _process.WaitForExit(500); + if (!_process.HasExited) { + _process.Kill(); + _process.WaitForExit(); + } + } catch (InvalidOperationException) { + } + } + _log.RHostProcessExited(); + } + } + } + } +} diff --git a/src/Host/Client/Impl/Program.cs b/src/Host/Client/Impl/Program.cs index 24d0d8ce7..a2ae8b1ec 100644 --- a/src/Host/Client/Impl/Program.cs +++ b/src/Host/Client/Impl/Program.cs @@ -11,7 +11,7 @@ class Program : IRCallbacks { static void Main(string[] args) { Console.CancelKeyPress += Console_CancelKeyPress; - var host = new RHost(null, new Program()); + var host = new RHost("Program", new Program()); host.CreateAndRun(args[0], string.Empty).GetAwaiter().GetResult(); _evaluator = host; } diff --git a/src/Host/Client/Impl/Session/RSession.cs b/src/Host/Client/Impl/Session/RSession.cs index 034136e4d..8cb530bfe 100644 --- a/src/Host/Client/Impl/Session/RSession.cs +++ b/src/Host/Client/Impl/Session/RSession.cs @@ -66,23 +66,23 @@ public void Dispose() { _onDispose(); } - public Task BeginInteractionAsync(bool isVisible = true) { + public Task BeginInteractionAsync(bool isVisible = true, CancellationToken cancellationToken = default(CancellationToken)) { if (!_isHostRunning) { return CanceledInteractionTask; } - RSessionRequestSource requestSource = new RSessionRequestSource(isVisible, _contexts); + RSessionRequestSource requestSource = new RSessionRequestSource(isVisible, _contexts, cancellationToken); _pendingRequestSources.Post(requestSource); return _isHostRunning ? requestSource.CreateRequestTask : CanceledInteractionTask; } - public Task BeginEvaluationAsync(bool isMutating = true) { + public Task BeginEvaluationAsync(bool isMutating = true, CancellationToken cancellationToken = default(CancellationToken)) { if (!_isHostRunning) { return CanceledEvaluationTask; } - var source = new RSessionEvaluationSource(isMutating); + var source = new RSessionEvaluationSource(isMutating, cancellationToken); _pendingEvaluationSources.Post(source); return _isHostRunning ? source.Task : CanceledEvaluationTask; @@ -92,32 +92,43 @@ public async Task CancelAllAsync() { var cancelTask = _host.CancelAllAsync(); var currentRequest = Interlocked.Exchange(ref _currentRequestSource, null); - currentRequest?.TryCancel(); + currentRequest?.Cancel(); ClearPendingRequests(); await cancelTask; } + public async Task EnsureHostStartedAsync(RHostStartupInfo startupInfo, int timeout = 3000) { + var existingInitializationTcs = Interlocked.CompareExchange(ref _initializationTcs, new TaskCompletionSource(), null); + if (existingInitializationTcs == null) { + await StartHostAsyncBackground(startupInfo, timeout); + } else { + await existingInitializationTcs.Task; + } + } + public async Task StartHostAsync(RHostStartupInfo startupInfo, int timeout = 3000) { - if (_hostRunTask != null && !_hostRunTask.IsCompleted) { + if (Interlocked.CompareExchange(ref _initializationTcs, new TaskCompletionSource(), null) != null) { throw new InvalidOperationException("Another instance of RHost is running for this RSession. Stop it before starting new one."); } + await StartHostAsyncBackground(startupInfo, timeout); + } + + private async Task StartHostAsyncBackground(RHostStartupInfo startupInfo, int timeout) { await TaskUtilities.SwitchToBackgroundThread(); - _host = new RHost(startupInfo.Name, this); - _initializationTcs = new TaskCompletionSource(); + _host = new RHost(startupInfo != null ? startupInfo.Name : "Empty", this); ClearPendingRequests(); - _hostRunTask = _host.CreateAndRun(RInstallation.GetRInstallPath(startupInfo.RBasePath), startupInfo.RCommandLineArguments, timeout); + _hostRunTask = CreateAndRunHost(startupInfo, timeout); ScheduleAfterHostStarted(startupInfo); - var initializationTask = _initializationTcs.Task; - await Task.WhenAny(initializationTask, _hostRunTask).Unwrap(); + await _initializationTcs.Task; } public async Task StopHostAsync() { - if (_hostRunTask == null || _hostRunTask.IsCompleted) { + if (_initializationTcs == null) { return; } @@ -160,8 +171,20 @@ public async Task StopHostAsync() { await _hostRunTask; } + private async Task CreateAndRunHost(RHostStartupInfo startupInfo, int timeout) { + try { + await _host.CreateAndRun(RInstallation.GetRInstallPath(startupInfo.RBasePath), startupInfo.RCommandLineArguments, timeout); + } catch (OperationCanceledException oce) { + _initializationTcs.TrySetCanceled(oce.CancellationToken); + } catch (Exception ex) { + _initializationTcs.TrySetException(ex); + } finally { + Interlocked.Exchange(ref _initializationTcs, null); + } + } + private void ScheduleAfterHostStarted(RHostStartupInfo startupInfo) { - var afterHostStartedEvaluationSource = new RSessionEvaluationSource(true); + var afterHostStartedEvaluationSource = new RSessionEvaluationSource(true, CancellationToken.None); _pendingEvaluationSources.Post(afterHostStartedEvaluationSource); AfterHostStarted(afterHostStartedEvaluationSource, startupInfo).DoNotWait(); } @@ -173,7 +196,6 @@ private async Task AfterHostStarted(RSessionEvaluationSource evaluationSource, R await LoadRtvsPackage(evaluation); await evaluation.SetDefaultWorkingDirectory(); - await evaluation.SetRdHelpExtraction(); if (_hostClientApp != null) { await evaluation.SetVsGraphicsDevice(); @@ -216,7 +238,7 @@ Task IRCallbacks.Disconnected() { Disconnected?.Invoke(this, EventArgs.Empty); var currentRequest = Interlocked.Exchange(ref _currentRequestSource, null); - currentRequest?.Complete(); + currentRequest?.CompleteResponse(); ClearPendingRequests(); @@ -226,7 +248,7 @@ Task IRCallbacks.Disconnected() { private void ClearPendingRequests() { RSessionRequestSource requestSource; while (_pendingRequestSources.TryReceive(out requestSource)) { - requestSource.TryCancel(); + requestSource.Cancel(); } RSessionEvaluationSource evalSource; @@ -262,7 +284,7 @@ async Task IRCallbacks.ReadConsole(IReadOnlyList contexts, st Mutated?.Invoke(this, EventArgs.Empty); } - currentRequest?.Complete(); + currentRequest?.CompleteResponse(); string consoleInput = null; @@ -361,7 +383,7 @@ Task IRCallbacks.WriteConsoleEx(string buf, OutputType otype, CancellationToken if (otype == OutputType.Error) { var currentRequest = Interlocked.Exchange(ref _currentRequestSource, null); - currentRequest?.Fail(buf); + currentRequest?.FailResponse(buf); } return Task.CompletedTask; diff --git a/src/Host/Client/Impl/Session/RSessionEvaluation.cs b/src/Host/Client/Impl/Session/RSessionEvaluation.cs index b2ac72e53..997c9d1ae 100644 --- a/src/Host/Client/Impl/Session/RSessionEvaluation.cs +++ b/src/Host/Client/Impl/Session/RSessionEvaluation.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Microsoft.R.Host.Client; namespace Microsoft.R.Host.Client.Session { internal sealed class RSessionEvaluation : IRSessionEvaluation { diff --git a/src/Host/Client/Impl/Session/RSessionEvaluationCommands.cs b/src/Host/Client/Impl/Session/RSessionEvaluationCommands.cs index 147907d12..1f5c91dc7 100644 --- a/src/Host/Client/Impl/Session/RSessionEvaluationCommands.cs +++ b/src/Host/Client/Impl/Session/RSessionEvaluationCommands.cs @@ -24,70 +24,42 @@ public static Task SaveWorkspace(this IRSessionEvaluation eva } public static Task SetVsGraphicsDevice(this IRSessionEvaluation evaluation) { - var script = @" -.rtvs.vsgdresize <- function(width, height) { - invisible(.External('Microsoft.R.Host::External.ide_graphicsdevice_resize', width, height)) -} -.rtvs.vsgd <- function() { - invisible(.External('Microsoft.R.Host::External.ide_graphicsdevice_new')) -} -.rtvs.vsgdexportimage <- function(filename, device) { - dev.copy(device=device,filename=filename) - dev.off() -} -.rtvs.vsgdexportpdf <- function(filename) { - dev.copy(device=pdf,file=filename) - dev.off() -} -.rtvs.vsgdnextplot <- function() { - invisible(.External('Microsoft.R.Host::External.ide_graphicsdevice_next_plot')) -} -.rtvs.vsgdpreviousplot <- function() { - invisible(.External('Microsoft.R.Host::External.ide_graphicsdevice_previous_plot')) -} -.rtvs.vsgdhistoryinfo <- function() { - .External('Microsoft.R.Host::External.ide_graphicsdevice_history_info') -} -xaml <- function(filename, width, height) { - invisible(.External('Microsoft.R.Host::External.xaml_graphicsdevice_new', filename, width, height)) -} -options(device='.rtvs.vsgd') -"; - + var script = "options(device=rtvs:::graphics.ide.new)\n"; return evaluation.EvaluateAsync(script); } public static Task ResizePlot(this IRSessionInteraction evaluation, int width, int height) { - var script = string.Format(".rtvs.vsgdresize({0}, {1})\n", width, height); + var script = string.Format("rtvs:::graphics.ide.resize({0}, {1})\n", width, height); return evaluation.RespondAsync(script); } public static Task NextPlot(this IRSessionInteraction evaluation) { - var script = ".rtvs.vsgdnextplot()\n"; + var script = "rtvs:::graphics.ide.nextplot()\n"; return evaluation.RespondAsync(script); } public static Task PreviousPlot(this IRSessionInteraction evaluation) { - var script = ".rtvs.vsgdpreviousplot()\n"; + var script = "rtvs:::graphics.ide.previousplot()\n"; return evaluation.RespondAsync(script); } public static Task PlotHistoryInfo(this IRSessionEvaluation evaluation) { - var script = @"rtvs:::toJSON(.rtvs.vsgdhistoryinfo())"; + var script = @"rtvs:::toJSON(rtvs:::graphics.ide.historyinfo())"; return evaluation.EvaluateAsync(script, REvaluationKind.Json); } - public static Task CopyToDevice(this IRSessionEvaluation evaluation, string deviceName, string outputFilePath) { - string script; - switch (deviceName) { - case "pdf": - script = string.Format(".rtvs.vsgdexportpdf('{0}')", outputFilePath.Replace("\\", "/")); - break; + public static Task ExportToBitmap(this IRSessionEvaluation evaluation, string deviceName, string outputFilePath, int widthInPixels, int heightInPixels) { + string script = string.Format("rtvs:::graphics.ide.exportimage(\"{0}\", {1}, {2}, {3})", outputFilePath.Replace("\\", "/"), deviceName, widthInPixels, heightInPixels); + return evaluation.EvaluateAsync(script); + } + + public static Task ExportToMetafile(this IRSessionEvaluation evaluation, string outputFilePath, double widthInInches, double heightInInches) { + string script = string.Format("rtvs:::graphics.ide.exportimage(\"{0}\", win.metafile, {1}, {2})", outputFilePath.Replace("\\", "/"), widthInInches, heightInInches); + return evaluation.EvaluateAsync(script); + } - default: - script = string.Format(".rtvs.vsgdexportimage('{0}', {1})", outputFilePath.Replace("\\", "/"), deviceName); - break; - } + public static Task ExportToPdf(this IRSessionEvaluation evaluation, string outputFilePath, double widthInInches, double heightInInches, string paper) { + string script = string.Format("rtvs:::graphics.ide.exportpdf(\"{0}\", {1}, {2}, '{3}')", outputFilePath.Replace("\\", "/"), widthInInches, heightInInches, paper); return evaluation.EvaluateAsync(script); } @@ -103,28 +75,13 @@ public static Task SetVsCranSelection(this IRSessionEvaluatio public static Task SetVsHelpRedirection(this IRSessionEvaluation evaluation) { var script = -@"options(browser = function(url) { +@"options(help_type = 'html') + options(browser = function(url) { .Call('Microsoft.R.Host::Call.send_message', 'Browser', rtvs:::toJSON(url)) })"; return evaluation.EvaluateAsync(script); } - public static Task SetRdHelpExtraction(this IRSessionEvaluation evaluation) { - var script = -@" .rtvs.signature.help2 <- function(f, p) { - x <- help(paste(f), paste(p)) - y <- utils:::.getHelpFile(x) - paste0(y, collapse = '') - } - - .rtvs.signature.help1 <- function(f) { - x <- help(paste(f)) - y <- utils:::.getHelpFile(x) - paste0(y, collapse = '') - }"; - return evaluation.EvaluateAsync(script); - } - public static Task SetChangeDirectoryRedirection(this IRSessionEvaluation evaluation) { var script = @"utils::assignInNamespace('setwd', function(dir) { diff --git a/src/Host/Client/Impl/Session/RSessionEvaluationSource.cs b/src/Host/Client/Impl/Session/RSessionEvaluationSource.cs index 239c11d84..e18ecfe33 100644 --- a/src/Host/Client/Impl/Session/RSessionEvaluationSource.cs +++ b/src/Host/Client/Impl/Session/RSessionEvaluationSource.cs @@ -1,7 +1,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; -using Microsoft.R.Host.Client; +using Microsoft.Common.Core; namespace Microsoft.R.Host.Client.Session { internal sealed class RSessionEvaluationSource { @@ -9,8 +9,9 @@ internal sealed class RSessionEvaluationSource { public bool IsMutating { get; } - public RSessionEvaluationSource(bool isMutating) { + public RSessionEvaluationSource(bool isMutating, CancellationToken ct) { _tcs = new TaskCompletionSource(); + ct.Register(() => _tcs.TrySetCanceled(ct), false); IsMutating = isMutating; } @@ -18,8 +19,7 @@ public RSessionEvaluationSource(bool isMutating) { public Task BeginEvaluationAsync(IReadOnlyList contexts, IRExpressionEvaluator evaluator, CancellationToken ct) { var evaluation = new RSessionEvaluation(contexts, evaluator, ct); - _tcs.SetResult(evaluation); - return evaluation.Task; + return _tcs.TrySetResult(evaluation) ? evaluation.Task : TaskUtilities.CompletedTask; } public bool TryCancel() { diff --git a/src/Host/Client/Impl/Session/RSessionInteraction.cs b/src/Host/Client/Impl/Session/RSessionInteraction.cs index 359d9b0c9..553630971 100644 --- a/src/Host/Client/Impl/Session/RSessionInteraction.cs +++ b/src/Host/Client/Impl/Session/RSessionInteraction.cs @@ -24,7 +24,7 @@ public RSessionInteraction( } public Task RespondAsync(string messageText) { - _requestTcs.SetResult(messageText); + _requestTcs.TrySetResult(messageText); return _responseTcs.Task; } diff --git a/src/Host/Client/Impl/Session/RSessionRequestSource.cs b/src/Host/Client/Impl/Session/RSessionRequestSource.cs index 5ec07e747..e7c194fe3 100644 --- a/src/Host/Client/Impl/Session/RSessionRequestSource.cs +++ b/src/Host/Client/Impl/Session/RSessionRequestSource.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using System.Threading.Tasks; namespace Microsoft.R.Host.Client.Session { @@ -11,9 +12,10 @@ internal sealed class RSessionRequestSource { public bool IsVisible { get; } public IReadOnlyList Contexts { get; } - public RSessionRequestSource(bool isVisible, IReadOnlyList contexts) { + public RSessionRequestSource(bool isVisible, IReadOnlyList contexts, CancellationToken ct) { _createRequestTcs = new TaskCompletionSource(); _responseTcs = new TaskCompletionSource(); + ct.Register(() => _createRequestTcs.TrySetCanceled(ct)); IsVisible = isVisible; Contexts = contexts ?? new[] { RHost.TopLevelContext }; @@ -25,21 +27,23 @@ public void Request(string prompt, int maxLength, TaskCompletionSource r return; } + request.Dispose(); if (CreateRequestTask.IsCanceled) { throw new OperationCanceledException(); } } - public void Fail(string text) { + public void FailResponse(string text) { _responseTcs.SetException(new RException(text)); } - public void Complete() { + public void CompleteResponse() { _responseTcs.SetResult(null); } - public bool TryCancel() { - return _createRequestTcs.TrySetCanceled(); + public void Cancel() { + _createRequestTcs.TrySetCanceled(); + _responseTcs.TrySetCanceled(); } } } \ No newline at end of file diff --git a/src/Host/Client/Test/Files/ExportPreviousPlotToImageExpectedResult.bmp b/src/Host/Client/Test/Files/ExportPreviousPlotToImageExpectedResult.bmp new file mode 100644 index 000000000..f11f447b6 Binary files /dev/null and b/src/Host/Client/Test/Files/ExportPreviousPlotToImageExpectedResult.bmp differ diff --git a/src/Host/Client/Test/Files/ExportToPdfExpectedResult.pdf b/src/Host/Client/Test/Files/ExportToPdfExpectedResult.pdf new file mode 100644 index 000000000..d268146e9 Binary files /dev/null and b/src/Host/Client/Test/Files/ExportToPdfExpectedResult.pdf differ diff --git a/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-HistoryInfo-0.png b/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-HistoryInfo-0.png new file mode 100644 index 000000000..e56e26643 Binary files /dev/null and b/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-HistoryInfo-0.png differ diff --git a/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-HistoryInfo-1.png b/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-HistoryInfo-1.png new file mode 100644 index 000000000..8f0b41562 Binary files /dev/null and b/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-HistoryInfo-1.png differ diff --git a/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-HistoryInfo-2.png b/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-HistoryInfo-2.png new file mode 100644 index 000000000..e56e26643 Binary files /dev/null and b/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-HistoryInfo-2.png differ diff --git a/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-HistoryResizeOldPlot-0.png b/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-HistoryResizeOldPlot-0.png new file mode 100644 index 000000000..e56e26643 Binary files /dev/null and b/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-HistoryResizeOldPlot-0.png differ diff --git a/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-HistoryResizeOldPlot-1.png b/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-HistoryResizeOldPlot-1.png new file mode 100644 index 000000000..8f0b41562 Binary files /dev/null and b/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-HistoryResizeOldPlot-1.png differ diff --git a/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-HistoryResizeOldPlot-2.png b/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-HistoryResizeOldPlot-2.png new file mode 100644 index 000000000..8c6d2aa62 Binary files /dev/null and b/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-HistoryResizeOldPlot-2.png differ diff --git a/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-HistoryResizeOldPlot-3.png b/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-HistoryResizeOldPlot-3.png new file mode 100644 index 000000000..37158f136 Binary files /dev/null and b/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-HistoryResizeOldPlot-3.png differ diff --git a/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-ResizeInteractiveMultiPlots-0.png b/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-ResizeInteractiveMultiPlots-0.png new file mode 100644 index 000000000..2790c2d00 Binary files /dev/null and b/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-ResizeInteractiveMultiPlots-0.png differ diff --git a/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-ResizeInteractiveMultiPlots-1.png b/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-ResizeInteractiveMultiPlots-1.png new file mode 100644 index 000000000..8e5e5f00d Binary files /dev/null and b/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-ResizeInteractiveMultiPlots-1.png differ diff --git a/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-ResizeInteractiveMultiPlots-2.png b/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-ResizeInteractiveMultiPlots-2.png new file mode 100644 index 000000000..de9099d4d Binary files /dev/null and b/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-ResizeInteractiveMultiPlots-2.png differ diff --git a/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-ResizeInteractiveMultiPlots-3.png b/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-ResizeInteractiveMultiPlots-3.png new file mode 100644 index 000000000..df38fd570 Binary files /dev/null and b/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-ResizeInteractiveMultiPlots-3.png differ diff --git a/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-ResizeInteractiveMultiPlots-4.png b/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-ResizeInteractiveMultiPlots-4.png new file mode 100644 index 000000000..d62046acb Binary files /dev/null and b/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-ResizeInteractiveMultiPlots-4.png differ diff --git a/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-ResizeNonInteractiveMultiPlots-0.png b/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-ResizeNonInteractiveMultiPlots-0.png new file mode 100644 index 000000000..df38fd570 Binary files /dev/null and b/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-ResizeNonInteractiveMultiPlots-0.png differ diff --git a/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-ResizeNonInteractiveMultiPlots-1.png b/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-ResizeNonInteractiveMultiPlots-1.png new file mode 100644 index 000000000..d62046acb Binary files /dev/null and b/src/Host/Client/Test/Files/Microsoft.R.Host.Client.Test.IdeGraphicsDeviceTest-ResizeNonInteractiveMultiPlots-1.png differ diff --git a/src/Host/Client/Test/GraphicsDeviceTestFilesFixture.cs b/src/Host/Client/Test/GraphicsDeviceTestFilesFixture.cs index 6bfa636d0..43cb334a0 100644 --- a/src/Host/Client/Test/GraphicsDeviceTestFilesFixture.cs +++ b/src/Host/Client/Test/GraphicsDeviceTestFilesFixture.cs @@ -11,13 +11,26 @@ public class GraphicsDeviceTestFilesFixture : DeployFilesFixture { public string ExportToPngResultPath { get; } public string ExportToJpegResultPath { get; } public string ExportToTiffResultPath { get; } + public string ExportPreviousPlotToImageResultPath { get; } + public string ExpectedExportPreviousPlotToImagePath { get; } + public string ExpectedExportToPdfPath { get; } + public string ActualFolderPath { get; } public GraphicsDeviceTestFilesFixture() : base(@"Host\Client\Test\Files", "Files") { - ExportToPdfResultPath = Path.Combine(DestinationPath, "ExportToPdfResult.pdf"); - ExportToBmpResultPath = Path.Combine(DestinationPath, "ExportToBmpResult.bmp"); - ExportToPngResultPath = Path.Combine(DestinationPath, "ExportToPngResult.png"); - ExportToJpegResultPath = Path.Combine(DestinationPath, "ExportToJpegResult.jpg"); - ExportToTiffResultPath = Path.Combine(DestinationPath, "ExportToTiffResult.tif"); + ActualFolderPath = Path.Combine(DestinationPath, "Actual"); + Directory.CreateDirectory(ActualFolderPath); + + // Path to files that are generated when tests are executed + ExportToPdfResultPath = Path.Combine(ActualFolderPath, "ExportToPdfResult.pdf"); + ExportToBmpResultPath = Path.Combine(ActualFolderPath, "ExportToBmpResult.bmp"); + ExportToPngResultPath = Path.Combine(ActualFolderPath, "ExportToPngResult.png"); + ExportToJpegResultPath = Path.Combine(ActualFolderPath, "ExportToJpegResult.jpg"); + ExportToTiffResultPath = Path.Combine(ActualFolderPath, "ExportToTiffResult.tif"); + ExportPreviousPlotToImageResultPath = Path.Combine(ActualFolderPath, "ExportPreviousPlotToImageResultPath.bmp"); + + // Path to files that are compared against and are included as part of test sources + ExpectedExportPreviousPlotToImagePath = Path.Combine(DestinationPath, "ExportPreviousPlotToImageExpectedResult.bmp"); + ExpectedExportToPdfPath = Path.Combine(DestinationPath, "ExportToPdfExpectedResult.pdf"); } } } \ No newline at end of file diff --git a/src/Host/Client/Test/IdeGraphicsDeviceTest.cs b/src/Host/Client/Test/IdeGraphicsDeviceTest.cs index 66d177dca..b6cbbf02f 100644 --- a/src/Host/Client/Test/IdeGraphicsDeviceTest.cs +++ b/src/Host/Client/Test/IdeGraphicsDeviceTest.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Drawing; using System.IO; @@ -12,7 +11,6 @@ using FluentAssertions; using Microsoft.Common.Core.Shell; using Microsoft.R.Actions.Utility; -using Microsoft.R.Support.Test.Utility; using Microsoft.UnitTests.Core.XUnit; using Xunit; @@ -32,12 +30,12 @@ public class IdeGraphicsDeviceTest { .rtvs.vsgd <- function() { invisible(.External('Microsoft.R.Host::External.ide_graphicsdevice_new')) } -.rtvs.vsgdexportimage <- function(filename, device) { - dev.copy(device=device,filename=filename) +.rtvs.vsgdexportimage <- function(filename, device, width, height) { + dev.copy(device=device,filename=filename,width=width,height=height) dev.off() } -.rtvs.vsgdexportpdf <- function(filename) { - dev.copy(device=pdf,file=filename) +.rtvs.vsgdexportpdf <- function(filename, width, height, paper) { + dev.copy(device=pdf,file=filename,width=width,height=height,paper=paper) dev.off() } .rtvs.vsgdnextplot <- function() { @@ -145,7 +143,7 @@ public void MultiplePlotsNoSleep() { plot(0:10) plot(5:15) "; - GraphicsTest(code).Should().ContainSingle(); + GraphicsTest(code).Should().HaveCount(2); } [Test] @@ -214,15 +212,17 @@ public void ExportToImage() { var code = string.Format(@" plot(0:10) Sys.sleep(1) -.rtvs.vsgdexportimage('{0}', bmp) -.rtvs.vsgdexportimage('{1}', png) -.rtvs.vsgdexportimage('{2}', jpeg) -.rtvs.vsgdexportimage('{3}', tiff) +.rtvs.vsgdexportimage('{0}', bmp, {4}, {5}) +.rtvs.vsgdexportimage('{1}', png, {4}, {5}) +.rtvs.vsgdexportimage('{2}', jpeg, {4}, {5}) +.rtvs.vsgdexportimage('{3}', tiff, {4}, {5}) ", exportedBmpFilePath.Replace("\\", "/"), exportedPngFilePath.Replace("\\", "/"), exportedJpegFilePath.Replace("\\", "/"), - exportedTiffFilePath.Replace("\\", "/")); + exportedTiffFilePath.Replace("\\", "/"), + DefaultExportWidth, + DefaultExportHeight); var actualPlotFilePaths = GraphicsTest(code).ToArray(); actualPlotFilePaths.Should().ContainSingle(); @@ -248,6 +248,28 @@ public void ExportToImage() { exportedTiff.Height.Should().Be(DefaultExportHeight); } + [Test] + [Category.Plots] + public void ExportPreviousPlotToImage() { + var exportedBmpFilePath = _files.ExportPreviousPlotToImageResultPath; + + var code = string.Format(@" +plot(0:10) +plot(10:20) +Sys.sleep(1) +.rtvs.vsgdpreviousplot() +.rtvs.vsgdexportimage('{0}', bmp, {1}, {2}) +", + exportedBmpFilePath.Replace("\\", "/"), + DefaultWidth, + DefaultHeight); + + var actualPlotFilePaths = GraphicsTest(code).ToArray(); + actualPlotFilePaths.Should().HaveCount(3); + + File.ReadAllBytes(exportedBmpFilePath).Should().Equal(File.ReadAllBytes(_files.ExpectedExportPreviousPlotToImagePath)); + } + [Test] [Category.Plots] public void ExportToPdf() { @@ -256,8 +278,8 @@ public void ExportToPdf() { var code = string.Format(@" plot(0:10) Sys.sleep(1) -.rtvs.vsgdexportpdf('{0}') -", exportedFilePath.Replace("\\", "/")); +.rtvs.vsgdexportpdf('{0}', {1}, {2}, '{3}') +", exportedFilePath.Replace("\\", "/"), 7, 7, "special"); var actualPlotFilePaths = GraphicsTest(code).ToArray(); actualPlotFilePaths.Should().ContainSingle(); @@ -266,25 +288,58 @@ public void ExportToPdf() { bmp.Width.Should().Be(DefaultWidth); bmp.Height.Should().Be(DefaultHeight); - File.Exists(exportedFilePath).Should().BeTrue(); + PdfComparer.ComparePdfFiles(exportedFilePath, _files.ExpectedExportToPdfPath); } [Test] [Category.Plots] - public void History() { + public void ResizeInteractiveMultiPlots() { + // Resize a graph with multiple plots, where the + // code is executed one line at a time interactively + // (simulated with sleep) + // Make sure that all parts of the graph are present + // We used to have a bug where the resized image only had + // the top left plot, and the others were missing var code = @" -plot(0:10) +par(mfrow = c(2, 2)) + +plot(0:1) Sys.sleep(1) -plot(5:15) + +plot(1:2) Sys.sleep(1) -.rtvs.vsgdpreviousplot() + +plot(2:3) +Sys.sleep(1) + +plot(3:4) +Sys.sleep(1) + +.rtvs.vsgdresize(600, 600) "; - // TODO: Make this validate against a set of expected files to avoid any false positive - var actualPlotFilePaths = GraphicsTest(code).ToArray(); - actualPlotFilePaths.Should().HaveCount(3); + GraphicsTestAgainstExpectedFiles(code); + } - File.ReadAllBytes(actualPlotFilePaths[2]).Should().Equal(File.ReadAllBytes(actualPlotFilePaths[0])); - File.ReadAllBytes(actualPlotFilePaths[1]).Should().NotEqual(File.ReadAllBytes(actualPlotFilePaths[0])); + [Test] + [Category.Plots] + public void ResizeNonInteractiveMultiPlots() { + // Resize a graph with multiple plots, where the + // code is executed all at once + // Make sure that all parts of the graph are present + // We used to have a bug where the resized image only had + // the top left plot, and the others were missing + var code = @" +par(mfrow = c(2, 2)) + +plot(0:1) +plot(1:2) +plot(2:3) +plot(3:4) +Sys.sleep(1) + +.rtvs.vsgdresize(600, 600) +"; + GraphicsTestAgainstExpectedFiles(code); } [Test] @@ -298,8 +353,7 @@ public void HistoryInfo() { .rtvs.vsgdpreviousplot() .rtvs.vsgdhistoryinfo() "; - // TODO: Make this validate against a set of expected files to avoid any false positive - var actualPlotFilePaths = GraphicsTest(code).ToArray(); + var actualPlotFilePaths = GraphicsTestAgainstExpectedFiles(code); actualPlotFilePaths.Should().HaveCount(3); File.ReadAllBytes(actualPlotFilePaths[2]).Should().Equal(File.ReadAllBytes(actualPlotFilePaths[0])); @@ -313,8 +367,11 @@ public void HistoryInfo() { string output = _callbacks.Output; string[] lines = output.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries); - lines.Length.Should().BeGreaterThan(0); - lines[lines.Length-1].Should().Be($"[1] {expectedActive} {expectedCount}"); + lines.Length.Should().BeGreaterOrEqualTo(4); + lines[lines.Length-4].Should().Be("[[1]]"); + lines[lines.Length-3].Should().Be($"[1] {expectedActive}"); + lines[lines.Length-2].Should().Be("[[2]]"); + lines[lines.Length-1].Should().Be($"[1] {expectedCount}"); } [Test] @@ -330,8 +387,7 @@ public void HistoryResizeOldPlot() { .rtvs.vsgdpreviousplot() Sys.sleep(1) "; - // TODO: Make this validate against a set of expected files to avoid any false positive - var actualPlotFilePaths = GraphicsTest(code).ToArray(); + var actualPlotFilePaths = GraphicsTestAgainstExpectedFiles(code); actualPlotFilePaths.Should().HaveCount(4); var bmp1 = (Bitmap)Image.FromFile(actualPlotFilePaths[0]); @@ -348,27 +404,29 @@ public void HistoryResizeOldPlot() { bmp4.Height.Should().Be(600); } - private void GraphicsTestAgainstExpectedFiles(string code) { + private string[] GraphicsTestAgainstExpectedFiles(string code) { var actualPlotPaths = GraphicsTest(code).ToArray(); var expectedPlotPaths = GetTestExpectedFiles(); - actualPlotPaths.Should().BeEquivalentTo(expectedPlotPaths); + actualPlotPaths.Length.Should().Be(expectedPlotPaths.Length); foreach (string path in expectedPlotPaths) { var actualPlotPath = actualPlotPaths.First(p => Path.GetFileName(p) == Path.GetFileName(path)); File.ReadAllBytes(actualPlotPath).Should().Equal(File.ReadAllBytes(path)); } + + return actualPlotPaths; } private string[] GetTestExpectedFiles() { var folderPath = _files.DestinationPath; - var expectedFilesFilter = $"{_testMethod.DeclaringType?.AssemblyQualifiedName}-{_testMethod.Name}-*.png"; + var expectedFilesFilter = $"{_testMethod.DeclaringType?.FullName}-{_testMethod.Name}-*.png"; var expectedFiles = Directory.GetFiles(folderPath, expectedFilesFilter); return expectedFiles; } internal string SavePlotFile(string plotFilePath, int i) { - var newFileName = $"{_testMethod.DeclaringType?.AssemblyQualifiedName}-{_testMethod.Name}-{i}{Path.GetExtension(plotFilePath)}"; - var testOutputFilePath = Path.Combine(_files.DestinationPath, newFileName); + var newFileName = $"{_testMethod.DeclaringType?.FullName}-{_testMethod.Name}-{i}{Path.GetExtension(plotFilePath)}"; + var testOutputFilePath = Path.Combine(_files.ActualFolderPath, newFileName); File.Copy(plotFilePath, testOutputFilePath); return testOutputFilePath; } diff --git a/src/Host/Client/Test/Microsoft.R.Host.Client.Test.csproj b/src/Host/Client/Test/Microsoft.R.Host.Client.Test.csproj index 60484b839..7173d893e 100644 --- a/src/Host/Client/Test/Microsoft.R.Host.Client.Test.csproj +++ b/src/Host/Client/Test/Microsoft.R.Host.Client.Test.csproj @@ -69,6 +69,12 @@ + + + + + + @@ -111,6 +117,7 @@ + @@ -120,6 +127,21 @@ + + + + + + + + + + + + + + + diff --git a/src/Host/Client/Test/Mocks/RContextMock.cs b/src/Host/Client/Test/Mocks/RContextMock.cs new file mode 100644 index 000000000..911dd2189 --- /dev/null +++ b/src/Host/Client/Test/Mocks/RContextMock.cs @@ -0,0 +1,5 @@ +namespace Microsoft.R.Host.Client.Test.Mocks { + public sealed class RContextMock : IRContext { + public RContextType CallFlag { get; set; } = RContextType.TopLevel; + } +} diff --git a/src/Host/Client/Test/Mocks/RSessionEvaluationMock.cs b/src/Host/Client/Test/Mocks/RSessionEvaluationMock.cs new file mode 100644 index 000000000..d0875688d --- /dev/null +++ b/src/Host/Client/Test/Mocks/RSessionEvaluationMock.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.R.Host.Client.Test.Mocks { + public sealed class RSessionEvaluationMock : IRSessionEvaluation { + public IReadOnlyList Contexts { + get { + return new List() { new RContextMock(), new RContextMock() }; + } + } + + public void Dispose() { + } + + public Task EvaluateAsync(string expression, REvaluationKind kind = REvaluationKind.Normal) { + return Task.FromResult(new REvaluationResult()); + } + } +} diff --git a/src/Host/Client/Test/Mocks/RSessionInteractionMock.cs b/src/Host/Client/Test/Mocks/RSessionInteractionMock.cs new file mode 100644 index 000000000..e602f8850 --- /dev/null +++ b/src/Host/Client/Test/Mocks/RSessionInteractionMock.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Microsoft.R.Host.Client.Test.Mocks { + public sealed class RSessionInteractionMock : IRSessionInteraction { + public IReadOnlyList Contexts { + get { + return new List() { new RContextMock() }; + } + } + + public int MaxLength => 4096; + + public string Prompt => ">"; + + public void Dispose() { + } + + public Task RespondAsync(string messageText) { + return Task.CompletedTask; + } + } +} diff --git a/src/Host/Client/Test/Mocks/RSessionMock.cs b/src/Host/Client/Test/Mocks/RSessionMock.cs new file mode 100644 index 000000000..8bdd389a0 --- /dev/null +++ b/src/Host/Client/Test/Mocks/RSessionMock.cs @@ -0,0 +1,74 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace Microsoft.R.Host.Client.Test.Mocks { + public sealed class RSessionMock : IRSession { + private IRSessionEvaluation _eval; + private IRSessionInteraction _inter; + + public int Id { get; set; } + + public bool IsHostRunning { get; set; } + + public string Prompt { get; set; } = ">"; + + public Task BeginEvaluationAsync(bool isMutating = true, CancellationToken cancellationToken = default(CancellationToken)) { + _eval = new RSessionEvaluationMock(); + + BeforeRequest?.Invoke(this, new RRequestEventArgs(_eval.Contexts, Prompt, 4096, true)); + if (isMutating) { + Mutated?.Invoke(this, EventArgs.Empty); + } + return Task.FromResult(_eval); + } + + public Task BeginInteractionAsync(bool isVisible = true, CancellationToken cancellationToken = default (CancellationToken)) { + _inter = new RSessionInteractionMock(); + BeforeRequest?.Invoke(this, new RRequestEventArgs(_inter.Contexts, Prompt, 4096, true)); + return Task.FromResult(_inter); + } + + public Task CancelAllAsync() { + if (_eval != null) { + AfterRequest?.Invoke(this, new RRequestEventArgs(_eval.Contexts, Prompt, 4096, true)); + _eval = null; + } + else if (_inter != null) { + AfterRequest?.Invoke(this, new RRequestEventArgs(_inter.Contexts, Prompt, 4096, true)); + _inter = null; + } + return Task.CompletedTask; + } + + public void Dispose() { + StopHostAsync().Wait(5000); + Disposed?.Invoke(this, EventArgs.Empty); + } + + public void FlushLog() { + } + + public Task StartHostAsync(RHostStartupInfo startupInfo, int timeout = 3000) { + IsHostRunning = true; + Connected?.Invoke(this, EventArgs.Empty); + return Task.CompletedTask; + } + + public Task StopHostAsync() { + IsHostRunning = false; + Disconnected?.Invoke(this, EventArgs.Empty); + return Task.CompletedTask; + } + +#pragma warning disable 67 + public event EventHandler AfterRequest; + public event EventHandler BeforeRequest; + public event EventHandler Connected; + public event EventHandler DirectoryChanged; + public event EventHandler Disconnected; + public event EventHandler Disposed; + public event EventHandler Mutated; + public event EventHandler Output; + } +} diff --git a/src/Host/Client/Test/Mocks/RSessionProviderMock.cs b/src/Host/Client/Test/Mocks/RSessionProviderMock.cs new file mode 100644 index 000000000..45fc40dd5 --- /dev/null +++ b/src/Host/Client/Test/Mocks/RSessionProviderMock.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using Microsoft.R.Host.Client.Test.Mocks; + +namespace Microsoft.R.Host.Client.Mocks { + public sealed class RSessionProviderMock : IRSessionProvider { + private Dictionary _sessions = new Dictionary(); + + public void Dispose() { + } + + public IRSession GetOrCreate(Guid guid, IRHostClientApp hostClientApp) { + IRSession session; + if (!_sessions.TryGetValue(guid, out session)) { + session = new RSessionMock(); + _sessions[guid] = session; + } + return session; + } + + public IEnumerable GetSessions() { + return _sessions.Values; + } + } +} diff --git a/src/Host/Client/Test/PdfComparer.cs b/src/Host/Client/Test/PdfComparer.cs new file mode 100644 index 000000000..16a34fc3a --- /dev/null +++ b/src/Host/Client/Test/PdfComparer.cs @@ -0,0 +1,49 @@ +using System; +using System.IO; +using System.Linq; +using FluentAssertions; + +namespace Microsoft.R.Host.Client.Test { + static class PdfComparer { + /// + /// Compare 2 PDF files, ignoring the headers. + /// + /// PDF file to validate. + /// PDF file to compare against. + public static void ComparePdfFiles(string actualFilePath, string expectedFilePath) { + var actualPdfBytes = File.ReadAllBytes(actualFilePath); + var expectedPdfBytes = File.ReadAllBytes(expectedFilePath); + ClearPdfHeader(actualPdfBytes); + ClearPdfHeader(expectedPdfBytes); + + actualPdfBytes.Should().Equal(expectedPdfBytes); + } + + private static void ClearPdfHeader(byte[] data) { + // PDF files have headers that include CreationDate, ModDate, Producer + // which will not match for 2 identical exports of plots + // That metadata is stored in the first object, between '<<' and '>>' + int startMetadataIndex = IndexOfSequence(data, new byte[] { 0x3c, 0x3c }, 0); + startMetadataIndex.Should().BeGreaterOrEqualTo(0); + int endMetadataIndex = IndexOfSequence(data, new byte[] { 0x3e, 0x3e }, startMetadataIndex); + endMetadataIndex.Should().BeGreaterThan(startMetadataIndex); + + // Replace the whole metadata block with spaces + for (int i = startMetadataIndex + 2; i < endMetadataIndex; i++) { + data[i] = 0x20; + } + } + + private static int IndexOfSequence(byte[] data, byte[] sequence, int startIndex) { + int index = startIndex; + while (index < data.Length - sequence.Length) { + var dataSegment = new ArraySegment(data, index, sequence.Length); + if (dataSegment.SequenceEqual(sequence)) { + return index; + } + index++; + } + return -1; + } + } +} diff --git a/src/Host/Client/Test/Script/RHostClientTestApp.cs b/src/Host/Client/Test/Script/RHostClientTestApp.cs index d88c70f1f..525780679 100644 --- a/src/Host/Client/Test/Script/RHostClientTestApp.cs +++ b/src/Host/Client/Test/Script/RHostClientTestApp.cs @@ -4,24 +4,24 @@ using Microsoft.Common.Core.Shell; namespace Microsoft.R.Host.Client.Test.Script { - public sealed class RHostClientTestApp : IRHostClientApp { - public string CranUrlFromName(string name) { + public class RHostClientTestApp : IRHostClientApp { + public virtual string CranUrlFromName(string name) { return "https://cran.rstudio.com"; } - public Task Plot(string filePath, CancellationToken ct) { + public virtual Task Plot(string filePath, CancellationToken ct) { throw new NotImplementedException(); } - public Task ShowErrorMessage(string message) { + public virtual Task ShowErrorMessage(string message) { return Task.CompletedTask; } - public Task ShowHelp(string url) { + public virtual Task ShowHelp(string url) { return Task.CompletedTask; } - public Task ShowMessage(string message, MessageButtons buttons) { + public virtual Task ShowMessage(string message, MessageButtons buttons) { return Task.FromResult(MessageButtons.OK); } } diff --git a/src/Host/Client/Test/Script/RHostScript.cs b/src/Host/Client/Test/Script/RHostScript.cs index 03090080b..f70179f97 100644 --- a/src/Host/Client/Test/Script/RHostScript.cs +++ b/src/Host/Client/Test/Script/RHostScript.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; +using FluentAssertions; using Microsoft.R.Support.Settings; namespace Microsoft.R.Host.Client.Test.Script { @@ -10,15 +11,18 @@ public class RHostScript : IDisposable { public IRSessionProvider SessionProvider { get; private set; } public IRSession Session { get; private set; } - public RHostScript(IRSessionProvider sessionProvider) { + public RHostScript(IRSessionProvider sessionProvider, IRHostClientApp clientApp = null) { SessionProvider = sessionProvider; - Session = SessionProvider.GetOrCreate(GuidList.InteractiveWindowRSessionGuid, new RHostClientTestApp()); + + Session = SessionProvider.GetOrCreate(GuidList.InteractiveWindowRSessionGuid, clientApp ?? new RHostClientTestApp()); + Session.IsHostRunning.Should().BeFalse(); + Session.StartHostAsync(new RHostStartupInfo { Name = "RHostScript", RBasePath = RToolsSettings.Current.RBasePath, RCommandLineArguments = RToolsSettings.Current.RCommandLineArguments, CranMirrorName = RToolsSettings.Current.CranMirror - }, 10000).Wait(); + }, 50000).Wait(); } public void Dispose() { @@ -34,7 +38,7 @@ protected virtual void Dispose(bool disposing) { if (disposing) { if (Session != null) { - Session.StopHostAsync().Wait(); + Session.StopHostAsync().Wait(5000); Session.Dispose(); Session = null; } diff --git a/src/Host/Process b/src/Host/Process index 8a94b796f..fad32ff34 160000 --- a/src/Host/Process +++ b/src/Host/Process @@ -1 +1 @@ -Subproject commit 8a94b796f41653d79e7127b5e867d02114ba09e1 +Subproject commit fad32ff340e3ca45b00a9552df1555aa626d427c diff --git a/src/Languages/Core/Impl/GlobalSuppressions.cs b/src/Languages/Core/Impl/GlobalSuppressions.cs deleted file mode 100644 index b1c68cce2..000000000 Binary files a/src/Languages/Core/Impl/GlobalSuppressions.cs and /dev/null differ diff --git a/src/Languages/Core/Impl/Microsoft.Languages.Core.csproj b/src/Languages/Core/Impl/Microsoft.Languages.Core.csproj index 11440ab1a..0cf1d1071 100644 --- a/src/Languages/Core/Impl/Microsoft.Languages.Core.csproj +++ b/src/Languages/Core/Impl/Microsoft.Languages.Core.csproj @@ -1,107 +1,106 @@ - - - - - - Debug - AnyCPU - {25CD8690-6208-4740-B123-6DBCE6B9444A} - Library - Properties - Microsoft.Languages.Core - Microsoft.Languages.Core - v4.6 - 512 - SAK - SAK - SAK - SAK - - - - - - $(RootDirectory)\obj\ - $(RootDirectory)\bin\ - $(BaseIntermediateOutputPath)\$(Configuration)\$(AssemblyName)\ - $(BaseOutputPath)\$(Configuration)\ - - - - - - - - - Properties\GlobalAssemblyInfo.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Microsoft - StrongName - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - + + + + + + Debug + AnyCPU + {25CD8690-6208-4740-B123-6DBCE6B9444A} + Library + Properties + Microsoft.Languages.Core + Microsoft.Languages.Core + v4.6 + 512 + SAK + SAK + SAK + SAK + + + + + + $(RootDirectory)\obj\ + $(RootDirectory)\bin\ + $(BaseIntermediateOutputPath)\$(Configuration)\$(AssemblyName)\ + $(BaseOutputPath)\$(Configuration)\ + + + + + + + + + Properties\GlobalAssemblyInfo.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Microsoft + StrongName + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/src/Languages/Core/Test/GlobalSuppressions.cs b/src/Languages/Core/Test/GlobalSuppressions.cs deleted file mode 100644 index 22320b8b1..000000000 Binary files a/src/Languages/Core/Test/GlobalSuppressions.cs and /dev/null differ diff --git a/src/Languages/Core/Test/Microsoft.Languages.Core.Test.csproj b/src/Languages/Core/Test/Microsoft.Languages.Core.Test.csproj index a652e9e59..c95fd89eb 100644 --- a/src/Languages/Core/Test/Microsoft.Languages.Core.Test.csproj +++ b/src/Languages/Core/Test/Microsoft.Languages.Core.Test.csproj @@ -1,125 +1,124 @@ - - - - Debug - AnyCPU - {EE2504A4-4666-460B-8552-5B342718CB02} - Library - Properties - Microsoft.Languages.Core.Test - Microsoft.Languages.Core.Test - v4.6 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - SAK - SAK - SAK - SAK - False - UnitTest - - - - $(RootDirectory)\obj\ - $(RootDirectory)\bin\ - $(BaseIntermediateOutputPath)\$(Configuration)\$(AssemblyName)\ - $(BaseOutputPath)\$(Configuration)\ - - - - ..\..\..\..\NugetPackages\FluentAssertions.4.1.1\lib\net45\FluentAssertions.dll - True - - - ..\..\..\..\NugetPackages\FluentAssertions.4.1.1\lib\net45\FluentAssertions.Core.dll - True - - - - - - ..\..\..\..\NugetPackages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll - True - - - ..\..\..\..\NugetPackages\xunit.assert.2.2.0-beta1-build3239\lib\dotnet\xunit.assert.dll - True - - - ..\..\..\..\NugetPackages\xunit.extensibility.core.2.2.0-beta1-build3239\lib\dotnet\xunit.core.dll - True - - - ..\..\..\..\NugetPackages\xunit.extensibility.execution.2.2.0-beta1-build3239\lib\net45\xunit.execution.desktop.dll - True - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {8d408909-459f-4853-a36c-745118f99869} - Microsoft.Common.Core - - - {fc4aad0a-13b9-49ee-a59c-f03142958170} - Microsoft.Common.Core.Test - - - {5EF2AD64-D6FE-446B-B350-8C7F0DF0834D} - Microsoft.UnitTests.Core - - - {25cd8690-6208-4740-b123-6dbce6b9444a} - Microsoft.Languages.Core - - - - - - - - Microsoft - StrongName - - - - - - - - - + + + + Debug + AnyCPU + {EE2504A4-4666-460B-8552-5B342718CB02} + Library + Properties + Microsoft.Languages.Core.Test + Microsoft.Languages.Core.Test + v4.6 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + SAK + SAK + SAK + SAK + False + UnitTest + + + + $(RootDirectory)\obj\ + $(RootDirectory)\bin\ + $(BaseIntermediateOutputPath)\$(Configuration)\$(AssemblyName)\ + $(BaseOutputPath)\$(Configuration)\ + + + + ..\..\..\..\NugetPackages\FluentAssertions.4.1.1\lib\net45\FluentAssertions.dll + True + + + ..\..\..\..\NugetPackages\FluentAssertions.4.1.1\lib\net45\FluentAssertions.Core.dll + True + + + + + + ..\..\..\..\NugetPackages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll + True + + + ..\..\..\..\NugetPackages\xunit.assert.2.2.0-beta1-build3239\lib\dotnet\xunit.assert.dll + True + + + ..\..\..\..\NugetPackages\xunit.extensibility.core.2.2.0-beta1-build3239\lib\dotnet\xunit.core.dll + True + + + ..\..\..\..\NugetPackages\xunit.extensibility.execution.2.2.0-beta1-build3239\lib\net45\xunit.execution.desktop.dll + True + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {8d408909-459f-4853-a36c-745118f99869} + Microsoft.Common.Core + + + {fc4aad0a-13b9-49ee-a59c-f03142958170} + Microsoft.Common.Core.Test + + + {5EF2AD64-D6FE-446B-B350-8C7F0DF0834D} + Microsoft.UnitTests.Core + + + {25cd8690-6208-4740-b123-6dbce6b9444a} + Microsoft.Languages.Core + + + + + + + + Microsoft + StrongName + + + + + + + + + \ No newline at end of file diff --git a/src/Languages/Editor/Impl/EditorHelpers/TextBufferHelper.cs b/src/Languages/Editor/Impl/EditorHelpers/TextBufferHelper.cs index 3a3576938..e0707bec6 100644 --- a/src/Languages/Editor/Impl/EditorHelpers/TextBufferHelper.cs +++ b/src/Languages/Editor/Impl/EditorHelpers/TextBufferHelper.cs @@ -1,15 +1,13 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.IO; using System.Linq; using Microsoft.Common.Core; +using Microsoft.Languages.Editor.Controller; +using Microsoft.Languages.Editor.Shell; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Projection; -using Microsoft.Languages.Core.Text; -using Microsoft.Languages.Editor.Controller; -using Microsoft.Languages.Editor.Shell; namespace Microsoft.Languages.Editor.EditorHelpers { public class TextChangeExtent { diff --git a/src/Languages/Editor/Impl/Microsoft.Languages.Editor.csproj b/src/Languages/Editor/Impl/Microsoft.Languages.Editor.csproj index 613652bd7..ff7e7f1d7 100644 --- a/src/Languages/Editor/Impl/Microsoft.Languages.Editor.csproj +++ b/src/Languages/Editor/Impl/Microsoft.Languages.Editor.csproj @@ -1,201 +1,201 @@ - - - - - - Debug - AnyCPU - {62857E49-E586-4BAA-AE4D-1232093E7378} - Library - Properties - Microsoft.Languages.Editor - Microsoft.Languages.Editor - v4.6 - 512 - SAK - SAK - SAK - SAK - - - - - - $(RootDirectory)\obj\ - $(RootDirectory)\bin\ - $(BaseIntermediateOutputPath)\$(Configuration)\$(AssemblyName)\ - $(BaseOutputPath)\$(Configuration)\ - - - - ..\..\..\..\NugetPackages\Microsoft.VisualStudio.CoreUtility.14.1.24720\lib\net45\Microsoft.VisualStudio.CoreUtility.dll - True - - - - - ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.Data.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.Data.dll - True - - - ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.Logic.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.Logic.dll - True - - - ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.UI.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.UI.dll - True - - - ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.UI.Wpf.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.UI.Wpf.dll - True - - - - - - - - - - - - - - - Properties\GlobalAssemblyInfo.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {8d408909-459f-4853-a36c-745118f99869} - Microsoft.Common.Core - - - {25cd8690-6208-4740-b123-6dbce6b9444a} - Microsoft.Languages.Core - - - - - - - - Microsoft - StrongName - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - + + + + + + Debug + AnyCPU + {62857E49-E586-4BAA-AE4D-1232093E7378} + Library + Properties + Microsoft.Languages.Editor + Microsoft.Languages.Editor + v4.6 + 512 + SAK + SAK + SAK + SAK + + + + + + $(RootDirectory)\obj\ + $(RootDirectory)\bin\ + $(BaseIntermediateOutputPath)\$(Configuration)\$(AssemblyName)\ + $(BaseOutputPath)\$(Configuration)\ + + + + ..\..\..\..\NugetPackages\Microsoft.VisualStudio.CoreUtility.14.1.24720\lib\net45\Microsoft.VisualStudio.CoreUtility.dll + True + + + + + ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.Data.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.Data.dll + True + + + ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.Logic.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.Logic.dll + True + + + ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.UI.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.UI.dll + True + + + ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.UI.Wpf.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.UI.Wpf.dll + True + + + + + + + + + + + + + + + Properties\GlobalAssemblyInfo.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {8d408909-459f-4853-a36c-745118f99869} + Microsoft.Common.Core + + + {25cd8690-6208-4740-b123-6dbce6b9444a} + Microsoft.Languages.Core + + + + + + + + Microsoft + StrongName + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/src/Languages/Editor/Impl/Selection/SelectionTracker.cs b/src/Languages/Editor/Impl/Selection/SelectionTracker.cs index a2a96f252..4d9beeb0e 100644 --- a/src/Languages/Editor/Impl/Selection/SelectionTracker.cs +++ b/src/Languages/Editor/Impl/Selection/SelectionTracker.cs @@ -1,4 +1,5 @@ -using Microsoft.VisualStudio.Text; +using Microsoft.Languages.Editor.Text; +using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Formatting; @@ -82,14 +83,12 @@ public void MoveToAfterChanges(int virtualSpaces = 0) { #endregion protected virtual void MoveCaretTo(SnapshotPoint position, int virtualSpaces) { - var viewPosition = TextView.BufferGraph.MapUpToBuffer(position, PointTrackingMode.Positive, PositionAffinity.Successor, TextView.TextBuffer); - + SnapshotPoint? viewPosition = TextView.MapUpToBuffer(position.Position, position.Snapshot.TextBuffer); if (viewPosition.HasValue) { TextView.Caret.MoveTo(new VirtualSnapshotPoint(viewPosition.Value, virtualSpaces)); if (TextView.Caret.ContainingTextViewLine.VisibilityState != VisibilityState.FullyVisible) { TextView.Caret.EnsureVisible(); - TextView.DisplayTextLineContainingBufferPosition(viewPosition.Value, _offsetFromTop, ViewRelativePosition.Top); } } diff --git a/src/Languages/Editor/Impl/Services/ServiceManager.cs b/src/Languages/Editor/Impl/Services/ServiceManager.cs index feb904002..ecd6452e1 100644 --- a/src/Languages/Editor/Impl/Services/ServiceManager.cs +++ b/src/Languages/Editor/Impl/Services/ServiceManager.cs @@ -130,21 +130,6 @@ public static T GetService(IPropertyOwner propertyOwner) where T : class { } } - /// - /// Retrieves service from a service manager for this Property owner given service type and content type - /// - /// Service type - /// Property owner - /// Content type - /// Service instance - public static T GetService(IPropertyOwner propertyOwner, IContentType contentType) where T : class { - var sm = ServiceManager.FromPropertyOwner(propertyOwner); - if (sm != null) - return sm.GetService(contentType); - - return null; - } - public static ICollection GetAllServices(IPropertyOwner propertyOwner) where T : class { var sm = ServiceManager.FromPropertyOwner(propertyOwner); if (sm != null) @@ -166,20 +151,6 @@ public static void AddService(T serviceInstance, IPropertyOwner propertyOwner sm.AddService(serviceInstance); } - /// - /// Add content type specific service to a service manager associated with a particular Property owner - /// - /// Service type - /// Service instance - /// Property owner - /// Content type of the service - public static void AddService(T serviceInstance, IPropertyOwner propertyOwner, IContentType contentType) where T : class { - var sm = ServiceManager.FromPropertyOwner(propertyOwner); - Debug.Assert(sm != null); - - sm.AddService(serviceInstance, contentType); - } - public static void RemoveService(IPropertyOwner propertyOwner) where T : class { if (propertyOwner != null) { var sm = ServiceManager.FromPropertyOwner(propertyOwner); @@ -189,13 +160,6 @@ public static void RemoveService(IPropertyOwner propertyOwner) where T : clas } } - public static void RemoveService(IPropertyOwner propertyOwner, IContentType contentType) where T : class { - var sm = ServiceManager.FromPropertyOwner(propertyOwner); - Debug.Assert(sm != null); - - sm.RemoveService(contentType); - } - private T GetService() where T : class { return GetService(true); } @@ -218,35 +182,6 @@ private T GetService(bool checkDerivation) where T : class { } } - private T GetService(IContentType contentType) where T : class { - lock (_lock) { - object service = null; - - _servicesByContentType.TryGetValue(Tuple.Create(typeof(T), contentType.TypeName), out service); - if (service != null) - return service as T; - - // Try walking through and cast. Perhaps someone is asking for IFoo - // that is implemented on class Bar but Bar was added as Bar, not as IFoo - foreach (var kvp in _servicesByContentType) { - if (String.Compare(kvp.Key.Item2, contentType.TypeName, StringComparison.OrdinalIgnoreCase) == 0) { - service = kvp.Value as T; - if (service != null) - return service as T; - } - } - - // iterate through base types since Razor, PHP and ASP.NET content type derive from HTML - foreach (var ct in contentType.BaseTypes) { - service = GetService(ct); - if (service != null) - break; - } - - return service as T; - } - } - private ICollection GetAllServices() where T : class { var list = new List(); @@ -277,22 +212,6 @@ private void AddService(T serviceInstance) where T : class { } } - private void AddService(T serviceInstance, IContentType contentType) where T : class { - bool added = false; - - lock (_lock) { - if (GetService(contentType) == null) { - _servicesByContentType.Add(Tuple.Create(typeof(T), contentType.TypeName), serviceInstance); - added = true; - } - } - - Debug.Assert(added); - if (added) { - FireServiceAdded(typeof(T), serviceInstance); - } - } - private void RemoveService() where T : class { bool foundServiceInstance = false; object serviceInstance; diff --git a/src/Languages/Editor/Impl/Shell/EditorShell.cs b/src/Languages/Editor/Impl/Shell/EditorShell.cs index c48113c4f..c71a12e5f 100644 --- a/src/Languages/Editor/Impl/Shell/EditorShell.cs +++ b/src/Languages/Editor/Impl/Shell/EditorShell.cs @@ -46,9 +46,7 @@ internal set { } } - public static bool IsUIThread { - get { return Current != null ? Current.MainThread == Thread.CurrentThread : true; } - } + public static bool IsUIThread => Current == null || Current.MainThread == Thread.CurrentThread; /// /// Provides a way to execute action on UI thread while diff --git a/src/Languages/Editor/Test/BraceMatch/GotoBraceCommandTest.cs b/src/Languages/Editor/Test/BraceMatch/GotoBraceCommandTest.cs index 1404ebd79..7d8d473f6 100644 --- a/src/Languages/Editor/Test/BraceMatch/GotoBraceCommandTest.cs +++ b/src/Languages/Editor/Test/BraceMatch/GotoBraceCommandTest.cs @@ -1,19 +1,11 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Threading; +using System.Diagnostics.CodeAnalysis; using FluentAssertions; using Microsoft.Languages.Editor.BraceMatch; using Microsoft.Languages.Editor.Controller.Constants; -using Microsoft.Languages.Editor.Services; -using Microsoft.Languages.Editor.Shell; -using Microsoft.Languages.Editor.Tasks; using Microsoft.UnitTests.Core.XUnit; using Microsoft.VisualStudio.Editor.Mocks; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; -using Microsoft.VisualStudio.Utilities; -using Xunit; namespace Microsoft.Languages.Editor.Test.BraceMatch { [ExcludeFromCodeCoverage] diff --git a/src/Languages/Editor/Test/GlobalSuppressions.cs b/src/Languages/Editor/Test/GlobalSuppressions.cs deleted file mode 100644 index 741d82e1a..000000000 Binary files a/src/Languages/Editor/Test/GlobalSuppressions.cs and /dev/null differ diff --git a/src/Languages/Editor/Test/Helpers/HelpersTest.cs b/src/Languages/Editor/Test/Helpers/HelpersTest.cs new file mode 100644 index 000000000..21149e3cf --- /dev/null +++ b/src/Languages/Editor/Test/Helpers/HelpersTest.cs @@ -0,0 +1,104 @@ +using System.Diagnostics.CodeAnalysis; +using FluentAssertions; +using Microsoft.Languages.Editor.BraceMatch; +using Microsoft.Languages.Editor.Controller.Constants; +using Microsoft.Languages.Editor.EditorHelpers; +using Microsoft.UnitTests.Core.XUnit; +using Microsoft.VisualStudio.Editor.Mocks; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using NSubstitute; + +namespace Microsoft.Languages.Editor.Test.Helpers { + [ExcludeFromCodeCoverage] + public class HelpersTest { + [Test] + [Category.Languages.Core] + public void TextChangeExtentTest() { + var actual = new TextChangeExtent(1, 2, 3); + actual.Start.Should().Be(1); + actual.OldEnd.Should().Be(2); + actual.NewEnd.Should().Be(3); + } + + [Test] + [Category.Languages.Core] + public void GetTextDocumentTest() { + var tb = new TextBufferMock(string.Empty, "R"); + + var s = Substitute.For(); + tb.Properties.AddProperty(typeof(ITextDocument), s); + + var td = tb.GetTextDocument(); + td.Should().NotBeNull(); + td.Should().Be(s); + td.Should().BeAssignableTo(); + } + + [Test] + [Category.Languages.Core] + public void GetLineColumnFromPositionTest() { + var tb = new TextBufferMock("a\r\nb", "R"); + + int line, col; + tb.GetLineColumnFromPosition(0, out line, out col); + line.Should().Be(0); + col.Should().Be(0); + + tb.GetLineColumnFromPosition(1, out line, out col); + line.Should().Be(0); + col.Should().Be(1); + + tb.GetLineColumnFromPosition(3, out line, out col); + line.Should().Be(1); + col.Should().Be(0); + } + + [Test] + [Category.Languages.Core] + public void GetPositionFromLineColumnTest() { + var tb = new TextBufferMock("a\r\nb", "R"); + + int? position; + position = tb.GetPositionFromLineColumn(0, 0); + position.Should().Be(0); + + position = tb.GetPositionFromLineColumn(1, 0); + position.Should().Be(3); + + position = tb.GetPositionFromLineColumn(100, 100); + position.Should().NotHaveValue(); + } + + [Test] + [Category.Languages.Core] + public void IsSignatureHelpBufferTest() { + var tb = new TextBufferMock(string.Empty, "R"); + tb.IsSignatureHelpBuffer().Should().BeFalse(); + + tb = new TextBufferMock(string.Empty, "R Signature Help"); + tb.IsSignatureHelpBuffer().Should().BeTrue(); + } + + [Test] + [Category.Languages.Core] + public void IsContentEqualsOrdinalTest() { + var tb = new TextBufferMock("abc abc", "R"); + + tb.IsContentEqualsOrdinal( + new TrackingSpanMock(tb, new Span(0, 3), SpanTrackingMode.EdgePositive, TrackingFidelityMode.Forward), + new TrackingSpanMock(tb, new Span(4, 3), SpanTrackingMode.EdgePositive, TrackingFidelityMode.Forward) + ).Should().BeTrue(); + + tb.IsContentEqualsOrdinal( + new TrackingSpanMock(tb, new Span(0, 4), SpanTrackingMode.EdgePositive, TrackingFidelityMode.Forward), + new TrackingSpanMock(tb, new Span(4, 3), SpanTrackingMode.EdgePositive, TrackingFidelityMode.Forward) + ).Should().BeFalse(); + + tb.IsContentEqualsOrdinal( + new TrackingSpanMock(tb, new Span(0, 4), SpanTrackingMode.EdgePositive, TrackingFidelityMode.Forward), + new TrackingSpanMock(tb, new Span(1, 4), SpanTrackingMode.EdgePositive, TrackingFidelityMode.Forward) + ).Should().BeFalse(); + } + } +} \ No newline at end of file diff --git a/src/Languages/Editor/Test/Microsoft.Languages.Editor.Test.csproj b/src/Languages/Editor/Test/Microsoft.Languages.Editor.Test.csproj index 6370570c1..59a305a49 100644 --- a/src/Languages/Editor/Test/Microsoft.Languages.Editor.Test.csproj +++ b/src/Languages/Editor/Test/Microsoft.Languages.Editor.Test.csproj @@ -1,163 +1,167 @@ - - - - Debug - AnyCPU - {5340191E-31E5-43A0-A485-B6678D391B10} - Library - Properties - Microsoft.Languages.Editor.Test - Microsoft.Languages.Editor.Test - v4.6 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest - SAK - SAK - SAK - SAK - - - - $(RootDirectory)\obj\ - $(RootDirectory)\bin\ - $(BaseIntermediateOutputPath)\$(Configuration)\$(AssemblyName)\ - $(BaseOutputPath)\$(Configuration)\ - - - - ..\..\..\..\NugetPackages\FluentAssertions.4.1.1\lib\net45\FluentAssertions.dll - True - - - ..\..\..\..\NugetPackages\FluentAssertions.4.1.1\lib\net45\FluentAssertions.Core.dll - True - - - ..\..\..\..\NugetPackages\Microsoft.VisualStudio.CoreUtility.14.1.24720\lib\net45\Microsoft.VisualStudio.CoreUtility.dll - True - - - - - ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.Data.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.Data.dll - True - - - ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.Logic.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.Logic.dll - True - - - ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.UI.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.UI.dll - True - - - ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.UI.Wpf.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.UI.Wpf.dll - True - - - - - - - - - ..\..\..\..\NugetPackages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll - True - - - ..\..\..\..\NugetPackages\xunit.assert.2.2.0-beta1-build3239\lib\dotnet\xunit.assert.dll - True - - - ..\..\..\..\NugetPackages\xunit.extensibility.core.2.2.0-beta1-build3239\lib\dotnet\xunit.core.dll - True - - - ..\..\..\..\NugetPackages\xunit.extensibility.execution.2.2.0-beta1-build3239\lib\net45\xunit.execution.desktop.dll - True - - - - - - - - - - - - - Properties\GlobalAssemblyInfo.cs - - - - - - - - - - - - - - - - - - {8d408909-459f-4853-a36c-745118f99869} - Microsoft.Common.Core - - - {fc4aad0a-13b9-49ee-a59c-f03142958170} - Microsoft.Common.Core.Test - - - {5fcb86d5-4b25-4039-858c-b5a06eb702e1} - Microsoft.VisualStudio.Editor.Mocks - - - {5EF2AD64-D6FE-446B-B350-8C7F0DF0834D} - Microsoft.UnitTests.Core - - - {25cd8690-6208-4740-b123-6dbce6b9444a} - Microsoft.Languages.Core - - - {ee2504a4-4666-460b-8552-5b342718cb02} - Microsoft.Languages.Core.Test - - - {62857e49-e586-4baa-ae4d-1232093e7378} - Microsoft.Languages.Editor - - - - - - - - Microsoft - StrongName - - - - - - - - - + + + + Debug + AnyCPU + {5340191E-31E5-43A0-A485-B6678D391B10} + Library + Properties + Microsoft.Languages.Editor.Test + Microsoft.Languages.Editor.Test + v4.6 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + SAK + SAK + SAK + SAK + + + + $(RootDirectory)\obj\ + $(RootDirectory)\bin\ + $(BaseIntermediateOutputPath)\$(Configuration)\$(AssemblyName)\ + $(BaseOutputPath)\$(Configuration)\ + + + + ..\..\..\..\NugetPackages\FluentAssertions.4.1.1\lib\net45\FluentAssertions.dll + True + + + ..\..\..\..\NugetPackages\FluentAssertions.4.1.1\lib\net45\FluentAssertions.Core.dll + True + + + ..\..\..\..\NugetPackages\Microsoft.VisualStudio.CoreUtility.14.1.24720\lib\net45\Microsoft.VisualStudio.CoreUtility.dll + True + + + + + ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.Data.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.Data.dll + True + + + ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.Logic.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.Logic.dll + True + + + ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.UI.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.UI.dll + True + + + ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.UI.Wpf.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.UI.Wpf.dll + True + + + ..\..\..\..\NugetPackages\NSubstitute.1.9.2.0\lib\net45\NSubstitute.dll + True + + + + + + + + + ..\..\..\..\NugetPackages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll + True + + + ..\..\..\..\NugetPackages\xunit.assert.2.2.0-beta1-build3239\lib\dotnet\xunit.assert.dll + True + + + ..\..\..\..\NugetPackages\xunit.extensibility.core.2.2.0-beta1-build3239\lib\dotnet\xunit.core.dll + True + + + ..\..\..\..\NugetPackages\xunit.extensibility.execution.2.2.0-beta1-build3239\lib\net45\xunit.execution.desktop.dll + True + + + + + + + + + + + + + Properties\GlobalAssemblyInfo.cs + + + + + + + + + + + + + + + + + + {8d408909-459f-4853-a36c-745118f99869} + Microsoft.Common.Core + + + {fc4aad0a-13b9-49ee-a59c-f03142958170} + Microsoft.Common.Core.Test + + + {5fcb86d5-4b25-4039-858c-b5a06eb702e1} + Microsoft.VisualStudio.Editor.Mocks + + + {5EF2AD64-D6FE-446B-B350-8C7F0DF0834D} + Microsoft.UnitTests.Core + + + {25cd8690-6208-4740-b123-6dbce6b9444a} + Microsoft.Languages.Core + + + {ee2504a4-4666-460b-8552-5b342718cb02} + Microsoft.Languages.Core.Test + + + {62857e49-e586-4baa-ae4d-1232093e7378} + Microsoft.Languages.Editor + + + + + + + + Microsoft + StrongName + + + + + + + + + \ No newline at end of file diff --git a/src/Languages/Editor/Test/Shell/TestShellBase.cs b/src/Languages/Editor/Test/Shell/TestShellBase.cs index 6c3bbd26f..2d7e73ae5 100644 --- a/src/Languages/Editor/Test/Shell/TestShellBase.cs +++ b/src/Languages/Editor/Test/Shell/TestShellBase.cs @@ -19,10 +19,10 @@ public MessageButtons ShowMessage(string message, MessageButtons buttons) { return MessageButtons.OK; } + public void ShowContextMenu(Guid contextMenuGroup, int contextMenuId, int x, int y) { } + public void DoIdle() { - if (Idle != null) { - Idle(null, EventArgs.Empty); - } + Idle?.Invoke(null, EventArgs.Empty); DoEvents(); } diff --git a/src/Languages/Editor/Test/packages.config b/src/Languages/Editor/Test/packages.config index 1d78ce582..77e59f93f 100644 --- a/src/Languages/Editor/Test/packages.config +++ b/src/Languages/Editor/Test/packages.config @@ -6,6 +6,7 @@ + diff --git a/src/Markdown/Editor/Impl/Classification/MarkdownClassificationNameProvider.cs b/src/Markdown/Editor/Impl/Classification/MarkdownClassificationNameProvider.cs index 51159fb17..4ff63750d 100644 --- a/src/Markdown/Editor/Impl/Classification/MarkdownClassificationNameProvider.cs +++ b/src/Markdown/Editor/Impl/Classification/MarkdownClassificationNameProvider.cs @@ -25,7 +25,9 @@ public string GetClassificationName(MarkdownToken t) { case MarkdownTokenType.BoldItalic: return MarkdownClassificationTypes.BoldItalic; - case MarkdownTokenType.Code: + case MarkdownTokenType.CodeStart: + case MarkdownTokenType.CodeContent: + case MarkdownTokenType.CodeEnd: return MarkdownClassificationTypes.Code; case MarkdownTokenType.Monospace: diff --git a/src/Markdown/Editor/Impl/Classification/MdClassifier.cs b/src/Markdown/Editor/Impl/Classification/MdClassifier.cs index 6ebbb6bd8..c3136b8b2 100644 --- a/src/Markdown/Editor/Impl/Classification/MdClassifier.cs +++ b/src/Markdown/Editor/Impl/Classification/MdClassifier.cs @@ -26,8 +26,13 @@ public MdClassifier(ITextBuffer textBuffer, protected override void RemoveSensitiveTokens(int position, TextRangeCollection tokens) { + // Check if change damages code block. Normally base classifier removes all tokens + // from the caret position to the end of the visible area. If typing is inside + // the code area it may also affects tokens starting from the beginning of the code + // block. For example, when user types ``` in a middle of existing ```...``` block. + // This is similar to typing %> or ?> in a middle of ASP.NET or PHP block. int last = tokens.Count - 1; - if (last >= 0 && tokens[last].TokenType == MarkdownTokenType.Code) { + if (last >= 0 && tokens[last].TokenType == MarkdownTokenType.CodeStart) { tokens.RemoveAt(last); } diff --git a/src/Markdown/Editor/Impl/Microsoft.Markdown.Editor.csproj b/src/Markdown/Editor/Impl/Microsoft.Markdown.Editor.csproj index ae800e7ed..fcf3f1356 100644 --- a/src/Markdown/Editor/Impl/Microsoft.Markdown.Editor.csproj +++ b/src/Markdown/Editor/Impl/Microsoft.Markdown.Editor.csproj @@ -102,7 +102,6 @@ - diff --git a/src/Markdown/Editor/Impl/Tokens/MarkdownRCodeToken.cs b/src/Markdown/Editor/Impl/Tokens/MarkdownRCodeToken.cs index 09eebda05..cefbbf84b 100644 --- a/src/Markdown/Editor/Impl/Tokens/MarkdownRCodeToken.cs +++ b/src/Markdown/Editor/Impl/Tokens/MarkdownRCodeToken.cs @@ -14,7 +14,7 @@ public class MarkdownRCodeToken : MarkdownToken, ICompositeToken { private ITextProvider _textProvider; public MarkdownRCodeToken(int start, int length, ITextProvider textProvider) : - base(MarkdownTokenType.Code, new TextRange(start, length)) { + base(MarkdownTokenType.CodeContent, new TextRange(start, length)) { _textProvider = textProvider; } diff --git a/src/Markdown/Editor/Impl/Tokens/MarkdownTokenType.cs b/src/Markdown/Editor/Impl/Tokens/MarkdownTokenType.cs index e8b75af71..20b9a9289 100644 --- a/src/Markdown/Editor/Impl/Tokens/MarkdownTokenType.cs +++ b/src/Markdown/Editor/Impl/Tokens/MarkdownTokenType.cs @@ -53,10 +53,20 @@ public enum MarkdownTokenType { Monospace, /// - /// Triple-backtick + /// Leading triple-backtick /// - Code, + CodeStart, + /// + /// Code inside ```code``` block + /// + CodeContent, + + /// + /// Trailing triple-backtick + /// + CodeEnd, + /// /// (url) /// diff --git a/src/Markdown/Editor/Impl/Tokens/MdTokenizer.cs b/src/Markdown/Editor/Impl/Tokens/MdTokenizer.cs index a4e996b89..1a9c6c2e0 100644 --- a/src/Markdown/Editor/Impl/Tokens/MdTokenizer.cs +++ b/src/Markdown/Editor/Impl/Tokens/MdTokenizer.cs @@ -146,6 +146,7 @@ protected bool HandleCode(bool block) { ticksLength = block ? 3 : 1; _cs.Advance(ticksLength); + // block in R: '''{r qplot, x=y, ...} bool rLanguage = block && (_cs.CurrentChar == '{' && (_cs.NextChar == 'r' || _cs.NextChar == 'R')); rLanguage |= !block && (_cs.CurrentChar == 'r' || _cs.CurrentChar == 'R'); @@ -154,30 +155,34 @@ protected bool HandleCode(bool block) { int codeStart = _cs.Position; while (!_cs.IsEndOfStream()) { + // End of R block: ``` bool endOfBlock = block && _cs.IsAtNewLine() && _cs.NextChar == '`' && _cs.LookAhead(2) == '`' && _cs.LookAhead(3) == '`'; + if (endOfBlock) { _cs.SkipLineBreak(); } else { + // inline code `r 1 + 1` endOfBlock = !block && _cs.CurrentChar == '`'; } - // Find end of the block (closing ```) if (endOfBlock) { int codeEnd = _cs.Position; - _cs.Advance(ticksLength); + _cs.Advance(ticksLength); // past the end of block now + // Opening ticks + AddToken(MarkdownTokenType.CodeStart, ticksStart, ticksLength); if (rLanguage) { - // code is inside ``` and after the language name. + // Code is inside ``` and after the language name. // We still want to colorize numbers in ```{r, x = 1.0, ...} - AddToken(MarkdownTokenType.Code, ticksStart, ticksLength); var token = new MarkdownRCodeToken(codeStart, codeEnd - codeStart, _cs.Text); _tokens.Add(token); - AddToken(MarkdownTokenType.Code, codeEnd, _cs.Position - codeEnd); } else { - AddToken(MarkdownTokenType.Code, ticksStart, _cs.Position - ticksStart); + AddToken(MarkdownTokenType.CodeContent, codeStart, codeEnd - codeStart); } + + AddToken(MarkdownTokenType.CodeEnd, _cs.Position - ticksLength, ticksLength); return true; } diff --git a/src/Markdown/Editor/Impl/Tokens/RmdTokenizer.cs b/src/Markdown/Editor/Impl/Tokens/RmdTokenizer.cs deleted file mode 100644 index a2121478f..000000000 --- a/src/Markdown/Editor/Impl/Tokens/RmdTokenizer.cs +++ /dev/null @@ -1,69 +0,0 @@ -namespace Microsoft.Markdown.Editor.Tokens { - /// - /// Main regular markdown tokenizer. R Markdown has - /// a separate tokenizer. - /// https://help.github.com/articles/markdown-basics/ - /// - internal class RmdTokenizer : MdTokenizer { - - protected override void HandleCharacter() { - while (!_cs.IsEndOfStream()) { - bool handled = false; - - // Regular content is Latex-like - switch (_cs.CurrentChar) { - case '#': - handled = HandleHeading(); - break; - - case '*': - handled = HandleStar(); - break; - - case '_': - if (!char.IsWhiteSpace(_cs.NextChar)) { - handled = HandleItalic('_', MarkdownTokenType.Italic); - } - break; - - case '>': - handled = HandleQuote(); - break; - - case '`': - handled = HandleBackTick(); - break; - - case '-': - if (_cs.NextChar == ' ') { - handled = HandleListItem(); - } else if (_cs.NextChar == '-' && _cs.LookAhead(2) == '-') { - handled = HandleHeading(); - } - break; - - case '=': - if (_cs.NextChar == '=' && _cs.LookAhead(2) == '=') { - handled = HandleHeading(); - } - break; - - case '[': - handled = HandleAltText(); - break; - - default: - if (_cs.IsDecimal()) { - handled = HandleNumberedListItem(); - } - break; - - } - - if (!handled) { - _cs.MoveToNextChar(); - } - } - } - } -} diff --git a/src/Markdown/Editor/Test/Classification/InvalidateCodeTest.cs b/src/Markdown/Editor/Test/Classification/InvalidateCodeTest.cs index e96872962..964fc7a3d 100644 --- a/src/Markdown/Editor/Test/Classification/InvalidateCodeTest.cs +++ b/src/Markdown/Editor/Test/Classification/InvalidateCodeTest.cs @@ -1,6 +1,7 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using FluentAssertions; +using Microsoft.Languages.Editor.Shell; using Microsoft.Languages.Editor.Test.Text; using Microsoft.Languages.Editor.Test.Utility; using Microsoft.Markdown.Editor.Classification.MD; @@ -13,21 +14,48 @@ namespace Microsoft.Markdown.Editor.Test.Classification { [ExcludeFromCodeCoverage] public class InvalidateCodeTest { - [Test(Skip = "Unstable")] + [Test] [Category.Md.Classifier] public void Markdown_InvalidateCodeTest() { - string content = "```'{r}\n#R\n```"; - TextBufferMock textBuffer = new TextBufferMock(content, MdContentTypeDefinition.ContentType); + string content = "```{r}#R\n```"; + var factory = EditorShell.Current.ExportProvider.GetExportedValue(); + ITextBuffer textBuffer = factory.CreateTextBuffer(new ContentTypeMock(MdContentTypeDefinition.ContentType)); + textBuffer.Insert(0, content); MdClassifierProvider classifierProvider = new MdClassifierProvider(); IClassifier cls = classifierProvider.GetClassifier(textBuffer); + string actual = GetSpans(cls, textBuffer); + actual.TrimEnd().Should().Be( +@"[0:3] Markdown Code +[6:2] comment +[9:3] Markdown Code"); + Typing.Type(textBuffer, 6, "\n"); + actual = GetSpans(cls, textBuffer); + actual.TrimEnd().Should().Be( +@"[0:3] Markdown Code +[7:2] comment +[10:3] Markdown Code"); - IList spans = cls.GetClassificationSpans(new SnapshotSpan(textBuffer.CurrentSnapshot, new Span(0, textBuffer.CurrentSnapshot.Length))); - string actual = ClassificationWriter.WriteClassifications(spans); + Typing.Delete(textBuffer, 4, 1); + actual = GetSpans(cls, textBuffer); + actual.TrimEnd().Should().Be( +@"[0:3] Markdown Code +[4:5] Markdown Code +[9:3] Markdown Code"); - actual.TrimEnd().Should().Be("[0:15] Markdown Code"); + Typing.Type(textBuffer, 4, "R"); + actual = GetSpans(cls, textBuffer); + actual.TrimEnd().Should().Be( +@"[0:3] Markdown Code +[7:2] comment +[10:3] Markdown Code"); + } + + private string GetSpans(IClassifier cls, ITextBuffer textBuffer) { + IList spans = cls.GetClassificationSpans(new SnapshotSpan(textBuffer.CurrentSnapshot, new Span(0, textBuffer.CurrentSnapshot.Length))); + return ClassificationWriter.WriteClassifications(spans); } } } diff --git a/src/Markdown/Editor/Test/Files/Tokenization/01.md.tokens b/src/Markdown/Editor/Test/Files/Tokenization/01.md.tokens index 8875a635e..a0d2d3217 100644 --- a/src/Markdown/Editor/Test/Files/Tokenization/01.md.tokens +++ b/src/Markdown/Editor/Test/Files/Tokenization/01.md.tokens @@ -7,7 +7,7 @@ Monospace : 223 - 234 (11) Italic : 236 - 243 (7) BoldItalic : 243 - 258 (15) Italic : 258 - 259 (1) -Code : 263 - 266 (3) +CodeStart : 263 - 266 (3) Composite: RToken 1 : 267 - 268 (1) Composite: RToken 14 : 268 - 269 (1) Composite: RToken 2 : 271 - 278 (7) @@ -27,7 +27,7 @@ Composite: RToken 12 : 315 - 316 (1) Composite: RToken 5 : 316 - 317 (1) Composite: RToken 20 : 317 - 318 (1) Composite: RToken 1 : 320 - 322 (2) -Code : 320 - 323 (3) +CodeEnd : 320 - 323 (3) AltText : 329 - 335 (6) ListItem : 380 - 388 (8) ListItem : 392 - 401 (9) diff --git a/src/Markdown/Editor/Test/Tokens/TokenizeBlockTest.cs b/src/Markdown/Editor/Test/Tokens/TokenizeBlockTest.cs index 8a0c60092..0f5f1cbb2 100644 --- a/src/Markdown/Editor/Test/Tokens/TokenizeBlockTest.cs +++ b/src/Markdown/Editor/Test/Tokens/TokenizeBlockTest.cs @@ -8,7 +8,7 @@ namespace Microsoft.Markdown.Editor.Test.Tokens { [ExcludeFromCodeCoverage] - public class TokenizeBlockTest : TokenizeTestBase { + public class TokenizeMdBlockTest : TokenizeTestBase { [CompositeTest] [InlineData( @"``` @@ -25,13 +25,35 @@ public class TokenizeBlockTest : TokenizeTestBase() + .And.BeAssignableTo(); + tokens[2].Should().HaveType(MarkdownTokenType.CodeEnd); + tokens[2].Length.Should().Be(3); } [CompositeTest] @@ -46,16 +68,15 @@ public void TokenizeMd_BlockEmpty(string text) { [Test] [Category.Md.Tokenizer] - public void TokenizeMd_Block06() { + public void CodeBlock03() { var tokens = Tokenize(@"`r x <- 1`", new MdTokenizer()); tokens.Should().HaveCount(3); - tokens[0].Should().HaveType(MarkdownTokenType.Code); + tokens[0].Should().HaveType(MarkdownTokenType.CodeStart); tokens[1].Should().BeOfType() .And.BeAssignableTo() .Which.TokenList.Should().HaveCount(3); - - tokens[2].Should().HaveType(MarkdownTokenType.Code); + tokens[2].Should().HaveType(MarkdownTokenType.CodeEnd); } } } diff --git a/src/Markdown/Editor/Test/Utility/TokenizeFiles.cs b/src/Markdown/Editor/Test/Utility/TokenizeFiles.cs index 92b1fbf2e..2fe2a396b 100644 --- a/src/Markdown/Editor/Test/Utility/TokenizeFiles.cs +++ b/src/Markdown/Editor/Test/Utility/TokenizeFiles.cs @@ -36,7 +36,7 @@ private static void TokenizeFileImplementation(M if (_regenerateBaselineFiles) { // Update this to your actual enlistment if you need to update baseline - string enlistmentPath = @"C:\RTVS\src\Markdown\Editor\Test\Files\Tokenization"; + string enlistmentPath = @"F:\RTVS\src\Markdown\Editor\Test\Files\Tokenization"; baselineFile = Path.Combine(enlistmentPath, Path.GetFileName(testFile)) + ".tokens"; TestFiles.UpdateBaseline(baselineFile, actual); diff --git a/src/Mocks/Editor/TextBufferMock.cs b/src/Mocks/Editor/TextBufferMock.cs index 4f680a94f..8fe0dd136 100644 --- a/src/Mocks/Editor/TextBufferMock.cs +++ b/src/Mocks/Editor/TextBufferMock.cs @@ -15,6 +15,9 @@ public TextBufferMock(string content, string contentTypeName) TextVersionMock initialVersion = new TextVersionMock(this, 0, content.Length); CurrentSnapshot = new TextSnapshotMock(content, this, initialVersion); } + public void Clear() { + Replace(new Span(0, CurrentSnapshot.Length), string.Empty); + } #region ITextBuffer Members diff --git a/src/Mocks/VisualStudio/EnumWindowFramesMock.cs b/src/Mocks/VisualStudio/EnumWindowFramesMock.cs new file mode 100644 index 000000000..2aada226f --- /dev/null +++ b/src/Mocks/VisualStudio/EnumWindowFramesMock.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections.Generic; +using Microsoft.VisualStudio.Shell.Interop; + +namespace Microsoft.VisualStudio.Shell.Mocks { + public sealed class EnumWindowFramesMock : IEnumWindowFrames { + private List _frames; + private int _index = 0; + + public EnumWindowFramesMock(List frames) { + _frames = frames; + } + + public int Clone(out IEnumWindowFrames ppenum) { + throw new NotImplementedException(); + } + + public int Next(uint celt, IVsWindowFrame[] rgelt, out uint pceltFetched) { + if(_index >= _frames.Count) { + pceltFetched = 0; + return VSConstants.S_FALSE; + } + + rgelt[0] = _frames[_index++]; + pceltFetched = 1; + return VSConstants.S_OK; + } + + public int Reset() { + _index = 0; + return VSConstants.S_OK; + } + + public int Skip(uint celt) { + _index += (int)celt; + return VSConstants.S_OK; + } + } +} diff --git a/src/Mocks/VisualStudio/InteractiveEvaluatorMock.cs b/src/Mocks/VisualStudio/InteractiveEvaluatorMock.cs new file mode 100644 index 000000000..f632ca0de --- /dev/null +++ b/src/Mocks/VisualStudio/InteractiveEvaluatorMock.cs @@ -0,0 +1,43 @@ +using System.Threading.Tasks; +using Microsoft.VisualStudio.InteractiveWindow; + +namespace Microsoft.VisualStudio.Shell.Mocks { + using Task = System.Threading.Tasks.Task; + + public sealed class InteractiveEvaluatorMock : IInteractiveEvaluator { + public InteractiveEvaluatorMock(IInteractiveWindow window) { + CurrentWindow = window; + } + public IInteractiveWindow CurrentWindow { get; set; } + + public void AbortExecution() { + } + + public bool CanExecuteCode(string text) { + return true; + } + + public void Dispose() { + } + + public Task ExecuteCodeAsync(string text) { + return Task.FromResult(ExecutionResult.Success); + } + + public string FormatClipboard() { + return string.Empty; + } + + public string GetPrompt() { + return ">"; + } + + public Task InitializeAsync() { + return Task.FromResult(ExecutionResult.Success); + } + + public Task ResetAsync(bool initialize = true) { + return Task.FromResult(ExecutionResult.Success); + } + } +} diff --git a/src/Mocks/VisualStudio/InteractiveWindowMock.cs b/src/Mocks/VisualStudio/InteractiveWindowMock.cs index cc8d92677..67152ef72 100644 --- a/src/Mocks/VisualStudio/InteractiveWindowMock.cs +++ b/src/Mocks/VisualStudio/InteractiveWindowMock.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.IO; -using Microsoft.VisualStudio.Editor.Mocks; using Microsoft.VisualStudio.InteractiveWindow; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; @@ -33,27 +32,15 @@ public TextWriter ErrorOutputWriter { public IInteractiveEvaluator Evaluator { get { - throw new NotImplementedException(); + return new InteractiveEvaluatorMock(this); } } - public bool IsInitializing { - get { - throw new NotImplementedException(); - } - } + public bool IsInitializing => false; - public bool IsResetting { - get { - throw new NotImplementedException(); - } - } + public bool IsResetting => false; - public bool IsRunning { - get { - throw new NotImplementedException(); - } - } + public bool IsRunning => true; public IInteractiveWindowOperations Operations { get { @@ -61,11 +48,7 @@ public IInteractiveWindowOperations Operations { } } - public ITextBuffer OutputBuffer { - get { - throw new NotImplementedException(); - } - } + public ITextBuffer OutputBuffer => _textBuffer; public TextWriter OutputWriter { get { @@ -90,15 +73,12 @@ public void AddInput(string input) { } public void Close() { - throw new NotImplementedException(); } public void Dispose() { - throw new NotImplementedException(); } public void FlushOutput() { - throw new NotImplementedException(); } public System.Threading.Tasks.Task InitializeAsync() { @@ -122,19 +102,20 @@ public void Write(System.Windows.UIElement element) { } public Span Write(string text) { - throw new NotImplementedException(); + InsertCode(text); + return new Span(0, _textBuffer.CurrentSnapshot.Length); } public Span WriteError(string text) { - throw new NotImplementedException(); + return Write(text); } public Span WriteErrorLine(string text) { - throw new NotImplementedException(); + return Write(text); } public Span WriteLine(string text) { - throw new NotImplementedException(); + return Write(text); } } } diff --git a/src/Mocks/VisualStudio/Microsoft.VisualStudio.Shell.Mocks.csproj b/src/Mocks/VisualStudio/Microsoft.VisualStudio.Shell.Mocks.csproj index 9c5ada1ba..c9533f6e1 100644 --- a/src/Mocks/VisualStudio/Microsoft.VisualStudio.Shell.Mocks.csproj +++ b/src/Mocks/VisualStudio/Microsoft.VisualStudio.Shell.Mocks.csproj @@ -191,7 +191,9 @@ + + @@ -206,6 +208,7 @@ + diff --git a/src/Mocks/VisualStudio/VsDebuggerMock.cs b/src/Mocks/VisualStudio/VsDebuggerMock.cs new file mode 100644 index 000000000..6e2dbf9bd --- /dev/null +++ b/src/Mocks/VisualStudio/VsDebuggerMock.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using Microsoft.VisualStudio.OLE.Interop; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.TextManager.Interop; + +namespace Microsoft.VisualStudio.Shell.Mocks { + public sealed class VsDebuggerMock : IVsDebugger { + private Dictionary _sinks = new Dictionary(); + private uint _cookie = 1; + + public DBGMODE Mode { get; set; } = DBGMODE.DBGMODE_Design; + + public int AdviseDebugEventCallback(object punkDebuggerEvents) { + throw new NotImplementedException(); + } + + public int AdviseDebuggerEvents(IVsDebuggerEvents pSink, out uint pdwCookie) { + pdwCookie = _cookie++; + _sinks[pdwCookie] = pSink; + return VSConstants.S_OK; + } + + public int AllowEditsWhileDebugging(ref Guid guidLanguageService) { + return VSConstants.S_OK; + } + + public int ExecCmdForTextPos(VsTextPos[] pTextPos, ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut) { + return VSConstants.S_OK; + } + + public int GetDataTipValue(IVsTextLines pTextBuf, TextSpan[] pTS, string pszExpression, out string pbstrValue) { + pbstrValue = string.Empty; + return VSConstants.S_OK; + } + + public int GetENCUpdate(out object ppUpdate) { + throw new NotImplementedException(); + } + + public int GetMode(DBGMODE[] pdbgmode) { + pdbgmode[0] = Mode; + return VSConstants.S_OK; + } + + public int InsertBreakpointByName(ref Guid guidLanguage, string pszCodeLocationText) { + throw new NotImplementedException(); + } + + public int IsBreakpointOnName(ref Guid guidLanguage, string pszCodeLocationText, out int pfIsBreakpoint) { + throw new NotImplementedException(); + } + + public int LaunchDebugTargets(uint cTargets, IntPtr rgDebugTargetInfo) { + throw new NotImplementedException(); + } + + public int ParseFileRedirection(string pszArgs, out string pbstrArgsProcessed, out IntPtr phStdInput, out IntPtr phStdOutput, out IntPtr phStdError) { + throw new NotImplementedException(); + } + + public int QueryStatusForTextPos(VsTextPos[] pTextPos, ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText) { + throw new NotImplementedException(); + } + + public int RemoveBreakpointsByName(ref Guid guidLanguage, string pszCodeLocationText) { + throw new NotImplementedException(); + } + + public int ToggleBreakpointByName(ref Guid guidLanguage, string pszCodeLocationText) { + throw new NotImplementedException(); + } + + public int UnadviseDebugEventCallback(object punkDebuggerEvents) { + throw new NotImplementedException(); + } + + public int UnadviseDebuggerEvents(uint dwCookie) { + _sinks.Remove(dwCookie); + return VSConstants.S_OK; + } + } +} diff --git a/src/Mocks/VisualStudio/VsUiShellMock.cs b/src/Mocks/VisualStudio/VsUiShellMock.cs index 49f05fb5e..1ab925297 100644 --- a/src/Mocks/VisualStudio/VsUiShellMock.cs +++ b/src/Mocks/VisualStudio/VsUiShellMock.cs @@ -22,7 +22,7 @@ public int CreateDocumentWindow(uint grfCDW, string pszMkDocument, IVsUIHierarch } public int CreateToolWindow(uint grfCTW, uint dwToolWindowId, object punkTool, ref Guid rclsidTool, ref Guid rguidPersistenceSlot, ref Guid rguidAutoActivate, OLE.Interop.IServiceProvider psp, string pszCaption, int[] pfDefaultPosition, out IVsWindowFrame ppWindowFrame) { - var mock = new VsWindowFrameMock(); + var mock = new VsWindowFrameMock(pszCaption); _frames[rguidPersistenceSlot] = mock; ppWindowFrame = mock; return VSConstants.S_OK; @@ -35,8 +35,13 @@ public int EnableModeless(int fEnable) { public int FindToolWindow(uint grfFTW, ref Guid rguidPersistenceSlot, out IVsWindowFrame ppWindowFrame) { VsWindowFrameMock mock; _frames.TryGetValue(rguidPersistenceSlot, out mock); - ppWindowFrame = mock; - return mock != null ? VSConstants.S_OK : VSConstants.E_FAIL; + if (mock == null && grfFTW == (uint)__VSFINDTOOLWIN.FTW_fForceCreate) { + Guid g = Guid.Empty; + CreateToolWindow(0, 1, null, ref g, ref rguidPersistenceSlot, ref g, null, string.Empty, null, out ppWindowFrame); + } else { + ppWindowFrame = mock; + } + return ppWindowFrame != null ? VSConstants.S_OK : VSConstants.E_FAIL; } public int FindToolWindowEx(uint grfFTW, ref Guid rguidPersistenceSlot, uint dwToolWinId, out IVsWindowFrame ppWindowFrame) { @@ -87,7 +92,8 @@ public int GetSaveFileNameViaDlg(VSSAVEFILENAMEW[] pSaveFileName) { } public int GetToolWindowEnum(out IEnumWindowFrames ppenum) { - throw new NotImplementedException(); + ppenum = new EnumWindowFramesMock(new List(_frames.Values)); + return VSConstants.S_OK; } public int GetURLViaDlg(string pszDlgTitle, string pszStaticLabel, string pszHelpTopic, out string pbstrURL) { diff --git a/src/Mocks/VisualStudio/VsWindowFrameMock.cs b/src/Mocks/VisualStudio/VsWindowFrameMock.cs index 0932eefaf..f091706ad 100644 --- a/src/Mocks/VisualStudio/VsWindowFrameMock.cs +++ b/src/Mocks/VisualStudio/VsWindowFrameMock.cs @@ -5,6 +5,14 @@ namespace Microsoft.VisualStudio.Shell.Mocks { [ExcludeFromCodeCoverage] public sealed class VsWindowFrameMock : IVsWindowFrame, IVsWindowFrame2 { + private string _caption; + private bool _visible; + + public VsWindowFrameMock(string caption) { + _caption = caption; + _visible = true; + } + #region IVsWindowFrame public int CloseFrame(uint grfSaveOptions) { return VSConstants.S_OK; @@ -25,6 +33,8 @@ public int GetGuidProperty(int propid, out Guid pguid) { public int GetProperty(int propid, out object pvar) { if (propid == (int)__VSFPROPID.VSFPROPID_ExtWindowObject) { pvar = new VsToolWindowToolbarHostMock(); + } else if (propid == (int)__VSFPROPID.VSFPROPID_Caption) { + pvar = _caption; } else { pvar = null; } @@ -32,6 +42,7 @@ public int GetProperty(int propid, out object pvar) { } public int Hide() { + _visible = false; return VSConstants.S_OK; } @@ -41,7 +52,7 @@ public int IsOnScreen(out int pfOnScreen) { } public int IsVisible() { - return VSConstants.S_OK; + return _visible ? VSConstants.S_OK : VSConstants.S_FALSE; } public int QueryViewInterface(ref Guid riid, out IntPtr ppv) { @@ -62,10 +73,12 @@ public int SetProperty(int propid, object var) { } public int Show() { + _visible = true; return VSConstants.S_OK; } public int ShowNoActivate() { + _visible = true; return VSConstants.S_OK; } #endregion diff --git a/src/Package/Impl/Commands/Markdown/VsMdCommandFactory.cs b/src/Package/Impl/Commands/Markdown/VsMdCommandFactory.cs index b1ac4ede0..d0f1bac79 100644 --- a/src/Package/Impl/Commands/Markdown/VsMdCommandFactory.cs +++ b/src/Package/Impl/Commands/Markdown/VsMdCommandFactory.cs @@ -4,6 +4,7 @@ using Microsoft.Markdown.Editor.ContentTypes; using Microsoft.VisualStudio.R.Package.Commands.Markdown; using Microsoft.VisualStudio.R.Package.Publishing.Commands; +using Microsoft.VisualStudio.R.Package.Repl.Commands; using Microsoft.VisualStudio.R.Packages.Markdown; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; @@ -19,6 +20,7 @@ public IEnumerable GetCommands(ITextView textView, ITextBuffer textBuf commands.Add(new PreviewHtmlCommand(textView)); commands.Add(new PreviewPdfCommand(textView)); commands.Add(new PreviewWordCommand(textView)); + commands.Add(new SendToReplCommand(textView)); commands.Add(new ShowContextMenuCommand(textView, MdGuidList.MdPackageGuid, MdGuidList.MdCmdSetGuid, (int)MarkdownContextMenuId.MD)); return commands; } diff --git a/src/Package/Impl/Commands/R/RPackageCommandId.cs b/src/Package/Impl/Commands/R/RPackageCommandId.cs index ea3b660cb..14d6f2c8a 100644 --- a/src/Package/Impl/Commands/R/RPackageCommandId.cs +++ b/src/Package/Impl/Commands/R/RPackageCommandId.cs @@ -16,7 +16,7 @@ public static class RPackageCommandId { public const int icmdLoadWorkspace = 502; public const int icmdSaveWorkspace = 503; public const int icmdSelectWorkingDirectory = 504; - public const int icmdRestartR = 505; + public const int icmdResetRepl = 505; public const int icmdInterruptR = 506; public const int icmdAttachDebugger = 507; public const int icmdSourceRScript = 508; @@ -27,6 +27,7 @@ public static class RPackageCommandId { public const int icmdStepInto = 513; public const int icmdStepOut = 514; public const int icmdStepOver = 515; + public const int icmdAttachToRInteractive = 516; public const int icmdRexecuteReplCmd = 571; public const int icmdPasteReplCmd = 572; @@ -36,20 +37,12 @@ public static class RPackageCommandId { public const int icmdCheckForPackageUpdates = 602; // Plots - //public const int icmdOpenPlot = 701; - //public const int icmdSavePlot = 702; - //public const int icmdFixPlot = 703; - public const int icmdExportPlot = 704; public const int icmdPrintPlot = 705; - //public const int icmdCopyPlot = 707; - //public const int icmdZoomInPlot = 708; - //public const int icmdZoomOutPlot = 709; public const int icmdNextPlot = 710; public const int icmdPrevPlot = 711; public const int icmdClearPlots = 712; - public const int icmdExportPlotAsPng = 713; + public const int icmdExportPlotAsImage = 713; public const int icmdExportPlotAsPdf = 714; - public const int icmdExportPlotAsBitmap = 715; public const int icmdCopyPlotAsBitmap = 716; public const int icmdCopyPlotAsMetafile = 717; @@ -87,5 +80,8 @@ public static class RPackageCommandId { public const int icmdDeleteSelectedHistoryEntries = 1204; public const int icmdDeleteAllHistoryEntries = 1205; public const int icmdToggleMultilineSelection = 1206; + + // Debugger + public const int icmdShowDotPrefixedVariables = 1300; } } diff --git a/src/Package/Impl/Commands/R/SourceRScriptCommand.cs b/src/Package/Impl/Commands/R/SourceRScriptCommand.cs deleted file mode 100644 index ad0b092c8..000000000 --- a/src/Package/Impl/Commands/R/SourceRScriptCommand.cs +++ /dev/null @@ -1,77 +0,0 @@ -using System; -using Microsoft.Common.Core; -using Microsoft.Languages.Editor; -using Microsoft.Languages.Editor.Controller.Command; -using Microsoft.R.Host.Client; -using Microsoft.VisualStudio.R.Package.Repl; -using Microsoft.VisualStudio.R.Package.Shell; -using Microsoft.VisualStudio.R.Package.Utilities; -using Microsoft.VisualStudio.R.Packages.R; -using Microsoft.VisualStudio.Shell.Interop; -using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Text.Editor; - -namespace Microsoft.VisualStudio.R.Package.Commands { - public sealed class SourceRScriptCommand : ViewCommand { - private static readonly CommandId[] Commands = { - new CommandId(RGuidList.RCmdSetGuid, RPackageCommandId.icmdSourceRScript) - }; - - private readonly ReplWindow _replWindow; - private readonly IVsMonitorSelection _monitorSelection; - private readonly uint _debugUIContextCookie; - - public SourceRScriptCommand(ITextView textView) - : base(textView, Commands, false) { - ReplWindow.EnsureReplWindow().DoNotWait(); - _replWindow = ReplWindow.Current; - - _monitorSelection = VsAppShell.Current.GetGlobalService(typeof(SVsShellMonitorSelection)); - if (_monitorSelection != null) { - var debugUIContextGuid = new Guid(UIContextGuids.Debugging); - if (ErrorHandler.Failed(_monitorSelection.GetCmdUIContextCookie(ref debugUIContextGuid, out _debugUIContextCookie))) { - _monitorSelection = null; - } - } - } - - private bool IsDebugging() { - if (_monitorSelection == null) { - return false; - } - - int fActive; - if (ErrorHandler.Succeeded(_monitorSelection.IsCmdUIContextActive(_debugUIContextCookie, out fActive))) { - return fActive != 0; - } - - return false; - } - - private string GetFilePath() { - ITextDocument document; - if (TextView.TextBuffer.Properties.TryGetProperty(typeof(ITextDocument), out document)) { - return document.FilePath; - } - - return null; - } - - public override CommandStatus Status(Guid group, int id) { - return GetFilePath() != null ? CommandStatus.SupportedAndEnabled : CommandStatus.NotSupported; - } - - public override CommandResult Invoke(Guid group, int id, object inputArg, ref object outputArg) { - string filePath = GetFilePath(); - if (filePath == null) { - return CommandResult.NotSupported; - } - - // Save file before sourcing - TextView.SaveFile(); - - _replWindow.ExecuteCode($"{(IsDebugging() ? "rtvs::debug_source" : "source")}({filePath.ToRStringLiteral()})"); - return CommandResult.Executed; - } - } -} diff --git a/src/Package/Impl/Commands/R/VsRCommandFactory.cs b/src/Package/Impl/Commands/R/VsRCommandFactory.cs index 977ca16b5..be4aff1b8 100644 --- a/src/Package/Impl/Commands/R/VsRCommandFactory.cs +++ b/src/Package/Impl/Commands/R/VsRCommandFactory.cs @@ -17,7 +17,6 @@ public IEnumerable GetCommands(ITextView textView, ITextBuffer textBuf commands.Add(new ShowContextMenuCommand(textView, RGuidList.RPackageGuid, RGuidList.RCmdSetGuid, (int)RContextMenuId.R)); commands.Add(new SendToReplCommand(textView)); - commands.Add(new SourceRScriptCommand(textView)); commands.Add(new GoToFormattingOptionsCommand(textView, textBuffer)); commands.Add(new WorkingDirectoryCommand()); diff --git a/src/Package/Impl/Commands/RHistory/VsRHistoryCommandFactory.cs b/src/Package/Impl/Commands/RHistory/VsRHistoryCommandFactory.cs index 3f11b3885..884a3f2a6 100644 --- a/src/Package/Impl/Commands/RHistory/VsRHistoryCommandFactory.cs +++ b/src/Package/Impl/Commands/RHistory/VsRHistoryCommandFactory.cs @@ -40,7 +40,6 @@ public IEnumerable GetCommands(ITextView textView, ITextBuffer textBuf var sendToSourceCommand = new SendHistoryToSourceCommand(textView, _historyProvider, _contentTypeRegistry, _textViewTracker); return new ICommand[] { - new ShowContextMenuCommand(textView, RGuidList.RPackageGuid, RGuidList.RCmdSetGuid, (int)RContextMenuId.RHistory), new LoadHistoryCommand(textView, _historyProvider), new SaveHistoryCommand(textView, _historyProvider), sendToReplCommand, @@ -49,6 +48,12 @@ public IEnumerable GetCommands(ITextView textView, ITextBuffer textBuf new DeleteAllHistoryEntriesCommand(textView, _historyProvider), new HistoryWindowVsStd2KCmdIdReturnCommand(textView, sendToReplCommand, sendToSourceCommand), new HistoryWindowVsStd97CmdIdSelectAllCommand(textView, _historyProvider), + new HistoryWindowVsStd2KCmdIdUp(textView, _historyProvider), + new HistoryWindowVsStd2KCmdIdDown(textView, _historyProvider), + new HistoryWindowVsStd2KCmdIdHome(textView, _historyProvider), + new HistoryWindowVsStd2KCmdIdEnd(textView, _historyProvider), + new HistoryWindowVsStd2KCmdIdPageUp(textView, _historyProvider), + new HistoryWindowVsStd2KCmdIdPageDown(textView, _historyProvider), new ToggleMultilineHistorySelectionCommand(textView, _historyProvider, RToolsSettings.Current), new CopySelectedHistoryCommand(textView, _historyProvider, _editorOperationsService) }; diff --git a/src/Package/Impl/Commands/ShowToolWindowCommand.cs b/src/Package/Impl/Commands/ShowToolWindowCommand.cs index 8959eed12..f614852d3 100644 --- a/src/Package/Impl/Commands/ShowToolWindowCommand.cs +++ b/src/Package/Impl/Commands/ShowToolWindowCommand.cs @@ -10,12 +10,12 @@ public ShowToolWindowCommand(int id) : base(RGuidList.RCmdSetGuid, id) {} - protected override void SetStatus() { + internal override void SetStatus() { Visible = true; Enabled = true; } - protected override void Handle() { + internal override void Handle() { ToolWindowUtilities.ShowWindowPane(0, true); } } diff --git a/src/Package/Impl/DataInspect/BindableBase.cs b/src/Package/Impl/DataInspect/BindableBase.cs index 37ce2e8b7..6a0c09e62 100644 --- a/src/Package/Impl/DataInspect/BindableBase.cs +++ b/src/Package/Impl/DataInspect/BindableBase.cs @@ -8,7 +8,7 @@ namespace Microsoft.VisualStudio.R.Package.DataInspect { /// /// Implementation of to simplify models. /// - public abstract class BindableBase : INotifyPropertyChanged // TODO: move to common + public abstract class BindableBase : INotifyPropertyChanged { /// /// Occurs when a property value changes. diff --git a/src/Package/Impl/DataInspect/Commands/ShowVariableWindowCommand.cs b/src/Package/Impl/DataInspect/Commands/ShowVariableWindowCommand.cs index d3793ad1f..ce2991b4a 100644 --- a/src/Package/Impl/DataInspect/Commands/ShowVariableWindowCommand.cs +++ b/src/Package/Impl/DataInspect/Commands/ShowVariableWindowCommand.cs @@ -7,7 +7,7 @@ internal sealed class ShowVariableWindowCommand : PackageCommand { public ShowVariableWindowCommand() : base(RGuidList.RCmdSetGuid, RPackageCommandId.icmdShowVariableExplorerWindow) { } - protected override void Handle() { + internal override void Handle() { ToolWindowUtilities.ShowWindowPane(0, true); } } diff --git a/src/Package/Impl/DataInspect/DataSource/DelegateList.cs b/src/Package/Impl/DataInspect/DataSource/DelegateList.cs index 54ffb2f6d..cbbeb9146 100644 --- a/src/Package/Impl/DataInspect/DataSource/DelegateList.cs +++ b/src/Package/Impl/DataInspect/DataSource/DelegateList.cs @@ -1,6 +1,7 @@ using System; using System.Collections; using System.Collections.Generic; +using static System.FormattableString; namespace Microsoft.VisualStudio.R.Package.DataInspect { /// @@ -10,7 +11,7 @@ namespace Microsoft.VisualStudio.R.Package.DataInspect { public class DelegateList : IList, IList, IIndexedItem where T : IIndexedItem { #region field and ctor - private readonly string ReadOnlyExceptionMessage = $"{typeof(DelegateList)} is read only"; + private readonly string ReadOnlyExceptionMessage = Invariant($"{typeof(DelegateList)} is read only"); private Func _getItem; public DelegateList( diff --git a/src/Package/Impl/DataInspect/DataSource/GridDataSource.cs b/src/Package/Impl/DataInspect/DataSource/GridDataSource.cs new file mode 100644 index 000000000..f89eee69f --- /dev/null +++ b/src/Package/Impl/DataInspect/DataSource/GridDataSource.cs @@ -0,0 +1,48 @@ +using System; +using System.Threading.Tasks; +using Microsoft.Common.Core; +using Microsoft.R.Host.Client; +using Microsoft.VisualStudio.R.Package.Repl; +using Microsoft.VisualStudio.R.Package.Shell; +using static System.FormattableString; + +namespace Microsoft.VisualStudio.R.Package.DataInspect.DataSource { + public class GridDataSource { + public static async Task> GetGridDataAsync(string expression, GridRange gridRange, IRSession rSession = null) { + await TaskUtilities.SwitchToBackgroundThread(); + + if (rSession == null) { + rSession = VsAppShell.Current.ExportProvider.GetExportedValue().GetInteractiveWindowRSession(); + if (rSession == null) { + throw new InvalidOperationException(Invariant($"{nameof(IRSessionProvider)} failed to return RSession for {nameof(EvaluationWrapper)}")); + } + } + + string rows = gridRange.Rows.ToRString(); + string columns = gridRange.Columns.ToRString(); + + REvaluationResult? result = null; + using (var evaluator = await rSession.BeginEvaluationAsync(false)) { + result = await evaluator.EvaluateAsync($"rtvs:::grid.dput(rtvs:::grid.data({expression}, {rows}, {columns}))", REvaluationKind.Normal); + + if (result.Value.ParseStatus != RParseStatus.OK || result.Value.Error != null) { + throw new InvalidOperationException($"Grid data evaluation failed:{result}"); + } + } + + GridData data = null; + + if (result.HasValue) { + data = GridParser.Parse(result.Value.StringResult); + data.Range = gridRange; + + if ((data.ValidHeaderNames.HasFlag(GridData.HeaderNames.Row) && data.RowNames.Count != gridRange.Rows.Count) + || (data.ValidHeaderNames.HasFlag(GridData.HeaderNames.Column) && data.ColumnNames.Count != gridRange.Columns.Count)) { + throw new InvalidOperationException("Header names lengths are different from data's length"); + } + } + + return data; + } + } +} diff --git a/src/Package/Impl/DataInspect/DataSource/IGridProvider.cs b/src/Package/Impl/DataInspect/DataSource/IGridProvider.cs index 34b931c0c..7e9b765d3 100644 --- a/src/Package/Impl/DataInspect/DataSource/IGridProvider.cs +++ b/src/Package/Impl/DataInspect/DataSource/IGridProvider.cs @@ -25,11 +25,6 @@ public interface IGridProvider { /// int ColumnCount { get; } - /// - /// Returns portion of data - /// - Task> GetRangeAsync(GridRange gridRange); - Task> GetAsync(GridRange range); } } diff --git a/src/Package/Impl/DataInspect/DataSource/IListProvider.cs b/src/Package/Impl/DataInspect/DataSource/IListProvider.cs deleted file mode 100644 index 73e0e8007..000000000 --- a/src/Package/Impl/DataInspect/DataSource/IListProvider.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; - -namespace Microsoft.VisualStudio.R.Package.DataInspect { - /// - /// One dimensional data provider - /// - /// data type - public interface IListProvider { - /// - /// total number of items - /// - int Count { get; } - - /// - /// returns portion of data - /// - Task> GetRangeAsync(Range range); - } -} diff --git a/src/Package/Impl/DataInspect/DataSource/Page.cs b/src/Package/Impl/DataInspect/DataSource/Page.cs deleted file mode 100644 index dcfab5467..000000000 --- a/src/Package/Impl/DataInspect/DataSource/Page.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; - -namespace Microsoft.VisualStudio.R.Package.DataInspect { - internal class Page { - private List> _list; - - public Page(int pageNumber, Range range) { - PageNumber = pageNumber; - Range = range; - Node = new LinkedListNode>(this); - LastAccessTime = DateTime.MinValue; - - List> list = new List>(range.Count); - for (int c = 0; c < range.Count; c++) { - list.Add(new PageItem(range.Start + c)); - } - _list = new List>(list); - } - - public int PageNumber { get; } - - public Range Range { get; } - - public LinkedListNode> Node; - - public DateTime LastAccessTime { get; set; } - - public PageItem GetItem(int index) { - Debug.Assert(Range.Contains(index)); - - return _list[index - Range.Start]; - } - - internal void PopulateData(IList data) { - if (data.Count != Range.Count) { - throw new ArgumentException("Input data doesn't match with page count"); - } - - for (int r = 0; r < data.Count; r++) { - _list[r].Data = data[r]; - } - } - } -} diff --git a/src/Package/Impl/DataInspect/DataSource/Page2D.cs b/src/Package/Impl/DataInspect/DataSource/Page2D.cs deleted file mode 100644 index 686bcd40a..000000000 --- a/src/Package/Impl/DataInspect/DataSource/Page2D.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; - -namespace Microsoft.VisualStudio.R.Package.DataInspect { - internal class Page2D { - private Grid> _grid; - - public Page2D(PageNumber pageNumber, GridRange range) { - PageNumber = pageNumber; - Range = range; - Node = new LinkedListNode>(this); - LastAccessTime = DateTime.UtcNow; - - _grid = new Grid>( - range, - (r, c) => new PageItem(c)); - } - - public PageNumber PageNumber { get; } - - public GridRange Range { get; } - - public LinkedListNode> Node; - - public DateTime LastAccessTime { get; set; } - - public PageItem GetItem(int row, int column) { - return _grid[row, column]; - } - - internal void PopulateData(IGrid data) { - if (!data.Range.Equals(Range)) { - throw new ArgumentException("Input data doesn't match with page's row or column counts"); - } - - foreach (int r in data.Range.Rows.GetEnumerable()) { - foreach (int c in data.Range.Columns.GetEnumerable()) { - _grid[r, c].Data = data[r, c]; - } - } - } - } -} diff --git a/src/Package/Impl/DataInspect/DataSource/Page2DManager.cs b/src/Package/Impl/DataInspect/DataSource/Page2DManager.cs deleted file mode 100644 index fb82ca337..000000000 --- a/src/Package/Impl/DataInspect/DataSource/Page2DManager.cs +++ /dev/null @@ -1,246 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.Common.Core; - -namespace Microsoft.VisualStudio.R.Package.DataInspect { - - public class Page2DManager { - #region synchronized by _syncObj - - private readonly object _syncObj = new object(); - private Dictionary>> _banks = new Dictionary>>(); - private Queue> _requests = new Queue>(); - private Task _loadTask = null; - - private Dictionary>> _rows = new Dictionary>>(); - - #endregion - - private IGridProvider _itemsProvider; - - public Page2DManager(IGridProvider itemsProvider, int pageSize, TimeSpan timeout, int keepPageCount) { - if (itemsProvider == null) { - throw new ArgumentNullException("itemsProvider"); - } - if (pageSize <= 0) { - throw new ArgumentOutOfRangeException("pageSize"); - } - if (keepPageCount <= 0) { - throw new ArgumentOutOfRangeException("keepPageCount"); - } - - _itemsProvider = itemsProvider; - - PageSize = pageSize; - Timeout = timeout; - KeepPageCount = keepPageCount; - } - - public int RowCount { get { return _itemsProvider.RowCount; } } - - public int ColumnCount { get { return _itemsProvider.ColumnCount; } } - - public int KeepPageCount { get; } - - public int PageSize { get; } - - public TimeSpan Timeout { get; } - - public bool WaitForItemRealization { get { return true; } } - - public PageItem GetItem(int row, int column) { - bool shouldWait = false; - Page2D foundPage = null; - Task waitTask; - - lock (_syncObj) { - int rowPageNumber, columnPageNumber; - ComputePageNumber(row, column, out rowPageNumber, out columnPageNumber); - - Dictionary> bank; - - if (_banks.TryGetValue(rowPageNumber, out bank)) { - if (!bank.TryGetValue(columnPageNumber, out foundPage)) { - foundPage = CreateEmptyPage(new PageNumber(rowPageNumber, columnPageNumber)); - shouldWait = WaitForItemRealization; - bank.Add(columnPageNumber, foundPage); - } - } else { - bank = new Dictionary>(); - foundPage = CreateEmptyPage(new PageNumber(rowPageNumber, columnPageNumber)); - shouldWait = WaitForItemRealization; - bank.Add(columnPageNumber, foundPage); - _banks.Add(rowPageNumber, bank); - } - - if (shouldWait) { - int firstRow = foundPage.Range.Rows.Start; - int firstColumn = foundPage.Range.Columns.Start; - var pageItem = foundPage.GetItem(firstRow, firstColumn); - waitTask = WaitPropertyChangeAsync(pageItem); - } else { - waitTask = Task.FromResult(null); - } - } - - waitTask.Wait(TimeSpan.FromMilliseconds(1000)); - - return foundPage.GetItem(row, column); - } - - private static async Task WaitPropertyChangeAsync(PageItem pageItem) { - var tcs = new TaskCompletionSource(); - PropertyChangedEventHandler handler = (sender, args) => tcs.TrySetResult(null); - try { - pageItem.PropertyChanged += handler; - await TaskUtilities.SwitchToBackgroundThread(); - await tcs.Task; - } finally { - pageItem.PropertyChanged -= handler; - } - } - - public DelegateList>> GetItemsSource() { - var itemsSource = new DelegateList>>( - 0, - (row) => GetRow(row), - RowCount); - - return itemsSource; - } - - public DelegateList> GetRow(int row) { - DelegateList> list; - lock (_syncObj) { - if (_rows.TryGetValue(row, out list)) { - return list; - } - list = new DelegateList>( - row, - (column) => GetItem(row, column), - ColumnCount); - _rows.Add(row, list); - return list; - } - } - - private Page2D CreateEmptyPage(PageNumber pageNumber) { - int rowStart, rowCount; - GetPageInfo(pageNumber.Row, RowCount, out rowStart, out rowCount); - - int columnStart, columnCount; - GetPageInfo(pageNumber.Column, ColumnCount, out columnStart, out columnCount); - - var range = new GridRange( - new Range(rowStart, rowCount), // row - new Range(columnStart, columnCount)); // column - - var page = new Page2D(pageNumber, range); - - lock (_syncObj) { - _requests.Enqueue(page); - EnsureLoadTask(); - } - - return page; - } - - private void ComputePageNumber(int row, int column, out int rowPageNumber, out int columnPageNumber) { - rowPageNumber = row / PageSize; - columnPageNumber = column / PageSize; - } - - private GridRange GetPageRange(PageNumber pageNumber) { - int start, count; - - GetPageInfo(pageNumber.Row, RowCount, out start, out count); - Range row = new Range(start, count); - - GetPageInfo(pageNumber.Column, ColumnCount, out start, out count); - Range column = new Range(start, count); - - return new GridRange(row, column); - } - - private void GetPageInfo(int pageNumber, int itemCount, out int pageStartIndex, out int pageSize) { - pageStartIndex = pageNumber * PageSize; - - int end = pageStartIndex + PageSize; - int count = itemCount; - if (end > count) { // last page - pageSize = count - pageStartIndex; - } else { - pageSize = PageSize; - } - } - - private void EnsureLoadTask() { - if (_loadTask == null) { - lock (_syncObj) { - if (_loadTask == null) { - _loadTask = Task.Run(async () => await LoadAndCleanPagesAsync()); - } - } - } - } - - private async Task LoadAndCleanPagesAsync() { - TaskUtilities.AssertIsOnBackgroundThread(); - - bool cleanHasRun = false; - while (true) { - Page2D page = null; - lock (_syncObj) { - if (_requests.Count == 0) { - if (cleanHasRun) { - _loadTask = null; - break; - } else { - - } - } else { - page = _requests.Dequeue(); - Debug.Assert(page != null); - } - } - - if (page != null) { - IGrid data = await _itemsProvider.GetRangeAsync(page.Range); - - page.PopulateData(data); - } else { - CleanOldPages(); - cleanHasRun = true; - } - } - } - - private void CleanOldPages() { - foreach (var bank in _banks.Values) { - while (bank.Count > KeepPageCount) { - DateTime lastTime = DateTime.UtcNow - Timeout; - IEnumerable>> toRemove; - - lock (_syncObj) { - toRemove = bank.Where(kv => (kv.Value.LastAccessTime < lastTime) && kv.Key != 0).ToList(); - if (toRemove.Count() <= 0) { - break; - } - } - - foreach (var item in toRemove) { - Trace.WriteLine(string.Format("Removing:Page:{0}", item.Value.PageNumber)); - lock (_syncObj) { - bank.Remove(item.Key); - } - // TODO: release hint - } - } - } - } - } -} diff --git a/src/Package/Impl/DataInspect/DataSource/PageItem.cs b/src/Package/Impl/DataInspect/DataSource/PageItem.cs deleted file mode 100644 index 24735474a..000000000 --- a/src/Package/Impl/DataInspect/DataSource/PageItem.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel; -using System.Runtime.CompilerServices; -using System.Windows.Threading; -using Microsoft.Common.Core; -using Microsoft.VisualStudio.Shell; - -namespace Microsoft.VisualStudio.R.Package.DataInspect { - /// - /// Item in Page. It implements for , which will fire when realized - /// - public class PageItem : IIndexedItem, INotifyPropertyChanged { - public PageItem(int index = -1) { - Index = index; - } - - public int Index { get; } - - private TData _data; - public TData Data { - get { return _data; } - set { - SetValue(ref _data, value); - } - } - - public event PropertyChangedEventHandler PropertyChanged; - - protected virtual bool SetValue(ref T storage, T value, [CallerMemberName]string propertyName = null) { - if (EqualityComparer.Default.Equals(storage, value)) { - return false; - } - - storage = value; - this.OnPropertyChanged(propertyName); - - return true; - } - - protected void OnPropertyChanged(string propertyName) { - var eventHandler = this.PropertyChanged; - if (eventHandler != null) { - eventHandler(this, new PropertyChangedEventArgs(propertyName)); - } - } - - public override string ToString() { - if (_data != null) { - return _data.ToString(); - } - return Index.ToString(); - } - } -} diff --git a/src/Package/Impl/DataInspect/DataSource/PageManager.cs b/src/Package/Impl/DataInspect/DataSource/PageManager.cs deleted file mode 100644 index 50f0f8587..000000000 --- a/src/Package/Impl/DataInspect/DataSource/PageManager.cs +++ /dev/null @@ -1,150 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Threading.Tasks; - -namespace Microsoft.VisualStudio.R.Package.DataInspect { - public class PageManager { - #region synchronized by _syncObj - - private readonly object _syncObj = new object(); - private Dictionary> _banks = new Dictionary>(); - private Queue> _requests = new Queue>(); - private Task _loadTask = null; - - #endregion - - private IListProvider _itemsProvider; - - public PageManager(IListProvider itemsProvider, int pageSize, TimeSpan timeout, int keepPageCount) { - if (itemsProvider == null) { - throw new ArgumentNullException("itemsProvider"); - } - if (pageSize <= 0) { - throw new ArgumentOutOfRangeException("pageSize"); - } - if (keepPageCount <= 0) { - throw new ArgumentOutOfRangeException("keepPageCount"); - } - - _itemsProvider = itemsProvider; - - PageSize = pageSize; - Timeout = timeout; - KeepPageCount = keepPageCount; - } - - public int Count { get { return _itemsProvider.Count; } } - - public int KeepPageCount { get; } - - public int PageSize { get; } - - public TimeSpan Timeout { get; } - - public PageItem GetItem(int index) { - lock (_syncObj) { - int pageNumber = index / PageSize; - - Page foundPage; - if (!_banks.TryGetValue(pageNumber, out foundPage)) { - foundPage = CreateEmptyPage(pageNumber); - _banks.Add(pageNumber, foundPage); - } - - return foundPage.GetItem(index); - } - } - - private Page CreateEmptyPage(int pageNumber) { - int start, count; - GetPageInfo(pageNumber, Count, out start, out count); - - var range = new Range(start, count); - - var page = new Page(pageNumber, range); - - lock (_syncObj) { - _requests.Enqueue(page); - EnsureLoadTask(); - } - - return page; - } - - private void ComputePageNumber(int row, int column, out int rowPageNumber, out int columnPageNumber) { - rowPageNumber = row / PageSize; - columnPageNumber = column / PageSize; - } - - private void GetPageInfo(int pageNumber, int itemCount, out int pageStartIndex, out int pageSize) { - pageStartIndex = pageNumber * PageSize; - - int end = pageStartIndex + PageSize; - int count = itemCount; - if (end > count) { // last page - pageSize = count - pageStartIndex; - } else { - pageSize = PageSize; - } - } - - private void EnsureLoadTask() { - if (_loadTask == null) { - lock (_syncObj) { - if (_loadTask == null) { - _loadTask = Task.Run(async () => await LoadAndCleanPagesAsync()); - } - } - } - } - - private async Task LoadAndCleanPagesAsync() { - bool cleanHasRun = false; - while (true) { - Page page = null; - lock (_syncObj) { - if (_requests.Count == 0) { - if (cleanHasRun) { - _loadTask = null; - return; - } else { - - } - } else { - page = _requests.Dequeue(); - Debug.Assert(page != null); - } - } - - if (page != null) { - IList data = await _itemsProvider.GetRangeAsync(page.Range); - - page.PopulateData(data); - } else { - CleanOldPages(); - cleanHasRun = true; - } - } - } - - private void CleanOldPages() { - while (_banks.Count > KeepPageCount) { - DateTime lastTime = DateTime.UtcNow - Timeout; - IEnumerable>> toRemove; - - lock (_syncObj) { - toRemove = _banks.Where(kv => (kv.Value.LastAccessTime < lastTime)).ToList(); - } - - foreach (var item in toRemove) { - lock (_syncObj) { - _banks.Remove(item.Key); - } - // TODO: release hint - } - } - } - } -} diff --git a/src/Package/Impl/DataInspect/DataSource/PageNumber.cs b/src/Package/Impl/DataInspect/DataSource/PageNumber.cs deleted file mode 100644 index ad092846d..000000000 --- a/src/Package/Impl/DataInspect/DataSource/PageNumber.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Microsoft.VisualStudio.R.Package.DataInspect { - internal struct PageNumber { - public PageNumber(int row, int column) { - Row = row; - Column = column; - } - - public int Row { get; } - - public int Column { get; } - } -} diff --git a/src/Package/Impl/DataInspect/DataSource/Range.cs b/src/Package/Impl/DataInspect/DataSource/Range.cs index 14d74f99b..92edb7b1e 100644 --- a/src/Package/Impl/DataInspect/DataSource/Range.cs +++ b/src/Package/Impl/DataInspect/DataSource/Range.cs @@ -1,9 +1,12 @@ using System.Collections.Generic; +using System.Diagnostics; +using static System.FormattableString; namespace Microsoft.VisualStudio.R.Package.DataInspect { /// /// Range of integers /// + [DebuggerDisplay("[{Start},{_end})")] public struct Range { int _end; @@ -38,5 +41,9 @@ public IEnumerable GetEnumerable(bool ascending = true) { } } } + + public string ToRString() { + return Invariant($"{Start + 1}:{Start + Count}"); + } } } diff --git a/src/Package/Impl/DataInspect/DebugGridViewProvider.cs b/src/Package/Impl/DataInspect/DebugGridViewProvider.cs new file mode 100644 index 000000000..e8f30a50a --- /dev/null +++ b/src/Package/Impl/DataInspect/DebugGridViewProvider.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.R.Debugger; +using Microsoft.R.Debugger.Engine; + +namespace Microsoft.VisualStudio.R.Package.DataInspect { + [Export(typeof(IDebugGridViewProvider))] + internal class DebugGridViewProvider : IDebugGridViewProvider { + public bool CanShowDataGrid(DebugEvaluationResult evaluationResult) { + var wrapper = new EvaluationWrapper(evaluationResult); + return wrapper.CanShowDetail; + } + + public void ShowDataGrid(DebugEvaluationResult evaluationResult) { + var wrapper = new EvaluationWrapper(evaluationResult); + if (!wrapper.CanShowDetail) { + throw new InvalidOperationException("Cannot show data grid on evaluation result " + evaluationResult); + } + wrapper.ShowDetailCommand.Execute(null); + } + } +} diff --git a/src/Package/Impl/DataInspect/DefaultHeaderData.cs b/src/Package/Impl/DataInspect/DefaultHeaderData.cs index fbe1aaad9..b28bbe5b9 100644 --- a/src/Package/Impl/DataInspect/DefaultHeaderData.cs +++ b/src/Package/Impl/DataInspect/DefaultHeaderData.cs @@ -1,4 +1,5 @@ using System; +using static System.FormattableString; namespace Microsoft.VisualStudio.R.Package.DataInspect { public class DefaultHeaderData : IRange { @@ -15,11 +16,16 @@ public DefaultHeaderData(Range range, Mode columnMode) { public string this[int index] { get { - int rIndex = index + 1; // R index is 1-based + int rIndex = index; + + checked { + rIndex = index + 1; // R index is 1-based + } + if (_mode == Mode.Column) { - return string.Format("[,{0}]", rIndex); + return Invariant($"[,{rIndex}]"); } - return string.Format("[{0},]", rIndex); + return Invariant($"[{rIndex},]"); } set { diff --git a/src/Package/Impl/DataInspect/Definitions/IVariableDataProvider.cs b/src/Package/Impl/DataInspect/Definitions/IVariableDataProvider.cs new file mode 100644 index 000000000..fe571af25 --- /dev/null +++ b/src/Package/Impl/DataInspect/Definitions/IVariableDataProvider.cs @@ -0,0 +1,24 @@ +using System; +using Microsoft.R.Debugger; + +namespace Microsoft.VisualStudio.R.Package.DataInspect.Definitions { + /// + /// provides evaluation from R Host + /// + public interface IVariableDataProvider { + /// + /// register a callback when evaluation is available + /// + /// frame index to evaluation the expression + /// expression to evaluate + /// callback when evaluation is avilable + /// a subscription + VariableSubscription Subscribe(int frameIndex, string expression, Action executeAction); + + /// + /// unregister the subscription + /// + /// the subscription to quit + void Unsubscribe(VariableSubscription subscription); + } +} diff --git a/src/Package/Impl/DataInspect/DynamicGrid/DynamicGrid.cs b/src/Package/Impl/DataInspect/DynamicGrid/DynamicGrid.cs deleted file mode 100644 index eb7479939..000000000 --- a/src/Package/Impl/DataInspect/DynamicGrid/DynamicGrid.cs +++ /dev/null @@ -1,356 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Controls.Primitives; -using System.Windows.Data; -using System.Windows.Media; -using System.Windows.Shapes; -using Microsoft.Common.Core; -using Microsoft.VisualStudio.R.Package.Wpf; - -namespace Microsoft.VisualStudio.R.Package.DataInspect { - /// - /// A grid control that populates columns dynamically just like DataGrid's rows are loaded in stack panel - /// - /// This stacks rows vertically, and each row stacks cells horizontally. - /// Vertical scroll is handled by this controls's panel. - /// Horizontal scroll is handled by propagating horizontal scroll event to each row. - /// The source of horitonal scroll event comes from a scrollbar, named as HorizontalScrollBar in template. The scrollbar should stand alone outside scrollviewer. - /// - public class DynamicGrid : MultiSelector { - private LinkedList _realizedRows = new LinkedList(); - - static DynamicGrid() { - DefaultStyleKeyProperty.OverrideMetadata(typeof(DynamicGrid), new FrameworkPropertyMetadata(typeof(DynamicGrid))); - } - - #region DataSource - - public IList RowHeaderSource { get; set; } - - public IList ColumnHeaderSource { get; set; } - - protected override void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue) { - base.OnItemsSourceChanged(oldValue, newValue); - - if (ColumnHeadersPresenter != null) { - ColumnHeadersPresenter.ItemsSource = ColumnHeaderSource; - } - - foreach (var item in newValue) { - var rowSource = item as IList; - if (rowSource != null) { - if (rowSource.Count > 0) { - _layoutInfo = new SharedScrollInfo() { - FirstItemIndex = 0, - FirstItemOffset = 0.0, - MaxItemInViewport = ColumnHeaderSource == null ? 0 : ColumnHeaderSource.Count - }; - HorizontalOffset = 0; - _columns.Clear(); - } - } else { - throw new NotSupportedException($"{nameof(DynamicGrid)} supports only nested collection for ItemsSource"); - } - break; - } - } - - #endregion - - #region override - - private ScrollBar _horizontalScrollbar; - public override void OnApplyTemplate() { - base.OnApplyTemplate(); - - var scrollbar = GetTemplateChild("HorizontalScrollBar") as ScrollBar; - if (scrollbar != null) { - if (_horizontalScrollbar != null) { - _horizontalScrollbar.Scroll -= Scrollbar_Scroll; - } - - scrollbar.Scroll += Scrollbar_Scroll; - _horizontalScrollbar = scrollbar; - } - } - - protected override DependencyObject GetContainerForItemOverride() { - return new DynamicGridRow(); - } - - protected override void PrepareContainerForItemOverride(DependencyObject element, object item) { - base.PrepareContainerForItemOverride(element, item); - - DynamicGridRow row = (DynamicGridRow)element; - if (row.ParentGrid != this) { - _realizedRows.AddFirst(row.Track); // ObservableCollection.Replace cause this fail, as it has been added already. That's fine for now. - } - - row.Header = RowHeaderSource[Items.IndexOf(item)]; - row.Prepare(this, item); - } - - protected override void ClearContainerForItemOverride(DependencyObject element, object item) { - base.ClearContainerForItemOverride(element, item); - - DynamicGridRow row = (DynamicGridRow)element; - - row.Header = null; - if (row.ParentGrid == this) { - _realizedRows.Remove(row.Track); - } - row.CleanUp(this, item); - } - - #endregion override - - #region RowHeader - - public double RowHeaderActualWidth { - get { return (double)GetValue(RowHeaderActualWidthProperty); } - set { SetValue(RowHeaderActualWidthPropertyKey, value); } - } - - private static readonly DependencyPropertyKey RowHeaderActualWidthPropertyKey = - DependencyProperty.RegisterReadOnly(nameof(RowHeaderActualWidth), typeof(double), typeof(DynamicGrid), new FrameworkPropertyMetadata(0.0, new PropertyChangedCallback(OnNotifyRowHeaderPropertyChanged))); - - public static readonly DependencyProperty RowHeaderActualWidthProperty = RowHeaderActualWidthPropertyKey.DependencyProperty; - - private static void OnNotifyRowHeaderPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - ((DynamicGrid)d).OnNotifyRowHeaderPropertyChanged(); - } - - private void OnNotifyRowHeaderPropertyChanged() { - ColumnHeadersPresenter?.NotifyRowHeader(); - foreach (var row in _realizedRows) { - row.NotifyRowHeader(); - } - } - - #endregion - - #region ColumnHeader - - private DynamicGridRow _columnHeadersPresenter; - - internal DynamicGridRow ColumnHeadersPresenter { - get { - return _columnHeadersPresenter; - } - set { - _columnHeadersPresenter = value; - _columnHeadersPresenter.ItemsSource = ColumnHeaderSource; - } - } - - #endregion - - #region Grid line - - public static readonly DependencyProperty GridLinesBrushProperty = - DependencyProperty.Register( - nameof(GridLinesBrush), - typeof(Brush), - typeof(DynamicGrid), - new FrameworkPropertyMetadata(Brushes.Black)); - - public Brush GridLinesBrush - { - get { return (Brush)GetValue(GridLinesBrushProperty); } - set { SetValue(GridLinesBrushProperty, value); } - } - - public static readonly DependencyProperty HeaderLinesBrushProperty = - DependencyProperty.Register( - nameof(HeaderLinesBrush), - typeof(Brush), - typeof(DynamicGrid), - new FrameworkPropertyMetadata(Brushes.Black)); - - public Brush HeaderLinesBrush - { - get { return (Brush)GetValue(HeaderLinesBrushProperty); } - set { SetValue(HeaderLinesBrushProperty, value); } - } - - #endregion - - #region Columns and Horizontal scroll - - private SortedList _columns = new SortedList(); - internal MaxDouble GetColumnWidth(int index) { - MaxDouble stack; - if (_columns.TryGetValue(index, out stack)) { - return stack; - } - - stack = new MaxDouble(0.0); - _columns.Add(index, stack); - - return stack; - } - - private const double EstimatedWidth = 20.0; // TODO: configurable - - Size _panelSize; - SharedScrollInfo _layoutInfo; - - private SharedScrollInfo ComputeHorizontalScroll(Size size) { - double horizontalOffset = HorizontalOffset; - - int? firstItemIndex = null; - double firstItemOffset = 0; - double extentWidth = 0.0; - - int columnCount = ColumnHeaderSource == null ? 0 : ColumnHeaderSource.Count; - - for (int i = 0; i < columnCount; i++) { - double currentWidth; - - MaxDouble columnWidth; - if (_columns.TryGetValue(i, out columnWidth)) { - currentWidth = columnWidth.Max; - } else { - currentWidth = EstimatedWidth; - } - - if (firstItemIndex == null && horizontalOffset <= extentWidth) { - firstItemIndex = i; - firstItemOffset = extentWidth; - } - - extentWidth += currentWidth; - } - - ExtentWidth = extentWidth; - ViewportWidth = size.Width; - ScrollableWidth = extentWidth - size.Width; - - int firstIndex = firstItemIndex.HasValue ? firstItemIndex.Value : 0; - return new SharedScrollInfo() { - FirstItemIndex = firstItemIndex.HasValue ? firstItemIndex.Value : 0, - FirstItemOffset = firstItemOffset, - MaxItemInViewport = columnCount - firstIndex, - }; - } - - internal void OnViewportSizeChanged(Size newSize) { - _panelSize = newSize; - - var newLayoutInfo = ComputeHorizontalScroll(newSize); - - if (!_layoutInfo.Equals(newLayoutInfo)) { - _layoutInfo = newLayoutInfo; - - if (_columnHeadersPresenter != null) { - _columnHeadersPresenter.ScrollChanged(); - } - - // TODO: move to background(?) - _columns.RemoveWhere( - c => c.Key < _layoutInfo.FirstItemIndex - || c.Key >= (_layoutInfo.FirstItemIndex + _layoutInfo.MaxItemInViewport)); - - foreach (var row in _realizedRows) { - row.ScrollChanged(); - } - } - } - - internal void OnInvalidateScrollInfo() { - ComputeHorizontalScroll(_panelSize); - } - - internal SharedScrollInfo GetLayoutInfo(Size size) { - return _layoutInfo; - } - - public static readonly DependencyProperty ScrollableWidthProperty = - DependencyProperty.Register( - nameof(ScrollableWidth), - typeof(double), - typeof(DynamicGrid), - new FrameworkPropertyMetadata(0d)); - - public double ScrollableWidth { - get { - return (double) GetValue(ScrollableWidthProperty); - } - set { - SetValue(ScrollableWidthProperty, value); - } - } - - public static readonly DependencyProperty ExtentWidthProperty = - DependencyProperty.Register( - nameof(ExtentWidth), - typeof(double), - typeof(DynamicGrid), - new FrameworkPropertyMetadata(0d)); - - public double ExtentWidth { - get { - return (double)GetValue(ExtentWidthProperty); - } - set { - SetValue(ExtentWidthProperty, value); - } - } - - public static readonly DependencyProperty HorizontalOffsetProperty = - DependencyProperty.Register( - nameof(HorizontalOffset), - typeof(double), - typeof(DynamicGrid), - new FrameworkPropertyMetadata(0d)); - - public double HorizontalOffset { - get { - return (double)GetValue(HorizontalOffsetProperty); - } - set { - SetValue(HorizontalOffsetProperty, value); - } - } - - public static readonly DependencyProperty ViewportWidthProperty = - DependencyProperty.Register( - nameof(ViewportWidth), - typeof(double), - typeof(DynamicGrid), - new FrameworkPropertyMetadata(0d)); - - public double ViewportWidth { - get { - return (double)GetValue(ViewportWidthProperty); - } - set { - SetValue(ViewportWidthProperty, value); - } - } - - private void Scrollbar_Scroll(object sender, ScrollEventArgs e) { - switch (e.ScrollEventType) { - case ScrollEventType.EndScroll: - case ScrollEventType.First: - case ScrollEventType.LargeDecrement: - case ScrollEventType.LargeIncrement: - case ScrollEventType.Last: - case ScrollEventType.SmallDecrement: - case ScrollEventType.SmallIncrement: - OnViewportSizeChanged(_panelSize); - break; - case ScrollEventType.ThumbPosition: - case ScrollEventType.ThumbTrack: - default: - break; - } - } - - #endregion - } -} diff --git a/src/Package/Impl/DataInspect/DynamicGrid/DynamicGridCell.cs b/src/Package/Impl/DataInspect/DynamicGrid/DynamicGridCell.cs deleted file mode 100644 index c8cc867e2..000000000 --- a/src/Package/Impl/DataInspect/DynamicGrid/DynamicGridCell.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Media; - -namespace Microsoft.VisualStudio.R.Package.DataInspect { - /// - /// Item container in - /// - public class DynamicGridCell : ContentControl { - static DynamicGridCell() { - DefaultStyleKeyProperty.OverrideMetadata(typeof(DynamicGridCell), new FrameworkPropertyMetadata(typeof(DynamicGridCell))); - } - - public DynamicGridCell() { - Track = new LinkedListNode(this); - } - - internal DynamicGridRow ParentRow { get; private set; } - - internal DynamicGrid ParentGrid { get { return ParentRow?.ParentGrid; } } - - internal LinkedListNode Track { get; private set; } - - internal MaxDouble ColumnWidth { get; set; } - - internal virtual void Prepare(DynamicGridRow owner, MaxDouble columnWidth) { - ParentRow = owner; - - if (ColumnWidth != null) { - ColumnWidth.MaxChanged -= LayoutSize_MaxChanged; - } - - ColumnWidth = columnWidth; - ColumnWidth.MaxChanged += LayoutSize_MaxChanged; - } - - private void LayoutSize_MaxChanged(object sender, EventArgs e) { - if (!object.Equals(sender, ColumnWidth)) { - InvalidateMeasure(); - } - } - - /// - /// Clean up data when virtualized - /// - internal virtual void CleanUp() { - this.Content = null; - - if (ColumnWidth != null) { - ColumnWidth.MaxChanged -= LayoutSize_MaxChanged; - } - ColumnWidth = null; - - this.ParentRow = null; - } - - private double LineThickness = 1.0; // TODO: configurable - protected override Size MeasureOverride(Size constraint) { - - Size adjustedConstraint = DynamicGridUtilities.DecreaseSize(constraint, LineThickness); - - Size desired = base.MeasureOverride(adjustedConstraint); - - desired.Height += LineThickness; - desired.Width += LineThickness; - - ColumnWidth.Max = desired.Width; - desired.Width = ColumnWidth.Max; - - return desired; - } - - protected override Size ArrangeOverride(Size arrangeBounds) { - Size adjustedBounds = DynamicGridUtilities.DecreaseSize(arrangeBounds, LineThickness); - - Size arrangedSize = base.ArrangeOverride(adjustedBounds); - - arrangedSize.Height += LineThickness; - arrangedSize.Width += LineThickness; - - return arrangedSize; - } - - - - protected override void OnRender(DrawingContext drawingContext) { - base.OnRender(drawingContext); - - Brush brush = ParentRow.ColumnHeader ? ParentGrid.HeaderLinesBrush : ParentGrid.GridLinesBrush; - - // vertical line - { - Rect rect = new Rect(new Size(LineThickness, RenderSize.Height)); - rect.X = RenderSize.Width - LineThickness; - - drawingContext.DrawRectangle(brush, null, rect); - } - - // horizontal line - { - Rect rect = new Rect(new Size(RenderSize.Width, LineThickness)); - rect.Y = RenderSize.Height - LineThickness; - - drawingContext.DrawRectangle(brush, null, rect); - } - } - } - - /// - /// This class exists for simple templating - /// - /// - /// Styling and property transfer could be also an answer, but this is much simpler irresistibly - /// - public class DynamicGridColumnHeaderCell : DynamicGridCell { - static DynamicGridColumnHeaderCell() { - DefaultStyleKeyProperty.OverrideMetadata(typeof(DynamicGridColumnHeaderCell), new FrameworkPropertyMetadata(typeof(DynamicGridColumnHeaderCell))); - } - - public DynamicGridColumnHeaderCell() : base() { } - } -} diff --git a/src/Package/Impl/DataInspect/DynamicGrid/DynamicGridCellsPanel.cs b/src/Package/Impl/DataInspect/DynamicGrid/DynamicGridCellsPanel.cs deleted file mode 100644 index ef6c0b135..000000000 --- a/src/Package/Impl/DataInspect/DynamicGrid/DynamicGridCellsPanel.cs +++ /dev/null @@ -1,152 +0,0 @@ -using System; -using System.Diagnostics; -using System.Windows; -using System.Windows.Controls; -using System.Windows.Controls.Primitives; -using System.Windows.Threading; - -namespace Microsoft.VisualStudio.R.Package.DataInspect { - - public struct SharedScrollInfo { - public int FirstItemIndex { get; set; } - public double FirstItemOffset { get; set; } - public int MaxItemInViewport { get; set; } - } - - public interface IScrollInfoGiver { - SharedScrollInfo GetScrollInfo(Size size); - - void InvalidateScrollInfo(); - - event EventHandler SharedScrollChanged; - } - - internal class DynamicGridCellsPanel : VirtualizingPanel { - private IScrollInfoGiver _sharedScrollInfo; - internal IScrollInfoGiver SharedScroll { - get { - if (_sharedScrollInfo == null) { - _sharedScrollInfo = ItemsControl.GetItemsOwner(this) as IScrollInfoGiver; - if (_sharedScrollInfo == null) { - throw new NotSupportedException($"{typeof(DynamicGridCellsPanel)} supports only ItemsControl that implements {typeof(IScrollInfoGiver)}"); - } - _sharedScrollInfo.SharedScrollChanged += SharedScrollChanged; - } - - return _sharedScrollInfo; - } - } - - private DynamicGridRow _owningRow; - internal DynamicGridRow OwningRow { - get { - if (_owningRow == null) { - _owningRow = ItemsControl.GetItemsOwner(this) as DynamicGridRow; - - Debug.Assert(_owningRow != null, "DynamicGridCellsPanel supports only DynamicGridRow"); - } - return _owningRow; - } - } - - #region Layout - - private void SharedScrollChanged(object sender, EventArgs e) { - InvalidateMeasure(); - } - - protected override Size MeasureOverride(Size availableSize) { - // work around to make sure ItemContainerGenerator non-null - var children = Children; - - var layoutInfo = SharedScroll.GetScrollInfo(availableSize); - - int startIndex = layoutInfo.FirstItemIndex; - int viewportCount = layoutInfo.MaxItemInViewport; - - IItemContainerGenerator generator = this.ItemContainerGenerator; - GeneratorPosition position = generator.GeneratorPositionFromIndex(startIndex); - - // if realized, position.Offset ==0, use just position's index - // otherwise, add one to insert after it. - int childIndex = (position.Offset == 0) ? position.Index : position.Index + 1; - - double height = 10.0; - double width = 0; - int finalCount = 0; - using (generator.StartAt(position, GeneratorDirection.Forward, true)) { - for (int i = 0; i < viewportCount; i++, childIndex++) { - bool newlyRealized; - DynamicGridCell child = (DynamicGridCell) generator.GenerateNext(out newlyRealized); - - if (newlyRealized) { - if (childIndex >= InternalChildren.Count) { - AddInternalChild(child); - } else { - InsertInternalChild(childIndex, child); - } - generator.PrepareItemContainer(child); - } else { - Debug.Assert(child == InternalChildren[childIndex]); - } - - child.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); - - if (child.DesiredSize.Height > height) { - height = child.DesiredSize.Height; - } - - child.ColumnWidth.Max = Math.Max(20.0, child.DesiredSize.Width); - - width += child.ColumnWidth.Max; - finalCount++; - - if (width > availableSize.Width) { - break; - } - } - } - - Size desired = new Size(width, height); - - if (finalCount > 0) { - CleanUpItems(startIndex, startIndex + finalCount - 1); - } - - SharedScroll.InvalidateScrollInfo(); - - return desired; - } - - protected override Size ArrangeOverride(Size finalSize) { - IItemContainerGenerator generator = this.ItemContainerGenerator; - - double x = 0.0; - for (int i = 0; i < InternalChildren.Count; i++) { - var child = (DynamicGridCell) InternalChildren[i]; - - child.Arrange(new Rect(x, 0, child.ColumnWidth.Max, finalSize.Height)); - - x += child.ColumnWidth.Max; - } - - return finalSize; - } - - private void CleanUpItems(int minDesiredGenerated, int maxDesiredGenerated) { - UIElementCollection children = this.InternalChildren; - IItemContainerGenerator generator = this.ItemContainerGenerator; - - for (int i = children.Count - 1; i >= 0; i--) { - GeneratorPosition childGeneratorPos = new GeneratorPosition(i, 0); - int itemIndex = generator.IndexFromGeneratorPosition(childGeneratorPos); - if (itemIndex < minDesiredGenerated || itemIndex > maxDesiredGenerated) { - generator.Remove(childGeneratorPos, 1); - RemoveInternalChildRange(i, 1); - } - } - } - - #endregion - } -} diff --git a/src/Package/Impl/DataInspect/DynamicGrid/DynamicGridRow.cs b/src/Package/Impl/DataInspect/DynamicGrid/DynamicGridRow.cs deleted file mode 100644 index 8e38a54f4..000000000 --- a/src/Package/Impl/DataInspect/DynamicGrid/DynamicGridRow.cs +++ /dev/null @@ -1,142 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Windows; -using System.Windows.Controls; -using Microsoft.VisualStudio.R.Package.Wpf; - -namespace Microsoft.VisualStudio.R.Package.DataInspect { - /// - /// Item container in , and also ItemsControl for cells in a row - /// - internal class DynamicGridRow : ItemsControl, IScrollInfoGiver { - private LinkedList _realizedCells = new LinkedList(); - - static DynamicGridRow() { - DefaultStyleKeyProperty.OverrideMetadata(typeof(DynamicGridRow), new FrameworkPropertyMetadata(typeof(DynamicGridRow))); - } - - public DynamicGridRow() { - Track = new LinkedListNode(this); - ColumnHeader = false; - } - - public bool ColumnHeader { get; set; } - - internal LinkedListNode Track { get; } - - public static readonly DependencyProperty HeaderProperty = - DependencyProperty.Register( - "Header", - typeof(object), - typeof(DynamicGridRow), - new FrameworkPropertyMetadata(null)); - - public object Header { - get { return GetValue(HeaderProperty); } - set { SetValue(HeaderProperty, value); } - } - - internal DynamicGrid ParentGrid { get; private set; } - - #region IScrollInfoGiver support - - public SharedScrollInfo GetScrollInfo(Size size) { - Debug.Assert(ParentGrid != null); - return ParentGrid.GetLayoutInfo(size); - } - - public void InvalidateScrollInfo() { - if (ColumnHeader) { - ParentGrid.OnInvalidateScrollInfo(); - } - } - - public event EventHandler SharedScrollChanged; - - #endregion - - public override void OnApplyTemplate() { - base.OnApplyTemplate(); - - if (ColumnHeader) { - ParentGrid = WpfHelper.FindParent(this); - ParentGrid.ColumnHeadersPresenter = this; - } - } - - protected override DependencyObject GetContainerForItemOverride() { - if (ColumnHeader) { - return new DynamicGridColumnHeaderCell(); - } else { - return new DynamicGridCell(); - } - } - - protected override void PrepareContainerForItemOverride(DependencyObject element, object item) { - base.PrepareContainerForItemOverride(element, item); - - var cell = (DynamicGridCell)element; - int column = this.Items.IndexOf(item); - if (column == -1) { - throw new InvalidOperationException("Item is not found in collection"); - } - - if (cell.ParentRow != this) { - _realizedCells.AddFirst(cell.Track); - } - - cell.Prepare(this, ParentGrid.GetColumnWidth(column)); - - } - - protected override void ClearContainerForItemOverride(DependencyObject element, object item) { - base.ClearContainerForItemOverride(element, item); - - var cell = (DynamicGridCell)element; - if (cell.ParentRow == this) { - _realizedCells.Remove(cell.Track); - } - cell.CleanUp(); - } - - internal void Prepare(DynamicGrid owner, object item) { - if (!(item is IList)) { - throw new NotSupportedException("JointCollectionGridRow supports only IList for item"); - } - - ParentGrid = owner; - - var items = (IList)item; - ItemsSource = items; - } - - internal void CleanUp(DynamicGrid owner, object item) { - // when VirtualizationMode == Recycling, next lines must not be called as system calls them - var mode = VirtualizingPanel.GetVirtualizationMode(ParentGrid); - if (mode != VirtualizationMode.Recycling) { - foreach (var cell in _realizedCells) { - cell.CleanUp(); - } - _realizedCells.Clear(); - } - - ParentGrid = null; - } - - internal void ScrollChanged() { - if (SharedScrollChanged != null) { - SharedScrollChanged(this, EventArgs.Empty); - } - } - - internal DynamicGridRowHeader RowHeader { get; set; } - - internal void NotifyRowHeader() { - if (RowHeader != null) { - RowHeader.InvalidateMeasure(); - } - } - } -} diff --git a/src/Package/Impl/DataInspect/DynamicGrid/DynamicGridRowHeader.cs b/src/Package/Impl/DataInspect/DynamicGrid/DynamicGridRowHeader.cs deleted file mode 100644 index 93735c6de..000000000 --- a/src/Package/Impl/DataInspect/DynamicGrid/DynamicGridRowHeader.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System.Windows; -using System.Windows.Controls; -using System.Windows.Media; -using Microsoft.VisualStudio.R.Package.Wpf; - -namespace Microsoft.VisualStudio.R.Package.DataInspect { - /// - /// A control that contains row header content - /// Width is synched with other row's header width through owning grid's property - /// - internal class DynamicGridRowHeader : ContentControl { - static DynamicGridRowHeader() { - DefaultStyleKeyProperty.OverrideMetadata(typeof(DynamicGridRowHeader), new FrameworkPropertyMetadata(typeof(DynamicGridRowHeader))); - } - - public override void OnApplyTemplate() { - base.OnApplyTemplate(); - - DynamicGridRow row = ParentRow; - if (row != null) { - row.RowHeader = this; - } - } - - protected override Size MeasureOverride(Size constraint) { - Size adjusted = DynamicGridUtilities.DecreaseSize(constraint, LineThickness); - - Size baseSize = base.MeasureOverride(adjusted); - - baseSize.Width += LineThickness; - baseSize.Height += LineThickness; - - var grid = ParentGrid; - if (grid == null) { - return baseSize; - } - - if (baseSize.Width > grid.RowHeaderActualWidth) { - grid.RowHeaderActualWidth = baseSize.Width; - } - - return new Size(grid.RowHeaderActualWidth, baseSize.Height); - } - - private double LineThickness = 1.0; - protected override Size ArrangeOverride(Size arrangeBounds) { - Size adjustedBounds = DynamicGridUtilities.DecreaseSize(arrangeBounds, LineThickness); - - Size arranged = base.ArrangeOverride(adjustedBounds); - - arranged.Width += LineThickness; - arranged.Height += LineThickness; - - return arranged; - } - - protected override void OnRender(DrawingContext drawingContext) { - base.OnRender(drawingContext); - - // vertical line - { - Rect rect = new Rect(new Size(LineThickness, RenderSize.Height)); - rect.X = RenderSize.Width - LineThickness; - - drawingContext.DrawRectangle(ParentGrid.HeaderLinesBrush, null, rect); - } - - // horizontal line - { - Rect rect = new Rect(new Size(RenderSize.Width, LineThickness)); - rect.Y = RenderSize.Height - LineThickness; - - drawingContext.DrawRectangle(ParentGrid.HeaderLinesBrush, null, rect); - } - } - - private DynamicGridRow _parentRow; - internal DynamicGridRow ParentRow { - get { - if (_parentRow == null) { - _parentRow = WpfHelper.FindParent(this); - } - return _parentRow; - } - } - - internal DynamicGrid ParentGrid { - get { - return ParentRow?.ParentGrid; - } - } - } -} diff --git a/src/Package/Impl/DataInspect/DynamicGrid/DynamicGridRowsPanel.cs b/src/Package/Impl/DataInspect/DynamicGrid/DynamicGridRowsPanel.cs deleted file mode 100644 index f2dfbcbfa..000000000 --- a/src/Package/Impl/DataInspect/DynamicGrid/DynamicGridRowsPanel.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System.Diagnostics; -using System.Windows; -using System.Windows.Controls; - -namespace Microsoft.VisualStudio.R.Package.DataInspect { - internal class DynamicGridRowsPanel : VirtualizingStackPanel { - private DynamicGrid _owningGrid; - internal DynamicGrid OwningGrid { - get { - if (_owningGrid == null) { - _owningGrid = ItemsControl.GetItemsOwner(this) as DynamicGrid; - - Debug.Assert(_owningGrid != null, "DynamicGridRowsPanel supports only DynamicGrid"); - } - return _owningGrid; - } - } - - protected override void OnViewportSizeChanged(Size oldViewportSize, Size newViewportSize) { - base.OnViewportSizeChanged(oldViewportSize, newViewportSize); - - DynamicGrid grid = OwningGrid; - if (grid != null) { - grid.OnViewportSizeChanged(newViewportSize); - } - } - } -} diff --git a/src/Package/Impl/DataInspect/DynamicGrid/DynamicGridStyle.xaml b/src/Package/Impl/DataInspect/DynamicGrid/DynamicGridStyle.xaml deleted file mode 100644 index 55b8004ed..000000000 --- a/src/Package/Impl/DataInspect/DynamicGrid/DynamicGridStyle.xaml +++ /dev/null @@ -1,193 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Package/Impl/DataInspect/DynamicGrid/DynamicGridUtilities.cs b/src/Package/Impl/DataInspect/DynamicGrid/DynamicGridUtilities.cs deleted file mode 100644 index c9e96ec34..000000000 --- a/src/Package/Impl/DataInspect/DynamicGrid/DynamicGridUtilities.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using System.Windows; - -namespace Microsoft.VisualStudio.R.Package.DataInspect { - internal class DynamicGridUtilities { - public static Size DecreaseSize(Size size, double thickness) { - double width = size.Width - thickness; - width = Math.Max(0.0, width); - - double height = size.Height - thickness; - height = Math.Max(0.0, height); - - return new Size(width, height); - } - } -} diff --git a/src/Package/Impl/DataInspect/DynamicGrid/MaxDouble.cs b/src/Package/Impl/DataInspect/DynamicGrid/MaxDouble.cs deleted file mode 100644 index ab14896eb..000000000 --- a/src/Package/Impl/DataInspect/DynamicGrid/MaxDouble.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.ComponentModel; - -namespace Microsoft.VisualStudio.R.Package.DataInspect { - /// - /// double that updates only when bigger value is assigned - /// - public class MaxDouble { - public MaxDouble() : this(double.NegativeInfinity) { } - - public MaxDouble(double initialValue) { - _max = initialValue; - } - - private double _max; - /// - /// Maximum value - /// - public double Max - { - get - { - return _max; - } - set - { - SetValue(value); - } - } - - public event EventHandler MaxChanged; - - private void SetValue(double value) { - if (MaxChanged != null) { - if (value > _max) { - _max = value; - MaxChanged(this, EventArgs.Empty); - } - } else { - _max = Math.Max(_max, value); - } - } - } -} diff --git a/src/Package/Impl/DataInspect/Elapsed.cs b/src/Package/Impl/DataInspect/Elapsed.cs deleted file mode 100644 index 3c6c730c7..000000000 --- a/src/Package/Impl/DataInspect/Elapsed.cs +++ /dev/null @@ -1,28 +0,0 @@ -//#define PRINT_ELAPSED - -using System; -using System.Diagnostics; - -namespace Microsoft.VisualStudio.R.Package.DataInspect { - /// - /// Prints elapsed time, simple development utility calss - /// - internal class Elapsed : IDisposable { -#if DEBUG && PRINT_ELAPSED - Stopwatch _watch; - string _header; -#endif - public Elapsed(string header) { -#if DEBUG && PRINT_ELAPSED - _header = header; - _watch = Stopwatch.StartNew(); -#endif - } - - public void Dispose() { -#if DEBUG && PRINT_ELAPSED - Trace.WriteLine(_header + _watch.ElapsedMilliseconds); -#endif - } - } -} diff --git a/src/Package/Impl/DataInspect/EvaluationWrapper.cs b/src/Package/Impl/DataInspect/EvaluationWrapper.cs index a3c90c0a2..240ccb78d 100644 --- a/src/Package/Impl/DataInspect/EvaluationWrapper.cs +++ b/src/Package/Impl/DataInspect/EvaluationWrapper.cs @@ -1,60 +1,138 @@ using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading.Tasks; using System.Windows.Input; using Microsoft.Common.Core; using Microsoft.R.Debugger; using Microsoft.R.Editor.Data; using Microsoft.VisualStudio.PlatformUI; +using Microsoft.VisualStudio.R.Package.DataInspect.DataSource; +using Microsoft.VisualStudio.R.Package.DataInspect.Office; using Microsoft.VisualStudio.R.Package.Utilities; namespace Microsoft.VisualStudio.R.Package.DataInspect { /// /// Model for variable tree grid, that provides UI customization of /// - internal sealed class EvaluationWrapper : RSessionDataObject, IIndexedItem { - public EvaluationWrapper() { } + public sealed class EvaluationWrapper : RSessionDataObject, IIndexedItem { - public EvaluationWrapper(int index, DebugEvaluationResult evaluation) : - this(index, evaluation, true) { } + public EvaluationWrapper() { Index = -1; } /// /// Create new instance of /// /// R session's evaluation result /// true to truncate children returned by GetChildrenAsync - public EvaluationWrapper(int index, DebugEvaluationResult evaluation, bool truncateChildren) : - base(index, evaluation, truncateChildren) { + public EvaluationWrapper(DebugEvaluationResult evaluation, int index = -1, int? maxChildrenCount = null) : + base(evaluation, maxChildrenCount) { + + Index = index; + + CanShowDetail = ComputeDetailAvailability(DebugEvaluation as DebugValueEvaluationResult); if (CanShowDetail) { ShowDetailCommand = new DelegateCommand(ShowVariableGridWindowPane, (o) => CanShowDetail); + OpenInExcelCommand = new DelegateCommand(OpenInExcel, (o) => CanShowDetail); } } + #region Ellipsis + private static Lazy _ellipsis = Lazy.Create(() => { var instance = new EvaluationWrapper(); instance.Name = string.Empty; - instance.Value = "[truncated]"; + instance.Value = Resources.VariableExplorer_Truncated; instance.HasChildren = false; return instance; }); + public static EvaluationWrapper Ellipsis { get { return _ellipsis.Value; } } - protected override List EvaluateChildren(IReadOnlyList children, bool truncateChildren) { - var result = new List(); - for (int i = 0; i < children.Count; i++) { - result.Add(new EvaluationWrapper(i, children[i], truncateChildren)); + #endregion + + public int FrameIndex { + get { + if (base.DebugEvaluation != null && base.DebugEvaluation.StackFrame != null) { + return base.DebugEvaluation.StackFrame.Index; + } + + Debug.Fail("DebugEvaluationResult doesn't set StackFrame"); + return 0; // global frame index, by default + } + } + + protected override async Task> GetChildrenAsyncInternal() { + List result = null; + + var valueEvaluation = DebugEvaluation as DebugValueEvaluationResult; + if (valueEvaluation == null) { + Debug.Assert(false, $"{nameof(EvaluationWrapper)} result type is not {typeof(DebugValueEvaluationResult)}"); + return result; } + + if (valueEvaluation.HasChildren) { + await TaskUtilities.SwitchToBackgroundThread(); + + var fields = (DebugEvaluationResultFields.All & ~DebugEvaluationResultFields.ReprAll) | + DebugEvaluationResultFields.Repr | DebugEvaluationResultFields.ReprStr; + + // assumption: DebugEvaluationResult returns children in ascending order + IReadOnlyList children = await valueEvaluation.GetChildrenAsync(fields, MaxChildrenCount, 100); + + result = new List(); + for (int i = 0; i < children.Count; i++) { + result.Add(new EvaluationWrapper(children[i], index: i, maxChildrenCount: DefaultMaxGrandChildren)); + } + + // return children can be less than value's length in some cases e.g. missing parameter + if (valueEvaluation.Length > result.Count + && (valueEvaluation.Length > MaxChildrenCount)) { + result.Add(EvaluationWrapper.Ellipsis); // insert dummy child to indicate truncation in UI + } + } + return result; } - #region Detail Grid + #region IIndexedItem support + + /// + /// Index returned from the evaluation provider. + /// DebugEvaluationResult returns in ascending order + /// + public int Index { get; } + + #endregion + + #region Variable Grid command + + public bool CanShowDetail { get; } + public ICommand ShowDetailCommand { get; } + public ICommand OpenInExcelCommand { get; } + private void ShowVariableGridWindowPane(object parameter) { VariableGridWindowPane pane = ToolWindowUtilities.ShowWindowPane(0, true); pane.SetEvaluation(this); } + + private void OpenInExcel(object parameter) { + //ExcelInterop.OpenDataInExcel(Name, Expression, Dimensions[0], Dimensions[1]).DoNotWait(); + } + + private static string[] detailClasses = new string[] { "matrix", "data.frame", "table" }; + private bool ComputeDetailAvailability(DebugValueEvaluationResult evaluation) { + if (evaluation != null && evaluation.Classes.Any(t => detailClasses.Contains(t))) { + if (evaluation.Dim != null && evaluation.Dim.Count == 2) { + return true; + } + } + return false; + } #endregion } } diff --git a/src/Package/Impl/DataInspect/GridData.cs b/src/Package/Impl/DataInspect/GridData.cs index 19f758e65..ae913ca21 100644 --- a/src/Package/Impl/DataInspect/GridData.cs +++ b/src/Package/Impl/DataInspect/GridData.cs @@ -1,7 +1,16 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Microsoft.VisualStudio.R.Package.DataInspect { internal class GridData : IGridData { + [Flags] + public enum HeaderNames { + None = 0, + Row = 1, + Column = 2, + } + + public GridData( IList rowNames, IList columnNames, @@ -11,7 +20,7 @@ public GridData( Values = values; } - public bool ValidHeaderNames { get; set; } + public HeaderNames ValidHeaderNames { get; set; } public IList RowNames { get; } @@ -25,7 +34,7 @@ public GridData( public IRange ColumnHeader { get { if (_columnHeader == null) { - if (ValidHeaderNames) { + if (ValidHeaderNames.HasFlag(HeaderNames.Column)) { _columnHeader = new ListToRange( Range.Columns, ColumnNames); @@ -41,7 +50,7 @@ public IRange ColumnHeader { public IRange RowHeader { get { if (_rowHeader == null) { - if (ValidHeaderNames) { + if (ValidHeaderNames.HasFlag(HeaderNames.Row)) { _rowHeader = new ListToRange( Range.Rows, RowNames); diff --git a/src/Package/Impl/DataInspect/GridDataProvider.cs b/src/Package/Impl/DataInspect/GridDataProvider.cs new file mode 100644 index 000000000..06c16aa6e --- /dev/null +++ b/src/Package/Impl/DataInspect/GridDataProvider.cs @@ -0,0 +1,35 @@ +using System; +using System.Diagnostics; +using System.Threading.Tasks; +using Microsoft.VisualStudio.R.Package.DataInspect.DataSource; +using static System.FormattableString; + +namespace Microsoft.VisualStudio.R.Package.DataInspect { + /// + /// grid data provider to control + /// + internal class GridDataProvider : IGridProvider { + private readonly EvaluationWrapper _evaluation; + + public GridDataProvider(EvaluationWrapper evaluation) { + _evaluation = evaluation; + + RowCount = evaluation.Dimensions[0]; + ColumnCount = evaluation.Dimensions[1]; + } + + public int ColumnCount { get; } + + public int RowCount { get; } + + public Task> GetAsync(GridRange gridRange) { + var t = GridDataSource.GetGridDataAsync(_evaluation.Expression, gridRange); + if (t == null) { + // May happen when R host is not running + Trace.Fail(Invariant($"{nameof(EvaluationWrapper)} returned null grid data")); + return Task.FromResult>(null); + } + return t; + } + } +} diff --git a/src/Package/Impl/DataInspect/GridParser.cs b/src/Package/Impl/DataInspect/GridParser.cs index 96732d011..745e08214 100644 --- a/src/Package/Impl/DataInspect/GridParser.cs +++ b/src/Package/Impl/DataInspect/GridParser.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Text; namespace Microsoft.VisualStudio.R.Package.DataInspect { @@ -26,16 +27,16 @@ public static GridData Parse(string input) { // This is for performance, as generic formatting such as json is too expensive // int current = 0; - current = input.IndexOf("structure", current); + current = input.IndexOf("structure", current, StringComparison.OrdinalIgnoreCase); current = input.IndexOf('(', current); - current = input.IndexOf("list", current); + current = input.IndexOf("list", current, StringComparison.OrdinalIgnoreCase); current = input.IndexOf('(', current); - current = input.IndexOf("dimnames", current); + current = input.IndexOf("dimnames", current, StringComparison.OrdinalIgnoreCase); current = input.IndexOf('=', current); string dimnamesValue; current = FirstQuotedString(input, current, out dimnamesValue); - bool validHeaderNames = bool.Parse(dimnamesValue); + var validHeaderNames = (GridData.HeaderNames) Enum.Parse(typeof(GridData.HeaderNames), dimnamesValue); List rowNames = new List(); current = NamedValue(input, "row.names", rowNames, current, true); @@ -45,27 +46,16 @@ public static GridData Parse(string input) { current = NamedValue(input, "col.names", columnNames, current, true); current = input.IndexOf(',', current); - current = input.IndexOf("data", current); + current = input.IndexOf("data", current, StringComparison.OrdinalIgnoreCase); current = input.IndexOf('=', current); - current = input.IndexOf("structure", current); + current = input.IndexOf("structure", current, StringComparison.OrdinalIgnoreCase); current = input.IndexOf('(', current); List values = new List(); current = Vector(input, values, current); - //while (true) { - // List columnValues = new List(); - // current = Vector(input, columnValues, current); - // data.Values.Add(columnValues); - - // current = input.IndexOfAny(ValueDelimiter, current); - // if (input[current] == ValueDelimiter[ClosingIndex]) { - // break; - // } - //} - GridData data = new GridData(rowNames, columnNames, values); data.ValidHeaderNames = validHeaderNames; @@ -90,7 +80,7 @@ public static string CleanEscape(string input) { } private static int NamedValue(string input, string name, List names, int current, bool optional = false) { - int nameIndex = input.IndexOf(name, current); + int nameIndex = input.IndexOf(name, current, StringComparison.OrdinalIgnoreCase); if (optional && nameIndex == -1) { return current; } diff --git a/src/Package/Impl/DataInspect/MultiplyConverter.cs b/src/Package/Impl/DataInspect/MultiplyConverter.cs index 4c58a4f1b..2c135df25 100644 --- a/src/Package/Impl/DataInspect/MultiplyConverter.cs +++ b/src/Package/Impl/DataInspect/MultiplyConverter.cs @@ -1,10 +1,6 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using System.Windows.Data; namespace Microsoft.VisualStudio.R.Package.DataInspect { @@ -16,17 +12,17 @@ public class MultiplyConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { if (parameter != null) { if (parameter is double) { - return System.Convert.ToDouble(value) * (double)parameter; + return System.Convert.ToDouble(value, CultureInfo.InvariantCulture) * (double)parameter; } else if (parameter is string) { double coefficient; if (double.TryParse((string)parameter, out coefficient)) { - return System.Convert.ToDouble(value) * coefficient; + return System.Convert.ToDouble(value, CultureInfo.InvariantCulture) * coefficient; } } Debug.Assert(false, "MultiplyConverter parameter is not convertable to double"); } - return System.Convert.ToDouble(value) * Coefficient; + return System.Convert.ToDouble(value, CultureInfo.InvariantCulture) * Coefficient; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { diff --git a/src/Package/Impl/DataInspect/Office/ExcelInterop.cs b/src/Package/Impl/DataInspect/Office/ExcelInterop.cs new file mode 100644 index 000000000..6770b9119 --- /dev/null +++ b/src/Package/Impl/DataInspect/Office/ExcelInterop.cs @@ -0,0 +1,205 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Microsoft.Common.Core; +using Microsoft.Office.Interop.Excel; +using Microsoft.VisualStudio.R.Package.DataInspect.DataSource; +using Microsoft.VisualStudio.R.Package.Shell; +using Excel = Microsoft.Office.Interop.Excel; + +namespace Microsoft.VisualStudio.R.Package.DataInspect.Office { + internal static class ExcelInterop { + private const string _workbookName = "RTVS_View.xlsx"; + private static bool _busy; + private static Application _excel; + + class ExcelData { + public object[] RowNames; + public object[] ColNames; + public object[,] CellData; + } + + public static async Task OpenDataInExcel(string variableName, string expression, int rows, int cols) { + if (_busy) { + return; + } + + _busy = true; + + try { + await TaskUtilities.SwitchToBackgroundThread(); + + ExcelData xlData = await GenerateExcelData(expression, rows, cols); + if (xlData != null) { + + VsAppShell.Current.DispatchOnUIThread(() => { + Workbook wb = RunExcel(); + if (wb != null) { + // Try finding existing worksheet first + Worksheet ws = GetOrCreateWorksheet(wb, variableName); + PopulateWorksheet(ws, xlData.RowNames, xlData.ColNames, xlData.CellData); + _excel.Visible = true; + } + }); + } + } finally { + _busy = false; + } + } + + private static async Task GenerateExcelData(string expression, int rows, int cols) { + + try { + ExcelData xlData = new ExcelData(); + IGridData data = await GridDataSource.GetGridDataAsync(expression, + new GridRange(new Range(0, rows), new Range(0, cols))); + if (data != null) { + if (data.RowHeader != null) { + xlData.RowNames = MakeRowNames(data, rows); + } + + if (data.RowHeader != null) { + xlData.ColNames = MakeColumnNames(data, cols); + } + + xlData.CellData = MakeExcelRange(data, rows, cols); + return xlData; + } + } catch (OperationCanceledException) { } + + return null; + } + + private static Workbook RunExcel() { + do { + if (_excel == null) { + _excel = CreateExcel(); + } + + if (_excel != null) { + try { + return GetOrCreateWorkbook(); + } catch (COMException) { + _excel = null; + } + } + } while (_excel == null); + + return null; + } + + private static Application CreateExcel() { + Application xlApp = null; + try { + xlApp = (Application)Marshal.GetActiveObject("Excel.Application"); + } catch (COMException) { } + + if (xlApp == null) { + try { + xlApp = new Application(); + } catch (COMException) { } + } + return xlApp; + } + + private static Workbook GetOrCreateWorkbook() { + Workbook wb = null; + try { + wb = _excel.Workbooks[_workbookName]; + } catch (COMException) { } + + if (wb == null) { + wb = _excel.Workbooks.Add(); + string wbName = null; + try { + string myDocPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); + wbName = Path.Combine(myDocPath, _workbookName); + if (File.Exists(wbName)) { + File.Delete(wbName); + } + } catch (IOException) { } + + try { + if (wbName != null) { + wb.SaveAs(wbName); + } + } catch (COMException) { } + } + + return wb; + } + + private static Worksheet GetOrCreateWorksheet(Workbook wb, string name) { + Worksheet ws = null; + try { + ws = (Worksheet)wb.Sheets[name]; + } catch (COMException) { } + + if (ws == null) { + ws = (Worksheet)wb.Sheets.Add(); + ws.Name = name; + } + return ws; + } + + private static void PopulateWorksheet(Worksheet ws, object[] rowNames, object[] colNames, object[,] cellData) { + int startRow = colNames != null ? 2 : 1; + int startCol = rowNames != null ? 2 : 1; + Excel.Range c1, c2; + + int rows = rowNames.Length; + int cols = colNames.Length; + + c1 = (Excel.Range)ws.Cells[startRow, startCol]; + c2 = (Excel.Range)ws.Cells[rows + startRow - 1, cols + startCol - 1]; + Excel.Range dataRange = ws.get_Range(c1, c2); + dataRange.Value = cellData; + + if (rowNames != null) { + for (int r = 0; r < rows; r++) { + ws.Cells[r + startRow, 1] = rowNames[r]; + } + + c1 = (Excel.Range)ws.Cells[startRow, 1]; + c2 = (Excel.Range)ws.Cells[rows + startRow - 1, 1]; + Excel.Range rowNamesRange = ws.get_Range(c1, c2); + rowNamesRange.Columns.AutoFit(); + } + + if (colNames != null) { + c1 = (Excel.Range)ws.Cells[1, startCol]; + c2 = (Excel.Range)ws.Cells[1, cols + startCol - 1]; + Excel.Range colRange = ws.get_Range(c1, c2); + colRange.Value = colNames; + colRange.HorizontalAlignment = XlHAlign.xlHAlignRight; + } + } + + private static object[] MakeRowNames(IGridData data, int rows) { + object[] arr = new object[rows]; + for (int r = 0; r < rows; r++) { + arr[r] = data.RowHeader[r]; + } + return arr; + } + + private static object[] MakeColumnNames(IGridData data, int cols) { + object[] arr = new object[cols]; + for (int c = 0; c < cols; c++) { + arr[c] = data.ColumnHeader[c]; + } + return arr; + } + + private static object[,] MakeExcelRange(IGridData data, int rows, int cols) { + object[,] arr = new object[rows, cols]; + for (int r = 0; r < rows; r++) { + for (int c = 0; c < cols; c++) { + arr[r, c] = data.Grid[r, c]; + } + } + return arr; + } + } +} diff --git a/src/Package/Impl/DataInspect/VariableChangedArgs.cs b/src/Package/Impl/DataInspect/VariableChangedArgs.cs deleted file mode 100644 index cb933cb45..000000000 --- a/src/Package/Impl/DataInspect/VariableChangedArgs.cs +++ /dev/null @@ -1,7 +0,0 @@ -using System; - -namespace Microsoft.VisualStudio.R.Package.DataInspect { - internal class VariableChangedArgs : EventArgs { - public EvaluationWrapper NewVariable { get; set; } - } -} \ No newline at end of file diff --git a/src/Package/Impl/DataInspect/VariableGridHost.xaml b/src/Package/Impl/DataInspect/VariableGridHost.xaml index e6537e1b1..7b58edfb9 100644 --- a/src/Package/Impl/DataInspect/VariableGridHost.xaml +++ b/src/Package/Impl/DataInspect/VariableGridHost.xaml @@ -9,17 +9,12 @@ HorizontalAlignment="Stretch" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> - - - - - - - - - + + + + diff --git a/src/Package/Impl/DataInspect/VariableGridHost.xaml.cs b/src/Package/Impl/DataInspect/VariableGridHost.xaml.cs index a1f960e66..b85c677e3 100644 --- a/src/Package/Impl/DataInspect/VariableGridHost.xaml.cs +++ b/src/Package/Impl/DataInspect/VariableGridHost.xaml.cs @@ -1,58 +1,84 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Diagnostics; -using System.Threading.Tasks; +using System.Windows; using System.Windows.Controls; -using Microsoft.Common.Core; -using Newtonsoft.Json.Linq; +using Microsoft.R.Debugger; +using Microsoft.VisualStudio.R.Package.DataInspect.Definitions; +using Microsoft.VisualStudio.R.Package.Shell; +using static System.FormattableString; namespace Microsoft.VisualStudio.R.Package.DataInspect { + /// + /// Control that shows two dimensional R object + /// public partial class VariableGridHost : UserControl { + private EvaluationWrapper _evaluation; + private VariableSubscription _subscription; + private IVariableDataProvider _variableProvider; + public VariableGridHost() { InitializeComponent(); - Loaded += VariableGridHost_Loaded; - Unloaded += VariableGridHost_Unloaded; + _variableProvider = VsAppShell.Current.ExportProvider.GetExportedValue(); } internal void SetEvaluation(EvaluationWrapper evaluation) { - VariableGrid.Initialize(new DataProvider(evaluation)); - } + ClearError(); - private void VariableGridHost_Loaded(object sender, System.Windows.RoutedEventArgs e) { - VariableProvider.Current.VariableChanged += VariableProvider_VariableChanged; - } + VariableGrid.Initialize(new GridDataProvider(evaluation)); - private void VariableGridHost_Unloaded(object sender, System.Windows.RoutedEventArgs e) { - VariableProvider.Current.VariableChanged -= VariableProvider_VariableChanged; - } + _evaluation = evaluation; + + if (_subscription != null) { + _variableProvider.Unsubscribe(_subscription); + _subscription = null; + } - private void VariableProvider_VariableChanged(object sender, VariableChangedArgs e) { - VariableGrid.Refresh(); + _subscription = _variableProvider.Subscribe( + evaluation.FrameIndex, + evaluation.Expression, + SubscribeAction); } - } - internal class DataProvider : IGridProvider { - private EvaluationWrapper _evaluation; + private void SubscribeAction(DebugEvaluationResult evaluation) { + VsAppShell.Current.DispatchOnUIThread( + () => { + if (evaluation is DebugErrorEvaluationResult) { + // evaluation error, this could happen if R object is removed + var error = (DebugErrorEvaluationResult)evaluation; + SetError(error.ErrorText); + return; + } - public DataProvider(EvaluationWrapper evaluation) { - _evaluation = evaluation; + var wrapper = new EvaluationWrapper(evaluation); - RowCount = evaluation.Dimensions[0]; - ColumnCount = evaluation.Dimensions[1]; - } + if (wrapper.Dimensions.Count != 2) { + // the same evaluation changed to non-matrix + SetError(Invariant($"object '{evaluation.Expression}' is not two dimensional.")); + } else if (wrapper.Dimensions[0] != _evaluation.Dimensions[0] + || wrapper.Dimensions[1] != _evaluation.Dimensions[1]) { + ClearError(); - public int ColumnCount { get; } + // matrix size changed. Reset the evaluation + SetEvaluation(wrapper); + } else { + ClearError(); + + // size stays same. Refresh + VariableGrid.Refresh(); + } + }); + } - public int RowCount { get; } + private void SetError(string text) { + ErrorTextBlock.Text = text; + ErrorTextBlock.Visibility = Visibility.Visible; - public Task> GetAsync(GridRange gridRange) { - return VariableProvider.Current.GetGridDataAsync(_evaluation.Expression, gridRange); + VariableGrid.Visibility = Visibility.Collapsed; } - public Task> GetRangeAsync(GridRange gridRange) { - throw new NotImplementedException(); + private void ClearError() { + ErrorTextBlock.Visibility = Visibility.Collapsed; + + VariableGrid.Visibility = Visibility.Visible; } } } diff --git a/src/Package/Impl/DataInspect/VariableGridWindowPane.cs b/src/Package/Impl/DataInspect/VariableGridWindowPane.cs index 2ce30ac8b..5b262af02 100644 --- a/src/Package/Impl/DataInspect/VariableGridWindowPane.cs +++ b/src/Package/Impl/DataInspect/VariableGridWindowPane.cs @@ -1,6 +1,7 @@ using System.Runtime.InteropServices; using Microsoft.VisualStudio.Imaging; using Microsoft.VisualStudio.Shell; +using static System.FormattableString; namespace Microsoft.VisualStudio.R.Package.DataInspect { [Guid("3F6855E6-E2DB-46F2-9820-EDC794FE8AFE")] @@ -8,15 +9,15 @@ public class VariableGridWindowPane : ToolWindowPane { private VariableGridHost _gridHost; public VariableGridWindowPane() { - Caption = "Variable Grid"; // TODO: temporary value + Caption = Resources.VariableGrid_Caption; Content = _gridHost = new VariableGridHost(); - BitmapImageMoniker = KnownMonikers.VariableProperty; // TODO: same icon as Variable Explorer. Is it O.K.? This appears on the tab + BitmapImageMoniker = KnownMonikers.VariableProperty; } internal void SetEvaluation(EvaluationWrapper evaluation) { - if (!string.IsNullOrWhiteSpace(evaluation.Name)) { - Caption = evaluation.Name; + if (!string.IsNullOrWhiteSpace(evaluation.Expression)) { + Caption = Invariant($"{Resources.VariableGrid_Caption}: {evaluation.Expression}"); } _gridHost.SetEvaluation(evaluation); diff --git a/src/Package/Impl/DataInspect/Variable.cs b/src/Package/Impl/DataInspect/VariableNode.cs similarity index 83% rename from src/Package/Impl/DataInspect/Variable.cs rename to src/Package/Impl/DataInspect/VariableNode.cs index 45b4785ed..8f223fcd6 100644 --- a/src/Package/Impl/DataInspect/Variable.cs +++ b/src/Package/Impl/DataInspect/VariableNode.cs @@ -5,17 +5,20 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.R.Editor.Data; +using Microsoft.R.Support.Settings.Definitions; namespace Microsoft.VisualStudio.R.Package.DataInspect { /// /// Model adapter to /// internal class VariableNode : ITreeNode { - #region member/ctor - private IRSessionDataObject _evaluation; - public VariableNode(IRSessionDataObject evaluation) { + private readonly IRToolsSettings _settings; + private EvaluationWrapper _evaluation; + + public VariableNode(IRToolsSettings settings, EvaluationWrapper evaluation) { + _settings = settings; _evaluation = evaluation; } @@ -39,8 +42,8 @@ public async Task> GetChildrenAsync(CancellationToken c result = new List(); foreach (var child in children) { - if (!child.IsHidden) { - result.Add(new VariableNode(child)); + if ((_settings != null && _settings.ShowDotPrefixedVariables) || !child.IsHidden) { + result.Add(new VariableNode(_settings, child as EvaluationWrapper)); } } } diff --git a/src/Package/Impl/DataInspect/VariableProvider.cs b/src/Package/Impl/DataInspect/VariableProvider.cs index 8ac3dfb1d..3a94db762 100644 --- a/src/Package/Impl/DataInspect/VariableProvider.cs +++ b/src/Package/Impl/DataInspect/VariableProvider.cs @@ -1,26 +1,52 @@ using System; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.Common.Core; using Microsoft.Languages.Editor.Tasks; using Microsoft.R.Debugger; using Microsoft.R.Host.Client; +using Microsoft.VisualStudio.R.Package.DataInspect.Definitions; using Microsoft.VisualStudio.R.Package.Repl; using Microsoft.VisualStudio.R.Package.Shell; +using static System.FormattableString; namespace Microsoft.VisualStudio.R.Package.DataInspect { - internal class VariableProvider: IDisposable { + [Export(typeof(IVariableDataProvider))] + internal class VariableProvider: IVariableDataProvider, IDisposable { #region members and ctor - private IRSession _rSession; + private IRSessionProvider _rSessionProvider; + private IDebugSessionProvider _debugSessionProvider; + private DebugSession _debugSession; - public VariableProvider() { - var sessionProvider = VsAppShell.Current.ExportProvider.GetExportedValue(); - _rSession = sessionProvider.GetInteractiveWindowRSession(); - _rSession.Mutated += RSession_Mutated; + [ImportingConstructor] + public VariableProvider( + [Import(typeof(IRSessionProvider))]IRSessionProvider sessionProvider, + [Import(typeof(IDebugSessionProvider))] IDebugSessionProvider debugSessionProvider) { + if (sessionProvider == null) { + throw new ArgumentNullException("sessionProvider"); + } + if (debugSessionProvider == null) { + throw new ArgumentNullException("debugSessionProvider"); + } + + _rSessionProvider = sessionProvider; + + + RSession = sessionProvider.GetInteractiveWindowRSession(); + if (RSession == null) { + throw new InvalidOperationException(Invariant($"{nameof(IRSessionProvider)} failed to return RSession for {nameof(IVariableDataProvider)}")); + } + RSession.Mutated += RSession_Mutated; + + _debugSessionProvider = debugSessionProvider; + IdleTimeAction.Create(() => { - InitializeData().SilenceException().DoNotWait(); + PublishAllAsync().SilenceException().DoNotWait(); }, 10, typeof(VariableProvider)); } @@ -28,93 +54,120 @@ public VariableProvider() { #region Public - private static VariableProvider _instance; - /// - /// Singleton - /// - public static VariableProvider Current => _instance ?? (_instance = new VariableProvider()); + public const string GlobalEnvironmentExpression = "base::.GlobalEnv"; - public event EventHandler VariableChanged; + public IRSession RSession { get; } - public EvaluationWrapper LastEvaluation { get; private set; } + public void Dispose() { + RSession.Mutated -= RSession_Mutated; + } + #endregion - public async Task> GetGridDataAsync(string expression, GridRange gridRange) { - await TaskUtilities.SwitchToBackgroundThread(); + #region RSession related event handler - var rSession = _rSession; + private void RSession_Mutated(object sender, EventArgs e) { + PublishAllAsync().SilenceException().DoNotWait(); + } - string rows = RangeToRString(gridRange.Rows); - string columns = RangeToRString(gridRange.Columns); + #endregion + private async Task InitializeData() { + await PublishAllAsync(); + } - using (var elapsed = new Elapsed("Data:Evaluate:")) { - using (var evaluator = await rSession.BeginEvaluationAsync(false)) { - var result = await evaluator.EvaluateAsync($"rtvs:::grid.dput(rtvs:::grid.data({expression}, {rows}, {columns}))", REvaluationKind.Normal); + #region variable subscription model - if (result.ParseStatus != RParseStatus.OK || result.Error != null) { - throw new InvalidOperationException($"Grid data evaluation failed:{result}"); - } + private readonly Dictionary> _subscribers = new Dictionary>(); - var data = GridParser.Parse(result.StringResult); - data.Range = gridRange; + public VariableSubscription Subscribe( + int frameIndex, + string variableExpression, + Action executeAction) { - if (data.ValidHeaderNames - && (data.ColumnNames.Count != gridRange.Columns.Count - || data.RowNames.Count != gridRange.Rows.Count)) { - throw new InvalidOperationException("Header names lengths are different from data's length"); - } + var token = new VariableSubscriptionToken(frameIndex, variableExpression); + + var subscription = new VariableSubscription( + token, + executeAction, + Unsubscribe); - return data; + lock (_subscribers) { + List subscriptions; + if (_subscribers.TryGetValue(subscription.Token, out subscriptions)) { + subscriptions.Add(subscription); + } else { + _subscribers.Add( + token, + new List() { subscription }); } } - } - public void Dispose() { - // Only used in tests to make sure each instance - // of the variable explorer uses fresh variable provider - _rSession.Mutated -= RSession_Mutated; - _rSession = null; - _instance = null; + return subscription; } - #endregion - #region RSession related event handler - - private void RSession_Mutated(object sender, EventArgs e) { - RefreshVariableCollection().SilenceException().DoNotWait(); + public void Unsubscribe(VariableSubscription subscription) { + lock (_subscribers) { + List subscriptions; + if (_subscribers.TryGetValue(subscription.Token, out subscriptions)) { + if (!subscriptions.Remove(subscription)) { + Debug.Fail("Subscription is not found"); + } + if (subscriptions.Count == 0) { + _subscribers.Remove(subscription.Token); + } + } + } } - #endregion - private async Task InitializeData() { + private async Task PublishAllAsync() { + await TaskUtilities.SwitchToBackgroundThread(); + var debugSessionProvider = VsAppShell.Current.ExportProvider.GetExportedValue(); - _debugSession = await debugSessionProvider.GetDebugSessionAsync(_rSession); + if (_debugSession == null) { + _debugSession = await debugSessionProvider.GetDebugSessionAsync(RSession); + if (_debugSession == null) { + Debug.Fail(""); + return; + } + } + + List subsribeTasks = new List(); + lock (_subscribers) { + foreach (var kv in _subscribers) { + subsribeTasks.Add(PublishAsync(kv.Key, kv.Value)); + } + } - await RefreshVariableCollection(); + await Task.WhenAll(subsribeTasks); } - private async Task RefreshVariableCollection() { - if (_debugSession == null) { + private async Task PublishAsync(VariableSubscriptionToken token, IList subscriptions) { + if (subscriptions.Count == 0) { return; } - var stackFrames = await _debugSession.GetStackFramesAsync(); - - var globalStackFrame = stackFrames.FirstOrDefault(s => s.IsGlobal); - if (globalStackFrame != null) { - DebugEvaluationResult evaluation = await globalStackFrame.EvaluateAsync("environment()", "Global Environment"); - - LastEvaluation = new EvaluationWrapper(-1, evaluation, false); // root level doesn't truncate children and return every variables + Debug.Assert(_debugSession != null); - if (VariableChanged != null) { - VariableChanged( - this, - new VariableChangedArgs() { NewVariable = LastEvaluation }); + var stackFrames = await _debugSession.GetStackFramesAsync(); + var stackFrame = stackFrames.FirstOrDefault(f => f.Index == token.FrameIndex); + + if (stackFrame != null) { + DebugEvaluationResult evaluation = await stackFrame.EvaluateAsync(token.Expression); + + foreach (var sub in subscriptions) { + try { + var action = sub.GetExecuteAction(); + if (action != null) { + action(evaluation); + } + } catch (Exception e) { + Debug.Fail(e.ToString()); + // swallow exception and continue + } } } } - private static string RangeToRString(Range range) { - return $"{range.Start + 1}:{range.Start + range.Count}"; - } + #endregion } } diff --git a/src/Package/Impl/DataInspect/VariableSubscription.cs b/src/Package/Impl/DataInspect/VariableSubscription.cs new file mode 100644 index 000000000..771ec530a --- /dev/null +++ b/src/Package/Impl/DataInspect/VariableSubscription.cs @@ -0,0 +1,59 @@ +using System; +using System.Reflection; +using Microsoft.R.Debugger; + +namespace Microsoft.VisualStudio.R.Package.DataInspect { + /// + /// Variable subscription from + /// + public sealed class VariableSubscription : IDisposable { + WeakReference _weakReference; + private readonly MethodInfo _method; + private readonly Type _delegateType; + + Action _unsubscribe; + + public VariableSubscription( + VariableSubscriptionToken token, + Action executeAction, + Action unsubscribeAction) { + + Token = token; + + _weakReference = new WeakReference(executeAction.Target); + _method = executeAction.Method; + _delegateType = executeAction.GetType(); + + _unsubscribe = unsubscribeAction; + } + + internal VariableSubscriptionToken Token { get; } + + /// + /// R expression to evaluate variable in environment + /// + public string Expression { + get { + return Token.Expression; + } + } + + public void Dispose() { + if (_unsubscribe != null) { + _unsubscribe(this); + _unsubscribe = null; + } + } + + public Action GetExecuteAction() { + if (_method.IsStatic) { + return (Action)Delegate.CreateDelegate(_delegateType, null, _method); + } + object target = _weakReference.Target; + if (target != null) { + return (Action)Delegate.CreateDelegate(_delegateType, target, _method, false); + } + return null; + } + } +} diff --git a/src/Package/Impl/DataInspect/VariableSubscriptionToken.cs b/src/Package/Impl/DataInspect/VariableSubscriptionToken.cs new file mode 100644 index 000000000..d0c748ea0 --- /dev/null +++ b/src/Package/Impl/DataInspect/VariableSubscriptionToken.cs @@ -0,0 +1,43 @@ +using System; + +namespace Microsoft.VisualStudio.R.Package.DataInspect { + /// + /// Token to distinguish variable subscription + /// + public class VariableSubscriptionToken : IEquatable { + public VariableSubscriptionToken(int frameIndex, string variableExpression) { + if (variableExpression == null) { + throw new ArgumentNullException("variableExpression"); + } + FrameIndex = frameIndex; + Expression = variableExpression; + } + + /// + /// frame index, global environment is 0 + /// + public int FrameIndex { get; } + + /// + /// R expression to evaluate variable in environment + /// + public string Expression { get; } + + public bool Equals(VariableSubscriptionToken other) { + if (other == null) { + return false; + } + + return FrameIndex == other.FrameIndex && Expression == other.Expression; + } + + public override bool Equals(object obj) { + if (ReferenceEquals(this, obj)) return true; + return Equals(obj as VariableSubscriptionToken); + } + + public override int GetHashCode() { + return FrameIndex.GetHashCode() ^ Expression.GetHashCode(); + } + } +} diff --git a/src/Package/Impl/DataInspect/VariableView.xaml b/src/Package/Impl/DataInspect/VariableView.xaml index 9fdd15a7e..7a54ab19b 100644 --- a/src/Package/Impl/DataInspect/VariableView.xaml +++ b/src/Package/Impl/DataInspect/VariableView.xaml @@ -6,11 +6,13 @@ xmlns:pkg="clr-namespace:Microsoft.VisualStudio.R.Package" xmlns:local="clr-namespace:Microsoft.VisualStudio.R.Package.DataInspect" xmlns:imaging="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.Imaging" + xmlns:img="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Imaging" xmlns:imagecatalog="clr-namespace:Microsoft.VisualStudio.Imaging;assembly=Microsoft.VisualStudio.ImageCatalog" xmlns:vsui="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.14.0" xmlns:core="clr-namespace:System;assembly=mscorlib" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300" + img:ImageThemingUtilities.ImageBackgroundColor="{DynamicResource {x:Static vsui:EnvironmentColors.ToolWindowBackgroundColorKey}}" Foreground="{DynamicResource {x:Static vsui:CommonControlsColors.TextBoxTextBrushKey}}" HorizontalAlignment="Stretch"> @@ -80,18 +82,57 @@ Grid.Column="1" Style="{StaticResource ExpanderToggleStyle}" ClickMode="Press" IsChecked="{Binding IsExpanded, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> - + - - + + + + + + + + + + + + + + + diff --git a/src/Package/Impl/DataInspect/VariableView.xaml.cs b/src/Package/Impl/DataInspect/VariableView.xaml.cs index 33820bae9..91a120a4b 100644 --- a/src/Package/Impl/DataInspect/VariableView.xaml.cs +++ b/src/Package/Impl/DataInspect/VariableView.xaml.cs @@ -2,34 +2,44 @@ using System.Collections.Generic; using System.ComponentModel; using System.Windows.Controls; -using Microsoft.R.Editor.Data; +using Microsoft.R.Debugger; +using Microsoft.R.Support.Settings.Definitions; +using Microsoft.VisualStudio.R.Package.DataInspect.Definitions; using Microsoft.VisualStudio.R.Package.Shell; namespace Microsoft.VisualStudio.R.Package.DataInspect { public partial class VariableView : UserControl, IDisposable { + private readonly IRToolsSettings _settings; ObservableTreeNode _rootNode; + VariableSubscription _globalEnvSubscription; + + const string GlobalEnvironmentName = "Global Environment"; + + public VariableView() : this(null) { } + + public VariableView(IRToolsSettings settings) { + _settings = settings; - public VariableView() { InitializeComponent(); SortDirection = ListSortDirection.Ascending; - if (VariableProvider.Current.LastEvaluation == null) { - SetRootNode(EvaluationWrapper.Ellipsis); - } else { - SetRootNode(VariableProvider.Current.LastEvaluation); - EnvironmentName.Text = VariableProvider.Current.LastEvaluation.Name; - } - VariableProvider.Current.VariableChanged += VariableProvider_VariableChanged; + var variableProvider = VsAppShell.Current.ExportProvider.GetExportedValue(); + + SetRootNode(EvaluationWrapper.Ellipsis); + + _globalEnvSubscription = variableProvider.Subscribe(0, VariableProvider.GlobalEnvironmentExpression, OnGlobalEnvironmentEvaluation); RootTreeGrid.Sorting += RootTreeGrid_Sorting; } public void Dispose() { - // Used in tests only - VariableProvider.Current.VariableChanged -= VariableProvider_VariableChanged; + if (_globalEnvSubscription != null) { + _globalEnvSubscription.Dispose(); + _globalEnvSubscription = null; + } + RootTreeGrid.Sorting -= RootTreeGrid_Sorting; - VariableProvider.Current.Dispose(); } private void RootTreeGrid_Sorting(object sender, DataGridSortingEventArgs e) { @@ -44,21 +54,21 @@ private void RootTreeGrid_Sorting(object sender, DataGridSortingEventArgs e) { e.Handled = true; } - private void VariableProvider_VariableChanged(object sender, VariableChangedArgs e) { - VariableChanged(e.NewVariable); - } + private void OnGlobalEnvironmentEvaluation(DebugEvaluationResult result) { + var wrapper = new EvaluationWrapper(result); + + var rootNodeModel = new VariableNode(_settings, wrapper); - private void VariableChanged(EvaluationWrapper variable) { VsAppShell.Current.DispatchOnUIThread( () => { - EnvironmentName.Text = variable.Name; - _rootNode.Model = new VariableNode(variable); + EnvironmentName.Text = GlobalEnvironmentName; + _rootNode.Model = rootNodeModel; }); } - private void SetRootNode(IRSessionDataObject evaluation) { + private void SetRootNode(EvaluationWrapper evaluation) { _rootNode = new ObservableTreeNode( - new VariableNode(evaluation), + new VariableNode(_settings, evaluation), Comparer.Create(Comparison)); RootTreeGrid.ItemsSource = new TreeNodeCollection(_rootNode).ItemList; diff --git a/src/Package/Impl/DataInspect/VariableWindowPane.cs b/src/Package/Impl/DataInspect/VariableWindowPane.cs index 61d7b6366..79b2cb37a 100644 --- a/src/Package/Impl/DataInspect/VariableWindowPane.cs +++ b/src/Package/Impl/DataInspect/VariableWindowPane.cs @@ -1,4 +1,5 @@ using System.Runtime.InteropServices; +using Microsoft.R.Support.Settings; using Microsoft.VisualStudio.Imaging; using Microsoft.VisualStudio.Shell; @@ -7,7 +8,7 @@ namespace Microsoft.VisualStudio.R.Package.DataInspect { public class VariableWindowPane : ToolWindowPane { public VariableWindowPane() { Caption = Resources.VariableWindowCaption; - Content = new VariableView(); + Content = new VariableView(RToolsSettings.Current); // this value matches with icmdShowVariableExplorerWindow's Icon in VSCT file BitmapImageMoniker = KnownMonikers.VariableProperty; diff --git a/src/Package/Impl/DataInspect/VisualGrid/GridPoints.cs b/src/Package/Impl/DataInspect/VisualGrid/GridPoints.cs index d0cfe03e6..d20030016 100644 --- a/src/Package/Impl/DataInspect/VisualGrid/GridPoints.cs +++ b/src/Package/Impl/DataInspect/VisualGrid/GridPoints.cs @@ -67,14 +67,22 @@ private void OnPointChanged() { _scrolledDirection = ScrollDirection.None; } - public DeferNotification DeferChangeNotification() { - return new DeferNotification(this); + public DeferNotification DeferChangeNotification(bool suppressNotification) { + return new DeferNotification(this, suppressNotification); } - public static double MinItemWidth { get { return 20.0; } } + public static double MinItemWidth { get { return 40.0; } } public static double MinItemHeight { get { return 10.0; } } + + public double AverageItemHeight { + get { + Debug.Assert(_rowCount != 0); + return VerticalExtent / _rowCount; + } + } + private double _verticalOffset; public double VerticalOffset { get { @@ -114,6 +122,24 @@ public double HorizontalOffset { } } + public double VerticalComputedOffset { + get { + return VerticalOffset; + } + set { + VerticalOffset = value; + } + } + + public double HorizontalComputedOffset { + get { + return HorizontalOffset; + } + set { + HorizontalOffset = value; + } + } + public double VerticalExtent { get { EnsureYPositions(); @@ -134,8 +160,10 @@ public double ViewportHeight { return _viewportHeight; } set { - _viewportHeight = value; - _scrolledDirection |= ScrollDirection.Vertical; + if (!LayoutDoubleUtil.AreClose(_viewportHeight, value)) { + _viewportHeight = value; + _scrolledDirection |= ScrollDirection.Vertical; + } } } @@ -145,8 +173,10 @@ public double ViewportWidth { return _viewportWidth; } set { - _viewportWidth = value; - _scrolledDirection |= ScrollDirection.Horizontal; + if (!LayoutDoubleUtil.AreClose(_viewportWidth, value)) { + _viewportWidth = value; + _scrolledDirection |= ScrollDirection.Horizontal; + } } } @@ -156,12 +186,12 @@ public IPoints GetAccessToPoints(ScrollDirection scrollDirection) { public double xPosition(int xIndex) { EnsureXPositions(); - return _xPositions[xIndex] - HorizontalOffset; + return _xPositions[xIndex] - HorizontalComputedOffset; } public double yPosition(int yIndex) { EnsureYPositions(); - return _yPositions[yIndex] - VerticalOffset; + return _yPositions[yIndex] - VerticalComputedOffset; } public double GetWidth(int columnIndex) { @@ -188,9 +218,29 @@ public void SetHeight(int yIndex, double value) { } } - public double ColumnHeight { get; set; } + private double _columnHeaderHeight; + public double ColumnHeaderHeight { + get { + return _columnHeaderHeight; + } + set { + if (_columnHeaderHeight.LessThan(value)) { + _columnHeaderHeight = value; + } + } + } - public double RowWidth { get; set; } + private double _rowHeaderWidth; + public double RowHeaderWidth { + get { + return _rowHeaderWidth; + } + set { + if (_rowHeaderWidth.LessThan(value)) { + _rowHeaderWidth = value; + } + } + } public int xIndex(double position) { EnsureXPositions(); @@ -222,20 +272,20 @@ private void InitializeWidthAndHeight() { _height[i] = MinItemHeight; } - ColumnHeight = MinItemHeight; - RowWidth = MinItemWidth; + ColumnHeaderHeight = MinItemHeight; + RowHeaderWidth = MinItemWidth; ComputePositions(); } - public GridRange ComputeDataViewport(Rect visualViewport, ref ScrollDirection overflow) { + public GridRange ComputeDataViewport(Rect visualViewport) { int columnStart = xIndex(visualViewport.X); int rowStart = yIndex(visualViewport.Y); - Debug.Assert(HorizontalOffset >= _xPositions[columnStart]); - Debug.Assert(VerticalOffset >= _yPositions[rowStart]); + Debug.Assert(HorizontalComputedOffset >= _xPositions[columnStart]); + Debug.Assert(VerticalComputedOffset >= _yPositions[rowStart]); - double width = _xPositions[columnStart] - HorizontalOffset; + double width = _xPositions[columnStart] - HorizontalComputedOffset; int columnCount = 0; for (int c = columnStart; c < _columnCount; c++) { width += GetWidth(c); @@ -247,7 +297,6 @@ public GridRange ComputeDataViewport(Rect visualViewport, ref ScrollDirection ov if (width.LessThan(visualViewport.Width)) { for (int c = columnStart - 1; c >= 0; c--) { - overflow |= ScrollDirection.Horizontal; width += GetWidth(c); if (width.GreaterThanOrClose(visualViewport.Width)) { break; @@ -255,7 +304,7 @@ public GridRange ComputeDataViewport(Rect visualViewport, ref ScrollDirection ov } } - double height = _yPositions[rowStart] - VerticalOffset; + double height = _yPositions[rowStart] - VerticalComputedOffset; int rowEnd = rowStart; int rowCount = 0; for (int r = rowStart; r < _rowCount; r++) { @@ -268,7 +317,6 @@ public GridRange ComputeDataViewport(Rect visualViewport, ref ScrollDirection ov if (height.LessThan(visualViewport.Height)) { for (int r = rowStart - 1; r >= 0; r--) { - overflow |= ScrollDirection.Vertical; height += GetHeight(r); if (height.GreaterThanOrClose(visualViewport.Height)) { break; @@ -324,12 +372,15 @@ private void ComputeXPositions() { public class DeferNotification : IDisposable { private GridPoints _gridPoints; - public DeferNotification(GridPoints gridPoints) { + private bool _suppressNotification; + + public DeferNotification(GridPoints gridPoints, bool suppressNotification) { _gridPoints = gridPoints; + _suppressNotification = suppressNotification; } public void Dispose() { - if (_gridPoints != null) { + if (!_suppressNotification && _gridPoints != null) { _gridPoints.OnPointChanged(); _gridPoints = null; @@ -342,14 +393,14 @@ public PointAccessor(GridPoints points, ScrollDirection scrollDirection) { if (scrollDirection == ScrollDirection.Horizontal) { // column header xPosition = new Indexer(points.xPosition, NotSupportedSetter); - yPosition = new Indexer((i) => (i == 0 ? 0.0 : points.ColumnHeight), NotSupportedSetter); + yPosition = new Indexer((i) => (i == 0 ? 0.0 : points.ColumnHeaderHeight), NotSupportedSetter); Width = new Indexer(points.GetWidth, points.SetWidth); - Height = new Indexer((i) => points.ColumnHeight, (i, v) => points.ColumnHeight = v); + Height = new Indexer((i) => points.ColumnHeaderHeight, (i, v) => points.ColumnHeaderHeight = v); } else if (scrollDirection == ScrollDirection.Vertical) { // row header - xPosition = new Indexer((i) => (i == 0 ? 0.0 : points.RowWidth), NotSupportedSetter); + xPosition = new Indexer((i) => (i == 0 ? 0.0 : points.RowHeaderWidth), NotSupportedSetter); yPosition = new Indexer(points.yPosition, NotSupportedSetter); - Width = new Indexer((i) => points.RowWidth, (i, v) => points.RowWidth = v); + Width = new Indexer((i) => points.RowHeaderWidth, (i, v) => points.RowHeaderWidth = v); Height = new Indexer(points.GetHeight, points.SetHeight); } else if (scrollDirection == ScrollDirection.Both) { // data diff --git a/src/Package/Impl/DataInspect/VisualGrid/MatrixView.xaml b/src/Package/Impl/DataInspect/VisualGrid/MatrixView.xaml index 729fe384e..9d09f0941 100644 --- a/src/Package/Impl/DataInspect/VisualGrid/MatrixView.xaml +++ b/src/Package/Impl/DataInspect/VisualGrid/MatrixView.xaml @@ -7,6 +7,19 @@ xmlns:vsui="clr-namespace:Microsoft.VisualStudio.PlatformUI;assembly=Microsoft.VisualStudio.Shell.14.0" mc:Ignorable="d" d:DesignHeight="300" d:DesignWidth="300"> + + + @@ -27,16 +40,16 @@ MinWidth="10" MinHeight="10" SnapsToDevicePixels="true" Fill="{Binding HeaderBackground, RelativeSource={RelativeSource AncestorType={x:Type local:MatrixView}}}" /> - - - dataProvider) { @@ -39,6 +34,10 @@ public void Initialize(IGridProvider dataProvider) { Points = new GridPoints(dataProvider.RowCount, dataProvider.ColumnCount, Data.RenderSize); Points.PointChanged += Points_PointChanged; + ColumnHeader.Clear(); + RowHeader.Clear(); + Data.Clear(); + DataProvider = dataProvider; _scroller?.StopScroller(); @@ -48,6 +47,7 @@ public void Initialize(IGridProvider dataProvider) { // reset scroll bar position to zero HorizontalScrollBar.Value = HorizontalScrollBar.Minimum; VerticalScrollBar.Value = VerticalScrollBar.Minimum; + SetScrollBar(ScrollDirection.Both); } public void Refresh() { @@ -197,6 +197,33 @@ private void OnHeaderBackgroundPropertyChanged(Brush headerBackground) { #endregion + protected override void OnKeyDown(KeyEventArgs e) { + e.Handled = true; + if (e.Key == Key.Up) { + _scroller?.EnqueueCommand(ScrollType.LineUp, 1); + } else if (e.Key == Key.Down) { + _scroller?.EnqueueCommand(ScrollType.LineDown, 1); + } else if (e.Key == Key.Right) { + _scroller?.EnqueueCommand(ScrollType.LineRight, 1); + } else if (e.Key == Key.Left) { + _scroller?.EnqueueCommand(ScrollType.LineLeft, 1); + } else if (e.Key == Key.PageUp) { + _scroller?.EnqueueCommand(ScrollType.PageUp, 1); + } else if (e.Key == Key.PageDown) { + _scroller?.EnqueueCommand(ScrollType.PageDown, 1); + } else if (e.Key == Key.Home) { + _scroller?.EnqueueCommand(ScrollType.SetVerticalOffset, 0.0, ThumbTrack.None); + } else if (e.Key == Key.End) { + _scroller?.EnqueueCommand(ScrollType.SetVerticalOffset, 1.0, ThumbTrack.None); + } else { + e.Handled = false; + } + + if (!e.Handled) { + base.OnKeyDown(e); + } + } + protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) { base.OnRenderSizeChanged(sizeInfo); @@ -239,33 +266,43 @@ private void VerticalScrollBar_Scroll(object sender, ScrollEventArgs e) { } switch (e.ScrollEventType) { - case ScrollEventType.EndScroll: - _scroller.EnqueueCommand(ScrollType.SetVerticalOffset, e.NewValue); - break; - case ScrollEventType.First: - _scroller.EnqueueCommand(ScrollType.SetVerticalOffset, e.NewValue); - break; + // page up/down case ScrollEventType.LargeDecrement: - _scroller.EnqueueCommand(ScrollType.PageUp, e.NewValue); + _scroller.EnqueueCommand(ScrollType.PageUp, 1); break; case ScrollEventType.LargeIncrement: - _scroller.EnqueueCommand(ScrollType.PageDown, e.NewValue); - break; - case ScrollEventType.Last: - _scroller.EnqueueCommand(ScrollType.SetVerticalOffset, e.NewValue); + _scroller.EnqueueCommand(ScrollType.PageDown, 1); break; + + // line up/down case ScrollEventType.SmallDecrement: - _scroller.EnqueueCommand(ScrollType.LineUp, e.NewValue); + _scroller.EnqueueCommand(ScrollType.LineUp, 1); break; case ScrollEventType.SmallIncrement: - _scroller.EnqueueCommand(ScrollType.LineDown, e.NewValue); + _scroller.EnqueueCommand(ScrollType.LineDown, 1); break; + + // scroll to here case ScrollEventType.ThumbPosition: - _scroller.EnqueueCommand(ScrollType.SetVerticalOffset, e.NewValue); + _scroller.EnqueueCommand(ScrollType.SetVerticalOffset, ComputeVerticalOffset(e), ThumbTrack.None); break; + + // thumb drag case ScrollEventType.ThumbTrack: - _scroller.EnqueueCommand(ScrollType.SetVerticalOffset, e.NewValue); + _scroller.EnqueueCommand(ScrollType.SetVerticalOffset, ComputeVerticalOffset(e), ThumbTrack.Track); break; + case ScrollEventType.EndScroll: + _scroller.EnqueueCommand(ScrollType.SetVerticalOffset, ComputeVerticalOffset(e), ThumbTrack.End); + break; + + // home/end (scroll to limit) + case ScrollEventType.First: + _scroller.EnqueueCommand(ScrollType.SetVerticalOffset, ComputeVerticalOffset(e), ThumbTrack.None); + break; + case ScrollEventType.Last: + _scroller.EnqueueCommand(ScrollType.SetVerticalOffset, ComputeVerticalOffset(e), ThumbTrack.None); + break; + default: break; } @@ -277,50 +314,70 @@ private void HorizontalScrollBar_Scroll(object sender, ScrollEventArgs e) { } switch (e.ScrollEventType) { - case ScrollEventType.EndScroll: - _scroller.EnqueueCommand(ScrollType.SetHorizontalOffset, e.NewValue); - break; - case ScrollEventType.First: - _scroller.EnqueueCommand(ScrollType.SetHorizontalOffset, e.NewValue); - break; + // page left/right case ScrollEventType.LargeDecrement: - _scroller.EnqueueCommand(ScrollType.PageLeft, e.NewValue); + _scroller.EnqueueCommand(ScrollType.PageLeft, 1); break; case ScrollEventType.LargeIncrement: - _scroller.EnqueueCommand(ScrollType.PageRight, e.NewValue); - break; - case ScrollEventType.Last: - _scroller.EnqueueCommand(ScrollType.SetHorizontalOffset, e.NewValue); + _scroller.EnqueueCommand(ScrollType.PageRight, 1); break; + + // line left/right case ScrollEventType.SmallDecrement: - _scroller.EnqueueCommand(ScrollType.LineLeft, e.NewValue); + _scroller.EnqueueCommand(ScrollType.LineLeft, 1); break; case ScrollEventType.SmallIncrement: - _scroller.EnqueueCommand(ScrollType.LineRight, e.NewValue); + _scroller.EnqueueCommand(ScrollType.LineRight, 1); break; + + // scroll to here case ScrollEventType.ThumbPosition: - _scroller.EnqueueCommand(ScrollType.SetHorizontalOffset, e.NewValue); + _scroller.EnqueueCommand(ScrollType.SetHorizontalOffset, ComputeHorizontalOffset(e), ThumbTrack.None); break; + + // thumb drag case ScrollEventType.ThumbTrack: - _scroller.EnqueueCommand(ScrollType.SetHorizontalOffset, e.NewValue); + _scroller.EnqueueCommand(ScrollType.SetHorizontalOffset, ComputeHorizontalOffset(e), ThumbTrack.Track); + break; + case ScrollEventType.EndScroll: + _scroller.EnqueueCommand(ScrollType.SetHorizontalOffset, ComputeHorizontalOffset(e), ThumbTrack.End); break; + + // home/end (scroll to limit) + case ScrollEventType.First: + _scroller.EnqueueCommand(ScrollType.SetHorizontalOffset, ComputeHorizontalOffset(e), ThumbTrack.None); + break; + case ScrollEventType.Last: + _scroller.EnqueueCommand(ScrollType.SetHorizontalOffset, ComputeHorizontalOffset(e), ThumbTrack.None); + break; + default: break; } } private void Points_PointChanged(object sender, PointChangedEventArgs e) { - if (e.Direction.HasFlag(ScrollDirection.Horizontal)) { - double width = Data.RenderSize.Width; - HorizontalScrollBar.ViewportSize = width; - HorizontalScrollBar.Maximum = Points.HorizontalExtent - width; + SetScrollBar(e.Direction); + } + + private double ComputeVerticalOffset(ScrollEventArgs e) { + return e.NewValue / VerticalScrollBar.Maximum; + } + + private double ComputeHorizontalOffset(ScrollEventArgs e) { + return e.NewValue / HorizontalScrollBar.Maximum; + } + + private void SetScrollBar(ScrollDirection direction) { + if (direction.HasFlag(ScrollDirection.Horizontal)) { + HorizontalScrollBar.ViewportSize = Points.ViewportWidth; + HorizontalScrollBar.Maximum = Points.HorizontalExtent - Points.ViewportWidth; HorizontalScrollBar.Value = Points.HorizontalOffset; } - if (e.Direction.HasFlag(ScrollDirection.Vertical)) { - double height = Data.RenderSize.Height; - VerticalScrollBar.ViewportSize = height; - VerticalScrollBar.Maximum = Points.VerticalExtent - height; + if (direction.HasFlag(ScrollDirection.Vertical)) { + VerticalScrollBar.ViewportSize = Points.ViewportHeight; + VerticalScrollBar.Maximum = Points.VerticalExtent - Points.ViewportHeight; VerticalScrollBar.Value = Points.VerticalOffset; } } diff --git a/src/Package/Impl/DataInspect/VisualGrid/ScrollCommand.cs b/src/Package/Impl/DataInspect/VisualGrid/ScrollCommand.cs new file mode 100644 index 000000000..b10383c7a --- /dev/null +++ b/src/Package/Impl/DataInspect/VisualGrid/ScrollCommand.cs @@ -0,0 +1,35 @@ +using System; +using System.Diagnostics; +using System.Windows; + +namespace Microsoft.VisualStudio.R.Package.DataInspect { + internal class ScrollCommand { + public ScrollCommand(ScrollType code, double param) { + Debug.Assert(code != ScrollType.SizeChange + && code != ScrollType.SetHorizontalOffset + && code != ScrollType.SetVerticalOffset); + + Code = code; + Param = param; + } + + public ScrollCommand(ScrollType code, Size size) { + Debug.Assert(code == ScrollType.SizeChange); + + Code = code; + Param = size; + } + + public ScrollCommand(ScrollType code, double offset, ThumbTrack thumbtrack) { + Debug.Assert(code == ScrollType.SetHorizontalOffset + || code == ScrollType.SetVerticalOffset); + + Code = code; + Param = new Tuple(offset, thumbtrack); + } + + public ScrollType Code { get; set; } + + public object Param { get; set; } + } +} diff --git a/src/Package/Impl/DataInspect/VisualGrid/ScrollDirection.cs b/src/Package/Impl/DataInspect/VisualGrid/ScrollDirection.cs new file mode 100644 index 000000000..01ce269e4 --- /dev/null +++ b/src/Package/Impl/DataInspect/VisualGrid/ScrollDirection.cs @@ -0,0 +1,29 @@ +using System; + +namespace Microsoft.VisualStudio.R.Package.DataInspect { + /// + /// Scroll orientation of Grid + /// + [Flags] + internal enum ScrollDirection { + /// + /// grid doesn't scroll + /// + None = 0x00, + + /// + /// grid scrolls horizontally + /// + Horizontal = 0x01, + + /// + /// grid scrolls vertically + /// + Vertical = 0x02, + + /// + /// grid scrolls in vertical and horizontal direction + /// + Both = 0x03, + } +} diff --git a/src/Package/Impl/DataInspect/VisualGrid/ScrollType.cs b/src/Package/Impl/DataInspect/VisualGrid/ScrollType.cs new file mode 100644 index 000000000..283cfe266 --- /dev/null +++ b/src/Package/Impl/DataInspect/VisualGrid/ScrollType.cs @@ -0,0 +1,18 @@ +namespace Microsoft.VisualStudio.R.Package.DataInspect { + internal enum ScrollType { + Invalid, + LineUp, + LineDown, + LineLeft, + LineRight, + PageUp, + PageDown, + PageLeft, + PageRight, + SetHorizontalOffset, + SetVerticalOffset, + MouseWheel, + SizeChange, + Refresh, + } +} diff --git a/src/Package/Impl/DataInspect/VisualGrid/TextVisual.cs b/src/Package/Impl/DataInspect/VisualGrid/TextVisual.cs index f0d9f219d..f11b79258 100644 --- a/src/Package/Impl/DataInspect/VisualGrid/TextVisual.cs +++ b/src/Package/Impl/DataInspect/VisualGrid/TextVisual.cs @@ -1,5 +1,4 @@ -using System; -using System.Globalization; +using System.Globalization; using System.Windows; using System.Windows.Media; @@ -7,7 +6,7 @@ namespace Microsoft.VisualStudio.R.Package.DataInspect { public class TextVisual : DrawingVisual { public TextVisual() { - Padding = 3.0; + Margin = 3.0; } public Brush Foreground { get; set; } @@ -20,7 +19,7 @@ public TextVisual() { public int Column { get; set; } - public double Padding { get; set; } + public double Margin { get; set; } private string _text; public string Text { @@ -51,17 +50,15 @@ public FormattedText GetFormattedText() { public Size Size { get; set; } private bool _drawValid = false; - public bool Draw(Size refSize) { + public bool Draw() { if (_drawValid) return false; DrawingContext dc = RenderOpen(); try { var formattedText = GetFormattedText(); - Size = new Size( - Math.Max(refSize.Width, formattedText.Width + (2 * Padding)), - Math.Max(refSize.Height, formattedText.Height + (2 * Padding))); + Size = new Size(formattedText.Width, formattedText.Height); - dc.DrawText(formattedText, new Point(Padding, Padding)); + dc.DrawText(formattedText, new Point()); _drawValid = true; return true; } finally { @@ -74,15 +71,7 @@ public void ToggleHighlight() { _isHighlight ^= true; _drawValid = false; - Draw(Size); - } - - protected override GeometryHitTestResult HitTestCore(GeometryHitTestParameters hitTestParameters) { - return base.HitTestCore(hitTestParameters); - } - - protected override HitTestResult HitTestCore(PointHitTestParameters hitTestParameters) { - return base.HitTestCore(hitTestParameters); + Draw(); } } } diff --git a/src/Package/Impl/DataInspect/VisualGrid/ThumbTrack.cs b/src/Package/Impl/DataInspect/VisualGrid/ThumbTrack.cs new file mode 100644 index 000000000..92fa93d56 --- /dev/null +++ b/src/Package/Impl/DataInspect/VisualGrid/ThumbTrack.cs @@ -0,0 +1,7 @@ +namespace Microsoft.VisualStudio.R.Package.DataInspect { + internal enum ThumbTrack { + None, + Track, + End, + } +} diff --git a/src/Package/Impl/DataInspect/VisualGrid/VisualGrid.cs b/src/Package/Impl/DataInspect/VisualGrid/VisualGrid.cs index 92ca14600..ae0ab1aa6 100644 --- a/src/Package/Impl/DataInspect/VisualGrid/VisualGrid.cs +++ b/src/Package/Impl/DataInspect/VisualGrid/VisualGrid.cs @@ -18,8 +18,14 @@ internal class VisualGrid : FrameworkElement { public VisualGrid() { _visualChildren = new VisualCollection(this); + _gridLine = new GridLineVisual(this); + AddLogicalChild(_gridLine); + AddVisualChild(_gridLine); + ClipToBounds = true; + + Focusable = true; } public ScrollDirection ScrollDirection { get; set; } @@ -52,13 +58,22 @@ public double FontSize { private Typeface Typeface { get { if (_typeFace == null) { - // TODO: fall back when the specific typeface is not found - _typeFace = FontFamily.GetTypefaces().First(tf => tf.Style == FontStyles.Normal && tf.Weight == FontWeights.Normal && tf.Stretch == FontStretches.Normal); + _typeFace = ChooseTypeface(); } return _typeFace; } } + private Typeface ChooseTypeface() { + if (ScrollDirection == ScrollDirection.Vertical + || ScrollDirection == ScrollDirection.Horizontal) { + // TODO: fall back + return FontFamily.GetTypefaces().First(tf => tf.Style == FontStyles.Normal && tf.Weight == FontWeights.DemiBold && tf.Stretch == FontStretches.Normal); + } else { + return FontFamily.GetTypefaces().First(tf => tf.Style == FontStyles.Normal && tf.Weight == FontWeights.Normal && tf.Stretch == FontStretches.Normal); + } + } + #endregion public double GridLineThickness { @@ -114,11 +129,9 @@ internal void MeasurePoints(IPoints points, GridRange newViewport, IGrid foreach (int r in newViewport.Rows.GetEnumerable()) { var visual = _visualGrid[r, c]; - double width = points.Width[c] - GridLineThickness; - double height = points.Height[r] - GridLineThickness; - visual.Draw(new Size(width, height)); - points.Width[c] = Math.Max(width, visual.Size.Width + GridLineThickness); - points.Height[r] = Math.Max(height, visual.Size.Height + GridLineThickness); + visual.Draw(); + points.Width[c] = visual.Size.Width + (visual.Margin * 2) + GridLineThickness; + points.Height[r] = visual.Size.Height + (visual.Margin * 2) + GridLineThickness; _visualChildren.Add(_visualGrid[r, c]); } @@ -127,19 +140,25 @@ internal void MeasurePoints(IPoints points, GridRange newViewport, IGrid _dataViewport = newViewport; } + private bool alignRight = true; + internal void ArrangeVisuals(IPoints points) { foreach (int c in _dataViewport.Columns.GetEnumerable()) { foreach (int r in _dataViewport.Rows.GetEnumerable()) { var visual = _visualGrid[r, c]; + Debug.Assert(r == visual.Row && c == visual.Column); + Debug.Assert(points.Width[c] >= visual.Size.Width && points.Height[r] >= visual.Size.Height); + + double x = points.xPosition[c] + (alignRight ? (points.Width[c] - visual.Size.Width - visual.Margin - GridLineThickness) : 0.0); + double y = points.yPosition[r]; + var transform = visual.Transform as TranslateTransform; if (transform == null) { - visual.Transform = new TranslateTransform( - points.xPosition[visual.Column], - points.yPosition[visual.Row]); + visual.Transform = new TranslateTransform(x, y); } else { - transform.X = points.xPosition[visual.Column]; - transform.Y = points.yPosition[visual.Row]; + transform.X = x; + transform.Y = y; } } } @@ -160,6 +179,10 @@ protected override void OnRender(DrawingContext drawingContext) { drawingContext.DrawRectangle(Background, null, new Rect(RenderSize)); } + public void Clear() { + _visualChildren.Clear(); + } + protected override int VisualChildrenCount { get { if (_visualChildren.Count == 0) return 0; @@ -174,30 +197,4 @@ protected override Visual GetVisualChild(int index) { return _visualChildren[index - 1]; } } - - /// - /// Scroll orientation of Grid - /// - [Flags] - internal enum ScrollDirection { - /// - /// grid doesn't scroll - /// - None = 0x00, - - /// - /// grid scrolls horizontally - /// - Horizontal = 0x01, - - /// - /// grid scrolls vertically - /// - Vertical = 0x02, - - /// - /// grid scrolls in vertical and horizontal direction - /// - Both = 0x03, - } } diff --git a/src/Package/Impl/DataInspect/VisualGrid/VisualGridScroller.cs b/src/Package/Impl/DataInspect/VisualGrid/VisualGridScroller.cs index 3cd02b85c..d567c4150 100644 --- a/src/Package/Impl/DataInspect/VisualGrid/VisualGridScroller.cs +++ b/src/Package/Impl/DataInspect/VisualGrid/VisualGridScroller.cs @@ -2,6 +2,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows; @@ -67,14 +68,21 @@ internal void EnqueueCommand(ScrollType code, double param) { _scrollCommands.Add(new ScrollCommand(code, param)); } + internal void EnqueueCommand(ScrollType code, double offset, ThumbTrack track) { + _scrollCommands.Add(new ScrollCommand(code, offset, track)); + } + internal void EnqueueCommand(ScrollType code, Size size) { _scrollCommands.Add(new ScrollCommand(code, size)); } + private static ScrollType[] RepeatSkip = new ScrollType[] { ScrollType.SizeChange, ScrollType.SetHorizontalOffset, ScrollType.SetVerticalOffset, ScrollType.Refresh }; + private static ScrollType[] RepeatAccum = new ScrollType[] { ScrollType.MouseWheel, ScrollType.LineUp, ScrollType.LineDown, ScrollType.PageUp, ScrollType.PageDown, ScrollType.LineLeft, ScrollType.LineRight, ScrollType.PageLeft, ScrollType.PageRight }; + private async Task ScrollCommandsHandler(CancellationToken cancellationToken) { await TaskUtilities.SwitchToBackgroundThread(); - const int ScrollCommandUpperBound = 50; + const int ScrollCommandUpperBound = 100; List batch = new List(); foreach (var command in _scrollCommands.GetConsumingEnumerable(cancellationToken)) { @@ -83,7 +91,7 @@ private async Task ScrollCommandsHandler(CancellationToken cancellationToken) { if (_scrollCommands.Count > 0 && _scrollCommands.Count < ScrollCommandUpperBound) { // another command has been queued already. continue to next - // upperbound 50 prevents infinite loop in case scroll commands is queued fast and endlessly, which happens only in theory + // upperbound prevents infinite loop in case scroll commands is queued fast and endlessly, which happens only in theory continue; } else { for (int i = 0; i < batch.Count; i++) { @@ -91,69 +99,81 @@ private async Task ScrollCommandsHandler(CancellationToken cancellationToken) { break; } + bool execute = true; // if next command is same the current one, skip to next (new one) for optimization - if (i < (batch.Count - 1) - && ((batch[i].Code == ScrollType.SizeChange && batch[i + 1].Code == ScrollType.SizeChange) - || (batch[i].Code == ScrollType.SetHorizontalOffset && batch[i + 1].Code == ScrollType.SetHorizontalOffset) - || (batch[i].Code == ScrollType.SetVerticalOffset && batch[i + 1].Code == ScrollType.SetVerticalOffset) - || (batch[i].Code == ScrollType.Refresh && batch[i + 1].Code == ScrollType.Refresh))) { - continue; - } else { + if (i < (batch.Count - 1)) { + if (IsRepeating(batch, i, RepeatSkip)) { + execute = false; + } else if (IsRepeating(batch, i, RepeatAccum)) { + batch[i + 1].Param = (double)batch[i + 1].Param + (double)batch[i].Param; + execute = false; + } + } + + if (execute) { await ExecuteCommandAsync(batch[i], cancellationToken); } } batch.Clear(); } } catch (Exception ex) { - Trace.WriteLine(ex); + Debug.Fail(ex.ToString()); batch.Clear(); } } } - private const double LineDelta = 10.0; - private const double PageDelta = 100.0; + private bool IsRepeating(List commands, int index, ScrollType[] scrollTypes) { + return commands[index].Code == commands[index + 1].Code && scrollTypes.Contains(commands[index].Code); + } private async Task ExecuteCommandAsync(ScrollCommand cmd, CancellationToken token) { bool drawVisual = true; bool refresh = false; + bool suppress = false; switch (cmd.Code) { case ScrollType.LineUp: - Points.VerticalOffset -= LineDelta; + Points.VerticalOffset -= Points.AverageItemHeight * (double)cmd.Param; break; case ScrollType.LineDown: - Points.VerticalOffset += LineDelta; + Points.VerticalOffset += Points.AverageItemHeight * (double)cmd.Param; break; case ScrollType.LineLeft: - Points.HorizontalOffset -= LineDelta; + Points.HorizontalOffset -= Points.AverageItemHeight * (double)cmd.Param; // for horizontal line increment, use vertical size break; case ScrollType.LineRight: - Points.HorizontalOffset += LineDelta; + Points.HorizontalOffset += Points.AverageItemHeight * (double)cmd.Param; // for horizontal line increment, use vertical size break; case ScrollType.PageUp: - Points.VerticalOffset -= PageDelta; + Points.VerticalOffset -= Points.ViewportHeight * (double)cmd.Param; break; case ScrollType.PageDown: - Points.VerticalOffset += PageDelta; + Points.VerticalOffset += Points.ViewportHeight * (double)cmd.Param; break; case ScrollType.PageLeft: - Points.HorizontalOffset -= PageDelta; + Points.HorizontalOffset -= Points.ViewportWidth * (double)cmd.Param; break; case ScrollType.PageRight: - Points.HorizontalOffset += PageDelta; + Points.HorizontalOffset += Points.ViewportWidth * (double)cmd.Param; break; - case ScrollType.SetHorizontalOffset: - Points.HorizontalOffset = cmd.Param; + case ScrollType.SetHorizontalOffset: { + var args = (Tuple)cmd.Param; + Points.HorizontalOffset = args.Item1 * (Points.HorizontalExtent - Points.ViewportWidth); + suppress = args.Item2 == ThumbTrack.Track; + } break; - case ScrollType.SetVerticalOffset: - Points.VerticalOffset = cmd.Param; + case ScrollType.SetVerticalOffset: { + var args = (Tuple)cmd.Param; + Points.VerticalOffset = args.Item1 * (Points.VerticalExtent - Points.ViewportHeight); + suppress = args.Item2 == ThumbTrack.Track; + } break; case ScrollType.MouseWheel: - Points.VerticalOffset -= cmd.Param; + Points.VerticalOffset -= (double)cmd.Param; break; case ScrollType.SizeChange: - Points.ViewportWidth = cmd.Size.Width; - Points.ViewportHeight = cmd.Size.Height; + Points.ViewportWidth = ((Size)cmd.Param).Width; + Points.ViewportHeight = ((Size)cmd.Param).Height; refresh = false; break; case ScrollType.Refresh: @@ -166,36 +186,25 @@ private async Task ExecuteCommandAsync(ScrollCommand cmd, CancellationToken toke } if (drawVisual) { - await DrawVisualsAsync(refresh, token); + await DrawVisualsAsync(refresh, suppress, token); } } - private async Task DrawVisualsAsync(bool refresh, CancellationToken token) { - using (var elapsed = new Elapsed("PullDataAndDraw:")) { + private async Task DrawVisualsAsync(bool refresh, bool suppressNotification, CancellationToken token) { + Rect visualViewport = new Rect( + Points.HorizontalOffset, + Points.VerticalOffset, + Points.ViewportWidth, + Points.ViewportHeight); - ScrollDirection overflowDirection = ScrollDirection.None; + GridRange newViewport = Points.ComputeDataViewport(visualViewport); - Rect visualViewport = new Rect( - Points.HorizontalOffset, - Points.VerticalOffset, - DataGrid.RenderSize.Width, - DataGrid.RenderSize.Height); - - GridRange newViewport = Points.ComputeDataViewport(visualViewport, ref overflowDirection); - - if (newViewport.Rows.Count < 1 || newViewport.Columns.Count < 1) { - Trace.WriteLine("Either row or column data viewport is empty"); - return; - } - - // adjust Offset in case of overflow - if (overflowDirection.HasFlag(ScrollDirection.Horizontal)) { - Points.HorizontalOffset = Points.HorizontalExtent - visualViewport.Width; - } else if (overflowDirection.HasFlag(ScrollDirection.Vertical)) { - Points.VerticalOffset = Points.VerticalExtent - visualViewport.Height; - } + if (newViewport.Rows.Count < 1 || newViewport.Columns.Count < 1) { + return; + } - // pull data from provider + // pull data from provider + try { var data = await DataProvider.GetAsync(newViewport); if (!data.Grid.Range.Contains(newViewport) || !data.ColumnHeader.Range.Contains(newViewport.Columns) @@ -205,16 +214,22 @@ private async Task DrawVisualsAsync(bool refresh, CancellationToken token) { // actual drawing runs in UI thread await Task.Factory.StartNew( - () => DrawVisuals(newViewport, data, refresh, overflowDirection, visualViewport), + () => DrawVisuals(newViewport, data, refresh, visualViewport, suppressNotification), token, TaskCreationOptions.None, _ui); } + catch(OperationCanceledException) { } } - private void DrawVisuals(GridRange dataViewport, IGridData data, bool refresh, - ScrollDirection overflowDirection, Rect visualViewport) { - using (var deferal = Points.DeferChangeNotification()) { + private void DrawVisuals( + GridRange dataViewport, + IGridData data, + bool refresh, + Rect visualViewport, + bool suppressNotification) { + + using (var deferal = Points.DeferChangeNotification(suppressNotification)) { // measure points ColumnHeader?.MeasurePoints( Points.GetAccessToPoints(ColumnHeader.ScrollDirection), @@ -235,10 +250,10 @@ private void DrawVisuals(GridRange dataViewport, IGridData data, bool re refresh); // adjust Offset in case of overflow - if (overflowDirection.HasFlag(ScrollDirection.Horizontal)) { + if ((Points.HorizontalOffset + visualViewport.Width).GreaterThanOrClose(Points.HorizontalExtent)) { Points.HorizontalOffset = Points.HorizontalExtent - visualViewport.Width; } - if (overflowDirection.HasFlag(ScrollDirection.Vertical)) { + if ((Points.VerticalOffset + visualViewport.Height).GreaterThanOrClose(Points.VerticalExtent)) { Points.VerticalOffset = Points.VerticalExtent - visualViewport.Height; } @@ -246,49 +261,10 @@ private void DrawVisuals(GridRange dataViewport, IGridData data, bool re ColumnHeader?.ArrangeVisuals(Points.GetAccessToPoints(ColumnHeader.ScrollDirection)); RowHeader?.ArrangeVisuals(Points.GetAccessToPoints(RowHeader.ScrollDirection)); DataGrid?.ArrangeVisuals(Points.GetAccessToPoints(DataGrid.ScrollDirection)); - } - } - } - - internal enum ScrollType { - Invalid, - LineUp, - LineDown, - LineLeft, - LineRight, - PageUp, - PageDown, - PageLeft, - PageRight, - SetHorizontalOffset, - SetVerticalOffset, - MouseWheel, - SizeChange, - Refresh, - } - - internal struct ScrollCommand { - private static Lazy _empty = new Lazy(() => new ScrollCommand(ScrollType.Invalid, 0)); - public static ScrollCommand Empty { get { return _empty.Value; } } - internal ScrollCommand(ScrollType code, double param) { - Debug.Assert(code != ScrollType.SizeChange); - - Code = code; - Param = param; - Size = Size.Empty; - } - - internal ScrollCommand(ScrollType code, Size size) { - Code = code; - Param = double.NaN; - Size = size; + Points.ViewportHeight = DataGrid.RenderSize.Height; + Points.ViewportWidth = DataGrid.RenderSize.Width; + } } - - public ScrollType Code { get; set; } - - public double Param { get; set; } - - public Size Size { get; set; } } } diff --git a/src/Package/Impl/Debugger/Commands/ShowDotPrefixedVariablesCommand.cs b/src/Package/Impl/Debugger/Commands/ShowDotPrefixedVariablesCommand.cs new file mode 100644 index 000000000..85dc1bb9c --- /dev/null +++ b/src/Package/Impl/Debugger/Commands/ShowDotPrefixedVariablesCommand.cs @@ -0,0 +1,36 @@ +using System; +using EnvDTE; +using Microsoft.R.Debugger.Engine; +using Microsoft.R.Editor.ContentType; +using Microsoft.R.Support.Settings; +using Microsoft.R.Support.Settings.Definitions; +using Microsoft.VisualStudio.R.Package.Commands; +using Microsoft.VisualStudio.R.Package.Shell; +using Microsoft.VisualStudio.R.Packages.R; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.TextManager.Interop; + +namespace Microsoft.VisualStudio.R.Package.Debugger.Commands { + internal class ShowDotPrefixedVariablesCommand : PackageCommand { + private readonly IRToolsSettings _settings; + + public ShowDotPrefixedVariablesCommand(IRToolsSettings settings) + : base(RGuidList.RCmdSetGuid, RPackageCommandId.icmdShowDotPrefixedVariables) { + _settings = settings; + } + + + internal override void SetStatus() { + Checked = _settings.ShowDotPrefixedVariables; + + // Only show it in the debugger context menu when debugging R code to avoid clutter. + var debugger = VsAppShell.Current.GetGlobalService().Debugger; + Enabled = Visible = debugger.CurrentStackFrame?.Language == RContentTypeDefinition.LanguageName; + } + + internal override void Handle() { + _settings.ShowDotPrefixedVariables = !_settings.ShowDotPrefixedVariables; + VsAppShell.Current.GetGlobalService().Debugger.RefreshVariableViews(); + } + } +} \ No newline at end of file diff --git a/src/Package/Impl/Debugger/RDebugSessionProvider.cs b/src/Package/Impl/Debugger/RDebugSessionProvider.cs index 1f88794c4..65444beee 100644 --- a/src/Package/Impl/Debugger/RDebugSessionProvider.cs +++ b/src/Package/Impl/Debugger/RDebugSessionProvider.cs @@ -11,14 +11,14 @@ internal class RDebugSessionProvider : IDebugSessionProvider { private readonly Dictionary _debugSessions = new Dictionary(); private readonly SemaphoreSlim _sem = new SemaphoreSlim(1, 1); - public async Task GetDebugSessionAsync(IRSession session) { + public async Task GetDebugSessionAsync(IRSession session, CancellationToken cancellationToken = default(CancellationToken)) { DebugSession debugSession; - await _sem.WaitAsync().ConfigureAwait(false); + await _sem.WaitAsync(cancellationToken).ConfigureAwait(false); try { if (!_debugSessions.TryGetValue(session, out debugSession)) { debugSession = new DebugSession(session); - await debugSession.InitializeAsync().ConfigureAwait(false); + await debugSession.InitializeAsync(cancellationToken).ConfigureAwait(false); _debugSessions.Add(session, debugSession); } diff --git a/src/Package/Impl/Definitions/IVisualComponent.cs b/src/Package/Impl/Definitions/IVisualComponent.cs new file mode 100644 index 000000000..ee1f386b3 --- /dev/null +++ b/src/Package/Impl/Definitions/IVisualComponent.cs @@ -0,0 +1,20 @@ +using System; +using System.Windows.Controls; +using Microsoft.Languages.Editor.Controller; + +namespace Microsoft.VisualStudio.R.Package.Definitions { + /// + /// Represents visual component such a control inside a tool window + /// + public interface IVisualComponent: IDisposable { + /// + /// Controller to send commands to + /// + ICommandTarget Controller { get; } + + /// + /// WPF control to embed in the tool window + /// + Control Control { get; } + } +} diff --git a/src/Package/Impl/Definitions/IVisualComponentContainer.cs b/src/Package/Impl/Definitions/IVisualComponentContainer.cs new file mode 100644 index 000000000..eeab1cf1f --- /dev/null +++ b/src/Package/Impl/Definitions/IVisualComponentContainer.cs @@ -0,0 +1,13 @@ +using System; +using System.Windows.Controls; +using Microsoft.Languages.Editor.Controller; + +namespace Microsoft.VisualStudio.R.Package.Definitions { + /// + /// Represents UI element that holds visual component + /// (typically a tool window) + /// + public interface IVisualComponentContainer where T : IVisualComponent { + T Component { get; } + } +} diff --git a/src/Package/Impl/Feedback/SendFrownCommand.cs b/src/Package/Impl/Feedback/SendFrownCommand.cs index a961bda49..4dcdaf6d4 100644 --- a/src/Package/Impl/Feedback/SendFrownCommand.cs +++ b/src/Package/Impl/Feedback/SendFrownCommand.cs @@ -14,11 +14,11 @@ public SendFrownCommand() : base(RGuidList.RCmdSetGuid, RPackageCommandId.icmdSendFrown) { } - protected override void SetStatus() { + internal override void SetStatus() { Enabled = true; } - protected override void Handle() { + internal override void Handle() { string zipPath = DiagnosticLogs.Collect(); SendMail(_disclaimer, "RTVS Frown", zipPath); } diff --git a/src/Package/Impl/Feedback/SendSmileCommand.cs b/src/Package/Impl/Feedback/SendSmileCommand.cs index 156395aa9..ec6c494b3 100644 --- a/src/Package/Impl/Feedback/SendSmileCommand.cs +++ b/src/Package/Impl/Feedback/SendSmileCommand.cs @@ -7,11 +7,11 @@ public SendSmileCommand() : base(RGuidList.RCmdSetGuid, RPackageCommandId.icmdSendSmile) { } - protected override void SetStatus() { + internal override void SetStatus() { Enabled = true; } - protected override void Handle() { + internal override void Handle() { SendMail(":-)", "RTVS Smile", null); } } diff --git a/src/Package/Impl/Help/HelpHomeCommand.cs b/src/Package/Impl/Help/HelpHomeCommand.cs index 01b07104d..e9d0c303e 100644 --- a/src/Package/Impl/Help/HelpHomeCommand.cs +++ b/src/Package/Impl/Help/HelpHomeCommand.cs @@ -11,7 +11,7 @@ namespace Microsoft.VisualStudio.R.Package.Help { internal sealed class HelpHomeCommand : Command { - public HelpHomeCommand(HelpWindowPane pane) : + public HelpHomeCommand(IHelpWindowVisualComponent component) : base(new CommandId(RGuidList.RCmdSetGuid, RPackageCommandId.icmdHelpHome)) { } diff --git a/src/Package/Impl/Help/HelpNextCommand.cs b/src/Package/Impl/Help/HelpNextCommand.cs index 952f9d351..5253b70c3 100644 --- a/src/Package/Impl/Help/HelpNextCommand.cs +++ b/src/Package/Impl/Help/HelpNextCommand.cs @@ -6,22 +6,22 @@ namespace Microsoft.VisualStudio.R.Package.Help { internal sealed class HelpNextCommand : Command { - private HelpWindowPane _pane; + private IHelpWindowVisualComponent _component; - public HelpNextCommand(HelpWindowPane pane) : + public HelpNextCommand(IHelpWindowVisualComponent component) : base(new CommandId(RGuidList.RCmdSetGuid, RPackageCommandId.icmdHelpNext)) { - _pane = pane; + _component = component; } public override CommandStatus Status(Guid group, int id) { - if (_pane.Browser != null && _pane.Browser.CanGoForward) { + if (_component.Browser != null && _component.Browser.CanGoForward) { return CommandStatus.SupportedAndEnabled; } return CommandStatus.Supported; } public override CommandResult Invoke(Guid group, int id, object inputArg, ref object outputArg) { - _pane.Browser.GoForward(); + _component.Browser.GoForward(); return CommandResult.Executed; } } diff --git a/src/Package/Impl/Help/HelpPreviousCommand.cs b/src/Package/Impl/Help/HelpPreviousCommand.cs index bf3e9c874..e79254f59 100644 --- a/src/Package/Impl/Help/HelpPreviousCommand.cs +++ b/src/Package/Impl/Help/HelpPreviousCommand.cs @@ -6,22 +6,22 @@ namespace Microsoft.VisualStudio.R.Package.Help { internal sealed class HelpPreviousCommand : Command { - private HelpWindowPane _pane; + private IHelpWindowVisualComponent _component; - public HelpPreviousCommand(HelpWindowPane pane) : + public HelpPreviousCommand(IHelpWindowVisualComponent component) : base(new CommandId(RGuidList.RCmdSetGuid, RPackageCommandId.icmdHelpPrevious)) { - _pane = pane; + _component = component; } public override CommandStatus Status(Guid group, int id) { - if (_pane.Browser != null && _pane.Browser.CanGoBack) { + if (_component.Browser != null && _component.Browser.CanGoBack) { return CommandStatus.SupportedAndEnabled; } return CommandStatus.Supported; } public override CommandResult Invoke(Guid group, int id, object inputArg, ref object outputArg) { - _pane.Browser.GoBack(); + _component.Browser.GoBack(); return CommandResult.Executed; } } diff --git a/src/Package/Impl/Help/HelpRefreshCommand.cs b/src/Package/Impl/Help/HelpRefreshCommand.cs index 92bad1f9a..3fec729f7 100644 --- a/src/Package/Impl/Help/HelpRefreshCommand.cs +++ b/src/Package/Impl/Help/HelpRefreshCommand.cs @@ -6,22 +6,22 @@ namespace Microsoft.VisualStudio.R.Package.Help { internal sealed class HelpRefreshCommand : Command { - private HelpWindowPane _pane; + private IHelpWindowVisualComponent _component; - public HelpRefreshCommand(HelpWindowPane pane) : + public HelpRefreshCommand(IHelpWindowVisualComponent component) : base(new CommandId(RGuidList.RCmdSetGuid, RPackageCommandId.icmdHelpRefresh)) { - _pane = pane; + _component = component; } public override CommandStatus Status(Guid group, int id) { - if (_pane.Browser != null && _pane.Browser.Source != null) { + if (_component.Browser != null && _component.Browser.Url != null) { return CommandStatus.SupportedAndEnabled; } return CommandStatus.Supported; } public override CommandResult Invoke(Guid group, int id, object inputArg, ref object outputArg) { - _pane.Browser.Refresh(); + _component.Browser.Refresh(); return CommandResult.Executed; } } diff --git a/src/Package/Impl/Help/HelpWindowPane.cs b/src/Package/Impl/Help/HelpWindowPane.cs index 73b6bde5d..068652af1 100644 --- a/src/Package/Impl/Help/HelpWindowPane.cs +++ b/src/Package/Impl/Help/HelpWindowPane.cs @@ -1,174 +1,39 @@ using System; -using System.Collections.Generic; using System.ComponentModel.Design; -using System.Diagnostics; using System.Runtime.InteropServices; -using System.Windows.Controls; -using System.Windows.Navigation; -using Microsoft.Languages.Editor.Controller; -using Microsoft.R.Host.Client; -using Microsoft.R.Support.Settings; -using Microsoft.R.Support.Settings.Definitions; using Microsoft.VisualStudio.Imaging; using Microsoft.VisualStudio.R.Package.Commands; +using Microsoft.VisualStudio.R.Package.Definitions; using Microsoft.VisualStudio.R.Package.Interop; -using Microsoft.VisualStudio.R.Package.Repl; -using Microsoft.VisualStudio.R.Package.Shell; -using Microsoft.VisualStudio.R.Package.Utilities; using Microsoft.VisualStudio.R.Packages.R; using Microsoft.VisualStudio.Shell; -using Microsoft.VisualStudio.Shell.Interop; namespace Microsoft.VisualStudio.R.Package.Help { [Guid(WindowGuid)] - internal class HelpWindowPane : ToolWindowPane { + internal class HelpWindowPane : ToolWindowPane, IVisualComponentContainer { internal const string WindowGuid = "9E909526-A616-43B2-A82B-FD639DCD40CB"; - private static bool _showDefaultPage; - - /// - /// Holds browser control. When R session is restarted - /// it is necessary to re-create the browser control since - /// help server changes port and current links stop working. - /// However, VS tool window doesn't like its child root - /// control changing so instead we keed content control - /// unchanged and only replace browser that is inside it. - /// - private ContentControl _windowContentControl; - - /// - /// Browser that displays help content - /// - public WebBrowser Browser { get; private set; } - private IRSession _session; + private IHelpWindowVisualComponent _component; public HelpWindowPane() { - _session = VsAppShell.Current.ExportProvider.GetExportedValue().GetInteractiveWindowRSession(); - _session.Disconnected += OnRSessionDisconnected; - _session.Connected += OnRSessionConnected; Caption = Resources.HelpWindowCaption; BitmapImageMoniker = KnownMonikers.StatusHelp; - _windowContentControl = new ContentControl(); - Content = _windowContentControl; - - CreateBrowser(_showDefaultPage); + _component = new HelpWindowVisualComponent(); + Content = _component.Control; this.ToolBar = new CommandID(RGuidList.RCmdSetGuid, RPackageCommandId.helpWindowToolBarId); - Controller c = new Controller(); - c.AddCommandSet(GetCommands()); - this.ToolBarCommandTarget = new CommandTargetToOleShim(null, c); - } - - private void OnRSessionConnected(object sender, EventArgs e) { - // Event fires on a background thread - VsAppShell.Current.DispatchOnUIThread(() => { - CreateBrowser(showDefaultPage: false); - }); - } - - private void OnRSessionDisconnected(object sender, EventArgs e) { - // Event fires on a background thread - VsAppShell.Current.DispatchOnUIThread(() => { - CloseBrowser(); - }); - } - - private void CreateBrowser(bool showDefaultPage = false) { - if (Browser == null) { - Browser = new WebBrowser(); - Browser.Navigating += OnNavigating; - Browser.Navigated += OnNavigated; - - _windowContentControl.Content = Browser; - if (showDefaultPage) { - HelpHomeCommand.ShowDefaultHelpPage(); - } - } - } - - private void OnNavigating(object sender, NavigatingCancelEventArgs e) { - string url = e.Uri.ToString(); - if (!IsHelpUrl(url)) { - e.Cancel = true; - Process.Start(url); - } - } - - private void OnNavigated(object sender, NavigationEventArgs e) { - // Upon vavigation we need to ask VS to update UI so - // Back /Forward buttons become properly enabled or disabled. - IVsUIShell shell = VsAppShell.Current.GetGlobalService(typeof(SVsUIShell)); - shell.UpdateCommandUI(1); - } - - private void NavigateTo(string url) { - if (Browser != null) { - Browser.Navigate(url); - } - } - - public static void Navigate(string url) { - // Filter out localhost help URL from absolute URLs - // except when the URL is the main landing page. - if (RToolsSettings.Current.HelpBrowser == HelpBrowserType.Automatic && IsHelpUrl(url)) { - // When control is just being created don't navigate - // to the default page since it will be replaced by - // the specific help page right away. - _showDefaultPage = false; - HelpWindowPane pane = ToolWindowUtilities.ShowWindowPane(0, focus: false); - if (pane != null) { - pane.NavigateTo(url); - return; - } - } - Process.Start(url); - } - - private static bool IsHelpUrl(string url) { - Uri uri = new Uri(url); - // dynamicHelp.R (startDynamicHelp function): - // # Choose 10 random port numbers between 10000 and 32000 - // ports <- 10000 + 22000*((stats::runif(10) + unclass(Sys.time())/300) %% 1) - return uri.IsLoopback && uri.Port >= 10000 && uri.Port <= 32000 && !string.IsNullOrEmpty(uri.PathAndQuery); + this.ToolBarCommandTarget = new CommandTargetToOleShim(null, _component.Controller); } - private IEnumerable GetCommands() { - List commands = new List() { - new HelpPreviousCommand(this), - new HelpNextCommand(this), - new HelpHomeCommand(this), - new HelpRefreshCommand(this) - }; - return commands; - } + public IHelpWindowVisualComponent Component => _component; protected override void Dispose(bool disposing) { - if (disposing) { - DisconnectFromSessionEvents(); - CloseBrowser(); + if (disposing && _component != null) { + _component.Dispose(); + _component = null; } base.Dispose(disposing); } - - - private void DisconnectFromSessionEvents() { - if (_session != null) { - _session.Disconnected -= OnRSessionDisconnected; - _session.Connected -= OnRSessionConnected; - _session = null; - } - } - - private void CloseBrowser() { - _windowContentControl.Content = null; - - if (Browser != null) { - Browser.Navigating -= OnNavigating; - Browser.Navigated -= OnNavigated; - Browser.Dispose(); - Browser = null; - } - } } } diff --git a/src/Package/Impl/Help/HelpWindowVisualComponent.cs b/src/Package/Impl/Help/HelpWindowVisualComponent.cs new file mode 100644 index 000000000..f46e01795 --- /dev/null +++ b/src/Package/Impl/Help/HelpWindowVisualComponent.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using System.Windows.Forms; +using System.Windows.Forms.Integration; +using System.Windows.Threading; +using Microsoft.Languages.Editor.Controller; +using Microsoft.R.Host.Client; +using Microsoft.R.Support.Settings; +using Microsoft.R.Support.Settings.Definitions; +using Microsoft.VisualStudio.R.Package.Repl; +using Microsoft.VisualStudio.R.Package.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using ContentControl = System.Windows.Controls.ContentControl; + +namespace Microsoft.VisualStudio.R.Package.Help { + public sealed class HelpWindowVisualComponent : IHelpWindowVisualComponent { + private static bool _showDefaultPage; + + /// + /// Holds browser control. When R session is restarted + /// it is necessary to re-create the browser control since + /// help server changes port and current links stop working. + /// However, VS tool window doesn't like its child root + /// control changing so instead we keed content control + /// unchanged and only replace browser that is inside it. + /// + private ContentControl _windowContentControl; + private IRSession _session; + Thread _creatorThread; + + public HelpWindowVisualComponent() { + _session = VsAppShell.Current.ExportProvider.GetExportedValue().GetInteractiveWindowRSession(); + _session.Disconnected += OnRSessionDisconnected; + _session.Connected += OnRSessionConnected; + + _windowContentControl = new System.Windows.Controls.ContentControl(); + this.Control = _windowContentControl; + _creatorThread = Thread.CurrentThread; + + CreateBrowser(_showDefaultPage); + + var c = new Controller(); + c.AddCommandSet(GetCommands()); + this.Controller = c; + } + + private void OnRSessionConnected(object sender, EventArgs e) { + // Event fires on a background thread + Dispatcher.FromThread(_creatorThread).InvokeAsync(() => { + CreateBrowser(showDefaultPage: false); + }); + } + + #region IVisualComponent + public ICommandTarget Controller { get; } + + public System.Windows.Controls.Control Control { get; } + #endregion + + #region IHelpWindowVisualComponent + /// + /// Browser that displays help content + /// + public WebBrowser Browser { get; private set; } + + public void Navigate(string url) { + // Filter out localhost help URL from absolute URLs + // except when the URL is the main landing page. + if (RToolsSettings.Current.HelpBrowser == HelpBrowserType.Automatic && IsHelpUrl(url)) { + // When control is just being created don't navigate + // to the default page since it will be replaced by + // the specific help page right away. + _showDefaultPage = false; + NavigateTo(url); + } else { + Process.Start(url); + } + } + #endregion + + private void OnRSessionDisconnected(object sender, EventArgs e) { + // Event fires on a background thread + Dispatcher.FromThread(_creatorThread).InvokeAsync(() => { + CloseBrowser(); + }); + } + + private void CreateBrowser(bool showDefaultPage = false) { + if (Browser == null) { + Browser = new WebBrowser(); + + Browser.WebBrowserShortcutsEnabled = true; + Browser.IsWebBrowserContextMenuEnabled = true; + + Browser.Navigating += OnNavigating; + Browser.Navigated += OnNavigated; + + var host = new WindowsFormsHost(); + host.Child = Browser; + + _windowContentControl.Content = host; + if (showDefaultPage) { + HelpHomeCommand.ShowDefaultHelpPage(); + } + } + } + + private void OnNavigating(object sender, WebBrowserNavigatingEventArgs e) { + string url = e.Url.ToString(); + if (!IsHelpUrl(url)) { + e.Cancel = true; + Process.Start(url); + } + } + + private void OnNavigated(object sender, WebBrowserNavigatedEventArgs e) { + // Upon vavigation we need to ask VS to update UI so + // Back /Forward buttons become properly enabled or disabled. + IVsUIShell shell = VsAppShell.Current.GetGlobalService(typeof(SVsUIShell)); + shell.UpdateCommandUI(1); + } + + private void NavigateTo(string url) { + if (Browser != null) { + Browser.Navigate(url); + } + } + + private static bool IsHelpUrl(string url) { + Uri uri = new Uri(url); + // dynamicHelp.R (startDynamicHelp function): + // # Choose 10 random port numbers between 10000 and 32000 + // ports <- 10000 + 22000*((stats::runif(10) + unclass(Sys.time())/300) %% 1) + return uri.IsLoopback && uri.Port >= 10000 && uri.Port <= 32000 && !string.IsNullOrEmpty(uri.PathAndQuery); + } + + private IEnumerable GetCommands() { + List commands = new List() { + new HelpPreviousCommand(this), + new HelpNextCommand(this), + new HelpHomeCommand(this), + new HelpRefreshCommand(this) + }; + return commands; + } + + public void Dispose() { + DisconnectFromSessionEvents(); + CloseBrowser(); + } + + + private void DisconnectFromSessionEvents() { + if (_session != null) { + _session.Disconnected -= OnRSessionDisconnected; + _session.Connected -= OnRSessionConnected; + _session = null; + } + } + + private void CloseBrowser() { + _windowContentControl.Content = null; + + if (Browser != null) { + Browser.Navigating -= OnNavigating; + Browser.Navigated -= OnNavigated; + Browser.Dispose(); + Browser = null; + } + } + } +} diff --git a/src/Package/Impl/Help/IHelpWindowVisualComponent.cs b/src/Package/Impl/Help/IHelpWindowVisualComponent.cs new file mode 100644 index 000000000..ef0242946 --- /dev/null +++ b/src/Package/Impl/Help/IHelpWindowVisualComponent.cs @@ -0,0 +1,13 @@ +using System.Windows.Forms; +using Microsoft.VisualStudio.R.Package.Definitions; + +namespace Microsoft.VisualStudio.R.Package.Help { + public interface IHelpWindowVisualComponent : IVisualComponent { + /// + /// Browser that displays help content + /// + WebBrowser Browser { get; } + + void Navigate(string url); + } +} diff --git a/src/Package/Impl/Help/ShowHelpOnCurrentCommand.cs b/src/Package/Impl/Help/ShowHelpOnCurrentCommand.cs index a9f56dc68..ddf420c83 100644 --- a/src/Package/Impl/Help/ShowHelpOnCurrentCommand.cs +++ b/src/Package/Impl/Help/ShowHelpOnCurrentCommand.cs @@ -3,6 +3,7 @@ using System.Globalization; using Microsoft.Common.Core; using Microsoft.R.Core.Tokens; +using Microsoft.R.Editor.ContentType; using Microsoft.R.Host.Client; using Microsoft.VisualStudio.R.Package.Commands; using Microsoft.VisualStudio.R.Package.Repl; @@ -24,10 +25,16 @@ namespace Microsoft.VisualStudio.R.Package.Help { /// internal sealed class ShowHelpOnCurrentCommand : PackageCommand { private const int MaxHelpItemLength = 128; - public ShowHelpOnCurrentCommand() : - base(RGuidList.RCmdSetGuid, RPackageCommandId.icmdHelpOnCurrent) { } + public ShowHelpOnCurrentCommand(IRSessionProvider sessionProvider, IActiveWpfTextViewTracker textViewTracker) : + base(RGuidList.RCmdSetGuid, RPackageCommandId.icmdHelpOnCurrent) { + _sessionProvider = sessionProvider; + _textViewTracker = textViewTracker; + } + + private IRSessionProvider _sessionProvider; + private IActiveWpfTextViewTracker _textViewTracker; - protected override void SetStatus() { + internal override void SetStatus() { string item = GetItemUnderCaret(); if (!string.IsNullOrEmpty(item)) { Enabled = true; @@ -37,10 +44,9 @@ protected override void SetStatus() { } } - protected override async void Handle() { + internal override async void Handle() { try { - var rSessionProvider = VsAppShell.Current.ExportProvider.GetExportedValue(); - IRSession session = rSessionProvider.GetInteractiveWindowRSession(); + IRSession session = _sessionProvider.GetInteractiveWindowRSession(); if (session != null) { // Fetch identifier under the cursor string item = GetItemUnderCaret(); @@ -95,7 +101,7 @@ protected override async void Handle() { } private string GetItemUnderCaret() { - ITextView textView = ViewUtilities.ActiveTextView; + ITextView textView = _textViewTracker.GetLastActiveTextView(RContentTypeDefinition.ContentType); if (textView != null && !textView.Caret.InVirtualSpace) { SnapshotPoint position = textView.Caret.Position.BufferPosition; ITextSnapshotLine line = position.GetContainingLine(); diff --git a/src/Package/Impl/History/Commands/CopySelectedHistoryCommand.cs b/src/Package/Impl/History/Commands/CopySelectedHistoryCommand.cs index 524880860..02b41308e 100644 --- a/src/Package/Impl/History/Commands/CopySelectedHistoryCommand.cs +++ b/src/Package/Impl/History/Commands/CopySelectedHistoryCommand.cs @@ -18,7 +18,7 @@ public CopySelectedHistoryCommand(ITextView textView, IRHistoryProvider historyP } public override CommandStatus Status(Guid guid, int id) { - return ReplWindow.ReplWindowExists() ? CommandStatus.SupportedAndEnabled : CommandStatus.Supported; + return ReplWindow.ReplWindowExists ? CommandStatus.SupportedAndEnabled : CommandStatus.Supported; } public override CommandResult Invoke(Guid group, int id, object inputArg, ref object outputArg) { diff --git a/src/Package/Impl/History/Commands/DeleteAllHistoryEntriesCommand.cs b/src/Package/Impl/History/Commands/DeleteAllHistoryEntriesCommand.cs index abd0bf46e..9f9c03ff9 100644 --- a/src/Package/Impl/History/Commands/DeleteAllHistoryEntriesCommand.cs +++ b/src/Package/Impl/History/Commands/DeleteAllHistoryEntriesCommand.cs @@ -18,7 +18,7 @@ public DeleteAllHistoryEntriesCommand(ITextView textView, IRHistoryProvider hist } public override CommandStatus Status(Guid guid, int id) { - return ReplWindow.ReplWindowExists() && _history.HasEntries + return ReplWindow.ReplWindowExists && _history.HasEntries ? CommandStatus.SupportedAndEnabled : CommandStatus.Supported; } diff --git a/src/Package/Impl/History/Commands/DeleteSelectedHistoryEntriesCommand.cs b/src/Package/Impl/History/Commands/DeleteSelectedHistoryEntriesCommand.cs index 771ed3cdd..8217f9e21 100644 --- a/src/Package/Impl/History/Commands/DeleteSelectedHistoryEntriesCommand.cs +++ b/src/Package/Impl/History/Commands/DeleteSelectedHistoryEntriesCommand.cs @@ -18,7 +18,7 @@ public DeleteSelectedHistoryEntriesCommand(ITextView textView, IRHistoryProvider } public override CommandStatus Status(Guid guid, int id) { - return ReplWindow.ReplWindowExists() && _history.HasSelectedEntries + return ReplWindow.ReplWindowExists && _history.HasSelectedEntries ? CommandStatus.SupportedAndEnabled : CommandStatus.Supported; } diff --git a/src/Package/Impl/History/Commands/HistoryWindowVsStd2KCmdIdDown.cs b/src/Package/Impl/History/Commands/HistoryWindowVsStd2KCmdIdDown.cs new file mode 100644 index 000000000..ba8eba113 --- /dev/null +++ b/src/Package/Impl/History/Commands/HistoryWindowVsStd2KCmdIdDown.cs @@ -0,0 +1,15 @@ +using System; +using Microsoft.Languages.Editor; +using Microsoft.VisualStudio.Text.Editor; + +namespace Microsoft.VisualStudio.R.Package.History.Commands { + internal class HistoryWindowVsStd2KCmdIdDown : NavigationCommandBase { + public HistoryWindowVsStd2KCmdIdDown(ITextView textView, IRHistoryProvider historyProvider) + : base(textView, historyProvider, VSConstants.VSStd2KCmdID.DOWN) {} + + public override CommandResult Invoke(Guid group, int id, object inputArg, ref object outputArg) { + History.SelectNextHistoryEntry(); + return CommandResult.Executed; + } + } +} \ No newline at end of file diff --git a/src/Package/Impl/History/Commands/HistoryWindowVsStd2KCmdIdEnd.cs b/src/Package/Impl/History/Commands/HistoryWindowVsStd2KCmdIdEnd.cs new file mode 100644 index 000000000..67a9d3a50 --- /dev/null +++ b/src/Package/Impl/History/Commands/HistoryWindowVsStd2KCmdIdEnd.cs @@ -0,0 +1,15 @@ +using System; +using Microsoft.Languages.Editor; +using Microsoft.VisualStudio.Text.Editor; + +namespace Microsoft.VisualStudio.R.Package.History.Commands { + internal class HistoryWindowVsStd2KCmdIdEnd : NavigationCommandBase { + public HistoryWindowVsStd2KCmdIdEnd(ITextView textView, IRHistoryProvider historyProvider) + : base(textView, historyProvider, VSConstants.VSStd2KCmdID.END, VSConstants.VSStd2KCmdID.EOL) {} + + public override CommandResult Invoke(Guid group, int id, object inputArg, ref object outputArg) { + History.ScrollToBottom(); + return CommandResult.Executed; + } + } +} \ No newline at end of file diff --git a/src/Package/Impl/History/Commands/HistoryWindowVsStd2KCmdIdHome.cs b/src/Package/Impl/History/Commands/HistoryWindowVsStd2KCmdIdHome.cs new file mode 100644 index 000000000..6904f448e --- /dev/null +++ b/src/Package/Impl/History/Commands/HistoryWindowVsStd2KCmdIdHome.cs @@ -0,0 +1,15 @@ +using System; +using Microsoft.Languages.Editor; +using Microsoft.VisualStudio.Text.Editor; + +namespace Microsoft.VisualStudio.R.Package.History.Commands { + internal class HistoryWindowVsStd2KCmdIdHome : NavigationCommandBase { + public HistoryWindowVsStd2KCmdIdHome(ITextView textView, IRHistoryProvider historyProvider) + : base(textView, historyProvider, VSConstants.VSStd2KCmdID.HOME, VSConstants.VSStd2KCmdID.BOL) {} + + public override CommandResult Invoke(Guid group, int id, object inputArg, ref object outputArg) { + History.ScrollToTop(); + return CommandResult.Executed; + } + } +} \ No newline at end of file diff --git a/src/Package/Impl/History/Commands/HistoryWindowVsStd2KCmdIdPageDown.cs b/src/Package/Impl/History/Commands/HistoryWindowVsStd2KCmdIdPageDown.cs new file mode 100644 index 000000000..59b211bf9 --- /dev/null +++ b/src/Package/Impl/History/Commands/HistoryWindowVsStd2KCmdIdPageDown.cs @@ -0,0 +1,15 @@ +using System; +using Microsoft.Languages.Editor; +using Microsoft.VisualStudio.Text.Editor; + +namespace Microsoft.VisualStudio.R.Package.History.Commands { + internal class HistoryWindowVsStd2KCmdIdPageDown: NavigationCommandBase { + public HistoryWindowVsStd2KCmdIdPageDown(ITextView textView, IRHistoryProvider historyProvider) + : base(textView, historyProvider, VSConstants.VSStd2KCmdID.PAGEDN) {} + + public override CommandResult Invoke(Guid group, int id, object inputArg, ref object outputArg) { + History.ScrollPageDown(); + return CommandResult.Executed; + } + } +} \ No newline at end of file diff --git a/src/Package/Impl/History/Commands/HistoryWindowVsStd2KCmdIdPageUp.cs b/src/Package/Impl/History/Commands/HistoryWindowVsStd2KCmdIdPageUp.cs new file mode 100644 index 000000000..f97ee3d71 --- /dev/null +++ b/src/Package/Impl/History/Commands/HistoryWindowVsStd2KCmdIdPageUp.cs @@ -0,0 +1,15 @@ +using System; +using Microsoft.Languages.Editor; +using Microsoft.VisualStudio.Text.Editor; + +namespace Microsoft.VisualStudio.R.Package.History.Commands { + internal class HistoryWindowVsStd2KCmdIdPageUp : NavigationCommandBase { + public HistoryWindowVsStd2KCmdIdPageUp(ITextView textView, IRHistoryProvider historyProvider) + : base(textView, historyProvider, VSConstants.VSStd2KCmdID.PAGEUP) {} + + public override CommandResult Invoke(Guid group, int id, object inputArg, ref object outputArg) { + History.ScrollPageUp(); + return CommandResult.Executed; + } + } +} \ No newline at end of file diff --git a/src/Package/Impl/History/Commands/HistoryWindowVsStd2KCmdIdUp.cs b/src/Package/Impl/History/Commands/HistoryWindowVsStd2KCmdIdUp.cs new file mode 100644 index 000000000..42084a526 --- /dev/null +++ b/src/Package/Impl/History/Commands/HistoryWindowVsStd2KCmdIdUp.cs @@ -0,0 +1,15 @@ +using System; +using Microsoft.Languages.Editor; +using Microsoft.VisualStudio.Text.Editor; + +namespace Microsoft.VisualStudio.R.Package.History.Commands { + internal class HistoryWindowVsStd2KCmdIdUp : NavigationCommandBase { + public HistoryWindowVsStd2KCmdIdUp(ITextView textView, IRHistoryProvider historyProvider) + : base(textView, historyProvider, VSConstants.VSStd2KCmdID.UP) {} + + public override CommandResult Invoke(Guid group, int id, object inputArg, ref object outputArg) { + History.SelectPreviousHistoryEntry(); + return CommandResult.Executed; + } + } +} \ No newline at end of file diff --git a/src/Package/Impl/History/Commands/HistoryWindowVsStd97CmdIdSelectAllCommand.cs b/src/Package/Impl/History/Commands/HistoryWindowVsStd97CmdIdSelectAllCommand.cs index 18e34cff5..ce56fb4ab 100644 --- a/src/Package/Impl/History/Commands/HistoryWindowVsStd97CmdIdSelectAllCommand.cs +++ b/src/Package/Impl/History/Commands/HistoryWindowVsStd97CmdIdSelectAllCommand.cs @@ -19,7 +19,7 @@ public HistoryWindowVsStd97CmdIdSelectAllCommand(ITextView textView, IRHistoryPr } public override CommandStatus Status(Guid guid, int id) { - return ReplWindow.ReplWindowExists() && _history.HasEntries + return ReplWindow.ReplWindowExists && _history.HasEntries ? CommandStatus.SupportedAndEnabled : CommandStatus.Supported; } diff --git a/src/Package/Impl/History/Commands/LoadHistoryCommand.cs b/src/Package/Impl/History/Commands/LoadHistoryCommand.cs index c5b9b21dd..4fabe847e 100644 --- a/src/Package/Impl/History/Commands/LoadHistoryCommand.cs +++ b/src/Package/Impl/History/Commands/LoadHistoryCommand.cs @@ -19,7 +19,7 @@ public LoadHistoryCommand(ITextView textView, IRHistoryProvider historyProvider) } public override CommandStatus Status(Guid guid, int id) { - return ReplWindow.ReplWindowExists() ? CommandStatus.SupportedAndEnabled : CommandStatus.Supported; + return ReplWindow.ReplWindowExists ? CommandStatus.SupportedAndEnabled : CommandStatus.Supported; } public override CommandResult Invoke(Guid group, int id, object inputArg, ref object outputArg) { diff --git a/src/Package/Impl/History/Commands/NavigationCommandBase.cs b/src/Package/Impl/History/Commands/NavigationCommandBase.cs new file mode 100644 index 000000000..e169e2a16 --- /dev/null +++ b/src/Package/Impl/History/Commands/NavigationCommandBase.cs @@ -0,0 +1,29 @@ +using System; +using System.Linq; +using Microsoft.Languages.Editor; +using Microsoft.Languages.Editor.Controller.Command; +using Microsoft.VisualStudio.Text.Editor; + +namespace Microsoft.VisualStudio.R.Package.History.Commands { + internal abstract class NavigationCommandBase : ViewCommand { + protected IRHistory History { get; } + + protected NavigationCommandBase(ITextView textView, IRHistoryProvider historyProvider, VSConstants.VSStd2KCmdID id) + : base(textView, VSConstants.VSStd2K, (int) id, false) { + History = historyProvider.GetAssociatedRHistory(textView); + } + + protected NavigationCommandBase(ITextView textView, IRHistoryProvider historyProvider, params VSConstants.VSStd2KCmdID[] ids) + : base(textView, GetCommandIds(ids), false) { + History = historyProvider.GetAssociatedRHistory(textView); + } + + public override CommandStatus Status(Guid guid, int id) { + return History.HasEntries + ? CommandStatus.SupportedAndEnabled + : CommandStatus.Supported; + } + + private static CommandId[] GetCommandIds(VSConstants.VSStd2KCmdID[] ids) => ids.Select(id => new CommandId(VSConstants.VSStd2K, (int) id)).ToArray(); + } +} \ No newline at end of file diff --git a/src/Package/Impl/History/Commands/SaveHistoryCommand.cs b/src/Package/Impl/History/Commands/SaveHistoryCommand.cs index 5b9a2b1f0..1274f5338 100644 --- a/src/Package/Impl/History/Commands/SaveHistoryCommand.cs +++ b/src/Package/Impl/History/Commands/SaveHistoryCommand.cs @@ -20,7 +20,7 @@ public SaveHistoryCommand(ITextView textView, IRHistoryProvider historyProvider) } public override CommandStatus Status(Guid guid, int id) { - return ReplWindow.ReplWindowExists() && _history.HasEntries + return ReplWindow.ReplWindowExists && _history.HasEntries ? CommandStatus.SupportedAndEnabled : CommandStatus.Supported; } diff --git a/src/Package/Impl/History/Commands/SendHistoryToReplCommand.cs b/src/Package/Impl/History/Commands/SendHistoryToReplCommand.cs index c2ea51001..3b2e0512e 100644 --- a/src/Package/Impl/History/Commands/SendHistoryToReplCommand.cs +++ b/src/Package/Impl/History/Commands/SendHistoryToReplCommand.cs @@ -19,7 +19,7 @@ public SendHistoryToReplCommand(ITextView textView, IRHistoryProvider historyPro } public override CommandStatus Status(Guid guid, int id) { - return ReplWindow.ReplWindowExists() && (_history.HasSelectedEntries || !TextView.Selection.IsEmpty) + return ReplWindow.ReplWindowExists && (_history.HasSelectedEntries || !TextView.Selection.IsEmpty) ? CommandStatus.SupportedAndEnabled : CommandStatus.Supported; } diff --git a/src/Package/Impl/History/Commands/SendHistoryToSourceCommand.cs b/src/Package/Impl/History/Commands/SendHistoryToSourceCommand.cs index eef945301..f9b0640b4 100644 --- a/src/Package/Impl/History/Commands/SendHistoryToSourceCommand.cs +++ b/src/Package/Impl/History/Commands/SendHistoryToSourceCommand.cs @@ -25,7 +25,7 @@ public SendHistoryToSourceCommand(ITextView textView, IRHistoryProvider historyP } public override CommandStatus Status(Guid guid, int id) { - return ReplWindow.ReplWindowExists() && (_history.HasSelectedEntries || !TextView.Selection.IsEmpty) && GetLastActiveRTextView() != null + return ReplWindow.ReplWindowExists && (_history.HasSelectedEntries || !TextView.Selection.IsEmpty) && GetLastActiveRTextView() != null ? CommandStatus.SupportedAndEnabled : CommandStatus.Supported; } diff --git a/src/Package/Impl/History/Commands/ToggleMultilineHistorySelectionCommand.cs b/src/Package/Impl/History/Commands/ToggleMultilineHistorySelectionCommand.cs index 5fe4a39e8..36e61a950 100644 --- a/src/Package/Impl/History/Commands/ToggleMultilineHistorySelectionCommand.cs +++ b/src/Package/Impl/History/Commands/ToggleMultilineHistorySelectionCommand.cs @@ -20,7 +20,7 @@ public ToggleMultilineHistorySelectionCommand(ITextView textView, IRHistoryProvi } public override CommandStatus Status(Guid guid, int id) { - return ReplWindow.ReplWindowExists() + return ReplWindow.ReplWindowExists ? RToolsSettings.Current.MultilineHistorySelection ? CommandStatus.Latched | CommandStatus.SupportedAndEnabled : CommandStatus.SupportedAndEnabled diff --git a/src/Package/Impl/History/HistorySelectionTextAdornmentFactory.cs b/src/Package/Impl/History/HistorySelectionTextAdornmentFactory.cs index 212dddbeb..2e17b340d 100644 --- a/src/Package/Impl/History/HistorySelectionTextAdornmentFactory.cs +++ b/src/Package/Impl/History/HistorySelectionTextAdornmentFactory.cs @@ -8,12 +8,12 @@ namespace Microsoft.VisualStudio.R.Package.History { [Export(typeof(IWpfTextViewCreationListener))] [ContentType(RHistoryContentTypeDefinition.ContentType)] - [TextViewRole(PredefinedTextViewRoles.Document)] + [TextViewRole(RHistory.TextViewRole)] internal sealed class HistorySelectionTextAdornmentFactory : IWpfTextViewCreationListener { [Export(typeof(AdornmentLayerDefinition))] [Name("HistorySelectionTextAdornment")] [Order(Before = PredefinedAdornmentLayers.Outlining)] - [TextViewRole(PredefinedTextViewRoles.Document)] + [TextViewRole(RHistory.TextViewRole)] public AdornmentLayerDefinition HistorySelectionTextAdornmentLayer { get; set; } private readonly IEditorFormatMapService _editorFormatMapService; diff --git a/src/Package/Impl/History/HistoryWindowPane.cs b/src/Package/Impl/History/HistoryWindowPane.cs index 06a59b5d5..e6e8f3ece 100644 --- a/src/Package/Impl/History/HistoryWindowPane.cs +++ b/src/Package/Impl/History/HistoryWindowPane.cs @@ -40,7 +40,7 @@ protected override void OnCreate() { _history.HistoryChanged += OnHistoryChanged; _historyFiltering = _historyProvider.CreateFiltering(_history); - var textView = _history.GetOrCreateTextView(); + var textView = _historyProvider.GetOrCreateTextView(_history); Content = _textEditorFactory.CreateTextViewHost(textView, false); _commandTarget = new CommandTargetToOleShim(textView, RMainController.FromTextView(textView)); diff --git a/src/Package/Impl/History/HistoryWindowPaneMouseProcessor.cs b/src/Package/Impl/History/HistoryWindowPaneMouseProcessor.cs index 336268ced..26f6d273e 100644 --- a/src/Package/Impl/History/HistoryWindowPaneMouseProcessor.cs +++ b/src/Package/Impl/History/HistoryWindowPaneMouseProcessor.cs @@ -3,12 +3,16 @@ using System.Linq; using System.Windows; using System.Windows.Input; +using Microsoft.Common.Core.Shell; +using Microsoft.VisualStudio.R.Package.Commands.R; +using Microsoft.VisualStudio.R.Packages.R; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Formatting; namespace Microsoft.VisualStudio.R.Package.History { internal class HistoryWindowPaneMouseProcessor : MouseProcessorBase, IMouseProcessor2 { private readonly IWpfTextView _textView; + private readonly ICoreShell _coreShell; private readonly IRHistory _history; private TimeSpan _elapsedSinceLastTap; @@ -20,23 +24,10 @@ internal class HistoryWindowPaneMouseProcessor : MouseProcessorBase, IMouseProce private readonly TimeSpan _maximumElapsedDoubleTap = new TimeSpan(0, 0, 0, 0, 600); private readonly int _minimumPositionDelta = 30; - public HistoryWindowPaneMouseProcessor(IWpfTextView wpfTextView, IRHistoryProvider historyProvider) { + public HistoryWindowPaneMouseProcessor(IWpfTextView wpfTextView, IRHistoryProvider historyProvider, ICoreShell coreShell) { _textView = wpfTextView; + _coreShell = coreShell; _history = historyProvider.GetAssociatedRHistory(_textView); - - _textView.Selection.SelectionChanged += SelectionChanged; - _textView.Closed += TextViewClosed; - } - - private void TextViewClosed(object sender, EventArgs e) { - _textView.Selection.SelectionChanged -= SelectionChanged; - _textView.Closed -= TextViewClosed; - } - - private void SelectionChanged(object sender, EventArgs args) { - if (_textView.Selection.Start != _textView.Selection.End) { - _history.ClearHistoryEntrySelection(); - } } #region IMouseProcessorProvider Member Implementations @@ -45,6 +36,12 @@ public override void PreprocessMouseLeftButtonDown(MouseButtonEventArgs e) { HandleLeftButtonDown(e); } + public override void PreprocessMouseRightButtonUp(MouseButtonEventArgs e) { + var point = _textView.VisualElement.PointToScreen(GetPosition(e, _textView.VisualElement)); + _coreShell.ShowContextMenu(RGuidList.RCmdSetGuid, (int)RContextMenuId.RHistory, (int)point.X, (int)point.Y); + e.Handled = true; + } + /// /// Handles the Mouse up event /// diff --git a/src/Package/Impl/History/HistoryWindowPaneMouseProcessorProvider.cs b/src/Package/Impl/History/HistoryWindowPaneMouseProcessorProvider.cs index e033e212f..152311724 100644 --- a/src/Package/Impl/History/HistoryWindowPaneMouseProcessorProvider.cs +++ b/src/Package/Impl/History/HistoryWindowPaneMouseProcessorProvider.cs @@ -1,4 +1,5 @@ using System.ComponentModel.Composition; +using Microsoft.Languages.Editor.Shell; using Microsoft.R.Editor.ContentType; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Utilities; @@ -8,7 +9,7 @@ namespace Microsoft.VisualStudio.R.Package.History { [Name("HistoryWindowPaneMouseProcessor")] [Order(Before = "WordSelection")] [ContentType(RHistoryContentTypeDefinition.ContentType)] - [TextViewRole(PredefinedTextViewRoles.Interactive)] + [TextViewRole(RHistory.TextViewRole)] internal sealed class HistoryWindowPaneMouseProcessorProvider : IMouseProcessorProvider { private readonly IRHistoryProvider _historyProvider; @@ -18,7 +19,7 @@ public HistoryWindowPaneMouseProcessorProvider(IRHistoryProvider historyProvider } public IMouseProcessor GetAssociatedProcessor(IWpfTextView wpfTextView) { - return wpfTextView.Properties.GetOrCreateSingletonProperty(() => new HistoryWindowPaneMouseProcessor(wpfTextView, _historyProvider)); + return wpfTextView.Properties.GetOrCreateSingletonProperty(() => new HistoryWindowPaneMouseProcessor(wpfTextView, _historyProvider, EditorShell.Current)); } } } \ No newline at end of file diff --git a/src/Package/Impl/History/IRHistory.cs b/src/Package/Impl/History/IRHistory.cs index 9342e7016..04426df10 100644 --- a/src/Package/Impl/History/IRHistory.cs +++ b/src/Package/Impl/History/IRHistory.cs @@ -2,13 +2,13 @@ using System.Collections.Generic; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; -using Microsoft.VisualStudio.Text.Projection; namespace Microsoft.VisualStudio.R.Package.History { public interface IRHistory : IDisposable { - IWpfTextView GetOrCreateTextView(); + IWpfTextView GetOrCreateTextView(ITextEditorFactoryService textEditorFactory); event EventHandler SelectionChanged; + event EventHandler HistoryChanging; event EventHandler HistoryChanged; bool HasSelectedEntries { get; } bool HasEntries { get; } @@ -22,6 +22,11 @@ public interface IRHistory : IDisposable { void NextEntry(); void CopySelection(); + void ScrollToTop(); + void ScrollPageUp(); + void ScrollPageDown(); + void ScrollToBottom(); + IReadOnlyList GetAllHistoryEntrySpans(); IReadOnlyList GetSelectedHistoryEntrySpans(); string GetSelectedText(); @@ -29,6 +34,8 @@ public interface IRHistory : IDisposable { SnapshotSpan SelectHistoryEntry(int lineNumber); SnapshotSpan DeselectHistoryEntry(int lineNumber); SnapshotSpan ToggleHistoryEntrySelection(int lineNumber); + void SelectNextHistoryEntry(); + void SelectPreviousHistoryEntry(); void SelectHistoryEntries(IEnumerable lineNumbers); void SelectAllEntries(); void ClearHistoryEntrySelection(); @@ -37,7 +44,5 @@ public interface IRHistory : IDisposable { void DeleteAllHistoryEntries(); void AddToHistory(string text); - - void Workaround169159(IElisionBuffer elisionBuffer); } } \ No newline at end of file diff --git a/src/Package/Impl/History/IRHistoryProvider.cs b/src/Package/Impl/History/IRHistoryProvider.cs index 6a18c235b..bdec147e6 100644 --- a/src/Package/Impl/History/IRHistoryProvider.cs +++ b/src/Package/Impl/History/IRHistoryProvider.cs @@ -6,5 +6,6 @@ public interface IRHistoryProvider { IRHistory CreateRHistory(IRInteractive rInteractive); IRHistory GetAssociatedRHistory(ITextView textView); IRHistoryFiltering CreateFiltering(IRHistory history); + IWpfTextView GetOrCreateTextView(IRHistory history); } } \ No newline at end of file diff --git a/src/Package/Impl/History/RHistory.cs b/src/Package/Impl/History/RHistory.cs index b8f55405c..daaa4ebf8 100644 --- a/src/Package/Impl/History/RHistory.cs +++ b/src/Package/Impl/History/RHistory.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Windows; +using Microsoft.Common.Core; using Microsoft.Common.Core.Disposables; using Microsoft.Common.Core.IO; using Microsoft.Languages.Editor.EditorHelpers; @@ -12,10 +13,10 @@ using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Formatting; using Microsoft.VisualStudio.Text.Operations; -using Microsoft.VisualStudio.Text.Projection; namespace Microsoft.VisualStudio.R.Package.History { internal sealed class RHistory : IRHistory { + public const string TextViewRole = "TextViewRole"; private const string BlockSeparator = "\r\n"; private const string LineSeparator = "\u00a0"; @@ -23,7 +24,6 @@ internal sealed class RHistory : IRHistory { private readonly IRToolsSettings _settings; private readonly IEditorOperationsFactoryService _editorOperationsFactory; private readonly IRInteractive _rInteractive; - private readonly ITextEditorFactoryService _textEditorFactory; private readonly ITextBuffer _historyTextBuffer; private readonly CountdownDisposable _textBufferIsEditable; private readonly IRtfBuilderService _rtfBuilderService; @@ -36,16 +36,15 @@ internal sealed class RHistory : IRHistory { private IRHistoryEntries _entries; private IReadOnlyRegion _readOnlyRegion; - private bool _isInsideWorkaround169159; private IRHistoryEntry _currentEntry; private bool _isMultiline; + public event EventHandler HistoryChanging; public event EventHandler HistoryChanged; public event EventHandler SelectionChanged; - public RHistory(IRInteractive rInteractive, ITextEditorFactoryService textEditorFactory, ITextBuffer textBuffer, IFileSystem fileSystem, IRToolsSettings settings, IEditorOperationsFactoryService editorOperationsFactory, IRtfBuilderService rtfBuilderService, IVsUIShell vsShell, Action dispose) { + public RHistory(IRInteractive rInteractive, ITextBuffer textBuffer, IFileSystem fileSystem, IRToolsSettings settings, IEditorOperationsFactoryService editorOperationsFactory, IRtfBuilderService rtfBuilderService, IVsUIShell vsShell, Action dispose) { _rInteractive = rInteractive; - _textEditorFactory = textEditorFactory; _historyTextBuffer = textBuffer; _fileSystem = fileSystem; _settings = settings; @@ -93,19 +92,20 @@ public bool IsMultiline { } } - public IWpfTextView GetOrCreateTextView() { + public IWpfTextView GetOrCreateTextView(ITextEditorFactoryService textEditorFactory) { if (_textView != null) { return _textView; } - _textView = CreateTextView(); + _textView = CreateTextView(textEditorFactory); _textViewSelection = _textView.Selection; + _textViewSelection.SelectionChanged += TextViewSelectionChanged; _editorOperations = _editorOperationsFactory.GetEditorOperations(_textView); return _textView; } - private IWpfTextView CreateTextView() { - var textView = _textEditorFactory.CreateTextView(_historyTextBuffer); + private IWpfTextView CreateTextView(ITextEditorFactoryService textEditorFactory) { + var textView = textEditorFactory.CreateTextView(_historyTextBuffer, textEditorFactory.DefaultRoles.UnionWith(textEditorFactory.CreateTextViewRoleSet(TextViewRole))); textView.Options.SetOptionValue(DefaultTextViewHostOptions.VerticalScrollBarId, true); textView.Options.SetOptionValue(DefaultTextViewHostOptions.HorizontalScrollBarId, true); textView.Options.SetOptionValue(DefaultTextViewHostOptions.SelectionMarginId, false); @@ -122,6 +122,12 @@ private IWpfTextView CreateTextView() { return textView; } + private void TextViewSelectionChanged(object sender, EventArgs e) { + if (_textView.Selection.Start != _textView.Selection.End) { + ClearHistoryEntrySelection(); + } + } + public bool TryLoadFromFile(string path) { string[] historyLines; try { @@ -134,7 +140,12 @@ public bool TryLoadFromFile(string path) { var raiseEvent = _entries.HasSelectedEntries; DeleteAllEntries(); - CreateEntries(historyLines); + try { + CreateEntries(historyLines); + } catch (Exception) { + // Don't crash if history file is corrupted. Just exit. + return false; + } if (raiseEvent) { OnSelectionChanged(); @@ -157,7 +168,9 @@ public bool TrySaveToFile(string path) { public void SendSelectedToRepl() { var selectedText = GetSelectedText(); + ReplWindow.ShowWindow(); ReplWindow.Current.ReplaceCurrentExpression(selectedText); + ReplWindow.PositionCaretAtPrompt(); } public void SendSelectedToTextView(ITextView textView) { @@ -227,6 +240,24 @@ public void CopySelection() { Clipboard.SetDataObject(data, false); } + public void ScrollToTop() { + var snapshotPoint = new SnapshotPoint(_historyTextBuffer.CurrentSnapshot, 0); + _textView?.DisplayTextLineContainingBufferPosition(snapshotPoint, 0, ViewRelativePosition.Top); + } + + public void ScrollPageUp() { + _editorOperations.ScrollPageUp(); + } + + public void ScrollPageDown() { + _editorOperations.ScrollPageDown(); + } + + public void ScrollToBottom() { + var snapshotPoint = new SnapshotPoint(_historyTextBuffer.CurrentSnapshot, _historyTextBuffer.CurrentSnapshot.Length); + _textView?.DisplayTextLineContainingBufferPosition(snapshotPoint, 0, ViewRelativePosition.Top); + } + public IReadOnlyList GetAllHistoryEntrySpans() { if (!HasEntries) { return new List(); @@ -275,7 +306,7 @@ public string GetSelectedText() { } public SnapshotSpan SelectHistoryEntry(int lineNumber) { - var entry = GetHistoryBlockFromLineNumber(lineNumber); + var entry = GetHistoryEntryFromLineNumber(lineNumber); if (!entry.IsSelected) { entry.IsSelected = true; OnSelectionChanged(); @@ -286,7 +317,7 @@ public SnapshotSpan SelectHistoryEntry(int lineNumber) { public void SelectHistoryEntries(IEnumerable lineNumbers) { var entriesToSelect = lineNumbers - .Select(GetHistoryBlockFromLineNumber) + .Select(GetHistoryEntryFromLineNumber) .Where(entry => !entry.IsSelected) .ToList(); @@ -300,8 +331,8 @@ public void SelectHistoryEntries(IEnumerable lineNumbers) { } public SnapshotSpan DeselectHistoryEntry(int lineNumber) { - var entry = GetHistoryBlockFromLineNumber(lineNumber); - if (!entry.IsSelected) { + var entry = GetHistoryEntryFromLineNumber(lineNumber); + if (entry.IsSelected) { entry.IsSelected = false; OnSelectionChanged(); } @@ -310,12 +341,80 @@ public SnapshotSpan DeselectHistoryEntry(int lineNumber) { } public SnapshotSpan ToggleHistoryEntrySelection(int lineNumber) { - var entry = GetHistoryBlockFromLineNumber(lineNumber); + var entry = GetHistoryEntryFromLineNumber(lineNumber); entry.IsSelected = !entry.IsSelected; OnSelectionChanged(); return entry.Span.GetSpan(_historyTextBuffer.CurrentSnapshot); } + public void SelectNextHistoryEntry() { + if (!HasEntries) { + return; + } + + if (HasSelectedEntries) { + var lastSelectedEntry = _entries.GetSelectedEntries().Last(); + if (lastSelectedEntry.Next == null) { + return; + } + + ClearHistoryEntrySelection(); + lastSelectedEntry.Next.IsSelected = true; + } else { + if (_textView == null) { + return; + } + + if (_textView.Selection.IsEmpty) { + _entries.FirstOrDefault().IsSelected = true; + } else { + var entry = GetHistoryEntryFromPosition(_textView.Selection.End.Position); + if (entry.Next == null) { + return; + } + + _textView.Selection.Clear(); + entry.Next.IsSelected = true; + } + } + + OnSelectionChanged(); + } + + public void SelectPreviousHistoryEntry() { + if (!HasEntries) { + return; + } + + if (HasSelectedEntries) { + var firstSelectedEntry = _entries.GetSelectedEntries().First(); + if (firstSelectedEntry.Previous == null) { + return; + } + + ClearHistoryEntrySelection(); + firstSelectedEntry.Previous.IsSelected = true; + } else { + if (_textView == null) { + return; + } + + if (_textView.Selection.IsEmpty) { + _entries.LastOrDefault().IsSelected = true; + } else { + var entry = GetHistoryEntryFromPosition(_textView.Selection.Start.Position); + if (entry.Previous == null) { + return; + } + + _textView.Selection.Clear(); + entry.Previous.IsSelected = true; + } + } + + OnSelectionChanged(); + } + public void SelectAllEntries() { if (!HasEntries) { return; @@ -355,14 +454,15 @@ public void DeleteAllHistoryEntries() { } public void AddToHistory(string text) { - text = text.TrimEnd('\r', '\n'); - if (string.IsNullOrWhiteSpace(text)) { + text = text.RemoveWhiteSpaceLines(); + if (string.IsNullOrEmpty(text)) { return; } var hasEntries = _entries.HasEntries; var snapshot = _historyTextBuffer.CurrentSnapshot; + using (AdjustAddToHistoryScrolling()) using (EditTextBuffer()) { if (hasEntries) { snapshot = _historyTextBuffer.Insert(snapshot.Length, BlockSeparator); @@ -370,23 +470,33 @@ public void AddToHistory(string text) { var position = snapshot.Length; snapshot = _historyTextBuffer.Insert(position, text); - _entries.Add(snapshot.CreateTrackingSpan(new Span(position, text.Length), SpanTrackingMode.EdgeExclusive)); } } - //TODO: Remove this method when bug #169159 is fixed: https://devdiv.visualstudio.com/DefaultCollection/DevDiv/_workitems/edit/169159 - public void Workaround169159(IElisionBuffer elisionBuffer) { - _isInsideWorkaround169159 = true; - using (EditTextBuffer()) { - elisionBuffer.ExpandSpans(new NormalizedSpanCollection(new Span(0, _historyTextBuffer.CurrentSnapshot.Length))); - _historyTextBuffer.Insert(0, "\u200B"); // 200B is a zero-width space - _textView.Caret.MoveTo(new SnapshotPoint(_historyTextBuffer.CurrentSnapshot, 0)); - elisionBuffer.ElideSpans(new NormalizedSpanCollection(new Span(1, _historyTextBuffer.CurrentSnapshot.Length - 1))); - _historyTextBuffer.Delete(new Span(0, 1)); + private IDisposable AdjustAddToHistoryScrolling() { + if (_textView == null) { + return Disposable.Empty; + } + + var firstVisibleLine = _textView.TextViewLines.FirstOrDefault(l => l.VisibilityState == VisibilityState.FullyVisible); + var lastLine = _textView.TextViewLines.LastOrDefault(); + var moveScrollingToLastLine = firstVisibleLine == null || lastLine == null || lastLine.VisibilityState == VisibilityState.FullyVisible; + if (moveScrollingToLastLine) { + return Disposable.Create(() => { + var last = _textView.TextViewLines.LastOrDefault(); + if (last == null || last.VisibilityState == VisibilityState.FullyVisible) { + return; + } + var snapshotPoint = new SnapshotPoint(_historyTextBuffer.CurrentSnapshot, _historyTextBuffer.CurrentSnapshot.Length); + _textView.DisplayTextLineContainingBufferPosition(snapshotPoint, 0, ViewRelativePosition.Bottom); + }); } - _isInsideWorkaround169159 = false; + var offset = firstVisibleLine.Top - _textView.ViewportTop; + return Disposable.Create(() => { + _textView.DisplayTextLineContainingBufferPosition(firstVisibleLine.Start, offset, ViewRelativePosition.Top); + }); } private void CreateEntries(string[] historyLines) { @@ -449,6 +559,7 @@ private IDisposable EditTextBuffer() { } } + HistoryChanging?.Invoke(this, new EventArgs()); return _textBufferIsEditable.Increment(); } @@ -460,12 +571,6 @@ private void MakeTextBufferReadOnly() { } _currentEntry = null; - - // Don't raise event for workaround edits - if (_isInsideWorkaround169159) { - return; - } - HistoryChanged?.Invoke(this, new EventArgs()); } @@ -474,7 +579,13 @@ private void OnSelectionChanged() { _vsUiShell.UpdateCommandUI(0); } - private IRHistoryEntry GetHistoryBlockFromLineNumber(int lineNumber) { + private IRHistoryEntry GetHistoryEntryFromPosition(int position) { + var snapshot = _historyTextBuffer.CurrentSnapshot; + var line = snapshot.GetLineFromPosition(position); + return _entries.Find(b => b.Span != null && b.Span.GetSpan(snapshot).Contains(line.Extent)); + } + + private IRHistoryEntry GetHistoryEntryFromLineNumber(int lineNumber) { var snapshot = _historyTextBuffer.CurrentSnapshot; var line = snapshot.GetLineFromLineNumber(lineNumber); return _entries.Find(b => b.Span != null && b.Span.GetSpan(snapshot).Contains(line.Extent)); diff --git a/src/Package/Impl/History/RHistoryFiltering.cs b/src/Package/Impl/History/RHistoryFiltering.cs index 8e7cc22d6..095cbf4e1 100644 --- a/src/Package/Impl/History/RHistoryFiltering.cs +++ b/src/Package/Impl/History/RHistoryFiltering.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using Microsoft.Common.Core; using Microsoft.R.Support.Settings.Definitions; @@ -21,13 +22,14 @@ internal class RHistoryFiltering : IRHistoryFiltering { private string _searchPattern; - public RHistoryFiltering(IRHistory history, IRToolsSettings settings, ITextSearchService2 textSearchService) { + public RHistoryFiltering(IRHistory history, ITextView textView, IRToolsSettings settings, ITextSearchService2 textSearchService) { _history = history; + _history.HistoryChanging += HistoryChanging; _history.HistoryChanged += HistoryChanged; _settings = settings; _textSearchService = textSearchService; - _textView = _history.GetOrCreateTextView(); + _textView = textView; _textBuffer = _textView.TextDataModel.DataBuffer; IElisionBuffer elisionBuffer; @@ -49,6 +51,15 @@ public void Filter(string searchPattern) { FilterImpl(searchPattern); } + private void HistoryChanging(object sender, EventArgs args) { + if (_searchPattern == null) { + return; + } + + var span = new Span(0, _textBuffer.CurrentSnapshot.Length); + _elisionBuffer.ExpandSpans(new NormalizedSpanCollection(span)); + } + private void HistoryChanged(object sender, EventArgs args) { if (_searchPattern == null) { return; @@ -63,32 +74,57 @@ private void HistoryChanged(object sender, EventArgs args) { private void FilterImpl(string searchPattern) { var snapshot = _textBuffer.CurrentSnapshot; - var entries = _history.GetAllHistoryEntrySpans(); - var startPoints = entries.Select(e => e.Start).ToList(); - var endPoints = startPoints.Skip(1).Append(entries[entries.Count - 1].End); - var spans = startPoints.Zip(endPoints, (start, end) => new SnapshotSpan(start, end)); - - _history.ClearHistoryEntrySelection(); - _textView.Selection.Clear(); + var entrySpans = _history.GetAllHistoryEntrySpans(); + Debug.Assert(entrySpans.Any()); + + var spansToShow = new List(); + var spansToHide = new List(); + + var span = entrySpans[0]; + var showSpan = _textSearchService.Find(span, span.Start, searchPattern, FindOptions.Multiline).HasValue; + + for (var i = 1; i < entrySpans.Count; i++) { + var nextSpan = entrySpans[i]; + var showNextSpan = _textSearchService.Find(nextSpan, nextSpan.Start, searchPattern, FindOptions.Multiline).HasValue; + if (showNextSpan) { + if (showSpan) { + span = new SnapshotSpan(span.Start, nextSpan.End); + } else if (spansToShow.Any()) { + spansToHide.Add(span); + span = new SnapshotSpan(span.End, nextSpan.End); + } else { + spansToHide.Add(new SnapshotSpan(span.Start, nextSpan.Start)); + span = nextSpan; + } + showSpan = true; + } else { + if (!showSpan) { + span = new SnapshotSpan(span.Start, nextSpan.End); + } else { + spansToShow.Add(span); + span = new SnapshotSpan(span.End, nextSpan.End); + } + showSpan = false; + } + } - IList spansToShow; - IList spansToHide; - spans.Split(s => _textSearchService.Find(s, s.Start, searchPattern, FindOptions.Multiline).HasValue, s => new Span(s.Start, s.Length), out spansToShow, - out spansToHide); + // Add last span + if (showSpan) { + spansToShow.Add(span); + } else { + spansToHide.Add(span); + } if (spansToShow.Count == 0) { if (_elisionBuffer.CurrentSnapshot.Length == 0) { return; } - _history.Workaround169159(_elisionBuffer); - //TODO: Uncomment lines when bug #169159 is fixed: https://devdiv.visualstudio.com/DefaultCollection/DevDiv/_workitems/edit/169159 - //_textView.Caret.MoveTo(new SnapshotPoint(snapshot, 0)); - //_elisionBuffer.ElideSpans(new NormalizedSpanCollection(new Span(0, snapshot.Length))); + _elisionBuffer.ModifySpans(new NormalizedSpanCollection(new Span(0, snapshot.Length)), new NormalizedSpanCollection(new Span(0, 0))); return; } - _elisionBuffer.ExpandSpans(new NormalizedSpanCollection(new Span(0, _textBuffer.CurrentSnapshot.Length))); + _elisionBuffer.ExpandSpans(new NormalizedSpanCollection(new Span(0, snapshot.Length))); MoveCaretToVisiblePoint(spansToShow, snapshot); @@ -129,7 +165,6 @@ public void ClearFilter() { _searchPattern = null; var span = new Span(0, _textBuffer.CurrentSnapshot.Length); _elisionBuffer.ExpandSpans(new NormalizedSpanCollection(span)); - _textView.ViewScroller.EnsureSpanVisible(new SnapshotSpan(_textView.TextSnapshot, new Span(0, 0))); } } } \ No newline at end of file diff --git a/src/Package/Impl/History/RHistoryProvider.cs b/src/Package/Impl/History/RHistoryProvider.cs index 16c0d8aab..100940361 100644 --- a/src/Package/Impl/History/RHistoryProvider.cs +++ b/src/Package/Impl/History/RHistoryProvider.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.ComponentModel.Composition; using Microsoft.Common.Core.IO; @@ -45,13 +43,18 @@ public IRHistory GetAssociatedRHistory(ITextView textView) { } public IRHistoryFiltering CreateFiltering(IRHistory history) { - return new RHistoryFiltering(history, RToolsSettings.Current, _textSearchService); + var textView = GetOrCreateTextView(history); + return new RHistoryFiltering(history, textView, RToolsSettings.Current, _textSearchService); + } + + public IWpfTextView GetOrCreateTextView(IRHistory history) { + return history.GetOrCreateTextView(_textEditorFactory); } public IRHistory CreateRHistory(IRInteractive rInteractive) { var vsUiShell = VsAppShell.Current.GetGlobalService(typeof(SVsUIShell)); var textBuffer = _textBufferFactory.CreateTextBuffer(_contentType); - var history = new RHistory(rInteractive, _textEditorFactory, textBuffer, _fileSystem, RToolsSettings.Current, _editorOperationsFactory, _rtfBuilderService, vsUiShell, () => RemoveRHistory(textBuffer)); + var history = new RHistory(rInteractive, textBuffer, _fileSystem, RToolsSettings.Current, _editorOperationsFactory, _rtfBuilderService, vsUiShell, () => RemoveRHistory(textBuffer)); _histories[textBuffer] = history; return history; } diff --git a/src/Package/Impl/Microsoft.VisualStudio.R.Package.csproj b/src/Package/Impl/Microsoft.VisualStudio.R.Package.csproj index 656c6f772..8cf0fa675 100644 --- a/src/Package/Impl/Microsoft.VisualStudio.R.Package.csproj +++ b/src/Package/Impl/Microsoft.VisualStudio.R.Package.csproj @@ -1,864 +1,882 @@ - - - - - - Debug - AnyCPU - 14.0 - Microsoft.VisualStudio.R.Package - Microsoft.VisualStudio.R.Package - Library - SAK - SAK - SAK - SAK - - - - - - - $(RootDirectory)\obj\ - $(RootDirectory)\bin\ - $(BaseIntermediateOutputPath)\$(Configuration)\$(AssemblyName)\ - $(BaseOutputPath)\$(Configuration)\ - 2.0 - {82B43B9B-A64C-4715-B499-D71E9CA2BD60};{60DC8134-EBA5-43B8-BCC9-BB4BC16C2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - {26035FE3-25AB-45EC-BB45-7FD0B6C1D545} - Properties - v4.6 - true - true - true - false - false - true - true - - - True - True - False - Program - $(DevEnvDir)\devenv.exe - /rootsuffix Exp - true - - - Program - $(DevEnvDir)\devenv.exe - /rootsuffix Exp - false - true - - - - Properties\GlobalAssemblyInfo.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - MatrixView.xaml - - - - - - - - - - - - - - - - - - - - VariableGridHost.xaml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - VariableView.xaml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - XamlPresenter.xaml - - - - - - - - - - - - - - - - - - - - - - Component - - - Component - - - - - - - - - - - - - - - - - - - - - True - True - Resources.resx - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Designer - - - Designer - - - - PreserveNewest - - - Designer - - - - - PublicResXFileCodeGenerator - Resources.Designer.cs - Designer - - - true - VSPackage - Designer - - - - - False - - - False - - - False - - - False - - - - - - False - True - ..\..\..\lib\Microsoft.Office.Interop.Outlook.dll - - - False - - - False - ..\..\..\NugetPackages\Microsoft.VisualStudio.Composition.14.0.50417-pre\lib\net451\Microsoft.VisualStudio.Composition.dll - False - - - ..\..\..\NugetPackages\Microsoft.VisualStudio.CoreUtility.14.1.24720\lib\net45\Microsoft.VisualStudio.CoreUtility.dll - True - - - True - - - False - - - ..\..\..\NugetPackages\Microsoft.VisualStudio.ImageCatalog.14.1.24720\lib\net45\Microsoft.VisualStudio.ImageCatalog.dll - False - - - ..\..\..\NugetPackages\Microsoft.VisualStudio.Imaging.14.1.24720\lib\net45\Microsoft.VisualStudio.Imaging.dll - True - - - True - ..\..\..\NugetPackages\Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime.14.1.24720\lib\Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime.dll - True - - - False - - - ..\..\..\NugetPackages\Microsoft.VisualStudio.Language.StandardClassification.14.1.24720\lib\net45\Microsoft.VisualStudio.Language.StandardClassification.dll - True - - - ..\..\..\NugetPackages\Microsoft.VisualStudio.OLE.Interop.7.10.6070\lib\Microsoft.VisualStudio.OLE.Interop.dll - True - - - False - ..\..\..\NugetPackages\Microsoft.VisualStudio.ProjectSystem.14.0.50617-pre\lib\net451\Microsoft.VisualStudio.ProjectSystem.Utilities.v14.0.dll - False - - - False - ..\..\..\NugetPackages\Microsoft.VisualStudio.ProjectSystem.14.0.50617-pre\lib\net451\Microsoft.VisualStudio.ProjectSystem.V14Only.dll - False - - - False - - - False - - - ..\..\..\NugetPackages\Microsoft.VisualStudio.Shell.Interop.7.10.6071\lib\Microsoft.VisualStudio.Shell.Interop.dll - True - - - True - ..\..\..\NugetPackages\Microsoft.VisualStudio.Shell.Interop.10.0.10.0.30319\lib\Microsoft.VisualStudio.Shell.Interop.10.0.dll - True - - - True - ..\..\..\NugetPackages\Microsoft.VisualStudio.Shell.Interop.11.0.11.0.61030\lib\Microsoft.VisualStudio.Shell.Interop.11.0.dll - True - - - True - ..\..\..\NugetPackages\Microsoft.VisualStudio.Shell.Interop.12.0.12.0.30110\lib\Microsoft.VisualStudio.Shell.Interop.12.0.dll - True - - - True - ..\..\..\NugetPackages\Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime.14.1.24720\lib\Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime.dll - True - - - ..\..\..\NugetPackages\Microsoft.VisualStudio.Shell.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.Shell.Interop.8.0.dll - True - - - ..\..\..\NugetPackages\Microsoft.VisualStudio.Shell.Interop.9.0.9.0.30729\lib\Microsoft.VisualStudio.Shell.Interop.9.0.dll - True - - - ..\..\..\NugetPackages\Microsoft.VisualStudio.Text.Data.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.Data.dll - True - - - ..\..\..\NugetPackages\Microsoft.VisualStudio.Text.Logic.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.Logic.dll - True - - - ..\..\..\NugetPackages\Microsoft.VisualStudio.Text.UI.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.UI.dll - True - - - ..\..\..\NugetPackages\Microsoft.VisualStudio.Text.UI.Wpf.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.UI.Wpf.dll - True - - - ..\..\..\NugetPackages\Microsoft.VisualStudio.TextManager.Interop.7.10.6070\lib\Microsoft.VisualStudio.TextManager.Interop.dll - True - - - True - ..\..\..\NugetPackages\Microsoft.VisualStudio.TextManager.Interop.10.0.10.0.30319\lib\Microsoft.VisualStudio.TextManager.Interop.10.0.dll - True - - - True - True - - - True - True - - - ..\..\..\NugetPackages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll - True - - - False - - - ..\..\..\NugetPackages\Microsoft.VisualStudio.Utilities.14.1.24720\lib\net45\Microsoft.VisualStudio.Utilities.dll - True - - - - False - ..\..\..\NugetPackages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll - - - - - - ..\..\..\NugetPackages\System.Collections.Immutable.1.1.37\lib\dotnet\System.Collections.Immutable.dll - True - - - - - - - - - - ..\..\..\NugetPackages\Microsoft.Tpl.Dataflow.4.5.24\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll - True - - - - - - - - - - - - - {8D408909-459F-4853-A36C-745118F99869} - Microsoft.Common.Core - - - {9de5e0b5-c8bd-482c-85c3-b8e20f08453b} - Microsoft.Common.Wpf - - - {6d62df0d-d9c3-49a7-a693-acd0691ba69d} - Microsoft.R.Debugger.Engine - - - {17e155bf-351c-4253-b9b1-36eeea35fe3c} - Microsoft.R.Debugger - - - {E09D3BDA-2E6B-47B5-87AC-B6FC2D33DFAB} - Microsoft.R.Host.Client - - - Platform=x64 - {131842ce-daf9-4c0e-8409-4a26ef7961a7} - Microsoft.R.Host - - - {25cd8690-6208-4740-b123-6dbce6b9444a} - Microsoft.Languages.Core - BuiltProjectOutputGroup%3bBuiltProjectOutputGroupDependencies%3bGetCopyToOutputDirectoryItems%3bSatelliteDllsProjectOutputGroup%3b - DebugSymbolsProjectOutputGroup%3b - - - {62857e49-e586-4baa-ae4d-1232093e7378} - Microsoft.Languages.Editor - BuiltProjectOutputGroup%3bBuiltProjectOutputGroupDependencies%3bGetCopyToOutputDirectoryItems%3bSatelliteDllsProjectOutputGroup%3b - DebugSymbolsProjectOutputGroup%3b - - - {98e0b8ac-1193-4bfd-bf5c-6712c93abd03} - Microsoft.Markdown.Editor - - - {41AA8769-0FBC-4A80-8498-21C833F0C2AC} - Microsoft.VisualStudio.ProjectSystem.FileSystemMirroring - BuiltProjectOutputGroup%3bBuiltProjectOutputGroupDependencies%3bGetCopyToOutputDirectoryItems%3bSatelliteDllsProjectOutputGroup%3b - DebugSymbolsProjectOutputGroup%3b - - - {b68d4ad2-2dc2-473e-ab06-408172c4fb92} - Microsoft.R.Actions - - - {0c4bce1d-3cb8-4e2a-9252-58784d7f26a5} - Microsoft.R.Core - BuiltProjectOutputGroup%3bBuiltProjectOutputGroupDependencies%3bGetCopyToOutputDirectoryItems%3bSatelliteDllsProjectOutputGroup%3b - DebugSymbolsProjectOutputGroup%3b - - - {d6eeef87-ce3a-4804-a409-39966b96c850} - Microsoft.R.Editor - BuiltProjectOutputGroup%3bBuiltProjectOutputGroupDependencies%3bGetCopyToOutputDirectoryItems%3bSatelliteDllsProjectOutputGroup%3b - DebugSymbolsProjectOutputGroup%3b - - - {c1957d47-b0b4-42e0-bc08-0d5e96e47fe4} - Microsoft.R.Support - - - - - - Designer - PreserveNewest - true - - - - - - - - - - - - - - Designer - PreserveNewest - true - - - - PreserveNewest - true - - - PreserveNewest - true - - - PreserveNewest - true - - - - Designer - MSBuild:Compile - - - - - Designer - - - - - - - - - - - - Designer - - - - - - Designer - - - - - - - - - - Designer - - - - - - Designer - - - - - - Designer - - - - - - Designer - - - - - - Designer - - - - - - Designer - - - - - - Designer - - - - - - Designer - - - - - Menus.ctmenu - Designer - - - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - - - MSBuild:Compile - Designer - - - Designer - MSBuild:Compile - - - MSBuild:Compile - Designer - - - MSBuild:Compile - Designer - - - MSBuild:Compile - Designer - - - PreserveNewest - Designer - - - MSBuild:Compile - Designer - - - - - Microsoft - StrongName - - - - - - - - ItemTemplates - - - ProjectTemplates - - - - - - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - - - - - - - - - - - + + + + + + Debug + AnyCPU + 14.0 + Microsoft.VisualStudio.R.Package + Microsoft.VisualStudio.R.Package + Library + SAK + SAK + SAK + SAK + + + + + + + $(RootDirectory)\obj\ + $(RootDirectory)\bin\ + $(BaseIntermediateOutputPath)\$(Configuration)\$(AssemblyName)\ + $(BaseOutputPath)\$(Configuration)\ + 2.0 + {82B43B9B-A64C-4715-B499-D71E9CA2BD60};{60DC8134-EBA5-43B8-BCC9-BB4BC16C2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + {26035FE3-25AB-45EC-BB45-7FD0B6C1D545} + Properties + v4.6 + true + true + true + false + false + true + true + + + True + True + False + Program + $(DevEnvDir)\devenv.exe + /rootsuffix Exp + true + + + Program + $(DevEnvDir)\devenv.exe + /rootsuffix Exp + false + true + + + + Properties\GlobalAssemblyInfo.cs + + + + + + + + + + + + + + + + + + + + + + + + + + MatrixView.xaml + + + + + + + + + + + + + + + + + + + + + + + + + + + VariableGridHost.xaml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + VariableView.xaml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + XamlPresenter.xaml + + + + + + + + + + + + + + + + + + + + + + Component + + + Component + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Designer + + + Designer + + + + PreserveNewest + + + Designer + + + + + PublicResXFileCodeGenerator + Resources.Designer.cs + Designer + + + true + VSPackage + Designer + + + + + False + + + False + + + False + + + False + + + + + + False + True + ..\..\..\lib\Microsoft.Office.Interop.Excel.dll + + + False + True + ..\..\..\lib\Microsoft.Office.Interop.Outlook.dll + + + False + + + False + ..\..\..\NugetPackages\Microsoft.VisualStudio.Composition.14.0.50417-pre\lib\net451\Microsoft.VisualStudio.Composition.dll + False + + + ..\..\..\NugetPackages\Microsoft.VisualStudio.CoreUtility.14.1.24720\lib\net45\Microsoft.VisualStudio.CoreUtility.dll + True + + + True + + + False + + + ..\..\..\NugetPackages\Microsoft.VisualStudio.ImageCatalog.14.1.24720\lib\net45\Microsoft.VisualStudio.ImageCatalog.dll + False + + + ..\..\..\NugetPackages\Microsoft.VisualStudio.Imaging.14.1.24720\lib\net45\Microsoft.VisualStudio.Imaging.dll + True + + + True + ..\..\..\NugetPackages\Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime.14.1.24720\lib\Microsoft.VisualStudio.Imaging.Interop.14.0.DesignTime.dll + True + + + False + + + ..\..\..\NugetPackages\Microsoft.VisualStudio.Language.StandardClassification.14.1.24720\lib\net45\Microsoft.VisualStudio.Language.StandardClassification.dll + True + + + ..\..\..\NugetPackages\Microsoft.VisualStudio.OLE.Interop.7.10.6070\lib\Microsoft.VisualStudio.OLE.Interop.dll + True + + + False + ..\..\..\NugetPackages\Microsoft.VisualStudio.ProjectSystem.14.0.50617-pre\lib\net451\Microsoft.VisualStudio.ProjectSystem.Utilities.v14.0.dll + False + + + False + ..\..\..\NugetPackages\Microsoft.VisualStudio.ProjectSystem.14.0.50617-pre\lib\net451\Microsoft.VisualStudio.ProjectSystem.V14Only.dll + False + + + False + + + False + + + ..\..\..\NugetPackages\Microsoft.VisualStudio.Shell.Interop.7.10.6071\lib\Microsoft.VisualStudio.Shell.Interop.dll + True + + + True + ..\..\..\NugetPackages\Microsoft.VisualStudio.Shell.Interop.10.0.10.0.30319\lib\Microsoft.VisualStudio.Shell.Interop.10.0.dll + True + + + True + ..\..\..\NugetPackages\Microsoft.VisualStudio.Shell.Interop.11.0.11.0.61030\lib\Microsoft.VisualStudio.Shell.Interop.11.0.dll + True + + + True + ..\..\..\NugetPackages\Microsoft.VisualStudio.Shell.Interop.12.0.12.0.30110\lib\Microsoft.VisualStudio.Shell.Interop.12.0.dll + True + + + True + ..\..\..\NugetPackages\Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime.14.1.24720\lib\Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime.dll + True + + + ..\..\..\NugetPackages\Microsoft.VisualStudio.Shell.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.Shell.Interop.8.0.dll + True + + + ..\..\..\NugetPackages\Microsoft.VisualStudio.Shell.Interop.9.0.9.0.30729\lib\Microsoft.VisualStudio.Shell.Interop.9.0.dll + True + + + ..\..\..\NugetPackages\Microsoft.VisualStudio.Text.Data.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.Data.dll + True + + + ..\..\..\NugetPackages\Microsoft.VisualStudio.Text.Logic.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.Logic.dll + True + + + ..\..\..\NugetPackages\Microsoft.VisualStudio.Text.UI.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.UI.dll + True + + + ..\..\..\NugetPackages\Microsoft.VisualStudio.Text.UI.Wpf.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.UI.Wpf.dll + True + + + ..\..\..\NugetPackages\Microsoft.VisualStudio.TextManager.Interop.7.10.6070\lib\Microsoft.VisualStudio.TextManager.Interop.dll + True + + + True + ..\..\..\NugetPackages\Microsoft.VisualStudio.TextManager.Interop.10.0.10.0.30319\lib\Microsoft.VisualStudio.TextManager.Interop.10.0.dll + True + + + True + True + + + True + True + + + ..\..\..\NugetPackages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll + True + + + False + + + ..\..\..\NugetPackages\Microsoft.VisualStudio.Utilities.14.1.24720\lib\net45\Microsoft.VisualStudio.Utilities.dll + True + + + + False + ..\..\..\NugetPackages\Newtonsoft.Json.7.0.1\lib\net45\Newtonsoft.Json.dll + + + + + + ..\..\..\NugetPackages\System.Collections.Immutable.1.1.37\lib\dotnet\System.Collections.Immutable.dll + True + + + + + + + + + + ..\..\..\NugetPackages\Microsoft.Tpl.Dataflow.4.5.24\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll + True + + + + + + + + + + + + + {8D408909-459F-4853-A36C-745118F99869} + Microsoft.Common.Core + + + {9de5e0b5-c8bd-482c-85c3-b8e20f08453b} + Microsoft.Common.Wpf + + + {6d62df0d-d9c3-49a7-a693-acd0691ba69d} + Microsoft.R.Debugger.Engine + + + {17e155bf-351c-4253-b9b1-36eeea35fe3c} + Microsoft.R.Debugger + + + {E09D3BDA-2E6B-47B5-87AC-B6FC2D33DFAB} + Microsoft.R.Host.Client + + + Platform=x64 + {131842ce-daf9-4c0e-8409-4a26ef7961a7} + Microsoft.R.Host + + + {25cd8690-6208-4740-b123-6dbce6b9444a} + Microsoft.Languages.Core + BuiltProjectOutputGroup%3bBuiltProjectOutputGroupDependencies%3bGetCopyToOutputDirectoryItems%3bSatelliteDllsProjectOutputGroup%3b + DebugSymbolsProjectOutputGroup%3b + + + {62857e49-e586-4baa-ae4d-1232093e7378} + Microsoft.Languages.Editor + BuiltProjectOutputGroup%3bBuiltProjectOutputGroupDependencies%3bGetCopyToOutputDirectoryItems%3bSatelliteDllsProjectOutputGroup%3b + DebugSymbolsProjectOutputGroup%3b + + + {98e0b8ac-1193-4bfd-bf5c-6712c93abd03} + Microsoft.Markdown.Editor + + + {41AA8769-0FBC-4A80-8498-21C833F0C2AC} + Microsoft.VisualStudio.ProjectSystem.FileSystemMirroring + BuiltProjectOutputGroup%3bBuiltProjectOutputGroupDependencies%3bGetCopyToOutputDirectoryItems%3bSatelliteDllsProjectOutputGroup%3b + DebugSymbolsProjectOutputGroup%3b + + + {b68d4ad2-2dc2-473e-ab06-408172c4fb92} + Microsoft.R.Actions + + + {0c4bce1d-3cb8-4e2a-9252-58784d7f26a5} + Microsoft.R.Core + BuiltProjectOutputGroup%3bBuiltProjectOutputGroupDependencies%3bGetCopyToOutputDirectoryItems%3bSatelliteDllsProjectOutputGroup%3b + DebugSymbolsProjectOutputGroup%3b + + + {d6eeef87-ce3a-4804-a409-39966b96c850} + Microsoft.R.Editor + BuiltProjectOutputGroup%3bBuiltProjectOutputGroupDependencies%3bGetCopyToOutputDirectoryItems%3bSatelliteDllsProjectOutputGroup%3b + DebugSymbolsProjectOutputGroup%3b + + + {c1957d47-b0b4-42e0-bc08-0d5e96e47fe4} + Microsoft.R.Support + + + + + + Designer + PreserveNewest + true + + + Designer + PreserveNewest + true + + + + + + + + + + + + + + Designer + PreserveNewest + true + + + + PreserveNewest + true + + + PreserveNewest + true + + + PreserveNewest + true + + + + Designer + MSBuild:Compile + + + + + Designer + + + + + + + + + + + + Designer + + + + + + Designer + + + + + + + + + + Designer + + + + + + Designer + + + + + + Designer + + + + + + Designer + + + + + + Designer + + + + + + Designer + + + + + + Designer + + + + + + Designer + + + + + Menus.ctmenu + Designer + + + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + PreserveNewest + + + + + Designer + MSBuild:Compile + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + PreserveNewest + Designer + + + MSBuild:Compile + Designer + + + + + Microsoft + StrongName + + + + + + + + ItemTemplates + + + ProjectTemplates + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Package/Impl/Options/R/Editor/REditorOptionsDialog.cs b/src/Package/Impl/Options/R/Editor/REditorOptionsDialog.cs index 8734c9c50..66bd1fa52 100644 --- a/src/Package/Impl/Options/R/Editor/REditorOptionsDialog.cs +++ b/src/Package/Impl/Options/R/Editor/REditorOptionsDialog.cs @@ -1,8 +1,8 @@ using System; using System.ComponentModel; -using System.Runtime.InteropServices; using Microsoft.R.Editor.Settings; using Microsoft.VisualStudio.R.Package.Options.Attributes; +using Microsoft.VisualStudio.R.Package.Telemetry; using Microsoft.VisualStudio.Shell; namespace Microsoft.VisualStudio.R.Package.Options.R.Editor { @@ -134,5 +134,16 @@ public override void ResetSettings() { REditorSettings.ResetSettings(); base.ResetSettings(); } + + protected override void OnApply(PageApplyEventArgs e) { + if (e.ApplyBehavior == ApplyKind.Apply) { + RtvsTelemetry.Current.ReportSettings(); + } + base.OnApply(e); + } + + protected override void OnClosed(EventArgs e) { + base.OnClosed(e); + } } } diff --git a/src/Package/Impl/Options/R/RToolsOptionsPage.cs b/src/Package/Impl/Options/R/RToolsOptionsPage.cs index 9bc42cf74..ac6125d5b 100644 --- a/src/Package/Impl/Options/R/RToolsOptionsPage.cs +++ b/src/Package/Impl/Options/R/RToolsOptionsPage.cs @@ -9,10 +9,13 @@ using Microsoft.VisualStudio.R.Package.Options.R.Tools; using Microsoft.VisualStudio.R.Package.Shell; using Microsoft.VisualStudio.Shell; +using Microsoft.VisualStudio.R.Package.Telemetry; +using System; namespace Microsoft.VisualStudio.R.Package.Options.R { public class RToolsOptionsPage : DialogPage { - private bool _loadingFromStorage; + private bool _allowLoadingFromStorage; + private bool _applied; public RToolsOptionsPage() { this.SettingsRegistryPath = @"UserSettings\R_Tools"; @@ -102,7 +105,7 @@ public string RVersion { set { value = ValidateRBasePath(value); if (value != null) { - if (RToolsSettings.Current.RBasePath != value && !_loadingFromStorage) { + if (RToolsSettings.Current.RBasePath != value && !_allowLoadingFromStorage) { VsAppShell.Current.ShowErrorMessage(Resources.RPathChangedRestartVS); } RToolsSettings.Current.RBasePath = value; @@ -120,6 +123,16 @@ public HelpBrowserType HelpBrowser { set { RToolsSettings.Current.HelpBrowser = value; } } + [LocCategory("Settings_DebuggingCategory")] + [CustomLocDisplayName("Settings_ShowDotPrefixedVariables")] + [LocDescription("Settings_ShowDotPrefixedVariables_Description")] + [DefaultValue(false)] + public bool ShowDotPrefixedVariables + { + get { return RToolsSettings.Current.ShowDotPrefixedVariables; } + set { RToolsSettings.Current.ShowDotPrefixedVariables = value; } + } + /// /// REPL working directory: not exposed in Tools | Options dialog, /// only saved along with other settings. @@ -134,21 +147,40 @@ internal string[] WorkingDirectoryList { set { RToolsSettings.Current.WorkingDirectoryList = value; } } - public override void LoadSettingsFromStorage() { - _loadingFromStorage = true; + /// + /// Loads all values from persistent storage. Typically called when package loads. + /// + public void LoadSettings() { + _allowLoadingFromStorage = true; try { - base.LoadSettingsFromStorage(); + LoadSettingsFromStorage(); } finally { - _loadingFromStorage = false; + _allowLoadingFromStorage = false; + } + } + + /// + /// Saves all values to persistent storage. Typically called when package unloads. + /// + public void SaveSettings() { + SaveSettingsToStorage(); + } + + public override void LoadSettingsFromStorage() { + // Only permit loading when loading was initiated via LoadSettings(). + // Otherwise dialog will load values from registry instead of using + // ones currently set in memory. + if (_allowLoadingFromStorage) { + base.LoadSettingsFromStorage(); } } private string ValidateRBasePath(string path) { // If path is null, folder selector dialog was canceled if (path != null) { - bool valid = SupportedRVersions.VerifyRIsInstalled(path, showErrors: !_loadingFromStorage); + bool valid = SupportedRVersions.VerifyRIsInstalled(path, showErrors: !_allowLoadingFromStorage); if (!valid) { - if (_loadingFromStorage) { + if (_allowLoadingFromStorage) { // Bad data in the settings storage. Fix the value to default. path = RInstallation.GetLatestEnginePathFromRegistry(); } else { @@ -159,5 +191,31 @@ private string ValidateRBasePath(string path) { return path; } + + protected override void OnClosed(EventArgs e) { + if(!_applied) { + // On cancel load previously saved settings back + LoadSettings(); + } + _applied = false; + base.OnClosed(e); + } + protected override void OnActivate(CancelEventArgs e) { + // Save in-memory settings to storage. In case dialog + // is canceled with some settings modified we will be + // able to restore them from storage. + SaveSettingsToStorage(); + base.OnActivate(e); + } + + protected override void OnApply(PageApplyEventArgs e) { + if (e.ApplyBehavior == ApplyKind.Apply) { + _applied = true; + // On OK persist settings + SaveSettingsToStorage(); + RtvsTelemetry.Current.ReportSettings(); + } + base.OnApply(e); + } } } diff --git a/src/Package/Impl/Options/R/SupportedRVersions.cs b/src/Package/Impl/Options/R/SupportedRVersions.cs index 7f01d0d07..5cd353219 100644 --- a/src/Package/Impl/Options/R/SupportedRVersions.cs +++ b/src/Package/Impl/Options/R/SupportedRVersions.cs @@ -18,6 +18,9 @@ public static bool VerifyRIsInstalled(string path, bool showErrors) { if (data.Status != RInstallStatus.OK && showErrors) { string message = FormatMessage(data); VsAppShell.Current.ShowErrorMessage(message); + if(data.Status == RInstallStatus.PathNotSpecified) { + Process.Start("https://mran.revolutionanalytics.com/download/#download"); + } return false; } diff --git a/src/Package/Impl/Options/R/Tools/ImportRSettingsCommand.cs b/src/Package/Impl/Options/R/Tools/ImportRSettingsCommand.cs index 8d97169eb..4642a68e0 100644 --- a/src/Package/Impl/Options/R/Tools/ImportRSettingsCommand.cs +++ b/src/Package/Impl/Options/R/Tools/ImportRSettingsCommand.cs @@ -5,7 +5,9 @@ using System.Reflection; using Microsoft.Common.Core; using Microsoft.Common.Core.Shell; +using Microsoft.Languages.Editor.Tasks; using Microsoft.VisualStudio.R.Package.Commands; +using Microsoft.VisualStudio.R.Package.Repl.Commands; using Microsoft.VisualStudio.R.Package.Shell; using Microsoft.VisualStudio.R.Packages.R; using Microsoft.VisualStudio.Shell.Interop; @@ -21,15 +23,27 @@ public static void OnCommand(object sender, EventArgs args) { Guid group = VSConstants.CMDSETID.StandardCommandSet2K_guid; string asmDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetAssemblyPath()); - string settingsFilePath = Path.Combine(asmDirectory, "R.vssettings"); - object arguments = string.Format(CultureInfo.InvariantCulture, "-import:\"{0}\"", settingsFilePath); + string settingsFilePath1 = Path.Combine(asmDirectory, @"Profiles\", "R.vssettings"); + string settingsFilePath2 = Path.Combine(asmDirectory, @"Profiles\", "RStudioKeyboard.vssettings"); + if (!File.Exists(settingsFilePath1)) { + string ideFolder = asmDirectory.Substring(0, asmDirectory.IndexOf(@"\Extensions", StringComparison.OrdinalIgnoreCase)); + settingsFilePath1 = Path.Combine(ideFolder, @"Profiles\", "R.vssettings"); + settingsFilePath2 = Path.Combine(ideFolder, @"Profiles\", "RStudioKeyboard.vssettings"); + } + + object arguments = string.Format(CultureInfo.InvariantCulture, "-import:\"{0}\"", settingsFilePath1); shell.PostExecCommand(ref group, (uint)VSConstants.VSStd2KCmdID.ManageUserSettings, 0, ref arguments); if (MessageButtons.Yes == VsAppShell.Current.ShowMessage(Resources.Warning_RStudioKeyboardShortcuts, MessageButtons.YesNo)) { - settingsFilePath = Path.Combine(asmDirectory, "RStudioKeyboard.vssettings"); - arguments = string.Format(CultureInfo.InvariantCulture, "-import:\"{0}\"", settingsFilePath); + arguments = string.Format(CultureInfo.InvariantCulture, "-import:\"{0}\"", settingsFilePath2); shell.PostExecCommand(ref group, (uint)VSConstants.VSStd2KCmdID.ManageUserSettings, 0, ref arguments); } + + // Restore Ctrl+Enter if necessary + IdleTimeAction.Create(() => { + ReplShortcutSetting.Close(); + ReplShortcutSetting.Initialize(); + }, 100, typeof(ImportRSettingsCommand)); } } } diff --git a/src/Package/Impl/Options/R/Tools/RToolsSettingsImplementation.cs b/src/Package/Impl/Options/R/Tools/RToolsSettingsImplementation.cs index bcc583ebe..87b770ae4 100644 --- a/src/Package/Impl/Options/R/Tools/RToolsSettingsImplementation.cs +++ b/src/Package/Impl/Options/R/Tools/RToolsSettingsImplementation.cs @@ -76,6 +76,8 @@ public string WorkingDirectory { public HelpBrowserType HelpBrowser { get; set; } + public bool ShowDotPrefixedVariables { get; set; } + public RToolsSettingsImplementation() { // Default settings. Will be overwritten with actual // settings (if any) when settings are loaded from storage diff --git a/src/Package/Impl/Package.vsct b/src/Package/Impl/Package.vsct index 93abffd1c..b2cf68b77 100644 --- a/src/Package/Impl/Package.vsct +++ b/src/Package/Impl/Package.vsct @@ -5,6 +5,8 @@ + + @@ -23,7 +25,7 @@ - + DontCache TextChanges @@ -32,7 +34,15 @@ R History Context - + + + + + + + &R + + @@ -169,6 +179,11 @@ + + + + + @@ -204,15 +219,9 @@ - - - - @@ -222,11 +231,19 @@ - + + + + + + + + + @@ -265,15 +282,9 @@ - - - - @@ -306,6 +317,9 @@ + + + @@ -340,6 +354,18 @@ + + + + - - - - - - - - @@ -634,16 +583,6 @@ - - @@ -957,8 +896,9 @@ + + + + @@ -1003,6 +956,10 @@ + + + + @@ -1086,7 +1043,7 @@ - + @@ -1095,8 +1052,13 @@ - + + + + + + @@ -1137,47 +1099,6 @@ - - - - - - - - - - - - - - - - - @@ -1186,7 +1107,7 @@ - + @@ -1194,10 +1115,6 @@ - - - - @@ -1206,43 +1123,11 @@ - - - - - - - - - - - - - - @@ -1251,7 +1136,7 @@ - + @@ -1259,22 +1144,14 @@ - - - - - + - - - - @@ -1284,39 +1161,51 @@ - - + + - + - + + + + + + + + + - + - + - + - + - + - + + + + + @@ -1331,7 +1220,7 @@ - + @@ -1371,6 +1260,7 @@ + @@ -1388,12 +1278,16 @@ + + + - + + @@ -1408,6 +1302,7 @@ + @@ -1418,6 +1313,7 @@ + @@ -1425,6 +1321,8 @@ + + @@ -1436,27 +1334,18 @@ - - - - - + + - + - - @@ -1491,9 +1380,7 @@ - - @@ -1507,6 +1394,9 @@ + + + diff --git a/src/Package/Impl/Packages/DefaultIndentAttribute.cs b/src/Package/Impl/Packages/LanguageEditorOptionsAttribute.cs similarity index 70% rename from src/Package/Impl/Packages/DefaultIndentAttribute.cs rename to src/Package/Impl/Packages/LanguageEditorOptionsAttribute.cs index 298d20b9e..ab0fc6ea8 100644 --- a/src/Package/Impl/Packages/DefaultIndentAttribute.cs +++ b/src/Package/Impl/Packages/LanguageEditorOptionsAttribute.cs @@ -3,15 +3,17 @@ namespace Microsoft.VisualStudio.R.Package.Packages { [AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = true)] - public sealed class DefaultIndentAttribute : RegistrationAttribute { + public sealed class LanguageEditorOptionsAttribute : RegistrationAttribute { private string _language; private int _indentSize; private bool _keepSpaces; + private bool _showLineNumbers; - public DefaultIndentAttribute(string language, int indentSize, bool keepSpaces) { + public LanguageEditorOptionsAttribute(string language, int indentSize, bool keepSpaces, bool showLineNumbers) { _language = language; _indentSize = indentSize; _keepSpaces = keepSpaces; + _showLineNumbers = showLineNumbers; } public override void Register(RegistrationContext context) { @@ -19,6 +21,7 @@ public override void Register(RegistrationContext context) { key.SetValue("Indent Size", _indentSize); key.SetValue("Tab Size", _indentSize); key.SetValue("Insert Tabs", _keepSpaces ? 0 : 1); + key.SetValue("Line Numbers", _showLineNumbers ? 1 : 0); } } diff --git a/src/Package/Impl/Packages/PackageCommand.cs b/src/Package/Impl/Packages/PackageCommand.cs index 442178f7b..901072b70 100644 --- a/src/Package/Impl/Packages/PackageCommand.cs +++ b/src/Package/Impl/Packages/PackageCommand.cs @@ -17,8 +17,8 @@ private static void OnBeforeQueryStatus(object sender, EventArgs e) { } } - protected virtual void SetStatus() { } - protected virtual void Handle() { } + internal virtual void SetStatus() { } + internal virtual void Handle() { } public static void OnCommand(object sender, EventArgs args) { var command = sender as PackageCommand; diff --git a/src/Package/Impl/Packages/ProvideDebugPortPickerAttribute.cs b/src/Package/Impl/Packages/ProvideComClassAttribute.cs similarity index 55% rename from src/Package/Impl/Packages/ProvideDebugPortPickerAttribute.cs rename to src/Package/Impl/Packages/ProvideComClassAttribute.cs index 70f0d52ba..8a77b4abe 100644 --- a/src/Package/Impl/Packages/ProvideDebugPortPickerAttribute.cs +++ b/src/Package/Impl/Packages/ProvideComClassAttribute.cs @@ -4,20 +4,20 @@ namespace Microsoft.VisualStudio.R.Packages { [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] - internal class ProvideDebugPortPickerAttribute : RegistrationAttribute { - private readonly Type _portPicker; + internal class ProvideComClassAttribute : RegistrationAttribute { + private readonly Type _comClass; - public ProvideDebugPortPickerAttribute(Type portPicker) { - _portPicker = portPicker; + public ProvideComClassAttribute(Type comClass) { + _comClass = comClass; } public override void Register(RegistrationContext context) { var clsidKey = context.CreateKey("CLSID"); - var clsidGuidKey = clsidKey.CreateSubkey(_portPicker.GUID.ToString("B")); - clsidGuidKey.SetValue("Assembly", _portPicker.Assembly.FullName); - clsidGuidKey.SetValue("Class", _portPicker.FullName); + var clsidGuidKey = clsidKey.CreateSubkey(_comClass.GUID.ToString("B")); + clsidGuidKey.SetValue("Assembly", _comClass.Assembly.FullName); + clsidGuidKey.SetValue("Class", _comClass.FullName); clsidGuidKey.SetValue("InprocServer32", context.InprocServerPath); - clsidGuidKey.SetValue("CodeBase", Path.Combine(context.ComponentPath, _portPicker.Module.Name)); + clsidGuidKey.SetValue("CodeBase", Path.Combine(context.ComponentPath, _comClass.Module.Name)); clsidGuidKey.SetValue("ThreadingModel", "Free"); } diff --git a/src/Package/Impl/Packages/ProvideDebugLanguageAttribute.cs b/src/Package/Impl/Packages/ProvideDebugLanguageAttribute.cs index 616fc2bf9..a5837e5e7 100644 --- a/src/Package/Impl/Packages/ProvideDebugLanguageAttribute.cs +++ b/src/Package/Impl/Packages/ProvideDebugLanguageAttribute.cs @@ -1,30 +1,38 @@ -using System; -using Microsoft.VisualStudio.Shell; - -namespace Microsoft.VisualStudio.R.Packages { - class ProvideDebugLanguageAttribute : RegistrationAttribute { - private readonly string _languageGuid, _languageName, _engineGuid, _eeGuid; - - public ProvideDebugLanguageAttribute(string languageName, string languageGuid, string eeGuid, string debugEngineGuid) { - _languageName = languageName; - _languageGuid = new Guid(languageGuid).ToString("B"); - _eeGuid = new Guid(eeGuid).ToString("B"); - _engineGuid = debugEngineGuid; - } - - public override void Register(RegistrationContext context) { - var langSvcKey = context.CreateKey("Languages\\Language Services\\" + _languageName + "\\Debugger Languages\\" + _languageGuid); - langSvcKey.SetValue("", _languageName); - // 994... is the vendor ID (Microsoft) - var eeKey = context.CreateKey("AD7Metrics\\ExpressionEvaluator\\" + _languageGuid + "\\{994B45C4-E6E9-11D2-903F-00C04FA302A1}"); - eeKey.SetValue("Language", _languageName); - eeKey.SetValue("Name", _languageName); - eeKey.SetValue("CLSID", _eeGuid); - - var engineKey = eeKey.CreateSubkey("Engine"); - engineKey.SetValue("0", _engineGuid); - } - - public override void Unregister(RegistrationContext context) { } - } -} +using System; +using Microsoft.VisualStudio.Shell; + +namespace Microsoft.VisualStudio.R.Packages { + class ProvideDebugLanguageAttribute : RegistrationAttribute { + private readonly string _languageGuid, _languageName, _engineGuid, _eeGuid, _customViewerGuid; + + public ProvideDebugLanguageAttribute(string languageName, string languageGuid, string eeGuid, string debugEngineGuid, string customViewerGuid = null) { + _languageName = languageName; + _languageGuid = new Guid(languageGuid).ToString("B"); + _eeGuid = new Guid(eeGuid).ToString("B"); + _engineGuid = debugEngineGuid; + + if (customViewerGuid != null) { + _customViewerGuid = new Guid(customViewerGuid).ToString("B"); + } + } + + public override void Register(RegistrationContext context) { + var langSvcKey = context.CreateKey("Languages\\Language Services\\" + _languageName + "\\Debugger Languages\\" + _languageGuid); + langSvcKey.SetValue("", _languageName); + // 994... is the vendor ID (Microsoft) + var eeKey = context.CreateKey("AD7Metrics\\ExpressionEvaluator\\" + _languageGuid + "\\{994B45C4-E6E9-11D2-903F-00C04FA302A1}"); + eeKey.SetValue("Language", _languageName); + eeKey.SetValue("Name", _languageName); + eeKey.SetValue("CLSID", _eeGuid); + + if (_customViewerGuid != null) { + eeKey.SetValue("CustomViewerCLSID", _customViewerGuid); + } + + var engineKey = eeKey.CreateSubkey("Engine"); + engineKey.SetValue("0", _engineGuid); + } + + public override void Unregister(RegistrationContext context) { } + } +} diff --git a/src/Package/Impl/Packages/R/PackageCommands.cs b/src/Package/Impl/Packages/R/PackageCommands.cs index ce6eebf9d..02841effd 100644 --- a/src/Package/Impl/Packages/R/PackageCommands.cs +++ b/src/Package/Impl/Packages/R/PackageCommands.cs @@ -2,23 +2,34 @@ using System.ComponentModel.Composition.Hosting; using System.ComponentModel.Design; using Microsoft.R.Host.Client; +using Microsoft.R.Support.Settings; using Microsoft.VisualStudio.ProjectSystem; using Microsoft.VisualStudio.R.Package.DataInspect.Commands; +using Microsoft.VisualStudio.R.Package.Debugger.Commands; using Microsoft.VisualStudio.R.Package.Feedback; using Microsoft.VisualStudio.R.Package.Help; using Microsoft.VisualStudio.R.Package.History; using Microsoft.VisualStudio.R.Package.Options.R.Tools; using Microsoft.VisualStudio.R.Package.Plots.Commands; +using Microsoft.VisualStudio.R.Package.Plots.Definitions; +using Microsoft.VisualStudio.R.Package.Repl; +using Microsoft.VisualStudio.R.Package.Repl.Commands; using Microsoft.VisualStudio.R.Package.Repl.Data; using Microsoft.VisualStudio.R.Package.Repl.Debugger; using Microsoft.VisualStudio.R.Package.Repl.Workspace; using Microsoft.VisualStudio.R.Package.RPackages.Commands; +using Microsoft.VisualStudio.R.Package.Shell; +using Microsoft.VisualStudio.R.Package.Utilities; +using Microsoft.VisualStudio.Shell.Interop; namespace Microsoft.VisualStudio.R.Packages.R { internal static class PackageCommands { public static IEnumerable GetCommands(ExportProvider exportProvider) { var rSessionProvider = exportProvider.GetExportedValue(); var projectServiceAccessor = exportProvider.GetExportedValue(); + var plotHistory = exportProvider.GetExportedValue(); + var debugger = VsAppShell.Current.GetGlobalService(typeof(IVsDebugger)); + var textViewTracker = exportProvider.GetExportedValue(); return new List { new GoToOptionsCommand(), @@ -31,14 +42,18 @@ public static IEnumerable GetCommands(ExportProvider exportProvider new LoadWorkspaceCommand(rSessionProvider, projectServiceAccessor), new SaveWorkspaceCommand(rSessionProvider, projectServiceAccessor), + new SourceRScriptCommand(VsAppShell.Current.CompositionService), + new AttachDebuggerCommand(rSessionProvider), + new AttachToRInteractiveCommand(rSessionProvider), new StopDebuggingCommand(rSessionProvider), new ContinueDebuggingCommand(rSessionProvider), new StepOverCommand(rSessionProvider), new StepOutCommand(rSessionProvider), new StepIntoCommand(rSessionProvider), - new InterruptRCommand(rSessionProvider), + new InterruptRCommand(ReplWindow.Current, rSessionProvider, debugger), + new ResetReplCommand(), new ImportDataSetTextFileCommand(), new ImportDataSetUrlCommand(), @@ -46,13 +61,21 @@ public static IEnumerable GetCommands(ExportProvider exportProvider new InstallPackagesCommand(), new CheckForPackageUpdatesCommand(), + // Window commands new ShowPlotWindowsCommand(), new ShowRInteractiveWindowsCommand(), - new ShowVariableWindowCommand(), new ShowHelpWindowCommand(), - new ShowHelpOnCurrentCommand(), - new ShowHistoryWindowCommand() + new ShowHelpOnCurrentCommand(rSessionProvider, textViewTracker), + new ShowHistoryWindowCommand(), + + // Plot commands + new ExportPlotAsImageCommand(plotHistory), + new ExportPlotAsPdfCommand(plotHistory), + new CopyPlotAsBitmapCommand(plotHistory), + new CopyPlotAsMetafileCommand(plotHistory), + new HistoryNextPlotCommand(plotHistory), + new HistoryPreviousPlotCommand(plotHistory) }; } } diff --git a/src/Package/Impl/Packages/R/RPackage.cs b/src/Package/Impl/Packages/R/RPackage.cs index 24ab38b62..7fb299ed1 100644 --- a/src/Package/Impl/Packages/R/RPackage.cs +++ b/src/Package/Impl/Packages/R/RPackage.cs @@ -25,6 +25,7 @@ using Microsoft.VisualStudio.R.Package.Repl.Commands; using Microsoft.VisualStudio.R.Package.RPackages.Mirrors; using Microsoft.VisualStudio.R.Package.Shell; +using Microsoft.VisualStudio.R.Package.Telemetry; using Microsoft.VisualStudio.R.Package.Utilities; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; @@ -42,7 +43,7 @@ namespace Microsoft.VisualStudio.R.Packages.R { ShowMatchingBrace = true, MatchBraces = true, MatchBracesAtCaret = true, ShowCompletion = true, EnableLineNumbers = true, EnableFormatSelection = true, DefaultToInsertSpaces = true, RequestStockColors = true)] [ShowBraceCompletion(RContentTypeDefinition.LanguageName)] - [DefaultIndentAttribute(RContentTypeDefinition.LanguageName, 2, true)] + [LanguageEditorOptionsAttribute(RContentTypeDefinition.LanguageName, 2, true, true)] [ProvideLanguageEditorOptionPage(typeof(REditorOptionsDialog), RContentTypeDefinition.LanguageName, "", "Advanced", "#20136")] [ProvideProjectFileGenerator(typeof(RProjectFileGenerator), RGuidList.CpsProjectFactoryGuidString, FileExtensions = RContentTypeDefinition.RStudioProjectExtension, DisplayGeneratorFilter = 300)] [DeveloperActivity(RContentTypeDefinition.LanguageName, RGuidList.RPackageGuidString, sortPriority: 9)] @@ -53,10 +54,11 @@ namespace Microsoft.VisualStudio.R.Packages.R { [ProvideToolWindow(typeof(HelpWindowPane), Style = VsDockStyle.Linked, Window = ToolWindowGuids80.PropertiesWindow)] [ProvideToolWindow(typeof(HistoryWindowPane), Style = VsDockStyle.Linked, Window = ToolWindowGuids80.SolutionExplorer)] [ProvideDebugEngine(RContentTypeDefinition.LanguageName, null, typeof(AD7Engine), DebuggerGuids.DebugEngineString)] - [ProvideDebugLanguage(RContentTypeDefinition.LanguageName, DebuggerGuids.LanguageGuidString, "{D67D5DB8-3D44-4105-B4B8-47AB1BA66180}", DebuggerGuids.DebugEngineString)] + [ProvideDebugLanguage(RContentTypeDefinition.LanguageName, DebuggerGuids.LanguageGuidString, "{D67D5DB8-3D44-4105-B4B8-47AB1BA66180}", DebuggerGuids.DebugEngineString, DebuggerGuids.CustomViewerString)] [ProvideDebugPortSupplier("R Interactive sessions", typeof(RDebugPortSupplier), DebuggerGuids.PortSupplierString, typeof(RDebugPortPicker))] - [ProvideDebugPortPicker(typeof(RDebugPortPicker))] - [ProvideToolWindow(typeof(VariableWindowPane), Style = VsDockStyle.Linked, Window = ToolWindowGuids80.SolutionExplorer, Transient = true)] + [ProvideComClass(typeof(RDebugPortPicker))] + [ProvideComClass(typeof(AD7CustomViewer))] + [ProvideToolWindow(typeof(VariableWindowPane), Style = VsDockStyle.Linked, Window = ToolWindowGuids80.SolutionExplorer)] [ProvideToolWindow(typeof(VariableGridWindowPane), Style = VsDockStyle.Linked, Window = ToolWindowGuids80.SolutionExplorer, Transient = true)] [ProvideNewFileTemplatesAttribute(RGuidList.MiscFilesProjectGuidString, RGuidList.RPackageGuidString, "#106", @"Templates\NewItem\")] internal class RPackage : BasePackage, IRPackage { @@ -74,12 +76,17 @@ protected override void Initialize() { Current = this; CranMirrorList.Download(); - base.Initialize(); + // Force app shell creation before everything else + var shell = VsAppShell.Current; + + RtvsTelemetry.Initialize(); - using (var p = RPackage.Current.GetDialogPage(typeof(RToolsOptionsPage))) { - p.LoadSettingsFromStorage(); + using (var p = RPackage.Current.GetDialogPage(typeof(RToolsOptionsPage)) as RToolsOptionsPage) { + p.LoadSettings(); } + base.Initialize(); + ReplShortcutSetting.Initialize(); ProjectIconProvider.LoadProjectImages(); LogCleanup.DeleteLogsAsync(DiagnosticLogs.DaysToRetain); @@ -87,6 +94,8 @@ protected override void Initialize() { _indexBuildingTask = FunctionIndex.BuildIndexAsync(); InitializeActiveWpfTextViewTracker(); + + System.Threading.Tasks.Task.Run(() => RtvsTelemetry.Current.ReportConfiguration()); } protected override void Dispose(bool disposing) { @@ -100,6 +109,13 @@ protected override void Dispose(bool disposing) { LogCleanup.Cancel(); ReplShortcutSetting.Close(); ProjectIconProvider.Close(); + + RtvsTelemetry.Current.Dispose(); + + using (var p = RPackage.Current.GetDialogPage(typeof(RToolsOptionsPage)) as RToolsOptionsPage) { + p.SaveSettings(); + } + base.Dispose(disposing); } diff --git a/src/Package/Impl/Plots/Commands/CopyPlotAsBitmapCommand.cs b/src/Package/Impl/Plots/Commands/CopyPlotAsBitmapCommand.cs new file mode 100644 index 000000000..fac34cf57 --- /dev/null +++ b/src/Package/Impl/Plots/Commands/CopyPlotAsBitmapCommand.cs @@ -0,0 +1,18 @@ +using Microsoft.VisualStudio.R.Package.Commands; +using Microsoft.VisualStudio.R.Package.Plots.Definitions; + +namespace Microsoft.VisualStudio.R.Package.Plots.Commands { + internal sealed class CopyPlotAsBitmapCommand : PlotWindowCommand { + public CopyPlotAsBitmapCommand(IPlotHistory plotHistory) : + base(plotHistory, RPackageCommandId.icmdCopyPlotAsBitmap) { + } + + internal override void SetStatus() { + Enabled = PlotHistory.ActivePlotIndex >= 0; + } + + internal override void Handle() { + PlotHistory.PlotContentProvider.CopyToClipboardAsBitmap(); + } + } +} diff --git a/src/Package/Impl/Plots/Commands/CopyPlotAsMetafileCommand.cs b/src/Package/Impl/Plots/Commands/CopyPlotAsMetafileCommand.cs new file mode 100644 index 000000000..9e7ef7583 --- /dev/null +++ b/src/Package/Impl/Plots/Commands/CopyPlotAsMetafileCommand.cs @@ -0,0 +1,18 @@ +using Microsoft.VisualStudio.R.Package.Commands; +using Microsoft.VisualStudio.R.Package.Plots.Definitions; + +namespace Microsoft.VisualStudio.R.Package.Plots.Commands { + internal sealed class CopyPlotAsMetafileCommand : PlotWindowCommand { + public CopyPlotAsMetafileCommand(IPlotHistory plotHistory) : + base(plotHistory, RPackageCommandId.icmdCopyPlotAsMetafile) { + } + + internal override void SetStatus() { + Enabled = PlotHistory.ActivePlotIndex >= 0; + } + + internal override void Handle() { + PlotHistory.PlotContentProvider.CopyToClipboardAsMetafile(); + } + } +} diff --git a/src/Package/Impl/Plots/Commands/CopyPlotCommand.cs b/src/Package/Impl/Plots/Commands/CopyPlotCommand.cs deleted file mode 100644 index d9443e7e7..000000000 --- a/src/Package/Impl/Plots/Commands/CopyPlotCommand.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using Microsoft.Languages.Editor; -using Microsoft.Languages.Editor.Controller.Command; -using Microsoft.VisualStudio.R.Package.Commands; -using Microsoft.VisualStudio.R.Packages.R; - -namespace Microsoft.VisualStudio.R.Package.Plots.Commands { - //internal sealed class CopyPlotCommand : PlotWindowCommand { - // public CopyPlotCommand(PlotWindowPane pane) : - // base(pane, RPackageCommandId.icmdCopyPlot) { - // } - - // public override CommandStatus Status(Guid group, int id) { - // return CommandStatus.Supported; - // } - - // public override CommandResult Invoke(Guid group, int id, object inputArg, ref object outputArg) { - // return CommandResult.Executed; - // } - //} -} diff --git a/src/Package/Impl/Plots/Commands/ExportPlotAsImageCommand.cs b/src/Package/Impl/Plots/Commands/ExportPlotAsImageCommand.cs new file mode 100644 index 000000000..3ef03d1d2 --- /dev/null +++ b/src/Package/Impl/Plots/Commands/ExportPlotAsImageCommand.cs @@ -0,0 +1,46 @@ +using System; +using System.IO; +using Microsoft.VisualStudio.R.Package.Commands; +using Microsoft.VisualStudio.R.Package.Plots.Definitions; +using Microsoft.VisualStudio.R.Package.Shell; + +namespace Microsoft.VisualStudio.R.Package.Plots.Commands { + internal sealed class ExportPlotAsImageCommand : PlotWindowCommand { + public ExportPlotAsImageCommand(IPlotHistory plotHistory) : + base(plotHistory, RPackageCommandId.icmdExportPlotAsImage) { + } + + internal override void SetStatus() { + Enabled = PlotHistory.ActivePlotIndex >= 0; + } + + internal override void Handle() { + string destinationFilePath = VsAppShell.Current.BrowseForFileSave(IntPtr.Zero, Resources.PlotExportAsImageFilter, null, Resources.ExportPlotAsImageDialogTitle); + if (!string.IsNullOrEmpty(destinationFilePath)) { + string device = String.Empty; + string extension = Path.GetExtension(destinationFilePath).TrimStart('.').ToLowerInvariant(); + switch (extension) { + case "png": + device = "png"; + break; + case "bmp": + device = "bmp"; + break; + case "tif": + case "tiff": + device = "tiff"; + break; + case "jpg": + case "jpeg": + device = "jpeg"; + break; + default: + VsAppShell.Current.ShowErrorMessage(string.Format(Resources.PlotExportUnsupportedImageFormat, extension)); + return; + } + + PlotHistory.PlotContentProvider.ExportAsImage(destinationFilePath, device); + } + } + } +} diff --git a/src/Package/Impl/Plots/Commands/ExportPlotAsPdfCommand.cs b/src/Package/Impl/Plots/Commands/ExportPlotAsPdfCommand.cs new file mode 100644 index 000000000..3657833c4 --- /dev/null +++ b/src/Package/Impl/Plots/Commands/ExportPlotAsPdfCommand.cs @@ -0,0 +1,23 @@ +using System; +using Microsoft.VisualStudio.R.Package.Commands; +using Microsoft.VisualStudio.R.Package.Plots.Definitions; +using Microsoft.VisualStudio.R.Package.Shell; + +namespace Microsoft.VisualStudio.R.Package.Plots.Commands { + internal sealed class ExportPlotAsPdfCommand : PlotWindowCommand { + public ExportPlotAsPdfCommand(IPlotHistory plotHistory) : + base(plotHistory, RPackageCommandId.icmdExportPlotAsPdf) { + } + + internal override void SetStatus() { + Enabled = PlotHistory.ActivePlotIndex >= 0; + } + + internal override void Handle() { + string destinationFilePath = VsAppShell.Current.BrowseForFileSave(IntPtr.Zero, Resources.PlotExportAsPdfFilter, null, Resources.ExportPlotAsPdfDialogTitle); + if (!string.IsNullOrEmpty(destinationFilePath)) { + PlotHistory.PlotContentProvider.ExportAsPdf(destinationFilePath); + } + } + } +} diff --git a/src/Package/Impl/Plots/Commands/ExportPlotCommand.cs b/src/Package/Impl/Plots/Commands/ExportPlotCommand.cs deleted file mode 100644 index ec48382ea..000000000 --- a/src/Package/Impl/Plots/Commands/ExportPlotCommand.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System; -using Microsoft.Languages.Editor; -using Microsoft.VisualStudio.R.Package.Commands; - -namespace Microsoft.VisualStudio.R.Package.Plots.Commands { - internal sealed class ExportPlotCommand : PlotWindowCommand { - public ExportPlotCommand(PlotWindowPane pane) : - base(pane, RPackageCommandId.icmdExportPlot) { - } - - public override CommandStatus Status(Guid group, int id) { - return CommandStatus.NotSupported; // implementation not completed yet - //return CommandStatus.Supported; - } - - public override CommandResult Invoke(Guid group, int id, object inputArg, ref object outputArg) { - _pane.ExportPlot(); - return CommandResult.Executed; - } - } -} diff --git a/src/Package/Impl/Plots/Commands/FixPlotCommand.cs b/src/Package/Impl/Plots/Commands/FixPlotCommand.cs deleted file mode 100644 index 23639589e..000000000 --- a/src/Package/Impl/Plots/Commands/FixPlotCommand.cs +++ /dev/null @@ -1,19 +0,0 @@ -using System; -using Microsoft.Languages.Editor; -using Microsoft.VisualStudio.R.Package.Commands; - -namespace Microsoft.VisualStudio.R.Package.Plots.Commands { - //internal sealed class FixPlotCommand : PlotWindowCommand { - // public FixPlotCommand(PlotWindowPane pane) : - // base(pane, RPackageCommandId.icmdFixPlot) { - // } - - // public override CommandStatus Status(Guid group, int id) { - // return CommandStatus.Supported; - // } - - // public override CommandResult Invoke(Guid group, int id, object inputArg, ref object outputArg) { - // return CommandResult.Executed; - // } - //} -} diff --git a/src/Package/Impl/Plots/Commands/HistoryNextPlotCommand.cs b/src/Package/Impl/Plots/Commands/HistoryNextPlotCommand.cs index 4bb2d9d52..641914c1a 100644 --- a/src/Package/Impl/Plots/Commands/HistoryNextPlotCommand.cs +++ b/src/Package/Impl/Plots/Commands/HistoryNextPlotCommand.cs @@ -1,16 +1,18 @@ -using System; -using Microsoft.Languages.Editor; -using Microsoft.VisualStudio.R.Package.Commands; +using Microsoft.VisualStudio.R.Package.Commands; +using Microsoft.VisualStudio.R.Package.Plots.Definitions; namespace Microsoft.VisualStudio.R.Package.Plots.Commands { internal sealed class HistoryNextPlotCommand : PlotWindowCommand { - public HistoryNextPlotCommand(PlotWindowPane pane) : - base(pane, RPackageCommandId.icmdNextPlot) { + public HistoryNextPlotCommand(IPlotHistory plotHistory) : + base(plotHistory, RPackageCommandId.icmdNextPlot) { } - public override CommandResult Invoke(Guid group, int id, object inputArg, ref object outputArg) { - _pane.NextPlot(); - return CommandResult.Executed; + internal override void SetStatus() { + Enabled = PlotHistory.ActivePlotIndex >= 0 && PlotHistory.ActivePlotIndex < PlotHistory.PlotCount - 1; + } + + internal override void Handle() { + PlotContentProvider.DoNotWait(PlotHistory.PlotContentProvider.NextPlotAsync()); } } } diff --git a/src/Package/Impl/Plots/Commands/HistoryPreviousPlotCommand.cs b/src/Package/Impl/Plots/Commands/HistoryPreviousPlotCommand.cs index 0ba8c256f..afec768e5 100644 --- a/src/Package/Impl/Plots/Commands/HistoryPreviousPlotCommand.cs +++ b/src/Package/Impl/Plots/Commands/HistoryPreviousPlotCommand.cs @@ -1,16 +1,16 @@ -using System; -using Microsoft.Languages.Editor; -using Microsoft.VisualStudio.R.Package.Commands; +using Microsoft.VisualStudio.R.Package.Commands; +using Microsoft.VisualStudio.R.Package.Plots.Definitions; namespace Microsoft.VisualStudio.R.Package.Plots.Commands { internal sealed class HistoryPreviousPlotCommand : PlotWindowCommand { - public HistoryPreviousPlotCommand(PlotWindowPane pane) : - base(pane, RPackageCommandId.icmdPrevPlot) { + public HistoryPreviousPlotCommand(IPlotHistory plotHistory) : + base(plotHistory, RPackageCommandId.icmdPrevPlot) { } - - public override CommandResult Invoke(Guid group, int id, object inputArg, ref object outputArg) { - _pane.PreviousPlot(); - return CommandResult.Executed; + internal override void SetStatus() { + Enabled = PlotHistory.ActivePlotIndex > 0; + } + internal override void Handle() { + PlotContentProvider.DoNotWait(PlotHistory.PlotContentProvider.PreviousPlotAsync()); } } } diff --git a/src/Package/Impl/Plots/Commands/OpenPlotCommand.cs b/src/Package/Impl/Plots/Commands/OpenPlotCommand.cs deleted file mode 100644 index 2d4e0a7df..000000000 --- a/src/Package/Impl/Plots/Commands/OpenPlotCommand.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using Microsoft.Languages.Editor; -using Microsoft.VisualStudio.R.Package.Commands; - -namespace Microsoft.VisualStudio.R.Package.Plots.Commands { - //internal sealed class OpenPlotCommand : PlotWindowCommand { - // public OpenPlotCommand(PlotWindowPane pane) : - // base(pane, RPackageCommandId.icmdOpenPlot) { - // } - - // public override CommandStatus Status(Guid group, int id) { - // return CommandStatus.SupportedAndEnabled; - // } - - // public override CommandResult Invoke(Guid group, int id, object inputArg, ref object outputArg) { - // _pane.OpenPlot(); - // return CommandResult.Executed; - // } - //} -} diff --git a/src/Package/Impl/Plots/Commands/PlotWindowCommand.cs b/src/Package/Impl/Plots/Commands/PlotWindowCommand.cs index b643e69fd..48f8dd3c8 100644 --- a/src/Package/Impl/Plots/Commands/PlotWindowCommand.cs +++ b/src/Package/Impl/Plots/Commands/PlotWindowCommand.cs @@ -1,29 +1,14 @@ -using System; -using Microsoft.Languages.Editor; -using Microsoft.Languages.Editor.Controller.Command; +using Microsoft.VisualStudio.R.Package.Commands; +using Microsoft.VisualStudio.R.Package.Plots.Definitions; using Microsoft.VisualStudio.R.Packages.R; namespace Microsoft.VisualStudio.R.Package.Plots.Commands { - internal class PlotWindowCommand : Command { - protected PlotWindowPane _pane; + internal class PlotWindowCommand : PackageCommand { + protected IPlotHistory PlotHistory { get; } - public PlotWindowCommand(PlotWindowPane pane, int id) : base(new CommandId(RGuidList.RCmdSetGuid, id), false) { - _pane = pane; - CurrentStatus = CommandStatus.Supported; - } - - public override CommandStatus Status(Guid group, int id) { - return CurrentStatus; - } - - protected CommandStatus CurrentStatus { get; private set; } - - public void Enable() { - CurrentStatus |= CommandStatus.Enabled; - } - - public void Disable() { - CurrentStatus &= ~CommandStatus.Enabled; + public PlotWindowCommand(IPlotHistory plotHistory, int id) : + base(RGuidList.RCmdSetGuid, id) { + PlotHistory = plotHistory; } } } diff --git a/src/Package/Impl/Plots/Commands/PrintPlotCommand.cs b/src/Package/Impl/Plots/Commands/PrintPlotCommand.cs deleted file mode 100644 index 3f3f2c599..000000000 --- a/src/Package/Impl/Plots/Commands/PrintPlotCommand.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using Microsoft.Languages.Editor; -using Microsoft.VisualStudio.R.Package.Commands; - -namespace Microsoft.VisualStudio.R.Package.Plots.Commands { - internal sealed class PrintPlotCommand : PlotWindowCommand { - public PrintPlotCommand(PlotWindowPane pane) : - base(pane, RPackageCommandId.icmdPrintPlot) { } - - public override CommandStatus Status(Guid group, int id) { - return CommandStatus.Supported; - } - - public override CommandResult Invoke(Guid group, int id, object inputArg, ref object outputArg) { - return CommandResult.Executed; - } - } -} diff --git a/src/Package/Impl/Plots/Commands/SavePlotCommand.cs b/src/Package/Impl/Plots/Commands/SavePlotCommand.cs deleted file mode 100644 index a3e68bf9f..000000000 --- a/src/Package/Impl/Plots/Commands/SavePlotCommand.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using Microsoft.Languages.Editor; -using Microsoft.VisualStudio.R.Package.Commands; - -namespace Microsoft.VisualStudio.R.Package.Plots.Commands { - //internal sealed class SavePlotCommand : PlotWindowCommand { - // public SavePlotCommand(PlotWindowPane pane) : - // base(pane, RPackageCommandId.icmdSavePlot) { } - - // public override CommandResult Invoke(Guid group, int id, object inputArg, ref object outputArg) { - // _pane.SavePlot(); - - // return CommandResult.Executed; - // } - //} -} diff --git a/src/Package/Impl/Plots/Commands/ShowPlotWindowsCommand.cs b/src/Package/Impl/Plots/Commands/ShowPlotWindowsCommand.cs index c78deb5cf..8a41acdb2 100644 --- a/src/Package/Impl/Plots/Commands/ShowPlotWindowsCommand.cs +++ b/src/Package/Impl/Plots/Commands/ShowPlotWindowsCommand.cs @@ -7,7 +7,7 @@ internal sealed class ShowPlotWindowsCommand : PackageCommand { public ShowPlotWindowsCommand() : base(RGuidList.RCmdSetGuid, RPackageCommandId.icmdShowPlotWindow) { } - protected override void Handle() { + internal override void Handle() { // TODO: find ad show all windows ToolWindowUtilities.ShowWindowPane(0, true); } diff --git a/src/Package/Impl/Plots/Commands/ZoomInPlotCommand.cs b/src/Package/Impl/Plots/Commands/ZoomInPlotCommand.cs deleted file mode 100644 index c2b78d6e9..000000000 --- a/src/Package/Impl/Plots/Commands/ZoomInPlotCommand.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using Microsoft.Languages.Editor; -using Microsoft.VisualStudio.R.Package.Commands; - -namespace Microsoft.VisualStudio.R.Package.Plots.Commands { - //internal sealed class ZoomInPlotCommand : PlotWindowCommand { - // public ZoomInPlotCommand(PlotWindowPane pane) : - // base(pane, RPackageCommandId.icmdZoomInPlot) { } - - // public override CommandStatus Status(Guid group, int id) { - // return CommandStatus.Supported; - // } - - // public override CommandResult Invoke(Guid group, int id, object inputArg, ref object outputArg) { - // return CommandResult.Executed; - // } - //} -} diff --git a/src/Package/Impl/Plots/Commands/ZoomOutPlotCommand.cs b/src/Package/Impl/Plots/Commands/ZoomOutPlotCommand.cs deleted file mode 100644 index 5d6f77484..000000000 --- a/src/Package/Impl/Plots/Commands/ZoomOutPlotCommand.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using Microsoft.Languages.Editor; -using Microsoft.VisualStudio.R.Package.Commands; - -namespace Microsoft.VisualStudio.R.Package.Plots.Commands { - //internal sealed class ZoomOutPlotCommand : PlotWindowCommand { - // public ZoomOutPlotCommand(PlotWindowPane pane) : - // base(pane, RPackageCommandId.icmdZoomOutPlot) { } - - // public override CommandStatus Status(Guid group, int id) { - // return CommandStatus.Supported; - // } - - // public override CommandResult Invoke(Guid group, int id, object inputArg, ref object outputArg) { - // return CommandResult.Executed; - // } - //} -} diff --git a/src/Package/Impl/Plots/Definitions/IPlotHistory.cs b/src/Package/Impl/Plots/Definitions/IPlotHistory.cs new file mode 100644 index 000000000..6d8a7e31b --- /dev/null +++ b/src/Package/Impl/Plots/Definitions/IPlotHistory.cs @@ -0,0 +1,13 @@ +using System; +using System.Threading.Tasks; + +namespace Microsoft.VisualStudio.R.Package.Plots.Definitions { + internal interface IPlotHistory: IDisposable { + int PlotCount { get; } + int ActivePlotIndex { get; } + IPlotContentProvider PlotContentProvider { get; } + Task RefreshHistoryInfo(); + + event EventHandler HistoryChanged; + } +} diff --git a/src/Package/Impl/Plots/IPlotContentProvider.cs b/src/Package/Impl/Plots/IPlotContentProvider.cs index 76627a575..7543e7500 100644 --- a/src/Package/Impl/Plots/IPlotContentProvider.cs +++ b/src/Package/Impl/Plots/IPlotContentProvider.cs @@ -19,10 +19,27 @@ internal interface IPlotContentProvider : IDisposable { void LoadFile(string filePath); /// - /// Export plot as an image + /// Export plot as an image. /// - /// the destination filepath - void ExportFile(string fileName); + /// Destination file path. + /// Name of R device to use (png, bmp, tiff, jpeg). + void ExportAsImage(string fileName, string deviceName); + + /// + /// Export plot as a PDF. + /// + /// Destination file path. + void ExportAsPdf(string fileName); + + /// + /// Copy plot to clipboard in bitmap format. + /// + void CopyToClipboardAsBitmap(); + + /// + /// Copy plot to clipboard in enhanced metafile format. + /// + void CopyToClipboardAsMetafile(); /// /// Resize the current plot or set the default size for future plots. diff --git a/src/Package/Impl/Plots/PlotContentProvider.cs b/src/Package/Impl/Plots/PlotContentProvider.cs index 5f200c2f7..1fa30ed00 100644 --- a/src/Package/Impl/Plots/PlotContentProvider.cs +++ b/src/Package/Impl/Plots/PlotContentProvider.cs @@ -19,7 +19,6 @@ namespace Microsoft.VisualStudio.R.Package.Plots { internal sealed class PlotContentProvider : IPlotContentProvider { private IRSession _rSession; - private IDebugSessionProvider _debugSessionProvider; private string _lastLoadFile; private int _lastWidth; private int _lastHeight; @@ -32,14 +31,6 @@ public PlotContentProvider() { _rSession = sessionProvider.GetInteractiveWindowRSession(); _rSession.Mutated += RSession_Mutated; _rSession.Connected += RSession_Connected; - - _debugSessionProvider = VsAppShell.Current.ExportProvider.GetExportedValue(); - - IdleTimeAction.Create(() => { - // debug session is created to trigger a load of the R package - // that has functions we need such as rtvs:::toJSON - _debugSessionProvider.GetDebugSessionAsync(_rSession).DoNotWait(); - }, 10, typeof(PlotContentProvider)); } private void RSession_Mutated(object sender, EventArgs e) { @@ -66,8 +57,16 @@ public void LoadFile(string fileName) { if (!string.IsNullOrEmpty(fileName)) { try { if (string.Compare(Path.GetExtension(fileName), ".png", StringComparison.InvariantCultureIgnoreCase) == 0) { + // Use Begin/EndInit to avoid locking the file on disk + var bmp = new BitmapImage(); + bmp.BeginInit(); + bmp.UriSource = new Uri(fileName); + bmp.CacheOption = BitmapCacheOption.OnLoad; + bmp.EndInit(); + var image = new Image(); - image.Source = new BitmapImage(new Uri(fileName)); + image.Source = bmp; + element = image; } else { element = (UIElement)XamlServices.Load(fileName); @@ -82,35 +81,92 @@ public void LoadFile(string fileName) { OnPlotChanged(element); } - public async void ExportFile(string fileName) { - if (_rSession == null) { - return; + public void ExportAsImage(string fileName, string deviceName) { + DoNotWait(ExportAsImageAsync(fileName, deviceName)); + } + + public void CopyToClipboardAsBitmap() { + DoNotWait(CopyToClipboardAsBitmapAsync()); + } + + public void CopyToClipboardAsMetafile() { + DoNotWait(CopyToClipboardAsMetafileAsync()); + } + + public void ExportAsPdf(string fileName) { + DoNotWait(ExportAsPdfAsync(fileName)); + } + + private async System.Threading.Tasks.Task ExportAsImageAsync(string fileName, string deviceName) { + if (_rSession != null) { + using (IRSessionEvaluation eval = await _rSession.BeginEvaluationAsync()) { + await eval.ExportToBitmap(deviceName, fileName, _lastWidth, _lastHeight); + } + } + } + + private async System.Threading.Tasks.Task CopyToClipboardAsBitmapAsync() { + if (_rSession != null) { + string fileName = Path.GetTempFileName(); + using (IRSessionEvaluation eval = await _rSession.BeginEvaluationAsync()) { + await eval.ExportToBitmap("bmp", fileName, _lastWidth, _lastHeight); + VsAppShell.Current.DispatchOnUIThread( + () => { + try { + // Use Begin/EndInit to avoid locking the file on disk + var image = new BitmapImage(); + image.BeginInit(); + image.UriSource = new Uri(fileName); + image.CacheOption = BitmapCacheOption.OnLoad; + image.EndInit(); + Clipboard.SetImage(image); + + SafeFileDelete(fileName); + } catch (Exception e) when (!e.IsCriticalException()) { + MessageBox.Show(string.Format(Resources.PlotCopyToClipboardError, e.Message)); + } + }); + } } + } + + private async System.Threading.Tasks.Task CopyToClipboardAsMetafileAsync() { + if (_rSession != null) { + string fileName = Path.GetTempFileName(); + using (IRSessionEvaluation eval = await _rSession.BeginEvaluationAsync()) { + await eval.ExportToMetafile(fileName, PixelsToInches(_lastWidth), PixelsToInches(_lastHeight)); - string device = String.Empty; - switch (Path.GetExtension(fileName).TrimStart('.').ToLowerInvariant()) { - case "png": - device = "png"; - break; - case "bmp": - device = "bmp"; - break; - case "tif": - case "tiff": - device = "tiff"; - break; - case "jpg": - case "jpeg": - device = "jpeg"; - break; - default: - //TODO - Debug.Assert(false, "Unsupported image format."); - return; + VsAppShell.Current.DispatchOnUIThread( + () => { + try { + var mf = new System.Drawing.Imaging.Metafile(fileName); + Clipboard.SetData(DataFormats.EnhancedMetafile, mf); + SafeFileDelete(fileName); + } catch (Exception e) when (!e.IsCriticalException()) { + MessageBox.Show(string.Format(Resources.PlotCopyToClipboardError, e.Message)); + } + }); + } } - using (IRSessionEvaluation eval = await _rSession.BeginEvaluationAsync()) { - await eval.CopyToDevice(device, fileName); + } + + private static double PixelsToInches(int pixels) { + return pixels / 96.0; + } + + private static void SafeFileDelete(string fileName) { + try { + File.Delete(fileName); + } catch (IOException) { + } + } + + private async System.Threading.Tasks.Task ExportAsPdfAsync(string fileName) { + if (_rSession != null) { + using (IRSessionEvaluation eval = await _rSession.BeginEvaluationAsync()) { + await eval.ExportToPdf(fileName, PixelsToInches(_lastWidth), PixelsToInches(_lastHeight), "special"); + } } } @@ -184,5 +240,18 @@ private void OnPlotChanged(UIElement element) { public void Dispose() { } + + public static void DoNotWait(System.Threading.Tasks.Task task) { + // Errors like invalid graphics state which go to the REPL stderr will come back + // in an Microsoft.R.Host.Client.RException, and we don't need to do anything with them, + // as the user can see them in the REPL. + // TODO: + // See if we can fix the cause of those errors - to be + // determined based on the various errors we see displayed + // in REPL during testing. + task.SilenceException() + .SilenceException() + .DoNotWait(); + } } } diff --git a/src/Package/Impl/Plots/PlotHistory.cs b/src/Package/Impl/Plots/PlotHistory.cs new file mode 100644 index 000000000..861e7e33f --- /dev/null +++ b/src/Package/Impl/Plots/PlotHistory.cs @@ -0,0 +1,62 @@ +using System; +using System.ComponentModel.Composition; +using System.Threading.Tasks; +using Microsoft.VisualStudio.R.Package.Plots.Definitions; +using Microsoft.VisualStudio.R.Package.Shell; +using Microsoft.VisualStudio.R.Package.Utilities; + +namespace Microsoft.VisualStudio.R.Package.Plots { + [Export(typeof(IPlotHistory))] + internal sealed class PlotHistory : IPlotHistory { + + public PlotHistory() { + ActivePlotIndex = -1; + PlotContentProvider = new PlotContentProvider(); + PlotContentProvider.PlotChanged += OnPlotChanged; + } + + #region IPlotHistory + public int ActivePlotIndex { get; private set; } + public int PlotCount { get; private set; } + public IPlotContentProvider PlotContentProvider { get; private set; } + + public async Task RefreshHistoryInfo() { + var info = await PlotContentProvider.GetHistoryInfoAsync(); + ActivePlotIndex = info.ActivePlotIndex; + PlotCount = info.PlotCount; + + VsAppShell.Current.DispatchOnUIThread(() => { + // We need to push creation of the tool window + // so it appears on the first plot + if (!VsAppShell.Current.IsUnitTestEnvironment) { + ToolWindowUtilities.ShowWindowPane(0, false); + } + HistoryChanged?.Invoke(this, EventArgs.Empty); + }); + } + + public event EventHandler HistoryChanged; + #endregion + + private void OnPlotChanged(object sender, PlotChangedEventArgs e) { + if (e.NewPlotElement == null) { + ClearHistoryInfo(); + } else { + Plots.PlotContentProvider.DoNotWait(RefreshHistoryInfo()); + } + } + + private void ClearHistoryInfo() { + ActivePlotIndex = -1; + PlotCount = 0; + } + + public void Dispose() { + if (PlotContentProvider != null) { + PlotContentProvider.PlotChanged -= OnPlotChanged; + PlotContentProvider.Dispose(); + PlotContentProvider = null; + } + } + } +} diff --git a/src/Package/Impl/Plots/PlotWindowPane.cs b/src/Package/Impl/Plots/PlotWindowPane.cs index 1dffab443..885989a17 100644 --- a/src/Package/Impl/Plots/PlotWindowPane.cs +++ b/src/Package/Impl/Plots/PlotWindowPane.cs @@ -1,13 +1,9 @@ using System; -using System.Collections.Generic; using System.ComponentModel.Design; using System.Runtime.InteropServices; -using Microsoft.Common.Core; -using Microsoft.Languages.Editor.Controller; -using Microsoft.R.Host.Client; +using Microsoft.Languages.Editor.Tasks; using Microsoft.VisualStudio.R.Package.Commands; -using Microsoft.VisualStudio.R.Package.Interop; -using Microsoft.VisualStudio.R.Package.Plots.Commands; +using Microsoft.VisualStudio.R.Package.Plots.Definitions; using Microsoft.VisualStudio.R.Package.Shell; using Microsoft.VisualStudio.R.Packages.R; using Microsoft.VisualStudio.Shell; @@ -22,28 +18,21 @@ internal class PlotWindowPane : ToolWindowPane, IVsWindowFrameNotify3 { private const int MinWidth = 150; private const int MinHeight = 150; - private ExportPlotCommand _exportPlotCommand; - private HistoryNextPlotCommand _historyNextPlotCommand; - private HistoryPreviousPlotCommand _historyPreviousPlotCommand; + private IPlotHistory PlotHistory; public PlotWindowPane() { Caption = Resources.PlotWindowCaption; + PlotHistory = VsAppShell.Current.ExportProvider.GetExportedValue(); + PlotHistory.HistoryChanged += OnPlotHistoryHistoryChanged; - // set content with presenter - PlotContentProvider = new PlotContentProvider(); - PlotContentProvider.PlotChanged += ContentProvider_PlotChanged; - - - var presenter = new XamlPresenter(PlotContentProvider); + var presenter = new XamlPresenter(PlotHistory.PlotContentProvider); presenter.SizeChanged += PlotWindowPane_SizeChanged; Content = presenter; - // initialize toolbar + // initialize toolbar. Commands are added via package + // so they appear correctly in the top level menu as well + // as on the plot window toolbar this.ToolBar = new CommandID(RGuidList.RCmdSetGuid, RPackageCommandId.plotWindowToolBarId); - - Controller c = new Controller(); - c.AddCommandSet(GetCommands()); - this.ToolBarCommandTarget = new CommandTargetToOleShim(null, c); } private void PlotWindowPane_SizeChanged(object sender, System.Windows.SizeChangedEventArgs e) { @@ -51,123 +40,23 @@ private void PlotWindowPane_SizeChanged(object sender, System.Windows.SizeChange // and user will be able to use scrollbars to see the whole thing int width = Math.Max((int)e.NewSize.Width, MinWidth); int height = Math.Max((int)e.NewSize.Height, MinHeight); - DoNotWait(PlotContentProvider.ResizePlotAsync(width, height)); - } - - private static void DoNotWait(System.Threading.Tasks.Task task) { - // Errors like invalid graphics state which go to the REPL stderr will come back - // in an Microsoft.R.Host.Client.RException, and we don't need to do anything with them, - // as the user can see them in the REPL. - // TODO: - // See if we can fix the cause of those errors - to be - // determined based on the various errors we see displayed - // in REPL during testing. - task.SilenceException() - .SilenceException() - .DoNotWait(); - } - - public override void OnToolWindowCreated() { - base.OnToolWindowCreated(); - - IVsWindowFrame frame = this.Frame as IVsWindowFrame; - frame.SetProperty((int)__VSFPROPID.VSFPROPID_ViewHelper, this); - } - - public IPlotContentProvider PlotContentProvider { get; private set; } - - private IEnumerable GetCommands() { - List commands = new List(); - - _exportPlotCommand = new ExportPlotCommand(this); - _historyNextPlotCommand = new HistoryNextPlotCommand(this); - _historyPreviousPlotCommand = new HistoryPreviousPlotCommand(this); - - commands.Add(_exportPlotCommand); - commands.Add(_historyNextPlotCommand); - commands.Add(_historyPreviousPlotCommand); - - //commands.Add(new FixPlotCommand(this)); - //commands.Add(new CopyPlotCommand(this)); - //commands.Add(new PrintPlotCommand(this)); - //commands.Add(new ZoomInPlotCommand(this)); - //commands.Add(new ZoomOutPlotCommand(this)); - - return commands; - } - private async System.Threading.Tasks.Task RefreshHistoryInfo() { - var info = await PlotContentProvider.GetHistoryInfoAsync(); - SetHistoryInfo(info.ActivePlotIndex, info.PlotCount); + // Throttle resize requests since we get a lot of size changed events when the tool window is undocked + IdleTimeAction.Cancel(this); + IdleTimeAction.Create(() => { + PlotContentProvider.DoNotWait(PlotHistory.PlotContentProvider.ResizePlotAsync(width, height)); + }, 100, this); } - private void ClearHistoryInfo() { - SetHistoryInfo(-1, 0); - } - - private void SetHistoryInfo(int activeIndex, int plotCount) { - if (activeIndex >= 0) { - if (activeIndex < (plotCount - 1)) { - _historyNextPlotCommand.Enable(); - } else { - _historyNextPlotCommand.Disable(); - } - if (activeIndex > 0) { - _historyPreviousPlotCommand.Enable(); - } else { - _historyPreviousPlotCommand.Disable(); - } - } else { - _historyNextPlotCommand.Disable(); - _historyPreviousPlotCommand.Disable(); - } - + private void OnPlotHistoryHistoryChanged(object sender, EventArgs e) { + ((IVsWindowFrame)Frame).ShowNoActivate(); IVsUIShell shell = VsAppShell.Current.GetGlobalService(typeof(SVsUIShell)); - shell.UpdateCommandUI(1); - } - - private void ContentProvider_PlotChanged(object sender, PlotChangedEventArgs e) { - if (e.NewPlotElement == null) { - ClearHistoryInfo(); - } else { - DoNotWait(RefreshHistoryInfo()); - } - } - - internal void ExportPlot() { - string destinationFilePath = GetExportFilePath(); - if (!string.IsNullOrEmpty(destinationFilePath)) { - PlotContentProvider.ExportFile(destinationFilePath); - } - } - - internal void NextPlot() { - DoNotWait(PlotContentProvider.NextPlotAsync()); - } - - internal void PreviousPlot() { - DoNotWait(PlotContentProvider.PreviousPlotAsync()); - } - - private string GetLoadFilePath() { - return VsAppShell.Current.BrowseForFileOpen(IntPtr.Zero, - Resources.PlotFileFilter, - // TODO: open in current project folder if one is active - Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments) + "\\", - Resources.OpenPlotDialogTitle); - } - - private string GetExportFilePath() { - return VsAppShell.Current.BrowseForFileSave(IntPtr.Zero, Resources.PlotExportFilter, null, Resources.ExportPlotDialogTitle); + shell.UpdateCommandUI(0); } protected override void Dispose(bool disposing) { - if (PlotContentProvider != null) { - PlotContentProvider.PlotChanged -= ContentProvider_PlotChanged; - PlotContentProvider.Dispose(); - PlotContentProvider = null; - } - + PlotHistory?.Dispose(); + PlotHistory = null; base.Dispose(disposing); } diff --git a/src/Package/Impl/R.vssettings b/src/Package/Impl/Profiles/R.vssettings similarity index 75% rename from src/Package/Impl/R.vssettings rename to src/Package/Impl/Profiles/R.vssettings index 75ee28894..4fc6f5f4a 100644 --- a/src/Package/Impl/R.vssettings +++ b/src/Package/Impl/Profiles/R.vssettings @@ -1,37 +1,24 @@ - + + + #8000 + {6D7C5336-C0CA-4857-A7E7-2E964EA836BF} + + + #8010 + {6D7C5336-C0CA-4857-A7E7-2E964EA836BF} + - - True - False - True - True - True - True - False - False - True - False - - - - - - - - - - 7 Design|Debug|NoToolWin - + @@ -86,88 +73,95 @@ + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + - - - - + + + + - - - - - - + + + + + + + + - - - - + + + + - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - + @@ -299,11 +293,23 @@ + + + + + + + + + + + + - + @@ -354,86 +360,98 @@ + - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + - - - - - + + + + + - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + + + + + + @@ -514,12 +532,6 @@ - - - - - - @@ -541,6 +553,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -684,5 +738,76 @@ + + 14.0.0.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Ctrl+Enter + + + \ No newline at end of file diff --git a/src/Package/Impl/Profiles/RCombined.vssettings b/src/Package/Impl/Profiles/RCombined.vssettings new file mode 100644 index 000000000..004e7e8d2 --- /dev/null +++ b/src/Package/Impl/Profiles/RCombined.vssettings @@ -0,0 +1,839 @@ + + + + + #8002 + {6D7C5336-C0CA-4857-A7E7-2E964EA836BF} + + + #8012 + {6D7C5336-C0CA-4857-A7E7-2E964EA836BF} + + + + + + + 7 + Design|Debug|NoToolWin + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 14.0.0.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Ctrl+Alt+S + Ctrl+W + Ctrl+Shift+W + Ctrl+Shift+J + Ctrl+Shift+A + Ctrl+Shift+C + Ctrl+Shift+U + Alt+O + Shift+Alt+O + Alt+L + Shift+Alt+L + Ctrl+Alt+F12 + Ctrl+Alt+F11 + Ctrl+Shift+S + Ctrl+2 + Ctrl+3 + Ctrl+4 + Ctrl+5 + Ctrl+6 + Ctrl+8 + Ctrl+L + Ctrl+Shift+F10 + Ctrl+L + Ctrl+Shift+F10 + + + + Ctrl+Enter + + + + + \ No newline at end of file diff --git a/src/Package/Impl/RStudioKeyboard.vssettings b/src/Package/Impl/Profiles/RStudioKeyboard.vssettings similarity index 90% rename from src/Package/Impl/RStudioKeyboard.vssettings rename to src/Package/Impl/Profiles/RStudioKeyboard.vssettings index 311e8ca36..779ebfecd 100644 --- a/src/Package/Impl/RStudioKeyboard.vssettings +++ b/src/Package/Impl/Profiles/RStudioKeyboard.vssettings @@ -1,12 +1,12 @@ - + - #7000 + #8001 {6D7C5336-C0CA-4857-A7E7-2E964EA836BF} - #7001 + #8011 {6D7C5336-C0CA-4857-A7E7-2E964EA836BF} @@ -43,6 +43,8 @@ Ctrl+8 Ctrl+L Ctrl+Shift+F10 + Ctrl+L + Ctrl+Shift+F10 diff --git a/src/Package/Impl/ProjectSystem/RProjectLoadHooks.cs b/src/Package/Impl/ProjectSystem/RProjectLoadHooks.cs index 5fa6e0d0b..39cf277cb 100644 --- a/src/Package/Impl/ProjectSystem/RProjectLoadHooks.cs +++ b/src/Package/Impl/ProjectSystem/RProjectLoadHooks.cs @@ -55,7 +55,8 @@ public async Task InitializeProjectFromDiskAsync() { _fileWatcher.Start(); // Force REPL window up - await ReplWindow.EnsureReplWindow(); + await _threadHandling.SwitchToUIThread(); + ReplWindow.EnsureReplWindow(); if (!_session.IsHostRunning) { return; @@ -72,8 +73,6 @@ public async Task InitializeProjectFromDiskAsync() { await evaluation.SetWorkingDirectory(_projectDirectory); } - await _threadHandling.SwitchToUIThread(); - _toolsSettings.WorkingDirectory = _projectDirectory; var history = GetRHistory(); history?.TryLoadFromFile(Path.Combine(_projectDirectory, DefaultRHistoryName)); diff --git a/src/Package/Impl/Properties/AssemblyInfo.cs b/src/Package/Impl/Properties/AssemblyInfo.cs index 663ed5629..20bae862d 100644 --- a/src/Package/Impl/Properties/AssemblyInfo.cs +++ b/src/Package/Impl/Properties/AssemblyInfo.cs @@ -2,6 +2,8 @@ #if SIGN [assembly: InternalsVisibleTo("Microsoft.VisualStudio.R.Package.Test, PublicKey=002400000480000094000000060200000024000052534131000400000100010007D1FA57C4AED9F0A32E84AA0FAEFD0DE9E8FD6AEC8F87FB03766C834C99921EB23BE79AD9D5DCC1DD9AD236132102900B723CF980957FC4E177108FC607774F29E8320E92EA05ECE4E821C0A5EFE8F1645C4C0C93C1AB99285D622CAA652C1DFAD63D745D6F2DE5F17E5EAF0FC4963D261C8A12436518206DC093344D5AD293")] +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.R.Interactive.Test, PublicKey=002400000480000094000000060200000024000052534131000400000100010007D1FA57C4AED9F0A32E84AA0FAEFD0DE9E8FD6AEC8F87FB03766C834C99921EB23BE79AD9D5DCC1DD9AD236132102900B723CF980957FC4E177108FC607774F29E8320E92EA05ECE4E821C0A5EFE8F1645C4C0C93C1AB99285D622CAA652C1DFAD63D745D6F2DE5F17E5EAF0FC4963D261C8A12436518206DC093344D5AD293")] #else [assembly: InternalsVisibleTo("Microsoft.VisualStudio.R.Package.Test")] +[assembly: InternalsVisibleTo("Microsoft.VisualStudio.R.Interactive.Test")] #endif \ No newline at end of file diff --git a/src/Package/Impl/RPackages/Commands/CheckForPackageUpdatesCommand.cs b/src/Package/Impl/RPackages/Commands/CheckForPackageUpdatesCommand.cs index adf9edc37..10b6f0b38 100644 --- a/src/Package/Impl/RPackages/Commands/CheckForPackageUpdatesCommand.cs +++ b/src/Package/Impl/RPackages/Commands/CheckForPackageUpdatesCommand.cs @@ -8,11 +8,11 @@ public CheckForPackageUpdatesCommand() : base(RGuidList.RCmdSetGuid, RPackageCommandId.icmdCheckForPackageUpdates) { } - protected override void SetStatus() { + internal override void SetStatus() { Enabled = false; } - protected override void Handle() { + internal override void Handle() { } } } diff --git a/src/Package/Impl/RPackages/Commands/InstallPackagesCommand.cs b/src/Package/Impl/RPackages/Commands/InstallPackagesCommand.cs index 52cd3e227..948d71aba 100644 --- a/src/Package/Impl/RPackages/Commands/InstallPackagesCommand.cs +++ b/src/Package/Impl/RPackages/Commands/InstallPackagesCommand.cs @@ -8,11 +8,11 @@ public InstallPackagesCommand() : base(RGuidList.RCmdSetGuid, RPackageCommandId.icmdInstallPackages) { } - protected override void SetStatus() { + internal override void SetStatus() { Enabled = false; } - protected override void Handle() { + internal override void Handle() { } } } diff --git a/src/Package/Impl/Repl/Commands/SendToReplCommand.cs b/src/Package/Impl/Repl/Commands/SendToReplCommand.cs index a4e0cbbc9..109499413 100644 --- a/src/Package/Impl/Repl/Commands/SendToReplCommand.cs +++ b/src/Package/Impl/Repl/Commands/SendToReplCommand.cs @@ -10,7 +10,7 @@ namespace Microsoft.VisualStudio.R.Package.Repl.Commands { public sealed class SendToReplCommand : ViewCommand { - private ReplWindow _replWindow; + private IReplWindow _replWindow; public SendToReplCommand(ITextView textView) : base(textView, new[] @@ -18,7 +18,7 @@ public SendToReplCommand(ITextView textView) : new CommandId(VSConstants.VsStd11, (int)VSConstants.VSStd11CmdID.ExecuteLineInInteractive), new CommandId(VSConstants.VsStd11, (int)VSConstants.VSStd11CmdID.ExecuteSelectionInInteractive) }, false) { - ReplWindow.EnsureReplWindow().DoNotWait(); + ReplWindow.EnsureReplWindow(); _replWindow = ReplWindow.Current; } @@ -29,7 +29,7 @@ public override CommandStatus Status(Guid group, int id) { public override CommandResult Invoke(Guid group, int id, object inputArg, ref object outputArg) { ITextSelection selection = TextView.Selection; ITextSnapshot snapshot = TextView.TextBuffer.CurrentSnapshot; - ReplWindow replWindow = ReplWindow.Current; + IReplWindow replWindow = ReplWindow.Current; int position = selection.Start.Position; ITextSnapshotLine line = snapshot.GetLineFromPosition(position); @@ -49,7 +49,7 @@ public override CommandResult Invoke(Guid group, int id, object inputArg, ref ob line = TextView.Selection.End.Position.GetContainingLine(); } - ReplWindow.Show(); + _replWindow.Show(); replWindow.EnqueueCode(text, addNewLine: true); var targetLine = line; diff --git a/src/Package/Impl/Repl/Commands/SourceRScriptCommand.cs b/src/Package/Impl/Repl/Commands/SourceRScriptCommand.cs new file mode 100644 index 000000000..f5af82e5e --- /dev/null +++ b/src/Package/Impl/Repl/Commands/SourceRScriptCommand.cs @@ -0,0 +1,84 @@ +using System; +using System.ComponentModel.Composition; +using Microsoft.R.Editor.ContentType; +using Microsoft.R.Host.Client; +using Microsoft.VisualStudio.R.Package.Commands; +using Microsoft.VisualStudio.R.Package.Shell; +using Microsoft.VisualStudio.R.Package.Utilities; +using Microsoft.VisualStudio.R.Packages.R; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.VisualStudio.R.Package.Repl.Commands { + internal sealed class SourceRScriptCommand : PackageCommand { + [Import] + private IActiveWpfTextViewTracker TextViewTracker { get; set; } + + [Import] + private IContentTypeRegistryService ContentTypeRegistryService { get; set; } + + private readonly IReplWindow _replWindow; + private readonly IVsMonitorSelection _monitorSelection; + private readonly uint _debugUIContextCookie; + + public SourceRScriptCommand(ICompositionService cs) + : base(RGuidList.RCmdSetGuid, RPackageCommandId.icmdSourceRScript) { + cs.SatisfyImportsOnce(this); + + ReplWindow.EnsureReplWindow(); + _replWindow = ReplWindow.Current; + + _monitorSelection = VsAppShell.Current.GetGlobalService(typeof(SVsShellMonitorSelection)); + if (_monitorSelection != null) { + var debugUIContextGuid = new Guid(UIContextGuids.Debugging); + if (ErrorHandler.Failed(_monitorSelection.GetCmdUIContextCookie(ref debugUIContextGuid, out _debugUIContextCookie))) { + _monitorSelection = null; + } + } + } + + private bool IsDebugging() { + if (_monitorSelection == null) { + return false; + } + + int fActive; + if (ErrorHandler.Succeeded(_monitorSelection.IsCmdUIContextActive(_debugUIContextCookie, out fActive))) { + return fActive != 0; + } + + return false; + } + + private ITextView GetActiveTextView() { + return TextViewTracker.GetLastActiveTextView(RContentTypeDefinition.ContentType); + } + + private string GetFilePath() { + ITextView textView = GetActiveTextView(); + if (textView != null && !textView.IsClosed) { + ITextDocument document; + if (textView.TextBuffer.Properties.TryGetProperty(typeof(ITextDocument), out document)) { + return document.FilePath; + } + } + return null; + } + + internal override void SetStatus() { + Enabled = GetFilePath() != null; + } + + internal override void Handle() { + string filePath = GetFilePath(); + if (filePath != null) { + // Save file before sourcing + ITextView textView = GetActiveTextView(); + textView.SaveFile(); + _replWindow.ExecuteCode($"{(IsDebugging() ? "rtvs::debug_source" : "source")}({filePath.ToRStringLiteral()})"); + } + } + } +} diff --git a/src/Package/Impl/Repl/Commands/WorkingDirectoryCommand.cs b/src/Package/Impl/Repl/Commands/WorkingDirectoryCommand.cs index d71aacb02..1d3f8b88c 100644 --- a/src/Package/Impl/Repl/Commands/WorkingDirectoryCommand.cs +++ b/src/Package/Impl/Repl/Commands/WorkingDirectoryCommand.cs @@ -61,7 +61,7 @@ private async Task FetchRWorkingDirectoryAsync() { } public override CommandStatus Status(Guid group, int id) { - if (ReplWindow.ReplWindowExists()) { + if (ReplWindow.ReplWindowExists) { return CommandStatus.SupportedAndEnabled; } return CommandStatus.Supported; diff --git a/src/Package/Impl/Repl/Data/ImportDataSetTextFileCommand.cs b/src/Package/Impl/Repl/Data/ImportDataSetTextFileCommand.cs index a15a92bd7..269014ea4 100644 --- a/src/Package/Impl/Repl/Data/ImportDataSetTextFileCommand.cs +++ b/src/Package/Impl/Repl/Data/ImportDataSetTextFileCommand.cs @@ -7,7 +7,7 @@ public ImportDataSetTextFileCommand() : base(RGuidList.RCmdSetGuid, RPackageCommandId.icmdImportDatasetTextFile) { } - protected override void SetStatus() { + internal override void SetStatus() { this.Enabled = false; // ReplWindow.ReplWindowExists(); } } diff --git a/src/Package/Impl/Repl/Data/ImportDataSetUrlCommand.cs b/src/Package/Impl/Repl/Data/ImportDataSetUrlCommand.cs index dace67b23..f009497c5 100644 --- a/src/Package/Impl/Repl/Data/ImportDataSetUrlCommand.cs +++ b/src/Package/Impl/Repl/Data/ImportDataSetUrlCommand.cs @@ -7,7 +7,7 @@ public ImportDataSetUrlCommand() : base(RGuidList.RCmdSetGuid, RPackageCommandId.icmdImportDatasetUrl) { } - protected override void SetStatus() { + internal override void SetStatus() { this.Enabled = false; // ReplWindow.ReplWindowExists(); } } diff --git a/src/Package/Impl/Repl/Debugger/AttachDebuggerCommand.cs b/src/Package/Impl/Repl/Debugger/AttachDebuggerCommand.cs index 865887694..e9d9c1656 100644 --- a/src/Package/Impl/Repl/Debugger/AttachDebuggerCommand.cs +++ b/src/Package/Impl/Repl/Debugger/AttachDebuggerCommand.cs @@ -9,12 +9,16 @@ using Microsoft.VisualStudio.Shell.Interop; namespace Microsoft.VisualStudio.R.Package.Repl.Debugger { - internal sealed class AttachDebuggerCommand : DebuggerCommand { + internal class AttachDebuggerCommand : DebuggerCommand { public AttachDebuggerCommand(IRSessionProvider rSessionProvider) : base(rSessionProvider, RPackageCommandId.icmdAttachDebugger, DebuggerCommandVisibility.DesignMode) { } - protected unsafe override void Handle() { + protected AttachDebuggerCommand(IRSessionProvider rSessionProvider, int cmdId, DebuggerCommandVisibility visibility) + : base(rSessionProvider, cmdId, visibility) { + } + + internal unsafe override void Handle() { if (!RSession.IsHostRunning) { return; } @@ -54,10 +58,7 @@ protected unsafe override void Handle() { // If we have successfully attached, VS has switched to debugging UI context, which hides // the REPL window. Show it again and give it focus. - IVsWindowFrame frame = ReplWindow.FindReplWindowFrame(__VSFINDTOOLWIN.FTW_fFindFirst); - if (frame != null) { - frame.Show(); - } + ReplWindow.ShowWindow(); } } } diff --git a/src/Package/Impl/Repl/Debugger/AttachToRInteractiveCommand.cs b/src/Package/Impl/Repl/Debugger/AttachToRInteractiveCommand.cs new file mode 100644 index 000000000..3a45c12fc --- /dev/null +++ b/src/Package/Impl/Repl/Debugger/AttachToRInteractiveCommand.cs @@ -0,0 +1,12 @@ +using Microsoft.R.Host.Client; +using Microsoft.VisualStudio.R.Package.Commands; + +namespace Microsoft.VisualStudio.R.Package.Repl.Debugger { + // Identical to AttachDebugger, and only exists as a separate command so that it can be + // given a different label for better fit in the "Debug" top-level menu. + internal sealed class AttachToRInteractiveCommand : AttachDebuggerCommand { + public AttachToRInteractiveCommand(IRSessionProvider rSessionProvider) + : base(rSessionProvider, RPackageCommandId.icmdAttachToRInteractive, DebuggerCommandVisibility.DesignMode) { + } + } +} diff --git a/src/Package/Impl/Repl/Debugger/DebuggerCommand.cs b/src/Package/Impl/Repl/Debugger/DebuggerCommand.cs index 9e67441a8..d91492e52 100644 --- a/src/Package/Impl/Repl/Debugger/DebuggerCommand.cs +++ b/src/Package/Impl/Repl/Debugger/DebuggerCommand.cs @@ -15,7 +15,7 @@ protected DebuggerCommand(IRSessionProvider rSessionProvider, int cmdId, Debugge _visibility = visibility; } - protected override void SetStatus() { + internal override void SetStatus() { Enabled = false; Visible = false; diff --git a/src/Package/Impl/Repl/Debugger/DebuggerWrappedCommand.cs b/src/Package/Impl/Repl/Debugger/DebuggerWrappedCommand.cs index ae388ae3f..a183bb70c 100644 --- a/src/Package/Impl/Repl/Debugger/DebuggerWrappedCommand.cs +++ b/src/Package/Impl/Repl/Debugger/DebuggerWrappedCommand.cs @@ -14,7 +14,7 @@ public DebuggerWrappedCommand(IRSessionProvider rSessionProvider, int cmdId, Gui _shellCmdId = (uint)shellCmdId; } - protected override void Handle() { + internal override void Handle() { IVsUIShell shell = VsAppShell.Current.GetGlobalService(typeof(SVsUIShell)); object o = null; shell.PostExecCommand(ref _shellGroup, _shellCmdId, 0, ref o); diff --git a/src/Package/Impl/Repl/IRInteractive.cs b/src/Package/Impl/Repl/Definitions/IRInteractive.cs similarity index 100% rename from src/Package/Impl/Repl/IRInteractive.cs rename to src/Package/Impl/Repl/Definitions/IRInteractive.cs diff --git a/src/Package/Impl/Repl/IRInteractiveProvider.cs b/src/Package/Impl/Repl/Definitions/IRInteractiveProvider.cs similarity index 100% rename from src/Package/Impl/Repl/IRInteractiveProvider.cs rename to src/Package/Impl/Repl/Definitions/IRInteractiveProvider.cs diff --git a/src/Package/Impl/Repl/Definitions/IReplWindow.cs b/src/Package/Impl/Repl/Definitions/IReplWindow.cs new file mode 100644 index 000000000..f15ff2d58 --- /dev/null +++ b/src/Package/Impl/Repl/Definitions/IReplWindow.cs @@ -0,0 +1,31 @@ +using System; +using Microsoft.VisualStudio.InteractiveWindow.Shell; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Text.Editor; + +namespace Microsoft.VisualStudio.R.Package.Repl { + public interface IReplWindow : IDisposable { + bool IsActive { get; } + void Show(); + IVsInteractiveWindow GetInteractiveWindow(); + + /// + /// Enqueues the provided code for execution. If there's no current execution the code is + /// inserted at the caret position. Otherwise the code is stored for when the current + /// execution is completed. + /// + /// If the current input becomes complete after inserting the code then the input is executed. + /// + /// If the code is not complete and addNewLine is true then a new line character is appended + /// to the end of the input. + /// + /// The code to be inserted + /// True to add a new line on non-complete inputs. + void EnqueueCode(string code, bool addNewLine); + + void ExecuteCode(string code); + void ReplaceCurrentExpression(string replaceWith); + void ExecuteCurrentExpression(ITextView textView); + void ClearPendingInputs(); + } +} diff --git a/src/Package/Impl/Repl/IVsInteractiveWindowProvider.cs b/src/Package/Impl/Repl/Definitions/IVsInteractiveWindowProvider.cs similarity index 100% rename from src/Package/Impl/Repl/IVsInteractiveWindowProvider.cs rename to src/Package/Impl/Repl/Definitions/IVsInteractiveWindowProvider.cs diff --git a/src/Package/Impl/Repl/RHostClientApp.cs b/src/Package/Impl/Repl/RHostClientApp.cs index d597c12c7..356e1e997 100644 --- a/src/Package/Impl/Repl/RHostClientApp.cs +++ b/src/Package/Impl/Repl/RHostClientApp.cs @@ -1,22 +1,28 @@ using System; +using System.Diagnostics; using System.Threading; using Microsoft.Common.Core.Shell; using Microsoft.R.Host.Client; +using Microsoft.R.Support.Settings; +using Microsoft.R.Support.Settings.Definitions; +using Microsoft.VisualStudio.R.Package.Definitions; using Microsoft.VisualStudio.R.Package.Help; using Microsoft.VisualStudio.R.Package.Plots; +using Microsoft.VisualStudio.R.Package.Plots.Definitions; using Microsoft.VisualStudio.R.Package.RPackages.Mirrors; using Microsoft.VisualStudio.R.Package.Shell; +using Microsoft.VisualStudio.R.Package.Utilities; using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Task = System.Threading.Tasks.Task; namespace Microsoft.VisualStudio.R.Package.Repl { - internal sealed class RHostClientApp: IRHostClientApp { + internal sealed class RHostClientApp : IRHostClientApp { private static readonly Lazy InstanceLazy = new Lazy(() => new RHostClientApp()); public static IRHostClientApp Instance => InstanceLazy.Value; - private RHostClientApp() {} + private RHostClientApp() { } /// /// Displays error message in the host-specific UI @@ -39,7 +45,13 @@ public async System.Threading.Tasks.Task ShowMessage(string mess /// public async Task ShowHelp(string url) { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - HelpWindowPane.Navigate(url); + if (RToolsSettings.Current.HelpBrowser == HelpBrowserType.External) { + Process.Start(url); + } else { + HelpWindowPane pane = ToolWindowUtilities.ShowWindowPane(0, focus: false); + var container = pane as IVisualComponentContainer; + container.Component.Navigate(url); + } } /// @@ -47,18 +59,8 @@ public async Task ShowHelp(string url) { /// public async Task Plot(string filePath, CancellationToken ct) { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(CancellationToken.None); - - var frame = FindPlotWindow(__VSFINDTOOLWIN.FTW_fFindFirst | __VSFINDTOOLWIN.FTW_fForceCreate); // TODO: acquire plot content provider through service - if (frame != null) { - object docView; - ErrorHandler.ThrowOnFailure(frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocView, out docView)); - if (docView != null) { - PlotWindowPane pane = (PlotWindowPane)docView; - pane.PlotContentProvider.LoadFile(filePath); - - frame.ShowNoActivate(); - } - } + IPlotHistory history = VsAppShell.Current.ExportProvider.GetExportedValue(); + history.PlotContentProvider.LoadFile(filePath); } /// diff --git a/src/Package/Impl/Repl/RInteractiveEvaluator.cs b/src/Package/Impl/Repl/RInteractiveEvaluator.cs index 9196e903f..d6cc7bace 100644 --- a/src/Package/Impl/Repl/RInteractiveEvaluator.cs +++ b/src/Package/Impl/Repl/RInteractiveEvaluator.cs @@ -7,11 +7,9 @@ using Microsoft.Common.Core; using Microsoft.R.Core.Parser; using Microsoft.R.Host.Client; -using Microsoft.R.Support.Settings; using Microsoft.R.Support.Settings.Definitions; using Microsoft.VisualStudio.InteractiveWindow; using Microsoft.VisualStudio.R.Package.History; -using Microsoft.VisualStudio.R.Package.Plots; using Microsoft.VisualStudio.R.Package.Shell; using Microsoft.VisualStudio.Shell; @@ -37,13 +35,14 @@ public void Dispose() { public async Task InitializeAsync() { try { - await Session.StartHostAsync(new RHostStartupInfo { - Name = "REPL", - RBasePath = _toolsSettings.RBasePath, - RCommandLineArguments = _toolsSettings.RCommandLineArguments, - CranMirrorName = _toolsSettings.CranMirror - }); - + if (!Session.IsHostRunning) { + await Session.StartHostAsync(new RHostStartupInfo { + Name = "REPL", + RBasePath = _toolsSettings.RBasePath, + RCommandLineArguments = _toolsSettings.RCommandLineArguments, + CranMirrorName = _toolsSettings.CranMirror + }); + } return ExecutionResult.Success; } catch (RHostBinaryMissingException) { await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(CancellationToken.None); diff --git a/src/Package/Impl/Repl/RInteractiveWindowProvider.cs b/src/Package/Impl/Repl/RInteractiveWindowProvider.cs index 787f9a547..92e793c0b 100644 --- a/src/Package/Impl/Repl/RInteractiveWindowProvider.cs +++ b/src/Package/Impl/Repl/RInteractiveWindowProvider.cs @@ -53,11 +53,11 @@ private static async Task InitializeWindowAsync(IInteractiveWindow window) { } public void Open(int instanceId, bool focus) { - if (!ReplWindow.ReplWindowExists()) { + if (!ReplWindow.ReplWindowExists) { var window = Create(instanceId); window.Show(focus); } else { - ReplWindow.Show(); + ReplWindow.ShowWindow(); } } } diff --git a/src/Package/Impl/Repl/ReplWindow.cs b/src/Package/Impl/Repl/ReplWindow.cs index 6b08f2734..a082eece0 100644 --- a/src/Package/Impl/Repl/ReplWindow.cs +++ b/src/Package/Impl/Repl/ReplWindow.cs @@ -12,22 +12,24 @@ using Microsoft.VisualStudio.Language.Intellisense; using Microsoft.VisualStudio.R.Package.Shell; using Microsoft.VisualStudio.R.Packages.R; -using Microsoft.VisualStudio.Shell; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.Text.Editor.OptionsExtensionMethods; -using Task = System.Threading.Tasks.Task; namespace Microsoft.VisualStudio.R.Package.Repl { /// /// Tracks most recently active REPL window /// - internal sealed class ReplWindow : IVsWindowFrameEvents, IDisposable { + internal sealed class ReplWindow : IReplWindow, IVsWindowFrameEvents, IVsDebuggerEvents, IDisposable { private uint _windowFrameEventsCookie; private IVsInteractiveWindow _lastUsedReplWindow; private ConcurrentQueue _pendingInputs = new ConcurrentQueue(); - private static readonly Lazy Instance = new Lazy(() => new ReplWindow()); + private static IReplWindow _instance; + private static IVsWindowFrame _frame; + private uint _debuggerEventsCookie; + private bool _replLostFocus; + private bool _enteredBreakMode; class PendingSubmission { public string Code; @@ -35,17 +37,33 @@ class PendingSubmission { } public ReplWindow() { - IVsUIShell7 shell = VsAppShell.Current.GetGlobalService(typeof(SVsUIShell)); - if (shell != null) { - _windowFrameEventsCookie = shell.AdviseWindowFrameEvents(this); + IVsUIShell7 shell7 = VsAppShell.Current.GetGlobalService(typeof(SVsUIShell)); + if (shell7 != null) { + _windowFrameEventsCookie = shell7.AdviseWindowFrameEvents(this); } + var debugger = VsAppShell.Current.GetGlobalService(typeof(IVsDebugger)); + if (debugger != null) { + debugger.AdviseDebuggerEvents(this, out _debuggerEventsCookie); + } + EnsureReplWindow(); } - public static ReplWindow Current => Instance.Value; + public static IReplWindow Current { + get { + if (_instance == null) { + _instance = new ReplWindow(); + } + return _instance; + } + internal set { + _instance = value; + } + } + #region IReplWindow public bool IsActive { get { - IVsWindowFrame frame = Current.GetToolWindow(); + IVsWindowFrame frame = GetToolWindow(); if (frame != null) { int onScreen; frame.IsOnScreen(out onScreen); @@ -55,63 +73,8 @@ public bool IsActive { } } - public IVsWindowFrame GetToolWindow() { - IVsWindowFrame frame; - IVsUIShell shell = VsAppShell.Current.GetGlobalService(typeof(SVsUIShell)); - Guid persistenceSlot = RGuidList.ReplInteractiveWindowProviderGuid; - - // First just find. If it exists, use it. - shell.FindToolWindow((int)__VSFINDTOOLWIN.FTW_fFindFirst, ref persistenceSlot, out frame); - return frame; - } - - private void ProcessQueuedInput() { - IVsInteractiveWindow interactive = Instance.Value.GetInteractiveWindow(); - if (interactive == null) { - return; - } - - var window = interactive.InteractiveWindow; - var view = interactive.InteractiveWindow.TextView; - - // Process all of our pending inputs until we get a complete statement - PendingSubmission current; - while (_pendingInputs.TryDequeue(out current)) { - var curLangBuffer = interactive.InteractiveWindow.CurrentLanguageBuffer; - - var curLangPoint = view.MapDownToBuffer( - interactive.InteractiveWindow.CurrentLanguageBuffer.CurrentSnapshot.Length, - curLangBuffer - ); - if (curLangPoint == null) { - // ensure the caret is in the input buffer, otherwise inserting code does nothing - view.Caret.MoveTo( - view.BufferGraph.MapUpToBuffer( - new SnapshotPoint( - curLangBuffer.CurrentSnapshot, curLangBuffer.CurrentSnapshot.Length - ), - PointTrackingMode.Positive, - PositionAffinity.Successor, - view.TextBuffer - ).Value - ); - } - - window.InsertCode(current.Code); - string fullCode = curLangBuffer.CurrentSnapshot.GetText(); - - if (window.Evaluator.CanExecuteCode(fullCode)) { - // the code is complete, execute it now - window.Operations.ExecuteInput(); - return; - } - - if (current.AddNewLine) { - // We want a new line after non-complete inputs, e.g. the user ctrl-entered on - // function() { - window.InsertCode(view.Options.GetNewLineCharacter()); - } - } + public void Show() { + GetToolWindow()?.Show(); } /// @@ -127,7 +90,7 @@ private void ProcessQueuedInput() { /// The code to be inserted /// True to add a new line on non-complete inputs. public void EnqueueCode(string code, bool addNewLine) { - IVsInteractiveWindow current = Instance.Value.GetInteractiveWindow(); + IVsInteractiveWindow current = _instance.GetInteractiveWindow(); if (current != null) { if (current.InteractiveWindow.IsResetting) { return; @@ -148,15 +111,21 @@ public void ClearPendingInputs() { } public void ExecuteCode(string code) { - IVsInteractiveWindow current = Instance.Value.GetInteractiveWindow(); + IVsInteractiveWindow current = _instance.GetInteractiveWindow(); if (current != null && !current.InteractiveWindow.IsInitializing && !string.IsNullOrWhiteSpace(code)) { current.InteractiveWindow.AddInput(code); + + var buffer = current.InteractiveWindow.CurrentLanguageBuffer; + var endPoint = current.InteractiveWindow.TextView.MapUpToBuffer(buffer.CurrentSnapshot.Length, buffer); + if (endPoint != null) { + current.InteractiveWindow.TextView.Caret.MoveTo(endPoint.Value); + } current.InteractiveWindow.Operations.ExecuteInput(); } } public void ReplaceCurrentExpression(string replaceWith) { - IVsInteractiveWindow current = Instance.Value.GetInteractiveWindow(); + IVsInteractiveWindow current = _instance.GetInteractiveWindow(); if (current != null && !current.InteractiveWindow.IsInitializing) { var textBuffer = current.InteractiveWindow.CurrentLanguageBuffer; var span = new Span(0, textBuffer.CurrentSnapshot.Length); @@ -170,7 +139,7 @@ public void ExecuteCurrentExpression(ITextView textView) { ICompletionBroker broker = VsAppShell.Current.ExportProvider.GetExportedValue(); broker.DismissAllSessions(textView); - IVsInteractiveWindow current = Instance.Value.GetInteractiveWindow(); + IVsInteractiveWindow current = _instance.GetInteractiveWindow(); if (current != null && !current.InteractiveWindow.IsRunning) { var curBuffer = current.InteractiveWindow.CurrentLanguageBuffer; SnapshotPoint? documentPoint = textView.MapDownToBuffer(textView.Caret.Position.BufferPosition, curBuffer); @@ -215,6 +184,67 @@ public void ExecuteCurrentExpression(ITextView textView) { } } } + #endregion + + private void ProcessQueuedInput() { + IVsInteractiveWindow interactive = _instance.GetInteractiveWindow(); + if (interactive == null) { + return; + } + + var window = interactive.InteractiveWindow; + var view = interactive.InteractiveWindow.TextView; + + // Process all of our pending inputs until we get a complete statement + PendingSubmission current; + while (_pendingInputs.TryDequeue(out current)) { + var curLangBuffer = interactive.InteractiveWindow.CurrentLanguageBuffer; + SnapshotPoint? curLangPoint = null; + + // If anything is selected we need to clear it before inserting new code + view.Selection.Clear(); + + // Find out if caret position is where code can be inserted. + // Caret must be in the area mappable to the language buffer. + if (!view.Caret.InVirtualSpace) { + curLangPoint = view.MapDownToBuffer(view.Caret.Position.BufferPosition, curLangBuffer); + } + + if (curLangPoint == null) { + // Ensure the caret is in the input buffer, otherwise inserting code does nothing. + SnapshotPoint? viewPoint = view.BufferGraph.MapUpToBuffer( + new SnapshotPoint(curLangBuffer.CurrentSnapshot, curLangBuffer.CurrentSnapshot.Length), + PointTrackingMode.Positive, + PositionAffinity.Predecessor, + view.TextBuffer); + + if (!viewPoint.HasValue) { + // Unable to map language buffer to view. + // Try moving caret to the end of the view then. + viewPoint = new SnapshotPoint(view.TextBuffer.CurrentSnapshot, view.TextBuffer.CurrentSnapshot.Length); + } + + if (viewPoint.HasValue) { + view.Caret.MoveTo(viewPoint.Value); + } + } + + window.InsertCode(current.Code); + string fullCode = curLangBuffer.CurrentSnapshot.GetText(); + + if (window.Evaluator.CanExecuteCode(fullCode)) { + // the code is complete, execute it now + window.Operations.ExecuteInput(); + return; + } + + if (current.AddNewLine) { + // We want a new line after non-complete inputs, e.g. the user ctrl-entered on + // function() { + window.InsertCode(view.Options.GetNewLineCharacter()); + } + } + } private static bool IsMultiLineCandidate(string text) { if (text.IndexOfAny(new[] { '\n', '\r' }) != -1) { @@ -243,36 +273,30 @@ public IVsInteractiveWindow GetInteractiveWindow() { if (frame != null) { frame.Show(); - CheckReplFrame(frame); + CheckReplFrame(frame, gettingFocus: true); } } return _lastUsedReplWindow; } - public static bool ReplWindowExists() { - IVsWindowFrame frame = FindReplWindowFrame(__VSFINDTOOLWIN.FTW_fFindFirst); - return frame != null; - } + public static bool ReplWindowExists => _frame != null; - public static void Show() { - IVsWindowFrame frame = FindReplWindowFrame(__VSFINDTOOLWIN.FTW_fFindFirst); - frame?.Show(); + public static void ShowWindow() { + _frame?.Show(); } - public static async Task EnsureReplWindow() { - await ThreadHelper.JoinableTaskFactory.SwitchToMainThreadAsync(); - if (!ReplWindowExists()) { - IVsWindowFrame frame = FindReplWindowFrame(__VSFINDTOOLWIN.FTW_fForceCreate); - if (frame != null) { - //IntPtr bitmap = Resources.ReplWindowIcon.GetHbitmap(); - frame.SetProperty((int)__VSFPROPID4.VSFPROPID_TabImage, Resources.ReplWindowIcon); - frame.Show(); + public static void EnsureReplWindow() { + if (!ReplWindowExists) { + _frame = FindReplWindowFrame(__VSFINDTOOLWIN.FTW_fForceCreate); + if (_frame != null) { + _frame.SetProperty((int)__VSFPROPID4.VSFPROPID_TabImage, Resources.ReplWindowIcon); + _frame.Show(); } } } - public static IVsWindowFrame FindReplWindowFrame(__VSFINDTOOLWIN flags) { + internal static IVsWindowFrame FindReplWindowFrame(__VSFINDTOOLWIN flags) { IVsWindowFrame frame; IVsUIShell shell = VsAppShell.Current.GetGlobalService(typeof(SVsUIShell)); @@ -289,7 +313,7 @@ public void OnFrameCreated(IVsWindowFrame frame) { } public void OnFrameDestroyed(IVsWindowFrame frame) { - + } public void OnFrameIsVisibleChanged(IVsWindowFrame frame, bool newIsVisible) { @@ -299,9 +323,13 @@ public void OnFrameIsOnScreenChanged(IVsWindowFrame frame, bool newIsOnScreen) { } public void OnActiveFrameChanged(IVsWindowFrame oldFrame, IVsWindowFrame newFrame) { + _replLostFocus = false; // Track last recently used REPL window - if (!CheckReplFrame(oldFrame)) { - CheckReplFrame(newFrame); + if (!CheckReplFrame(oldFrame, gettingFocus: false)) { + CheckReplFrame(newFrame, gettingFocus: true); + } else { + _replLostFocus = true; + VsAppShell.Current.DispatchOnUIThread(() => CheckPossibleBreakModeFocusChange()); } } #endregion @@ -314,25 +342,47 @@ public void Dispose() { _windowFrameEventsCookie = 0; } + if (_debuggerEventsCookie != 0) { + var debugger = VsAppShell.Current.GetGlobalService(typeof(IVsDebugger)); + debugger.UnadviseDebuggerEvents(_debuggerEventsCookie); + _debuggerEventsCookie = 0; + } + _lastUsedReplWindow = null; } #endregion - private bool CheckReplFrame(IVsWindowFrame frame) { + private IVsWindowFrame GetToolWindow() { + IVsWindowFrame frame; + IVsUIShell shell = VsAppShell.Current.GetGlobalService(typeof(SVsUIShell)); + Guid persistenceSlot = RGuidList.ReplInteractiveWindowProviderGuid; + + // First just find. If it exists, use it. + shell.FindToolWindow((int)__VSFINDTOOLWIN.FTW_fFindFirst, ref persistenceSlot, out frame); + return frame; + } + + private bool CheckReplFrame(IVsWindowFrame frame, bool gettingFocus) { if (frame != null) { Guid property; frame.GetGuidProperty((int)__VSFPROPID.VSFPROPID_GuidPersistenceSlot, out property); if (property == RGuidList.ReplInteractiveWindowProviderGuid) { object docView; + frame.GetProperty((int)__VSFPROPID.VSFPROPID_DocView, out docView); if (_lastUsedReplWindow != null) { _lastUsedReplWindow.InteractiveWindow.ReadyForInput -= ProcessQueuedInput; } + _lastUsedReplWindow = docView as IVsInteractiveWindow; if (_lastUsedReplWindow != null) { IVsUIShell shell = VsAppShell.Current.GetGlobalService(typeof(SVsUIShell)); shell.UpdateCommandUI(1); + _lastUsedReplWindow.InteractiveWindow.ReadyForInput += ProcessQueuedInput; + if (gettingFocus) { + PositionCaretAtPrompt(_lastUsedReplWindow.InteractiveWindow.TextView); + } } return _lastUsedReplWindow != null; } @@ -340,5 +390,42 @@ private bool CheckReplFrame(IVsWindowFrame frame) { return false; } + + private void CheckPossibleBreakModeFocusChange() { + if (_enteredBreakMode && _replLostFocus) { + // When debugger hits a breakpoint it typically activates the editor. + // This is not desirable when focus was in the interactive window + // i.e. user worked in the REPL and not in the editor. Pull + // the focus back here. + Show(); + + _replLostFocus = false; + _enteredBreakMode = false; + } + } + + #region IVsDebuggerEvents + public int OnModeChange(DBGMODE dbgmodeNew) { + _enteredBreakMode = dbgmodeNew == DBGMODE.DBGMODE_Break; + return VSConstants.S_OK; + } + #endregion + + public static void PositionCaretAtPrompt(ITextView textView = null) { + textView = textView ?? _instance.GetInteractiveWindow()?.InteractiveWindow.TextView; + if (textView == null) { + return; + } + + // Click on text view will move the caret so we need + // to move caret to the prompt after view finishes its + // mouse processing. + VsAppShell.Current.DispatchOnUIThread(() => { + textView.Selection.Clear(); + ITextSnapshot snapshot = textView.TextBuffer.CurrentSnapshot; + SnapshotPoint caretPosition = new SnapshotPoint(snapshot, snapshot.Length); + textView.Caret.MoveTo(caretPosition); + }); + } } } diff --git a/src/Package/Impl/Repl/Workspace/InterruptRCommand.cs b/src/Package/Impl/Repl/Workspace/InterruptRCommand.cs index 256aa30f7..1e7c20157 100644 --- a/src/Package/Impl/Repl/Workspace/InterruptRCommand.cs +++ b/src/Package/Impl/Repl/Workspace/InterruptRCommand.cs @@ -1,17 +1,21 @@ using System; using Microsoft.Common.Core; using Microsoft.R.Host.Client; -using Microsoft.R.Host.Client.Session; -using Microsoft.R.Support.Settings; using Microsoft.VisualStudio.R.Package.Commands; using Microsoft.VisualStudio.R.Packages.R; +using Microsoft.VisualStudio.Shell.Interop; namespace Microsoft.VisualStudio.R.Package.Repl.Workspace { internal sealed class InterruptRCommand : PackageCommand { + private IReplWindow _replWindow; private readonly IRSession _session; + private IVsDebugger _debugger; private volatile bool _enabled; - public InterruptRCommand(IRSessionProvider rSessionProvider) : base(RGuidList.RCmdSetGuid, RPackageCommandId.icmdInterruptR) { + public InterruptRCommand(IReplWindow replWindow, IRSessionProvider rSessionProvider, IVsDebugger debugger) : + base(RGuidList.RCmdSetGuid, RPackageCommandId.icmdInterruptR) { + _replWindow = replWindow; + _debugger = debugger; _session = rSessionProvider.GetInteractiveWindowRSession(); _session.Disconnected += OnDisconnected; _session.BeforeRequest += OnBeforeRequest; @@ -30,18 +34,22 @@ private void OnAfterRequest(object sender, RRequestEventArgs e) { _enabled = true; } - protected override void SetStatus() { - if (ReplWindow.Current.IsActive) { + internal override void SetStatus() { + DBGMODE[] mode = new DBGMODE[1]; + _debugger.GetMode(mode); + + if (_replWindow.IsActive) { Visible = true; - Enabled = _session.IsHostRunning && _enabled; + Enabled = _session.IsHostRunning && _enabled && mode[0] != DBGMODE.DBGMODE_Break; } else { Visible = false; + Enabled = false; } } - protected override void Handle() {; + internal override void Handle() { if (_enabled) { - ReplWindow.Current.ClearPendingInputs(); + _replWindow.ClearPendingInputs(); _session.CancelAllAsync().DoNotWait(); _enabled = false; } diff --git a/src/Package/Impl/Repl/Workspace/LoadWorkspaceCommand.cs b/src/Package/Impl/Repl/Workspace/LoadWorkspaceCommand.cs index 2421057a9..ad8d3de2a 100644 --- a/src/Package/Impl/Repl/Workspace/LoadWorkspaceCommand.cs +++ b/src/Package/Impl/Repl/Workspace/LoadWorkspaceCommand.cs @@ -22,7 +22,7 @@ public LoadWorkspaceCommand(IRSessionProvider rSessionProvider, IProjectServiceA _projectServiceAccessor = projectServiceAccessor; } - protected override void SetStatus() { + internal override void SetStatus() { if (ReplWindow.Current.IsActive) { Visible = true; Enabled = _rSession.IsHostRunning; @@ -31,7 +31,7 @@ protected override void SetStatus() { } } - protected override void Handle() { + internal override void Handle() { var projectService = _projectServiceAccessor.GetProjectService(); var lastLoadedProject = projectService.LoadedUnconfiguredProjects.LastOrDefault(); diff --git a/src/Package/Impl/Repl/Workspace/ResetReplCommand.cs b/src/Package/Impl/Repl/Workspace/ResetReplCommand.cs new file mode 100644 index 000000000..d1953afc6 --- /dev/null +++ b/src/Package/Impl/Repl/Workspace/ResetReplCommand.cs @@ -0,0 +1,34 @@ +using Microsoft.Common.Core; +using Microsoft.VisualStudio.InteractiveWindow.Shell; +using Microsoft.VisualStudio.R.Package.Commands; +using Microsoft.VisualStudio.R.Packages.R; + +namespace Microsoft.VisualStudio.R.Package.Repl.Workspace { + internal sealed class ResetReplCommand : PackageCommand { + public ResetReplCommand() : + base(RGuidList.RCmdSetGuid, RPackageCommandId.icmdResetRepl) { + } + + internal override void SetStatus() { + Visible = false; + Enabled = false; + + if (ReplWindow.Current.IsActive) { + IVsInteractiveWindow window = ReplWindow.Current.GetInteractiveWindow(); + if (window != null && window.InteractiveWindow != null) { + Visible = true; + Enabled = true; + } + } + } + + internal override void Handle() { + if (ReplWindow.Current.IsActive) { + IVsInteractiveWindow window = ReplWindow.Current.GetInteractiveWindow(); + if (window != null && window.InteractiveWindow != null) { + window.InteractiveWindow.Operations.ResetAsync().DoNotWait(); + } + } + } + } +} diff --git a/src/Package/Impl/Repl/Workspace/SaveWorkspaceCommand.cs b/src/Package/Impl/Repl/Workspace/SaveWorkspaceCommand.cs index d5432a3be..0da4759b6 100644 --- a/src/Package/Impl/Repl/Workspace/SaveWorkspaceCommand.cs +++ b/src/Package/Impl/Repl/Workspace/SaveWorkspaceCommand.cs @@ -23,7 +23,7 @@ public SaveWorkspaceCommand(IRSessionProvider rSessionProvider, IProjectServiceA _projectServiceAccessor = projectServiceAccessor; } - protected override void SetStatus() { + internal override void SetStatus() { if (ReplWindow.Current.IsActive) { Visible = true; Enabled = _rSession.IsHostRunning; @@ -32,7 +32,7 @@ protected override void SetStatus() { } } - protected override void Handle() { + internal override void Handle() { var projectService = _projectServiceAccessor.GetProjectService(); var lastLoadedProject = projectService.LoadedUnconfiguredProjects.LastOrDefault(); diff --git a/src/Package/Impl/Repl/Workspace/ShowRInteractiveWindowsCommand.cs b/src/Package/Impl/Repl/Workspace/ShowRInteractiveWindowsCommand.cs index e3777c3f0..7667078ac 100644 --- a/src/Package/Impl/Repl/Workspace/ShowRInteractiveWindowsCommand.cs +++ b/src/Package/Impl/Repl/Workspace/ShowRInteractiveWindowsCommand.cs @@ -6,7 +6,7 @@ internal sealed class ShowRInteractiveWindowsCommand : PackageCommand { public ShowRInteractiveWindowsCommand() : base(RGuidList.RCmdSetGuid, RPackageCommandId.icmdShowReplWindow) { } - protected override void Handle() { + internal override void Handle() { RPackage.Current.InteractiveWindowProvider.Open(instanceId: 0, focus: true); } } diff --git a/src/Package/Impl/Resources.Designer.cs b/src/Package/Impl/Resources.Designer.cs index 703ead029..e9d33bebd 100644 --- a/src/Package/Impl/Resources.Designer.cs +++ b/src/Package/Impl/Resources.Designer.cs @@ -105,15 +105,6 @@ public static string AttachmentTooLarge3 { } } - /// - /// Looks up a localized string similar to Unable to open plot file. Exception: {0}. - /// - public static string CannotOpenPlotFile { - get { - return ResourceManager.GetString("CannotOpenPlotFile", resourceCulture); - } - } - /// /// Looks up a localized string similar to Choose Directory. /// @@ -322,8 +313,7 @@ public static string Error_ReplUnicodeCoversion { } /// - /// Looks up a localized string similar to Unable to determine location of R binaries. If R is not installed - ///please navigate to https://cran.r-project.org, install R for Windows and restart Visual Studio. If R is installed please specify path to R binaries in Tools | Options | R Tools.. + /// Looks up a localized string similar to Unable to determine location of R binaries. If R is not installed please install Microsoft R Open and restart Visual Studio. If R is installed please specify path to R binaries in Tools | Options | R Tools.. /// public static string Error_UnableToFindR { get { @@ -343,9 +333,18 @@ public static string Error_UnsupportedRVersion { /// /// Looks up a localized string similar to Export Plot As Image. /// - public static string ExportPlotDialogTitle { + public static string ExportPlotAsImageDialogTitle { get { - return ResourceManager.GetString("ExportPlotDialogTitle", resourceCulture); + return ResourceManager.GetString("ExportPlotAsImageDialogTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Export Plot As PDF. + /// + public static string ExportPlotAsPdfDialogTitle { + get { + return ResourceManager.GetString("ExportPlotAsPdfDialogTitle", resourceCulture); } } @@ -530,15 +529,6 @@ public static string OpenFunctionHelp { } } - /// - /// Looks up a localized string similar to Open R Plot File. - /// - public static string OpenPlotDialogTitle { - get { - return ResourceManager.GetString("OpenPlotDialogTitle", resourceCulture); - } - } - /// /// Looks up a localized string similar to R Packages. /// @@ -557,21 +547,42 @@ public static string OutputWindowName_Publish { } } + /// + /// Looks up a localized string similar to Error copying plot to the clipboard. + ///{0}. + /// + public static string PlotCopyToClipboardError { + get { + return ResourceManager.GetString("PlotCopyToClipboardError", resourceCulture); + } + } + /// /// Looks up a localized string similar to PNG (*.png)|*.png|JPEG (*.jpg;*.jpeg)|*.jpg;*.jpeg|BMP (*.bmp)|*.bmp|TIFF (*.tif;*.tiff)|*.tif;*.tiff|All Files (*.*)|*.*. /// - public static string PlotExportFilter { + public static string PlotExportAsImageFilter { + get { + return ResourceManager.GetString("PlotExportAsImageFilter", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to PDF (*.pdf)|*.pdf|All Files (*.*)|*.*. + /// + public static string PlotExportAsPdfFilter { get { - return ResourceManager.GetString("PlotExportFilter", resourceCulture); + return ResourceManager.GetString("PlotExportAsPdfFilter", resourceCulture); } } /// - /// Looks up a localized string similar to R Plot Files (*.vsplot)|*.vsplot|All Files (*.*)|*.*. + /// Looks up a localized string similar to File extension '{0}' is not supported. + /// + ///Supported formats are: jpg/jpeg, png, tif/tiff, bmp.. /// - public static string PlotFileFilter { + public static string PlotExportUnsupportedImageFormat { get { - return ResourceManager.GetString("PlotFileFilter", resourceCulture); + return ResourceManager.GetString("PlotExportUnsupportedImageFormat", resourceCulture); } } @@ -652,7 +663,7 @@ public static string RPromptClassification { } /// - /// Looks up a localized string similar to Markdown Files (*.md;*.rmd;*.markdown)|*.md;*.rmd;*.markdown|All Files (*.*)|*.*. + /// Looks up a localized string similar to Markdown Files (*.md;*.rmd;*.markdown)|*.md;*.rmd;*.markdown|. /// public static string SaveAsFilterMD { get { @@ -661,7 +672,7 @@ public static string SaveAsFilterMD { } /// - /// Looks up a localized string similar to R Language Files (*.r)|*.r|All Files (*.*)|*.*. + /// Looks up a localized string similar to R Language Files (*.r)|*.r|. /// public static string SaveAsFilterR { get { @@ -850,6 +861,15 @@ public static string Settings_CranMirror_Description { } } + /// + /// Looks up a localized string similar to Debugging. + /// + public static string Settings_DebuggingCategory { + get { + return ResourceManager.GetString("Settings_DebuggingCategory", resourceCulture); + } + } + /// /// Looks up a localized string similar to Show syntax errors. /// @@ -968,7 +988,7 @@ public static string Settings_MultilineHistorySelection { } /// - /// Looks up a localized string similar to Single click in R history window selects the entire fragment that was sent to R.\r\nUp/Down arrows in the R Interactive Window navigate through chunks instead of single lines.. + /// Looks up a localized string similar to Single click in R history window selects the entire fragment that was sent to R. Up/Down arrows in the R Interactive Window navigate through chunks instead of single lines.. /// public static string Settings_MultilineHistorySelection_Description { get { @@ -1093,6 +1113,24 @@ public static string Settings_SendToRepl_Description { } } + /// + /// Looks up a localized string similar to Show dot-prefixed variables. + /// + public static string Settings_ShowDotPrefixedVariables { + get { + return ResourceManager.GetString("Settings_ShowDotPrefixedVariables", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Show variables that have names beginning with "." in Variable Explorer and debugger tool windows.. + /// + public static string Settings_ShowDotPrefixedVariables_Description { + get { + return ResourceManager.GetString("Settings_ShowDotPrefixedVariables_Description", resourceCulture); + } + } + /// /// Looks up a localized string similar to Show internal functions. /// @@ -1237,6 +1275,15 @@ public static string VariableExplorer_NameHeader { } } + /// + /// Looks up a localized string similar to [truncated]. + /// + public static string VariableExplorer_Truncated { + get { + return ResourceManager.GetString("VariableExplorer_Truncated", resourceCulture); + } + } + /// /// Looks up a localized string similar to Type. /// @@ -1255,6 +1302,15 @@ public static string VariableExplorer_ValueHeader { } } + /// + /// Looks up a localized string similar to R Data. + /// + public static string VariableGrid_Caption { + get { + return ResourceManager.GetString("VariableGrid_Caption", resourceCulture); + } + } + /// /// Looks up a localized string similar to Variable Explorer. /// @@ -1265,7 +1321,7 @@ public static string VariableWindowCaption { } /// - /// Looks up a localized string similar to Do you want to reset keyboard shortcuts to be compable with the RStudio where possible?. + /// Looks up a localized string similar to Do you want to reset keyboard shortcuts to be compatible with the RStudio where possible?. /// public static string Warning_RStudioKeyboardShortcuts { get { diff --git a/src/Package/Impl/Resources.resx b/src/Package/Impl/Resources.resx index bb641b72f..643c3583d 100644 --- a/src/Package/Impl/Resources.resx +++ b/src/Package/Impl/Resources.resx @@ -166,7 +166,7 @@ Reformat code as you type - R Language Files (*.r)|*.r|All Files (*.*)|*.* + R Language Files (*.r)|*.r| IntelliSense @@ -220,7 +220,7 @@ Microsoft.R.Host.exe is missing. Click OK to open download link in the default browser. - Markdown Files (*.md;*.rmd;*.markdown)|*.md;*.rmd;*.markdown|All Files (*.*)|*.* + Markdown Files (*.md;*.rmd;*.markdown)|*.md;*.rmd;*.markdown| Control+E, Control+E @@ -264,17 +264,15 @@ Run plotting command in R Interactive Window - - Open R Plot File + + Error copying plot to the clipboard. +{0} - - R Plot Files (*.vsplot)|*.vsplot|All Files (*.*)|*.* - - + PNG (*.png)|*.png|JPEG (*.jpg;*.jpeg)|*.jpg;*.jpeg|BMP (*.bmp)|*.bmp|TIFF (*.tif;*.tiff)|*.tif;*.tiff|All Files (*.*)|*.* - - Unable to open plot file. Exception: {0} + + PDF (*.pdf)|*.pdf|All Files (*.*)|*.* Starting R Session... @@ -282,9 +280,17 @@ Save R Plot File - + Export Plot As Image + + Export Plot As PDF + + + File extension '{0}' is not supported. + +Supported formats are: jpg/jpeg, png, tif/tiff, bmp. + Do you want to load the R data file {0} into the global environment? @@ -423,7 +429,7 @@ Use multiline selection - Single click in R history window selects the entire fragment that was sent to R.\r\nUp/Down arrows in the R Interactive Window navigate through chunks instead of single lines. + Single click in R history window selects the entire fragment that was sent to R. Up/Down arrows in the R Interactive Window navigate through chunks instead of single lines. Ask @@ -496,8 +502,7 @@ Command line text cannot be converted to default OS code page. Please set locale for non-Unicode programs in Control Panel -> Region -> Administrative to the locale you wish to use. - Unable to determine location of R binaries. If R is not installed -please navigate to https://cran.r-project.org, install R for Windows and restart Visual Studio. If R is installed please specify path to R binaries in Tools | Options | R Tools. + Unable to determine location of R binaries. If R is not installed please install Microsoft R Open and restart Visual Studio. If R is installed please specify path to R binaries in Tools | Options | R Tools. R History @@ -515,7 +520,7 @@ please navigate to https://cran.r-project.org, install R for Windows and restart History - Do you want to reset keyboard shortcuts to be compable with the RStudio where possible? + Do you want to reset keyboard shortcuts to be compatible with the RStudio where possible? This will reset Visual Studio window layout to the Data Scientist profile. Proceed? @@ -532,4 +537,19 @@ please navigate to https://cran.r-project.org, install R for Windows and restart Determines if R Tools should always be using external Web browser or try and send Help pages to the Help window and other Web requests to the external default Web browser. + + [truncated] + + + R Data + + + Debugging + + + Show dot-prefixed variables + + + Show variables that have names beginning with "." in Variable Explorer and debugger tool windows. + \ No newline at end of file diff --git a/src/Package/Impl/Shell/VsAppShell.cs b/src/Package/Impl/Shell/VsAppShell.cs index d89c1ea3b..41f89c858 100644 --- a/src/Package/Impl/Shell/VsAppShell.cs +++ b/src/Package/Impl/Shell/VsAppShell.cs @@ -1,6 +1,7 @@ using System; using System.ComponentModel.Composition; using System.ComponentModel.Composition.Hosting; +using System.ComponentModel.Design; using System.Diagnostics; using System.IO; using System.Runtime.InteropServices; @@ -20,6 +21,7 @@ using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; +using IServiceProvider = System.IServiceProvider; namespace Microsoft.VisualStudio.R.Package.Shell { /// @@ -158,6 +160,14 @@ public void ShowErrorMessage(string message) { OLEMSGBUTTON.OLEMSGBUTTON_OK, OLEMSGDEFBUTTON.OLEMSGDEFBUTTON_FIRST, OLEMSGICON.OLEMSGICON_CRITICAL, 0, out result); } + public void ShowContextMenu(Guid contextMenuGroup, int contextMenuId, int x, int y) { + var package = EnsurePackageLoaded(RGuidList.RPackageGuid); + if (package != null) { + var menuService = (IMenuCommandService)((IServiceProvider)package).GetService(typeof(IMenuCommandService)); + menuService.ShowContextMenu(new CommandID(contextMenuGroup, contextMenuId), x, y); + } + } + /// /// Displays question in a host-specific UI /// @@ -308,6 +318,7 @@ public string BrowseForFileSave(IntPtr owner, string filter, string initialPath VSSAVEFILENAMEW[] saveInfo = new VSSAVEFILENAMEW[1]; saveInfo[0].lStructSize = (uint)Marshal.SizeOf(typeof(VSSAVEFILENAMEW)); + saveInfo[0].dwFlags = 0x00000002; // OFN_OVERWRITEPROMPT saveInfo[0].pwzFilter = filter.Replace('|', '\0') + "\0"; saveInfo[0].hwndOwner = owner; saveInfo[0].pwzDlgTitle = title; @@ -363,5 +374,18 @@ private void DetemineTestEnvironment() { //TODO: check for VS Test host this.IsUITestEnvironment = false; } + + private IVsPackage EnsurePackageLoaded(Guid guidPackage) { + var shell = GetGlobalService(); + var guid = guidPackage; + IVsPackage package; + int hr = ErrorHandler.ThrowOnFailure(shell.IsPackageLoaded(ref guid, out package), VSConstants.E_FAIL); + guid = guidPackage; + if (hr != VSConstants.S_OK) { + ErrorHandler.ThrowOnFailure(shell.LoadPackage(ref guid, out package), VSConstants.E_FAIL); + } + + return package; + } } } diff --git a/src/Package/Impl/Telemetry/Data/FolderUtility.cs b/src/Package/Impl/Telemetry/Data/FolderUtility.cs new file mode 100644 index 000000000..83202d265 --- /dev/null +++ b/src/Package/Impl/Telemetry/Data/FolderUtility.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Microsoft.VisualStudio.R.Package.Telemetry.Data { + internal static class FolderUtility { + /// + /// Returns names of subfolders (relative paths) in a given directory. + /// + /// + /// + public static IEnumerable GetSubfolderRelativePaths(string directory) { + if (Directory.Exists(directory)) { + return Directory.EnumerateDirectories(directory).Select(x => x.Substring(directory.Length + 1)); + } + return Enumerable.Empty(); + } + + /// + /// Counts files in a folder and its subfolders + /// + internal static int CountFiles(string path) { + try { + return Directory.EnumerateFiles(path, "*.*", SearchOption.AllDirectories).Count(); + } catch (IOException) { } + return 0; + } + } +} diff --git a/src/Package/Impl/Telemetry/Data/RPackageData.cs b/src/Package/Impl/Telemetry/Data/RPackageData.cs new file mode 100644 index 000000000..de69f582a --- /dev/null +++ b/src/Package/Impl/Telemetry/Data/RPackageData.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using Microsoft.R.Actions.Utility; +using Microsoft.R.Support.Settings; + +namespace Microsoft.VisualStudio.R.Package.Telemetry.Data { + /// + /// Represents R package data as reported in telemetry + /// + internal static class RPackageData { + /// + /// Retrieves hashes for all R package names in a given folder + /// + /// + public static IEnumerable GetInstalledPackageHashes(RPackageType packageType) { + + string rInstallPath = RInstallation.GetRInstallPath(RToolsSettings.Current.RBasePath); + if (!string.IsNullOrEmpty(rInstallPath)) { + IEnumerable packageNames = Enumerable.Empty(); + if (packageType == RPackageType.Base) { + packageNames = FolderUtility.GetSubfolderRelativePaths(Path.Combine(rInstallPath, "library")); + } else { + Version v = RInstallation.GetRVersionFromFolderName(rInstallPath.Substring(rInstallPath.LastIndexOf('\\') + 1)); + if (v.Major > 0) { + string userLibraryPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), + @"R\win-library\", v.Major.ToString() + "." + v.Minor.ToString()); + + packageNames = FolderUtility.GetSubfolderRelativePaths(userLibraryPath); + } + } + + foreach (string p in packageNames) { + string hash = CalculateMD5Hash(p); + yield return hash; + } + } + } + + private static string CalculateMD5Hash(string input) { + SHA512 sha = SHA512.Create(); + byte[] inputBytes = Encoding.Unicode.GetBytes(input); + byte[] hash = sha.ComputeHash(inputBytes); + + return BitConverter.ToString(hash); + } + } +} diff --git a/src/Package/Impl/Telemetry/Data/RPackageType.cs b/src/Package/Impl/Telemetry/Data/RPackageType.cs new file mode 100644 index 000000000..4f95d5cc3 --- /dev/null +++ b/src/Package/Impl/Telemetry/Data/RPackageType.cs @@ -0,0 +1,6 @@ +namespace Microsoft.VisualStudio.R.Package.Telemetry.Data { + internal enum RPackageType { + Base, + User + } +} diff --git a/src/Package/Impl/Telemetry/Data/ToolWindowData.cs b/src/Package/Impl/Telemetry/Data/ToolWindowData.cs new file mode 100644 index 000000000..1dc597cc6 --- /dev/null +++ b/src/Package/Impl/Telemetry/Data/ToolWindowData.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using Microsoft.VisualStudio.Shell.Interop; + +namespace Microsoft.VisualStudio.R.Package.Telemetry.Data { + /// + /// Data on a tool window as reported in the telemetry + /// + internal class ToolWindowData { + public string Caption { get; private set; } + public int X { get; private set; } + public int Y { get; private set; } + public int Width { get; private set; } + public int Height { get; private set; } + + /// + /// Retrieves captions and positions of all active tool windows + /// + /// + public static IEnumerable GetToolWindowData(IVsUIShell shell) { + var data = new List(); + try { + IEnumWindowFrames e; + shell.GetToolWindowEnum(out e); + + IVsWindowFrame[] frame = new IVsWindowFrame[1]; + uint fetched = 0; + while (VSConstants.S_OK == e.Next(1, frame, out fetched) && fetched > 0) { + object objCaption; + frame[0].GetProperty((int)__VSFPROPID.VSFPROPID_Caption, out objCaption); + + VSSETFRAMEPOS[] pos = new VSSETFRAMEPOS[1]; + Guid relative; + int x, y, cx, cy; + frame[0].GetFramePos(pos, out relative, out x, out y, out cx, out cy); + + var d = new ToolWindowData() { + Caption = objCaption as string, + X = x, + Y = y, + Width = cx, + Height = cy + }; + + data.Add(d); + } + } catch (Exception) { } + + return data; + } + } +} diff --git a/src/Package/Impl/Telemetry/Definitions/IRtvsTelemetry.cs b/src/Package/Impl/Telemetry/Definitions/IRtvsTelemetry.cs new file mode 100644 index 000000000..816668b44 --- /dev/null +++ b/src/Package/Impl/Telemetry/Definitions/IRtvsTelemetry.cs @@ -0,0 +1,13 @@ +using System; +using Microsoft.VisualStudio.Shell.Interop; + +namespace Microsoft.VisualStudio.R.Package.Telemetry.Definitions { + /// + /// Represents telemetry operations in RTVS + /// + internal interface IRtvsTelemetry : IDisposable { + void ReportConfiguration(); + void ReportSettings(); + void ReportWindowLayout(IVsUIShell shell); + } +} diff --git a/src/Package/Impl/Telemetry/Definitions/ITelemetryLog.cs b/src/Package/Impl/Telemetry/Definitions/ITelemetryLog.cs new file mode 100644 index 000000000..06d24fa36 --- /dev/null +++ b/src/Package/Impl/Telemetry/Definitions/ITelemetryLog.cs @@ -0,0 +1,17 @@ + +namespace Microsoft.VisualStudio.R.Package.Telemetry { + /// + /// Represent persistent telemetry log + /// + public interface ITelemetryLog { + /// + /// Resets current session and clear telemetry log. + /// + void Reset(); + + /// + /// Returns current telemetry log as a string. + /// + string SessionLog { get; } + } +} diff --git a/src/Package/Impl/Telemetry/RtvsTelemetry.cs b/src/Package/Impl/Telemetry/RtvsTelemetry.cs new file mode 100644 index 000000000..0f49d67a5 --- /dev/null +++ b/src/Package/Impl/Telemetry/RtvsTelemetry.cs @@ -0,0 +1,159 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using Microsoft.Common.Core.Telemetry; +using Microsoft.R.Actions.Utility; +using Microsoft.R.Editor.Settings; +using Microsoft.R.Support.Settings; +using Microsoft.VisualStudio.R.Package.Telemetry.Data; +using Microsoft.VisualStudio.R.Package.Telemetry.Definitions; +using Microsoft.VisualStudio.R.Package.Telemetry.Windows; +using Microsoft.VisualStudio.Shell.Interop; + +namespace Microsoft.VisualStudio.R.Package.Telemetry { + /// + /// Represents telemetry operations in RTVS + /// + internal sealed class RtvsTelemetry : IRtvsTelemetry { + private ToolWindowTracker _toolWindowTracker = new ToolWindowTracker(); + private ITelemetryService _telemetryService; + + public static IRtvsTelemetry Current { get; set; } + + internal class ConfigurationEvents { + public const string RtvsVersion = "RTVS Version"; + public const string RInstallPath = "R Install Path"; + public const string REngine = "R Engine"; + public const string RROEngine = "RRO Engine"; + public const string MROEngine = "MRO Engine"; + public const string RBasePackages = "R Base Package"; + public const string RUserPackages = "R User Package"; + } + + internal class SettingEvents { + public const string Settings = "Settings"; + } + + internal class WindowEvents { + public const string ToolWindow = "Tool Window"; + } + + public static void Initialize(ITelemetryService service = null) { + if (Current == null) { + Current = new RtvsTelemetry(service); + } + } + + public RtvsTelemetry(ITelemetryService service = null) { + _telemetryService = service ?? VsTelemetryService.Current; + } + + public void ReportConfiguration() { + if (_telemetryService.IsEnabled) { + try { + Assembly thisAssembly = Assembly.GetExecutingAssembly(); + _telemetryService.ReportEvent(TelemetryArea.Configuration, ConfigurationEvents.RtvsVersion, thisAssembly.GetName().Version.ToString()); + + string rInstallPath = RInstallation.GetRInstallPath(RToolsSettings.Current.RBasePath); + _telemetryService.ReportEvent(TelemetryArea.Configuration, ConfigurationEvents.RInstallPath, rInstallPath); + + var rEngines = GetRSubfolders("R"); + foreach (var s in rEngines) { + _telemetryService.ReportEvent(TelemetryArea.Configuration, ConfigurationEvents.REngine, s); + } + + var rroEngines = GetRSubfolders("RRO"); + foreach (var s in rroEngines) { + _telemetryService.ReportEvent(TelemetryArea.Configuration, ConfigurationEvents.RROEngine, s); + } + + var mroEngines = GetRSubfolders("MRO"); + foreach (var s in mroEngines) { + _telemetryService.ReportEvent(TelemetryArea.Configuration, ConfigurationEvents.MROEngine, s); + } + + var hashes = RPackageData.GetInstalledPackageHashes(RPackageType.Base); + foreach (var s in hashes) { + _telemetryService.ReportEvent(TelemetryArea.Configuration, ConfigurationEvents.RBasePackages, s); + } + + hashes = RPackageData.GetInstalledPackageHashes(RPackageType.User); + foreach (var s in hashes) { + _telemetryService.ReportEvent(TelemetryArea.Configuration, ConfigurationEvents.RUserPackages, s); + } + } catch (Exception ex) { + Trace.Fail("Telemetry exception: " + ex.Message); + } + } + } + + public void ReportSettings() { + if (_telemetryService.IsEnabled) { + try { + _telemetryService.ReportEvent(TelemetryArea.Configuration, SettingEvents.Settings, + new { + Cran = RToolsSettings.Current.CranMirror, + LoadRData = RToolsSettings.Current.LoadRDataOnProjectLoad, + SaveRData = RToolsSettings.Current.SaveRDataOnProjectUnload, + RCommandLineArguments = RToolsSettings.Current.RCommandLineArguments, + MultilineHistorySelection = RToolsSettings.Current.MultilineHistorySelection, + AlwaysSaveHistory = RToolsSettings.Current.AlwaysSaveHistory, + AutoFormat = REditorSettings.AutoFormat, + CommitOnEnter = REditorSettings.CommitOnEnter, + CommitOnSpace = REditorSettings.CommitOnSpace, + FormatOnPaste = REditorSettings.FormatOnPaste, + SendToReplOnCtrlEnter = REditorSettings.SendToReplOnCtrlEnter, + ShowCompletionOnFirstChar = REditorSettings.ShowCompletionOnFirstChar, + SignatureHelpEnabled = REditorSettings.SignatureHelpEnabled, + CompletionEnabled = REditorSettings.CompletionEnabled, + SyntaxCheckInRepl = REditorSettings.SyntaxCheckInRepl, + PartialArgumentNameMatch = REditorSettings.PartialArgumentNameMatch, + }); + } catch (Exception ex) { + Trace.Fail("Telemetry exception: " + ex.Message); + } + } + } + + public void ReportWindowLayout(IVsUIShell shell) { + if (_telemetryService.IsEnabled) { + try { + var windows = ToolWindowData.GetToolWindowData(shell); + foreach (var w in windows) { + _telemetryService.ReportEvent(TelemetryArea.Configuration, WindowEvents.ToolWindow, + new { Caption = w.Caption, Left = w.X, Top = w.Y, Width = w.Width, Height = w.Height }); + } + } catch (Exception ex) { + Trace.Fail("Telemetry exception: " + ex.Message); + } + } + } + + /// + /// Retrieves all subfolders under R, RRO or MRO + /// + /// + /// + private static IEnumerable GetRSubfolders(string directory) { + string root = Path.GetPathRoot(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)); + string baseRFolder = Path.Combine(root + @"Program Files\", directory); + try { + return FolderUtility.GetSubfolderRelativePaths(baseRFolder); + } catch (IOException) { + // Don't do anything if there is no RRO installed + } + return Enumerable.Empty(); + } + + public void Dispose() { + _toolWindowTracker?.Dispose(); + _toolWindowTracker = null; + + var disp = _telemetryService as IDisposable; + disp?.Dispose(); + } + } +} diff --git a/src/Package/Impl/Telemetry/StringTelemetryRecorder.cs b/src/Package/Impl/Telemetry/StringTelemetryRecorder.cs new file mode 100644 index 000000000..10b28756e --- /dev/null +++ b/src/Package/Impl/Telemetry/StringTelemetryRecorder.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Text; +using Microsoft.Common.Core; +using Microsoft.Common.Core.Telemetry; +using Microsoft.VisualStudio.Telemetry; + +namespace Microsoft.VisualStudio.R.Package.Telemetry { + + /// + /// Records telemetry events into a string. The resulting log can be used + /// for testing or for submitting telemetry as a file rather than via + /// VS telemetry Web service. + /// + public sealed class StringTelemetryRecorder : ITelemetryRecorder, ITelemetryLog, IDisposable { + private StringBuilder _stringBuilder = new StringBuilder(); + + #region ITelemetryRecorder + public bool IsEnabled { + get { return true; } + } + + public bool CanCollectPrivateInformation { + get { return true; } + } + + public void RecordEvent(string eventName, object parameters = null) { + _stringBuilder.AppendLine(eventName); + if (parameters != null) { + if (parameters is string) { + WriteProperty("Value", parameters.ToString()); + } else { + WriteDictionary(DictionaryExtension.FromAnonymousObject(parameters)); + } + } + } + + public void RecordActivity(object telemetryActivity) { + TelemetryActivity activity = telemetryActivity as TelemetryActivity; + _stringBuilder.AppendLine(activity.Name); + if (activity.HasProperties) { + WriteDictionary(activity.Properties); + } + } + #endregion + + #region ITelemetryLog + public void Reset() { + _stringBuilder.Clear(); + } + + public string SessionLog { + get { return _stringBuilder.ToString(); } + } + #endregion + + public string FileName { get; private set; } + + public void Dispose() { + try { + FileName = Path.Combine(Path.GetTempPath(), string.Format(CultureInfo.InvariantCulture, "RTVS_Telemetry_{0}.log", DateTime.Now.ToFileTimeUtc())); + using (var sw = new StreamWriter(FileName)) { + sw.Write(_stringBuilder.ToString()); + } + } catch (IOException) { } + } + + private void WriteDictionary(IDictionary dict) { + foreach (KeyValuePair kvp in dict) { + WriteProperty(kvp.Key, kvp.Value); + } + } + + private void WriteProperty(string name, object value) { + _stringBuilder.Append('\t'); + _stringBuilder.Append(name); + _stringBuilder.Append(" : "); + _stringBuilder.AppendLine(value.ToString()); + } + } +} diff --git a/src/Package/Impl/Telemetry/TelemetryEvents.cs b/src/Package/Impl/Telemetry/TelemetryEvents.cs deleted file mode 100644 index 422f9aef4..000000000 --- a/src/Package/Impl/Telemetry/TelemetryEvents.cs +++ /dev/null @@ -1,13 +0,0 @@ - -namespace Microsoft.VisualStudio.R.Package.Telemetry { - /// - /// Telemetry event names - /// - public static class TelemetryEvents { - public static readonly string Command = "Command"; - public static readonly string DirectoryChange = "DirectoryChange"; - public static readonly string Open = "Open"; - public static readonly string Build = "Build"; - public static readonly string Debug = "Debug"; - } -} diff --git a/src/Package/Impl/Telemetry/TelemetryProperties.cs b/src/Package/Impl/Telemetry/TelemetryProperties.cs deleted file mode 100644 index 69ccaffdf..000000000 --- a/src/Package/Impl/Telemetry/TelemetryProperties.cs +++ /dev/null @@ -1,10 +0,0 @@ - -namespace Microsoft.VisualStudio.R.Package.Telemetry { - /// - /// Telemetry property names - /// - public static class TelemetryProperties { - // Sorted alphabetically - public static readonly string CommandId = "CommandId"; - } -} diff --git a/src/Package/Impl/Telemetry/VsTelemetryRecorder.cs b/src/Package/Impl/Telemetry/VsTelemetryRecorder.cs index 4c46d1eb0..3e94830db 100644 --- a/src/Package/Impl/Telemetry/VsTelemetryRecorder.cs +++ b/src/Package/Impl/Telemetry/VsTelemetryRecorder.cs @@ -31,10 +31,17 @@ private VsTelemetryRecorder() { /// public void RecordEvent(string eventName, object parameters = null) { if (this.IsEnabled) { - IDictionary dict = DictionaryExtension.FromAnonymousObject(parameters); TelemetryEvent telemetryEvent = new TelemetryEvent(eventName); - foreach (KeyValuePair kvp in dict) { - telemetryEvent.Properties[kvp.Key] = kvp.Value; + if (parameters != null) { + var stringParameter = parameters as string; + if (stringParameter != null) { + telemetryEvent.Properties["Value"] = stringParameter; + } else { + IDictionary dict = DictionaryExtension.FromAnonymousObject(parameters); + foreach (KeyValuePair kvp in dict) { + telemetryEvent.Properties[kvp.Key] = kvp.Value; + } + } } _session.PostEvent(telemetryEvent); } @@ -61,5 +68,7 @@ public void RecordActivity(object telemetryActivity) { } } #endregion + + public void Dispose() { } } } diff --git a/src/Package/Impl/Telemetry/VsTelemetryService.cs b/src/Package/Impl/Telemetry/VsTelemetryService.cs index f8fa26956..9ea04e572 100644 --- a/src/Package/Impl/Telemetry/VsTelemetryService.cs +++ b/src/Package/Impl/Telemetry/VsTelemetryService.cs @@ -1,24 +1,33 @@ using System; -using System.ComponentModel.Composition; using Microsoft.Common.Core; using Microsoft.Common.Core.Diagnostics; using Microsoft.Common.Core.Telemetry; namespace Microsoft.VisualStudio.R.Package.Telemetry { - [Export(typeof(ITelemetryService))] - internal sealed class VsTelemetryService : TelemetryServiceBase { + internal sealed class VsTelemetryService : TelemetryServiceBase, ITelemetryLog { public static readonly string EventNamePrefixString = "VS/RTools/"; public static readonly string PropertyNamePrefixString = "VS.RTools."; private static Lazy _instance = Lazy.Create(() => new VsTelemetryService()); public VsTelemetryService() - : base(VsTelemetryService.EventNamePrefixString, VsTelemetryService.PropertyNamePrefixString, VsTelemetryRecorder.Current) { + : base(VsTelemetryService.EventNamePrefixString, VsTelemetryService.PropertyNamePrefixString, new StringTelemetryRecorder()) { +// : base(VsTelemetryService.EventNamePrefixString, VsTelemetryService.PropertyNamePrefixString, VsTelemetryRecorder.Current) { } public static TelemetryServiceBase Current => _instance.Value; + #region ITelemetryLog + public string SessionLog { + get { return (base.TelemetryRecorder as ITelemetryLog)?.SessionLog; } + } + + public void Reset() { + (base.TelemetryRecorder as ITelemetryLog)?.Reset(); + } + #endregion + /// /// Start a telemetry activity, dispose of the return value when the activity is complete /// diff --git a/src/Package/Impl/Telemetry/Windows/ToolWindowTracker.cs b/src/Package/Impl/Telemetry/Windows/ToolWindowTracker.cs new file mode 100644 index 000000000..955abd82d --- /dev/null +++ b/src/Package/Impl/Telemetry/Windows/ToolWindowTracker.cs @@ -0,0 +1,61 @@ +using System; +using System.Timers; +using Microsoft.VisualStudio.R.Package.Shell; +using Microsoft.VisualStudio.Shell.Interop; + +namespace Microsoft.VisualStudio.R.Package.Telemetry.Windows { + internal sealed class ToolWindowTracker: IVsDebuggerEvents, IDisposable { + private Timer _timer = new Timer(); + private IVsDebugger _debugger; + private uint _debuggerEventCookie; + private uint _reportCount; + + public ToolWindowTracker() { + _debugger = VsAppShell.Current.GetGlobalService(typeof(IVsDebugger)); + if (_debugger != null) { + _debugger.AdviseDebuggerEvents(this, out _debuggerEventCookie); + + _timer.Interval = new TimeSpan(0, 0, 10).TotalMilliseconds; + _timer.AutoReset = true; + _timer.Elapsed += OnElapsed; + _timer.Start(); + } + } + + private void OnElapsed(object sender, ElapsedEventArgs e) { + VsAppShell.Current.DispatchOnUIThread(() => { + ReportWindowLayout(); + }); + } + + + public int OnModeChange(DBGMODE dbgmodeNew) { + if(dbgmodeNew == DBGMODE.DBGMODE_Run) { + ReportWindowLayout(); + } + return VSConstants.S_OK; + } + + private void ReportWindowLayout() { + if (_reportCount < 4) { + RtvsTelemetry.Current.ReportWindowLayout(VsAppShell.Current.GetGlobalService(typeof(SVsUIShell))); + _reportCount++; + if (_reportCount > 4) { + _timer?.Stop(); + } + } + } + + public void Dispose() { + _timer?.Stop(); + _timer?.Dispose(); + _timer = null; + + if(_debuggerEventCookie != 0 && _debugger != null) { + _debugger.UnadviseDebuggerEvents(_debuggerEventCookie); + _debuggerEventCookie = 0; + _debugger = null; + } + } + } +} diff --git a/src/Package/Impl/Utilities/ActiveWpfTextViewTracker.cs b/src/Package/Impl/Utilities/ActiveWpfTextViewTracker.cs index cdaa8d539..17664e2a0 100644 --- a/src/Package/Impl/Utilities/ActiveWpfTextViewTracker.cs +++ b/src/Package/Impl/Utilities/ActiveWpfTextViewTracker.cs @@ -12,10 +12,12 @@ namespace Microsoft.VisualStudio.R.Package.Utilities { internal class ActiveWpfTextViewTracker : IActiveWpfTextViewTracker, IVsWindowFrameEvents { private readonly Dictionary _textViews; private readonly IVsEditorAdaptersFactoryService _editorAdaptersFactory; + private readonly IContentTypeRegistryService _registryService; [ImportingConstructor] - public ActiveWpfTextViewTracker(IVsEditorAdaptersFactoryService editorAdaptersFactory) { + public ActiveWpfTextViewTracker(IVsEditorAdaptersFactoryService editorAdaptersFactory, IContentTypeRegistryService registryService) { _editorAdaptersFactory = editorAdaptersFactory; + _registryService = registryService; _textViews = new Dictionary(); } @@ -24,6 +26,15 @@ public IWpfTextView GetLastActiveTextView(IContentType contentType) { return _textViews.TryGetValue(contentType, out value) ? value : null; } + public IWpfTextView GetLastActiveTextView(string contentTypeName) { + IContentType contentType = _registryService.GetContentType(contentTypeName); + if (contentType != null) { + IWpfTextView value; + return _textViews.TryGetValue(contentType, out value) ? value : null; + } + return null; + } + public void OnFrameCreated(IVsWindowFrame frame) { } diff --git a/src/Package/Impl/Utilities/IActiveWpfTextViewTracker.cs b/src/Package/Impl/Utilities/IActiveWpfTextViewTracker.cs index 46a8c081c..230278dc3 100644 --- a/src/Package/Impl/Utilities/IActiveWpfTextViewTracker.cs +++ b/src/Package/Impl/Utilities/IActiveWpfTextViewTracker.cs @@ -4,5 +4,6 @@ namespace Microsoft.VisualStudio.R.Package.Utilities { public interface IActiveWpfTextViewTracker { IWpfTextView GetLastActiveTextView(IContentType contentType); + IWpfTextView GetLastActiveTextView(string contentType); } } \ No newline at end of file diff --git a/src/Package/Impl/Utilities/ToolWindowUtilities.cs b/src/Package/Impl/Utilities/ToolWindowUtilities.cs index de76692df..574b6f1d4 100644 --- a/src/Package/Impl/Utilities/ToolWindowUtilities.cs +++ b/src/Package/Impl/Utilities/ToolWindowUtilities.cs @@ -16,12 +16,14 @@ public static T ShowWindowPane(int id, bool focus) where T : ToolWindowPane { if (window != null) { var frame = window.Frame as IVsWindowFrame; if (frame != null) { - ErrorHandler.ThrowOnFailure(frame.Show()); - } - if (focus) { - var content = window.Content as System.Windows.UIElement; - if (content != null) { - content.Focus(); + if (focus) { + ErrorHandler.ThrowOnFailure(frame.Show()); + var content = window.Content as System.Windows.UIElement; + if (content != null) { + content.Focus(); + } + } else { + ErrorHandler.ThrowOnFailure(frame.ShowNoActivate()); } } } diff --git a/src/Package/Impl/Utilities/ViewUtilities.cs b/src/Package/Impl/Utilities/ViewUtilities.cs index c2e18f318..7efa4d7d4 100644 --- a/src/Package/Impl/Utilities/ViewUtilities.cs +++ b/src/Package/Impl/Utilities/ViewUtilities.cs @@ -1,12 +1,7 @@ using System; using System.Runtime.InteropServices; -using Microsoft.Languages.Core.Text; -using Microsoft.Languages.Editor.Settings; -using Microsoft.Languages.Editor.Shell; -using Microsoft.R.Editor.Commands; using Microsoft.VisualStudio.Editor; using Microsoft.VisualStudio.OLE.Interop; -using Microsoft.VisualStudio.R.Package.Commands; using Microsoft.VisualStudio.R.Package.Shell; using Microsoft.VisualStudio.R.Package.Workspace; using Microsoft.VisualStudio.R.Packages.R; @@ -14,7 +9,6 @@ using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.Text.Editor; using Microsoft.VisualStudio.TextManager.Interop; -using Microsoft.VisualStudio.Utilities; namespace Microsoft.VisualStudio.R.Package.Utilities { public static class ViewUtilities { @@ -40,21 +34,6 @@ public static IVsWindowFrame GetActiveFrame() { return null; } - public static ITextView ActiveTextView { - get { - IVsTextView vsTextView = null; - ITextView activeTextView = null; - - IVsTextManager2 textManager = VsAppShell.Current.GetGlobalService(typeof(SVsTextManager)); - - if (ErrorHandler.Succeeded(textManager.GetActiveView2(0, null, (uint)(_VIEWFRAMETYPE.vftCodeWindow), out vsTextView))) { - activeTextView = AdaptersFactoryService.GetWpfTextView(vsTextView); - } - - return activeTextView; - } - } - public static T GetService(this ITextView textView, Type type = null) where T : class { IVsTextView vsTextView = AdaptersFactoryService.GetViewAdapter(textView); @@ -99,16 +78,5 @@ public static void SaveFile(this ITextView textView) { string filePath = VsFileInfo.GetFileName(textView); rdt.SaveFileIfDirty(filePath); } - - public static void SourceActiveFile() { - var activeView = ViewUtilities.ActiveTextView; - if (activeView != null) { - var controller = RMainController.FromTextView(activeView); - if (controller != null) { - object o = null; - controller.Invoke(RGuidList.RCmdSetGuid, RPackageCommandId.icmdSourceRScript, null, ref o); - } - } - } } } diff --git a/src/Package/Impl/VSPackage.resx b/src/Package/Impl/VSPackage.resx index b67f4c481..159900bf7 100644 --- a/src/Package/Impl/VSPackage.resx +++ b/src/Package/Impl/VSPackage.resx @@ -178,16 +178,28 @@ Options for checking language syntax, automatic formatting. - - Data Science (R) - - - Customizes the environment to improve the visibility of commands and options that are most relevant to doing data science and machine learning work using R. - R Tools for Visual Studio Provides project system, R Interactive window, plotting, and more for the R programming language. + + Data Science (R) + + + Data Science (R) - RStudio shortcuts + + + Data Science (R) with RStudio shortcuts + + + Customizes the environment to improve the visibility of commands and options that are most relevant to doing data science and machine learning work using R. + + + Provides RStudio-compatible keyboard shortcuts for many functions in R Tools for Visual Studio. + + + Customizes the environment to improve the visibility of commands and options that are most relevant to doing data science and machine learning work using R. Includes RStudio-compatible keyboard shortcuts. + \ No newline at end of file diff --git a/src/Package/Impl/Workspace/VsFile.cs b/src/Package/Impl/Workspace/VsFile.cs deleted file mode 100644 index cec21e630..000000000 --- a/src/Package/Impl/Workspace/VsFile.cs +++ /dev/null @@ -1,25 +0,0 @@ -using System; -using System.IO; -using Microsoft.Languages.Editor.Workspace; - -namespace Microsoft.VisualStudio.R.Package.Workspace { - /// - /// Visual Studio implementation of the file abstraction - /// - internal class VsFile : IFile { - public VsFile(string absolutePath) { - if (absolutePath == null) - throw new ArgumentNullException("absolutePath"); - - Name = Path.GetFileName(absolutePath); - Folder = new VsFolder(Path.GetDirectoryName(absolutePath)); - } - - #region IFile - - public string Name { get; private set; } - public IFolder Folder { get; private set; } - - #endregion - } -} diff --git a/src/Package/Impl/Workspace/VsFolder.cs b/src/Package/Impl/Workspace/VsFolder.cs deleted file mode 100644 index 6c010883e..000000000 --- a/src/Package/Impl/Workspace/VsFolder.cs +++ /dev/null @@ -1,63 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.IO; -using Microsoft.Languages.Editor.Workspace; - -namespace Microsoft.VisualStudio.R.Package.Workspace { - /// - /// Visual Studio implementation of the folder abstraction - /// - internal class VsFolder : IFolder { - public VsFolder(string folder) { - if (folder == null) - throw new ArgumentNullException("folder"); - - Name = folder; - } - - #region IWebFolder - public string Name { get; private set; } - - public IFolder Parent { - get { - string parent = Path.GetDirectoryName(Name); - if (String.IsNullOrEmpty(parent)) - return null; - - return new VsFolder(parent); - } - } - - public ReadOnlyCollection Folders { - get { - var list = new List(); - - if (Directory.Exists(Name)) { - var folders = Directory.GetDirectories(Name); - foreach (var folder in folders) { - list.Add(new VsFolder(folder)); - } - } - - return new ReadOnlyCollection(list); - } - } - - public ReadOnlyCollection Files { - get { - var list = new List(); - - if (Directory.Exists(Name)) { - var files = Directory.GetFiles(Name); - foreach (var file in files) { - list.Add(new VsFile(file)); - } - } - - return new ReadOnlyCollection(list); - } - } - #endregion - } -} diff --git a/src/Package/Impl/Wpf/Commands.cs b/src/Package/Impl/Wpf/Commands.cs index 743b7c2f5..38d6528c5 100644 --- a/src/Package/Impl/Wpf/Commands.cs +++ b/src/Package/Impl/Wpf/Commands.cs @@ -19,6 +19,7 @@ using System.Windows.Data; using System.Windows.Input; using System.Windows.Interop; +using Microsoft.VisualStudio.R.Package.Shell; namespace Microsoft.VisualStudioTools.Wpf { /// @@ -116,7 +117,7 @@ private static void BrowseOpenFileExecute(Window window, ExecutedRoutedEventArgs var filter = (e.Parameter as string) ?? "All Files (*.*)|*.*"; var path = tb.GetValue(TextBox.TextProperty) as string; - path = Dialogs.BrowseForFileOpen( + path = VsAppShell.Current.BrowseForFileOpen( window == null ? IntPtr.Zero : new WindowInteropHelper(window).Handle, filter, path @@ -135,7 +136,7 @@ private static void BrowseSaveFileExecute(Window window, ExecutedRoutedEventArgs var filter = (e.Parameter as string) ?? "All Files (*.*)|*.*"; var path = tb.GetValue(TextBox.TextProperty) as string; - path = Dialogs.BrowseForFileSave( + path = VsAppShell.Current.BrowseForFileSave( window == null ? IntPtr.Zero : new WindowInteropHelper(window).Handle, filter, path diff --git a/src/Package/Impl/Wpf/Dialogs.cs b/src/Package/Impl/Wpf/Dialogs.cs index 357c4eadf..b3c81c1a8 100644 --- a/src/Package/Impl/Wpf/Dialogs.cs +++ b/src/Package/Impl/Wpf/Dialogs.cs @@ -26,132 +26,6 @@ private static object GetService(Type serviceType) { return Package.GetGlobalService(serviceType); } - public static string BrowseForFileOpen( - IntPtr owner, - string filter, - string initialPath = null, - string title = null - ) { - if (string.IsNullOrEmpty(initialPath)) { - initialPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal) + Path.DirectorySeparatorChar; - } - - IVsUIShell uiShell = GetService(typeof(SVsUIShell)) as IVsUIShell; - if (null == uiShell) { - using (var ofd = new System.Windows.Forms.OpenFileDialog()) { - ofd.AutoUpgradeEnabled = true; - ofd.Filter = filter; - ofd.FileName = Path.GetFileName(initialPath); - ofd.InitialDirectory = Path.GetDirectoryName(initialPath); - if (!string.IsNullOrEmpty(title)) { - ofd.Title = title; - } - DialogResult result; - if (owner == IntPtr.Zero) { - result = ofd.ShowDialog(); - } else { - result = ofd.ShowDialog(NativeWindow.FromHandle(owner)); - } - if (result == DialogResult.OK) { - return ofd.FileName; - } else { - return null; - } - } - } - - if (owner == IntPtr.Zero) { - ErrorHandler.ThrowOnFailure(uiShell.GetDialogOwnerHwnd(out owner)); - } - - VSOPENFILENAMEW[] openInfo = new VSOPENFILENAMEW[1]; - openInfo[0].lStructSize = (uint)Marshal.SizeOf(typeof(VSOPENFILENAMEW)); - openInfo[0].pwzFilter = filter.Replace('|', '\0') + "\0"; - openInfo[0].hwndOwner = owner; - openInfo[0].pwzDlgTitle = title; - openInfo[0].nMaxFileName = 260; - var pFileName = Marshal.AllocCoTaskMem(520); - openInfo[0].pwzFileName = pFileName; - openInfo[0].pwzInitialDir = Path.GetDirectoryName(initialPath); - var nameArray = (Path.GetFileName(initialPath) + "\0").ToCharArray(); - Marshal.Copy(nameArray, 0, pFileName, nameArray.Length); - try { - int hr = uiShell.GetOpenFileNameViaDlg(openInfo); - if (hr == VSConstants.OLE_E_PROMPTSAVECANCELLED) { - return null; - } - ErrorHandler.ThrowOnFailure(hr); - return Marshal.PtrToStringAuto(openInfo[0].pwzFileName); - } finally { - if (pFileName != IntPtr.Zero) { - Marshal.FreeCoTaskMem(pFileName); - } - } - } - - public static string BrowseForFileSave( - IntPtr owner, - string filter, - string initialPath = null, - string title = null - ) { - if (string.IsNullOrEmpty(initialPath)) { - initialPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal) + Path.DirectorySeparatorChar; - } - - IVsUIShell uiShell = GetService(typeof(SVsUIShell)) as IVsUIShell; - if (null == uiShell) { - using (var sfd = new System.Windows.Forms.SaveFileDialog()) { - sfd.AutoUpgradeEnabled = true; - sfd.Filter = filter; - sfd.FileName = Path.GetFileName(initialPath); - sfd.InitialDirectory = Path.GetDirectoryName(initialPath); - if (!string.IsNullOrEmpty(title)) { - sfd.Title = title; - } - DialogResult result; - if (owner == IntPtr.Zero) { - result = sfd.ShowDialog(); - } else { - result = sfd.ShowDialog(NativeWindow.FromHandle(owner)); - } - if (result == DialogResult.OK) { - return sfd.FileName; - } else { - return null; - } - } - } - - if (owner == IntPtr.Zero) { - ErrorHandler.ThrowOnFailure(uiShell.GetDialogOwnerHwnd(out owner)); - } - - VSSAVEFILENAMEW[] saveInfo = new VSSAVEFILENAMEW[1]; - saveInfo[0].lStructSize = (uint)Marshal.SizeOf(typeof(VSSAVEFILENAMEW)); - saveInfo[0].pwzFilter = filter.Replace('|', '\0') + "\0"; - saveInfo[0].hwndOwner = owner; - saveInfo[0].pwzDlgTitle = title; - saveInfo[0].nMaxFileName = 260; - var pFileName = Marshal.AllocCoTaskMem(520); - saveInfo[0].pwzFileName = pFileName; - saveInfo[0].pwzInitialDir = Path.GetDirectoryName(initialPath); - var nameArray = (Path.GetFileName(initialPath) + "\0").ToCharArray(); - Marshal.Copy(nameArray, 0, pFileName, nameArray.Length); - try { - int hr = uiShell.GetSaveFileNameViaDlg(saveInfo); - if (hr == VSConstants.OLE_E_PROMPTSAVECANCELLED) { - return null; - } - ErrorHandler.ThrowOnFailure(hr); - return Marshal.PtrToStringAuto(saveInfo[0].pwzFileName); - } finally { - if (pFileName != IntPtr.Zero) { - Marshal.FreeCoTaskMem(pFileName); - } - } - } - public static string BrowseForDirectory( IntPtr owner, string initialDirectory = null, diff --git a/src/Package/Impl/source.extension.vsixmanifest b/src/Package/Impl/source.extension.vsixmanifest index a117a92ed..a3c7e513b 100644 --- a/src/Package/Impl/source.extension.vsixmanifest +++ b/src/Package/Impl/source.extension.vsixmanifest @@ -1,7 +1,7 @@ - + R Tools for Visual Studio R Tools for Visual Studio diff --git a/src/Package/Test/Commands/CommandFactoryTest.cs b/src/Package/Test/Commands/CommandFactoryTest.cs deleted file mode 100644 index f18b93e3c..000000000 --- a/src/Package/Test/Commands/CommandFactoryTest.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using FluentAssertions; -using Microsoft.Languages.Editor.Composition; -using Microsoft.Languages.Editor.Controller; -using Microsoft.Languages.Editor.Shell; -using Microsoft.R.Editor.ContentType; - -namespace Microsoft.VisualStudio.R.Package.Test.Commands { - [ExcludeFromCodeCoverage] - public class CommandFactoryTest - { - //[Test] - //[Category.R.Package] - public void Package_CommandFactoryImportTest() - { - var importComposer = new ContentTypeImportComposer(EditorShell.Current.CompositionService); - ICollection factories = importComposer.GetAll(RContentTypeDefinition.ContentType); - - factories.Should().HaveCount(2); - } - } -} diff --git a/src/Package/Test/Commands/ReplCommandTest.cs b/src/Package/Test/Commands/ReplCommandTest.cs deleted file mode 100644 index 7840f6103..000000000 --- a/src/Package/Test/Commands/ReplCommandTest.cs +++ /dev/null @@ -1,8 +0,0 @@ -using System.Diagnostics.CodeAnalysis; - -namespace Microsoft.VisualStudio.R.Package.Test.Commands { - [ExcludeFromCodeCoverage] - public class ReplCommandTest - { - } -} diff --git a/src/Package/Test/DataInspect/EvaluationWrapperTest.cs b/src/Package/Test/DataInspect/EvaluationWrapperTest.cs index a22083084..2bbaba41c 100644 --- a/src/Package/Test/DataInspect/EvaluationWrapperTest.cs +++ b/src/Package/Test/DataInspect/EvaluationWrapperTest.cs @@ -1,33 +1,16 @@ using System.Diagnostics.CodeAnalysis; -using System.Threading; +using System.Linq; using System.Threading.Tasks; using FluentAssertions; -using Microsoft.Common.Core.Test.Script; -using Microsoft.Languages.Editor.Shell; -using Microsoft.UnitTests.Core.Threading; using Microsoft.UnitTests.Core.XUnit; -using Microsoft.VisualStudio.R.Package.Shell; +using Microsoft.VisualStudio.R.Package.DataInspect; +using Microsoft.VisualStudio.R.Package.DataInspect.DataSource; using Xunit; namespace Microsoft.VisualStudio.R.Package.Test.DataInspect { [ExcludeFromCodeCoverage] [Collection(CollectionNames.NonParallel)] // required for tests using R Host public class EvaluationWrapperTest { - - public EvaluationWrapperTest() { - } - - [Test] - [Category.Variable.Explorer] - public async Task GlobalEnvironmentTest() { - using (var hostScript = new VariableRHostScript()) { - await hostScript.EvaluateAsync("ls()"); // run anything - - var target = hostScript.GlobalEnvironment; - target.Name.Should().BeEquivalentTo("Global Environment"); - } - } - // TODO: RStudio difference // value.integer.1 RS 1L RTVS just 1 // value.numeric.big RS 98765432109876543210 RTVS 9.88e+19 @@ -68,6 +51,10 @@ public async Task GlobalEnvironmentTest() { { "list.length1 <- list(c(1, 2, 3))", new VariableExpectation() { Name = "list.length1", Value = "List of 1", TypeName = "list", Class = "list", HasChildren = true, CanShowDetail = false } }, }; + object[,] activeBindingTestData = new object[,] { + { "makeActiveBinding('z.activebinding1', function() 123, .GlobalEnv);", new VariableExpectation() { Name = "z.activebinding1", Value = "", TypeName = "", Class = "", HasChildren = false, CanShowDetail = false } }, + }; + [Test] [Category.Variable.Explorer] public Task ValuesTest() { @@ -98,6 +85,351 @@ public Task ListTest() { return RunTest(listTestData); } + [Test] + [Category.Variable.Explorer] + public Task ActiveBindingTest() { + return RunTest(activeBindingTestData); + } + + [Test] + [Category.Variable.Explorer] + public async Task TruncateGrandChildrenTest() { + using (var hostScript = new VariableRHostScript()) { + await hostScript.EvaluateAsync("x.truncate.children<-1:100"); + var children = await hostScript.GlobalEnvrionment.GetChildrenAsync(); + var child = children.First(c => c.Name == "x.truncate.children"); + + var grandChildren = await child.GetChildrenAsync(); + + grandChildren.Count.ShouldBeEquivalentTo(21); // truncate 20 + ellipsis + grandChildren[20].Value.ShouldBeEquivalentTo(Resources.VariableExplorer_Truncated); + } + } + + [Test] + [Category.Variable.Explorer] + public async Task Matrix10x100Test() { + var script = "matrix.10x100 <-matrix(1:1000, 10, 100)"; + var expectation = new VariableExpectation() { Name = "matrix.10x100", Value = "int [1:10, 1:100] 1 2 3 4 5 6 7 8 9 10 ...", TypeName = "integer", Class = "matrix", HasChildren = true, CanShowDetail = true }; + + using (var hostScript = new VariableRHostScript()) { + var evaluation = (EvaluationWrapper)await hostScript.EvaluateAndAssert( + script, + expectation, + VariableRHostScript.AssertEvaluationWrapper); + + Range rowRange = new Range(0, 2); + Range columnRange = new Range(1, 3); + var grid = await GridDataSource.GetGridDataAsync(evaluation.Expression, new GridRange(rowRange, columnRange)); + + grid.ColumnHeader.Range.ShouldBeEquivalentTo(columnRange); + grid.ColumnHeader[1].ShouldBeEquivalentTo("[,2]"); + grid.ColumnHeader[2].ShouldBeEquivalentTo("[,3]"); + grid.ColumnHeader[3].ShouldBeEquivalentTo("[,4]"); + + grid.RowHeader.Range.ShouldBeEquivalentTo(rowRange); + grid.RowHeader[0].ShouldBeEquivalentTo("[1,]"); + grid.RowHeader[1].ShouldBeEquivalentTo("[2,]"); + + grid.Grid.Range.ShouldBeEquivalentTo(new GridRange(rowRange, columnRange)); + grid.Grid[0, 1].ShouldBeEquivalentTo("11"); + grid.Grid[0, 2].ShouldBeEquivalentTo("21"); + grid.Grid[0, 3].ShouldBeEquivalentTo("31"); + grid.Grid[1, 1].ShouldBeEquivalentTo("12"); + grid.Grid[1, 2].ShouldBeEquivalentTo("22"); + grid.Grid[1, 3].ShouldBeEquivalentTo("32"); + } + } + + [Test] + [Category.Variable.Explorer] + public async Task MatrixNamedTest() { + var script = "matrix.named <- matrix(1:10, 2, 5, dimnames = list(r = c('r1', 'r2'), c = c('a', 'b', 'c', 'd', 'e')))"; + var expectation = new VariableExpectation() { Name = "matrix.named", Value = "int [1:2, 1:5] 1 2 3 4 5 6 7 8 9 10", TypeName = "integer", Class = "matrix", HasChildren = true, CanShowDetail = true }; + + using (var hostScript = new VariableRHostScript()) { + var evaluation = (EvaluationWrapper)await hostScript.EvaluateAndAssert( + script, + expectation, + VariableRHostScript.AssertEvaluationWrapper); + + Range rowRange = new Range(0, 2); + Range columnRange = new Range(2, 3); + var grid = await GridDataSource.GetGridDataAsync(evaluation.Expression, new GridRange(rowRange, columnRange)); + + grid.ColumnHeader.Range.ShouldBeEquivalentTo(columnRange); + grid.ColumnHeader[2].ShouldBeEquivalentTo("c"); + grid.ColumnHeader[3].ShouldBeEquivalentTo("d"); + grid.ColumnHeader[4].ShouldBeEquivalentTo("e"); + + grid.RowHeader.Range.ShouldBeEquivalentTo(rowRange); + grid.RowHeader[0].ShouldBeEquivalentTo("r1"); + grid.RowHeader[1].ShouldBeEquivalentTo("r2"); + + grid.Grid.Range.ShouldBeEquivalentTo(new GridRange(rowRange, columnRange)); + grid.Grid[0, 2].ShouldBeEquivalentTo("5"); + grid.Grid[0, 3].ShouldBeEquivalentTo("7"); + grid.Grid[0, 4].ShouldBeEquivalentTo("9"); + grid.Grid[1, 2].ShouldBeEquivalentTo("6"); + grid.Grid[1, 3].ShouldBeEquivalentTo("8"); + grid.Grid[1, 4].ShouldBeEquivalentTo("10"); + } + } + + [Test] + [Category.Variable.Explorer] + public async Task MatrixNATest() { + var script = "matrix.na.header <- matrix(c(1, 2, 3, 4, NA, NaN, 7, 8, 9, 10), 2, 5, dimnames = list(r = c('r1', NA), c = c('a', 'b', NA, 'd', NA)))"; + var expectation = new VariableExpectation() { Name = "matrix.na.header", Value = "num [1:2, 1:5] 1 2 3 4 NA NaN 7 8 9 10", TypeName = "double", Class = "matrix", HasChildren = true, CanShowDetail = true }; + + using (var hostScript = new VariableRHostScript()) { + var evaluation = (EvaluationWrapper)await hostScript.EvaluateAndAssert( + script, + expectation, + VariableRHostScript.AssertEvaluationWrapper); + + Range rowRange = new Range(0, 2); + Range columnRange = new Range(2, 3); + var grid = await GridDataSource.GetGridDataAsync(evaluation.Expression, new GridRange(rowRange, columnRange)); + + grid.ColumnHeader.Range.ShouldBeEquivalentTo(columnRange); + grid.ColumnHeader[2].ShouldBeEquivalentTo("NA"); + grid.ColumnHeader[3].ShouldBeEquivalentTo("d"); + grid.ColumnHeader[4].ShouldBeEquivalentTo("NA"); + + grid.RowHeader.Range.ShouldBeEquivalentTo(rowRange); + grid.RowHeader[0].ShouldBeEquivalentTo("r1"); + grid.RowHeader[1].ShouldBeEquivalentTo("NA"); + + grid.Grid.Range.ShouldBeEquivalentTo(new GridRange(rowRange, columnRange)); + grid.Grid[0, 2].ShouldBeEquivalentTo("NA"); + grid.Grid[0, 3].ShouldBeEquivalentTo("7"); + grid.Grid[0, 4].ShouldBeEquivalentTo("9"); + grid.Grid[1, 2].ShouldBeEquivalentTo("NaN"); + grid.Grid[1, 3].ShouldBeEquivalentTo("8"); + grid.Grid[1, 4].ShouldBeEquivalentTo("10"); + } + } + + [Test] + [Category.Variable.Explorer] + public async Task MatrixOneRowColumnTest() { + var script1 = "matrix.singlerow <- matrix(1:3, nrow =1);"; + var expectation1 = new VariableExpectation() { Name = "matrix.singlerow", Value = "int [1, 1:3] 1 2 3", TypeName = "integer", Class = "matrix", HasChildren = true, CanShowDetail = true }; + + var script2 = "matrix.singlecolumn <- matrix(1:3, ncol=1);"; + var expectation2 = new VariableExpectation() { Name = "matrix.singlecolumn", Value = "int [1:3, 1] 1 2 3", TypeName = "integer", Class = "matrix", HasChildren = true, CanShowDetail = true }; + + using (var hostScript = new VariableRHostScript()) { + var evaluation = (EvaluationWrapper)await hostScript.EvaluateAndAssert( + script1, + expectation1, + VariableRHostScript.AssertEvaluationWrapper); + + Range rowRange = new Range(0, 1); + Range columnRange = new Range(0, 3); + var grid = await GridDataSource.GetGridDataAsync(evaluation.Expression, new GridRange(rowRange, columnRange)); + + grid.ColumnHeader.Range.ShouldBeEquivalentTo(columnRange); + grid.ColumnHeader[0].ShouldBeEquivalentTo("[,1]"); + grid.ColumnHeader[1].ShouldBeEquivalentTo("[,2]"); + grid.ColumnHeader[2].ShouldBeEquivalentTo("[,3]"); + + grid.RowHeader.Range.ShouldBeEquivalentTo(rowRange); + grid.RowHeader[0].ShouldBeEquivalentTo("[1,]"); + + grid.Grid.Range.ShouldBeEquivalentTo(new GridRange(rowRange, columnRange)); + grid.Grid[0, 0].ShouldBeEquivalentTo("1"); + grid.Grid[0, 1].ShouldBeEquivalentTo("2"); + grid.Grid[0, 2].ShouldBeEquivalentTo("3"); + + + evaluation = (EvaluationWrapper)await hostScript.EvaluateAndAssert( + script2, + expectation2, + VariableRHostScript.AssertEvaluationWrapper); + + rowRange = new Range(0, 3); + columnRange = new Range(0, 1); + grid = await GridDataSource.GetGridDataAsync(evaluation.Expression, new GridRange(rowRange, columnRange)); + + grid.ColumnHeader.Range.ShouldBeEquivalentTo(columnRange); + grid.ColumnHeader[0].ShouldBeEquivalentTo("[,1]"); + + grid.RowHeader.Range.ShouldBeEquivalentTo(rowRange); + grid.RowHeader[0].ShouldBeEquivalentTo("[1,]"); + grid.RowHeader[1].ShouldBeEquivalentTo("[2,]"); + grid.RowHeader[2].ShouldBeEquivalentTo("[3,]"); + + grid.Grid.Range.ShouldBeEquivalentTo(new GridRange(rowRange, columnRange)); + grid.Grid[0, 0].ShouldBeEquivalentTo("1"); + grid.Grid[1, 0].ShouldBeEquivalentTo("2"); + grid.Grid[2, 0].ShouldBeEquivalentTo("3"); + } + } + + [Test] + [Category.Variable.Explorer] + public async Task MatrixOnlyRowNameTest() { + var script = "matrix.rowname.na <- matrix(c(1,2,3,4), nrow=2, ncol=2);rownames(matrix.rowname.na)<-c(NA, 'row2');"; + var expectation = new VariableExpectation() { Name = "matrix.rowname.na", Value = "num [1:2, 1:2] 1 2 3 4", TypeName = "double", Class = "matrix", HasChildren = true, CanShowDetail = true }; + + using (var hostScript = new VariableRHostScript()) { + var evaluation = (EvaluationWrapper)await hostScript.EvaluateAndAssert( + script, + expectation, + VariableRHostScript.AssertEvaluationWrapper); + + Range rowRange = new Range(0, 2); + Range columnRange = new Range(0, 2); + var grid = await GridDataSource.GetGridDataAsync(evaluation.Expression, new GridRange(rowRange, columnRange)); + + grid.ColumnHeader.Range.ShouldBeEquivalentTo(columnRange); + grid.ColumnHeader[0].ShouldBeEquivalentTo("[,1]"); + grid.ColumnHeader[1].ShouldBeEquivalentTo("[,2]"); + + grid.RowHeader.Range.ShouldBeEquivalentTo(rowRange); + grid.RowHeader[0].ShouldBeEquivalentTo("NA"); + grid.RowHeader[1].ShouldBeEquivalentTo("row2"); + + grid.Grid.Range.ShouldBeEquivalentTo(new GridRange(rowRange, columnRange)); + grid.Grid[0, 0].ShouldBeEquivalentTo("1"); + grid.Grid[0, 1].ShouldBeEquivalentTo("3"); + grid.Grid[1, 0].ShouldBeEquivalentTo("2"); + grid.Grid[1, 1].ShouldBeEquivalentTo("4"); + } + } + + [Test] + [Category.Variable.Explorer] + public async Task MatrixOnlyColumnNameTest() { + var script = "matrix.colname.na <- matrix(1:6, nrow=2, ncol=3);colnames(matrix.colname.na)<-c('col1',NA,'col3');"; + var expectation = new VariableExpectation() { Name = "matrix.colname.na", Value = "int [1:2, 1:3] 1 2 3 4 5 6", TypeName = "integer", Class = "matrix", HasChildren = true, CanShowDetail = true }; + + using (var hostScript = new VariableRHostScript()) { + var evaluation = (EvaluationWrapper)await hostScript.EvaluateAndAssert( + script, + expectation, + VariableRHostScript.AssertEvaluationWrapper); + + Range rowRange = new Range(0, 2); + Range columnRange = new Range(0, 3); + var grid = await GridDataSource.GetGridDataAsync(evaluation.Expression, new GridRange(rowRange, columnRange)); + + grid.ColumnHeader.Range.ShouldBeEquivalentTo(columnRange); + grid.ColumnHeader[0].ShouldBeEquivalentTo("col1"); + grid.ColumnHeader[1].ShouldBeEquivalentTo("NA"); + grid.ColumnHeader[2].ShouldBeEquivalentTo("col3"); + + grid.RowHeader.Range.ShouldBeEquivalentTo(rowRange); + grid.RowHeader[0].ShouldBeEquivalentTo("[1,]"); + grid.RowHeader[1].ShouldBeEquivalentTo("[2,]"); + + grid.Grid.Range.ShouldBeEquivalentTo(new GridRange(rowRange, columnRange)); + grid.Grid[0, 0].ShouldBeEquivalentTo("1"); + grid.Grid[0, 1].ShouldBeEquivalentTo("3"); + grid.Grid[0, 2].ShouldBeEquivalentTo("5"); + grid.Grid[1, 0].ShouldBeEquivalentTo("2"); + grid.Grid[1, 1].ShouldBeEquivalentTo("4"); + grid.Grid[1, 2].ShouldBeEquivalentTo("6"); + } + } + + [Test] + [Category.Variable.Explorer] + public async Task MatrixLargeCellTest() { + var script = "matrix.largecell <- matrix(list(as.double(1:5000), 2, 3, 4), nrow = 2, ncol = 2);"; + var expectation = new VariableExpectation() { Name = "matrix.largecell", Value = "List of 4", TypeName = "list", Class = "matrix", HasChildren = true, CanShowDetail = true }; + + using (var hostScript = new VariableRHostScript()) { + var evaluation = (EvaluationWrapper)await hostScript.EvaluateAndAssert( + script, + expectation, + VariableRHostScript.AssertEvaluationWrapper); + + Range rowRange = new Range(0, 1); + Range columnRange = new Range(0, 1); + var grid = await GridDataSource.GetGridDataAsync(evaluation.Expression, new GridRange(rowRange, columnRange)); + + grid.ColumnHeader.Range.ShouldBeEquivalentTo(columnRange); + grid.RowHeader.Range.ShouldBeEquivalentTo(rowRange); + grid.Grid.Range.ShouldBeEquivalentTo(new GridRange(rowRange, columnRange)); + + grid.Grid[0, 0].ShouldBeEquivalentTo("1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27..."); + } + } + + [Test] + [Category.Variable.Explorer] + public async Task DataFrameTest() { + var script = "df.test <- data.frame(101:103, c('a', 'b', 'c'))"; + var expectation = new VariableExpectation() { Name = "df.test", Value = "3 obs. of 2 variables", TypeName = "list", Class = "data.frame", HasChildren = true, CanShowDetail = true }; + + using (var hostScript = new VariableRHostScript()) { + var evaluation = (EvaluationWrapper)await hostScript.EvaluateAndAssert( + script, + expectation, + VariableRHostScript.AssertEvaluationWrapper); + + Range rowRange = new Range(0, 3); + Range columnRange = new Range(0, 2); + var grid = await GridDataSource.GetGridDataAsync(evaluation.Expression, new GridRange(rowRange, columnRange)); + + grid.ColumnHeader.Range.ShouldBeEquivalentTo(columnRange); + grid.ColumnHeader[0].ShouldBeEquivalentTo("X101.103"); + grid.ColumnHeader[1].ShouldBeEquivalentTo("c..a....b....c.."); + + grid.RowHeader.Range.ShouldBeEquivalentTo(rowRange); + grid.RowHeader[0].ShouldBeEquivalentTo("1"); + grid.RowHeader[1].ShouldBeEquivalentTo("2"); + grid.RowHeader[2].ShouldBeEquivalentTo("3"); + + grid.Grid.Range.ShouldBeEquivalentTo(new GridRange(rowRange, columnRange)); + grid.Grid[0, 0].ShouldBeEquivalentTo("101"); + grid.Grid[0, 1].ShouldBeEquivalentTo("a"); + grid.Grid[1, 0].ShouldBeEquivalentTo("102"); + grid.Grid[1, 1].ShouldBeEquivalentTo("b"); + grid.Grid[2, 0].ShouldBeEquivalentTo("103"); + grid.Grid[2, 1].ShouldBeEquivalentTo("c"); + } + } + + [Test] + [Category.Variable.Explorer] + public async Task PromiseTest() { + var script = "e <- (function(x, y, z) base::environment())(1,,3)"; + var expectation = new VariableExpectation() { Name = "e", Value = " + /// contains expectation for EvaluationWrapper + /// + [ExcludeFromCodeCoverage] + public class VariableExpectation { + public string Name { get; set; } + public string Value { get; set; } + public string Class { get; set; } + public string TypeName { get; set; } + public bool HasChildren { get; set; } + public bool CanShowDetail { get; set; } + } +} diff --git a/src/Package/Test/DataInspect/VariableRHostScript.cs b/src/Package/Test/DataInspect/VariableRHostScript.cs index 9b9d9618d..e2a4dce68 100644 --- a/src/Package/Test/DataInspect/VariableRHostScript.cs +++ b/src/Package/Test/DataInspect/VariableRHostScript.cs @@ -6,86 +6,106 @@ using FluentAssertions; using Microsoft.Common.Core.Test.Script; using Microsoft.Languages.Editor.Shell; +using Microsoft.R.Debugger; using Microsoft.R.Editor.Data; using Microsoft.R.Host.Client; using Microsoft.R.Host.Client.Test.Script; using Microsoft.UnitTests.Core.Threading; using Microsoft.VisualStudio.R.Package.DataInspect; +using Microsoft.VisualStudio.R.Package.DataInspect.Definitions; using Microsoft.VisualStudio.R.Package.Shell; namespace Microsoft.VisualStudio.R.Package.Test.DataInspect { - /// - /// contains expectation for EvaluationWrapper - /// [ExcludeFromCodeCoverage] - class VariableExpectation { - public string Name { get; set; } - public string Value { get; set; } - public string Class { get; set; } - public string TypeName { get; set; } - public bool HasChildren { get; set; } - public bool CanShowDetail { get; set; } - } - - [ExcludeFromCodeCoverage] - class VariableRHostScript : RHostScript { + public class VariableRHostScript : RHostScript { private VariableProvider _variableProvider; + private EvaluationWrapper _globalEnv; public VariableRHostScript() : base(VsAppShell.Current.ExportProvider.GetExportedValue()) { + _variableProvider = new VariableProvider(base.SessionProvider, VsAppShell.Current.ExportProvider.GetExportedValue()); - _variableProvider = VariableProvider.Current; DoIdle(100); } - public EvaluationWrapper GlobalEnvironment { + public IVariableDataProvider VariableProvider { + get { + return _variableProvider; + } + } + + public EvaluationWrapper GlobalEnvrionment { get { - return _variableProvider.LastEvaluation; + return _globalEnv; } } + private void OnGlobalEnvironmentEvaluated(DebugEvaluationResult result) { + _globalEnv = new EvaluationWrapper(result); + _mre.Set(); + } + private ManualResetEventSlim _mre; public async Task EvaluateAsync(string rScript) { + VariableSubscription subscription = null; try { _mre = new ManualResetEventSlim(); - _variableProvider.VariableChanged += VariableProvider_VariableChanged; + + _globalEnv = null; + subscription = _variableProvider.Subscribe(0, "base::environment()", OnGlobalEnvironmentEvaluated); + using (var evaluation = await base.Session.BeginEvaluationAsync()) { await evaluation.EvaluateAsync(rScript); } - if (!_mre.Wait(TimeSpan.FromMilliseconds(1000))) { - throw new TimeoutException("Evaluate time out"); + if (System.Diagnostics.Debugger.IsAttached) { + _mre.Wait(); + } else { + if (!_mre.Wait(TimeSpan.FromSeconds(10))) { + throw new TimeoutException("Evaluate time out"); + } } } finally { - _variableProvider.VariableChanged -= VariableProvider_VariableChanged; + _variableProvider.Unsubscribe(subscription); } } /// /// evaluate R script and assert if the expectation is not found in global environment /// - /// - /// - /// - public async Task EvaluateAndAssert(string rScript, VariableExpectation expectation) { + public async Task EvaluateAndAssert( + string rScript, + VariableExpectation expectation, + Action assertAction) { + await EvaluateAsync(rScript); - var children = await GlobalEnvironment.GetChildrenAsync(); + var children = await _globalEnv.GetChildrenAsync(); // must contain one and only expectation in result var evaluation = children.First(v => v.Name == expectation.Name); - AssertEvaluationWrapper(evaluation, expectation); + assertAction(evaluation, expectation); + + return evaluation; } - private static void AssertEvaluationWrapper(IRSessionDataObject v, VariableExpectation expectation) { + public static void AssertEvaluationWrapper(IRSessionDataObject rdo, VariableExpectation expectation) { + var v = (EvaluationWrapper)rdo; v.ShouldBeEquivalentTo(expectation, o => o.ExcludingMissingMembers()); } - private void VariableProvider_VariableChanged(object sender, VariableChangedArgs e) { - _mre.Set(); + public static void AssertEvaluationWrapper_ValueStartWith(IRSessionDataObject rdo, VariableExpectation expectation) { + var v = (EvaluationWrapper)rdo; + v.Name.ShouldBeEquivalentTo(expectation.Name); + v.Value.Should().StartWith(expectation.Value); + v.Class.ShouldBeEquivalentTo(expectation.Class); + v.TypeName.ShouldBeEquivalentTo(expectation.TypeName); + v.HasChildren.ShouldBeEquivalentTo(expectation.HasChildren); } protected override void Dispose(bool disposing) { + DoIdle(2000); + base.Dispose(disposing); if (disposing) { diff --git a/src/Package/Test/Microsoft.VisualStudio.R.Package.Test.csproj b/src/Package/Test/Microsoft.VisualStudio.R.Package.Test.csproj index 3d8c9f81c..034f6f786 100644 --- a/src/Package/Test/Microsoft.VisualStudio.R.Package.Test.csproj +++ b/src/Package/Test/Microsoft.VisualStudio.R.Package.Test.csproj @@ -80,6 +80,7 @@ ..\..\..\NugetPackages\Microsoft.VisualStudio.Text.Data.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.Data.dll True + @@ -149,6 +150,10 @@ {fc4aad0a-13b9-49ee-a59c-f03142958170} Microsoft.Common.Core.Test + + {17e155bf-351c-4253-b9b1-36eeea35fe3c} + Microsoft.R.Debugger + {e09d3bda-2e6b-47b5-87ac-b6fc2d33dfab} Microsoft.R.Host.Client @@ -216,11 +221,16 @@ + + + + - - + + + @@ -231,6 +241,7 @@ + diff --git a/src/Package/Test/Mocks/ActiveTextViewTrackerMock.cs b/src/Package/Test/Mocks/ActiveTextViewTrackerMock.cs new file mode 100644 index 000000000..72981369d --- /dev/null +++ b/src/Package/Test/Mocks/ActiveTextViewTrackerMock.cs @@ -0,0 +1,23 @@ +using Microsoft.VisualStudio.Editor.Mocks; +using Microsoft.VisualStudio.R.Package.Utilities; +using Microsoft.VisualStudio.Text.Editor; +using Microsoft.VisualStudio.Utilities; + +namespace Microsoft.VisualStudio.R.Package.Test.Mocks { + public sealed class ActiveTextViewTrackerMock : IActiveWpfTextViewTracker { + private WpfTextViewMock _textView; + + public ActiveTextViewTrackerMock(string content, string contentTypeName) { + var tb = new TextBufferMock(content, contentTypeName); + _textView = new WpfTextViewMock(tb); + } + + public IWpfTextView GetLastActiveTextView(string contentType) { + return _textView; + } + + public IWpfTextView GetLastActiveTextView(IContentType contentType) { + return _textView; + } + } +} diff --git a/src/Package/Test/Mocks/ReplWindowMock.cs b/src/Package/Test/Mocks/ReplWindowMock.cs new file mode 100644 index 000000000..6c801c7f8 --- /dev/null +++ b/src/Package/Test/Mocks/ReplWindowMock.cs @@ -0,0 +1,47 @@ +using Microsoft.R.Editor.ContentType; +using Microsoft.VisualStudio.Editor.Mocks; +using Microsoft.VisualStudio.InteractiveWindow.Shell; +using Microsoft.VisualStudio.R.Package.Repl; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Shell.Mocks; +using Microsoft.VisualStudio.Text.Editor; + +namespace Microsoft.VisualStudio.R.Package.Test.Mocks { + public sealed class ReplWindowMock : IReplWindow { + public bool IsActive { get; set; } + public string EnqueuedCode { get; set; } + public string ExecutedCode { get; set; } + + public void ClearPendingInputs() { + } + + public void Dispose() { + } + + public void EnqueueCode(string code, bool addNewLine) { + EnqueuedCode = code; + } + + public void ExecuteCode(string code) { + ExecutedCode = code; + } + + public void ExecuteCurrentExpression(ITextView textView) { + } + + public IVsInteractiveWindow GetInteractiveWindow() { + return new VsInteractiveWindowMock( + new WpfTextViewMock( + new TextBufferMock(string.Empty, RContentTypeDefinition.ContentType))); + } + + public void Show() { + ReplWindow.ShowWindow(); + } + + public void ReplaceCurrentExpression(string replaceWith) { + ExecutedCode = replaceWith; + EnqueuedCode = replaceWith; + } + } +} diff --git a/src/Package/Test/Plots/PlotCommandsTest.cs b/src/Package/Test/Plots/PlotCommandsTest.cs new file mode 100644 index 000000000..16ba4f62d --- /dev/null +++ b/src/Package/Test/Plots/PlotCommandsTest.cs @@ -0,0 +1,147 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using FluentAssertions; +using Microsoft.UnitTests.Core.XUnit; +using Microsoft.VisualStudio.R.Package.Commands; +using Microsoft.VisualStudio.R.Package.Plots; +using Microsoft.VisualStudio.R.Package.Plots.Commands; +using Microsoft.VisualStudio.R.Package.Plots.Definitions; +using NSubstitute; + +namespace Microsoft.VisualStudio.R.Package.Test.Plots { + [ExcludeFromCodeCoverage] + public class PlotCommandsTest { + [Test] + [Category.Plots] + public void HistoryNext() { + var history = new PlotHistory(); + var cmd = new HistoryNextPlotCommand(history); + + cmd.CommandID.ID.Should().Be(RPackageCommandId.icmdNextPlot); + cmd.SetStatus(); + cmd.Enabled.Should().BeFalse(); + + history.ActivePlotIndex = 0; + history.PlotCount = 1; + cmd.SetStatus(); + cmd.Enabled.Should().BeFalse(); + + history.ActivePlotIndex = 0; + history.PlotCount = 2; + cmd.SetStatus(); + cmd.Enabled.Should().BeTrue(); + + history.ActivePlotIndex = 1; + history.PlotCount = 2; + cmd.SetStatus(); + cmd.Enabled.Should().BeFalse(); + } + + [Test] + [Category.Plots] + public void HistoryPrevious() { + var history = new PlotHistory(); + var cmd = new HistoryPreviousPlotCommand(history); + + cmd.CommandID.ID.Should().Be(RPackageCommandId.icmdPrevPlot); + cmd.SetStatus(); + cmd.Enabled.Should().BeFalse(); + + history.ActivePlotIndex = 0; + history.PlotCount = 1; + cmd.SetStatus(); + cmd.Enabled.Should().BeFalse(); + + history.ActivePlotIndex = 0; + history.PlotCount = 2; + cmd.SetStatus(); + cmd.Enabled.Should().BeFalse(); + + history.ActivePlotIndex = 1; + history.PlotCount = 2; + cmd.SetStatus(); + cmd.Enabled.Should().BeTrue(); + } + + [Test] + [Category.Plots] + public void CopyPlotAsBitmap() { + var history = new PlotHistory(); + var cmd = new CopyPlotAsBitmapCommand(history); + + cmd.CommandID.ID.Should().Be(RPackageCommandId.icmdCopyPlotAsBitmap); + cmd.SetStatus(); + cmd.Enabled.Should().BeFalse(); + + history.ActivePlotIndex = 0; + history.PlotCount = 1; + cmd.SetStatus(); + cmd.Enabled.Should().BeTrue(); + } + + [Test] + [Category.Plots] + public void CopyPlotAsMetafile() { + var history = new PlotHistory(); + var cmd = new CopyPlotAsMetafileCommand(history); + + cmd.CommandID.ID.Should().Be(RPackageCommandId.icmdCopyPlotAsMetafile); + cmd.SetStatus(); + cmd.Enabled.Should().BeFalse(); + + history.ActivePlotIndex = 0; + history.PlotCount = 1; + cmd.SetStatus(); + cmd.Enabled.Should().BeTrue(); + } + + [Test] + [Category.Plots] + public void ExportPlotAsImage() { + var history = new PlotHistory(); + var cmd = new ExportPlotAsImageCommand(history); + + cmd.CommandID.ID.Should().Be(RPackageCommandId.icmdExportPlotAsImage); + cmd.SetStatus(); + cmd.Enabled.Should().BeFalse(); + + history.ActivePlotIndex = 0; + history.PlotCount = 1; + cmd.SetStatus(); + cmd.Enabled.Should().BeTrue(); + } + + [Test] + [Category.Plots] + public void ExportPlotAsPdf() { + var history = new PlotHistory(); + var cmd = new ExportPlotAsPdfCommand(history); + + cmd.CommandID.ID.Should().Be(RPackageCommandId.icmdExportPlotAsPdf); + cmd.SetStatus(); + cmd.Enabled.Should().BeFalse(); + + history.ActivePlotIndex = 0; + history.PlotCount = 1; + cmd.SetStatus(); + cmd.Enabled.Should().BeTrue(); + } + + class PlotHistory : IPlotHistory { + public int ActivePlotIndex { get; set; } = -1; + public IPlotContentProvider PlotContentProvider { get; set; } + + public int PlotCount { get; set; } +#pragma warning disable 67 + public event EventHandler HistoryChanged; + + public void Dispose() { + } + + public Task RefreshHistoryInfo() { + return Task.CompletedTask; + } + } + } +} diff --git a/src/Package/Test/Repl/RInteractiveEvaluatorTest.cs b/src/Package/Test/Repl/RInteractiveEvaluatorTest.cs new file mode 100644 index 000000000..13b0ea3f3 --- /dev/null +++ b/src/Package/Test/Repl/RInteractiveEvaluatorTest.cs @@ -0,0 +1,74 @@ +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using FluentAssertions; +using Microsoft.R.Editor.ContentType; +using Microsoft.R.Host.Client; +using Microsoft.R.Support.Settings; +using Microsoft.UnitTests.Core.XUnit; +using Microsoft.VisualStudio.Editor.Mocks; +using Microsoft.VisualStudio.InteractiveWindow; +using Microsoft.VisualStudio.R.Package.History; +using Microsoft.VisualStudio.R.Package.Repl; +using Microsoft.VisualStudio.R.Package.Shell; +using Microsoft.VisualStudio.R.Package.Test.Utility; +using Microsoft.VisualStudio.Shell.Mocks; +using Xunit; + +namespace Microsoft.VisualStudio.R.Package.Test.Repl { + [ExcludeFromCodeCoverage] + [Collection(CollectionNames.NonParallel)] + public sealed class RInteractiveEvaluatorTest { + [Test] + [Category.Repl] + public async Task EvaluatorTest() { + using (new VsRHostScript()) { + var sessionProvider = VsAppShell.Current.ExportProvider.GetExportedValue(); + var historyProvider = VsAppShell.Current.ExportProvider.GetExportedValue(); + var rInteractive = new RInteractive(sessionProvider, historyProvider, RToolsSettings.Current); + var history = historyProvider.CreateRHistory(rInteractive); + + var session = sessionProvider.GetInteractiveWindowRSession(); + using (var eval = new RInteractiveEvaluator(session, history, RToolsSettings.Current)) { + var tb = new TextBufferMock(string.Empty, RContentTypeDefinition.ContentType); + var tv = new WpfTextViewMock(tb); + + var iwm = new InteractiveWindowMock(tv); + eval.CurrentWindow = iwm; + + var result = await eval.InitializeAsync(); + result.Should().Be(ExecutionResult.Success); + session.IsHostRunning.Should().BeTrue(); + + eval.CanExecuteCode("x <-").Should().BeFalse(); + eval.CanExecuteCode("(()").Should().BeFalse(); + eval.CanExecuteCode("a *(b+c)").Should().BeTrue(); + + result = await eval.ExecuteCodeAsync(new string(new char[10000])); + result.Should().Be(ExecutionResult.Failure); + string text = tb.CurrentSnapshot.GetText(); + text.Should().Contain(string.Format(Resources.InputIsTooLong, 4096)); + + tb.Clear(); + + result = await eval.ExecuteCodeAsync("電話帳 全米のお"); + result.Should().Be(ExecutionResult.Failure); + text = tb.CurrentSnapshot.GetText(); + text.Should().Be(Resources.Error_ReplUnicodeCoversion); + + tb.Clear(); + + result = await eval.ExecuteCodeAsync("x <- c(1:10)"); + result.Should().Be(ExecutionResult.Success); + text = tb.CurrentSnapshot.GetText(); + text.Should().Be(string.Empty); + + tb.Clear(); + + await eval.ResetAsync(initialize: false); + text = tb.CurrentSnapshot.GetText(); + text.Should().StartWith(Resources.MicrosoftRHostStopping); + } + } + } + } +} diff --git a/src/Package/Test/Repl/ReplCommandTest.cs b/src/Package/Test/Repl/ReplCommandTest.cs new file mode 100644 index 000000000..aa0861c99 --- /dev/null +++ b/src/Package/Test/Repl/ReplCommandTest.cs @@ -0,0 +1,117 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using FluentAssertions; +using Microsoft.Languages.Editor; +using Microsoft.R.Editor.ContentType; +using Microsoft.R.Host.Client; +using Microsoft.R.Host.Client.Mocks; +using Microsoft.R.Host.Client.Test.Script; +using Microsoft.UnitTests.Core.XUnit; +using Microsoft.VisualStudio.Editor.Mocks; +using Microsoft.VisualStudio.R.Package.Repl; +using Microsoft.VisualStudio.R.Package.Repl.Commands; +using Microsoft.VisualStudio.R.Package.Repl.Workspace; +using Microsoft.VisualStudio.R.Package.Test.Mocks; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Shell.Mocks; +using Microsoft.VisualStudio.Text; + +namespace Microsoft.VisualStudio.R.Package.Test.Commands { + [ExcludeFromCodeCoverage] + public class ReplCommandTest { + [Test] + [Category.Repl] + public void InterruptRStatusTest() { + var debugger = new VsDebuggerMock(); + var sp = new RSessionProviderMock(); + var rw = new ReplWindowMock(); + var command = new InterruptRCommand(rw, sp, debugger); + + command.SetStatus(); + command.Visible.Should().BeFalse(); + command.Enabled.Should().BeFalse(); + + rw.IsActive = true; + + command.SetStatus(); + command.Visible.Should().BeTrue(); + command.Enabled.Should().BeFalse(); + + var session = sp.GetOrCreate(GuidList.InteractiveWindowRSessionGuid, new RHostClientTestApp()); + session.StartHostAsync(null); + + command.SetStatus(); + command.Visible.Should().BeTrue(); + command.Enabled.Should().BeFalse(); + + session.BeginEvaluationAsync(); + + command.SetStatus(); + command.Visible.Should().BeTrue(); + command.Enabled.Should().BeTrue(); + + debugger.Mode = DBGMODE.DBGMODE_Break; + + command.SetStatus(); + command.Visible.Should().BeTrue(); + command.Enabled.Should().BeFalse(); + + debugger.Mode = DBGMODE.DBGMODE_Run; + + command.SetStatus(); + command.Visible.Should().BeTrue(); + command.Enabled.Should().BeTrue(); + + command.Handle(); + + command.SetStatus(); + command.Visible.Should().BeTrue(); + command.Enabled.Should().BeFalse(); + + session.Dispose(); + + command.SetStatus(); + command.Visible.Should().BeTrue(); + command.Enabled.Should().BeFalse(); + } + + [Test] + [Category.Repl] + public void SendToReplTest() { + string content = "x <- 1\r\ny <- 2\r\n"; + + var tb = new TextBufferMock(content, RContentTypeDefinition.ContentType); + var tv = new TextViewMock(tb); + + var rw = new ReplWindowMock(); + ReplWindow.Current = rw; + var command = new SendToReplCommand(tv); + + var frame = ReplWindow.FindReplWindowFrame(__VSFINDTOOLWIN.FTW_fFindFirst); + frame.Should().NotBeNull(); + frame.IsVisible().Should().Be(VSConstants.S_OK); + + Guid group = VSConstants.VsStd11; + int id = (int)VSConstants.VSStd11CmdID.ExecuteLineInInteractive; + object o = new object(); + + command.Status(group, id).Should().Be(CommandStatus.SupportedAndEnabled); + + frame.Hide(); + frame.IsVisible().Should().Be(VSConstants.S_FALSE); + + command.Invoke(group, id, null, ref o); + + frame.IsVisible().Should().Be(VSConstants.S_OK); + rw.EnqueuedCode.Should().Be("x <- 1"); + + int caretPos = tv.Caret.Position.BufferPosition.Position; + int lineNum = tb.CurrentSnapshot.GetLineNumberFromPosition(caretPos); + lineNum.Should().Be(1); + + tv.Selection.Select(new SnapshotSpan(tb.CurrentSnapshot, new Span(0, 1)), false); + command.Invoke(group, id, null, ref o); + rw.EnqueuedCode.Should().Be("x"); + } + } +} diff --git a/src/Package/Test/Repl/ReplHistoryTest.cs b/src/Package/Test/Repl/ReplHistoryTest.cs new file mode 100644 index 000000000..e4fc337c3 --- /dev/null +++ b/src/Package/Test/Repl/ReplHistoryTest.cs @@ -0,0 +1,173 @@ +using System.Diagnostics.CodeAnalysis; +using System.Threading.Tasks; +using FluentAssertions; +using Microsoft.R.Editor.ContentType; +using Microsoft.R.Host.Client; +using Microsoft.R.Support.Settings; +using Microsoft.UnitTests.Core.XUnit; +using Microsoft.VisualStudio.Editor.Mocks; +using Microsoft.VisualStudio.InteractiveWindow; +using Microsoft.VisualStudio.R.Package.History; +using Microsoft.VisualStudio.R.Package.Repl; +using Microsoft.VisualStudio.R.Package.Shell; +using Microsoft.VisualStudio.R.Package.Test.Mocks; +using Microsoft.VisualStudio.R.Package.Test.Utility; +using Microsoft.VisualStudio.Shell.Mocks; +using Xunit; + +namespace Microsoft.VisualStudio.R.Package.Test.Repl { + [ExcludeFromCodeCoverage] + [Collection(CollectionNames.NonParallel)] + public sealed class ReplHistoryTest { + [Test] + [Category.Repl] + public async Task HistoryTest01() { + using (new VsRHostScript()) { + var sessionProvider = VsAppShell.Current.ExportProvider.GetExportedValue(); + var historyProvider = VsAppShell.Current.ExportProvider.GetExportedValue(); + var rInteractive = new RInteractive(sessionProvider, historyProvider, RToolsSettings.Current); + var history = historyProvider.CreateRHistory(rInteractive); + + var session = sessionProvider.GetInteractiveWindowRSession(); + using (var eval = new RInteractiveEvaluator(session, history, RToolsSettings.Current)) { + var tb = new TextBufferMock(string.Empty, RContentTypeDefinition.ContentType); + var tv = new WpfTextViewMock(tb); + + var iwm = new InteractiveWindowMock(tv); + eval.CurrentWindow = iwm; + + var result = await eval.InitializeAsync(); + result.Should().Be(ExecutionResult.Success); + session.IsHostRunning.Should().BeTrue(); + + result = await eval.ExecuteCodeAsync("x <- c(1:10)"); + result.Should().Be(ExecutionResult.Success); + history.HasEntries.Should().BeTrue(); + history.HasSelectedEntries.Should().BeFalse(); + history.SelectHistoryEntry(0); + history.HasSelectedEntries.Should().BeTrue(); + + tb.Clear(); + history.SendSelectedToTextView(tv); + string text = tb.CurrentSnapshot.GetText(); + text.Should().Be("x <- c(1:10)"); + } + } + } + + [Test] + [Category.Repl] + public async Task HistoryTest02() { + using (var script = new VsRHostScript()) { + var sessionProvider = VsAppShell.Current.ExportProvider.GetExportedValue(); + var historyProvider = VsAppShell.Current.ExportProvider.GetExportedValue(); + var rInteractive = new RInteractive(sessionProvider, historyProvider, RToolsSettings.Current); + var history = historyProvider.CreateRHistory(rInteractive); + + var session = sessionProvider.GetInteractiveWindowRSession(); + using (var eval = new RInteractiveEvaluator(session, history, RToolsSettings.Current)) { + var tb = new TextBufferMock(string.Empty, RContentTypeDefinition.ContentType); + var tv = new WpfTextViewMock(tb); + + var iwm = new InteractiveWindowMock(tv); + eval.CurrentWindow = iwm; + + var rw = new ReplWindowMock(); + ReplWindow.Current = rw; + + var result = await eval.InitializeAsync(); + result.Should().Be(ExecutionResult.Success); + session.IsHostRunning.Should().BeTrue(); + + result = await eval.ExecuteCodeAsync("x <- c(1:10)"); + result.Should().Be(ExecutionResult.Success); + + await eval.ExecuteCodeAsync("\r\n"); + + result = await eval.ExecuteCodeAsync("x <- c(1:20)"); + result.Should().Be(ExecutionResult.Success); + + history.HasEntries.Should().BeTrue(); + history.HasSelectedEntries.Should().BeFalse(); + + int eventCount = 0; + history.SelectionChanged += (s, e) => { + eventCount++; + }; + + history.SelectHistoryEntry(1); + history.HasSelectedEntries.Should().BeTrue(); + eventCount.Should().Be(1); + + history.SelectPreviousHistoryEntry(); + eventCount.Should().Be(3); // event fires twice? + VerifyHistoryText(history, tb, tv, "x <- c(1:10)"); + + history.SelectNextHistoryEntry(); + eventCount.Should().Be(5); + VerifyHistoryText(history, tb, tv, "x <- c(1:20)"); + + history.PreviousEntry(); + rw.EnqueuedCode.Should().Be("x <- c(1:20)"); + + history.PreviousEntry(); + rw.EnqueuedCode.Should().Be("x <- c(1:10)"); + + history.NextEntry(); + rw.EnqueuedCode.Should().Be("x <- c(1:20)"); + + history.SelectHistoryEntries(new int[] { 0, 1 }); + VerifyHistoryText(history, tb, tv, "x <- c(1:10)\r\nx <- c(1:20)"); + eventCount.Should().Be(6); + + history.ToggleHistoryEntrySelection(1); + VerifyHistoryText(history, tb, tv, "x <- c(1:10)"); + eventCount.Should().Be(7); + + history.DeselectHistoryEntry(0); + history.HasSelectedEntries.Should().BeFalse(); + eventCount.Should().Be(8); + + history.SelectAllEntries(); + history.HasSelectedEntries.Should().BeTrue(); + string text = history.GetSelectedText(); + text.Should().Be("x <- c(1:10)\r\nx <- c(1:20)"); + + var spans = history.GetSelectedHistoryEntrySpans(); + spans.Count.Should().Be(1); + + spans[0].Start.Position.Should().Be(0); + spans[0].End.Position.Should().Be(26); + + history.DeselectHistoryEntry(0); + history.DeselectHistoryEntry(1); + history.HasSelectedEntries.Should().BeFalse(); + + history.SelectAllEntries(); + history.ToggleHistoryEntrySelection(1); + + history.DeleteSelectedHistoryEntries(); + history.SelectAllEntries(); + + text = history.GetSelectedText(); + text.Should().Be("x <- c(1:20)"); + + history.DeleteAllHistoryEntries(); + + history.HasEntries.Should().BeFalse(); + history.HasSelectedEntries.Should().BeFalse(); + + text = history.GetSelectedText(); + text.Should().Be(string.Empty); + } + } + } + + private void VerifyHistoryText(IRHistory history, TextBufferMock tb, TextViewMock tv, string expected) { + tb.Clear(); + history.SendSelectedToTextView(tv); + string text = tb.CurrentSnapshot.GetText(); + text.Should().Be(expected); + } + } +} diff --git a/src/Package/Test/Shell/TestVsAppShell.cs b/src/Package/Test/Shell/TestVsAppShell.cs index d240cdf13..9d8fcf49a 100644 --- a/src/Package/Test/Shell/TestVsAppShell.cs +++ b/src/Package/Test/Shell/TestVsAppShell.cs @@ -7,6 +7,7 @@ using Microsoft.Languages.Editor.Undo; using Microsoft.R.Support.Settings; using Microsoft.R.Support.Test.Utility; +using Microsoft.UnitTests.Core.Threading; using Microsoft.VisualStudio.R.Package.Shell; using Microsoft.VisualStudio.R.Package.Test.Utility; using Microsoft.VisualStudio.Text; @@ -35,9 +36,12 @@ public static void Create() { // replacements. For example, interactive editor tests // need smaller MEF catalog which excludes certain // VS-specific implementations. - _instance = new TestVsAppShell(); - VsAppShell.Current = _instance; - RToolsSettings.Current = new TestRToolsSettings(); + UIThreadHelper.Instance.Invoke(() => { + _instance = new TestVsAppShell(); + _instance.MainThread = UIThreadHelper.Instance.Thread; + VsAppShell.Current = _instance; + RToolsSettings.Current = new TestRToolsSettings(); + }); } #region ICompositionCatalog diff --git a/src/Package/Test/Telemetry/TelemetryTest.cs b/src/Package/Test/Telemetry/TelemetryTest.cs new file mode 100644 index 000000000..28d466f0c --- /dev/null +++ b/src/Package/Test/Telemetry/TelemetryTest.cs @@ -0,0 +1,86 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using FluentAssertions; +using Microsoft.Common.Core.Test.Telemetry; +using Microsoft.UnitTests.Core.XUnit; +using Microsoft.VisualStudio.R.Package.Telemetry; +using Microsoft.VisualStudio.Shell.Interop; +using Microsoft.VisualStudio.Shell.Mocks; + +namespace Microsoft.VisualStudio.R.Package.Test.Telemetry { + [ExcludeFromCodeCoverage] + public class TelemetryTest { + [Test] + [Category.Telemetry] + public void ReportConfiguration() { + var svc = new TelemetryTestService(); + string log; + using (var t = new RtvsTelemetry(svc)) { + t.ReportConfiguration(); + log = svc.SessionLog; + } + + log.Length.Should().BeGreaterThan(0); + log.Should().Contain(TelemetryTestService.EventNamePrefixString); + log.Should().Contain(RtvsTelemetry.ConfigurationEvents.RBasePackages); + log.Should().Contain(RtvsTelemetry.ConfigurationEvents.RUserPackages); + } + + [Test] + [Category.Telemetry] + public void ReportSettings() { + var svc = new TelemetryTestService(); + string log; + using (var t = new RtvsTelemetry(svc)) { + t.ReportSettings(); + log = svc.SessionLog; + } + + log.Length.Should().BeGreaterThan(0); + log.Should().Contain("Cran"); + log.Should().Contain("LoadRData"); + log.Should().Contain("SaveRData"); + log.Should().Contain("RCommandLineArguments"); + log.Should().Contain("MultilineHistorySelection"); + log.Should().Contain("AlwaysSaveHistory"); + log.Should().Contain("AutoFormat"); + log.Should().Contain("CommitOnEnter"); + log.Should().Contain("CommitOnSpace"); + log.Should().Contain("FormatOnPaste"); + log.Should().Contain("SendToReplOnCtrlEnter"); + log.Should().Contain("ShowCompletionOnFirstChar"); + log.Should().Contain("SignatureHelpEnabled"); + log.Should().Contain("CompletionEnabled"); + log.Should().Contain("SyntaxCheckInRepl"); + log.Should().Contain("PartialArgumentNameMatch"); + } + + [Test] + [Category.Telemetry] + public void ReportWindowLayout() { + var svc = new TelemetryTestService(); + var shell = new VsUiShellMock(); + Guid g = new Guid("6B72640E-99F8-40A5-BCDB-BB8CF250A1B5"); + Guid p1 = new Guid("E5815AEF-4D98-4BC4-84A0-5DF62ED0755D"); + Guid p2 = new Guid("22130C87-7D87-4C41-9BC0-14BFA3261DA8"); + Guid p3 = new Guid("B7AF6C09-BC6F-41F1-AF1A-B67D267F1C0E"); + + IVsWindowFrame frame; + shell.CreateToolWindow(0, 1, null, ref g, ref p1, ref g, null, "Window#1", null, out frame); + shell.CreateToolWindow(0, 2, null, ref g, ref p2, ref g, null, "Window#2", null, out frame); + shell.CreateToolWindow(0, 3, null, ref g, ref p3, ref g, null, "Window#3", null, out frame); + + string log; + using (var t = new RtvsTelemetry(svc)) { + t.ReportWindowLayout(shell); + log = svc.SessionLog; + } + + log.Length.Should().BeGreaterThan(0); + log.Should().Contain("Window#1"); + log.Should().Contain("Window#2"); + log.Should().Contain("Window#3"); + log.Should().Contain("100"); + } + } +} diff --git a/src/Package/Test/Utility/VsRHostScript.cs b/src/Package/Test/Utility/VsRHostScript.cs index 71197693b..dc8e3397f 100644 --- a/src/Package/Test/Utility/VsRHostScript.cs +++ b/src/Package/Test/Utility/VsRHostScript.cs @@ -6,8 +6,8 @@ namespace Microsoft.VisualStudio.R.Package.Test.Utility { [ExcludeFromCodeCoverage] public sealed class VsRHostScript : RHostScript { - public VsRHostScript() : - base(VsAppShell.Current.ExportProvider.GetExportedValue()) { + public VsRHostScript(IRHostClientApp clientApp = null) : + base(VsAppShell.Current.ExportProvider.GetExportedValue(), clientApp) { } } } diff --git a/src/Package/TestApp/Data/VariableExplorerTest.cs b/src/Package/TestApp/Data/VariableExplorerTest.cs index 53db3f964..c02e43603 100644 --- a/src/Package/TestApp/Data/VariableExplorerTest.cs +++ b/src/Package/TestApp/Data/VariableExplorerTest.cs @@ -1,8 +1,11 @@ -using System.Diagnostics.CodeAnalysis; -using System.Threading; +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Threading.Tasks; +using System.Windows; +using System.Xml.Serialization; using Microsoft.Common.Core.Test.Controls; -using Microsoft.UnitTests.Core.Threading; +using Microsoft.R.Host.Client; using Microsoft.UnitTests.Core.XUnit; using Microsoft.VisualStudio.R.Interactive.Test.Utility; using Microsoft.VisualStudio.R.Package.DataInspect; @@ -12,7 +15,7 @@ namespace Microsoft.VisualStudio.R.Interactive.Test.Data { [ExcludeFromCodeCoverage] [Collection(CollectionNames.NonParallel)] - public class VariableExplorerTest { + public class VariableExplorerTest : InteractiveTest { private readonly TestFilesFixture _files; public VariableExplorerTest(TestFilesFixture files) { @@ -21,19 +24,10 @@ public VariableExplorerTest(TestFilesFixture files) { [Test] [Category.Interactive] - public void VaraibleExplorer_ConstructorTest01() { - using (var script = new ControlTestScript(typeof(VariableGridHost))) { - string actual = script.WriteVisualTree(); - ViewTreeDump.CompareVisualTrees(_files, actual, "VariableExplorer01"); - } - } - - [Test] - [Category.Interactive] - public void VaraibleExplorer_ConstructorTest02() { + public void VariableExplorer_ConstructorTest02() { using (var hostScript = new VsRHostScript()) { using (var script = new ControlTestScript(typeof(VariableView))) { - string actual = script.WriteVisualTree(); + var actual = VisualTreeObject.Create(script.Control); ViewTreeDump.CompareVisualTrees(_files, actual, "VariableExplorer02"); } } @@ -41,8 +35,8 @@ public void VaraibleExplorer_ConstructorTest02() { [Test] [Category.Interactive] - public void VaraibleExplorer_SimpleDataTest() { - string actual = null; + public void VariableExplorer_SimpleDataTest() { + VisualTreeObject actual = null; using (var hostScript = new VsRHostScript()) { using (var script = new ControlTestScript(typeof(VariableView))) { DoIdle(100); @@ -53,21 +47,10 @@ public void VaraibleExplorer_SimpleDataTest() { }).Wait(); DoIdle(2000); - actual = script.WriteVisualTree(); + actual = VisualTreeObject.Create(script.Control); } } ViewTreeDump.CompareVisualTrees(_files, actual, "VariableExplorer03"); } - - private static void DoIdle(int ms) { - UIThreadHelper.Instance.Invoke(() => { - int time = 0; - while (time < ms) { - IdleTime.DoIdle(); - Thread.Sleep(20); - time += 20; - } - }); - } } } diff --git a/src/Package/TestApp/Data/VariableGridTest.cs b/src/Package/TestApp/Data/VariableGridTest.cs new file mode 100644 index 000000000..dd5109122 --- /dev/null +++ b/src/Package/TestApp/Data/VariableGridTest.cs @@ -0,0 +1,73 @@ +using System; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Threading.Tasks; +using System.Windows; +using System.Xml.Serialization; +using FluentAssertions; +using Microsoft.Common.Core.Test.Controls; +using Microsoft.R.Debugger; +using Microsoft.R.Host.Client; +using Microsoft.UnitTests.Core.Threading; +using Microsoft.UnitTests.Core.XUnit; +using Microsoft.VisualStudio.R.Interactive.Test.Utility; +using Microsoft.VisualStudio.R.Package.DataInspect; +using Microsoft.VisualStudio.R.Package.Test.DataInspect; +using Microsoft.VisualStudio.R.Package.Test.Utility; +using Xunit; + +namespace Microsoft.VisualStudio.R.Interactive.Test.Data { + [ExcludeFromCodeCoverage] + [Collection(CollectionNames.NonParallel)] + public class VariableGridTest : InteractiveTest { + private readonly TestFilesFixture _files; + + public VariableGridTest(TestFilesFixture files) { + _files = files; + } + + [Test] + [Category.Interactive] + public void VariableGrid_ConstructorTest01() { + using (var script = new ControlTestScript(typeof(VariableGridHost))) { + var actual = VisualTreeObject.Create(script.Control); + ViewTreeDump.CompareVisualTrees(_files, actual, "VariableExplorer01"); + } + } + + [Test] + [Category.Interactive] + public void VariableGrid_ConstructorTest02() { + VisualTreeObject actual = null; + using (var hostScript = new VariableRHostScript()) { + using (var script = new ControlTestScript(typeof(VariableGridHost))) { + DoIdle(100); + + EvaluationWrapper wrapper = null; + Task.Run(async () => { + hostScript.VariableProvider.Subscribe( + 0, + "grid.test", + (r) => wrapper = new EvaluationWrapper(r)); + + await hostScript.EvaluateAsync("grid.test <- matrix(1:10, 2, 5)"); + }).Wait(); + + DoIdle(2000); + + wrapper.Should().NotBeNull(); + + UIThreadHelper.Instance.Invoke(() => { + var host = (VariableGridHost)script.Control; + host.SetEvaluation(wrapper); + }); + + DoIdle(1000); + + actual = VisualTreeObject.Create(script.Control); + } + } + ViewTreeDump.CompareVisualTrees(_files, actual, "VariableGrid02"); + } + } +} diff --git a/src/Package/TestApp/Files/VariableExplorer01.tree b/src/Package/TestApp/Files/VariableExplorer01.tree index 2d052ae8a..6b0d66503 100644 --- a/src/Package/TestApp/Files/VariableExplorer01.tree +++ b/src/Package/TestApp/Files/VariableExplorer01.tree @@ -1,1041 +1,12367 @@ -VariableGridHost -Content = Microsoft.VisualStudio.R.Package.DataInspect.MatrixView -ContentTemplate = null -Padding = 0,0,0,0 -DataContext = null -Name = -Margin = 0,0,0,0 -HorizontalAlignment = Stretch -VerticalAlignment = Stretch -ToolTip = null -ContextMenu = null -Visibility = Visible -IsEnabled = True -IsVisible = True -IsEnabled = True -IsEnabled = True -TextAlignment = Left -IsVirtualizing = True -HorizontalScrollBarVisibility = Disabled -VerticalScrollBarVisibility = Visible -Name = --------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = Microsoft.VisualStudio.R.Package.DataInspect.MatrixView - ContentTemplate = null - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - MatrixView - Content = System.Windows.Controls.Grid - ContentTemplate = null - Padding = 0,0,0,0 - DataContext = null - Name = VariableGrid - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = System.Windows.Controls.Grid - ContentTemplate = null - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Grid - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Rectangle - DataContext = null - Name = LeftTopCorner - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - VisualGrid - DataContext = null - Name = ColumnHeader - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - Rectangle - DataContext = null - Name = RightTopCorner - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - VisualGrid - DataContext = null - Name = RowHeader - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - VisualGrid - DataContext = null - Name = Data - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - ScrollBar - Padding = 0,0,0,0 - DataContext = null - Name = VerticalScrollBar - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Grid - DataContext = null - Name = Bg - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - RepeatButton - Content = System.Windows.Shapes.Path - ContentTemplate = null - Padding = 1,1,1,1 - DataContext = null - Name = PART_LineUpButton - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = null - Name = border - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = System.Windows.Shapes.Path - ContentTemplate = null - DataContext = null - Name = contentPresenter - Margin = 1,1,1,1 - HorizontalAlignment = Center - VerticalAlignment = Center - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Path - Data = M0,4C0,4 0,6 0,6 0,6 3.5,2.5 3.5,2.5 3.5,2.5 7,6 7,6 7,6 7,4 7,4 7,4 3.5,0.5 3.5,0.5 3.5,0.5 0,4 0,4z - DataContext = null - Name = ArrowTop - Margin = 3,4,3,3 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - Track - DataContext = null - Name = PART_Track - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - RepeatButton - Content = null - ContentTemplate = null - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Rectangle - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - RepeatButton - Content = null - ContentTemplate = null - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Rectangle - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Thumb - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Rectangle - DataContext = null - Name = rectangle - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - RepeatButton - Content = System.Windows.Shapes.Path - ContentTemplate = null - Padding = 1,1,1,1 - DataContext = null - Name = PART_LineDownButton - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = null - Name = border - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = System.Windows.Shapes.Path - ContentTemplate = null - DataContext = null - Name = contentPresenter - Margin = 1,1,1,1 - HorizontalAlignment = Center - VerticalAlignment = Center - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Path - Data = M0,2.5C0,2.5 0,0.5 0,0.5 0,0.5 3.5,4 3.5,4 3.5,4 7,0.5 7,0.5 7,0.5 7,2.5 7,2.5 7,2.5 3.5,6 3.5,6 3.5,6 0,2.5 0,2.5z - DataContext = null - Name = ArrowBottom - Margin = 3,4,3,3 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - Rectangle - DataContext = null - Name = LeftBottomCorner - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - ScrollBar - Padding = 0,0,0,0 - DataContext = null - Name = HorizontalScrollBar - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Grid - DataContext = null - Name = Bg - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - RepeatButton - Content = System.Windows.Shapes.Path - ContentTemplate = null - Padding = 1,1,1,1 - DataContext = null - Name = PART_LineLeftButton - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = null - Name = border - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = System.Windows.Shapes.Path - ContentTemplate = null - DataContext = null - Name = contentPresenter - Margin = 1,1,1,1 - HorizontalAlignment = Center - VerticalAlignment = Center - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Path - Data = M3.18,7C3.18,7 5,7 5,7 5,7 1.81,3.5 1.81,3.5 1.81,3.5 5,0 5,0 5,0 3.18,0 3.18,0 3.18,0 0,3.5 0,3.5 0,3.5 3.18,7 3.18,7z - DataContext = null - Name = ArrowLeft - Margin = 3,3,3,3 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - Track - DataContext = null - Name = PART_Track - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - RepeatButton - Content = null - ContentTemplate = null - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Rectangle - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - RepeatButton - Content = null - ContentTemplate = null - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Rectangle - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Thumb - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Rectangle - DataContext = null - Name = rectangle - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - RepeatButton - Content = System.Windows.Shapes.Path - ContentTemplate = null - Padding = 1,1,1,1 - DataContext = null - Name = PART_LineRightButton - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = null - Name = border - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = System.Windows.Shapes.Path - ContentTemplate = null - DataContext = null - Name = contentPresenter - Margin = 1,1,1,1 - HorizontalAlignment = Center - VerticalAlignment = Center - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Path - Data = M1.81,7C1.81,7 0,7 0,7 0,7 3.18,3.5 3.18,3.5 3.18,3.5 0,0 0,0 0,0 1.81,0 1.81,0 1.81,0 5,3.5 5,3.5 5,3.5 1.81,7 1.81,7z - DataContext = null - Name = ArrowRight - Margin = 3,3,3,3 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - Rectangle - DataContext = null - Name = RightBottomCorner - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- --------------------------------------------------------------- + + + VariableGridHost + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextBlock + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MatrixView + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rectangle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + VisualGrid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rectangle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + VisualGrid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + VisualGrid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ScrollBar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RepeatButton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Path + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Track + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RepeatButton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rectangle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RepeatButton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rectangle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thumb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rectangle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RepeatButton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Path + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rectangle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ScrollBar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RepeatButton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Path + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Track + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RepeatButton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rectangle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RepeatButton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rectangle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thumb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rectangle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RepeatButton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Path + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rectangle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Package/TestApp/Files/VariableExplorer02.tree b/src/Package/TestApp/Files/VariableExplorer02.tree index a6070542f..cf60edd88 100644 --- a/src/Package/TestApp/Files/VariableExplorer02.tree +++ b/src/Package/TestApp/Files/VariableExplorer02.tree @@ -1,1686 +1,20118 @@ -VariableView -Content = System.Windows.Controls.Grid -ContentTemplate = null -Padding = 0,0,0,0 -DataContext = null -Name = -Margin = 0,0,0,0 -HorizontalAlignment = Stretch -VerticalAlignment = Stretch -ToolTip = null -ContextMenu = null -Visibility = Visible -IsEnabled = True -IsVisible = True -IsEnabled = True -IsEnabled = True -TextAlignment = Left -IsVirtualizing = True -HorizontalScrollBarVisibility = Disabled -VerticalScrollBarVisibility = Visible -Name = --------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = System.Windows.Controls.Grid - ContentTemplate = null - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Grid - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - TextBlock - Text = No R Interactive - Padding = 0,0,0,0 - TextAlignment = Left - DataContext = null - Name = EnvironmentName - Margin = 4,4,4,4 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - TreeGrid - HorizontalScrollBarVisibility = Auto - VerticalScrollBarVisibility = Visible - Padding = 0,0,0,0 - DataContext = null - Name = RootTreeGrid - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ScrollViewer - HorizontalScrollBarVisibility = Auto - VerticalScrollBarVisibility = Visible - Content = System.Windows.Controls.ItemsPresenter - ContentTemplate = null - Padding = 0,0,0,0 - DataContext = null - Name = DG_ScrollViewer - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - Name = - -------------------------------------------------------------- - Grid - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Button - Content = null - ContentTemplate = null - Padding = 1,1,1,1 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Collapsed - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Grid - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Rectangle - DataContext = null - Name = Border - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - Polygon - DataContext = null - Name = Arrow - Margin = 8,8,3,3 - HorizontalAlignment = Right - VerticalAlignment = Bottom - ToolTip = null - ContextMenu = null - Visibility = Collapsed - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - DataGridColumnHeadersPresenter - Padding = 0,0,0,0 - DataContext = null - Name = PART_ColumnHeadersPresenter - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = False - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Grid - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - DataGridColumnHeader - Content = null - ContentTemplate = null - Padding = 4,4,4,4 - DataContext = null - Name = PART_FillerColumnHeader - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Grid - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - DataGridHeaderBorder - Padding = 4,4,4,4 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = null - ContentTemplate = null - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Left - VerticalAlignment = Center - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Thumb - Padding = 0,0,0,0 - DataContext = null - Name = PART_LeftHeaderGripper - Margin = 0,0,0,0 - HorizontalAlignment = Left - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Thumb - Padding = 0,0,0,0 - DataContext = null - Name = PART_RightHeaderGripper - Margin = 0,0,0,0 - HorizontalAlignment = Right - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - ItemsPresenter - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - DataGridCellsPanel - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - DataGridColumnHeader - Content = Name - ContentTemplate = null - Padding = 4,4,4,4 - DataContext = Name - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Grid - DataContext = Name - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - DataGridHeaderBorder - Padding = 4,4,4,4 - DataContext = Name - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = Name - ContentTemplate = null - DataContext = Name - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Left - VerticalAlignment = Center - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - TextBlock - Text = Name - Padding = 0,0,0,0 - TextAlignment = Left - DataContext = Name - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - Thumb - Padding = 0,0,0,0 - DataContext = Name - Name = PART_LeftHeaderGripper - Margin = 0,0,0,0 - HorizontalAlignment = Left - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Collapsed - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - Thumb - Padding = 0,0,0,0 - DataContext = Name - Name = PART_RightHeaderGripper - Margin = 0,0,0,0 - HorizontalAlignment = Right - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = Name - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - DataGridColumnHeader - Content = Value - ContentTemplate = null - Padding = 4,4,4,4 - DataContext = Value - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Grid - DataContext = Value - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - DataGridHeaderBorder - Padding = 4,4,4,4 - DataContext = Value - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = Value - ContentTemplate = null - DataContext = Value - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Left - VerticalAlignment = Center - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - TextBlock - Text = Value - Padding = 0,0,0,0 - TextAlignment = Left - DataContext = Value - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - Thumb - Padding = 0,0,0,0 - DataContext = Value - Name = PART_LeftHeaderGripper - Margin = 0,0,0,0 - HorizontalAlignment = Left - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = Value - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Thumb - Padding = 0,0,0,0 - DataContext = Value - Name = PART_RightHeaderGripper - Margin = 0,0,0,0 - HorizontalAlignment = Right - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = Value - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - DataGridColumnHeader - Content = Class - ContentTemplate = null - Padding = 4,4,4,4 - DataContext = Class - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Grid - DataContext = Class - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - DataGridHeaderBorder - Padding = 4,4,4,4 - DataContext = Class - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = Class - ContentTemplate = null - DataContext = Class - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Left - VerticalAlignment = Center - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - TextBlock - Text = Class - Padding = 0,0,0,0 - TextAlignment = Left - DataContext = Class - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - Thumb - Padding = 0,0,0,0 - DataContext = Class - Name = PART_LeftHeaderGripper - Margin = 0,0,0,0 - HorizontalAlignment = Left - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = Class - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Thumb - Padding = 0,0,0,0 - DataContext = Class - Name = PART_RightHeaderGripper - Margin = 0,0,0,0 - HorizontalAlignment = Right - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = Class - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - DataGridColumnHeader - Content = Type - ContentTemplate = null - Padding = 4,4,4,4 - DataContext = Type - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Grid - DataContext = Type - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - DataGridHeaderBorder - Padding = 4,4,4,4 - DataContext = Type - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = Type - ContentTemplate = null - DataContext = Type - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Left - VerticalAlignment = Center - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - TextBlock - Text = Type - Padding = 0,0,0,0 - TextAlignment = Left - DataContext = Type - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - Thumb - Padding = 0,0,0,0 - DataContext = Type - Name = PART_LeftHeaderGripper - Margin = 0,0,0,0 - HorizontalAlignment = Left - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = Type - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Thumb - Padding = 0,0,0,0 - DataContext = Type - Name = PART_RightHeaderGripper - Margin = 0,0,0,0 - HorizontalAlignment = Right - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = Type - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - ScrollContentPresenter - Content = System.Windows.Controls.ItemsPresenter - ContentTemplate = null - DataContext = null - Name = PART_ScrollContentPresenter - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ItemsPresenter - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - DataGridRowsPresenter - DataContext = null - Name = PART_RowsPresenter - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - AdornerLayer - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ScrollBar - Padding = 0,0,0,0 - DataContext = null - Name = PART_VerticalScrollBar - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Grid - DataContext = null - Name = Bg - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - RepeatButton - Content = System.Windows.Shapes.Path - ContentTemplate = null - Padding = 1,1,1,1 - DataContext = null - Name = PART_LineUpButton - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = null - Name = border - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = System.Windows.Shapes.Path - ContentTemplate = null - DataContext = null - Name = contentPresenter - Margin = 1,1,1,1 - HorizontalAlignment = Center - VerticalAlignment = Center - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Path - Data = M0,4C0,4 0,6 0,6 0,6 3.5,2.5 3.5,2.5 3.5,2.5 7,6 7,6 7,6 7,4 7,4 7,4 3.5,0.5 3.5,0.5 3.5,0.5 0,4 0,4z - DataContext = null - Name = ArrowTop - Margin = 3,4,3,3 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - Track - DataContext = null - Name = PART_Track - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Hidden - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - RepeatButton - Content = null - ContentTemplate = null - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - RepeatButton - Content = null - ContentTemplate = null - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - Thumb - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Rectangle - DataContext = null - Name = rectangle - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - RepeatButton - Content = System.Windows.Shapes.Path - ContentTemplate = null - Padding = 1,1,1,1 - DataContext = null - Name = PART_LineDownButton - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = null - Name = border - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = System.Windows.Shapes.Path - ContentTemplate = null - DataContext = null - Name = contentPresenter - Margin = 1,1,1,1 - HorizontalAlignment = Center - VerticalAlignment = Center - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Path - Data = M0,2.5C0,2.5 0,0.5 0,0.5 0,0.5 3.5,4 3.5,4 3.5,4 7,0.5 7,0.5 7,0.5 7,2.5 7,2.5 7,2.5 3.5,6 3.5,6 3.5,6 0,2.5 0,2.5z - DataContext = null - Name = ArrowBottom - Margin = 3,4,3,3 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - Grid - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ScrollBar - Padding = 0,0,0,0 - DataContext = null - Name = PART_HorizontalScrollBar - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Collapsed - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- --------------------------------------------------------------- + + + VariableView + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextBlock + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TreeGrid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ScrollViewer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Button + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rectangle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Polygon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridColumnHeadersPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridColumnHeader + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridHeaderBorder + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thumb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thumb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ItemsPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridCellsPanel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridColumnHeader + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridHeaderBorder + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextBlock + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thumb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thumb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridColumnHeader + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridHeaderBorder + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextBlock + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thumb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thumb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridColumnHeader + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridHeaderBorder + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextBlock + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thumb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thumb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridColumnHeader + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridHeaderBorder + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextBlock + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thumb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thumb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ScrollContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ItemsPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridRowsPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AdornerLayer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ScrollBar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RepeatButton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Path + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Track + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RepeatButton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RepeatButton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thumb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rectangle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RepeatButton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Path + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ScrollBar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Package/TestApp/Files/VariableExplorer03.tree b/src/Package/TestApp/Files/VariableExplorer03.tree index 634662319..b92bc063e 100644 --- a/src/Package/TestApp/Files/VariableExplorer03.tree +++ b/src/Package/TestApp/Files/VariableExplorer03.tree @@ -1,3897 +1,34766 @@ -VariableView -Content = System.Windows.Controls.Grid -ContentTemplate = null -Padding = 0,0,0,0 -DataContext = null -Name = -Margin = 0,0,0,0 -HorizontalAlignment = Stretch -VerticalAlignment = Stretch -ToolTip = null -ContextMenu = null -Visibility = Visible -IsEnabled = True -IsVisible = True -IsEnabled = True -IsEnabled = True -TextAlignment = Left -IsVirtualizing = True -HorizontalScrollBarVisibility = Disabled -VerticalScrollBarVisibility = Visible -Name = --------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = System.Windows.Controls.Grid - ContentTemplate = null - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Grid - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - TextBlock - Text = Global Environment - Padding = 0,0,0,0 - TextAlignment = Left - DataContext = null - Name = EnvironmentName - Margin = 4,4,4,4 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - TreeGrid - HorizontalScrollBarVisibility = Auto - VerticalScrollBarVisibility = Visible - Padding = 0,0,0,0 - DataContext = null - Name = RootTreeGrid - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ScrollViewer - HorizontalScrollBarVisibility = Auto - VerticalScrollBarVisibility = Visible - Content = System.Windows.Controls.ItemsPresenter - ContentTemplate = null - Padding = 0,0,0,0 - DataContext = null - Name = DG_ScrollViewer - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - Name = - -------------------------------------------------------------- - Grid - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Button - Content = null - ContentTemplate = null - Padding = 1,1,1,1 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Collapsed - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Grid - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Rectangle - DataContext = null - Name = Border - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - Polygon - DataContext = null - Name = Arrow - Margin = 8,8,3,3 - HorizontalAlignment = Right - VerticalAlignment = Bottom - ToolTip = null - ContextMenu = null - Visibility = Collapsed - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - DataGridColumnHeadersPresenter - Padding = 0,0,0,0 - DataContext = null - Name = PART_ColumnHeadersPresenter - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = False - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Grid - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - DataGridColumnHeader - Content = null - ContentTemplate = null - Padding = 4,4,4,4 - DataContext = null - Name = PART_FillerColumnHeader - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Grid - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - DataGridHeaderBorder - Padding = 4,4,4,4 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = null - ContentTemplate = null - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Left - VerticalAlignment = Center - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Thumb - Padding = 0,0,0,0 - DataContext = null - Name = PART_LeftHeaderGripper - Margin = 0,0,0,0 - HorizontalAlignment = Left - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Thumb - Padding = 0,0,0,0 - DataContext = null - Name = PART_RightHeaderGripper - Margin = 0,0,0,0 - HorizontalAlignment = Right - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - ItemsPresenter - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - DataGridCellsPanel - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - DataGridColumnHeader - Content = Name - ContentTemplate = null - Padding = 4,4,4,4 - DataContext = Name - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Grid - DataContext = Name - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - DataGridHeaderBorder - Padding = 4,4,4,4 - DataContext = Name - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = Name - ContentTemplate = null - DataContext = Name - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Left - VerticalAlignment = Center - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - TextBlock - Text = Name - Padding = 0,0,0,0 - TextAlignment = Left - DataContext = Name - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - Thumb - Padding = 0,0,0,0 - DataContext = Name - Name = PART_LeftHeaderGripper - Margin = 0,0,0,0 - HorizontalAlignment = Left - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Collapsed - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - Thumb - Padding = 0,0,0,0 - DataContext = Name - Name = PART_RightHeaderGripper - Margin = 0,0,0,0 - HorizontalAlignment = Right - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = Name - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - DataGridColumnHeader - Content = Value - ContentTemplate = null - Padding = 4,4,4,4 - DataContext = Value - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Grid - DataContext = Value - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - DataGridHeaderBorder - Padding = 4,4,4,4 - DataContext = Value - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = Value - ContentTemplate = null - DataContext = Value - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Left - VerticalAlignment = Center - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - TextBlock - Text = Value - Padding = 0,0,0,0 - TextAlignment = Left - DataContext = Value - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - Thumb - Padding = 0,0,0,0 - DataContext = Value - Name = PART_LeftHeaderGripper - Margin = 0,0,0,0 - HorizontalAlignment = Left - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = Value - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Thumb - Padding = 0,0,0,0 - DataContext = Value - Name = PART_RightHeaderGripper - Margin = 0,0,0,0 - HorizontalAlignment = Right - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = Value - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - DataGridColumnHeader - Content = Class - ContentTemplate = null - Padding = 4,4,4,4 - DataContext = Class - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Grid - DataContext = Class - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - DataGridHeaderBorder - Padding = 4,4,4,4 - DataContext = Class - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = Class - ContentTemplate = null - DataContext = Class - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Left - VerticalAlignment = Center - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - TextBlock - Text = Class - Padding = 0,0,0,0 - TextAlignment = Left - DataContext = Class - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - Thumb - Padding = 0,0,0,0 - DataContext = Class - Name = PART_LeftHeaderGripper - Margin = 0,0,0,0 - HorizontalAlignment = Left - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = Class - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Thumb - Padding = 0,0,0,0 - DataContext = Class - Name = PART_RightHeaderGripper - Margin = 0,0,0,0 - HorizontalAlignment = Right - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = Class - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - DataGridColumnHeader - Content = Type - ContentTemplate = null - Padding = 4,4,4,4 - DataContext = Type - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Grid - DataContext = Type - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - DataGridHeaderBorder - Padding = 4,4,4,4 - DataContext = Type - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = Type - ContentTemplate = null - DataContext = Type - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Left - VerticalAlignment = Center - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - TextBlock - Text = Type - Padding = 0,0,0,0 - TextAlignment = Left - DataContext = Type - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - Thumb - Padding = 0,0,0,0 - DataContext = Type - Name = PART_LeftHeaderGripper - Margin = 0,0,0,0 - HorizontalAlignment = Left - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = Type - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Thumb - Padding = 0,0,0,0 - DataContext = Type - Name = PART_RightHeaderGripper - Margin = 0,0,0,0 - HorizontalAlignment = Right - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = Type - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - ScrollContentPresenter - Content = System.Windows.Controls.ItemsPresenter - ContentTemplate = null - DataContext = null - Name = PART_ScrollContentPresenter - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ItemsPresenter - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - DataGridRowsPresenter - DataContext = null - Name = PART_RowsPresenter - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - DataGridRow - Padding = 0,0,0,0 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = DGR_Border - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - SelectiveScrollingGrid - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - DataGridCellsPresenter - Padding = 0,0,0,0 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = False - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ItemsPresenter - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - DataGridCellsPanel - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - DataGridCell - Content = System.Windows.Controls.ContentPresenter - ContentTemplate = null - Padding = 1,1,1,1 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 1,1,1,1 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = System.Windows.Controls.ContentPresenter - ContentTemplate = null - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - ContentTemplate = System.Windows.DataTemplate - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Grid - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Rectangle - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = Indentation - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - ToggleButton - Content = null - ContentTemplate = null - Padding = 1,1,1,1 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = Expander - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 5,5,5,5 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Path - Data = M0,0L0,6 6,0z - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = ExpandPath - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - TextBlock - Text = x - Padding = 0,0,0,0 - TextAlignment = Left - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - DataGridCell - Content = System.Windows.Controls.ContentPresenter - ContentTemplate = null - Padding = 1,1,1,1 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 1,1,1,1 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = System.Windows.Controls.ContentPresenter - ContentTemplate = null - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - ContentTemplate = System.Windows.DataTemplate - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Grid - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - TextBlock - Text = int [1:10] 1 2 3 4 5 6 7 8 9 10 - Padding = 0,0,0,0 - TextAlignment = Left - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - Button - Content = Microsoft.VisualStudio.Imaging.CrispImage - ContentTemplate = null - Padding = 1,1,1,1 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Right - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Collapsed - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = border - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = Microsoft.VisualStudio.Imaging.CrispImage - ContentTemplate = null - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = contentPresenter - Margin = 1,1,1,1 - HorizontalAlignment = Center - VerticalAlignment = Center - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - CrispImage - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - DataGridCell - Content = System.Windows.Controls.TextBlock - ContentTemplate = null - Padding = 1,1,1,1 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 1,1,1,1 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = System.Windows.Controls.TextBlock - ContentTemplate = null - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - TextBlock - Text = integer - Padding = 0,0,0,0 - TextAlignment = Left - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 2,0,2,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - DataGridCell - Content = System.Windows.Controls.TextBlock - ContentTemplate = null - Padding = 1,1,1,1 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 1,1,1,1 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = System.Windows.Controls.TextBlock - ContentTemplate = null - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - TextBlock - Text = integer - Padding = 0,0,0,0 - TextAlignment = Left - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 2,0,2,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - DataGridDetailsPresenter - Content = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - ContentTemplate = null - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Collapsed - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - DataGridRowHeader - Content = null - ContentTemplate = null - Padding = 0,0,0,0 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Collapsed - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Grid - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - DataGridHeaderBorder - Padding = 0,0,0,0 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - StackPanel - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = null - ContentTemplate = null - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Center - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - Control - Padding = 0,0,0,0 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Collapsed - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - Thumb - Padding = 0,0,0,0 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = PART_TopHeaderGripper - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Top - ToolTip = null - ContextMenu = null - Visibility = Collapsed - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - Thumb - Padding = 0,0,0,0 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = PART_BottomHeaderGripper - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Bottom - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - DataGridRow - Padding = 0,0,0,0 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = DGR_Border - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - SelectiveScrollingGrid - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - DataGridCellsPresenter - Padding = 0,0,0,0 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = False - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ItemsPresenter - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - DataGridCellsPanel - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - DataGridCell - Content = System.Windows.Controls.ContentPresenter - ContentTemplate = null - Padding = 1,1,1,1 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 1,1,1,1 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = System.Windows.Controls.ContentPresenter - ContentTemplate = null - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - ContentTemplate = System.Windows.DataTemplate - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Grid - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Rectangle - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = Indentation - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - ToggleButton - Content = null - ContentTemplate = null - Padding = 1,1,1,1 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = Expander - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Hidden - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 5,5,5,5 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Path - Data = M0,0L0,6 6,0z - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = ExpandPath - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - TextBlock - Text = xaml - Padding = 0,0,0,0 - TextAlignment = Left - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - DataGridCell - Content = System.Windows.Controls.ContentPresenter - ContentTemplate = null - Padding = 1,1,1,1 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 1,1,1,1 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = System.Windows.Controls.ContentPresenter - ContentTemplate = null - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - ContentTemplate = System.Windows.DataTemplate - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Grid - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - TextBlock - Text = function (filename, width, height) - Padding = 0,0,0,0 - TextAlignment = Left - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - Button - Content = Microsoft.VisualStudio.Imaging.CrispImage - ContentTemplate = null - Padding = 1,1,1,1 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Right - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Collapsed - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = border - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = Microsoft.VisualStudio.Imaging.CrispImage - ContentTemplate = null - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = contentPresenter - Margin = 1,1,1,1 - HorizontalAlignment = Center - VerticalAlignment = Center - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - CrispImage - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - DataGridCell - Content = System.Windows.Controls.TextBlock - ContentTemplate = null - Padding = 1,1,1,1 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 1,1,1,1 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = System.Windows.Controls.TextBlock - ContentTemplate = null - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - TextBlock - Text = function - Padding = 0,0,0,0 - TextAlignment = Left - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 2,0,2,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - DataGridCell - Content = System.Windows.Controls.TextBlock - ContentTemplate = null - Padding = 1,1,1,1 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 1,1,1,1 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = System.Windows.Controls.TextBlock - ContentTemplate = null - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - TextBlock - Text = closure - Padding = 0,0,0,0 - TextAlignment = Left - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 2,0,2,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - DataGridDetailsPresenter - Content = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - ContentTemplate = null - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Collapsed - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - DataGridRowHeader - Content = null - ContentTemplate = null - Padding = 0,0,0,0 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Collapsed - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Grid - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - DataGridHeaderBorder - Padding = 0,0,0,0 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - StackPanel - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = null - ContentTemplate = null - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Center - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - Control - Padding = 0,0,0,0 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Collapsed - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - Thumb - Padding = 0,0,0,0 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = PART_TopHeaderGripper - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Top - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Thumb - Padding = 0,0,0,0 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = PART_BottomHeaderGripper - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Bottom - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = Microsoft.VisualStudio.R.Package.DataInspect.ObservableTreeNode - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - AdornerLayer - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ScrollBar - Padding = 0,0,0,0 - DataContext = null - Name = PART_VerticalScrollBar - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Grid - DataContext = null - Name = Bg - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - RepeatButton - Content = System.Windows.Shapes.Path - ContentTemplate = null - Padding = 1,1,1,1 - DataContext = null - Name = PART_LineUpButton - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = null - Name = border - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = System.Windows.Shapes.Path - ContentTemplate = null - DataContext = null - Name = contentPresenter - Margin = 1,1,1,1 - HorizontalAlignment = Center - VerticalAlignment = Center - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Path - Data = M0,4C0,4 0,6 0,6 0,6 3.5,2.5 3.5,2.5 3.5,2.5 7,6 7,6 7,6 7,4 7,4 7,4 3.5,0.5 3.5,0.5 3.5,0.5 0,4 0,4z - DataContext = null - Name = ArrowTop - Margin = 3,4,3,3 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - Track - DataContext = null - Name = PART_Track - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Hidden - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - RepeatButton - Content = null - ContentTemplate = null - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - RepeatButton - Content = null - ContentTemplate = null - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - Thumb - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Rectangle - DataContext = null - Name = rectangle - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - RepeatButton - Content = System.Windows.Shapes.Path - ContentTemplate = null - Padding = 1,1,1,1 - DataContext = null - Name = PART_LineDownButton - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = null - Name = border - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = System.Windows.Shapes.Path - ContentTemplate = null - DataContext = null - Name = contentPresenter - Margin = 1,1,1,1 - HorizontalAlignment = Center - VerticalAlignment = Center - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Path - Data = M0,2.5C0,2.5 0,0.5 0,0.5 0,0.5 3.5,4 3.5,4 3.5,4 7,0.5 7,0.5 7,0.5 7,2.5 7,2.5 7,2.5 3.5,6 3.5,6 3.5,6 0,2.5 0,2.5z - DataContext = null - Name = ArrowBottom - Margin = 3,4,3,3 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - Grid - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = True - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ScrollBar - Padding = 0,0,0,0 - DataContext = null - Name = PART_HorizontalScrollBar - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Collapsed - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Grid - DataContext = null - Name = Bg - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = True - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - RepeatButton - Content = System.Windows.Shapes.Path - ContentTemplate = null - Padding = 1,1,1,1 - DataContext = null - Name = PART_LineLeftButton - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = null - Name = border - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = System.Windows.Shapes.Path - ContentTemplate = null - DataContext = null - Name = contentPresenter - Margin = 1,1,1,1 - HorizontalAlignment = Center - VerticalAlignment = Center - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Path - Data = M3.18,7C3.18,7 5,7 5,7 5,7 1.81,3.5 1.81,3.5 1.81,3.5 5,0 5,0 5,0 3.18,0 3.18,0 3.18,0 0,3.5 0,3.5 0,3.5 3.18,7 3.18,7z - DataContext = null - Name = ArrowLeft - Margin = 3,3,3,3 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - Track - DataContext = null - Name = PART_Track - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - RepeatButton - Content = null - ContentTemplate = null - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Rectangle - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - RepeatButton - Content = null - ContentTemplate = null - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Rectangle - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Thumb - Padding = 0,0,0,0 - DataContext = null - Name = - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Rectangle - DataContext = null - Name = rectangle - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - RepeatButton - Content = System.Windows.Shapes.Path - ContentTemplate = null - Padding = 1,1,1,1 - DataContext = null - Name = PART_LineRightButton - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Border - Padding = 0,0,0,0 - DataContext = null - Name = border - Margin = 0,0,0,0 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - ContentPresenter - Content = System.Windows.Shapes.Path - ContentTemplate = null - DataContext = null - Name = contentPresenter - Margin = 1,1,1,1 - HorizontalAlignment = Center - VerticalAlignment = Center - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - Path - Data = M1.81,7C1.81,7 0,7 0,7 0,7 3.18,3.5 3.18,3.5 3.18,3.5 0,0 0,0 0,0 1.81,0 1.81,0 1.81,0 5,3.5 5,3.5 5,3.5 1.81,7 1.81,7z - DataContext = null - Name = ArrowRight - Margin = 3,3,3,3 - HorizontalAlignment = Stretch - VerticalAlignment = Stretch - ToolTip = null - ContextMenu = null - Visibility = Visible - IsEnabled = False - IsVisible = False - IsEnabled = True - IsEnabled = True - TextAlignment = Left - IsVirtualizing = True - HorizontalScrollBarVisibility = Disabled - VerticalScrollBarVisibility = Visible - Name = - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- - -------------------------------------------------------------- --------------------------------------------------------------- + + + VariableView + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextBlock + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TreeGrid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ScrollViewer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Button + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rectangle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Polygon + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridColumnHeadersPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridColumnHeader + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridHeaderBorder + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thumb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thumb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ItemsPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridCellsPanel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridColumnHeader + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridHeaderBorder + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextBlock + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thumb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thumb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridColumnHeader + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridHeaderBorder + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextBlock + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thumb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thumb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridColumnHeader + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridHeaderBorder + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextBlock + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thumb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thumb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridColumnHeader + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridHeaderBorder + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextBlock + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thumb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thumb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ScrollContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ItemsPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridRowsPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridRow + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + SelectiveScrollingGrid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridCellsPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ItemsPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridCellsPanel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridCell + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rectangle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ToggleButton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Path + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextBlock + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridCell + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextBlock + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + StackPanel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Button + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CrispImage + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridCell + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextBlock + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridCell + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextBlock + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridDetailsPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridRowHeader + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DataGridHeaderBorder + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + StackPanel + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Control + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thumb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thumb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + AdornerLayer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ScrollBar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RepeatButton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Path + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Track + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RepeatButton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RepeatButton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thumb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rectangle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RepeatButton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Path + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ScrollBar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RepeatButton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Path + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Track + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RepeatButton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rectangle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RepeatButton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rectangle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thumb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rectangle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RepeatButton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Path + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Package/TestApp/Files/VariableGrid02.tree b/src/Package/TestApp/Files/VariableGrid02.tree new file mode 100644 index 000000000..96267b686 --- /dev/null +++ b/src/Package/TestApp/Files/VariableGrid02.tree @@ -0,0 +1,15916 @@ + + + VariableGridHost + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextBlock + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + MatrixView + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rectangle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + VisualGrid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + GridLineVisual + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextVisual + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextVisual + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextVisual + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextVisual + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextVisual + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rectangle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + VisualGrid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + GridLineVisual + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextVisual + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextVisual + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + VisualGrid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + GridLineVisual + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextVisual + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextVisual + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextVisual + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextVisual + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextVisual + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextVisual + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextVisual + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextVisual + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextVisual + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + TextVisual + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ScrollBar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RepeatButton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Path + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Track + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RepeatButton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rectangle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RepeatButton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rectangle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thumb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rectangle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RepeatButton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Path + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rectangle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ScrollBar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Grid + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RepeatButton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Path + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Track + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RepeatButton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rectangle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RepeatButton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rectangle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Thumb + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rectangle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + RepeatButton + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Border + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ContentPresenter + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Path + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Rectangle + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Package/TestApp/Help/HelpOnCurrentTest.cs b/src/Package/TestApp/Help/HelpOnCurrentTest.cs new file mode 100644 index 000000000..3ca19d9a6 --- /dev/null +++ b/src/Package/TestApp/Help/HelpOnCurrentTest.cs @@ -0,0 +1,63 @@ +using System.Diagnostics.CodeAnalysis; +using FluentAssertions; +using Microsoft.Common.Core.Test.Controls; +using Microsoft.R.Editor.ContentType; +using Microsoft.R.Host.Client; +using Microsoft.R.Host.Client.Test.Script; +using Microsoft.UnitTests.Core.XUnit; +using Microsoft.VisualStudio.R.Package.Help; +using Microsoft.VisualStudio.R.Package.Shell; +using Microsoft.VisualStudio.R.Package.Test.Mocks; +using Microsoft.VisualStudio.Text; +using Xunit; + +namespace Microsoft.VisualStudio.R.Interactive.Test.Help { + [ExcludeFromCodeCoverage] + [Collection(CollectionNames.NonParallel)] + public class HelpOnCurrentTest : InteractiveTest { + [Test] + [Category.Interactive] + public void HelpTest() { + var clientApp = new RHostClientHelpTestApp(); + var sessionProvider = VsAppShell.Current.ExportProvider.GetExportedValue(); + using (var hostScript = new RHostScript(sessionProvider, clientApp)) { + using (var script = new ControlTestScript(typeof(HelpWindowVisualComponent))) { + DoIdle(100); + + var component = ControlWindow.Component as IHelpWindowVisualComponent; + component.Should().NotBeNull(); + clientApp.Component = component; + + var viewTracker = new ActiveTextViewTrackerMock(" plot", RContentTypeDefinition.ContentType); + var view = viewTracker.GetLastActiveTextView(RContentTypeDefinition.ContentType); + var cmd = new ShowHelpOnCurrentCommand(sessionProvider, viewTracker); + + cmd.SetStatus(); + cmd.Visible.Should().BeTrue(); + cmd.Enabled.Should().BeFalse(); + + view.Caret.MoveTo(new SnapshotPoint(view.TextBuffer.CurrentSnapshot, 3)); + + cmd.SetStatus(); + cmd.Visible.Should().BeTrue(); + cmd.Enabled.Should().BeTrue(); + cmd.Text.Should().EndWith("plot"); + + cmd.Handle(); + WaitForAppReady(clientApp); + + clientApp.Uri.IsLoopback.Should().Be(true); + clientApp.Uri.PathAndQuery.Should().Be("/library/graphics/html/plot.html"); + + DoIdle(500); + } + } + } + + private void WaitForAppReady(RHostClientHelpTestApp clientApp) { + for (int i = 0; i < 100 && !clientApp.Ready; i++) { + DoIdle(200); + } + } + } +} diff --git a/src/Package/TestApp/Help/HelpWindowTest.cs b/src/Package/TestApp/Help/HelpWindowTest.cs new file mode 100644 index 000000000..eeb1130f5 --- /dev/null +++ b/src/Package/TestApp/Help/HelpWindowTest.cs @@ -0,0 +1,73 @@ +using System.Diagnostics.CodeAnalysis; +using FluentAssertions; +using Microsoft.Common.Core; +using Microsoft.Common.Core.Test.Controls; +using Microsoft.R.Host.Client; +using Microsoft.UnitTests.Core.Threading; +using Microsoft.UnitTests.Core.XUnit; +using Microsoft.VisualStudio.R.Package.Commands; +using Microsoft.VisualStudio.R.Package.Help; +using Microsoft.VisualStudio.R.Package.Test.Utility; +using Microsoft.VisualStudio.R.Packages.R; +using Xunit; + +namespace Microsoft.VisualStudio.R.Interactive.Test.Help { + [ExcludeFromCodeCoverage] + [Collection(CollectionNames.NonParallel)] + public class HelpWindowTest : InteractiveTest { + [Test] + [Category.Interactive] + public void HelpTest() { + var clientApp = new RHostClientHelpTestApp(); + using (var hostScript = new VsRHostScript(clientApp)) { + using (var script = new ControlTestScript(typeof(HelpWindowVisualComponent))) { + DoIdle(100); + + var component = ControlWindow.Component as IHelpWindowVisualComponent; + component.Should().NotBeNull(); + + clientApp.Component = component; + + ShowHelp("?plot\n", hostScript, clientApp); + clientApp.Uri.IsLoopback.Should().Be(true); + clientApp.Uri.PathAndQuery.Should().Be("/library/graphics/html/plot.html"); + + ShowHelp("?lm\n", hostScript, clientApp); + clientApp.Uri.PathAndQuery.Should().Be("/library/stats/html/lm.html"); + + ExecCommand(clientApp, RPackageCommandId.icmdHelpPrevious); + clientApp.Uri.PathAndQuery.Should().Be("/library/graphics/html/plot.html"); + + ExecCommand(clientApp, RPackageCommandId.icmdHelpNext); + clientApp.Uri.PathAndQuery.Should().Be("/library/stats/html/lm.html"); + + ExecCommand(clientApp, RPackageCommandId.icmdHelpHome); + clientApp.Uri.PathAndQuery.Should().Be("/doc/html/index.html"); + } + } + } + + private void ShowHelp(string command, VsRHostScript hostScript, RHostClientHelpTestApp clientApp) { + clientApp.Ready = false; + using (var request = hostScript.Session.BeginInteractionAsync().Result) { + request.RespondAsync(command).SilenceException(); + } + WaitForAppReady(clientApp); + } + + private void ExecCommand(RHostClientHelpTestApp clientApp, int commandId) { + UIThreadHelper.Instance.Invoke(() => { + clientApp.Ready = false; + object o = new object(); + clientApp.Component.Controller.Invoke(RGuidList.RCmdSetGuid, commandId, null, ref o); + }); + WaitForAppReady(clientApp); + } + + private void WaitForAppReady(RHostClientHelpTestApp clientApp) { + for (int i = 0; i < 100 && !clientApp.Ready; i++) { + DoIdle(200); + } + } + } +} diff --git a/src/Package/TestApp/Help/RHostClientHelpTestApp.cs b/src/Package/TestApp/Help/RHostClientHelpTestApp.cs new file mode 100644 index 000000000..bf3ae3df5 --- /dev/null +++ b/src/Package/TestApp/Help/RHostClientHelpTestApp.cs @@ -0,0 +1,32 @@ +using System; +using System.Threading.Tasks; +using System.Windows.Forms; +using Microsoft.R.Host.Client.Test.Script; +using Microsoft.UnitTests.Core.Threading; +using Microsoft.VisualStudio.R.Package.Help; + +namespace Microsoft.VisualStudio.R.Interactive.Test.Help { + class RHostClientHelpTestApp : RHostClientTestApp { + IHelpWindowVisualComponent _component; + public IHelpWindowVisualComponent Component { + get { return _component; } + set { + _component = value; + _component.Browser.Navigated += Browser_Navigated; + } + } + public bool Ready { get; set; } + public Uri Uri { get; private set; } + private void Browser_Navigated(object sender, WebBrowserNavigatedEventArgs e) { + Ready = true; + Uri = _component.Browser.Url; + } + + public override Task ShowHelp(string url) { + UIThreadHelper.Instance.Invoke(() => { + Component.Navigate(url); + }); + return Task.CompletedTask; + } + } +} diff --git a/src/Package/TestApp/InteractiveTest.cs b/src/Package/TestApp/InteractiveTest.cs new file mode 100644 index 000000000..f5b0c67a6 --- /dev/null +++ b/src/Package/TestApp/InteractiveTest.cs @@ -0,0 +1,18 @@ +using System.Threading; +using Microsoft.UnitTests.Core.Threading; +using Microsoft.VisualStudio.R.Interactive.Test.Utility; + +namespace Microsoft.VisualStudio.R.Interactive.Test { + public class InteractiveTest { + public static void DoIdle(int ms) { + UIThreadHelper.Instance.Invoke(() => { + int time = 0; + while (time < ms) { + IdleTime.DoIdle(); + Thread.Sleep(20); + time += 20; + } + }); + } + } +} diff --git a/src/Package/TestApp/Microsoft.VisualStudio.R.Interactive.Test.csproj b/src/Package/TestApp/Microsoft.VisualStudio.R.Interactive.Test.csproj index 3dd025eef..9fc3034c7 100644 --- a/src/Package/TestApp/Microsoft.VisualStudio.R.Interactive.Test.csproj +++ b/src/Package/TestApp/Microsoft.VisualStudio.R.Interactive.Test.csproj @@ -16,24 +16,12 @@ true - - AnyCPU - true - full - false - ..\..\..\bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - ..\..\..\bin\Release\ - TRACE - prompt - 4 + + + $(RootDirectory)\obj\ + $(RootDirectory)\bin\ + $(BaseIntermediateOutputPath)\$(Configuration)\$(AssemblyName)\ + $(BaseOutputPath)\$(Configuration)\ @@ -47,15 +35,24 @@ ..\..\..\NugetPackages\FluentAssertions.4.1.1\lib\net45\FluentAssertions.Core.dll True + + + + + + + + + ..\..\..\NugetPackages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll True @@ -75,9 +72,16 @@ + + + + + + + @@ -100,6 +104,7 @@ + SettingsSingleFileGenerator @@ -119,6 +124,10 @@ {fc4aad0a-13b9-49ee-a59c-f03142958170} Microsoft.Common.Core.Test + + {17e155bf-351c-4253-b9b1-36eeea35fe3c} + Microsoft.R.Debugger + {e09d3bda-2e6b-47b5-87ac-b6fc2d33dfab} Microsoft.R.Host.Client @@ -131,6 +140,14 @@ {62857e49-e586-4baa-ae4d-1232093e7378} Microsoft.Languages.Editor + + {5fcb86d5-4b25-4039-858c-b5a06eb702e1} + Microsoft.VisualStudio.Editor.Mocks + + + {d6eeef87-ce3a-4804-a409-39966b96c850} + Microsoft.R.Editor + {5EF2AD64-D6FE-446B-B350-8C7F0DF0834D} Microsoft.UnitTests.Core @@ -147,8 +164,15 @@ + + + Microsoft + StrongName + + + + + + + + + Debug + AnyCPU + {41AA8769-0FBC-4A80-8498-21C833F0C2AC} + Library + Properties + Microsoft.VisualStudio.ProjectSystem.FileSystemMirroring + Microsoft.VisualStudio.ProjectSystem.FileSystemMirroring + v4.6 + 512 + + + + + + $(RootDirectory)\obj\ + $(RootDirectory)\bin\ + $(BaseIntermediateOutputPath)\$(Configuration)\$(AssemblyName)\ + $(BaseOutputPath)\$(Configuration)\ + + + + Properties\GlobalAssemblyInfo.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + False + + + ..\..\..\NugetPackages\Microsoft.CodeAnalysis.Common.1.1.1\lib\net45\Microsoft.CodeAnalysis.dll + True + + + ..\..\..\NugetPackages\Microsoft.CodeAnalysis.CSharp.1.1.1\lib\net45\Microsoft.CodeAnalysis.CSharp.dll + True + + + ..\..\..\NugetPackages\Microsoft.VisualStudio.Composition.14.0.50417-pre\lib\net451\Microsoft.VisualStudio.Composition.dll + False + + + ..\..\..\NugetPackages\Microsoft.VisualStudio.Composition.14.0.50417-pre\lib\net451\Microsoft.VisualStudio.Composition.Configuration.dll + False + + + ..\..\..\NugetPackages\Microsoft.VisualStudio.CoreUtility.14.1.24720\lib\net45\Microsoft.VisualStudio.CoreUtility.dll + True + + + ..\..\..\NugetPackages\Microsoft.VisualStudio.OLE.Interop.7.10.6070\lib\Microsoft.VisualStudio.OLE.Interop.dll + True + + + ..\..\..\NugetPackages\Microsoft.VisualStudio.ProjectSystem.14.0.50617-pre\lib\net451\Microsoft.VisualStudio.ProjectSystem.Interop.dll + False + + + ..\..\..\NugetPackages\Microsoft.VisualStudio.ProjectSystem.14.0.50617-pre\lib\net451\Microsoft.VisualStudio.ProjectSystem.Utilities.v14.0.dll + False + + + ..\..\..\NugetPackages\Microsoft.VisualStudio.ProjectSystem.14.0.50617-pre\lib\net451\Microsoft.VisualStudio.ProjectSystem.V14Only.dll + False + + + ..\..\..\NugetPackages\Microsoft.VisualStudio.ProjectSystem.14.0.50617-pre\lib\net451\Microsoft.VisualStudio.ProjectSystem.VS.V14Only.dll + False + + + False + + + False + + + ..\..\..\NugetPackages\Microsoft.VisualStudio.Shell.Interop.7.10.6071\lib\Microsoft.VisualStudio.Shell.Interop.dll + True + + + True + ..\..\..\NugetPackages\Microsoft.VisualStudio.Shell.Interop.10.0.10.0.30319\lib\Microsoft.VisualStudio.Shell.Interop.10.0.dll + True + + + True + ..\..\..\NugetPackages\Microsoft.VisualStudio.Shell.Interop.11.0.11.0.61030\lib\Microsoft.VisualStudio.Shell.Interop.11.0.dll + True + + + ..\..\..\NugetPackages\Microsoft.VisualStudio.Shell.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.Shell.Interop.8.0.dll + True + + + ..\..\..\NugetPackages\Microsoft.VisualStudio.TextManager.Interop.7.10.6070\lib\Microsoft.VisualStudio.TextManager.Interop.dll + True + + + ..\..\..\NugetPackages\Microsoft.VisualStudio.TextManager.Interop.8.0.8.0.50727\lib\Microsoft.VisualStudio.TextManager.Interop.8.0.dll + True + + + ..\..\..\NugetPackages\Microsoft.VisualStudio.Threading.14.1.114\lib\net45\Microsoft.VisualStudio.Threading.dll + True + + + ..\..\..\NugetPackages\Microsoft.VisualStudio.Validation.14.1.111\lib\net45\Microsoft.VisualStudio.Validation.dll + True + + + + ..\..\..\NugetPackages\System.Collections.Immutable.1.1.37\lib\dotnet\System.Collections.Immutable.dll + True + + + + ..\..\..\NugetPackages\Microsoft.Composition.1.0.30\lib\portable-net45+win8+wp8+wpa81\System.Composition.AttributedModel.dll + False + + + ..\..\..\NugetPackages\Microsoft.Composition.1.0.30\lib\portable-net45+win8+wp8+wpa81\System.Composition.Convention.dll + False + + + ..\..\..\NugetPackages\Microsoft.Composition.1.0.30\lib\portable-net45+win8+wp8+wpa81\System.Composition.Hosting.dll + False + + + ..\..\..\NugetPackages\Microsoft.Composition.1.0.30\lib\portable-net45+win8+wp8+wpa81\System.Composition.Runtime.dll + False + + + ..\..\..\NugetPackages\Microsoft.Composition.1.0.30\lib\portable-net45+win8+wp8+wpa81\System.Composition.TypedParts.dll + False + + + ..\..\..\NugetPackages\System.Reflection.Metadata.1.1.0\lib\dotnet5.2\System.Reflection.Metadata.dll + True + + + ..\..\..\NugetPackages\Microsoft.Tpl.Dataflow.4.5.24\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll + False + + + + + + + {8D408909-459F-4853-A36C-745118F99869} + Microsoft.Common.Core + + + {B68D4AD2-2DC2-473E-AB06-408172C4FB92} + Microsoft.R.Actions + + + + + Microsoft + StrongName + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + + \ No newline at end of file diff --git a/src/R.Build.Version.targets b/src/R.Build.Version.targets index 4f88ccced..7fa5300f1 100644 --- a/src/R.Build.Version.targets +++ b/src/R.Build.Version.targets @@ -6,7 +6,7 @@ - + $([System.DateTime]::Now.Year) $([MSBuild]::Subtract($(Year), 4)) $(YearMinus4.ToString()) @@ -14,8 +14,8 @@ $([System.DateTime]::Now.ToString("MMdd")) - 1 - 0 + 0 + 1 $(LastDigitOfYear)$(Date) $(Time) $(Build).$(Revision) diff --git a/src/R.sln b/src/R.sln index 2d1c7a1d3..89862fcd2 100644 --- a/src/R.sln +++ b/src/R.sln @@ -84,6 +84,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.VSIntegrationTests", "UnitTests\Microsoft.VSIntegrationTests\Microsoft.VSIntegrationTests.csproj", "{2BBB937B-EFD3-48AA-BEF8-05E203589D24}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SetupCustomActions", "SetupCustomActions\SetupCustomActions.csproj", "{F2149709-A88B-4F36-ABCA-307CA96E9FD1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.R.Debugger.Test", "Debugger\Test\Microsoft.R.Debugger.Test.csproj", "{6D3AC84F-D53F-4494-8142-DD2BC8FD9CFD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -347,6 +351,7 @@ Global {56534417-6C60-48A5-B355-12225C70431E}.Release|x86.ActiveCfg = Release|Any CPU {56534417-6C60-48A5-B355-12225C70431E}.Release|x86.Build.0 = Release|Any CPU {B86E0BA1-82AF-45F8-9BE8-FDDCB8EF7F13}.Debug|Any CPU.ActiveCfg = Debug|x86 + {B86E0BA1-82AF-45F8-9BE8-FDDCB8EF7F13}.Debug|Any CPU.Build.0 = Debug|x86 {B86E0BA1-82AF-45F8-9BE8-FDDCB8EF7F13}.Debug|x64.ActiveCfg = Debug|x86 {B86E0BA1-82AF-45F8-9BE8-FDDCB8EF7F13}.Debug|x86.ActiveCfg = Debug|x86 {B86E0BA1-82AF-45F8-9BE8-FDDCB8EF7F13}.Release|Any CPU.ActiveCfg = Release|x86 @@ -482,14 +487,14 @@ Global {131842CE-DAF9-4C0E-8409-4A26EF7961A7}.Debug|Any CPU.Build.0 = Debug|x64 {131842CE-DAF9-4C0E-8409-4A26EF7961A7}.Debug|x64.ActiveCfg = Debug|x64 {131842CE-DAF9-4C0E-8409-4A26EF7961A7}.Debug|x64.Build.0 = Debug|x64 - {131842CE-DAF9-4C0E-8409-4A26EF7961A7}.Debug|x86.ActiveCfg = Debug|Win32 - {131842CE-DAF9-4C0E-8409-4A26EF7961A7}.Debug|x86.Build.0 = Debug|Win32 + {131842CE-DAF9-4C0E-8409-4A26EF7961A7}.Debug|x86.ActiveCfg = Debug|x64 + {131842CE-DAF9-4C0E-8409-4A26EF7961A7}.Debug|x86.Build.0 = Debug|x64 {131842CE-DAF9-4C0E-8409-4A26EF7961A7}.Release|Any CPU.ActiveCfg = Release|x64 {131842CE-DAF9-4C0E-8409-4A26EF7961A7}.Release|Any CPU.Build.0 = Release|x64 {131842CE-DAF9-4C0E-8409-4A26EF7961A7}.Release|x64.ActiveCfg = Release|x64 {131842CE-DAF9-4C0E-8409-4A26EF7961A7}.Release|x64.Build.0 = Release|x64 - {131842CE-DAF9-4C0E-8409-4A26EF7961A7}.Release|x86.ActiveCfg = Release|Win32 - {131842CE-DAF9-4C0E-8409-4A26EF7961A7}.Release|x86.Build.0 = Release|Win32 + {131842CE-DAF9-4C0E-8409-4A26EF7961A7}.Release|x86.ActiveCfg = Release|x64 + {131842CE-DAF9-4C0E-8409-4A26EF7961A7}.Release|x86.Build.0 = Release|x64 {0D80F608-CF60-42F8-A5E1-4E0ACFA384AF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0D80F608-CF60-42F8-A5E1-4E0ACFA384AF}.Debug|Any CPU.Build.0 = Debug|Any CPU {0D80F608-CF60-42F8-A5E1-4E0ACFA384AF}.Debug|x64.ActiveCfg = Debug|Any CPU @@ -514,18 +519,30 @@ Global {130C9240-9537-42D4-A62C-2A13815F0020}.Release|x64.Build.0 = Release|Any CPU {130C9240-9537-42D4-A62C-2A13815F0020}.Release|x86.ActiveCfg = Release|Any CPU {130C9240-9537-42D4-A62C-2A13815F0020}.Release|x86.Build.0 = Release|Any CPU - {2BBB937B-EFD3-48AA-BEF8-05E203589D24}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2BBB937B-EFD3-48AA-BEF8-05E203589D24}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2BBB937B-EFD3-48AA-BEF8-05E203589D24}.Debug|x64.ActiveCfg = Debug|Any CPU - {2BBB937B-EFD3-48AA-BEF8-05E203589D24}.Debug|x64.Build.0 = Debug|Any CPU - {2BBB937B-EFD3-48AA-BEF8-05E203589D24}.Debug|x86.ActiveCfg = Debug|Any CPU - {2BBB937B-EFD3-48AA-BEF8-05E203589D24}.Debug|x86.Build.0 = Debug|Any CPU - {2BBB937B-EFD3-48AA-BEF8-05E203589D24}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2BBB937B-EFD3-48AA-BEF8-05E203589D24}.Release|Any CPU.Build.0 = Release|Any CPU - {2BBB937B-EFD3-48AA-BEF8-05E203589D24}.Release|x64.ActiveCfg = Release|Any CPU - {2BBB937B-EFD3-48AA-BEF8-05E203589D24}.Release|x64.Build.0 = Release|Any CPU - {2BBB937B-EFD3-48AA-BEF8-05E203589D24}.Release|x86.ActiveCfg = Release|Any CPU - {2BBB937B-EFD3-48AA-BEF8-05E203589D24}.Release|x86.Build.0 = Release|Any CPU + {F2149709-A88B-4F36-ABCA-307CA96E9FD1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F2149709-A88B-4F36-ABCA-307CA96E9FD1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F2149709-A88B-4F36-ABCA-307CA96E9FD1}.Debug|x64.ActiveCfg = Debug|Any CPU + {F2149709-A88B-4F36-ABCA-307CA96E9FD1}.Debug|x64.Build.0 = Debug|Any CPU + {F2149709-A88B-4F36-ABCA-307CA96E9FD1}.Debug|x86.ActiveCfg = Debug|Any CPU + {F2149709-A88B-4F36-ABCA-307CA96E9FD1}.Debug|x86.Build.0 = Debug|Any CPU + {F2149709-A88B-4F36-ABCA-307CA96E9FD1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F2149709-A88B-4F36-ABCA-307CA96E9FD1}.Release|Any CPU.Build.0 = Release|Any CPU + {F2149709-A88B-4F36-ABCA-307CA96E9FD1}.Release|x64.ActiveCfg = Release|Any CPU + {F2149709-A88B-4F36-ABCA-307CA96E9FD1}.Release|x64.Build.0 = Release|Any CPU + {F2149709-A88B-4F36-ABCA-307CA96E9FD1}.Release|x86.ActiveCfg = Release|Any CPU + {F2149709-A88B-4F36-ABCA-307CA96E9FD1}.Release|x86.Build.0 = Release|Any CPU + {6D3AC84F-D53F-4494-8142-DD2BC8FD9CFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6D3AC84F-D53F-4494-8142-DD2BC8FD9CFD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6D3AC84F-D53F-4494-8142-DD2BC8FD9CFD}.Debug|x64.ActiveCfg = Debug|Any CPU + {6D3AC84F-D53F-4494-8142-DD2BC8FD9CFD}.Debug|x64.Build.0 = Debug|Any CPU + {6D3AC84F-D53F-4494-8142-DD2BC8FD9CFD}.Debug|x86.ActiveCfg = Debug|Any CPU + {6D3AC84F-D53F-4494-8142-DD2BC8FD9CFD}.Debug|x86.Build.0 = Debug|Any CPU + {6D3AC84F-D53F-4494-8142-DD2BC8FD9CFD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6D3AC84F-D53F-4494-8142-DD2BC8FD9CFD}.Release|Any CPU.Build.0 = Release|Any CPU + {6D3AC84F-D53F-4494-8142-DD2BC8FD9CFD}.Release|x64.ActiveCfg = Release|Any CPU + {6D3AC84F-D53F-4494-8142-DD2BC8FD9CFD}.Release|x64.Build.0 = Release|Any CPU + {6D3AC84F-D53F-4494-8142-DD2BC8FD9CFD}.Release|x86.ActiveCfg = Release|Any CPU + {6D3AC84F-D53F-4494-8142-DD2BC8FD9CFD}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -550,6 +567,6 @@ Global {E4528324-C399-48D2-AB38-66D951158F12} = {EE50720D-A0E5-45EE-B2EC-E065B569DB27} {0D80F608-CF60-42F8-A5E1-4E0ACFA384AF} = {EE50720D-A0E5-45EE-B2EC-E065B569DB27} {130C9240-9537-42D4-A62C-2A13815F0020} = {EE50720D-A0E5-45EE-B2EC-E065B569DB27} - {2BBB937B-EFD3-48AA-BEF8-05E203589D24} = {EE50720D-A0E5-45EE-B2EC-E065B569DB27} + {6D3AC84F-D53F-4494-8142-DD2BC8FD9CFD} = {EE50720D-A0E5-45EE-B2EC-E065B569DB27} EndGlobalSection EndGlobal diff --git a/src/R/Actions/Impl/Utility/RInstallation.cs b/src/R/Actions/Impl/Utility/RInstallation.cs index 9274e8f10..69902c056 100644 --- a/src/R/Actions/Impl/Utility/RInstallation.cs +++ b/src/R/Actions/Impl/Utility/RInstallation.cs @@ -12,6 +12,8 @@ namespace Microsoft.R.Actions.Utility { /// settings try and find highest version. /// public static class RInstallation { + private static string[] rFolders = new string[] { "MRO", "RRO", "R" }; + public static RInstallData GetInstallationData(string basePath, int minMajorVersion, int minMinorVersion, int maxMajorVersion, int maxMinorVersion, bool useRegistry = true) { string path = string.Empty; if (useRegistry) { @@ -19,15 +21,18 @@ public static RInstallData GetInstallationData(string basePath, int minMajorVers } if (string.IsNullOrEmpty(path)) { - path = TryFindRInProgramFiles("RRO", minMajorVersion, minMinorVersion, maxMajorVersion, maxMinorVersion); - if(string.IsNullOrEmpty(path)) { - path = TryFindRInProgramFiles("R", minMajorVersion, minMinorVersion, maxMajorVersion, maxMinorVersion); - if (string.IsNullOrEmpty(path)) { - return new RInstallData() { Status = RInstallStatus.PathNotSpecified }; + foreach (var f in rFolders) { + path = TryFindRInProgramFiles(f, minMajorVersion, minMinorVersion, maxMajorVersion, maxMinorVersion); + if (!string.IsNullOrEmpty(path)) { + break; } } } + if (string.IsNullOrEmpty(path)) { + return new RInstallData() { Status = RInstallStatus.PathNotSpecified }; + } + RInstallData data = new RInstallData() { Status = RInstallStatus.OK, Path = path }; try { @@ -60,7 +65,7 @@ public static RInstallData GetInstallationData(string basePath, int minMajorVers } public static void GoToRInstallPage() { - Process.Start("https://cran.r-project.org/"); + Process.Start("https://mran.revolutionanalytics.com/download/#download"); } /// @@ -117,24 +122,50 @@ public static string GetLatestEnginePathFromRegistry() { Version highest = null; foreach (string name in installedEngines) { - Version v = new Version(name); - if (highest != null) { - if (v > highest) { - highest = v; - highestVersionName = name; + // Protect from random key name format changes + if (!string.IsNullOrEmpty(name)) { + string versionString = ExtractVersionString(name); + Version v; + if (Version.TryParse(versionString, out v)) { + if (highest != null) { + if (v > highest) { + highest = v; + highestVersionName = name; + } + } else { + highest = v; + highestVersionName = name; + } } - } else { - highest = v; - highestVersionName = name; } } return GetRVersionInstallPathFromRegistry(highestVersionName); } + private static string ExtractVersionString(string original) { + int start = 0; + int end = original.Length; + + for (; start < original.Length; start++) { + if (Char.IsDigit(original[start])) { + break; + } + } + + for (; end > 0; end--) { + if (Char.IsDigit(original[end - 1])) { + break; + } + } + + return end > start ? original.Substring(start, end - start) : string.Empty; + } + /// /// Retrieves installed R versions. Returns array of strings - /// that typically look like 'R-3.2.1' and typically are + /// that typically look like 'R-3.2.1' (but development versions + /// may also look like '3.3.0 Pre-release' and typically are /// subfolders of 'Program Files\R' /// public static string[] GetInstalledEngineVersionsFromRegistry() { @@ -142,24 +173,13 @@ public static string[] GetInstalledEngineVersionsFromRegistry() { // HKEY_LOCAL_MACHINE\SOFTWARE\R-core // HKEY_LOCAL_MACHINE\SOFTWARE\Wow6432Node\R-core + // HKEY_LOCAL_MACHINE\SOFTWARE\R-core\R64\3.3.0 Pre-release using (RegistryKey hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64)) { - RegistryKey rKey = null; - try { - rKey = hklm.OpenSubKey(@"SOFTWARE\R-core\R"); - if (rKey == null) { - // Possibly 64-bit machine with only 32-bit R installed - // This is not supported as we require 64-bit R. - // rKey = hklm.OpenSubKey(@"SOFTWARE\Wow6432Node\R-core\R"); - } - if (rKey != null) { + using (var rKey = hklm.OpenSubKey(@"SOFTWARE\R-core\R")) { return rKey.GetSubKeyNames(); } - } catch (Exception) { } finally { - if (rKey != null) { - rKey.Dispose(); - } - } + } catch (Exception) { } } return new string[0]; @@ -168,20 +188,24 @@ public static string[] GetInstalledEngineVersionsFromRegistry() { private static string GetRVersionInstallPathFromRegistry(string version) { // HKEY_LOCAL_MACHINE\SOFTWARE\R-core using (RegistryKey hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry64)) { - using (var rKey = hklm.OpenSubKey(@"SOFTWARE\R-core\R\" + version)) { - if (rKey != null) { - return rKey.GetValue("InstallPath") as string; + try { + using (var rKey = hklm.OpenSubKey(@"SOFTWARE\R-core\R\" + version)) { + if (rKey != null) { + return rKey.GetValue("InstallPath") as string; + } } - } + } catch(Exception) { } } - return string.Empty; } - private static Version GetRVersionFromFolderName(string folderName) { + public static Version GetRVersionFromFolderName(string folderName) { if (folderName.StartsWith("R-")) { try { - return new Version(folderName.Substring(2)); + Version v; + if (Version.TryParse(folderName.Substring(2), out v)) { + return v; + } } catch (Exception) { } } return new Version(0, 0); @@ -191,12 +215,19 @@ private static string TryFindRInProgramFiles(string folder, int minMajorVersion, string root = Path.GetPathRoot(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFiles)); string baseRFolder = Path.Combine(root + @"Program Files\", folder); List versions = new List(); - foreach (string dir in Directory.EnumerateDirectories(baseRFolder)) { - string subFolderName = dir.Substring(baseRFolder.Length + 1); - Version v = GetRVersionFromFolderName(subFolderName); - if (v.Major >= minMajorVersion && v.Minor >= minMinorVersion && v.Major <= maxMajorVersion && v.Minor <= maxMinorVersion) { - versions.Add(v); + try { + foreach (string dir in Directory.EnumerateDirectories(baseRFolder)) { + string subFolderName = dir.Substring(baseRFolder.Length + 1); + Version v = GetRVersionFromFolderName(subFolderName); + if (v.Major >= minMajorVersion && + v.Minor >= minMinorVersion && + v.Major <= maxMajorVersion && + v.Minor <= maxMinorVersion) { + versions.Add(v); + } } + } catch (IOException) { + // Don't do anything if there is no RRO installed } if (versions.Count > 0) { diff --git a/src/R/Actions/Test/Installation/RInstallationTest.cs b/src/R/Actions/Test/Installation/RInstallationTest.cs index 0863ed857..4ce2628a6 100644 --- a/src/R/Actions/Test/Installation/RInstallationTest.cs +++ b/src/R/Actions/Test/Installation/RInstallationTest.cs @@ -21,17 +21,8 @@ public void RInstallation_Test02() { data.Status.Should().Be(RInstallStatus.OK); data.Version.Major.Should().BeGreaterOrEqualTo(3); data.Version.Minor.Should().BeGreaterOrEqualTo(2); - data.Path.Should().StartWithEquivalent(@"C:\Program Files\R"); - } - - [Test] - [Category.R.Install] - public void RInstallation_Test03() { - RInstallData data = RInstallation.GetInstallationData(null, 3, 2, 3, 2, useRegistry: false); - data.Status.Should().Be(RInstallStatus.OK); - data.Version.Major.Should().BeGreaterOrEqualTo(3); - data.Version.Minor.Should().BeGreaterOrEqualTo(2); - data.Path.Should().StartWithEquivalent(@"C:\Program Files\R"); + data.Path.Should().StartWithEquivalent(@"C:\Program Files"); + data.Path.Should().Contain("R-"); } } } diff --git a/src/R/Core/Impl/AST/Expressions/ExpressionParser.cs b/src/R/Core/Impl/AST/Expressions/ExpressionParser.cs index 3f90197fe..82c619a6e 100644 --- a/src/R/Core/Impl/AST/Expressions/ExpressionParser.cs +++ b/src/R/Core/Impl/AST/Expressions/ExpressionParser.cs @@ -1,593 +1,608 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using Microsoft.Languages.Core.Text; -using Microsoft.Languages.Core.Tokens; -using Microsoft.R.Core.AST.Definitions; -using Microsoft.R.Core.AST.Functions; -using Microsoft.R.Core.AST.Operands; -using Microsoft.R.Core.AST.Operators; -using Microsoft.R.Core.AST.Operators.Definitions; -using Microsoft.R.Core.AST.Values; -using Microsoft.R.Core.AST.Variables; -using Microsoft.R.Core.Parser; -using Microsoft.R.Core.Tokens; - -namespace Microsoft.R.Core.AST.Expressions { - /// - /// Implements shunting yard algorithm of expression parsing. - /// https://en.wikipedia.org/wiki/Shunting-yard_algorithm - /// - public sealed partial class Expression { - private static readonly IOperator Sentinel = new TokenOperator(OperatorType.Sentinel, false); - - /// - /// Describes current and previous operation types - /// in the current expression. Helps to detect - /// errors like missing operands or operators. - /// - enum OperationType { - None, - UnaryOperator, - BinaryOperator, - Operand, - Function, - EndOfExpression - } - - private Stack _operands = new Stack(); - private Stack _operators = new Stack(); - private OperationType _previousOperationType = OperationType.None; - - internal bool IsGroupOpen() { - IOperator[] ops = _operators.ToArray(); - for (int i = ops.Length - 1; i >= 1; i--) { - if (ops[i].OperatorType == OperatorType.Group) { - return true; - } - } - - return false; - } - - private bool ParseExpression(ParseContext context) { - // https://en.wikipedia.org/wiki/Shunting-yard_algorithm - // http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm - // Instead of evaluating expressions like calculator would do, - // we create tree nodes with operator and its operands. - - TokenStream tokens = context.Tokens; - OperationType currentOperationType = OperationType.None; - ParseErrorType errorType = ParseErrorType.None; - ErrorLocation errorLocation = ErrorLocation.AfterToken; - bool endOfExpression = false; - - // Push sentinel - _operators.Push(Expression.Sentinel); - - while (!tokens.IsEndOfStream() && errorType == ParseErrorType.None && !endOfExpression) { - RToken token = tokens.CurrentToken; - - switch (token.TokenType) { - // Terminal constants - case RTokenType.Number: - case RTokenType.Complex: - case RTokenType.Logical: - case RTokenType.String: - case RTokenType.Null: - case RTokenType.Missing: - case RTokenType.NaN: - case RTokenType.Infinity: - currentOperationType = HandleConstant(context); - break; - - // Variables and function calls - case RTokenType.Identifier: - currentOperationType = HandleIdentifier(context); - break; - - // Nested expression such as a*(b+c) or a nameless - // function call like a[2](x, y) or func(a, b)(c, d) - case RTokenType.OpenBrace: - currentOperationType = HandleOpenBrace(context, out errorType); - break; - - case RTokenType.OpenCurlyBrace: - currentOperationType = HandleLambda(context, out errorType); - break; - - case RTokenType.OpenSquareBracket: - case RTokenType.OpenDoubleSquareBracket: - currentOperationType = HandleSquareBrackets(context, out errorType); - break; - - case RTokenType.Operator: - currentOperationType = HandleOperator(context, out errorType); - break; - - case RTokenType.CloseBrace: - currentOperationType = OperationType.EndOfExpression; - endOfExpression = true; - break; - - case RTokenType.CloseCurlyBrace: - case RTokenType.CloseSquareBracket: - case RTokenType.CloseDoubleSquareBracket: - currentOperationType = OperationType.EndOfExpression; - endOfExpression = true; - break; - - case RTokenType.Comma: - case RTokenType.Semicolon: - currentOperationType = OperationType.EndOfExpression; - endOfExpression = true; - break; - - case RTokenType.Keyword: - currentOperationType = HandleKeyword(context, out errorType); - endOfExpression = true; - break; - - default: - errorType = ParseErrorType.UnexpectedToken; - errorLocation = ErrorLocation.Token; - break; - } - - if (errorType == ParseErrorType.None && !IsConsistentOperationSequence(context, currentOperationType)) { - return false; - } - - if (errorType != ParseErrorType.None || endOfExpression) { - break; - } - - _previousOperationType = currentOperationType; - - if (!endOfExpression) { - endOfExpression = CheckEndOfExpression(context, currentOperationType); - } - } - - if (errorType != ParseErrorType.None) { - if (errorLocation == ErrorLocation.AfterToken) { - context.AddError(new ParseError(errorType, ErrorLocation.AfterToken, tokens.PreviousToken)); - } else { - context.AddError(new ParseError(errorType, ErrorLocation.Token, tokens.CurrentToken)); - } - } - - if (_operators.Count > 1) { - // If there are still operators to process, - // construct final node. After this only sentil - // operator should be in the operators stack - // and a single final root node in the operand stack. - errorType = this.ProcessHigherPrecendenceOperators(context, Expression.Sentinel); - } - - if (errorType != ParseErrorType.None) { - if (errorType != ParseErrorType.LeftOperandExpected) { - context.AddError(new ParseError(errorType, ErrorLocation.Token, GetErrorRange(context))); - } - - // Although there may be errors such as incomplete function - // we still want to include the result into the tree since - // even in the code like 'func(,,, for(' we want intellisense - // and parameter to work. - if (_operands.Count == 1) { - Content = _operands.Pop(); - AppendChild(this.Content); - } - } else { - Debug.Assert(_operators.Count == 1); - - // If operand stack ie empty and there is no error - // then the expression is empty. - if (_operands.Count > 0) { - Debug.Assert(_operands.Count == 1); - - Content = _operands.Pop(); - AppendChild(Content); - } - } - - return true; - } - - private bool CheckEndOfExpression(ParseContext context, OperationType currentOperationType) { - // In R there may not be explicit end of statement. Semicolon is optional and - // R console figures out if there is continuation from the context. For example, - // if statement is incomplete such as brace is not closed or last token in the line - // is an operator, it continues with the next line. However, - // in 'x + 1 + y' it stops expression parsing at the line break. - - if (currentOperationType == OperationType.Function || (currentOperationType == OperationType.Operand && _previousOperationType != OperationType.None)) { - // Since we haven't seen explicit end of expression and the last operation - // was 'operand' which is a variable or a constant and there is a line break - // ahead of us then the expression is complete. Outer parser may still continue - // if braces are not closed yet. - - if (!IsInGroup || context.Tokens.CurrentToken.TokenType == RTokenType.CloseBrace) { - if (context.Tokens.IsLineBreakAfter(context.TextProvider, context.Tokens.Position - 1)) { - // There is a line break before this token - return true; - } - } - } - - return false; - } - - private bool IsConsistentOperationSequence(ParseContext context, OperationType currentOperationType) { - // Binary operator followed by another binary like 'a +/ b' is an error. - // 'func()()' or 'func() +' is allowed but 'func() operand' is not. - // Binary operator without anything behind it is an error; - if ((_previousOperationType == OperationType.Function && currentOperationType == OperationType.Operand) || - (_previousOperationType == currentOperationType && currentOperationType != OperationType.Function)) { - switch (currentOperationType) { - case OperationType.Operand: - // 'operand operand' sequence is an error - context.AddError(new ParseError(ParseErrorType.OperatorExpected, ErrorLocation.Token, GetOperandErrorRange(context))); - break; - - default: - // 'operator operator' sequence is an error - context.AddError(new ParseError(ParseErrorType.RightOperandExpected, ErrorLocation.Token, GetOperatorErrorRange(context))); - break; - } - - return false; - } else if (currentOperationType == OperationType.BinaryOperator && context.Tokens.IsEndOfStream()) { - // 'operator ' sequence is an error - context.AddError(new ParseError(ParseErrorType.RightOperandExpected, ErrorLocation.Token, GetOperatorErrorRange(context))); - return false; - } else if (_previousOperationType == OperationType.UnaryOperator && currentOperationType == OperationType.BinaryOperator) { - // unary followed by binary doesn't make sense - context.AddError(new ParseError(ParseErrorType.IndentifierExpected, ErrorLocation.Token, GetOperatorErrorRange(context))); - return false; - } else if (_previousOperationType == OperationType.BinaryOperator && currentOperationType == OperationType.EndOfExpression) { - // missing list selector: z$ } - context.AddError(new ParseError(ParseErrorType.RightOperandExpected, ErrorLocation.Token, GetErrorRange(context))); - return false; - } - - return true; - } - - private ITextRange GetErrorRange(ParseContext context) { - return context.Tokens.IsEndOfStream() ? context.Tokens.PreviousToken : context.Tokens.CurrentToken; - } - - private ITextRange GetIndexerOrFunctionErrorRange(ParseContext context, IOperator operatorNode) { - ITextRange range = null; - if (operatorNode is Indexer) { - range = ((Indexer)operatorNode).LeftBrackets; - } else if (operatorNode is FunctionCall) { - range = ((FunctionCall)operatorNode).OpenBrace; - } - - if (range == null) { - range = context.Tokens.FirstOrDefault((x) => x.Start >= operatorNode.End); - } - - if (range == null) { - range = operatorNode; - } - - return range; - } - - private ITextRange GetOperandErrorRange(ParseContext context) { - if (_operands.Count > 0) { - IAstNode node = _operands.Peek(); - if (node.Children.Count > 0) { - return node.Children[0]; - } - - return node; - } - - return GetErrorRange(context); - } - - private ITextRange GetOperatorErrorRange(ParseContext context) { - if (_operators.Count > 0) { - IAstNode node = _operators.Peek(); - if (node.Children.Count > 0) { - return node.Children[0]; - } - - return node; - } - - return GetErrorRange(context); - } - - private OperationType HandleConstant(ParseContext context) { - IRValueNode constant = Expression.CreateConstant(context); - - _operands.Push(constant); - return OperationType.Operand; - } - - private OperationType HandleIdentifier(ParseContext context) { - Variable variable = new Variable(); - variable.Parse(context, null); - - _operands.Push(variable); - return OperationType.Operand; - } - - private OperationType HandleLambda(ParseContext context, out ParseErrorType errorType) { - errorType = ParseErrorType.None; - - Lambda lambda = new Lambda(); - lambda.Parse(context, null); - - _operands.Push(lambda); - return OperationType.Operand; - } - - private OperationType HandleOpenBrace(ParseContext context, out ParseErrorType errorType) { - TokenStream tokens = context.Tokens; - errorType = ParseErrorType.None; - - // Separate expression from function call. In case of - // function call previous token is either closing indexer - // brace or closing function brace. Identifier with brace - // is handled up above. - // Indentifier followed by a brace needs to be analyzed - // so we can tell between previous expression that ended - // with identifier and identifier that is a function name: - // - // a <- 2*b - // (expression) - // - // in this case b is not a function name. Similarly, - // - // a <- 2*b[1] - // (expression) - // - // is not a function call operator over b[1]. - - if (_operators.Count > 1 || _operands.Count > 0) { - // We are not in the beginning of the expression - if (tokens.PreviousToken.TokenType == RTokenType.CloseBrace || - tokens.PreviousToken.TokenType == RTokenType.CloseSquareBracket || - tokens.PreviousToken.TokenType == RTokenType.CloseSquareBracket || - tokens.PreviousToken.TokenType == RTokenType.Identifier) { - FunctionCall functionCall = new FunctionCall(); - functionCall.Parse(context, null); - - errorType = HandleFunctionOrIndexer(functionCall); - return OperationType.Function; - } - } - - Group group = new Group(); - group.Parse(context, null); - - _operands.Push(group); - return OperationType.Operand; - } - - private OperationType HandleSquareBrackets(ParseContext context, out ParseErrorType errorType) { - Indexer indexer = new Indexer(); - indexer.Parse(context, null); - - errorType = HandleFunctionOrIndexer(indexer); - return OperationType.Function; - } - - private OperationType HandleOperator(ParseContext context, out ParseErrorType errorType) { - bool isUnary; - errorType = this.HandleOperator(context, null, out isUnary); - return isUnary ? OperationType.UnaryOperator : OperationType.BinaryOperator; - } - - private OperationType HandleKeyword(ParseContext context, out ParseErrorType errorType) { - errorType = ParseErrorType.None; - - string keyword = context.TextProvider.GetText(context.Tokens.CurrentToken); - if (IsTerminatingKeyword(keyword)) { - return OperationType.EndOfExpression; - } - - errorType = HandleKeyword(context, keyword); - if (errorType == ParseErrorType.None) { - return OperationType.Operand; - } - - return _previousOperationType; - } - - private ParseErrorType HandleKeyword(ParseContext context, string keyword) { - ParseErrorType errorType = ParseErrorType.None; - - if (keyword.Equals("function", StringComparison.Ordinal)) { - // Special case 'exp <- function(...) { } - FunctionDefinition funcDef = new FunctionDefinition(); - funcDef.Parse(context, null); - - // Add to the stack even if it has errors in order - // to avoid extra errors - _operands.Push(funcDef); - } else if (keyword.Equals("if", StringComparison.Ordinal)) { - // If needs to know parent expression since - // it must figure out how to handle 'else' - // when if is without { }. - context.Expressions.Push(this); - - InlineIf inlineIf = new InlineIf(); - inlineIf.Parse(context, null); - - context.Expressions.Pop(); - - // Add to the stack even if it has errors in order - // to avoid extra errors - _operands.Push(inlineIf); - } else { - errorType = ParseErrorType.UnexpectedToken; - } - - return errorType; - } - - private bool IsTerminatingKeyword(string s) { - if (_terminatingKeyword == null) { - return false; - } - - return s.Equals(_terminatingKeyword, StringComparison.Ordinal); - } - - private ParseErrorType HandleFunctionOrIndexer(IRValueNode operatorNode) { - // Indexing or function call is performed on the topmost operand which - // generally should be a variable or a node that evaluates to it. - // However, we leave syntax check to separate code. - - IRValueNode operand = this.SafeGetOperand(); - if (operand == null) { - // Oddly, no operand - return ParseErrorType.IndentifierExpected; - } - - operatorNode.AppendChild(operand); - _operands.Push(operatorNode); - - return ParseErrorType.None; - } - - private ParseErrorType HandleOperator(ParseContext context, IAstNode parent, out bool isUnary) { - ParseErrorType errorType = ParseErrorType.None; - - // If operands stack is empty operator is unary. - // If operator is preceded by another operator, - // it is interpreted as unary. - - TokenOperator currentOperator = new TokenOperator(_operands.Count == 0); - - currentOperator.Parse(context, null); - isUnary = currentOperator.IsUnary; - - IOperator lastOperator = _operators.Peek(); - if (currentOperator.Precedence <= lastOperator.Precedence && - !(currentOperator.OperatorType == lastOperator.OperatorType && currentOperator.Association == Association.Right)) { - // New operator has lower or equal precedence. We need to make a tree from - // the topmost operator and its operand(s). Example: a*b+c. + has lower priority - // and a and b should be on the stack along with * on the operator stack. - // Repeat until there are no more higher precendece operators on the stack. - - errorType = this.ProcessHigherPrecendenceOperators(context, currentOperator); - } - - if (errorType == ParseErrorType.None) { - _operators.Push(currentOperator); - } - - return errorType; - } - - private ParseErrorType ProcessHigherPrecendenceOperators(ParseContext context, IOperator currentOperator) { - Debug.Assert(_operators.Count > 1); - ParseErrorType errorType = ParseErrorType.None; - Association association = currentOperator.Association; - - // At least one operator above sentinel is on the stack. - do { - errorType = MakeNode(context); - if (errorType == ParseErrorType.None) { - IOperator nextOperatorNode = _operators.Peek(); - - if (association == Association.Left && nextOperatorNode.Precedence <= currentOperator.Precedence) { - break; - } - - if (association == Association.Right && nextOperatorNode.Precedence < currentOperator.Precedence) { - break; - } - } - } while (_operators.Count > 1 && errorType == ParseErrorType.None); - - return errorType; - } - - private ParseErrorType MakeNode(ParseContext context) { - IOperator operatorNode = _operators.Pop(); - - IRValueNode rightOperand = this.SafeGetOperand(); - if (rightOperand == null) { - // Oddly, no operands - return ParseErrorType.RightOperandExpected; - } - - if (operatorNode.IsUnary) { - operatorNode.AppendChild(rightOperand); - operatorNode.RightOperand = rightOperand; - } else { - IRValueNode leftOperand = this.SafeGetOperand(); - if (leftOperand == null) { - // Operand is missing in expression like x <- []. - // Operator on top of the stack is <- since [] was not - // successfully parsed. So we need to mark right operand - // as error token. - context.AddError(new ParseError(ParseErrorType.LeftOperandExpected, ErrorLocation.Token, GetIndexerOrFunctionErrorRange(context, operatorNode))); - return ParseErrorType.LeftOperandExpected; - } - - operatorNode.LeftOperand = leftOperand; - operatorNode.RightOperand = rightOperand; - - operatorNode.AppendChild(leftOperand); - operatorNode.AppendChild(rightOperand); - } - - _operands.Push(operatorNode); - - return ParseErrorType.None; - } - - private IRValueNode SafeGetOperand() { - return _operands.Count > 0 ? _operands.Pop() : null; - } - - private static IRValueNode CreateConstant(ParseContext context) { - TokenStream tokens = context.Tokens; - RToken currentToken = tokens.CurrentToken; - IRValueNode term = null; - - switch (currentToken.TokenType) { - case RTokenType.NaN: - case RTokenType.Infinity: - case RTokenType.Number: - term = new NumericalValue(); - break; - - case RTokenType.Complex: - term = new ComplexValue(); - break; - - case RTokenType.Logical: - term = new LogicalValue(); - break; - - case RTokenType.String: - term = new StringValue(); - break; - - case RTokenType.Null: - term = new NullValue(); - break; - - case RTokenType.Missing: - term = new MissingValue(); - break; - } - - Debug.Assert(term != null); - term.Parse(context, null); - return term; - } - } -} +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using Microsoft.Languages.Core.Text; +using Microsoft.Languages.Core.Tokens; +using Microsoft.R.Core.AST.Definitions; +using Microsoft.R.Core.AST.Functions; +using Microsoft.R.Core.AST.Operands; +using Microsoft.R.Core.AST.Operators; +using Microsoft.R.Core.AST.Operators.Definitions; +using Microsoft.R.Core.AST.Values; +using Microsoft.R.Core.AST.Variables; +using Microsoft.R.Core.Parser; +using Microsoft.R.Core.Tokens; + +namespace Microsoft.R.Core.AST.Expressions { + /// + /// Implements shunting yard algorithm of expression parsing. + /// https://en.wikipedia.org/wiki/Shunting-yard_algorithm + /// + public sealed partial class Expression { + private static readonly IOperator Sentinel = new TokenOperator(OperatorType.Sentinel, false); + + /// + /// Describes current and previous operation types + /// in the current expression. Helps to detect + /// errors like missing operands or operators. + /// + enum OperationType { + None, + UnaryOperator, + BinaryOperator, + Operand, + Function, + EndOfExpression + } + + private Stack _operands = new Stack(); + private Stack _operators = new Stack(); + private OperationType _previousOperationType = OperationType.None; + + internal bool IsGroupOpen() { + IOperator[] ops = _operators.ToArray(); + for (int i = ops.Length - 1; i >= 1; i--) { + if (ops[i].OperatorType == OperatorType.Group) { + return true; + } + } + + return false; + } + + private bool ParseExpression(ParseContext context) { + // https://en.wikipedia.org/wiki/Shunting-yard_algorithm + // http://www.engr.mun.ca/~theo/Misc/exp_parsing.htm + // Instead of evaluating expressions like calculator would do, + // we create tree nodes with operator and its operands. + + TokenStream tokens = context.Tokens; + OperationType currentOperationType = OperationType.None; + ParseErrorType errorType = ParseErrorType.None; + ErrorLocation errorLocation = ErrorLocation.AfterToken; + bool endOfExpression = false; + + // Push sentinel + _operators.Push(Expression.Sentinel); + + while (!tokens.IsEndOfStream() && errorType == ParseErrorType.None && !endOfExpression) { + RToken token = tokens.CurrentToken; + + switch (token.TokenType) { + // Terminal constants + case RTokenType.Number: + case RTokenType.Complex: + case RTokenType.Logical: + case RTokenType.String: + case RTokenType.Null: + case RTokenType.Missing: + case RTokenType.NaN: + case RTokenType.Infinity: + currentOperationType = HandleConstant(context); + break; + + // Variables and function calls + case RTokenType.Identifier: + currentOperationType = HandleIdentifier(context); + break; + + // Nested expression such as a*(b+c) or a nameless + // function call like a[2](x, y) or func(a, b)(c, d) + case RTokenType.OpenBrace: + currentOperationType = HandleOpenBrace(context, out errorType); + break; + + case RTokenType.OpenCurlyBrace: + currentOperationType = HandleLambda(context, out errorType); + break; + + case RTokenType.OpenSquareBracket: + case RTokenType.OpenDoubleSquareBracket: + currentOperationType = HandleSquareBrackets(context, out errorType); + break; + + case RTokenType.Operator: + currentOperationType = HandleOperator(context, out errorType); + break; + + case RTokenType.CloseBrace: + currentOperationType = OperationType.EndOfExpression; + endOfExpression = true; + break; + + case RTokenType.CloseCurlyBrace: + case RTokenType.CloseSquareBracket: + case RTokenType.CloseDoubleSquareBracket: + currentOperationType = OperationType.EndOfExpression; + endOfExpression = true; + break; + + case RTokenType.Comma: + case RTokenType.Semicolon: + currentOperationType = OperationType.EndOfExpression; + endOfExpression = true; + break; + + case RTokenType.Keyword: + currentOperationType = HandleKeyword(context, out errorType); + endOfExpression = true; + break; + + default: + errorType = ParseErrorType.UnexpectedToken; + errorLocation = ErrorLocation.Token; + break; + } + + if (errorType == ParseErrorType.None && !IsConsistentOperationSequence(context, currentOperationType)) { + return false; + } + + if (errorType != ParseErrorType.None || endOfExpression) { + break; + } + + _previousOperationType = currentOperationType; + + if (!endOfExpression) { + endOfExpression = CheckEndOfExpression(context, currentOperationType); + } + } + + if (errorType != ParseErrorType.None) { + if (errorLocation == ErrorLocation.AfterToken) { + context.AddError(new ParseError(errorType, ErrorLocation.AfterToken, tokens.PreviousToken)); + } else { + context.AddError(new ParseError(errorType, ErrorLocation.Token, tokens.CurrentToken)); + } + } + + if (_operators.Count > 1) { + // If there are still operators to process, + // construct final node. After this only sentil + // operator should be in the operators stack + // and a single final root node in the operand stack. + errorType = this.ProcessHigherPrecendenceOperators(context, Expression.Sentinel); + } + + if (errorType != ParseErrorType.None) { + if (errorType != ParseErrorType.LeftOperandExpected) { + context.AddError(new ParseError(errorType, ErrorLocation.Token, GetErrorRange(context))); + } + + // Although there may be errors such as incomplete function + // we still want to include the result into the tree since + // even in the code like 'func(,,, for(' we want intellisense + // and parameter to work. + if (_operands.Count == 1) { + Content = _operands.Pop(); + AppendChild(this.Content); + } + } else { + Debug.Assert(_operators.Count == 1); + + // If operand stack ie empty and there is no error + // then the expression is empty. + if (_operands.Count > 0) { + Debug.Assert(_operands.Count == 1); + + Content = _operands.Pop(); + AppendChild(Content); + } + } + + return true; + } + + private bool CheckEndOfExpression(ParseContext context, OperationType currentOperationType) { + // In R there may not be explicit end of statement. Semicolon is optional and + // R console figures out if there is continuation from the context. For example, + // if statement is incomplete such as brace is not closed or last token in the line + // is an operator, it continues with the next line. However, + // in 'x + 1 + y' it stops expression parsing at the line break. + + if (currentOperationType == OperationType.Function || (currentOperationType == OperationType.Operand && _previousOperationType != OperationType.None)) { + // Since we haven't seen explicit end of expression and the last operation + // was 'operand' which is a variable or a constant and there is a line break + // ahead of us then the expression is complete. Outer parser may still continue + // if braces are not closed yet. + + if (!IsInGroup || context.Tokens.CurrentToken.TokenType == RTokenType.CloseBrace) { + if (context.Tokens.IsLineBreakAfter(context.TextProvider, context.Tokens.Position - 1)) { + // There is a line break before this token + return true; + } + } + } + + return false; + } + + private bool IsConsistentOperationSequence(ParseContext context, OperationType currentOperationType) { + // Binary operator followed by another binary like 'a +/ b' is an error. + // 'func()()' or 'func() +' is allowed but 'func() operand' is not. + // Binary operator without anything behind it is an error; + if ((_previousOperationType == OperationType.Function && currentOperationType == OperationType.Operand) || + (_previousOperationType == currentOperationType && currentOperationType != OperationType.Function)) { + switch (currentOperationType) { + case OperationType.Operand: + // 'operand operand' sequence is an error + context.AddError(new ParseError(ParseErrorType.OperatorExpected, ErrorLocation.Token, GetOperandErrorRange(context))); + break; + + case OperationType.UnaryOperator: + // 'unary unary' and 'binary unary' are OK + return true; + + default: + // 'operator operator' sequence is an error + context.AddError(new ParseError(ParseErrorType.RightOperandExpected, ErrorLocation.Token, GetOperatorErrorRange(context))); + break; + } + + return false; + } else if (currentOperationType == OperationType.BinaryOperator && context.Tokens.IsEndOfStream()) { + // 'operator ' sequence is an error + context.AddError(new ParseError(ParseErrorType.RightOperandExpected, ErrorLocation.Token, GetOperatorErrorRange(context))); + return false; + } else if (_previousOperationType == OperationType.UnaryOperator && currentOperationType == OperationType.BinaryOperator) { + // unary followed by binary doesn't make sense + context.AddError(new ParseError(ParseErrorType.IndentifierExpected, ErrorLocation.Token, GetOperatorErrorRange(context))); + return false; + } else if (_previousOperationType == OperationType.BinaryOperator && currentOperationType == OperationType.EndOfExpression) { + // missing list selector: z$ } + context.AddError(new ParseError(ParseErrorType.RightOperandExpected, ErrorLocation.Token, GetErrorRange(context))); + return false; + } + + return true; + } + + private ITextRange GetErrorRange(ParseContext context) { + return context.Tokens.IsEndOfStream() ? context.Tokens.PreviousToken : context.Tokens.CurrentToken; + } + + private ITextRange GetIndexerOrFunctionErrorRange(ParseContext context, IOperator operatorNode) { + ITextRange range = null; + if (operatorNode is Indexer) { + range = ((Indexer)operatorNode).LeftBrackets; + } else if (operatorNode is FunctionCall) { + range = ((FunctionCall)operatorNode).OpenBrace; + } + + if (range == null) { + range = context.Tokens.FirstOrDefault((x) => x.Start >= operatorNode.End); + } + + if (range == null) { + range = operatorNode; + } + + return range; + } + + private ITextRange GetOperandErrorRange(ParseContext context) { + if (_operands.Count > 0) { + IAstNode node = _operands.Peek(); + if (node.Children.Count > 0) { + return node.Children[0]; + } + + return node; + } + + return GetErrorRange(context); + } + + private ITextRange GetOperatorErrorRange(ParseContext context) { + if (_operators.Count > 0) { + IAstNode node = _operators.Peek(); + if (node.Children.Count > 0) { + return node.Children[0]; + } + + return node; + } + + return GetErrorRange(context); + } + + private OperationType HandleConstant(ParseContext context) { + IRValueNode constant = Expression.CreateConstant(context); + + _operands.Push(constant); + return OperationType.Operand; + } + + private OperationType HandleIdentifier(ParseContext context) { + Variable variable = new Variable(); + variable.Parse(context, null); + + _operands.Push(variable); + return OperationType.Operand; + } + + private OperationType HandleLambda(ParseContext context, out ParseErrorType errorType) { + errorType = ParseErrorType.None; + + Lambda lambda = new Lambda(); + lambda.Parse(context, null); + + _operands.Push(lambda); + return OperationType.Operand; + } + + private OperationType HandleOpenBrace(ParseContext context, out ParseErrorType errorType) { + TokenStream tokens = context.Tokens; + errorType = ParseErrorType.None; + + // Separate expression from function call. In case of + // function call previous token is either closing indexer + // brace or closing function brace. Identifier with brace + // is handled up above. + // Indentifier followed by a brace needs to be analyzed + // so we can tell between previous expression that ended + // with identifier and identifier that is a function name: + // + // a <- 2*b + // (expression) + // + // in this case b is not a function name. Similarly, + // + // a <- 2*b[1] + // (expression) + // + // is not a function call operator over b[1]. + + if (_operators.Count > 1 || _operands.Count > 0) { + // We are not in the beginning of the expression + if (tokens.PreviousToken.TokenType == RTokenType.CloseBrace || + tokens.PreviousToken.TokenType == RTokenType.CloseSquareBracket || + tokens.PreviousToken.TokenType == RTokenType.CloseSquareBracket || + tokens.PreviousToken.TokenType == RTokenType.Identifier) { + FunctionCall functionCall = new FunctionCall(); + functionCall.Parse(context, null); + + errorType = HandleFunctionOrIndexer(functionCall); + return OperationType.Function; + } + } + + Group group = new Group(); + group.Parse(context, null); + + _operands.Push(group); + return OperationType.Operand; + } + + private OperationType HandleSquareBrackets(ParseContext context, out ParseErrorType errorType) { + Indexer indexer = new Indexer(); + indexer.Parse(context, null); + + errorType = HandleFunctionOrIndexer(indexer); + return OperationType.Function; + } + + private OperationType HandleOperator(ParseContext context, out ParseErrorType errorType) { + bool isUnary; + errorType = this.HandleOperator(context, null, out isUnary); + return isUnary ? OperationType.UnaryOperator : OperationType.BinaryOperator; + } + + private OperationType HandleKeyword(ParseContext context, out ParseErrorType errorType) { + errorType = ParseErrorType.None; + + string keyword = context.TextProvider.GetText(context.Tokens.CurrentToken); + if (IsTerminatingKeyword(keyword)) { + return OperationType.EndOfExpression; + } + + errorType = HandleKeyword(context, keyword); + if (errorType == ParseErrorType.None) { + return OperationType.Operand; + } + + return _previousOperationType; + } + + private ParseErrorType HandleKeyword(ParseContext context, string keyword) { + ParseErrorType errorType = ParseErrorType.None; + + if (keyword.Equals("function", StringComparison.Ordinal)) { + // Special case 'exp <- function(...) { } + FunctionDefinition funcDef = new FunctionDefinition(); + funcDef.Parse(context, null); + + // Add to the stack even if it has errors in order + // to avoid extra errors + _operands.Push(funcDef); + } else if (keyword.Equals("if", StringComparison.Ordinal)) { + // If needs to know parent expression since + // it must figure out how to handle 'else' + // when if is without { }. + context.Expressions.Push(this); + + InlineIf inlineIf = new InlineIf(); + inlineIf.Parse(context, null); + + context.Expressions.Pop(); + + // Add to the stack even if it has errors in order + // to avoid extra errors + _operands.Push(inlineIf); + } else { + errorType = ParseErrorType.UnexpectedToken; + } + + return errorType; + } + + private bool IsTerminatingKeyword(string s) { + if (_terminatingKeyword == null) { + return false; + } + + return s.Equals(_terminatingKeyword, StringComparison.Ordinal); + } + + private ParseErrorType HandleFunctionOrIndexer(IRValueNode operatorNode) { + // Indexing or function call is performed on the topmost operand which + // generally should be a variable or a node that evaluates to it. + // However, we leave syntax check to separate code. + + IRValueNode operand = this.SafeGetOperand(); + if (operand == null) { + // Oddly, no operand + return ParseErrorType.IndentifierExpected; + } + + operatorNode.AppendChild(operand); + _operands.Push(operatorNode); + + return ParseErrorType.None; + } + + private ParseErrorType HandleOperator(ParseContext context, IAstNode parent, out bool isUnary) { + ParseErrorType errorType = ParseErrorType.None; + + // If operands stack is empty the operator is unary. + // If operator is preceded by another operator, + // it is interpreted as unary. + + TokenOperator currentOperator = new TokenOperator(_operands.Count == 0); + + currentOperator.Parse(context, null); + isUnary = currentOperator.IsUnary; + + IOperator lastOperator = _operators.Peek(); + if (isUnary && lastOperator != null && lastOperator.IsUnary) { + // !!!x is treated as !(!(!x))) + // Step back and re-parse as expression + context.Tokens.Position -= 1; + var exp = new Expression(inGroup: false); + if (exp.Parse(context, null)) { + _operands.Push(exp); + return ParseErrorType.None; + } + } + + if (currentOperator.Precedence <= lastOperator.Precedence && + !(currentOperator.OperatorType == lastOperator.OperatorType && currentOperator.Association == Association.Right)) { + // New operator has lower or equal precedence. We need to make a tree from + // the topmost operator and its operand(s). Example: a*b+c. + has lower priority + // and a and b should be on the stack along with * on the operator stack. + // Repeat until there are no more higher precendece operators on the stack. + + errorType = this.ProcessHigherPrecendenceOperators(context, currentOperator); + } + + if (errorType == ParseErrorType.None) { + _operators.Push(currentOperator); + } + + return errorType; + } + + private ParseErrorType ProcessHigherPrecendenceOperators(ParseContext context, IOperator currentOperator) { + Debug.Assert(_operators.Count > 1); + ParseErrorType errorType = ParseErrorType.None; + Association association = currentOperator.Association; + + // At least one operator above sentinel is on the stack. + do { + errorType = MakeNode(context); + if (errorType == ParseErrorType.None) { + IOperator nextOperatorNode = _operators.Peek(); + + if (association == Association.Left && nextOperatorNode.Precedence <= currentOperator.Precedence) { + break; + } + + if (association == Association.Right && nextOperatorNode.Precedence < currentOperator.Precedence) { + break; + } + } + } while (_operators.Count > 1 && errorType == ParseErrorType.None); + + return errorType; + } + + private ParseErrorType MakeNode(ParseContext context) { + IOperator operatorNode = _operators.Pop(); + + IRValueNode rightOperand = this.SafeGetOperand(); + if (rightOperand == null) { + // Oddly, no operands + return ParseErrorType.RightOperandExpected; + } + + if (operatorNode.IsUnary) { + operatorNode.AppendChild(rightOperand); + operatorNode.RightOperand = rightOperand; + } else { + IRValueNode leftOperand = this.SafeGetOperand(); + if (leftOperand == null) { + // Operand is missing in expression like x <- []. + // Operator on top of the stack is <- since [] was not + // successfully parsed. So we need to mark right operand + // as error token. + context.AddError(new ParseError(ParseErrorType.LeftOperandExpected, ErrorLocation.Token, GetIndexerOrFunctionErrorRange(context, operatorNode))); + return ParseErrorType.LeftOperandExpected; + } + + operatorNode.LeftOperand = leftOperand; + operatorNode.RightOperand = rightOperand; + + operatorNode.AppendChild(leftOperand); + operatorNode.AppendChild(rightOperand); + } + + _operands.Push(operatorNode); + + return ParseErrorType.None; + } + + private IRValueNode SafeGetOperand() { + return _operands.Count > 0 ? _operands.Pop() : null; + } + + private static IRValueNode CreateConstant(ParseContext context) { + TokenStream tokens = context.Tokens; + RToken currentToken = tokens.CurrentToken; + IRValueNode term = null; + + switch (currentToken.TokenType) { + case RTokenType.NaN: + case RTokenType.Infinity: + case RTokenType.Number: + term = new NumericalValue(); + break; + + case RTokenType.Complex: + term = new ComplexValue(); + break; + + case RTokenType.Logical: + term = new LogicalValue(); + break; + + case RTokenType.String: + term = new StringValue(); + break; + + case RTokenType.Null: + term = new NullValue(); + break; + + case RTokenType.Missing: + term = new MissingValue(); + break; + } + + Debug.Assert(term != null); + term.Parse(context, null); + return term; + } + } +} diff --git a/src/R/Core/Impl/AST/Operators/TokenOperator.cs b/src/R/Core/Impl/AST/Operators/TokenOperator.cs index 14e631065..4609d9377 100644 --- a/src/R/Core/Impl/AST/Operators/TokenOperator.cs +++ b/src/R/Core/Impl/AST/Operators/TokenOperator.cs @@ -38,7 +38,6 @@ public override bool Parse(ParseContext context, IAstNode parent) { _operatorType = TokenOperator.GetOperatorType(context.TextProvider.GetText(context.Tokens.CurrentToken)); this.OperatorToken = RParser.ParseToken(context, this); - this.Association = OperatorAssociation.GetAssociation(_operatorType); bool isUnary; @@ -48,7 +47,6 @@ public override bool Parse(ParseContext context, IAstNode parent) { _isUnary = isUnary; } - return base.Parse(context, parent); } diff --git a/src/R/Core/Impl/AST/Statements/Conditionals/If.cs b/src/R/Core/Impl/AST/Statements/Conditionals/If.cs index b66d3b2d8..e0a469cb4 100644 --- a/src/R/Core/Impl/AST/Statements/Conditionals/If.cs +++ b/src/R/Core/Impl/AST/Statements/Conditionals/If.cs @@ -3,7 +3,6 @@ using Microsoft.R.Core.AST.Definitions; using Microsoft.R.Core.AST.Expressions; using Microsoft.R.Core.AST.Operands; -using Microsoft.R.Core.AST.Scopes; using Microsoft.R.Core.Parser; using Microsoft.R.Core.Tokens; @@ -33,12 +32,6 @@ public If() : public KeywordScopeStatement Else { get; private set; } - /// - /// If true then if/else construct is sensitive to - /// changes in line breaks before 'else'. - /// - public bool LineBreakSensitive { get; private set; } - public override bool Parse(ParseContext context, IAstNode parent) { // First parse base which should pick up keyword, braces, inner // expression and either full or simple (single statement) scope @@ -51,36 +44,14 @@ public override bool Parse(ParseContext context, IAstNode parent) { TokenStream tokens = context.Tokens; if (tokens.CurrentToken.IsKeywordText(context.TextProvider, "else")) { - // Language spec says: - // - // When the if statement is not in a block the else, if present, - // must appear on the same line as the end of statement2. Otherwise - // the new line at the end of statement2 completes the if and yields - // a syntactically complete statement that is evaluated. - // . - // - // However, this appears only to be applicable when conditinal is in - // global scope (i.e. not inside any { } block). Such as in the R - // console command line or in script that looks like - // - // x <- if(y < 0) 1 - // else 0 - // - // in which case 'else' is dangling. - - bool isSimpleScope = this.Scope.OpenCurlyBrace == null; bool allowLineBreak = AllowLineBreakBeforeElse(context); - - if (isSimpleScope && !allowLineBreak) { - LineBreakSensitive = true; - + if (!allowLineBreak) { // Verify that there is no line break before the 'else' if (context.Tokens.IsLineBreakAfter(context.TextProvider, tokens.Position - 1)) { context.AddError(new ParseError(ParseErrorType.UnexpectedToken, ErrorLocation.Token, tokens.CurrentToken)); return true; } } - this.Else = new KeywordScopeStatement(allowsSimpleScope: true); return this.Else.Parse(context, this); } diff --git a/src/R/Core/Impl/Formatting/FormattingScope.cs b/src/R/Core/Impl/Formatting/FormattingScope.cs index dfcad7568..87d78e0c9 100644 --- a/src/R/Core/Impl/Formatting/FormattingScope.cs +++ b/src/R/Core/Impl/Formatting/FormattingScope.cs @@ -1,4 +1,5 @@ -using System.Diagnostics; +using System.Collections.Generic; +using System.Diagnostics; using Microsoft.Languages.Core.Formatting; using Microsoft.Languages.Core.Text; using Microsoft.Languages.Core.Tokens; @@ -16,6 +17,11 @@ internal sealed class FormattingScope { public int SuppressLineBreakCount { get; set; } + /// + /// Control block defining keywords indented in this scope + /// + public List Keywords { get; } = new List(); + public FormattingScope(IndentBuilder indentBuilder) { _indentBuilder = indentBuilder; } diff --git a/src/R/Core/Impl/Formatting/RFormatter.cs b/src/R/Core/Impl/Formatting/RFormatter.cs index 9aa24fee7..9899296b7 100644 --- a/src/R/Core/Impl/Formatting/RFormatter.cs +++ b/src/R/Core/Impl/Formatting/RFormatter.cs @@ -145,8 +145,9 @@ private void CloseFormattingScope() { if (SuppressLineBreakCount == 0 && !_tokens.IsEndOfStream()) { // We insert line break after } unless next token is comma // (scope is in the argument list) or a closing brace - // (last parameter in a function or indexer). - if (!IsClosingToken(_tokens.CurrentToken.TokenType) && !IsInArguments()) { + // (last parameter in a function or indexer) or it is followed by 'else' + // so 'else' does not get separated from 'if'. + if (!IsClosingToken(_tokens.CurrentToken) && !IsInArguments()) { _tb.SoftLineBreak(); } } @@ -201,6 +202,7 @@ private void AppendKeyword() { AppendToken(leadingSpace: LeadingSpaceNeeded(), trailingSpace: trailingSpace); if (controlBlock) { + _formattingScopes.Peek().Keywords.Add(keyword); // Keyword defines optional condition // and possibly new '{ }' scope AppendControlBlock(keyword); @@ -369,14 +371,34 @@ private static bool IsOpenBraceToken(RTokenType tokenType) { return false; } - private static bool IsClosingToken(RTokenType tokenType) { - switch (tokenType) { + private bool IsClosingToken(RToken token) { + switch (token.TokenType) { case RTokenType.Comma: case RTokenType.CloseBrace: case RTokenType.CloseSquareBracket: case RTokenType.CloseDoubleSquareBracket: case RTokenType.Semicolon: return true; + + case RTokenType.Keyword: + if (_tokens.PreviousToken.TokenType == RTokenType.CloseCurlyBrace && + _textProvider.GetText(token) == "else") { + // Check what was the most recent keyword indented in this scope. + // If it is 'if' then there should not be a break between + // the closing brace and the 'else'. This prevents false positives + // in constructs like + // + // if(TRUE) + // repeat { + // } else + // + // in which else should be placed at the next line and indented with the if. + var scope = _formattingScopes.Peek(); + if (scope.Keywords.Count > 0) { + return scope.Keywords[scope.Keywords.Count - 1] == "if"; + } + } + break; } return false; @@ -386,8 +408,7 @@ private void AppendOperator() { string text = _textProvider.GetText(_tokens.CurrentToken); if (TokenOperator.IsUnaryOperator(_tokens, TokenOperator.GetOperatorType(text))) { AppendToken(leadingSpace: true, trailingSpace: false); - } - else if (IsOperatorWithoutSpaces(text)) { + } else if (IsOperatorWithoutSpaces(text)) { AppendToken(leadingSpace: false, trailingSpace: false); } else { AppendToken(leadingSpace: true, trailingSpace: true); @@ -400,7 +421,11 @@ private string AppendToken(bool leadingSpace, bool trailingSpace) { } string text = _textProvider.GetText(_tokens.CurrentToken); - _tb.AppendText(text); + if (text.IndexOfAny(new char[] { '\r', '\n' }) >= 0) { + _tb.AppendPreformattedText(text); + } else { + _tb.AppendText(text); + } HandleBrace(); _tokens.MoveToNextToken(); @@ -617,7 +642,7 @@ private void AppendTextBeforeToken() { private void AppendComma() { bool trailingSpace; - if (IsClosingToken(_tokens.NextToken.TokenType)) { + if (IsClosingToken(_tokens.NextToken)) { trailingSpace = false; } else { trailingSpace = _options.SpaceAfterComma; @@ -627,7 +652,7 @@ private void AppendComma() { } private bool ShouldAppendTextBeforeToken() { - if (IsClosingToken(_tokens.CurrentToken.TokenType)) { + if (IsClosingToken(_tokens.CurrentToken)) { return false; } diff --git a/src/R/Core/Impl/Microsoft.R.Core.csproj b/src/R/Core/Impl/Microsoft.R.Core.csproj index df3607c02..3df11cd98 100644 --- a/src/R/Core/Impl/Microsoft.R.Core.csproj +++ b/src/R/Core/Impl/Microsoft.R.Core.csproj @@ -1,202 +1,202 @@ - - - - - - Debug - AnyCPU - {0C4BCE1D-3CB8-4E2A-9252-58784D7F26A5} - Library - Properties - Microsoft.R.Core - Microsoft.R.Core - v4.6 - 512 - SAK - SAK - SAK - SAK - - - - - - $(RootDirectory)\obj\ - $(RootDirectory)\bin\ - $(BaseIntermediateOutputPath)\$(Configuration)\$(AssemblyName)\ - $(BaseOutputPath)\$(Configuration)\ - - - - - - - - - - Properties\GlobalAssemblyInfo.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {25cd8690-6208-4740-b123-6dbce6b9444a} - Microsoft.Languages.Core - - - - - - - - Microsoft - StrongName - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - + + + + + + Debug + AnyCPU + {0C4BCE1D-3CB8-4E2A-9252-58784D7F26A5} + Library + Properties + Microsoft.R.Core + Microsoft.R.Core + v4.6 + 512 + SAK + SAK + SAK + SAK + + + + + + $(RootDirectory)\obj\ + $(RootDirectory)\bin\ + $(BaseIntermediateOutputPath)\$(Configuration)\$(AssemblyName)\ + $(BaseOutputPath)\$(Configuration)\ + + + + + + + + + + Properties\GlobalAssemblyInfo.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {25cd8690-6208-4740-b123-6dbce6b9444a} + Microsoft.Languages.Core + + + + + + + + Microsoft + StrongName + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/src/R/Core/Test/Formatting/FormatConditionalsTest.cs b/src/R/Core/Test/Formatting/FormatConditionalsTest.cs index 21dac2e11..ca9cbe9f5 100644 --- a/src/R/Core/Test/Formatting/FormatConditionalsTest.cs +++ b/src/R/Core/Test/Formatting/FormatConditionalsTest.cs @@ -62,8 +62,7 @@ public void FormatConditionalTest04() { @"if (TRUE) { 1 -} -else +} else { 2 } @@ -83,12 +82,10 @@ public void FormatConditionalTest05() { @"if (TRUE) { 1 -} -else if (FALSE) +} else if (FALSE) { 2 -} -else +} else { 3 } diff --git a/src/R/Core/Test/Microsoft.R.Core.Test.csproj b/src/R/Core/Test/Microsoft.R.Core.Test.csproj index 6a1867538..6473599e7 100644 --- a/src/R/Core/Test/Microsoft.R.Core.Test.csproj +++ b/src/R/Core/Test/Microsoft.R.Core.Test.csproj @@ -1,190 +1,190 @@ - - - - Debug - AnyCPU - {58D86BE4-FA8B-4F59-91FE-A9B348C70ED2} - Library - Properties - Microsoft.R.Core.Test - Microsoft.R.Core.Test - v4.6 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest - SAK - SAK - SAK - SAK - - - - $(RootDirectory)\obj\ - $(RootDirectory)\bin\ - $(BaseIntermediateOutputPath)\$(Configuration)\$(AssemblyName)\ - $(BaseOutputPath)\$(Configuration)\ - - - - ..\..\..\..\NugetPackages\FluentAssertions.4.1.1\lib\net45\FluentAssertions.dll - True - - - ..\..\..\..\NugetPackages\FluentAssertions.4.1.1\lib\net45\FluentAssertions.Core.dll - True - - - - - - ..\..\..\..\NugetPackages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll - True - - - ..\..\..\..\NugetPackages\xunit.assert.2.2.0-beta1-build3239\lib\dotnet\xunit.assert.dll - True - - - ..\..\..\..\NugetPackages\xunit.extensibility.core.2.2.0-beta1-build3239\lib\dotnet\xunit.core.dll - True - - - ..\..\..\..\NugetPackages\xunit.extensibility.execution.2.2.0-beta1-build3239\lib\net45\xunit.execution.desktop.dll - True - - - - - - - - - - - - - {fc4aad0a-13b9-49ee-a59c-f03142958170} - Microsoft.Common.Core.Test - - - {25cd8690-6208-4740-b123-6dbce6b9444a} - Microsoft.Languages.Core - - - {ee2504a4-4666-460b-8552-5b342718cb02} - Microsoft.Languages.Core.Test - - - {5ef2ad64-d6fe-446b-b350-8c7f0df0834d} - Microsoft.UnitTests.Core - - - {0c4bce1d-3cb8-4e2a-9252-58784d7f26a5} - Microsoft.R.Core - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Microsoft - StrongName - - - - - - + + + + Debug + AnyCPU + {58D86BE4-FA8B-4F59-91FE-A9B348C70ED2} + Library + Properties + Microsoft.R.Core.Test + Microsoft.R.Core.Test + v4.6 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + SAK + SAK + SAK + SAK + + + + $(RootDirectory)\obj\ + $(RootDirectory)\bin\ + $(BaseIntermediateOutputPath)\$(Configuration)\$(AssemblyName)\ + $(BaseOutputPath)\$(Configuration)\ + + + + ..\..\..\..\NugetPackages\FluentAssertions.4.1.1\lib\net45\FluentAssertions.dll + True + + + ..\..\..\..\NugetPackages\FluentAssertions.4.1.1\lib\net45\FluentAssertions.Core.dll + True + + + + + + ..\..\..\..\NugetPackages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll + True + + + ..\..\..\..\NugetPackages\xunit.assert.2.2.0-beta1-build3239\lib\dotnet\xunit.assert.dll + True + + + ..\..\..\..\NugetPackages\xunit.extensibility.core.2.2.0-beta1-build3239\lib\dotnet\xunit.core.dll + True + + + ..\..\..\..\NugetPackages\xunit.extensibility.execution.2.2.0-beta1-build3239\lib\net45\xunit.execution.desktop.dll + True + + + + + + + + + + + + + {fc4aad0a-13b9-49ee-a59c-f03142958170} + Microsoft.Common.Core.Test + + + {25cd8690-6208-4740-b123-6dbce6b9444a} + Microsoft.Languages.Core + + + {ee2504a4-4666-460b-8552-5b342718cb02} + Microsoft.Languages.Core.Test + + + {5ef2ad64-d6fe-446b-b350-8c7f0df0834d} + Microsoft.UnitTests.Core + + + {0c4bce1d-3cb8-4e2a-9252-58784d7f26a5} + Microsoft.R.Core + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Microsoft + StrongName + + + + + + \ No newline at end of file diff --git a/src/R/Core/Test/Parser/ParseExpressionsTest.cs b/src/R/Core/Test/Parser/ParseExpressionsTest.cs index 5078b4e1c..291901c7e 100644 --- a/src/R/Core/Test/Parser/ParseExpressionsTest.cs +++ b/src/R/Core/Test/Parser/ParseExpressionsTest.cs @@ -168,5 +168,99 @@ Variable [c] (c+1)"; ParserTest.VerifyParse(expected, content); } + + [Test] + [Category.R.Parser] + public void ParseMultipleTilde() { + string expected = +@"GlobalScope [Global] + ExpressionStatement [x ~ ~ ~ y] + Expression [x ~ ~ ~ y] + TokenOperator [~ [2...3)] + Variable [x] + TokenNode [~ [2...3)] + TokenOperator [~ [4...5)] + TokenNode [~ [4...5)] + Expression [~ y] + TokenOperator [~ [6...7)] + TokenNode [~ [6...7)] + Variable [y] +"; + string content = "x ~ ~ ~ y"; + + ParserTest.VerifyParse(expected, content); + } + + [Test] + [Category.R.Parser] + public void ParseMultipleUnary01() { + string expected = +@"GlobalScope [Global] + ExpressionStatement [!!!TRUE] + Expression [!!!TRUE] + TokenOperator [! [0...1)] + TokenNode [! [0...1)] + Expression [!!TRUE] + TokenOperator [! [1...2)] + TokenNode [! [1...2)] + Expression [!TRUE] + TokenOperator [! [2...3)] + TokenNode [! [2...3)] + LogicalValue [TRUE [3...7)] +"; + string content = "!!!TRUE"; + + ParserTest.VerifyParse(expected, content); + } + + [Test] + [Category.R.Parser] + public void ParseMultipleUnary02() { + string expected = +@"GlobalScope [Global] + ExpressionStatement [1-+-+-3] + Expression [1-+-+-3] + TokenOperator [- [1...2)] + NumericalValue [1 [0...1)] + TokenNode [- [1...2)] + TokenOperator [+ [2...3)] + TokenNode [+ [2...3)] + Expression [-+-3] + TokenOperator [- [3...4)] + TokenNode [- [3...4)] + Expression [+-3] + TokenOperator [+ [4...5)] + TokenNode [+ [4...5)] + NumericalValue [-3 [5...7)] +"; + string content = "1-+-+-3"; + + ParserTest.VerifyParse(expected, content); + } + + [Test] + [Category.R.Parser] + public void ParseMultipleUnary03() { + string expected = +@"GlobalScope [Global] + ExpressionStatement [1/+-+-3] + Expression [1/+-+-3] + TokenOperator [/ [1...2)] + NumericalValue [1 [0...1)] + TokenNode [/ [1...2)] + TokenOperator [+ [2...3)] + TokenNode [+ [2...3)] + Expression [-+-3] + TokenOperator [- [3...4)] + TokenNode [- [3...4)] + Expression [+-3] + TokenOperator [+ [4...5)] + TokenNode [+ [4...5)] + NumericalValue [-3 [5...7)] +"; + string content = "1/+-+-3"; + + ParserTest.VerifyParse(expected, content); + } } } diff --git a/src/R/Core/Test/Parser/ParseIfElseTest.cs b/src/R/Core/Test/Parser/ParseIfElseTest.cs index 36b06b4a6..aecd4ac0c 100644 --- a/src/R/Core/Test/Parser/ParseIfElseTest.cs +++ b/src/R/Core/Test/Parser/ParseIfElseTest.cs @@ -1,7 +1,5 @@ using System.Diagnostics.CodeAnalysis; -using FluentAssertions; using Microsoft.R.Core.AST; -using Microsoft.R.Core.AST.Statements.Conditionals; using Microsoft.R.Core.Test.Utility; using Microsoft.UnitTests.Core.XUnit; @@ -34,7 +32,6 @@ Variable [x] NumericalValue [1 [17...18)] "; AstRoot ast = ParserTest.VerifyParse(expected, "if(x < y) x <- x+1"); - ast.GetNodeOfTypeFromPosition(1).LineBreakSensitive.Should().BeFalse(); } [Test] @@ -65,7 +62,6 @@ Variable [x] TokenNode [} [21...22)] "; AstRoot ast = ParserTest.VerifyParse(expected, "if(x < y) { x <- x+1 }"); - ast.GetNodeOfTypeFromPosition(1).LineBreakSensitive.Should().BeFalse(); } [Test] @@ -110,7 +106,6 @@ Variable [x] TokenNode [} [41...42)] "; AstRoot ast = ParserTest.VerifyParse(expected, "if(x < y) { x <- x+1 } else { x <- x + 2 }"); - ast.GetNodeOfTypeFromPosition(1).LineBreakSensitive.Should().BeFalse(); } [Test] @@ -153,7 +148,6 @@ Variable [x] TokenNode [} [37...38)] "; AstRoot ast = ParserTest.VerifyParse(expected, "if(x < y) x <- x+1 else { x <- x + 2 }"); - ast.GetNodeOfTypeFromPosition(1).LineBreakSensitive.Should().BeTrue(); } [Test] @@ -194,7 +188,6 @@ Variable [x] NumericalValue [2 [33...34)] "; AstRoot ast = ParserTest.VerifyParse(expected, "if(x < y) x <- x+1 else x <- x + 2"); - ast.GetNodeOfTypeFromPosition(1).LineBreakSensitive.Should().BeTrue(); } [Test] @@ -237,7 +230,6 @@ Variable [x] TokenNode [} [41...42)] "; AstRoot ast = ParserTest.VerifyParse(expected, "if(x < y) \n x <- x+1 else \n { x <- x + 2 }"); - ast.GetNodeOfTypeFromPosition(1).LineBreakSensitive.Should().BeTrue(); } [Test] @@ -266,21 +258,19 @@ Variable [x] TokenNode [+ [18...19)] NumericalValue [1 [19...20)] TokenNode [} [21...22)] - KeywordScopeStatement [] - TokenNode [else [25...29)] - SimpleScope [32...42) - ExpressionStatement [x <- x + 2] - Expression [x <- x + 2] - TokenOperator [<- [34...36)] - Variable [x] - TokenNode [<- [34...36)] - TokenOperator [+ [39...40)] - Variable [x] - TokenNode [+ [39...40)] - NumericalValue [2 [41...42)] + ExpressionStatement [x <- x + 2] + Expression [x <- x + 2] + TokenOperator [<- [34...36)] + Variable [x] + TokenNode [<- [34...36)] + TokenOperator [+ [39...40)] + Variable [x] + TokenNode [+ [39...40)] + NumericalValue [2 [41...42)] + +UnexpectedToken Token [25...29) "; AstRoot ast = ParserTest.VerifyParse(expected, "if(x < y) { x <- x+1 } \n else \n x <- x + 2"); - ast.GetNodeOfTypeFromPosition(1).LineBreakSensitive.Should().BeFalse(); } [Test] @@ -322,7 +312,6 @@ Variable [a] TokenNode [) [30...31)] "; AstRoot ast = ParserTest.VerifyParse(expected, "func(if(x < y) 1 \n else \n 2, a)"); - ast.GetNodeOfTypeFromPosition(6).LineBreakSensitive.Should().BeFalse(); } [Test] @@ -382,7 +371,6 @@ Expression [2] UnexpectedToken Token [19...23) "; AstRoot ast = ParserTest.VerifyParse(expected, "x <- if(x < y) 1 \n else 2"); - ast.GetNodeOfTypeFromPosition(6).LineBreakSensitive.Should().BeTrue(); } [Test] @@ -425,7 +413,6 @@ Expression [2] TokenNode [) [34...35)] "; AstRoot ast = ParserTest.VerifyParse(expected, "x <- func(a = if(x < y) 1 \n else 2)"); - ast.GetNodeOfTypeFromPosition(15).LineBreakSensitive.Should().BeFalse(); } [Test] @@ -457,7 +444,6 @@ KeywordScopeStatement [] CloseCurlyBraceExpected AfterToken [25...29) "; AstRoot ast = ParserTest.VerifyParse(expected, "{if (x > 1)\r\n x <- 1\r\nelse\n"); - ast.GetNodeOfTypeFromPosition(2).LineBreakSensitive.Should().BeFalse(); } } } diff --git a/src/R/Core/Test/Utility/ParserTest.cs b/src/R/Core/Test/Utility/ParserTest.cs index 6504e820b..49fcddd90 100644 --- a/src/R/Core/Test/Utility/ParserTest.cs +++ b/src/R/Core/Test/Utility/ParserTest.cs @@ -1,35 +1,32 @@ -using System; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; -using FluentAssertions; -using Microsoft.Common.Core.Test.Utility; -using Microsoft.Languages.Core.Text; -using Microsoft.R.Core.AST; -using Microsoft.R.Core.Parser; -using Microsoft.R.Core.Utility; -using Xunit; - -namespace Microsoft.R.Core.Test.Utility { - [ExcludeFromCodeCoverage] - public static class ParserTest - { - public static AstRoot VerifyParse(string expected, string expression) - { - AstRoot ast = RParser.Parse(new TextStream(expression)); - ParserTest.CompareTrees(expected, ast); - return ast; - } - - public static void CompareTrees(string expected, AstRoot actualTree) - { - AstWriter astWriter = new AstWriter(); - string actual = astWriter.WriteTree(actualTree); - - string expectedLine, actualLine; - int index; - int result = BaselineCompare.CompareLines(expected, actual, out expectedLine, out actualLine, out index); - - result.Should().Be(0, "Line at {0} should be {1}, but found {2}, different at position {3}", result, expectedLine, actualLine, index); - } - } -} +using System.Diagnostics.CodeAnalysis; +using FluentAssertions; +using Microsoft.Common.Core.Test.Utility; +using Microsoft.Languages.Core.Text; +using Microsoft.R.Core.AST; +using Microsoft.R.Core.Parser; +using Microsoft.R.Core.Utility; + +namespace Microsoft.R.Core.Test.Utility { + [ExcludeFromCodeCoverage] + public static class ParserTest + { + public static AstRoot VerifyParse(string expected, string expression) + { + AstRoot ast = RParser.Parse(new TextStream(expression)); + ParserTest.CompareTrees(expected, ast); + return ast; + } + + public static void CompareTrees(string expected, AstRoot actualTree) + { + AstWriter astWriter = new AstWriter(); + string actual = astWriter.WriteTree(actualTree); + + string expectedLine, actualLine; + int index; + int result = BaselineCompare.CompareLines(expected, actual, out expectedLine, out actualLine, out index); + + result.Should().Be(0, "Line at {0} should be {1}, but found {2}, different at position {3}", result, expectedLine, actualLine, index); + } + } +} diff --git a/src/R/Editor/Application.Test/Completion/IntellisenseTest.cs b/src/R/Editor/Application.Test/Completion/IntellisenseTest.cs index 620ad737a..2e9e0847a 100644 --- a/src/R/Editor/Application.Test/Completion/IntellisenseTest.cs +++ b/src/R/Editor/Application.Test/Completion/IntellisenseTest.cs @@ -1,12 +1,16 @@ using System.Diagnostics.CodeAnalysis; +using System.IO; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using FluentAssertions; +using Microsoft.Common.Core; using Microsoft.Languages.Editor.Shell; using Microsoft.R.Editor.Application.Test.TestShell; using Microsoft.R.Editor.ContentType; using Microsoft.R.Host.Client; using Microsoft.R.Host.Client.Test.Script; +using Microsoft.R.Support.Settings; using Microsoft.UnitTests.Core.XUnit; using Xunit; @@ -142,6 +146,82 @@ public void R_LoadedPackageFunctionCompletion() { } } + [Test] + [Category.Interactive] + public void R_CompletionFiles() { + using (var script = new TestScript(RContentTypeDefinition.ContentType)) { + string asmPath = Assembly.GetExecutingAssembly().GetAssemblyPath(); + RToolsSettings.Current.WorkingDirectory = Path.GetDirectoryName(asmPath); + + script.DoIdle(100); + script.Type("x <- \""); + script.DoIdle(1000); + script.Type("{TAB}"); + script.DoIdle(100); + + var session = script.GetCompletionSession(); + session.Should().NotBeNull(); + script.DoIdle(200); + + var list = session.SelectedCompletionSet.Completions.ToList(); + var item = list.FirstOrDefault(x => x.DisplayText == "ItemTemplates"); + item.Should().NotBeNull(); + } + } + + [Test] + [Category.Interactive] + public void R_CompletionFunctionBraces01() { + using (var script = new TestScript(RContentTypeDefinition.ContentType)) { + var provider = EditorShell.Current.ExportProvider.GetExportedValue(); + using (var hostScript = new RHostScript(provider)) { + + string message = null; + hostScript.Session.Output += (s, e) => { + message = e.Message; + }; + + script.DoIdle(100); + script.Type("instal"); + script.DoIdle(1000); + script.Type("{TAB}"); + script.DoIdle(100); + + string actual = script.EditorText; + actual.Should().Be("install.packages()"); + EditorWindow.CoreEditor.View.Caret.Position.BufferPosition.Position.Should().Be(actual.Length - 1); + + message.Should().BeNull(); + } + } + } + + [Test] + [Category.Interactive] + public void R_CompletionFunctionBraces02() { + using (var script = new TestScript(RContentTypeDefinition.ContentType)) { + var provider = EditorShell.Current.ExportProvider.GetExportedValue(); + using (var hostScript = new RHostScript(provider)) { + + string message = null; + hostScript.Session.Output += (s, e) => { + message = e.Message; + }; + + script.DoIdle(100); + script.Type("bas"); + script.DoIdle(1000); + script.Type("{TAB}"); + script.DoIdle(100); + + string actual = script.EditorText; + actual.Should().Be("base"); + + message.Should().BeNull(); + } + } + } + //[Test] //[Category.Interactive] public void R_DeclaredVariablesCompletion() { diff --git a/src/R/Editor/Application.Test/Signatures/SignatureTest.cs b/src/R/Editor/Application.Test/Signatures/SignatureTest.cs index a18c085a1..5c11d5c94 100644 --- a/src/R/Editor/Application.Test/Signatures/SignatureTest.cs +++ b/src/R/Editor/Application.Test/Signatures/SignatureTest.cs @@ -26,7 +26,7 @@ public void R_SignatureParametersMatch() { FunctionIndexUtility.GetFunctionInfoAsync("lm").Wait(3000); script.Type("x <- lm("); - script.DoIdle(1000); + script.DoIdle(2000); ISignatureHelpSession session = script.GetSignatureSession(); session.Should().NotBeNull(); diff --git a/src/R/Editor/Impl/Completion/Engine/RCompletionEngine.cs b/src/R/Editor/Impl/Completion/Engine/RCompletionEngine.cs index a8989e4d0..e59c5e393 100644 --- a/src/R/Editor/Impl/Completion/Engine/RCompletionEngine.cs +++ b/src/R/Editor/Impl/Completion/Engine/RCompletionEngine.cs @@ -30,7 +30,6 @@ internal static class RCompletionEngine { /// List of completion entries for a given location in the AST public static IReadOnlyCollection GetCompletionForLocation(RCompletionContext context, bool autoShownCompletion) { List providers = new List(); - IREditorDocument document = REditorDocument.FindInProjectedBuffers(context.Session.TextView.TextBuffer); if (context.AstRoot.Comments.Contains(context.Position)) { // No completion in comments @@ -74,9 +73,7 @@ public static IReadOnlyCollection GetCompletionForLoca } } - if (document != null && document.IsTransient) { - providers.Add(new WorkspaceVariableCompletionProvider()); - } + providers.Add(new WorkspaceVariableCompletionProvider()); return providers; } diff --git a/src/R/Editor/Impl/Completion/Providers/FilesCompletionProvider.cs b/src/R/Editor/Impl/Completion/Providers/FilesCompletionProvider.cs index 85c4367ca..5298ebf81 100644 --- a/src/R/Editor/Impl/Completion/Providers/FilesCompletionProvider.cs +++ b/src/R/Editor/Impl/Completion/Providers/FilesCompletionProvider.cs @@ -15,7 +15,7 @@ namespace Microsoft.R.Editor.Completion.Providers { /// Provides list of files and folder in the current directory /// public class FilesCompletionProvider : IRCompletionListProvider { - [Import] + [Import(AllowDefault = true)] private IImagesProvider ImagesProvider { get; set; } private string _directory; @@ -53,7 +53,7 @@ public IReadOnlyCollection GetEntries(RCompletionContext context) { foreach (string file in Directory.GetFiles(directory)) { FileInfo di = new FileInfo(file); if (!di.Attributes.HasFlag(FileAttributes.Hidden) && !di.Attributes.HasFlag(FileAttributes.System)) { - ImageSource fileGlyph = ImagesProvider.GetFileIcon(file); + ImageSource fileGlyph = ImagesProvider?.GetFileIcon(file); string fileName = Path.GetFileName(file); completions.Add(new RCompletion(fileName, fileName, string.Empty, fileGlyph)); } diff --git a/src/R/Editor/Impl/Completion/RCompletionController.cs b/src/R/Editor/Impl/Completion/RCompletionController.cs index 5bdceb8be..fd2a24287 100644 --- a/src/R/Editor/Impl/Completion/RCompletionController.cs +++ b/src/R/Editor/Impl/Completion/RCompletionController.cs @@ -14,7 +14,10 @@ using Microsoft.VisualStudio.Text.Editor; namespace Microsoft.R.Editor.Completion { + using System.Threading.Tasks; using Core.Tokens; + using Host.Client; + using Languages.Editor.Shell; using Completion = Microsoft.VisualStudio.Language.Intellisense.Completion; /// @@ -164,7 +167,7 @@ public override bool IsCommitChar(char typedChar) { if (char.IsWhiteSpace(typedChar)) { IREditorDocument document = REditorDocument.TryFromTextBuffer(TextView.TextBuffer); if (document != null && document.IsTransient) { - return typedChar == '\t'; + return CompletionSession.SelectedCompletionSet.SelectionStatus.IsSelected && typedChar == '\t'; } if (typedChar == '\n' || typedChar == '\r') { @@ -333,6 +336,18 @@ protected override void UpdateInsertionText() { } } + protected override void OnCompletionSessionCommitted(object sender, EventArgs eventArgs) { + if (CompletionSession != null) { + if (CompletionSession.CompletionSets.Count > 0) { + Completion completion = CompletionSession.SelectedCompletionSet.SelectionStatus.Completion; + string name = completion.InsertionText; + SnapshotPoint position = CompletionSession.TextView.Caret.Position.BufferPosition; + Task.Run(async () => await InsertFunctionBraces(position, name)); + } + } + base.OnCompletionSessionCommitted(sender, eventArgs); + } + /// /// Overrides default session since we want to track signature as caret moves. /// Default signature session dismisses when caret changes position. @@ -345,5 +360,34 @@ public override void TriggerSignatureHelp() { SignatureBroker.TriggerSignatureHelp(TextView, trackingPoint, trackCaret: false); } } + + private async Task IsFunction(string name) { + var sessionProvider = EditorShell.Current.ExportProvider.GetExportedValue(); + IRSession session = sessionProvider.GetOrCreate(GuidList.InteractiveWindowRSessionGuid, null); + if (session != null) { + using (IRSessionEvaluation eval = await session.BeginEvaluationAsync(isMutating: false)) { + REvaluationResult result = await eval.EvaluateAsync($"tryCatch(is.function({name}), error = function(e) {{ }})"); + if (result.ParseStatus == RParseStatus.OK && + !string.IsNullOrEmpty(result.StringResult) && + (result.StringResult == "T" || result.StringResult == "TRUE")) { + return true; + } + } + } + return false; + } + + private async Task InsertFunctionBraces(SnapshotPoint position, string name) { + bool function = await IsFunction(name); + if (function) { + EditorShell.DispatchOnUIThread(() => { + if (TextView.TextBuffer.CurrentSnapshot.Version.VersionNumber == position.Snapshot.Version.VersionNumber) { + TextView.TextBuffer.Insert(position.Position, "()"); + TextView.Caret.MoveTo(new SnapshotPoint(TextView.TextBuffer.CurrentSnapshot, position.Position + 1)); + EditorShell.DispatchOnUIThread(() => TriggerSignatureHelp()); + } + }); + } + } } } \ No newline at end of file diff --git a/src/R/Editor/Impl/Data/IRSessionDataObject.cs b/src/R/Editor/Impl/Data/IRSessionDataObject.cs index 51a3c17d2..5b905705e 100644 --- a/src/R/Editor/Impl/Data/IRSessionDataObject.cs +++ b/src/R/Editor/Impl/Data/IRSessionDataObject.cs @@ -3,12 +3,6 @@ namespace Microsoft.R.Editor.Data { public interface IRSessionDataObject { - /// - /// Index returned from the evaluation provider. - /// DebugEvaluationResult returns in ascending order - /// - int Index { get; } - string Name { get; } string Value { get; } @@ -27,8 +21,6 @@ public interface IRSessionDataObject { string Expression { get; } - bool CanShowDetail { get; } - Task> GetChildrenAsync(); } } diff --git a/src/R/Editor/Impl/Data/RSessionDataObject.cs b/src/R/Editor/Impl/Data/RSessionDataObject.cs index 1078fc793..023463220 100644 --- a/src/R/Editor/Impl/Data/RSessionDataObject.cs +++ b/src/R/Editor/Impl/Data/RSessionDataObject.cs @@ -12,48 +12,71 @@ namespace Microsoft.R.Editor.Data { /// Model for variable tree grid, that provides UI customization of /// public class RSessionDataObject : IRSessionDataObject { - private readonly DebugEvaluationResult _evaluation; - private static readonly char[] NameTrimChars = new char[] { '$' }; private static readonly string HiddenVariablePrefix = "."; private static readonly char[] NewLineDelimiter = new char[] { '\r', '\n' }; - private readonly bool _truncateChildren; private readonly object syncObj = new object(); private Task> _getChildrenTask = null; - protected RSessionDataObject() { Index = -1; } + protected const int DefaultMaxReprLength = 100; + protected const int DefaultMaxGrandChildren = 20; + + protected RSessionDataObject() { + MaxReprLength = DefaultMaxReprLength; + } /// /// Create new instance of /// /// R session's evaluation result - /// true to truncate children returned by GetChildrenAsync - public RSessionDataObject(int index, DebugEvaluationResult evaluation, bool truncateChildren) { - _evaluation = evaluation; - _truncateChildren = truncateChildren; + public RSessionDataObject(DebugEvaluationResult evaluation, int? maxChildrenCount = null) : this() { + DebugEvaluation = evaluation; - Name = _evaluation.Name.TrimStart(NameTrimChars); + Name = DebugEvaluation.Name?.TrimStart(NameTrimChars); - if (_evaluation is DebugValueEvaluationResult) { - var valueEvaluation = (DebugValueEvaluationResult)_evaluation; + if (DebugEvaluation is DebugValueEvaluationResult) { + var valueEvaluation = (DebugValueEvaluationResult)DebugEvaluation; - Value = GetValue(valueEvaluation).Trim(); - ValueDetail = valueEvaluation.Representation.DPut; + Value = GetValue(valueEvaluation)?.Trim(); + ValueDetail = valueEvaluation.Representation.Deparse; TypeName = valueEvaluation.TypeName; - var escaped = valueEvaluation.Classes.Select((x) => x.IndexOf(' ') >= 0 ? "'" + x + "'" : x); - Class = string.Join(", ", escaped); // TODO: escape ',' in class names - HasChildren = valueEvaluation.HasChildren; - CanShowDetail = ComputeDetailAvailability(valueEvaluation); - if (CanShowDetail) { - Dimensions = valueEvaluation.Dim; - } else { - Dimensions = new List(); + if (valueEvaluation.Classes != null) { + var escaped = valueEvaluation.Classes.Select((x) => x.IndexOf(' ') >= 0 ? "'" + x + "'" : x); + Class = string.Join(", ", escaped); // TODO: escape ',' in class names } + + HasChildren = valueEvaluation.HasChildren; + + Dimensions = valueEvaluation.Dim ?? new List(); + } else if (DebugEvaluation is DebugPromiseEvaluationResult) { + const string PromiseVaue = ""; + var promise = (DebugPromiseEvaluationResult)DebugEvaluation; + + Value = promise.Code; + TypeName = PromiseVaue; + Class = PromiseVaue; + } else if (DebugEvaluation is DebugActiveBindingEvaluationResult) { + const string ActiveBindingValue = ""; + var activeBinding = (DebugActiveBindingEvaluationResult)DebugEvaluation; + + Value = ActiveBindingValue; + TypeName = ActiveBindingValue; + Class = ActiveBindingValue; } + + if (Dimensions == null) Dimensions = new List(); + + MaxChildrenCount = maxChildrenCount; } + protected int? MaxChildrenCount { get; set; } + + protected int MaxReprLength { get; set; } + + protected DebugEvaluationResult DebugEvaluation { get; } + public Task> GetChildrenAsync() { if (_getChildrenTask == null) { lock (syncObj) { @@ -66,12 +89,12 @@ public Task> GetChildrenAsync() { return _getChildrenTask; } - private async Task> GetChildrenAsyncInternal() { + protected virtual async Task> GetChildrenAsyncInternal() { List result = null; - var valueEvaluation = _evaluation as DebugValueEvaluationResult; + var valueEvaluation = DebugEvaluation as DebugValueEvaluationResult; if (valueEvaluation == null) { - Debug.Assert(false, $"EvaluationWrapper result type is not {typeof(DebugValueEvaluationResult)}"); + Debug.Assert(false, $"{nameof(RSessionDataObject)} result type is not {typeof(DebugValueEvaluationResult)}"); return result; } @@ -82,17 +105,17 @@ private async Task> GetChildrenAsyncInternal( DebugEvaluationResultFields.Repr | DebugEvaluationResultFields.ReprStr; // assumption: DebugEvaluationResult returns children in ascending order - IReadOnlyList children = await valueEvaluation.GetChildrenAsync(fields, _truncateChildren ? (int?)20 : null, 100); // TODO: consider exception propagation such as OperationCanceledException - result = EvaluateChildren(children, _truncateChildren); + IReadOnlyList children = await valueEvaluation.GetChildrenAsync(fields, MaxChildrenCount, MaxReprLength); + result = EvaluateChildren(children); } return result; } - protected virtual List EvaluateChildren(IReadOnlyList children, bool truncateChildren) { + protected virtual List EvaluateChildren(IReadOnlyList children) { var result = new List(); for (int i = 0; i < children.Count; i++) { - result.Add(new RSessionDataObject(i, children[i], _truncateChildren)); + result.Add(new RSessionDataObject(children[i], DefaultMaxGrandChildren)); } return result; } @@ -109,21 +132,7 @@ private string GetValue(DebugValueEvaluationResult v) { return value; } - private static string[] detailClasses = new string[] { "matrix", "data.frame", "table" }; - private bool ComputeDetailAvailability(DebugValueEvaluationResult evaluation) { - if (evaluation.Classes.Any(t => detailClasses.Contains(t))) { - if (evaluation.Dim != null && evaluation.Dim.Count == 2) { - return true; - } - } - return false; - } - #region IRSessionDataObject - /// - /// Index returned from evaluation provider, Sort is based on this, and assumes that DebugEvaluationResult returns in ascending order - /// - public int Index { get; } public string Name { get; protected set; } @@ -145,11 +154,10 @@ public bool IsHidden { public string Expression { get { - return _evaluation.Expression; + return DebugEvaluation.Expression; } } - public bool CanShowDetail { get; protected set; } #endregion } } diff --git a/src/R/Editor/Impl/Data/WorkspaceVariableProvider.cs b/src/R/Editor/Impl/Data/WorkspaceVariableProvider.cs index 48f9ef5bb..f1e6dfc44 100644 --- a/src/R/Editor/Impl/Data/WorkspaceVariableProvider.cs +++ b/src/R/Editor/Impl/Data/WorkspaceVariableProvider.cs @@ -112,23 +112,25 @@ protected override void SessionMutated() { } private async Task UpdateList() { - var debugSessionProvider = EditorShell.Current.ExportProvider.GetExportedValue(); + // May be null in tests + var debugSessionProvider = EditorShell.Current.ExportProvider.GetExportedValueOrDefault(); + if (debugSessionProvider != null) { + var debugSession = await debugSessionProvider.GetDebugSessionAsync(Session); + if (debugSession != null) { + var stackFrames = await debugSession.GetStackFramesAsync(); - var debugSession = await debugSessionProvider.GetDebugSessionAsync(Session); - if (debugSession != null) { - var stackFrames = await debugSession.GetStackFramesAsync(); + var globalStackFrame = stackFrames.FirstOrDefault(s => s.IsGlobal); + if (globalStackFrame != null) { + DebugEvaluationResult evaluation = await globalStackFrame.EvaluateAsync("base::environment()", "Global Environment"); + var e = new RSessionDataObject(evaluation); // root level doesn't truncate children and return every variables - var globalStackFrame = stackFrames.FirstOrDefault(s => s.IsGlobal); - if (globalStackFrame != null) { - DebugEvaluationResult evaluation = await globalStackFrame.EvaluateAsync("environment()", "Global Environment"); - var e = new RSessionDataObject(-1, evaluation, false); // root level doesn't truncate children and return every variables + _topLevelVariables.Clear(); - _topLevelVariables.Clear(); - - var children = await e.GetChildrenAsync(); - if (children != null) { - foreach (var x in children) { - _topLevelVariables[x.Name] = x; // TODO: BUGBUG: this doesn't address removed variables + var children = await e.GetChildrenAsync(); + if (children != null) { + foreach (var x in children) { + _topLevelVariables[x.Name] = x; // TODO: BUGBUG: this doesn't address removed variables + } } } } diff --git a/src/R/Editor/Impl/Formatting/AutoFormat.cs b/src/R/Editor/Impl/Formatting/AutoFormat.cs index 669cdd69d..a2fa8b5fc 100644 --- a/src/R/Editor/Impl/Formatting/AutoFormat.cs +++ b/src/R/Editor/Impl/Formatting/AutoFormat.cs @@ -28,11 +28,18 @@ public static void HandleAutoformat(ITextView textView, char typedChar) { if (rPoint.HasValue) { ITextBuffer subjectBuffer = rPoint.Value.Snapshot.TextBuffer; if (typedChar == '\r' || typedChar == '\n') { - bool formatScope = ShouldFormatScope(textView, subjectBuffer, -1); - if (formatScope) { - FormatOperations.FormatCurrentNode(textView, subjectBuffer); - } else { - FormatOperations.FormatLine(textView, subjectBuffer, tree.AstRoot, -1); + // Special case for hitting caret after } and before 'else'. We do want to format + // the construct as '} else {' but if user types Enter after } and we auto-format + // it will look as if the editor just eats the Enter. Instead, we will not be + // autoformatting in this specific case. User can always format either the document + // or select the block and reformat it. + if (!IsBetweenCurlyAndElse(subjectBuffer, rPoint.Value.Position)) { + bool formatScope = ShouldFormatScope(textView, subjectBuffer, -1); + if (formatScope) { + FormatOperations.FormatCurrentNode(textView, subjectBuffer); + } else { + FormatOperations.FormatLine(textView, subjectBuffer, tree.AstRoot, -1); + } } } else if (typedChar == ';') { // Verify we are at the end of the string and not in a middle @@ -49,6 +56,29 @@ public static void HandleAutoformat(ITextView textView, char typedChar) { } } + private static bool IsBetweenCurlyAndElse(ITextBuffer textBuffer, int position) { + // Note that this is post-typing to the construct is now '}else' + int lineNum = textBuffer.CurrentSnapshot.GetLineNumberFromPosition(position); + if (lineNum < 1) { + return false; + } + + ITextSnapshotLine prevLine = textBuffer.CurrentSnapshot.GetLineFromLineNumber(lineNum-1); + + string leftSide = prevLine.GetText().TrimEnd(); + if (!leftSide.EndsWith("}", StringComparison.Ordinal)) { + return false; + } + + ITextSnapshotLine currentLine = textBuffer.CurrentSnapshot.GetLineFromLineNumber(lineNum); + string rightSide = currentLine.GetText().TrimStart(); + if (!rightSide.StartsWith("else", StringComparison.Ordinal)) { + return false; + } + + return true; + } + private static SnapshotPoint? GetCaretPointInBuffer(ITextView textView, out IEditorTree tree) { tree = null; IREditorDocument document = REditorDocument.TryFromTextBuffer(textView.TextBuffer); diff --git a/src/R/Editor/Impl/Formatting/FormatOnPasteCommand.cs b/src/R/Editor/Impl/Formatting/FormatOnPasteCommand.cs index 4cf33b96c..864e022a1 100644 --- a/src/R/Editor/Impl/Formatting/FormatOnPasteCommand.cs +++ b/src/R/Editor/Impl/Formatting/FormatOnPasteCommand.cs @@ -52,6 +52,8 @@ public override CommandResult Invoke(Guid group, int id, object inputArg, ref ob if (document != null) { int insertionPoint = targetSpan.Start; targetSpan.Snapshot.TextBuffer.Replace(targetSpan, text); + document.EditorTree.EnsureTreeReady(); + RangeFormatter.FormatRange(TextView, targetSpan.Snapshot.TextBuffer, new TextRange(insertionPoint, text.Length), document.EditorTree.AstRoot, REditorSettings.FormatOptions); } } diff --git a/src/R/Editor/Impl/Microsoft.R.Editor.csproj b/src/R/Editor/Impl/Microsoft.R.Editor.csproj index d73f41105..6957efa16 100644 --- a/src/R/Editor/Impl/Microsoft.R.Editor.csproj +++ b/src/R/Editor/Impl/Microsoft.R.Editor.csproj @@ -1,258 +1,257 @@ - - - - - - Debug - AnyCPU - {D6EEEF87-CE3A-4804-A409-39966B96C850} - Library - Properties - Microsoft.R.Editor - Microsoft.R.Editor - v4.6 - 512 - SAK - SAK - SAK - SAK - - - - - - $(RootDirectory)\obj\ - $(RootDirectory)\bin\ - $(BaseIntermediateOutputPath)\$(Configuration)\$(AssemblyName)\ - $(BaseOutputPath)\$(Configuration)\ - - - - ..\..\..\..\NugetPackages\Microsoft.VisualStudio.CoreUtility.14.1.24720\lib\net45\Microsoft.VisualStudio.CoreUtility.dll - True - - - False - - - False - - - ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Language.StandardClassification.14.1.24720\lib\net45\Microsoft.VisualStudio.Language.StandardClassification.dll - True - - - ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.Data.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.Data.dll - True - - - ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.Logic.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.Logic.dll - True - - - ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.UI.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.UI.dll - True - - - ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.UI.Wpf.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.UI.Wpf.dll - True - - - - - - - - - - - - Properties\GlobalAssemblyInfo.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - True - True - Resources.resx - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {8d408909-459f-4853-a36c-745118f99869} - Microsoft.Common.Core - - - {17e155bf-351c-4253-b9b1-36eeea35fe3c} - Microsoft.R.Debugger - - - {e09d3bda-2e6b-47b5-87ac-b6fc2d33dfab} - Microsoft.R.Host.Client - - - {25cd8690-6208-4740-b123-6dbce6b9444a} - Microsoft.Languages.Core - - - {62857e49-e586-4baa-ae4d-1232093e7378} - Microsoft.Languages.Editor - - - {c1957d47-b0b4-42e0-bc08-0d5e96e47fe4} - Microsoft.R.Support - - - {0c4bce1d-3cb8-4e2a-9252-58784d7f26a5} - Microsoft.R.Core - - - - - ResXFileCodeGenerator - Resources.Designer.cs - - - - - Microsoft - StrongName - - - - - - - - This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. - - - - - - + + + + + + Debug + AnyCPU + {D6EEEF87-CE3A-4804-A409-39966B96C850} + Library + Properties + Microsoft.R.Editor + Microsoft.R.Editor + v4.6 + 512 + SAK + SAK + SAK + SAK + + + + + + $(RootDirectory)\obj\ + $(RootDirectory)\bin\ + $(BaseIntermediateOutputPath)\$(Configuration)\$(AssemblyName)\ + $(BaseOutputPath)\$(Configuration)\ + + + + ..\..\..\..\NugetPackages\Microsoft.VisualStudio.CoreUtility.14.1.24720\lib\net45\Microsoft.VisualStudio.CoreUtility.dll + True + + + False + + + False + + + ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Language.StandardClassification.14.1.24720\lib\net45\Microsoft.VisualStudio.Language.StandardClassification.dll + True + + + ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.Data.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.Data.dll + True + + + ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.Logic.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.Logic.dll + True + + + ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.UI.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.UI.dll + True + + + ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.UI.Wpf.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.UI.Wpf.dll + True + + + + + + + + + + + + Properties\GlobalAssemblyInfo.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {8d408909-459f-4853-a36c-745118f99869} + Microsoft.Common.Core + + + {17e155bf-351c-4253-b9b1-36eeea35fe3c} + Microsoft.R.Debugger + + + {e09d3bda-2e6b-47b5-87ac-b6fc2d33dfab} + Microsoft.R.Host.Client + + + {25cd8690-6208-4740-b123-6dbce6b9444a} + Microsoft.Languages.Core + + + {62857e49-e586-4baa-ae4d-1232093e7378} + Microsoft.Languages.Editor + + + {c1957d47-b0b4-42e0-bc08-0d5e96e47fe4} + Microsoft.R.Support + + + {0c4bce1d-3cb8-4e2a-9252-58784d7f26a5} + Microsoft.R.Core + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + Microsoft + StrongName + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/src/R/Editor/Impl/Outline/OutlineTagsCommandHandler.cs b/src/R/Editor/Impl/Outline/OutlineTagsCommandHandler.cs deleted file mode 100644 index fbc3854f7..000000000 --- a/src/R/Editor/Impl/Outline/OutlineTagsCommandHandler.cs +++ /dev/null @@ -1,145 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Microsoft.Languages.Editor; -using Microsoft.Languages.Editor.Controller.Command; -using Microsoft.Languages.Editor.Controller.Constants; -using Microsoft.Languages.Editor.EditorHelpers; -using Microsoft.Languages.Editor.Shell; -using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Text.Editor; -using Microsoft.VisualStudio.Text.Outlining; - -namespace Microsoft.R.Editor.Outline -{ - /// - /// Hander of VS outlining commands typically found - /// in the Edit | Outlining menu. - /// - internal sealed class ROutlineTagsCommandHandler : ViewCommand - { - private static CommandId[] _commandIds = new CommandId[] - { - new CommandId(VSConstants.VSStd2K, (int)VSConstants.VSStd2KCmdID.COLLAPSETAG), - new CommandId(VSConstants.VSStd2K, (int)VSConstants.VSStd2KCmdID.UNCOLLAPSETAG), - }; - - private IOutliningManager _outliningManager; - - internal ROutlineTagsCommandHandler(ITextView textView) : - base(textView, _commandIds, false) - { - } - - private IOutliningManager OutliningManager - { - get - { - if (_outliningManager == null) - { - IOutliningManagerService outliningManagerService = EditorShell.Current.ExportProvider.GetExport().Value; - _outliningManager = outliningManagerService.GetOutliningManager(TextView); - } - - return _outliningManager; - } - } - - private IEnumerable CollapsibleRegions - { - get - { - SnapshotSpan snapSpan = new SnapshotSpan(TextView.TextSnapshot, new Span(TextView.Caret.Position.BufferPosition, 0)); - IEnumerable allRegions = OutliningManager.GetAllRegions(snapSpan); - return allRegions.Where(c => c.IsCollapsible && (c.Tag is ROutliningRegionTag)); - } - } - - private IEnumerable CollapsedRegions - { - get - { - SnapshotSpan snapSpan = new SnapshotSpan(TextView.TextSnapshot, new Span(TextView.Caret.Position.BufferPosition, 0)); - IEnumerable collapsedRegions = OutliningManager.GetCollapsedRegions(snapSpan); - return collapsedRegions.Where(c => (c.Tag is ROutliningRegionTag)); - } - } - - #region ICommand - public override CommandStatus Status(Guid group, int id) - { - if (group == VSConstants.VSStd2K) - { - VSConstants.VSStd2KCmdID vsCmdID = (VSConstants.VSStd2KCmdID)id; - - switch (vsCmdID) - { - case VSConstants.VSStd2KCmdID.COLLAPSETAG: - return CollapsibleRegions.Any() ? CommandStatus.SupportedAndEnabled : CommandStatus.Invisible; - case VSConstants.VSStd2KCmdID.UNCOLLAPSETAG: - return CollapsedRegions.Any() ? CommandStatus.SupportedAndEnabled : CommandStatus.Invisible; - } - } - - return CommandStatus.NotSupported; - } - - public override CommandResult Invoke(Guid group, int id, object inputArg, ref object outputArg) - { - if (group == VSConstants.VSStd2K) - { - VSConstants.VSStd2KCmdID vsCmdID = (VSConstants.VSStd2KCmdID)id; - - switch (vsCmdID) - { - case VSConstants.VSStd2KCmdID.COLLAPSETAG: - { - ICollapsible maxCollapsibleRegion = null; - int maxStart = 0; - foreach (ICollapsible curRegion in CollapsibleRegions) - { - int curStart = curRegion.Extent.GetCurrentStart(); - if ((maxCollapsibleRegion == null) || (curStart > maxStart)) - { - maxStart = curStart; - maxCollapsibleRegion = curRegion; - } - } - - if (maxCollapsibleRegion != null) - { - OutliningManager.TryCollapse(maxCollapsibleRegion); - } - - return CommandResult.Executed; - } - - case VSConstants.VSStd2KCmdID.UNCOLLAPSETAG: - { - ICollapsed minCollapsedRegion = null; - int minStart = Int32.MaxValue; - foreach (ICollapsed curRegion in CollapsedRegions) - { - int curStart = curRegion.Extent.GetCurrentStart(); - if ((minCollapsedRegion == null) || (curStart < minStart)) - { - minStart = curStart; - minCollapsedRegion = curRegion; - } - } - - if (minCollapsedRegion != null) - { - OutliningManager.Expand(minCollapsedRegion); - } - - return CommandResult.Executed; - } - } - } - - return CommandResult.NotSupported; - } - #endregion - } -} diff --git a/src/R/Editor/Impl/Resources.Designer.cs b/src/R/Editor/Impl/Resources.Designer.cs index 2c65449a4..1492f4ca9 100644 --- a/src/R/Editor/Impl/Resources.Designer.cs +++ b/src/R/Editor/Impl/Resources.Designer.cs @@ -1,423 +1,423 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace Microsoft.R.Editor { - using System; - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager { - get { - if (object.ReferenceEquals(resourceMan, null)) { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.R.Editor.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture { - get { - return resourceCulture; - } - set { - resourceCulture = value; - } - } - - /// - /// Looks up a localized string similar to Parsing document.... - /// - internal static string AsyncIntellisense { - get { - return ResourceManager.GetString("AsyncIntellisense", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Automatic formatting. - /// - internal static string AutoFormat { - get { - return ResourceManager.GetString("AutoFormat", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to R Assignment. - /// - internal static string ColorName_R_Assignment { - get { - return ResourceManager.GetString("ColorName_R_Assignment", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to R Braces. - /// - internal static string ColorName_R_Braces { - get { - return ResourceManager.GetString("ColorName_R_Braces", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to R Square Brackets. - /// - internal static string ColorName_R_Brackets { - get { - return ResourceManager.GetString("ColorName_R_Brackets", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to R Builtin Function. - /// - internal static string ColorName_R_Builtin { - get { - return ResourceManager.GetString("ColorName_R_Builtin", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to R Comment. - /// - internal static string ColorName_R_Comment { - get { - return ResourceManager.GetString("ColorName_R_Comment", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to R Curly Brace. - /// - internal static string ColorName_R_CurlyBrace { - get { - return ResourceManager.GetString("ColorName_R_CurlyBrace", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to R Curly Braces. - /// - internal static string ColorName_R_CurlyBraces { - get { - return ResourceManager.GetString("ColorName_R_CurlyBraces", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to R Function Default Parameter. - /// - internal static string ColorName_R_FunctionDefaultParameter { - get { - return ResourceManager.GetString("ColorName_R_FunctionDefaultParameter", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to R Function Reference. - /// - internal static string ColorName_R_FunctionReference { - get { - return ResourceManager.GetString("ColorName_R_FunctionReference", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to R Keyword. - /// - internal static string ColorName_R_Keyword { - get { - return ResourceManager.GetString("ColorName_R_Keyword", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to R Number. - /// - internal static string ColorName_R_Number { - get { - return ResourceManager.GetString("ColorName_R_Number", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to R Operator. - /// - internal static string ColorName_R_Operator { - get { - return ResourceManager.GetString("ColorName_R_Operator", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to R Punctuation. - /// - internal static string ColorName_R_Punctuation { - get { - return ResourceManager.GetString("ColorName_R_Punctuation", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to R String. - /// - internal static string ColorName_R_String { - get { - return ResourceManager.GetString("ColorName_R_String", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to R Type Functions. - /// - internal static string ColorName_R_TypeFunction { - get { - return ResourceManager.GetString("ColorName_R_TypeFunction", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Comment selection. - /// - internal static string CommentSelection { - get { - return ResourceManager.GetString("CommentSelection", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Format document. - /// - internal static string FormatDocument { - get { - return ResourceManager.GetString("FormatDocument", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to ) expected. - /// - internal static string ParseError_CloseBraceExpected { - get { - return ResourceManager.GetString("ParseError_CloseBraceExpected", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to } expected. - /// - internal static string ParseError_CloseCurlyBraceExpected { - get { - return ResourceManager.GetString("ParseError_CloseCurlyBraceExpected", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to ] or ]] expected. - /// - internal static string ParseError_CloseSquareBracketExpected { - get { - return ResourceManager.GetString("ParseError_CloseSquareBracketExpected", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Expression expected. - /// - internal static string ParseError_ExpressionExpected { - get { - return ResourceManager.GetString("ParseError_ExpressionExpected", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Function body expected. - /// - internal static string ParseError_FunctionBodyExpected { - get { - return ResourceManager.GetString("ParseError_FunctionBodyExpected", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Function expected. - /// - internal static string ParseError_FunctionExpected { - get { - return ResourceManager.GetString("ParseError_FunctionExpected", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Syntax error. - /// - internal static string ParseError_General { - get { - return ResourceManager.GetString("ParseError_General", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Identifier expected. - /// - internal static string ParseError_IndentifierExpected { - get { - return ResourceManager.GetString("ParseError_IndentifierExpected", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to 'in' expected. - /// - internal static string ParseError_InKeywordExpected { - get { - return ResourceManager.GetString("ParseError_InKeywordExpected", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Operand to the left expected. - /// - internal static string ParseError_LeftOperandExpected { - get { - return ResourceManager.GetString("ParseError_LeftOperandExpected", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Logical expected. - /// - internal static string ParseError_LogicalExpected { - get { - return ResourceManager.GetString("ParseError_LogicalExpected", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Number expected. - /// - internal static string ParseError_NumberExpected { - get { - return ResourceManager.GetString("ParseError_NumberExpected", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to ( expected. - /// - internal static string ParseError_OpenBraceExpected { - get { - return ResourceManager.GetString("ParseError_OpenBraceExpected", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to { expected. - /// - internal static string ParseError_OpenCurlyBraceExpected { - get { - return ResourceManager.GetString("ParseError_OpenCurlyBraceExpected", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to [ or [[ expected. - /// - internal static string ParseError_OpenSquareBracketExpected { - get { - return ResourceManager.GetString("ParseError_OpenSquareBracketExpected", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Operator expected. - /// - internal static string ParseError_OperatorExpected { - get { - return ResourceManager.GetString("ParseError_OperatorExpected", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Operand to the right expected. - /// - internal static string ParseError_RightOperandExpected { - get { - return ResourceManager.GetString("ParseError_RightOperandExpected", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to String expected. - /// - internal static string ParseError_StringExpected { - get { - return ResourceManager.GetString("ParseError_StringExpected", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unexpected end of file. - /// - internal static string ParseError_UnexpectedEndOfFile { - get { - return ResourceManager.GetString("ParseError_UnexpectedEndOfFile", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Unexpected token. - /// - internal static string ParseError_UnexpectedToken { - get { - return ResourceManager.GetString("ParseError_UnexpectedToken", resourceCulture); - } - } - - /// - /// Looks up a localized string similar to Uncomment selection. - /// - internal static string UncommentSelection { - get { - return ResourceManager.GetString("UncommentSelection", resourceCulture); - } - } - } -} +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.R.Editor { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.R.Editor.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Parsing document.... + /// + internal static string AsyncIntellisense { + get { + return ResourceManager.GetString("AsyncIntellisense", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Automatic formatting. + /// + internal static string AutoFormat { + get { + return ResourceManager.GetString("AutoFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to R Assignment. + /// + internal static string ColorName_R_Assignment { + get { + return ResourceManager.GetString("ColorName_R_Assignment", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to R Braces. + /// + internal static string ColorName_R_Braces { + get { + return ResourceManager.GetString("ColorName_R_Braces", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to R Square Brackets. + /// + internal static string ColorName_R_Brackets { + get { + return ResourceManager.GetString("ColorName_R_Brackets", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to R Builtin Function. + /// + internal static string ColorName_R_Builtin { + get { + return ResourceManager.GetString("ColorName_R_Builtin", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to R Comment. + /// + internal static string ColorName_R_Comment { + get { + return ResourceManager.GetString("ColorName_R_Comment", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to R Curly Brace. + /// + internal static string ColorName_R_CurlyBrace { + get { + return ResourceManager.GetString("ColorName_R_CurlyBrace", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to R Curly Braces. + /// + internal static string ColorName_R_CurlyBraces { + get { + return ResourceManager.GetString("ColorName_R_CurlyBraces", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to R Function Default Parameter. + /// + internal static string ColorName_R_FunctionDefaultParameter { + get { + return ResourceManager.GetString("ColorName_R_FunctionDefaultParameter", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to R Function Reference. + /// + internal static string ColorName_R_FunctionReference { + get { + return ResourceManager.GetString("ColorName_R_FunctionReference", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to R Keyword. + /// + internal static string ColorName_R_Keyword { + get { + return ResourceManager.GetString("ColorName_R_Keyword", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to R Number. + /// + internal static string ColorName_R_Number { + get { + return ResourceManager.GetString("ColorName_R_Number", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to R Operator. + /// + internal static string ColorName_R_Operator { + get { + return ResourceManager.GetString("ColorName_R_Operator", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to R Punctuation. + /// + internal static string ColorName_R_Punctuation { + get { + return ResourceManager.GetString("ColorName_R_Punctuation", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to R String. + /// + internal static string ColorName_R_String { + get { + return ResourceManager.GetString("ColorName_R_String", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to R Type Functions. + /// + internal static string ColorName_R_TypeFunction { + get { + return ResourceManager.GetString("ColorName_R_TypeFunction", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Comment selection. + /// + internal static string CommentSelection { + get { + return ResourceManager.GetString("CommentSelection", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Format document. + /// + internal static string FormatDocument { + get { + return ResourceManager.GetString("FormatDocument", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ) expected. + /// + internal static string ParseError_CloseBraceExpected { + get { + return ResourceManager.GetString("ParseError_CloseBraceExpected", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to } expected. + /// + internal static string ParseError_CloseCurlyBraceExpected { + get { + return ResourceManager.GetString("ParseError_CloseCurlyBraceExpected", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ] or ]] expected. + /// + internal static string ParseError_CloseSquareBracketExpected { + get { + return ResourceManager.GetString("ParseError_CloseSquareBracketExpected", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Expression expected. + /// + internal static string ParseError_ExpressionExpected { + get { + return ResourceManager.GetString("ParseError_ExpressionExpected", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Function body expected. + /// + internal static string ParseError_FunctionBodyExpected { + get { + return ResourceManager.GetString("ParseError_FunctionBodyExpected", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Function expected. + /// + internal static string ParseError_FunctionExpected { + get { + return ResourceManager.GetString("ParseError_FunctionExpected", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Syntax error. + /// + internal static string ParseError_General { + get { + return ResourceManager.GetString("ParseError_General", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Identifier expected. + /// + internal static string ParseError_IndentifierExpected { + get { + return ResourceManager.GetString("ParseError_IndentifierExpected", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to 'in' expected. + /// + internal static string ParseError_InKeywordExpected { + get { + return ResourceManager.GetString("ParseError_InKeywordExpected", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Operand to the left expected. + /// + internal static string ParseError_LeftOperandExpected { + get { + return ResourceManager.GetString("ParseError_LeftOperandExpected", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Logical expected. + /// + internal static string ParseError_LogicalExpected { + get { + return ResourceManager.GetString("ParseError_LogicalExpected", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Number expected. + /// + internal static string ParseError_NumberExpected { + get { + return ResourceManager.GetString("ParseError_NumberExpected", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ( expected. + /// + internal static string ParseError_OpenBraceExpected { + get { + return ResourceManager.GetString("ParseError_OpenBraceExpected", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to { expected. + /// + internal static string ParseError_OpenCurlyBraceExpected { + get { + return ResourceManager.GetString("ParseError_OpenCurlyBraceExpected", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to [ or [[ expected. + /// + internal static string ParseError_OpenSquareBracketExpected { + get { + return ResourceManager.GetString("ParseError_OpenSquareBracketExpected", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Operator expected. + /// + internal static string ParseError_OperatorExpected { + get { + return ResourceManager.GetString("ParseError_OperatorExpected", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Operand to the right expected. + /// + internal static string ParseError_RightOperandExpected { + get { + return ResourceManager.GetString("ParseError_RightOperandExpected", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to String expected. + /// + internal static string ParseError_StringExpected { + get { + return ResourceManager.GetString("ParseError_StringExpected", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unexpected end of file. + /// + internal static string ParseError_UnexpectedEndOfFile { + get { + return ResourceManager.GetString("ParseError_UnexpectedEndOfFile", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unexpected token. + /// + internal static string ParseError_UnexpectedToken { + get { + return ResourceManager.GetString("ParseError_UnexpectedToken", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Uncomment selection. + /// + internal static string UncommentSelection { + get { + return ResourceManager.GetString("UncommentSelection", resourceCulture); + } + } + } +} diff --git a/src/R/Editor/Impl/Selection/RSelectionTracker.cs b/src/R/Editor/Impl/Selection/RSelectionTracker.cs index 1d5dfca51..da3a142fb 100644 --- a/src/R/Editor/Impl/Selection/RSelectionTracker.cs +++ b/src/R/Editor/Impl/Selection/RSelectionTracker.cs @@ -5,18 +5,22 @@ using Microsoft.R.Core.Tokens; using Microsoft.VisualStudio.Text; using Microsoft.VisualStudio.Text.Editor; -using Microsoft.VisualStudio.Text.Projection; -namespace Microsoft.R.Editor.Selection -{ +namespace Microsoft.R.Editor.Selection { /// /// JScript selection tracker. Helps preserve selection and correct /// caret position during script autoformatting. Uses tokenizer to /// calculate where caret should be after autoatic formatting. /// - internal sealed class RSelectionTracker : SelectionTracker - { + internal sealed class RSelectionTracker : SelectionTracker { + /// + /// Index of token that is nearest to the caret + /// private int _index; + + /// + /// Offset from token to the caret position + /// private int _offset; /// @@ -25,8 +29,7 @@ internal sealed class RSelectionTracker : SelectionTracker /// Text view /// Editor text buffer (may be different from one attached to text view) public RSelectionTracker(ITextView textView, ITextBuffer textBuffer) - : base(textView) - { + : base(textView) { TextBuffer = textBuffer; } @@ -40,35 +43,31 @@ public RSelectionTracker(ITextView textView, ITextBuffer textBuffer) /// /// Saves current selection /// - public override void StartTracking(bool automaticTracking) - { + public override void StartTracking(bool automaticTracking) { int position = TextView.Caret.Position.BufferPosition; - VirtualSpaces = TextView.Caret.Position.VirtualSpaces; - TokenFromPosition(TextBuffer.CurrentSnapshot, position, out _index, out _offset); - + SnapshotPoint? documentPosition = TextView.MapDownToBuffer(position, TextBuffer); + if (documentPosition.HasValue) { + VirtualSpaces = TextView.Caret.Position.VirtualSpaces; + TokenFromPosition(TextBuffer.CurrentSnapshot, documentPosition.Value, out _index, out _offset); + } base.StartTracking(false); } /// /// Restores saved selection /// - public override void EndTracking() - { + public override void EndTracking() { int position = PositionFromTokens(TextBuffer.CurrentSnapshot, _index, _offset); - if (position >= 0) - { + if (position >= 0) { PositionAfterChanges = new SnapshotPoint(TextBuffer.CurrentSnapshot, position); } - MoveToAfterChanges(VirtualSpaces); } #endregion - private static void TokenFromPosition(ITextSnapshot snapshot, int position, out int itemIndex, out int offset) - { + private static void TokenFromPosition(ITextSnapshot snapshot, int position, out int itemIndex, out int offset) { // Normally token stream does not change after formatting so we can simply rely on the fact // that caret position is going to remain relative to the same token index - itemIndex = -1; offset = 0; @@ -77,22 +76,19 @@ private static void TokenFromPosition(ITextSnapshot snapshot, int position, out // Check if position is adjacent to previous token int prevItemIndex = tokens.GetFirstItemBeforePosition(position); - if (prevItemIndex >= 0 && tokens[prevItemIndex].End == position) - { + if (prevItemIndex >= 0 && tokens[prevItemIndex].End == position) { itemIndex = prevItemIndex; offset = -tokens[itemIndex].Length; return; } int nextItemIndex = tokens.GetFirstItemAfterOrAtPosition(position); - if (nextItemIndex >= 0) - { + if (nextItemIndex >= 0) { // If two tokens are adjacent, gravity is negative, i.e. caret travels // with preceding token so it won't just to aniother line if, say, // formatter decides to insert a new line between tokens. - if (nextItemIndex > 0 && tokens[nextItemIndex - 1].End == tokens[nextItemIndex].Start) - { + if (nextItemIndex > 0 && tokens[nextItemIndex - 1].End == tokens[nextItemIndex].Start) { nextItemIndex--; } @@ -102,25 +98,20 @@ private static void TokenFromPosition(ITextSnapshot snapshot, int position, out } // We are past last token - if(tokens.Count > 0) - { + if (tokens.Count > 0) { itemIndex = tokens.Count - 1; offset = tokens[itemIndex].Start - position; - } - else - { + } else { itemIndex = -1; offset = position; } } - private static int PositionFromTokens(ITextSnapshot snapshot, int itemIndex, int offset) - { + private static int PositionFromTokens(ITextSnapshot snapshot, int itemIndex, int offset) { var tokenizer = new RTokenizer(); var tokens = tokenizer.Tokenize(new TextProvider(snapshot), 0, snapshot.Length, true); - if (itemIndex >= 0 && itemIndex < tokens.Count) - { + if (itemIndex >= 0 && itemIndex < tokens.Count) { int position = tokens[itemIndex].Start - offset; position = Math.Min(position, snapshot.Length); diff --git a/src/R/Editor/Impl/Tree/ApplyTreeChanges.cs b/src/R/Editor/Impl/Tree/ApplyTreeChanges.cs index 773d2d1f5..48d52b915 100644 --- a/src/R/Editor/Impl/Tree/ApplyTreeChanges.cs +++ b/src/R/Editor/Impl/Tree/ApplyTreeChanges.cs @@ -1,62 +1,62 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.Threading; -using Microsoft.Languages.Core.Text; -using Microsoft.R.Core.AST.Definitions; - -namespace Microsoft.R.Editor.Tree { - public partial class EditorTree { - internal List ApplyChangesFromQueue(Queue queue) { - if (_ownerThread != Thread.CurrentThread.ManagedThreadId) - throw new ThreadStateException("Method should only be called on the main thread"); - - var changesToFire = new List(); - - if (queue == null || queue.Count == 0) - return changesToFire; - - // Since we have write lock we cannot fire events. If we fire an event, - // listener may try and access the tree while a) tree not ready and - // b) accessing AstRoot may check tree readiness and since tree is not - // ready yet (as it is still applying changes) it may try and update - // tree on its own and end up hanging trying to acquire write lock again. - // Hence we must store events in a list and fire then when update - // is done and the lock is released. - - try { - AcquireWriteLock(); - - while (queue.Count > 0) { - var change = queue.Dequeue(); - - switch (change.ChangeType) { - case TreeChangeType.NewTree: { - var c = change as EditorTreeChange_NewTree; - _astRoot = c.NewTree; - changesToFire.Add(new TreeChangeEventRecord(change.ChangeType)); - } - break; - - default: - Debug.Fail("Unknown tree change"); - break; - } - } - } finally { - ReleaseWriteLock(); - } - return changesToFire; - } - - internal void FirePostUpdateEvents(List changes, bool fullParse) { - List textChanges = new List(); - - FireOnUpdatesPending(textChanges); - FireOnUpdateBegin(); - - FireOnUpdateCompleted(TreeUpdateType.NewTree); // newTree ? TreeUpdateType.NewTree : TreeUpdateType.ScopeChanged); - } - } -} +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Threading; +using Microsoft.Languages.Core.Text; +using Microsoft.R.Core.AST.Definitions; + +namespace Microsoft.R.Editor.Tree { + public partial class EditorTree { + internal List ApplyChangesFromQueue(Queue queue) { + if (_ownerThread != Thread.CurrentThread.ManagedThreadId) + throw new ThreadStateException("Method should only be called on the main thread"); + + var changesToFire = new List(); + + if (queue == null || queue.Count == 0) + return changesToFire; + + // Since we have write lock we cannot fire events. If we fire an event, + // listener may try and access the tree while a) tree not ready and + // b) accessing AstRoot may check tree readiness and since tree is not + // ready yet (as it is still applying changes) it may try and update + // tree on its own and end up hanging trying to acquire write lock again. + // Hence we must store events in a list and fire then when update + // is done and the lock is released. + + try { + AcquireWriteLock(); + + while (queue.Count > 0) { + var change = queue.Dequeue(); + + switch (change.ChangeType) { + case TreeChangeType.NewTree: { + var c = change as EditorTreeChange_NewTree; + _astRoot = c.NewTree; + changesToFire.Add(new TreeChangeEventRecord(change.ChangeType)); + } + break; + + default: + Debug.Fail("Unknown tree change"); + break; + } + } + } finally { + ReleaseWriteLock(); + } + return changesToFire; + } + + internal void FirePostUpdateEvents(List changes, bool fullParse) { + List textChanges = new List(); + + FireOnUpdatesPending(textChanges); + FireOnUpdateBegin(); + + FireOnUpdateCompleted(TreeUpdateType.NewTree); // newTree ? TreeUpdateType.NewTree : TreeUpdateType.ScopeChanged); + } + } +} diff --git a/src/R/Editor/Impl/Tree/ChangeProcessor.cs b/src/R/Editor/Impl/Tree/ChangeProcessor.cs index 6d688fb45..17daee2bb 100644 --- a/src/R/Editor/Impl/Tree/ChangeProcessor.cs +++ b/src/R/Editor/Impl/Tree/ChangeProcessor.cs @@ -1,127 +1,127 @@ -using System; -using System.Diagnostics; -using Microsoft.Languages.Core.Text; -using Microsoft.R.Core.AST; -using Microsoft.R.Core.AST.Definitions; -using Microsoft.R.Core.AST.Scopes.Definitions; -using Microsoft.R.Core.Parser; - -namespace Microsoft.R.Editor.Tree { - /// - /// Class that handles processing of changes happened in the text buffer. - /// - internal sealed class TextChangeProcessor { - /// - /// Editor tree - /// - private EditorTree _editorTree; - - /// - /// Tree root node - /// - private AstRoot _astRoot; - - /// - /// A callback that provides a way to check if processing should be canceled. - /// - private Func _cancelCallback; - - public TextChangeProcessor(EditorTree editorTree, AstRoot astRoot, Func cancelCallback = null) { - _editorTree = editorTree; - _astRoot = astRoot; - _cancelCallback = cancelCallback; - } - - private bool IsCancellationRequested() { - return _cancelCallback != null ? _cancelCallback() : false; - } - - /// - /// Processes a single text change incrementally. Enqueues resulting - /// tree changes in the supplied queue. Does not modify the tree. - /// Changes are to be sent to the main thread and applied from there. - /// Caller is responsible for the tree read lock acquisition. - /// - /// Start position of the change - /// Length of the original text (0 if insertion) - /// Length of the new text (0 if deletion) - /// Text snapshot before the change - /// Text snapshot after the change - /// Collection of tree changes to apply - /// from the main thread - public void ProcessChange(TextChange textChange, EditorTreeChangeCollection treeChanges) { - IAstNode startNode = null, endNode = null; - PositionType startPositionType = PositionType.Undefined; - PositionType endPositionType = PositionType.Undefined; - IAstNode commonParent = null; - - int start = textChange.OldRange.Start; - int oldLength = textChange.OldRange.Length; - int newLength = textChange.NewRange.Length; - int offset = newLength - oldLength; - - ITextProvider oldSnapshot = textChange.OldTextProvider; - ITextProvider newSnapshot = textChange.NewTextProvider; - - // Find position type and the enclosing element node. Note that element - // positions have been adjusted already (it happens immediately in OnTextChange) - // so we should be looking at the new range even that tree hasn't - // been fully updated yet. For example,if we delete a node, subsequent - // elements were already shifted up and damaged nodes have been removed - // so current node positions reflect text buffer state after the change. - - _astRoot.GetElementsEnclosingRange(start, newLength, out startNode, - out startPositionType, out endNode, out endPositionType); - - if (startNode is AstRoot) { - commonParent = _astRoot; - } else if (startNode == endNode) { - if (startPositionType == PositionType.Token) { - // Change in comment or string content. - commonParent = OnTokenNodeChange(startNode as TokenNode, start, oldLength, newLength); - } - } else { - //if (commonParent == null) - //{ - // // Find parent that still has well formed curly braces. - // commonParent = FindWellFormedOuterScope(startNode); - //} - - if (commonParent == null) { - commonParent = _astRoot; - } - } - - if (IsCancellationRequested()) - return; - - if (!(commonParent is AstRoot)) { - Debug.Assert(commonParent is IScope); - AstRoot subTree = RParser.Parse(newSnapshot, commonParent); - return; - } - - AstRoot newTree = RParser.Parse(newSnapshot); - treeChanges.ChangeQueue.Enqueue(new EditorTreeChange_NewTree(newTree)); - } - - /// - /// Reflects change inside string or comment by shrinking or expanding token node. - /// - /// - private IAstNode OnTokenNodeChange(TokenNode node, int start, int oldLength, int newLength) { - Debug.Assert(node != null); - node.Token.Expand(0, newLength - oldLength); - - return node; - } - - /// - /// Invokes full parse pass. Called from a background tree updating task. - /// - public void FullParse(EditorTreeChangeCollection changes, ITextProvider newSnapshot) { - AstRoot newTree = RParser.Parse(newSnapshot); - changes.ChangeQueue.Enqueue(new EditorTreeChange_NewTree(newTree)); - } - } -} +using System; +using System.Diagnostics; +using Microsoft.Languages.Core.Text; +using Microsoft.R.Core.AST; +using Microsoft.R.Core.AST.Definitions; +using Microsoft.R.Core.AST.Scopes.Definitions; +using Microsoft.R.Core.Parser; + +namespace Microsoft.R.Editor.Tree { + /// + /// Class that handles processing of changes happened in the text buffer. + /// + internal sealed class TextChangeProcessor { + /// + /// Editor tree + /// + private EditorTree _editorTree; + + /// + /// Tree root node + /// + private AstRoot _astRoot; + + /// + /// A callback that provides a way to check if processing should be canceled. + /// + private Func _cancelCallback; + + public TextChangeProcessor(EditorTree editorTree, AstRoot astRoot, Func cancelCallback = null) { + _editorTree = editorTree; + _astRoot = astRoot; + _cancelCallback = cancelCallback; + } + + private bool IsCancellationRequested() { + return _cancelCallback != null ? _cancelCallback() : false; + } + + /// + /// Processes a single text change incrementally. Enqueues resulting + /// tree changes in the supplied queue. Does not modify the tree. + /// Changes are to be sent to the main thread and applied from there. + /// Caller is responsible for the tree read lock acquisition. + /// + /// Start position of the change + /// Length of the original text (0 if insertion) + /// Length of the new text (0 if deletion) + /// Text snapshot before the change + /// Text snapshot after the change + /// Collection of tree changes to apply + /// from the main thread + public void ProcessChange(TextChange textChange, EditorTreeChangeCollection treeChanges) { + IAstNode startNode = null, endNode = null; + PositionType startPositionType = PositionType.Undefined; + PositionType endPositionType = PositionType.Undefined; + IAstNode commonParent = null; + + int start = textChange.OldRange.Start; + int oldLength = textChange.OldRange.Length; + int newLength = textChange.NewRange.Length; + int offset = newLength - oldLength; + + ITextProvider oldSnapshot = textChange.OldTextProvider; + ITextProvider newSnapshot = textChange.NewTextProvider; + + // Find position type and the enclosing element node. Note that element + // positions have been adjusted already (it happens immediately in OnTextChange) + // so we should be looking at the new range even that tree hasn't + // been fully updated yet. For example,if we delete a node, subsequent + // elements were already shifted up and damaged nodes have been removed + // so current node positions reflect text buffer state after the change. + + _astRoot.GetElementsEnclosingRange(start, newLength, out startNode, + out startPositionType, out endNode, out endPositionType); + + if (startNode is AstRoot) { + commonParent = _astRoot; + } else if (startNode == endNode) { + if (startPositionType == PositionType.Token) { + // Change in comment or string content. + commonParent = OnTokenNodeChange(startNode as TokenNode, start, oldLength, newLength); + } + } else { + //if (commonParent == null) + //{ + // // Find parent that still has well formed curly braces. + // commonParent = FindWellFormedOuterScope(startNode); + //} + + if (commonParent == null) { + commonParent = _astRoot; + } + } + + if (IsCancellationRequested()) + return; + + if (!(commonParent is AstRoot)) { + Debug.Assert(commonParent is IScope); + AstRoot subTree = RParser.Parse(newSnapshot, commonParent); + return; + } + + AstRoot newTree = RParser.Parse(newSnapshot); + treeChanges.ChangeQueue.Enqueue(new EditorTreeChange_NewTree(newTree)); + } + + /// + /// Reflects change inside string or comment by shrinking or expanding token node. + /// + /// + private IAstNode OnTokenNodeChange(TokenNode node, int start, int oldLength, int newLength) { + Debug.Assert(node != null); + node.Token.Expand(0, newLength - oldLength); + + return node; + } + + /// + /// Invokes full parse pass. Called from a background tree updating task. + /// + public void FullParse(EditorTreeChangeCollection changes, ITextProvider newSnapshot) { + AstRoot newTree = RParser.Parse(newSnapshot); + changes.ChangeQueue.Enqueue(new EditorTreeChange_NewTree(newTree)); + } + } +} diff --git a/src/R/Editor/Impl/Tree/EditorTreeChangeCollection.cs b/src/R/Editor/Impl/Tree/EditorTreeChangeCollection.cs index b2058ed73..3dcb14336 100644 --- a/src/R/Editor/Impl/Tree/EditorTreeChangeCollection.cs +++ b/src/R/Editor/Impl/Tree/EditorTreeChangeCollection.cs @@ -1,39 +1,39 @@ -using System.Collections.Generic; - -namespace Microsoft.R.Editor.Tree -{ - /// - /// Describes set of changes in the tree that were generated - /// by the background parser. Changes are applied to the tree - /// in the main thread in order to avoid unnecessary locks. - /// - internal sealed class EditorTreeChangeCollection - { - /// - /// Changes to apply to the tree - /// - public Queue ChangeQueue { get; private set; } - - /// - /// Version of the text snaphot the changes were generated agaist. - /// - public int SnapshotVersion { get; private set; } - - /// - /// Indicates if full parse required - /// - public bool FullParseRequired { get; private set; } - - public EditorTreeChangeCollection(int _snapshotVersion, bool fullParseRequired) - : this(new Queue(), _snapshotVersion, fullParseRequired) - { - } - - public EditorTreeChangeCollection(Queue changes, int _snapshotVersion, bool fullParseRequired) - { - ChangeQueue = changes; - SnapshotVersion = _snapshotVersion; - FullParseRequired = fullParseRequired; - } - } -} +using System.Collections.Generic; + +namespace Microsoft.R.Editor.Tree +{ + /// + /// Describes set of changes in the tree that were generated + /// by the background parser. Changes are applied to the tree + /// in the main thread in order to avoid unnecessary locks. + /// + internal sealed class EditorTreeChangeCollection + { + /// + /// Changes to apply to the tree + /// + public Queue ChangeQueue { get; private set; } + + /// + /// Version of the text snaphot the changes were generated agaist. + /// + public int SnapshotVersion { get; private set; } + + /// + /// Indicates if full parse required + /// + public bool FullParseRequired { get; private set; } + + public EditorTreeChangeCollection(int _snapshotVersion, bool fullParseRequired) + : this(new Queue(), _snapshotVersion, fullParseRequired) + { + } + + public EditorTreeChangeCollection(Queue changes, int _snapshotVersion, bool fullParseRequired) + { + ChangeQueue = changes; + SnapshotVersion = _snapshotVersion; + FullParseRequired = fullParseRequired; + } + } +} diff --git a/src/R/Editor/Impl/Tree/TextChangeAnalyzer.cs b/src/R/Editor/Impl/Tree/TextChangeAnalyzer.cs index adecc1622..09846f5b1 100644 --- a/src/R/Editor/Impl/Tree/TextChangeAnalyzer.cs +++ b/src/R/Editor/Impl/Tree/TextChangeAnalyzer.cs @@ -1,153 +1,153 @@ -using System.Collections.Generic; -using System.Diagnostics; -using Microsoft.Languages.Core.Text; -using Microsoft.R.Core.AST; -using Microsoft.R.Core.AST.Definitions; -using Microsoft.R.Core.AST.Statements.Conditionals; -using Microsoft.R.Core.Tokens; - -namespace Microsoft.R.Editor.Tree { - internal static class TextChangeAnalyzer { - private static char[] _lineBreaks = new char[] { '\n', '\r' }; - private static char[] _stringSensitiveCharacters = new char[] { '\\', '\'', '\"' }; - - public static void DetermineChangeType(TextChangeContext change) { - change.PendingChanges.TextChangeType |= CheckChangeInsideComment(change); - if (change.PendingChanges.TextChangeType == TextChangeType.Comment) { - return; - } else if (change.PendingChanges.TextChangeType == TextChangeType.Trivial) { - IAstNode node; - PositionType positionType; - - change.PendingChanges.TextChangeType |= CheckChangeInsideString(change, out node, out positionType); - if (change.PendingChanges.TextChangeType == TextChangeType.Token) { - return; - } else if (change.PendingChanges.TextChangeType == TextChangeType.Trivial) { - change.PendingChanges.TextChangeType |= CheckWhiteSpaceChange(change, node, positionType); - if (change.PendingChanges.TextChangeType == TextChangeType.Trivial) { - return; - } - } - } - - change.PendingChanges.TextChangeType = TextChangeType.Structure; - change.PendingChanges.FullParseRequired = true; - } - - private static TextChangeType CheckWhiteSpaceChange(TextChangeContext context, IAstNode node, PositionType positionType) { - context.ChangedNode = node; - - if (string.IsNullOrWhiteSpace(context.OldText) && string.IsNullOrWhiteSpace(context.NewText)) { - // In R there is no line continuation so expression may change when user adds or deletes line breaks. - bool lineBreakSensitive = (node is If) && ((If)node).LineBreakSensitive; - if (lineBreakSensitive) { - string oldLineText = context.OldTextProvider.GetText(new TextRange(context.OldStart, context.OldLength)); - string newLineText = context.NewTextProvider.GetText(new TextRange(context.NewStart, context.NewLength)); - - if (oldLineText.IndexOfAny(_lineBreaks) >= 0 || newLineText.IndexOfAny(_lineBreaks) >= 0) { - return TextChangeType.Structure; - } - } - - // Change inside token node is destructive: consider adding space inside an indentifier - if (!IsChangeDestructiveForChildNodes(node, context.OldRange)) { - return TextChangeType.Trivial; - } - } - - return TextChangeType.Structure; - } - - private static bool IsChangeDestructiveForChildNodes(IAstNode node, ITextRange changedRange) { - if(changedRange.End <= node.Start || changedRange.Start >= node.End) { - return false; - } - else if(node.Children.Count == 0) { - return true; - } - - bool result = false; - foreach (var child in node.Children) { - result |= IsChangeDestructiveForChildNodes(child, changedRange); - if(result) { - break; - } - } - - return result; - } - - private static TextChangeType CheckChangeInsideComment(TextChangeContext context) { - var comments = context.EditorTree.AstRoot.Comments; - - IReadOnlyList affectedComments = comments.GetItemsContainingInclusiveEnd(context.NewStart); - if (affectedComments.Count == 0) { - return TextChangeType.Trivial; - } - - if (affectedComments.Count > 1) { - return TextChangeType.Structure; - } - - // Make sure user is not deleting leading # effectively - // destroying the comment - RToken comment = comments[affectedComments[0]]; - if (comment.Start == context.NewStart && context.OldLength > 0) { - return TextChangeType.Structure; - } - - // The collection will return a match if the comment starts - // at the requested location. However, the change is not - // inside the comment if it's at the comment start and - // the old length of the change is zero. - - if (comment.Start == context.NewStart && context.OldLength == 0) { - if (context.NewText.IndexOf('#') < 0) { - context.ChangedComment = comment; - return TextChangeType.Comment; - } - } - - if (context.NewText.IndexOfAny(_lineBreaks) >= 0) { - return TextChangeType.Structure; - } - - // The change is not safe if old or new text contains line breaks - // as in R comments runs to the end of the line and deleting - // line break at the end of the comment may bring code into - // the comment range and change the entire file structure. - - if (context.OldText.IndexOfAny(_lineBreaks) >= 0) { - return TextChangeType.Structure; - } - - context.ChangedComment = comment; - return TextChangeType.Comment; - } - - private static TextChangeType CheckChangeInsideString(TextChangeContext context, out IAstNode node, out PositionType positionType) { - positionType = context.EditorTree.AstRoot.GetPositionNode(context.NewStart, out node); - - if (positionType == PositionType.Token) { - TokenNode tokenNode = node as TokenNode; - Debug.Assert(tokenNode != null); - - if (tokenNode.Token.TokenType == RTokenType.String) { - - if (context.OldText.IndexOfAny(_stringSensitiveCharacters) >= 0) { - return TextChangeType.Structure; - } - - if (context.NewText.IndexOfAny(_stringSensitiveCharacters) >= 0) { - return TextChangeType.Structure; - } - - context.ChangedNode = node; - return TextChangeType.Token; - } - } - - return TextChangeType.Trivial; - } - } -} +using System.Collections.Generic; +using System.Diagnostics; +using Microsoft.Languages.Core.Text; +using Microsoft.R.Core.AST; +using Microsoft.R.Core.AST.Definitions; +using Microsoft.R.Core.AST.Statements.Conditionals; +using Microsoft.R.Core.Tokens; + +namespace Microsoft.R.Editor.Tree { + internal static class TextChangeAnalyzer { + private static char[] _lineBreaks = new char[] { '\n', '\r' }; + private static char[] _stringSensitiveCharacters = new char[] { '\\', '\'', '\"' }; + + public static void DetermineChangeType(TextChangeContext change) { + change.PendingChanges.TextChangeType |= CheckChangeInsideComment(change); + if (change.PendingChanges.TextChangeType == TextChangeType.Comment) { + return; + } else if (change.PendingChanges.TextChangeType == TextChangeType.Trivial) { + IAstNode node; + PositionType positionType; + + change.PendingChanges.TextChangeType |= CheckChangeInsideString(change, out node, out positionType); + if (change.PendingChanges.TextChangeType == TextChangeType.Token) { + return; + } else if (change.PendingChanges.TextChangeType == TextChangeType.Trivial) { + change.PendingChanges.TextChangeType |= CheckWhiteSpaceChange(change, node, positionType); + if (change.PendingChanges.TextChangeType == TextChangeType.Trivial) { + return; + } + } + } + + change.PendingChanges.TextChangeType = TextChangeType.Structure; + change.PendingChanges.FullParseRequired = true; + } + + private static TextChangeType CheckWhiteSpaceChange(TextChangeContext context, IAstNode node, PositionType positionType) { + context.ChangedNode = node; + + if (string.IsNullOrWhiteSpace(context.OldText) && string.IsNullOrWhiteSpace(context.NewText)) { + // In R there is no line continuation so expression may change when user adds or deletes line breaks. + bool lineBreakSensitive = node is If; + if (lineBreakSensitive) { + string oldLineText = context.OldTextProvider.GetText(new TextRange(context.OldStart, context.OldLength)); + string newLineText = context.NewTextProvider.GetText(new TextRange(context.NewStart, context.NewLength)); + + if (oldLineText.IndexOfAny(_lineBreaks) >= 0 || newLineText.IndexOfAny(_lineBreaks) >= 0) { + return TextChangeType.Structure; + } + } + + // Change inside token node is destructive: consider adding space inside an indentifier + if (!IsChangeDestructiveForChildNodes(node, context.OldRange)) { + return TextChangeType.Trivial; + } + } + + return TextChangeType.Structure; + } + + private static bool IsChangeDestructiveForChildNodes(IAstNode node, ITextRange changedRange) { + if(changedRange.End <= node.Start || changedRange.Start >= node.End) { + return false; + } + else if(node.Children.Count == 0) { + return true; + } + + bool result = false; + foreach (var child in node.Children) { + result |= IsChangeDestructiveForChildNodes(child, changedRange); + if(result) { + break; + } + } + + return result; + } + + private static TextChangeType CheckChangeInsideComment(TextChangeContext context) { + var comments = context.EditorTree.AstRoot.Comments; + + IReadOnlyList affectedComments = comments.GetItemsContainingInclusiveEnd(context.NewStart); + if (affectedComments.Count == 0) { + return TextChangeType.Trivial; + } + + if (affectedComments.Count > 1) { + return TextChangeType.Structure; + } + + // Make sure user is not deleting leading # effectively + // destroying the comment + RToken comment = comments[affectedComments[0]]; + if (comment.Start == context.NewStart && context.OldLength > 0) { + return TextChangeType.Structure; + } + + // The collection will return a match if the comment starts + // at the requested location. However, the change is not + // inside the comment if it's at the comment start and + // the old length of the change is zero. + + if (comment.Start == context.NewStart && context.OldLength == 0) { + if (context.NewText.IndexOf('#') < 0) { + context.ChangedComment = comment; + return TextChangeType.Comment; + } + } + + if (context.NewText.IndexOfAny(_lineBreaks) >= 0) { + return TextChangeType.Structure; + } + + // The change is not safe if old or new text contains line breaks + // as in R comments runs to the end of the line and deleting + // line break at the end of the comment may bring code into + // the comment range and change the entire file structure. + + if (context.OldText.IndexOfAny(_lineBreaks) >= 0) { + return TextChangeType.Structure; + } + + context.ChangedComment = comment; + return TextChangeType.Comment; + } + + private static TextChangeType CheckChangeInsideString(TextChangeContext context, out IAstNode node, out PositionType positionType) { + positionType = context.EditorTree.AstRoot.GetPositionNode(context.NewStart, out node); + + if (positionType == PositionType.Token) { + TokenNode tokenNode = node as TokenNode; + Debug.Assert(tokenNode != null); + + if (tokenNode.Token.TokenType == RTokenType.String) { + + if (context.OldText.IndexOfAny(_stringSensitiveCharacters) >= 0) { + return TextChangeType.Structure; + } + + if (context.NewText.IndexOfAny(_stringSensitiveCharacters) >= 0) { + return TextChangeType.Structure; + } + + context.ChangedNode = node; + return TextChangeType.Token; + } + } + + return TextChangeType.Trivial; + } + } +} diff --git a/src/R/Editor/Impl/Tree/TreeUpdateTask.cs b/src/R/Editor/Impl/Tree/TreeUpdateTask.cs index 865b0a31f..b83d6bf38 100644 --- a/src/R/Editor/Impl/Tree/TreeUpdateTask.cs +++ b/src/R/Editor/Impl/Tree/TreeUpdateTask.cs @@ -1,641 +1,641 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.Threading; -using System.Windows.Threading; -using Microsoft.Languages.Core.Text; -using Microsoft.Languages.Core.Utility; -using Microsoft.Languages.Editor.Shell; -using Microsoft.Languages.Editor.Tasks; -using Microsoft.Languages.Editor.Text; -using Microsoft.Languages.Editor.Utility; -using Microsoft.R.Core.AST; -using Microsoft.R.Core.AST.Definitions; -using Microsoft.VisualStudio.Text; - -namespace Microsoft.R.Editor.Tree { - /// - /// Asynchronous text change processing task - /// - internal sealed partial class TreeUpdateTask : CancellableTask { - #region Private members - - private static readonly Guid _treeUserId = new Guid("BE78E649-B9D4-4BC0-A332-F38A2B16CD10"); - private static int _parserDelay = 200; - - /// - /// Owner thread - typically main thread ID - /// - private int _ownerThreadId = Thread.CurrentThread.ManagedThreadId; - - /// - /// Editor tree that task is servicing - /// - private EditorTree _editorTree; - - /// - /// Text buffer - /// - private ITextBuffer TextBuffer { - get { return _editorTree.TextBuffer; } - } - - /// - /// Output queue of the background parser - /// - private ConcurrentQueue _backgroundParsingResults = new ConcurrentQueue(); - - /// - /// Pending changes since the last parse or since async parsing task started. - /// - private TextChange _pendingChanges = new TextChange(); - - /// - /// Time when background task requested transition to main thread. - /// Used for debugging/profiling purposes. - /// - private DateTime _uiThreadTransitionRequestTime; - - /// - /// If true the task was disposed (document was closed and tree is now orphaned). - /// - private bool _disposed; - - /// - /// Prevents disposing when background task is running - /// - private readonly object _disposeLock = new object(); - - /// - /// List of tree update completion callbacks supplied called once - /// when tree update completes. Typically used by async intellisense - /// providers that show intellisense list placeholder if tree is not - /// ready and populate it with actual items when tree update completes. - /// - private List _completionCallbacks = new List(); - - private DateTime _lastChangeTime = DateTime.UtcNow; - #endregion - - #region Constructors - public TreeUpdateTask(EditorTree editorTree) { - _editorTree = editorTree; - if (EditorShell.HasShell) { - // Can be null in test cases - EditorShell.Current.Idle += OnIdle; - } - } - #endregion - - #region Properties - /// - /// Detemines if tree is 'out of date' i.e. user made changes to the document - /// so text snapshot attached to the tree is no longer the same as ITextBuffer.CurrentSnapshot - /// - /// - internal bool ChangesPending { - get { return !_pendingChanges.IsEmpty; } - } - - /// - /// Returns an object that describes pending changes if any, null otherwise - /// - internal TextChange Changes { - get { return _pendingChanges; } - } - #endregion - - internal void TakeThreadOwnership() { - _ownerThreadId = Thread.CurrentThread.ManagedThreadId; - } - - internal void RegisterCompletionCallback(Action action) { - _completionCallbacks.Add(action); - } - - internal void ClearChanges() { - if (Thread.CurrentThread.ManagedThreadId != _ownerThreadId) - throw new ThreadStateException("Method should only be called on the main thread"); - - _pendingChanges.Clear(); - } - - /// - /// Indicates that parser is suspended and tree is not - /// getting updated on text buffer changes. This may, for example, - /// happen during document formatting or other massive changes. - /// - public bool UpdatesSuspended { get; private set; } - - /// - /// Suspend tree updates. Typically called before massive - /// changes to the document. - /// - internal void Suspend() { - UpdatesSuspended = true; - TextBufferChangedSinceSuspend = false; - } - - /// - /// Resumes tree updates. If changes were made to the text buffer - /// since suspend, full parse is performed. - /// - internal void Resume() { - if (UpdatesSuspended) { - UpdatesSuspended = false; - - if (TextBufferChangedSinceSuspend) { - TextBufferChangedSinceSuspend = false; - - GuardedOperations.DispatchInvoke(() => - ProcessPendingTextBufferChanges(async: true), - DispatcherPriority.ApplicationIdle); - } - } - } - - /// - /// Indicates if text buffer changed since tree updates were suspended. - /// - internal bool TextBufferChangedSinceSuspend { get; private set; } - - /// - /// Text buffer change event handler. Performs analysis of the change. - /// If change is trivial, such as change in whitespace (excluding line - /// breaks that in R may be sensistive), simply applies the changes - /// by shifting tree elements. If some elements get deleted or otherwise - /// damaged, removes them from the tree right away. Non-trivial changes - /// are queued for background parsing which starts on next on idle. - /// Methond must be called on a main thread only, typically from an event - /// handler that receives text buffer change events. - /// - internal void OnTextChanges(IReadOnlyCollection textChanges) { - if (Thread.CurrentThread.ManagedThreadId != _ownerThreadId) - throw new ThreadStateException("Method should only be called on the main thread"); - - _editorTree.FireOnUpdatesPending(textChanges); - if (UpdatesSuspended) { - this.TextBufferChangedSinceSuspend = true; - _pendingChanges.FullParseRequired = true; - } else { - foreach (TextChangeEventArgs change in textChanges) { - _lastChangeTime = DateTime.UtcNow; - var context = new TextChangeContext(_editorTree, change, _pendingChanges); - - // No need to analyze changes if full parse is already pending - if (!_pendingChanges.FullParseRequired) { - TextChangeAnalyzer.DetermineChangeType(context); - } - - ProcessChange(context); - } - } - } - - private void ProcessChange(TextChangeContext context) { - _editorTree.FireOnUpdateBegin(); - - if (_pendingChanges.IsSimpleChange) { - ProcessSimpleChange(context); - } else { - ProcessComplexChange(context); - } - } - - /// - /// Handles simple (safe) changes. - /// - private void ProcessSimpleChange(TextChangeContext context) { - bool elementsRemoved = false; - - try { - _editorTree.AcquireWriteLock(); - - elementsRemoved = DeleteAndShiftElements(context); - UpdateTreeTextSnapshot(); - - // If no elements were invalidated and full parse is not required, clear pending changes - if (!elementsRemoved) { - ClearChanges(); - } - } finally { - _editorTree.ReleaseWriteLock(); - } - - if (!elementsRemoved) { - if (context.ChangedNode != null || context.PendingChanges.TextChangeType == TextChangeType.Trivial) { - _editorTree.FireOnPositionsOnlyChanged(); - } - - _editorTree.FireOnUpdateCompleted(TreeUpdateType.PositionsOnly); - } else { - _editorTree.FireOnUpdateCompleted(TreeUpdateType.NodesRemoved); - } - - DebugTree.VerifyTree(_editorTree); - Debug.Assert(_editorTree.AstRoot.Children.Count > 0); - } - - /// - /// Handles non-trivial changes like changes that delete elements, - /// change identifier names, introducing new braces: changes - /// that cannot be handled without background parse. - /// - private void ProcessComplexChange(TextChangeContext context) { - // Cancel background parse if it is running - Cancel(); - - TextChange textChange = new TextChange() { - OldTextProvider = context.OldTextProvider, - NewTextProvider = context.NewTextProvider - }; - - try { - // Get write lock since there may be concurrent readers - // of the tree. Note that there are no concurrent writers - // since changes can only come from a background parser - // and are always applied from the main thread. - _editorTree.AcquireWriteLock(); - - if (_pendingChanges.FullParseRequired) { - // When full parse is required, change is like replace the entire file - textChange.OldRange = TextRange.FromBounds(0, context.OldText.Length); - textChange.NewRange = TextRange.FromBounds(0, context.NewText.Length); - - // Remove all elements from the tree - _editorTree.Invalidate(); - } else { - textChange.OldRange = context.OldRange; - textChange.NewRange = context.NewRange; - - DeleteAndShiftElements(context); - Debug.Assert(_editorTree.AstRoot.Children.Count > 0); - } - - _pendingChanges.Combine(textChange); - _pendingChanges.Version = TextBuffer != null ? TextBuffer.CurrentSnapshot.Version.VersionNumber : 1; - - UpdateTreeTextSnapshot(); - } finally { - // Lock must be released before firing events otherwise we may hang - _editorTree.ReleaseWriteLock(); - } - - _editorTree.FireOnUpdateCompleted(TreeUpdateType.NodesRemoved); - } - - private void UpdateTreeTextSnapshot() { - if (TextBuffer != null) { - if (_pendingChanges.OldTextProvider == null) - _pendingChanges.OldTextProvider = new TextProvider(_editorTree.TextSnapshot, partial: true); - - _editorTree.TextSnapshot = TextBuffer.CurrentSnapshot; - } - } - - // internal for unit tests - internal bool DeleteAndShiftElements(TextChangeContext context) { - if (Thread.CurrentThread.ManagedThreadId != _ownerThreadId) - throw new ThreadStateException("Method should only be called on the main thread"); - - TextChange textChange = context.PendingChanges; - var changeType = textChange.TextChangeType; - bool elementsChanged = false; - - if (changeType == TextChangeType.Structure) { - IAstNode changedElement = context.ChangedNode; - int start = context.NewStart; - - // We delete change nodes unless node is a token node - // which range can be modified such as string or comment - var positionType = PositionType.Undefined; - - if (changedElement != null) { - IAstNode node; - positionType = changedElement.GetPositionNode(context.NewStart, out node); - } - - bool deleteElements = (context.OldLength > 0) || (positionType != PositionType.Token); - - // In case of delete or replace we need to invalidate elements that were - // damaged by the delete operation. We need to remove elements and their keys - // so they won't be found by validator and incremental change analysis - // will not be looking at zombies. - - if (deleteElements) { - _pendingChanges.FullParseRequired = - _editorTree.InvalidateInRange(_editorTree.AstRoot, context.OldRange, out elementsChanged); - } - } - - _editorTree.NotifyTextChange(context.NewStart, context.OldLength, context.NewLength); - - return elementsChanged; - } - - /// - /// Idle time event handler. Kicks background parsing if there are pending changes - /// - private void OnIdle(object sender, EventArgs e) { - if (Thread.CurrentThread.ManagedThreadId != _ownerThreadId) - throw new ThreadStateException("Method should only be called on the main thread"); - - if (TextBuffer == null || TextBuffer.EditInProgress) - return; - - if (_lastChangeTime != DateTime.MinValue && TimeUtility.MillisecondsSinceUtc(_lastChangeTime) > _parserDelay) { - // Kick background parsing when idle slot comes so parser does not hit on every keystroke - ProcessPendingTextBufferChanges(async: true); - - _lastChangeTime = DateTime.MinValue; - } - } - - internal void ProcessPendingTextBufferChanges(bool async) { - // Text buffer can be null in unit tests - if (TextBuffer != null) { - ProcessPendingTextBufferChanges(new TextProvider(TextBuffer.CurrentSnapshot, partial: true), async); - } - } - - /// - /// Processes text buffer changed accumulated so far. - /// Typically called on idle. - /// - /// New text buffer content - /// True if processing is to be done asynchronously. - /// Non-async processing is typically used in unit tests only. - internal void ProcessPendingTextBufferChanges(ITextProvider newTextProvider, bool async) { - if (Thread.CurrentThread.ManagedThreadId != _ownerThreadId) - throw new ThreadStateException("Method should only be called on the main thread"); - - if (ChangesPending) { - if (async && (IsTaskRunning() || _backgroundParsingResults.Count > 0)) { - // Try next time or we may end up spawning a lot of tasks - return; - } - - // Combine changes in processing with pending changes. - var changesToProcess = new TextChange(_pendingChanges, newTextProvider); - - // We need to signal task start here, in the main thread since it takes - // some time before task is created and when it actually starts. - // Therefore setting task state in the task body creates a gap - // where we may end up spawning another task. - - base.Run((isCancelledCallback) => ProcessTextChanges(changesToProcess, async, isCancelledCallback), async); - } - } - - /// - /// Main asyncronous task body - /// - void ProcessTextChanges(TextChange changeToProcess, bool async, Func isCancelledCallback) { - lock (_disposeLock) { - if (_editorTree == null || _disposed || isCancelledCallback()) - return; - - EditorTreeChangeCollection treeChanges = null; - // Cache id since it can change if task is canceled - long taskId = TaskId; - - try { - AstRoot rootNode; - - // We only need read lock since changes will be applied - // from the main thread - if (async) { - rootNode = _editorTree.AcquireReadLock(_treeUserId); - } else { - rootNode = _editorTree.GetAstRootUnsafe(); - } - - treeChanges = new EditorTreeChangeCollection(changeToProcess.Version, changeToProcess.FullParseRequired); - TextChangeProcessor changeProcessor = new TextChangeProcessor(_editorTree, rootNode, isCancelledCallback); - - bool fullParseRequired = changeToProcess.FullParseRequired; - if (fullParseRequired) { - changeProcessor.FullParse(treeChanges, changeToProcess.NewTextProvider); - } else { - changeProcessor.ProcessChange(changeToProcess, treeChanges); - } - } finally { - if (async && _editorTree != null) - _editorTree.ReleaseReadLock(_treeUserId); - } - - // Lock should be released at this point since actual application - // of tree changes is going to be happen from the main thread. - - if (!isCancelledCallback() && treeChanges != null) { - // Queue results for the main thread application. This must be done before - // signaling that the task is complete since if EnsureProcessingComplete - // is waiting it will want to apply changes itself rather than wait for - // the DispatchOnUIThread to go though and hence it will need all changes - // stored and ready for application. - - _backgroundParsingResults.Enqueue(treeChanges); - } - - // Signal task complete now so if main thread is waiting - // it can proceed and appy the changes immediately. - SignalTaskComplete(taskId); - - if (_backgroundParsingResults.Count > 0) { - _uiThreadTransitionRequestTime = DateTime.UtcNow; - - // It is OK to post results while main thread might be working - // on them since if if it does, by the time posted request comes - // queue will already be empty. - if (async) { - // Post request to apply tree changes to the main thread. - // This must NOT block or else task will never enter 'RanToCompletion' state. - EditorShell.DispatchOnUIThread(() => ApplyBackgroundProcessingResults()); - } else { - // When processing is synchronous, apply changes and fire events right away. - ApplyBackgroundProcessingResults(); - } - } - } - } - - /// - /// Makes sure all pending changes are processed and applied to the tree - /// - internal void EnsureProcessingComplete() { - if (_ownerThreadId != Thread.CurrentThread.ManagedThreadId) - throw new ThreadStateException("Method should only be called on the main thread"); - - // We want to make sure changes that are in a background processing are applied to the tree - // before returning. We can't wait on events since call comes on a main thread and wait - // will prevent WPF dispatcher call from going through. - - // this will attempt to apply changes from the background processing results queue. - // It will discard stale changes and only apply changes if they match current - // text buffer snapshot version. This will only apply changes are that already - // in the queue. If background task is still running or all changes are stale - // the tree still will be out of date. - - // Check if tree is up to date. It is up to date if there are no text buffer changes that - // are pending for background processing. - if (ChangesPending) { - // If task is running, give it a chance to finish. No need to wait long - // since even on a large file full parse rarely takes more than 50 ms. - // Also we can't wait indefinitely since if task is *scheduled to run* - // and then got cancelled before actually sarting, it will never complete. - WaitForCompletion(2000); - - _uiThreadTransitionRequestTime = DateTime.UtcNow; - ApplyBackgroundProcessingResults(); - - if (ChangesPending) { -#if DEBUG - string originalPendingChanges = Changes.ToString(); -#endif - - // We *sometimes* still have pending changes even after calling ProcessPendingTextBufferChanges(async: false). - // I'd like to determine whether this is a timing issue by retrying here multiple times and seeing if it helps. - int retryCount = 0; - while (retryCount < 10 && ChangesPending) { - // Changes are still pending. Even if they are already in a backround processing, - // process them right away here and ignore background processing results - ProcessPendingTextBufferChanges(async: false); - retryCount += 1; - } - -#if DEBUG - if (retryCount == 10) { - string msg = string.Format(CultureInfo.InvariantCulture, - "Pending changes remain: ChangesPending: {0}, original:\"{1}\", new:\"{2}\"", - ChangesPending, originalPendingChanges, Changes.ToString()); - - // using Debugger.Break as I want all threads suspended so the state doesn't change - Debug.Assert(false, msg); - } -#endif - } - } - - Debug.Assert(!ChangesPending); - Debug.Assert(_editorTree.AstRoot.Children.Count > 0); - } - - /// - /// Applies queued changes to the tree. Must only be called in a main thread context. - /// - /// - internal void ApplyBackgroundProcessingResults() { - if (_ownerThreadId != Thread.CurrentThread.ManagedThreadId) - throw new ThreadStateException("Method should only be called on the main thread"); - - if (_disposed) - return; - - EditorTreeChangeCollection treeChanges; - var eventsToFire = new List(); - bool changed = false; - bool fullParse = false; - bool staleChanges = false; - - while (_backgroundParsingResults.TryDequeue(out treeChanges)) { - // If no changes are pending, then main thread already processes - // everything in EnsureProcessingComplete call. Changes are pending - // until they are applied to the tree. If queue is not empty - // it either contains stale changes or main thread had to handle - // changes in sync per request from, say, intellisense or formatting. - if (ChangesPending) { - // Check if background processing result matches current text buffer snapshot version - staleChanges = (TextBuffer != null && treeChanges.SnapshotVersion < TextBuffer.CurrentSnapshot.Version.VersionNumber); - - if (!staleChanges) { - // We can't fire events when appying changes since listeners may - // attempt to access tree which is not fully updated and/or may - // try to acquire read lock and hang since ApplyTreeChanges - // hols write lock. - - eventsToFire = ApplyTreeChanges(treeChanges); - fullParse = _pendingChanges.FullParseRequired; - - // Queue must be empty by now since only most recent changes are not stale - // Added local variable as I hit this assert, but _backgroundParsingResults.Count was zero - // by the time I broke into the debugger. If this hits again, we may need to - // think through this code and whether we need to be protecting against concurrent access. - int count = _backgroundParsingResults.Count; - Debug.Assert(count == 0); - - // Clear pending changes as we are done - ClearChanges(); - - changed = true; - - // No need for further processing as queue must be empty - break; - } - } - } - - if (!staleChanges) { - // Now that tree is fully updated, fire events - if (_editorTree != null) { - // First notify registered callbacks - InvokeCompletionCallbacks(); - _editorTree.FirePostUpdateEvents(eventsToFire, fullParse); - - if (changed) { - DebugTree.VerifyTree(_editorTree); - } - - if (!ChangesPending) { - Debug.Assert(_editorTree.AstRoot.Children.Count > 0); - } - } - } - } - - private void InvokeCompletionCallbacks() { - // Copy collection since callbacks may disconnet on invoke - var list = new List(); - list.AddRange(_completionCallbacks); - - foreach (var cb in list) - cb.Invoke(); - - _completionCallbacks.Clear(); - } - - List ApplyTreeChanges(EditorTreeChangeCollection changesToApply) { - // Check editor tree reference since document could have been - // closed before parsing was completed - - if (!_disposed && _editorTree != null) { - if (TextBuffer != null) - _editorTree.TextSnapshot = TextBuffer.CurrentSnapshot; - - return _editorTree.ApplyChangesFromQueue(changesToApply.ChangeQueue); - } - - return new List(); - } - - #region Dispose - protected override void Dispose(bool disposing) { - if (Thread.CurrentThread.ManagedThreadId != _ownerThreadId) - throw new ThreadStateException("Method should only be called on the main thread"); - - lock (_disposeLock) { - if (disposing) { - Cancel(); - - _disposed = true; - if (EditorShell.HasShell) { - EditorShell.Current.Idle -= OnIdle; - } - } - base.Dispose(disposing); - } - } - #endregion - } -} +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Threading; +using System.Windows.Threading; +using Microsoft.Languages.Core.Text; +using Microsoft.Languages.Core.Utility; +using Microsoft.Languages.Editor.Shell; +using Microsoft.Languages.Editor.Tasks; +using Microsoft.Languages.Editor.Text; +using Microsoft.Languages.Editor.Utility; +using Microsoft.R.Core.AST; +using Microsoft.R.Core.AST.Definitions; +using Microsoft.VisualStudio.Text; + +namespace Microsoft.R.Editor.Tree { + /// + /// Asynchronous text change processing task + /// + internal sealed partial class TreeUpdateTask : CancellableTask { + #region Private members + + private static readonly Guid _treeUserId = new Guid("BE78E649-B9D4-4BC0-A332-F38A2B16CD10"); + private static int _parserDelay = 200; + + /// + /// Owner thread - typically main thread ID + /// + private int _ownerThreadId = Thread.CurrentThread.ManagedThreadId; + + /// + /// Editor tree that task is servicing + /// + private EditorTree _editorTree; + + /// + /// Text buffer + /// + private ITextBuffer TextBuffer { + get { return _editorTree.TextBuffer; } + } + + /// + /// Output queue of the background parser + /// + private ConcurrentQueue _backgroundParsingResults = new ConcurrentQueue(); + + /// + /// Pending changes since the last parse or since async parsing task started. + /// + private TextChange _pendingChanges = new TextChange(); + + /// + /// Time when background task requested transition to main thread. + /// Used for debugging/profiling purposes. + /// + private DateTime _uiThreadTransitionRequestTime; + + /// + /// If true the task was disposed (document was closed and tree is now orphaned). + /// + private bool _disposed; + + /// + /// Prevents disposing when background task is running + /// + private readonly object _disposeLock = new object(); + + /// + /// List of tree update completion callbacks supplied called once + /// when tree update completes. Typically used by async intellisense + /// providers that show intellisense list placeholder if tree is not + /// ready and populate it with actual items when tree update completes. + /// + private List _completionCallbacks = new List(); + + private DateTime _lastChangeTime = DateTime.UtcNow; + #endregion + + #region Constructors + public TreeUpdateTask(EditorTree editorTree) { + _editorTree = editorTree; + if (EditorShell.HasShell) { + // Can be null in test cases + EditorShell.Current.Idle += OnIdle; + } + } + #endregion + + #region Properties + /// + /// Detemines if tree is 'out of date' i.e. user made changes to the document + /// so text snapshot attached to the tree is no longer the same as ITextBuffer.CurrentSnapshot + /// + /// + internal bool ChangesPending { + get { return !_pendingChanges.IsEmpty; } + } + + /// + /// Returns an object that describes pending changes if any, null otherwise + /// + internal TextChange Changes { + get { return _pendingChanges; } + } + #endregion + + internal void TakeThreadOwnership() { + _ownerThreadId = Thread.CurrentThread.ManagedThreadId; + } + + internal void RegisterCompletionCallback(Action action) { + _completionCallbacks.Add(action); + } + + internal void ClearChanges() { + if (Thread.CurrentThread.ManagedThreadId != _ownerThreadId) + throw new ThreadStateException("Method should only be called on the main thread"); + + _pendingChanges.Clear(); + } + + /// + /// Indicates that parser is suspended and tree is not + /// getting updated on text buffer changes. This may, for example, + /// happen during document formatting or other massive changes. + /// + public bool UpdatesSuspended { get; private set; } + + /// + /// Suspend tree updates. Typically called before massive + /// changes to the document. + /// + internal void Suspend() { + UpdatesSuspended = true; + TextBufferChangedSinceSuspend = false; + } + + /// + /// Resumes tree updates. If changes were made to the text buffer + /// since suspend, full parse is performed. + /// + internal void Resume() { + if (UpdatesSuspended) { + UpdatesSuspended = false; + + if (TextBufferChangedSinceSuspend) { + TextBufferChangedSinceSuspend = false; + + GuardedOperations.DispatchInvoke(() => + ProcessPendingTextBufferChanges(async: true), + DispatcherPriority.ApplicationIdle); + } + } + } + + /// + /// Indicates if text buffer changed since tree updates were suspended. + /// + internal bool TextBufferChangedSinceSuspend { get; private set; } + + /// + /// Text buffer change event handler. Performs analysis of the change. + /// If change is trivial, such as change in whitespace (excluding line + /// breaks that in R may be sensistive), simply applies the changes + /// by shifting tree elements. If some elements get deleted or otherwise + /// damaged, removes them from the tree right away. Non-trivial changes + /// are queued for background parsing which starts on next on idle. + /// Methond must be called on a main thread only, typically from an event + /// handler that receives text buffer change events. + /// + internal void OnTextChanges(IReadOnlyCollection textChanges) { + if (Thread.CurrentThread.ManagedThreadId != _ownerThreadId) + throw new ThreadStateException("Method should only be called on the main thread"); + + _editorTree.FireOnUpdatesPending(textChanges); + if (UpdatesSuspended) { + this.TextBufferChangedSinceSuspend = true; + _pendingChanges.FullParseRequired = true; + } else { + foreach (TextChangeEventArgs change in textChanges) { + _lastChangeTime = DateTime.UtcNow; + var context = new TextChangeContext(_editorTree, change, _pendingChanges); + + // No need to analyze changes if full parse is already pending + if (!_pendingChanges.FullParseRequired) { + TextChangeAnalyzer.DetermineChangeType(context); + } + + ProcessChange(context); + } + } + } + + private void ProcessChange(TextChangeContext context) { + _editorTree.FireOnUpdateBegin(); + + if (_pendingChanges.IsSimpleChange) { + ProcessSimpleChange(context); + } else { + ProcessComplexChange(context); + } + } + + /// + /// Handles simple (safe) changes. + /// + private void ProcessSimpleChange(TextChangeContext context) { + bool elementsRemoved = false; + + try { + _editorTree.AcquireWriteLock(); + + elementsRemoved = DeleteAndShiftElements(context); + UpdateTreeTextSnapshot(); + + // If no elements were invalidated and full parse is not required, clear pending changes + if (!elementsRemoved) { + ClearChanges(); + } + } finally { + _editorTree.ReleaseWriteLock(); + } + + if (!elementsRemoved) { + if (context.ChangedNode != null || context.PendingChanges.TextChangeType == TextChangeType.Trivial) { + _editorTree.FireOnPositionsOnlyChanged(); + } + + _editorTree.FireOnUpdateCompleted(TreeUpdateType.PositionsOnly); + } else { + _editorTree.FireOnUpdateCompleted(TreeUpdateType.NodesRemoved); + } + + DebugTree.VerifyTree(_editorTree); + Debug.Assert(_editorTree.AstRoot.Children.Count > 0); + } + + /// + /// Handles non-trivial changes like changes that delete elements, + /// change identifier names, introducing new braces: changes + /// that cannot be handled without background parse. + /// + private void ProcessComplexChange(TextChangeContext context) { + // Cancel background parse if it is running + Cancel(); + + TextChange textChange = new TextChange() { + OldTextProvider = context.OldTextProvider, + NewTextProvider = context.NewTextProvider + }; + + try { + // Get write lock since there may be concurrent readers + // of the tree. Note that there are no concurrent writers + // since changes can only come from a background parser + // and are always applied from the main thread. + _editorTree.AcquireWriteLock(); + + if (_pendingChanges.FullParseRequired) { + // When full parse is required, change is like replace the entire file + textChange.OldRange = TextRange.FromBounds(0, context.OldText.Length); + textChange.NewRange = TextRange.FromBounds(0, context.NewText.Length); + + // Remove all elements from the tree + _editorTree.Invalidate(); + } else { + textChange.OldRange = context.OldRange; + textChange.NewRange = context.NewRange; + + DeleteAndShiftElements(context); + Debug.Assert(_editorTree.AstRoot.Children.Count > 0); + } + + _pendingChanges.Combine(textChange); + _pendingChanges.Version = TextBuffer != null ? TextBuffer.CurrentSnapshot.Version.VersionNumber : 1; + + UpdateTreeTextSnapshot(); + } finally { + // Lock must be released before firing events otherwise we may hang + _editorTree.ReleaseWriteLock(); + } + + _editorTree.FireOnUpdateCompleted(TreeUpdateType.NodesRemoved); + } + + private void UpdateTreeTextSnapshot() { + if (TextBuffer != null) { + if (_pendingChanges.OldTextProvider == null) + _pendingChanges.OldTextProvider = new TextProvider(_editorTree.TextSnapshot, partial: true); + + _editorTree.TextSnapshot = TextBuffer.CurrentSnapshot; + } + } + + // internal for unit tests + internal bool DeleteAndShiftElements(TextChangeContext context) { + if (Thread.CurrentThread.ManagedThreadId != _ownerThreadId) + throw new ThreadStateException("Method should only be called on the main thread"); + + TextChange textChange = context.PendingChanges; + var changeType = textChange.TextChangeType; + bool elementsChanged = false; + + if (changeType == TextChangeType.Structure) { + IAstNode changedElement = context.ChangedNode; + int start = context.NewStart; + + // We delete change nodes unless node is a token node + // which range can be modified such as string or comment + var positionType = PositionType.Undefined; + + if (changedElement != null) { + IAstNode node; + positionType = changedElement.GetPositionNode(context.NewStart, out node); + } + + bool deleteElements = (context.OldLength > 0) || (positionType != PositionType.Token); + + // In case of delete or replace we need to invalidate elements that were + // damaged by the delete operation. We need to remove elements and their keys + // so they won't be found by validator and incremental change analysis + // will not be looking at zombies. + + if (deleteElements) { + _pendingChanges.FullParseRequired = + _editorTree.InvalidateInRange(_editorTree.AstRoot, context.OldRange, out elementsChanged); + } + } + + _editorTree.NotifyTextChange(context.NewStart, context.OldLength, context.NewLength); + + return elementsChanged; + } + + /// + /// Idle time event handler. Kicks background parsing if there are pending changes + /// + private void OnIdle(object sender, EventArgs e) { + if (Thread.CurrentThread.ManagedThreadId != _ownerThreadId) + throw new ThreadStateException("Method should only be called on the main thread"); + + if (TextBuffer == null || TextBuffer.EditInProgress) + return; + + if (_lastChangeTime != DateTime.MinValue && TimeUtility.MillisecondsSinceUtc(_lastChangeTime) > _parserDelay) { + // Kick background parsing when idle slot comes so parser does not hit on every keystroke + ProcessPendingTextBufferChanges(async: true); + + _lastChangeTime = DateTime.MinValue; + } + } + + internal void ProcessPendingTextBufferChanges(bool async) { + // Text buffer can be null in unit tests + if (TextBuffer != null) { + ProcessPendingTextBufferChanges(new TextProvider(TextBuffer.CurrentSnapshot, partial: true), async); + } + } + + /// + /// Processes text buffer changed accumulated so far. + /// Typically called on idle. + /// + /// New text buffer content + /// True if processing is to be done asynchronously. + /// Non-async processing is typically used in unit tests only. + internal void ProcessPendingTextBufferChanges(ITextProvider newTextProvider, bool async) { + if (Thread.CurrentThread.ManagedThreadId != _ownerThreadId) + throw new ThreadStateException("Method should only be called on the main thread"); + + if (ChangesPending) { + if (async && (IsTaskRunning() || _backgroundParsingResults.Count > 0)) { + // Try next time or we may end up spawning a lot of tasks + return; + } + + // Combine changes in processing with pending changes. + var changesToProcess = new TextChange(_pendingChanges, newTextProvider); + + // We need to signal task start here, in the main thread since it takes + // some time before task is created and when it actually starts. + // Therefore setting task state in the task body creates a gap + // where we may end up spawning another task. + + base.Run((isCancelledCallback) => ProcessTextChanges(changesToProcess, async, isCancelledCallback), async); + } + } + + /// + /// Main asyncronous task body + /// + void ProcessTextChanges(TextChange changeToProcess, bool async, Func isCancelledCallback) { + lock (_disposeLock) { + if (_editorTree == null || _disposed || isCancelledCallback()) + return; + + EditorTreeChangeCollection treeChanges = null; + // Cache id since it can change if task is canceled + long taskId = TaskId; + + try { + AstRoot rootNode; + + // We only need read lock since changes will be applied + // from the main thread + if (async) { + rootNode = _editorTree.AcquireReadLock(_treeUserId); + } else { + rootNode = _editorTree.GetAstRootUnsafe(); + } + + treeChanges = new EditorTreeChangeCollection(changeToProcess.Version, changeToProcess.FullParseRequired); + TextChangeProcessor changeProcessor = new TextChangeProcessor(_editorTree, rootNode, isCancelledCallback); + + bool fullParseRequired = changeToProcess.FullParseRequired; + if (fullParseRequired) { + changeProcessor.FullParse(treeChanges, changeToProcess.NewTextProvider); + } else { + changeProcessor.ProcessChange(changeToProcess, treeChanges); + } + } finally { + if (async && _editorTree != null) + _editorTree.ReleaseReadLock(_treeUserId); + } + + // Lock should be released at this point since actual application + // of tree changes is going to be happen from the main thread. + + if (!isCancelledCallback() && treeChanges != null) { + // Queue results for the main thread application. This must be done before + // signaling that the task is complete since if EnsureProcessingComplete + // is waiting it will want to apply changes itself rather than wait for + // the DispatchOnUIThread to go though and hence it will need all changes + // stored and ready for application. + + _backgroundParsingResults.Enqueue(treeChanges); + } + + // Signal task complete now so if main thread is waiting + // it can proceed and appy the changes immediately. + SignalTaskComplete(taskId); + + if (_backgroundParsingResults.Count > 0) { + _uiThreadTransitionRequestTime = DateTime.UtcNow; + + // It is OK to post results while main thread might be working + // on them since if if it does, by the time posted request comes + // queue will already be empty. + if (async) { + // Post request to apply tree changes to the main thread. + // This must NOT block or else task will never enter 'RanToCompletion' state. + EditorShell.DispatchOnUIThread(() => ApplyBackgroundProcessingResults()); + } else { + // When processing is synchronous, apply changes and fire events right away. + ApplyBackgroundProcessingResults(); + } + } + } + } + + /// + /// Makes sure all pending changes are processed and applied to the tree + /// + internal void EnsureProcessingComplete() { + if (_ownerThreadId != Thread.CurrentThread.ManagedThreadId) + throw new ThreadStateException("Method should only be called on the main thread"); + + // We want to make sure changes that are in a background processing are applied to the tree + // before returning. We can't wait on events since call comes on a main thread and wait + // will prevent WPF dispatcher call from going through. + + // this will attempt to apply changes from the background processing results queue. + // It will discard stale changes and only apply changes if they match current + // text buffer snapshot version. This will only apply changes are that already + // in the queue. If background task is still running or all changes are stale + // the tree still will be out of date. + + // Check if tree is up to date. It is up to date if there are no text buffer changes that + // are pending for background processing. + if (ChangesPending) { + // If task is running, give it a chance to finish. No need to wait long + // since even on a large file full parse rarely takes more than 50 ms. + // Also we can't wait indefinitely since if task is *scheduled to run* + // and then got cancelled before actually sarting, it will never complete. + WaitForCompletion(2000); + + _uiThreadTransitionRequestTime = DateTime.UtcNow; + ApplyBackgroundProcessingResults(); + + if (ChangesPending) { +#if DEBUG + string originalPendingChanges = Changes.ToString(); +#endif + + // We *sometimes* still have pending changes even after calling ProcessPendingTextBufferChanges(async: false). + // I'd like to determine whether this is a timing issue by retrying here multiple times and seeing if it helps. + int retryCount = 0; + while (retryCount < 10 && ChangesPending) { + // Changes are still pending. Even if they are already in a backround processing, + // process them right away here and ignore background processing results + ProcessPendingTextBufferChanges(async: false); + retryCount += 1; + } + +#if DEBUG + if (retryCount == 10) { + string msg = string.Format(CultureInfo.InvariantCulture, + "Pending changes remain: ChangesPending: {0}, original:\"{1}\", new:\"{2}\"", + ChangesPending, originalPendingChanges, Changes.ToString()); + + // using Debugger.Break as I want all threads suspended so the state doesn't change + Debug.Assert(false, msg); + } +#endif + } + } + + Debug.Assert(!ChangesPending); + Debug.Assert(_editorTree.AstRoot.Children.Count > 0); + } + + /// + /// Applies queued changes to the tree. Must only be called in a main thread context. + /// + /// + internal void ApplyBackgroundProcessingResults() { + if (_ownerThreadId != Thread.CurrentThread.ManagedThreadId) + throw new ThreadStateException("Method should only be called on the main thread"); + + if (_disposed) + return; + + EditorTreeChangeCollection treeChanges; + var eventsToFire = new List(); + bool changed = false; + bool fullParse = false; + bool staleChanges = false; + + while (_backgroundParsingResults.TryDequeue(out treeChanges)) { + // If no changes are pending, then main thread already processes + // everything in EnsureProcessingComplete call. Changes are pending + // until they are applied to the tree. If queue is not empty + // it either contains stale changes or main thread had to handle + // changes in sync per request from, say, intellisense or formatting. + if (ChangesPending) { + // Check if background processing result matches current text buffer snapshot version + staleChanges = (TextBuffer != null && treeChanges.SnapshotVersion < TextBuffer.CurrentSnapshot.Version.VersionNumber); + + if (!staleChanges) { + // We can't fire events when appying changes since listeners may + // attempt to access tree which is not fully updated and/or may + // try to acquire read lock and hang since ApplyTreeChanges + // hols write lock. + + eventsToFire = ApplyTreeChanges(treeChanges); + fullParse = _pendingChanges.FullParseRequired; + + // Queue must be empty by now since only most recent changes are not stale + // Added local variable as I hit this assert, but _backgroundParsingResults.Count was zero + // by the time I broke into the debugger. If this hits again, we may need to + // think through this code and whether we need to be protecting against concurrent access. + int count = _backgroundParsingResults.Count; + Debug.Assert(count == 0); + + // Clear pending changes as we are done + ClearChanges(); + + changed = true; + + // No need for further processing as queue must be empty + break; + } + } + } + + if (!staleChanges) { + // Now that tree is fully updated, fire events + if (_editorTree != null) { + // First notify registered callbacks + InvokeCompletionCallbacks(); + _editorTree.FirePostUpdateEvents(eventsToFire, fullParse); + + if (changed) { + DebugTree.VerifyTree(_editorTree); + } + + if (!ChangesPending) { + Debug.Assert(_editorTree.AstRoot.Children.Count > 0); + } + } + } + } + + private void InvokeCompletionCallbacks() { + // Copy collection since callbacks may disconnet on invoke + var list = new List(); + list.AddRange(_completionCallbacks); + + foreach (var cb in list) + cb.Invoke(); + + _completionCallbacks.Clear(); + } + + List ApplyTreeChanges(EditorTreeChangeCollection changesToApply) { + // Check editor tree reference since document could have been + // closed before parsing was completed + + if (!_disposed && _editorTree != null) { + if (TextBuffer != null) + _editorTree.TextSnapshot = TextBuffer.CurrentSnapshot; + + return _editorTree.ApplyChangesFromQueue(changesToApply.ChangeQueue); + } + + return new List(); + } + + #region Dispose + protected override void Dispose(bool disposing) { + if (Thread.CurrentThread.ManagedThreadId != _ownerThreadId) + throw new ThreadStateException("Method should only be called on the main thread"); + + lock (_disposeLock) { + if (disposing) { + Cancel(); + + _disposed = true; + if (EditorShell.HasShell) { + EditorShell.Current.Idle -= OnIdle; + } + } + base.Dispose(disposing); + } + } + #endregion + } +} diff --git a/src/R/Editor/Impl/Validation/Definitions/IValidationError.cs b/src/R/Editor/Impl/Validation/Definitions/IValidationError.cs index 4f0ef88fe..324d6871a 100644 --- a/src/R/Editor/Impl/Validation/Definitions/IValidationError.cs +++ b/src/R/Editor/Impl/Validation/Definitions/IValidationError.cs @@ -1,29 +1,29 @@ -using Microsoft.Languages.Core.Text; -using Microsoft.R.Core.AST.Definitions; -using Microsoft.R.Core.Parser; -using Microsoft.R.Core.Tokens; - -namespace Microsoft.R.Editor.Validation.Definitions -{ - /// - /// Represents validation result. May be based - /// on AST node or a standalone token. - /// - public interface IValidationError: ITextRange - { - /// - /// Message to place in a task list and/or tooltip - /// - string Message { get; } - - /// - /// Location of the error in the element. - /// - ErrorLocation Location { get; } - - /// - /// Error severity - /// - ErrorSeverity Severity { get; } - } -} +using Microsoft.Languages.Core.Text; +using Microsoft.R.Core.AST.Definitions; +using Microsoft.R.Core.Parser; +using Microsoft.R.Core.Tokens; + +namespace Microsoft.R.Editor.Validation.Definitions +{ + /// + /// Represents validation result. May be based + /// on AST node or a standalone token. + /// + public interface IValidationError: ITextRange + { + /// + /// Message to place in a task list and/or tooltip + /// + string Message { get; } + + /// + /// Location of the error in the element. + /// + ErrorLocation Location { get; } + + /// + /// Error severity + /// + ErrorSeverity Severity { get; } + } +} diff --git a/src/R/Editor/Impl/Validation/Errors/ErrorText.cs b/src/R/Editor/Impl/Validation/Errors/ErrorText.cs index 60f658f7c..901850e1f 100644 --- a/src/R/Editor/Impl/Validation/Errors/ErrorText.cs +++ b/src/R/Editor/Impl/Validation/Errors/ErrorText.cs @@ -1,73 +1,73 @@ -using Microsoft.R.Core.Parser; - -namespace Microsoft.R.Editor.Validation.Errors -{ - internal static class ErrorText - { - public static string GetText(ParseErrorType errorType) - { - switch (errorType) - { - default: - return Resources.ParseError_General; - - case ParseErrorType.UnexpectedToken: - return Resources.ParseError_UnexpectedToken; - - case ParseErrorType.IndentifierExpected: - return Resources.ParseError_IndentifierExpected; - - case ParseErrorType.NumberExpected: - return Resources.ParseError_NumberExpected; - - case ParseErrorType.LogicalExpected: - return Resources.ParseError_LogicalExpected; - - case ParseErrorType.StringExpected: - return Resources.ParseError_StringExpected; - - case ParseErrorType.ExpressionExpected: - return Resources.ParseError_ExpressionExpected; - - case ParseErrorType.LeftOperandExpected: - return Resources.ParseError_LeftOperandExpected; - - case ParseErrorType.RightOperandExpected: - return Resources.ParseError_RightOperandExpected; - - case ParseErrorType.OpenCurlyBraceExpected: - return Resources.ParseError_OpenCurlyBraceExpected; - - case ParseErrorType.CloseCurlyBraceExpected: - return Resources.ParseError_CloseCurlyBraceExpected; - - case ParseErrorType.OpenBraceExpected: - return Resources.ParseError_OpenBraceExpected; - - case ParseErrorType.CloseBraceExpected: - return Resources.ParseError_CloseBraceExpected; - - case ParseErrorType.OpenSquareBracketExpected: - return Resources.ParseError_OpenSquareBracketExpected; - - case ParseErrorType.CloseSquareBracketExpected: - return Resources.ParseError_CloseSquareBracketExpected; - - case ParseErrorType.OperatorExpected: - return Resources.ParseError_OperatorExpected; - - case ParseErrorType.FunctionExpected: - return Resources.ParseError_FunctionExpected; - - case ParseErrorType.InKeywordExpected: - return Resources.ParseError_InKeywordExpected; - - case ParseErrorType.UnexpectedEndOfFile: - return Resources.ParseError_UnexpectedEndOfFile; - - case ParseErrorType.FunctionBodyExpected: - return Resources.ParseError_FunctionBodyExpected; - } - } - } -} +using Microsoft.R.Core.Parser; + +namespace Microsoft.R.Editor.Validation.Errors +{ + internal static class ErrorText + { + public static string GetText(ParseErrorType errorType) + { + switch (errorType) + { + default: + return Resources.ParseError_General; + + case ParseErrorType.UnexpectedToken: + return Resources.ParseError_UnexpectedToken; + + case ParseErrorType.IndentifierExpected: + return Resources.ParseError_IndentifierExpected; + + case ParseErrorType.NumberExpected: + return Resources.ParseError_NumberExpected; + + case ParseErrorType.LogicalExpected: + return Resources.ParseError_LogicalExpected; + + case ParseErrorType.StringExpected: + return Resources.ParseError_StringExpected; + + case ParseErrorType.ExpressionExpected: + return Resources.ParseError_ExpressionExpected; + + case ParseErrorType.LeftOperandExpected: + return Resources.ParseError_LeftOperandExpected; + + case ParseErrorType.RightOperandExpected: + return Resources.ParseError_RightOperandExpected; + + case ParseErrorType.OpenCurlyBraceExpected: + return Resources.ParseError_OpenCurlyBraceExpected; + + case ParseErrorType.CloseCurlyBraceExpected: + return Resources.ParseError_CloseCurlyBraceExpected; + + case ParseErrorType.OpenBraceExpected: + return Resources.ParseError_OpenBraceExpected; + + case ParseErrorType.CloseBraceExpected: + return Resources.ParseError_CloseBraceExpected; + + case ParseErrorType.OpenSquareBracketExpected: + return Resources.ParseError_OpenSquareBracketExpected; + + case ParseErrorType.CloseSquareBracketExpected: + return Resources.ParseError_CloseSquareBracketExpected; + + case ParseErrorType.OperatorExpected: + return Resources.ParseError_OperatorExpected; + + case ParseErrorType.FunctionExpected: + return Resources.ParseError_FunctionExpected; + + case ParseErrorType.InKeywordExpected: + return Resources.ParseError_InKeywordExpected; + + case ParseErrorType.UnexpectedEndOfFile: + return Resources.ParseError_UnexpectedEndOfFile; + + case ParseErrorType.FunctionBodyExpected: + return Resources.ParseError_FunctionBodyExpected; + } + } + } +} diff --git a/src/R/Editor/Impl/Validation/Errors/ValidationError.cs b/src/R/Editor/Impl/Validation/Errors/ValidationError.cs index 84101d50f..b80c92350 100644 --- a/src/R/Editor/Impl/Validation/Errors/ValidationError.cs +++ b/src/R/Editor/Impl/Validation/Errors/ValidationError.cs @@ -1,27 +1,27 @@ -using System.Diagnostics.CodeAnalysis; -using Microsoft.Languages.Core.Text; -using Microsoft.R.Core.Parser; -using Microsoft.R.Editor.Validation.Definitions; - -namespace Microsoft.R.Editor.Validation.Errors -{ - [ExcludeFromCodeCoverage] - public class ValidationError : ValidationErrorBase - { - /// - /// Constructs validation error based on existing error. - /// - public ValidationError(IValidationError error) : - base(error, error.Message, error.Location, error.Severity) - { - } - - /// - /// Constructs validation error for an element at a specified location. - /// - public ValidationError(ITextRange range, string message, ErrorLocation location) : - base(range, message, location, ErrorSeverity.Error) - { - } - } -} +using System.Diagnostics.CodeAnalysis; +using Microsoft.Languages.Core.Text; +using Microsoft.R.Core.Parser; +using Microsoft.R.Editor.Validation.Definitions; + +namespace Microsoft.R.Editor.Validation.Errors +{ + [ExcludeFromCodeCoverage] + public class ValidationError : ValidationErrorBase + { + /// + /// Constructs validation error based on existing error. + /// + public ValidationError(IValidationError error) : + base(error, error.Message, error.Location, error.Severity) + { + } + + /// + /// Constructs validation error for an element at a specified location. + /// + public ValidationError(ITextRange range, string message, ErrorLocation location) : + base(range, message, location, ErrorSeverity.Error) + { + } + } +} diff --git a/src/R/Editor/Impl/Validation/Errors/ValidationErrorBase.cs b/src/R/Editor/Impl/Validation/Errors/ValidationErrorBase.cs index a5d51b9e0..497bd751a 100644 --- a/src/R/Editor/Impl/Validation/Errors/ValidationErrorBase.cs +++ b/src/R/Editor/Impl/Validation/Errors/ValidationErrorBase.cs @@ -1,52 +1,52 @@ -using System.Diagnostics.CodeAnalysis; -using Microsoft.Languages.Core.Text; -using Microsoft.R.Core.AST.Definitions; -using Microsoft.R.Core.Parser; -using Microsoft.R.Core.Tokens; -using Microsoft.R.Editor.Validation.Definitions; - -namespace Microsoft.R.Editor.Validation.Errors -{ - [ExcludeFromCodeCoverage] - public class ValidationErrorBase : TextRange, IValidationError - { - /// - /// Error or warning message. - /// - public string Message { get; private set; } - - /// - /// Error location - /// - public ErrorLocation Location { get; private set; } - - /// - /// Error severity - /// - public ErrorSeverity Severity { get; private set; } - - public ValidationErrorBase(ITextRange range, string message, ErrorLocation location, ErrorSeverity severity) : - base(range) - { - Message = message; - Severity = severity; - Location = location; - } - - private static ITextRange GetLocationRange(IAstNode node, RToken token, ErrorLocation location) - { - ITextRange itemRange = node != null ? node : token as ITextRange; - - //switch (location) - //{ - // case ErrorLocation.BeforeToken: - // return new TextRange(Math.Max(0, itemRange.Start-1), 1); - - // case ErrorLocation.AfterToken: - // return new TextRange(itemRange.End, 1); - //} - - return itemRange; - } - } -} +using System.Diagnostics.CodeAnalysis; +using Microsoft.Languages.Core.Text; +using Microsoft.R.Core.AST.Definitions; +using Microsoft.R.Core.Parser; +using Microsoft.R.Core.Tokens; +using Microsoft.R.Editor.Validation.Definitions; + +namespace Microsoft.R.Editor.Validation.Errors +{ + [ExcludeFromCodeCoverage] + public class ValidationErrorBase : TextRange, IValidationError + { + /// + /// Error or warning message. + /// + public string Message { get; private set; } + + /// + /// Error location + /// + public ErrorLocation Location { get; private set; } + + /// + /// Error severity + /// + public ErrorSeverity Severity { get; private set; } + + public ValidationErrorBase(ITextRange range, string message, ErrorLocation location, ErrorSeverity severity) : + base(range) + { + Message = message; + Severity = severity; + Location = location; + } + + private static ITextRange GetLocationRange(IAstNode node, RToken token, ErrorLocation location) + { + ITextRange itemRange = node != null ? node : token as ITextRange; + + //switch (location) + //{ + // case ErrorLocation.BeforeToken: + // return new TextRange(Math.Max(0, itemRange.Start-1), 1); + + // case ErrorLocation.AfterToken: + // return new TextRange(itemRange.End, 1); + //} + + return itemRange; + } + } +} diff --git a/src/R/Editor/Impl/Validation/Errors/ValidationErrorCollection.cs b/src/R/Editor/Impl/Validation/Errors/ValidationErrorCollection.cs index 0fb7c092f..524f5caf3 100644 --- a/src/R/Editor/Impl/Validation/Errors/ValidationErrorCollection.cs +++ b/src/R/Editor/Impl/Validation/Errors/ValidationErrorCollection.cs @@ -1,15 +1,15 @@ -using System.Collections.Generic; -using Microsoft.Languages.Core.Text; -using Microsoft.R.Core.Parser; -using Microsoft.R.Editor.Validation.Definitions; - -namespace Microsoft.R.Editor.Validation.Errors -{ - public class ValidationErrorCollection : List - { - public void Add(ITextRange range, string message, ErrorLocation location) - { - Add(new ValidationError(range, message, location)); - } - } -} +using System.Collections.Generic; +using Microsoft.Languages.Core.Text; +using Microsoft.R.Core.Parser; +using Microsoft.R.Editor.Validation.Definitions; + +namespace Microsoft.R.Editor.Validation.Errors +{ + public class ValidationErrorCollection : List + { + public void Add(ITextRange range, string message, ErrorLocation location) + { + Add(new ValidationError(range, message, location)); + } + } +} diff --git a/src/R/Editor/Impl/Validation/Errors/ValidationMessage.cs b/src/R/Editor/Impl/Validation/Errors/ValidationMessage.cs index 696ce7f11..04e12a66b 100644 --- a/src/R/Editor/Impl/Validation/Errors/ValidationMessage.cs +++ b/src/R/Editor/Impl/Validation/Errors/ValidationMessage.cs @@ -1,15 +1,15 @@ -using System.Diagnostics.CodeAnalysis; -using Microsoft.Languages.Core.Text; -using Microsoft.R.Core.Parser; - -namespace Microsoft.R.Editor.Validation.Errors -{ - [ExcludeFromCodeCoverage] - public class ValidationMessage : ValidationErrorBase - { - public ValidationMessage(ITextRange range, string message, ErrorLocation location) : - base(range, message, location, ErrorSeverity.Informational) - { - } - } -} +using System.Diagnostics.CodeAnalysis; +using Microsoft.Languages.Core.Text; +using Microsoft.R.Core.Parser; + +namespace Microsoft.R.Editor.Validation.Errors +{ + [ExcludeFromCodeCoverage] + public class ValidationMessage : ValidationErrorBase + { + public ValidationMessage(ITextRange range, string message, ErrorLocation location) : + base(range, message, location, ErrorSeverity.Informational) + { + } + } +} diff --git a/src/R/Editor/Impl/Validation/Errors/ValidationWarning.cs b/src/R/Editor/Impl/Validation/Errors/ValidationWarning.cs index f814bda21..47e187abe 100644 --- a/src/R/Editor/Impl/Validation/Errors/ValidationWarning.cs +++ b/src/R/Editor/Impl/Validation/Errors/ValidationWarning.cs @@ -1,20 +1,20 @@ -using Microsoft.R.Core.AST.Definitions; -using Microsoft.R.Core.Parser; -using Microsoft.R.Core.Tokens; -using System.Diagnostics.CodeAnalysis; -using Microsoft.Languages.Core.Text; - -namespace Microsoft.R.Editor.Validation.Errors -{ - [ExcludeFromCodeCoverage] - public class ValidationWarning : ValidationErrorBase - { - /// - /// Constructs validation warning for an element at a specified location. - /// - public ValidationWarning(ITextRange range, string message, ErrorLocation location) : - base(range, message, location, ErrorSeverity.Warning) - { - } - } -} +using Microsoft.R.Core.AST.Definitions; +using Microsoft.R.Core.Parser; +using Microsoft.R.Core.Tokens; +using System.Diagnostics.CodeAnalysis; +using Microsoft.Languages.Core.Text; + +namespace Microsoft.R.Editor.Validation.Errors +{ + [ExcludeFromCodeCoverage] + public class ValidationWarning : ValidationErrorBase + { + /// + /// Constructs validation warning for an element at a specified location. + /// + public ValidationWarning(ITextRange range, string message, ErrorLocation location) : + base(range, message, location, ErrorSeverity.Warning) + { + } + } +} diff --git a/src/R/Editor/Impl/Validation/Tagger/EditorErrorTag.cs b/src/R/Editor/Impl/Validation/Tagger/EditorErrorTag.cs index b96336913..688f4a547 100644 --- a/src/R/Editor/Impl/Validation/Tagger/EditorErrorTag.cs +++ b/src/R/Editor/Impl/Validation/Tagger/EditorErrorTag.cs @@ -1,231 +1,231 @@ -using System; -using Microsoft.Languages.Core.Text; -using Microsoft.Languages.Editor.Services; -using Microsoft.Languages.Editor.TaskList.Definitions; -using Microsoft.R.Core.Parser; -using Microsoft.R.Editor.Document; -using Microsoft.R.Editor.Tree.Definitions; -using Microsoft.R.Editor.Validation.Definitions; -using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Text.Adornments; -using Microsoft.VisualStudio.Text.Tagging; - -namespace Microsoft.R.Editor.Validation.Tagger { - /// - /// This represents an underlined syntax error in the editor - /// - internal class EditorErrorTag : ErrorTag, ITagSpan, IExpandableTextRange, IEditorTaskListItem - { - private ITextBuffer _textBuffer; - private ITextRange _range; - - public EditorErrorTag(IEditorTree editorTree, IValidationError error) - : base(GetErrorType(error), error.Message) - { - _textBuffer = editorTree.TextBuffer; - - Description = error.Message; - TaskType = GetTaskType(error); - - _range = error; - - if (_range == null || _range.Start < 0) - _range = TextRange.EmptyRange; - } - - #region ITagSpan Members - public IErrorTag Tag - { - get { return this; } - } - - public SnapshotSpan Span - { - get - { - // Positions may be out of date: editor is asking about current snapshot - // while tree can still be holding on the earlier one since tree snapshot - // is updated when background parsing completes. However, tag positions - // are constantly updated when text buffer changes and hence they match - // current text buffer snapshot. - - var snapshot = _textBuffer.CurrentSnapshot; - var start = Math.Max(0, Math.Min(_range.Start, snapshot.Length - 1)); - var end = Math.Max(0, Math.Min(_range.End, snapshot.Length)); - - return new SnapshotSpan(snapshot, start, end - start); - } - } - #endregion - - static string GetErrorType(IValidationError error) - { - string errorType = PredefinedErrorTypeNames.SyntaxError; - - switch (error.Severity) - { - case ErrorSeverity.Fatal: - case ErrorSeverity.Error: - errorType = PredefinedErrorTypeNames.SyntaxError; - break; - case ErrorSeverity.Warning: - errorType = PredefinedErrorTypeNames.Warning; - break; - - case ErrorSeverity.Informational: - errorType = PredefinedErrorTypeNames.OtherError; - break; - } - - return errorType; - } - - static TaskType GetTaskType(IValidationError error) - { - switch (error.Severity) - { - case ErrorSeverity.Fatal: - case ErrorSeverity.Error: - return TaskType.Error; - - case ErrorSeverity.Warning: - return TaskType.Warning; - - case ErrorSeverity.Informational: - return TaskType.Informational; - } - - return TaskType.Error; - } - - #region ITextRange Members - public int Start - { - get { return _range.Start; } - } - - public int End - { - get { return _range.End; } - } - - public int Length - { - get { return _range.Length; } - } - - public bool Contains(int position) - { - return _range.Contains(position); - } - - public void Shift(int offset) - { - _range.Shift(offset); - } - - #endregion - - #region IExpandableTextRange - public void Expand(int startOffset, int endOffset) - { - var expandable = _range as IExpandableTextRange; - - if (expandable != null) - expandable.Expand(startOffset, endOffset); - } - - public bool AllowZeroLength - { - get - { - return false; - } - } - - public bool IsStartInclusive - { - get - { - return true; - } - } - - public bool IsEndInclusive - { - get - { - return false; - } - } - - public bool ContainsUsingInclusion(int position) - { - return Contains(position); - } - - public bool IsWellFormed - { - get { return true; } - } - #endregion - - #region IEditorTaskListItem - public string Description { get; private set; } - public TaskType TaskType { get; private set; } - - public int Line - { - get - { - if (Span.Start < Span.Snapshot.Length) - { - // Add 1 for WebMatrix compatability, - // remember to subtract 1 in VS-specific code - return Span.Snapshot.GetLineNumberFromPosition(Span.Start) + 1; - } - - return 0; - } - } - - public int Column - { - get - { - if (Span.Start < Span.Snapshot.Length) - { - var line = Span.Snapshot.GetLineFromPosition(Span.Start); - // Add 1 for WebMatrix compatability, - // remember to subtract 1 in VS-specific code - return Span.Start.Position - line.Start + 1; - } - - return 0; - } - } - - public string FileName - { - get - { - var document = ServiceManager.GetService(_textBuffer); - if (document != null && document.WorkspaceItem != null) - { - return document.WorkspaceItem.Path; - } - - return String.Empty; - } - } - - public string HelpKeyword - { - get - { - return "vs.r.validationerror"; - } - } - #endregion - } -} +using System; +using Microsoft.Languages.Core.Text; +using Microsoft.Languages.Editor.Services; +using Microsoft.Languages.Editor.TaskList.Definitions; +using Microsoft.R.Core.Parser; +using Microsoft.R.Editor.Document; +using Microsoft.R.Editor.Tree.Definitions; +using Microsoft.R.Editor.Validation.Definitions; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Adornments; +using Microsoft.VisualStudio.Text.Tagging; + +namespace Microsoft.R.Editor.Validation.Tagger { + /// + /// This represents an underlined syntax error in the editor + /// + internal class EditorErrorTag : ErrorTag, ITagSpan, IExpandableTextRange, IEditorTaskListItem + { + private ITextBuffer _textBuffer; + private ITextRange _range; + + public EditorErrorTag(IEditorTree editorTree, IValidationError error) + : base(GetErrorType(error), error.Message) + { + _textBuffer = editorTree.TextBuffer; + + Description = error.Message; + TaskType = GetTaskType(error); + + _range = error; + + if (_range == null || _range.Start < 0) + _range = TextRange.EmptyRange; + } + + #region ITagSpan Members + public IErrorTag Tag + { + get { return this; } + } + + public SnapshotSpan Span + { + get + { + // Positions may be out of date: editor is asking about current snapshot + // while tree can still be holding on the earlier one since tree snapshot + // is updated when background parsing completes. However, tag positions + // are constantly updated when text buffer changes and hence they match + // current text buffer snapshot. + + var snapshot = _textBuffer.CurrentSnapshot; + var start = Math.Max(0, Math.Min(_range.Start, snapshot.Length - 1)); + var end = Math.Max(0, Math.Min(_range.End, snapshot.Length)); + + return new SnapshotSpan(snapshot, start, end - start); + } + } + #endregion + + static string GetErrorType(IValidationError error) + { + string errorType = PredefinedErrorTypeNames.SyntaxError; + + switch (error.Severity) + { + case ErrorSeverity.Fatal: + case ErrorSeverity.Error: + errorType = PredefinedErrorTypeNames.SyntaxError; + break; + case ErrorSeverity.Warning: + errorType = PredefinedErrorTypeNames.Warning; + break; + + case ErrorSeverity.Informational: + errorType = PredefinedErrorTypeNames.OtherError; + break; + } + + return errorType; + } + + static TaskType GetTaskType(IValidationError error) + { + switch (error.Severity) + { + case ErrorSeverity.Fatal: + case ErrorSeverity.Error: + return TaskType.Error; + + case ErrorSeverity.Warning: + return TaskType.Warning; + + case ErrorSeverity.Informational: + return TaskType.Informational; + } + + return TaskType.Error; + } + + #region ITextRange Members + public int Start + { + get { return _range.Start; } + } + + public int End + { + get { return _range.End; } + } + + public int Length + { + get { return _range.Length; } + } + + public bool Contains(int position) + { + return _range.Contains(position); + } + + public void Shift(int offset) + { + _range.Shift(offset); + } + + #endregion + + #region IExpandableTextRange + public void Expand(int startOffset, int endOffset) + { + var expandable = _range as IExpandableTextRange; + + if (expandable != null) + expandable.Expand(startOffset, endOffset); + } + + public bool AllowZeroLength + { + get + { + return false; + } + } + + public bool IsStartInclusive + { + get + { + return true; + } + } + + public bool IsEndInclusive + { + get + { + return false; + } + } + + public bool ContainsUsingInclusion(int position) + { + return Contains(position); + } + + public bool IsWellFormed + { + get { return true; } + } + #endregion + + #region IEditorTaskListItem + public string Description { get; private set; } + public TaskType TaskType { get; private set; } + + public int Line + { + get + { + if (Span.Start < Span.Snapshot.Length) + { + // Add 1 for WebMatrix compatability, + // remember to subtract 1 in VS-specific code + return Span.Snapshot.GetLineNumberFromPosition(Span.Start) + 1; + } + + return 0; + } + } + + public int Column + { + get + { + if (Span.Start < Span.Snapshot.Length) + { + var line = Span.Snapshot.GetLineFromPosition(Span.Start); + // Add 1 for WebMatrix compatability, + // remember to subtract 1 in VS-specific code + return Span.Start.Position - line.Start + 1; + } + + return 0; + } + } + + public string FileName + { + get + { + var document = ServiceManager.GetService(_textBuffer); + if (document != null && document.WorkspaceItem != null) + { + return document.WorkspaceItem.Path; + } + + return String.Empty; + } + } + + public string HelpKeyword + { + get + { + return "vs.r.validationerror"; + } + } + #endregion + } +} diff --git a/src/R/Editor/Impl/Validation/Tagger/EditorErrorTagger.cs b/src/R/Editor/Impl/Validation/Tagger/EditorErrorTagger.cs index ae0d77649..a93c5a23b 100644 --- a/src/R/Editor/Impl/Validation/Tagger/EditorErrorTagger.cs +++ b/src/R/Editor/Impl/Validation/Tagger/EditorErrorTagger.cs @@ -1,364 +1,316 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.ComponentModel.Composition; -using System.Diagnostics; -using Microsoft.Languages.Core.Text; -using Microsoft.Languages.Editor.Services; -using Microsoft.Languages.Editor.Shell; -using Microsoft.Languages.Editor.TaskList.Definitions; -using Microsoft.Languages.Editor.Text; -using Microsoft.R.Core.AST.Definitions; -using Microsoft.R.Editor.Document; -using Microsoft.R.Editor.Document.Definitions; -using Microsoft.R.Editor.Settings; -using Microsoft.R.Editor.Tree; -using Microsoft.R.Editor.Validation.Definitions; -using Microsoft.VisualStudio.Text; -using Microsoft.VisualStudio.Text.Tagging; - -namespace Microsoft.R.Editor.Validation.Tagger -{ - /// - /// This gives the editor a list of ranges that need to be underlined with red squiggles. - /// As the stylesheet changes, this class keeps the list of syntax errors up to date. - /// - public class EditorErrorTagger : ITagger, IEditorTaskListItemSource - { - [Import(AllowDefault = true)] - internal IEditorTaskList TaskList { get; set; } - - /// - /// Tells the editor (or any listener) that syntax errors have changed - /// - public event EventHandler TagsChanged; - - /// - /// Reference to the validation results queue - /// - internal ConcurrentQueue ResultsQueue; - - ITextBuffer _textBuffer; - IREditorDocument _document; - ErrorTagCollection _errorTags; - private bool _fireCodeMarkerUponCompletion; - - public EditorErrorTagger(ITextBuffer textBuffer) - { - EditorShell.Current.CompositionService.SatisfyImportsOnce(this); - - _document = REditorDocument.FromTextBuffer(textBuffer); - _document.DocumentClosing += OnDocumentClosing; - _document.EditorTree.UpdateCompleted += OnTreeUpdateCompleted; - _document.EditorTree.NodesRemoved += OnNodesRemoved; - _errorTags = new ErrorTagCollection(_document.EditorTree); - - _textBuffer = _document.EditorTree.TextBuffer; - _textBuffer.Changed += OnTextBufferChanged; - - _fireCodeMarkerUponCompletion = true; - - // Don't push syntax errors to the Error List in transient - // documents such as in document attached to a projected buffer - // in the R interactive window - if (TaskList != null && _document.WorkspaceItem != null && _document.WorkspaceItem.Path.Length > 0) - { - TaskList.AddTaskSource(this); - } - - TreeValidator validator = TreeValidator.EnsureFromTextBuffer(_textBuffer, _document.EditorTree); - - validator.Cleared += OnCleared; - ResultsQueue = validator.ValidationResults; - EditorShell.Current.Idle += OnIdle; - - ServiceManager.AddService(this, textBuffer); - } - - /// - /// Retriever error (squiggly) tagger associated with the text buffer - /// - /// Text buffer - public static EditorErrorTagger FromTextBuffer(ITextBuffer textBuffer) - { - return ServiceManager.GetService(textBuffer); - } - - private void OnNodesRemoved(object sender, TreeNodesRemovedEventArgs e) - { - foreach(IAstNode node in e.Nodes) - { - _errorTags.RemoveTagsForNode(node); - } - } - - private void OnTreeUpdateCompleted(object sender, TreeUpdatedEventArgs e) - { - if (e.UpdateType != TreeUpdateType.PositionsOnly) - { - RemoveAllTags(); - } - } - - private void OnCleared(object sender, EventArgs e) - { - RemoveAllTags(); - } - - private void RemoveAllTags() - { - _errorTags.Clear(); - - if (TasksCleared != null) - TasksCleared(this, EventArgs.Empty); - - if (TagsChanged != null) - { - TagsChanged(this, new SnapshotSpanEventArgs( - new SnapshotSpan(_textBuffer.CurrentSnapshot, 0, _textBuffer.CurrentSnapshot.Length))); - } - } - - private void OnTextBufferChanged(object sender, TextContentChangedEventArgs e) - { - if (REditorSettings.SyntaxCheck && e.Changes.Count > 0) - { - var changes = TextUtility.ConvertToRelative(e); - foreach (var change in changes) - { - _errorTags.ReflectTextChange(change.Start, change.OldLength, change.NewLength); - } - - if ((_errorTags.RemovedTags.Count > 0) && (TagsChanged != null)) - { - int start = Int32.MaxValue; - int end = Int32.MinValue; - - foreach(var errorTag in _errorTags.RemovedTags) - { - start = Math.Min(start, errorTag.Start); - end = Math.Max(end, errorTag.End); - } - - // RemovedTags haven't had their positions updated, verify their - // values won't break the SnapshotSpan creation - ITextSnapshot snapshot = _textBuffer.CurrentSnapshot; - start = Math.Min(start, snapshot.Length); - end = Math.Min(end, snapshot.Length); - - TagsChanged(this, new SnapshotSpanEventArgs( - new SnapshotSpan(snapshot, start, end - start))); - } - - if (TasksUpdated != null) - TasksUpdated(this, EventArgs.Empty); - } - } - - #region Tree event handlers - private void OnDocumentClosing(object sender, EventArgs e) - { - if (_textBuffer != null) - { - EditorShell.Current.Idle -= OnIdle; - - _document.EditorTree.UpdateCompleted -= OnTreeUpdateCompleted; - _document.EditorTree.NodesRemoved -= OnNodesRemoved; - - _document.DocumentClosing -= OnDocumentClosing; - _document = null; - - _errorTags.Clear(); - _errorTags = null; - - ServiceManager.RemoveService(_textBuffer); - ResultsQueue = null; - - _textBuffer.Changed -= OnTextBufferChanged; - _textBuffer = null; - - if (TaskList != null) - TaskList.RemoveTaskSource(this); - } - } - #endregion - - /// - /// Idle time event handler. Retrieves results from the validation task queue, - /// creates new error tags (squiggles) and fires an event telling editor that - /// tags changed. The code must operate on UI thread and hence it is called on idle. - /// - /// - /// - private void OnIdle(object sender, EventArgs eventArgs) - { - if (REditorSettings.SyntaxCheck && _textBuffer != null) - { - if (ResultsQueue.Count > 0) - { - _fireCodeMarkerUponCompletion = true; - - List addedTags = new List(); - - ITextRange changedRange = _errorTags.BeginUpdate(); - changedRange = TextRange.Intersection(changedRange, 0, _textBuffer.CurrentSnapshot.Length); - - var timer = Stopwatch.StartNew(); - timer.Reset(); - - while (timer.ElapsedMilliseconds < 100) - { - IValidationError error; - if (!ResultsQueue.TryDequeue(out error)) - { - break; - } - - if (String.IsNullOrEmpty(error.Message)) - { - // Empty message means remove all error for the element. - ITextRange removedRange = TextRange.EmptyRange; // _errorTags.RemoveTagsForNode(error.NodeKey); - - // Only update changedRange if there were errors removed - if (removedRange.End > 0) - { - if (changedRange.End == 0) - { - changedRange = removedRange; - } - else - { - changedRange = TextRange.Union(changedRange, removedRange); - } - } - } - else - { - EditorErrorTag tag = new EditorErrorTag(_document.EditorTree, error); - if (tag.Length > 0) - { - if (changedRange.End == 0) - { - changedRange = tag; - } - else - { - changedRange = TextRange.Union(changedRange, tag); - } - - _errorTags.Add(tag); - addedTags.Add(tag); - } - } - } - - _errorTags.EndUpdate(changedRange.Length > 0); - - // Clip range to the current snapshop - int start = Math.Max(changedRange.Start, 0); - start = Math.Min(start, _textBuffer.CurrentSnapshot.Length); - int end = Math.Min(changedRange.End, _textBuffer.CurrentSnapshot.Length); - - if (changedRange.Length > 0 && TagsChanged != null) - TagsChanged(this, new SnapshotSpanEventArgs(new SnapshotSpan(_textBuffer.CurrentSnapshot, start, end - start))); - - if (BeginUpdatingTasks != null) - BeginUpdatingTasks(this, EventArgs.Empty); - - try - { - if (addedTags.Count > 0 && TasksAdded != null) - TasksAdded(this, new TasksListItemsChangedEventArgs(addedTags)); - - if (_errorTags.RemovedTags.Count > 0) - { - List removedTags = new List(); - while (_errorTags.RemovedTags.Count > 0) - { - EditorErrorTag tag; - - if (_errorTags.RemovedTags.TryDequeue(out tag)) - { - removedTags.Add(tag); - } - } - - if (TasksRemoved != null && removedTags.Count > 0) - TasksRemoved(this, new TasksListItemsChangedEventArgs(removedTags)); - } - } - finally - { - if (EndUpdatingTasks != null) - EndUpdatingTasks(this, EventArgs.Empty); - } - - timer.Stop(); - } - - if (_fireCodeMarkerUponCompletion && (ResultsQueue.Count == 0)) - { - // Use this flag so we don't incessantly fire this code marker on every idle. - // TODO: Even this isn't quite correct, as it's possible that a validator - // yet pushed all it's entries into the results queue. There should really - // be a notification from the validators to indicate their completeness. If there - // were such a notification, then we could actually even unhook ourselves from idle. - _fireCodeMarkerUponCompletion = false; - } - } - } - - /// - /// Provides the list of squiggles to the editor - /// - public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans) - { - List> tags = new List>(); - - if (REditorSettings.SyntaxCheck && _errorTags != null) - { - foreach (var span in spans) - { - // Force the input span to cover at least one character - // (this helps make tooltips work where the input span is empty) - int spanAfterEnd = (span.Length == 0) ? span.Start.Position + 1 : span.End.Position; - tags.AddRange(_errorTags.ItemsInRange(TextRange.FromBounds(span.Start, spanAfterEnd))); - } - } - - return tags; - } - - #region IEditorTaskListItemSource - - public event EventHandler TasksAdded; - public event EventHandler TasksRemoved; - public event EventHandler BeginUpdatingTasks; - public event EventHandler EndUpdatingTasks; -#pragma warning disable 67 - public event EventHandler TasksCleared; -#pragma warning restore 67 - public event EventHandler TasksUpdated; - - public IReadOnlyCollection Tasks - { - get - { - var array = _errorTags.ToArray(); - var tasks = new List(); - - tasks.AddRange(array); - return tasks; - } - } - - public ITextBuffer TextBuffer - { - get - { - return _textBuffer; - } - } - #endregion - } -} +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.ComponentModel.Composition; +using System.Diagnostics; +using Microsoft.Languages.Core.Text; +using Microsoft.Languages.Editor.Services; +using Microsoft.Languages.Editor.Shell; +using Microsoft.Languages.Editor.TaskList.Definitions; +using Microsoft.Languages.Editor.Text; +using Microsoft.R.Core.AST.Definitions; +using Microsoft.R.Editor.Document; +using Microsoft.R.Editor.Document.Definitions; +using Microsoft.R.Editor.Settings; +using Microsoft.R.Editor.Tree; +using Microsoft.R.Editor.Validation.Definitions; +using Microsoft.VisualStudio.Text; +using Microsoft.VisualStudio.Text.Tagging; + +namespace Microsoft.R.Editor.Validation.Tagger { + /// + /// This gives the editor a list of ranges that need to be underlined with red squiggles. + /// As the stylesheet changes, this class keeps the list of syntax errors up to date. + /// + public class EditorErrorTagger : ITagger, IEditorTaskListItemSource { + [Import(AllowDefault = true)] + internal IEditorTaskList TaskList { get; set; } + + /// + /// Tells the editor (or any listener) that syntax errors have changed + /// + public event EventHandler TagsChanged; + + /// + /// Reference to the validation results queue + /// + internal ConcurrentQueue ResultsQueue; + + ITextBuffer _textBuffer; + IREditorDocument _document; + ErrorTagCollection _errorTags; + private bool _fireCodeMarkerUponCompletion; + + public EditorErrorTagger(ITextBuffer textBuffer) { + EditorShell.Current.CompositionService.SatisfyImportsOnce(this); + + _document = REditorDocument.FromTextBuffer(textBuffer); + _document.DocumentClosing += OnDocumentClosing; + _document.EditorTree.UpdateCompleted += OnTreeUpdateCompleted; + _document.EditorTree.NodesRemoved += OnNodesRemoved; + _errorTags = new ErrorTagCollection(_document.EditorTree); + + _textBuffer = _document.EditorTree.TextBuffer; + _textBuffer.Changed += OnTextBufferChanged; + + _fireCodeMarkerUponCompletion = true; + + // Don't push syntax errors to the Error List in transient + // documents such as in document attached to a projected buffer + // in the R interactive window + if (TaskList != null && _document.WorkspaceItem != null && _document.WorkspaceItem.Path.Length > 0) { + TaskList.AddTaskSource(this); + } + + TreeValidator validator = TreeValidator.EnsureFromTextBuffer(_textBuffer, _document.EditorTree); + + validator.Cleared += OnCleared; + ResultsQueue = validator.ValidationResults; + EditorShell.Current.Idle += OnIdle; + + ServiceManager.AddService(this, textBuffer); + } + + /// + /// Retriever error (squiggly) tagger associated with the text buffer + /// + /// Text buffer + public static EditorErrorTagger FromTextBuffer(ITextBuffer textBuffer) { + return ServiceManager.GetService(textBuffer); + } + + private void OnNodesRemoved(object sender, TreeNodesRemovedEventArgs e) { + foreach (IAstNode node in e.Nodes) { + _errorTags.RemoveTagsForNode(node); + } + } + + private void OnTreeUpdateCompleted(object sender, TreeUpdatedEventArgs e) { + if (e.UpdateType != TreeUpdateType.PositionsOnly) { + RemoveAllTags(); + } + } + + private void OnCleared(object sender, EventArgs e) { + RemoveAllTags(); + } + + private void RemoveAllTags() { + _errorTags.Clear(); + + if (TasksCleared != null) + TasksCleared(this, EventArgs.Empty); + + if (TagsChanged != null) { + TagsChanged(this, new SnapshotSpanEventArgs( + new SnapshotSpan(_textBuffer.CurrentSnapshot, 0, _textBuffer.CurrentSnapshot.Length))); + } + } + + private void OnTextBufferChanged(object sender, TextContentChangedEventArgs e) { + if (REditorSettings.SyntaxCheck && e.Changes.Count > 0) { + var changes = TextUtility.ConvertToRelative(e); + foreach (var change in changes) { + _errorTags.ReflectTextChange(change.Start, change.OldLength, change.NewLength, + trivialChange: !_document.EditorTree.IsReady); + } + + if ((_errorTags.RemovedTags.Count > 0) && (TagsChanged != null)) { + int start = Int32.MaxValue; + int end = Int32.MinValue; + + foreach (var errorTag in _errorTags.RemovedTags) { + start = Math.Min(start, errorTag.Start); + end = Math.Max(end, errorTag.End); + } + + // RemovedTags haven't had their positions updated, verify their + // values won't break the SnapshotSpan creation + ITextSnapshot snapshot = _textBuffer.CurrentSnapshot; + start = Math.Min(start, snapshot.Length); + end = Math.Min(end, snapshot.Length); + + TagsChanged(this, new SnapshotSpanEventArgs( + new SnapshotSpan(snapshot, start, end - start))); + } + + if (TasksUpdated != null) + TasksUpdated(this, EventArgs.Empty); + } + } + + #region Tree event handlers + private void OnDocumentClosing(object sender, EventArgs e) { + if (_textBuffer != null) { + EditorShell.Current.Idle -= OnIdle; + + _document.EditorTree.UpdateCompleted -= OnTreeUpdateCompleted; + _document.EditorTree.NodesRemoved -= OnNodesRemoved; + + _document.DocumentClosing -= OnDocumentClosing; + _document = null; + + _errorTags.Clear(); + _errorTags = null; + + ServiceManager.RemoveService(_textBuffer); + ResultsQueue = null; + + _textBuffer.Changed -= OnTextBufferChanged; + _textBuffer = null; + + if (TaskList != null) + TaskList.RemoveTaskSource(this); + } + } + #endregion + + /// + /// Idle time event handler. Retrieves results from the validation task queue, + /// creates new error tags (squiggles) and fires an event telling editor that + /// tags changed. The code must operate on UI thread and hence it is called on idle. + /// + /// + /// + private void OnIdle(object sender, EventArgs eventArgs) { + if (REditorSettings.SyntaxCheck && _textBuffer != null) { + if (ResultsQueue.Count > 0) { + _fireCodeMarkerUponCompletion = true; + + List addedTags = new List(); + + ITextRange changedRange = _errorTags.BeginUpdate(); + changedRange = TextRange.Intersection(changedRange, 0, _textBuffer.CurrentSnapshot.Length); + + var timer = Stopwatch.StartNew(); + timer.Reset(); + + while (timer.ElapsedMilliseconds < 100) { + IValidationError error; + if (!ResultsQueue.TryDequeue(out error)) { + break; + } + + if (String.IsNullOrEmpty(error.Message)) { + // Empty message means remove all error for the element. + ITextRange removedRange = TextRange.EmptyRange; // _errorTags.RemoveTagsForNode(error.NodeKey); + + // Only update changedRange if there were errors removed + if (removedRange.End > 0) { + if (changedRange.End == 0) { + changedRange = removedRange; + } else { + changedRange = TextRange.Union(changedRange, removedRange); + } + } + } else { + EditorErrorTag tag = new EditorErrorTag(_document.EditorTree, error); + if (tag.Length > 0) { + if (changedRange.End == 0) { + changedRange = tag; + } else { + changedRange = TextRange.Union(changedRange, tag); + } + + _errorTags.Add(tag); + addedTags.Add(tag); + } + } + } + + _errorTags.EndUpdate(changedRange.Length > 0); + + // Clip range to the current snapshop + int start = Math.Max(changedRange.Start, 0); + start = Math.Min(start, _textBuffer.CurrentSnapshot.Length); + int end = Math.Min(changedRange.End, _textBuffer.CurrentSnapshot.Length); + + if (changedRange.Length > 0 && TagsChanged != null) + TagsChanged(this, new SnapshotSpanEventArgs(new SnapshotSpan(_textBuffer.CurrentSnapshot, start, end - start))); + + if (BeginUpdatingTasks != null) + BeginUpdatingTasks(this, EventArgs.Empty); + + try { + if (addedTags.Count > 0 && TasksAdded != null) + TasksAdded(this, new TasksListItemsChangedEventArgs(addedTags)); + + if (_errorTags.RemovedTags.Count > 0) { + List removedTags = new List(); + while (_errorTags.RemovedTags.Count > 0) { + EditorErrorTag tag; + + if (_errorTags.RemovedTags.TryDequeue(out tag)) { + removedTags.Add(tag); + } + } + + if (TasksRemoved != null && removedTags.Count > 0) + TasksRemoved(this, new TasksListItemsChangedEventArgs(removedTags)); + } + } finally { + if (EndUpdatingTasks != null) + EndUpdatingTasks(this, EventArgs.Empty); + } + + timer.Stop(); + } + + if (_fireCodeMarkerUponCompletion && (ResultsQueue.Count == 0)) { + // Use this flag so we don't incessantly fire this code marker on every idle. + // TODO: Even this isn't quite correct, as it's possible that a validator + // yet pushed all it's entries into the results queue. There should really + // be a notification from the validators to indicate their completeness. If there + // were such a notification, then we could actually even unhook ourselves from idle. + _fireCodeMarkerUponCompletion = false; + } + } + } + + /// + /// Provides the list of squiggles to the editor + /// + public IEnumerable> GetTags(NormalizedSnapshotSpanCollection spans) { + List> tags = new List>(); + + if (REditorSettings.SyntaxCheck && _errorTags != null) { + foreach (var span in spans) { + // Force the input span to cover at least one character + // (this helps make tooltips work where the input span is empty) + int spanAfterEnd = (span.Length == 0) ? span.Start.Position + 1 : span.End.Position; + tags.AddRange(_errorTags.ItemsInRange(TextRange.FromBounds(span.Start, spanAfterEnd))); + } + } + + return tags; + } + + #region IEditorTaskListItemSource + + public event EventHandler TasksAdded; + public event EventHandler TasksRemoved; + public event EventHandler BeginUpdatingTasks; + public event EventHandler EndUpdatingTasks; +#pragma warning disable 67 + public event EventHandler TasksCleared; +#pragma warning restore 67 + public event EventHandler TasksUpdated; + + public IReadOnlyCollection Tasks { + get { + var array = _errorTags.ToArray(); + var tasks = new List(); + + tasks.AddRange(array); + return tasks; + } + } + + public ITextBuffer TextBuffer { + get { + return _textBuffer; + } + } + #endregion + } +} diff --git a/src/R/Editor/Impl/Validation/Tagger/ErrorTagCollection.cs b/src/R/Editor/Impl/Validation/Tagger/ErrorTagCollection.cs index caabc0751..0bd061360 100644 --- a/src/R/Editor/Impl/Validation/Tagger/ErrorTagCollection.cs +++ b/src/R/Editor/Impl/Validation/Tagger/ErrorTagCollection.cs @@ -8,16 +8,14 @@ using Microsoft.R.Editor.Tree; using Microsoft.R.Editor.Tree.Definitions; -namespace Microsoft.R.Editor.Validation.Tagger -{ +namespace Microsoft.R.Editor.Validation.Tagger { /// /// Represents collection of HTML validation errors and tasks. Tag collection /// is a primary source for validation squiggles in the editor as well as /// source of the corresponsing messages for the application task list. /// Collection is thread safe. /// - internal sealed class ErrorTagCollection - { + internal sealed class ErrorTagCollection { /// /// Queue of tags removed in async operations. Typically used /// by task list item sources to update the task list. @@ -59,8 +57,7 @@ internal sealed class ErrorTagCollection /// private long _taskRunning = 0; - public ErrorTagCollection(IEditorTree editorTree) - { + public ErrorTagCollection(IEditorTree editorTree) { RemovedTags = new ConcurrentQueue(); _editorTree = editorTree; @@ -70,12 +67,10 @@ public ErrorTagCollection(IEditorTree editorTree) _editorTree.UpdateCompleted += OnUpdateCompleted; } - public EditorErrorTag[] ToArray() - { + public EditorErrorTag[] ToArray() { EditorErrorTag[] array; - lock (_lockObj) - { + lock (_lockObj) { array = _tags.ToArray(); } @@ -83,24 +78,19 @@ public EditorErrorTag[] ToArray() } - public IReadOnlyList ItemsInRange(ITextRange range) - { + public IReadOnlyList ItemsInRange(ITextRange range) { IReadOnlyList list; - lock (_lockObj) - { + lock (_lockObj) { list = _tags.ItemsInRange(range); } return list; } - public void Clear() - { - lock (_lockObj) - { - foreach (var tag in _tags) - { + public void Clear() { + lock (_lockObj) { + foreach (var tag in _tags) { RemovedTags.Enqueue(tag); } @@ -108,31 +98,30 @@ public void Clear() } } - public void ReflectTextChange(int start, int oldLength, int newLength) - { - lock (_lockObj) - { + public void ReflectTextChange(int start, int oldLength, int newLength, bool trivialChange) { + lock (_lockObj) { // Remove items first. This is different from default ReflectTextChange // implementation since ReflectTextChange does not remove item // that fully contains the change and rather expands it. We don't - // want this behavior since changed element or attribute needs to be - // revalidated and hence expanding squiggle does not make sense. - - var removed = _tags.RemoveInRange(new TextRange(start, oldLength), true); + // want this behavior since changed elementneeds to be revalidated + // and hence expanding squiggle does not make sense. + ICollection removed = null; + if (!trivialChange) { + removed = _tags.RemoveInRange(new TextRange(start, oldLength), true); + } _tags.ReflectTextChange(start, oldLength, newLength); - foreach (var tag in removed) - { - RemovedTags.Enqueue(tag); + if (!trivialChange) { + foreach (var tag in removed) { + RemovedTags.Enqueue(tag); + } } } } - internal void Add(EditorErrorTag tag) - { - lock (_lockObj) - { + internal void Add(EditorErrorTag tag) { + lock (_lockObj) { _tags.Add(tag); } } @@ -143,18 +132,14 @@ internal void Add(EditorErrorTag tag) /// calculated span of changes in the text buffer. /// /// - public ITextRange BeginUpdate() - { + public ITextRange BeginUpdate() { ITextRange range = TextRange.EmptyRange; - if (_editorTree != null) - { + if (_editorTree != null) { SpinWait.SpinUntil(() => Interlocked.Read(ref _taskRunning) == 0); - lock (_lockObj) - { - if (_removedRange != null) - { + lock (_lockObj) { + if (_removedRange != null) { // Removed range may be out of current snapshot boundaries if, say, // a lot of elements were removed. Clip it appropriately. @@ -172,12 +157,9 @@ public ITextRange BeginUpdate() /// Signals that collection update has been completed. /// /// True if collection was indeed modified since BeginUpdate was called - public void EndUpdate(bool modified) - { - if (modified) - { - lock (_lockObj) - { + public void EndUpdate(bool modified) { + if (modified) { + lock (_lockObj) { _tags.Sort(); } } @@ -188,19 +170,15 @@ public void EndUpdate(bool modified) /// /// Node in the AST /// Text range that encloses removed tag spans - public ITextRange RemoveTagsForNode(IAstNode node) - { + public ITextRange RemoveTagsForNode(IAstNode node) { int start = _editorTree.TextBuffer.CurrentSnapshot.Length; ITextRange range = TextRange.EmptyRange; int end = 0; - lock (_lockObj) - { + lock (_lockObj) { // Remove all tags for this node - for (int i = 0; i < _tags.Count; i++) - { - if (TextRange.ContainsInclusiveEnd(node, _tags[i])) - { + for (int i = 0; i < _tags.Count; i++) { + if (TextRange.ContainsInclusiveEnd(node, _tags[i])) { start = Math.Min(start, _tags[i].Start); end = Math.Max(end, _tags[i].End); @@ -217,10 +195,8 @@ public ITextRange RemoveTagsForNode(IAstNode node) return range; } - private void OnTreeClosing(object sender, EventArgs e) - { - if (_editorTree != null) - { + private void OnTreeClosing(object sender, EventArgs e) { + if (_editorTree != null) { _editorTree.Closing -= OnTreeClosing; _editorTree.NodesRemoved -= OnNodesRemoved; _editorTree.UpdateCompleted -= OnUpdateCompleted; @@ -229,59 +205,44 @@ private void OnTreeClosing(object sender, EventArgs e) } } - private void OnNewTree(object sender, EventArgs e) - { + private void OnNewTree(object sender, EventArgs e) { Clear(); } - private void OnNodesRemoved(object sender, TreeNodesRemovedEventArgs e) - { - foreach (var node in e.Nodes) - { + private void OnNodesRemoved(object sender, TreeNodesRemovedEventArgs e) { + foreach (var node in e.Nodes) { StoreRemovedNodes(node); } } - private void OnUpdateCompleted(object sender, TreeUpdatedEventArgs e) - { - if (_nodesPendingRemoval.Count > 0) - { - if (Interlocked.CompareExchange(ref _taskRunning, 1, 0) == 0) - { + private void OnUpdateCompleted(object sender, TreeUpdatedEventArgs e) { + if (_nodesPendingRemoval.Count > 0) { + if (Interlocked.CompareExchange(ref _taskRunning, 1, 0) == 0) { Task.Run(() => ProcessPendingNodeRemoval()); } } } - private void StoreRemovedNodes(IAstNode node) - { - foreach (var child in node.Children) - { + private void StoreRemovedNodes(IAstNode node) { + foreach (var child in node.Children) { StoreRemovedNodes(child); } _nodesPendingRemoval.Enqueue(node); } - private void ProcessPendingNodeRemoval() - { + private void ProcessPendingNodeRemoval() { int start = Int32.MaxValue; int end = 0; - try - { - if (_nodesPendingRemoval.Count > 0) - { - lock (_lockObj) - { + try { + if (_nodesPendingRemoval.Count > 0) { + lock (_lockObj) { IAstNode node; - while (_nodesPendingRemoval.TryDequeue(out node)) - { - for (int j = 0; j < _tags.Count; j++) - { - if (TextRange.ContainsInclusiveEnd(node, _tags[j]) || node.Parent == null) - { + while (_nodesPendingRemoval.TryDequeue(out node)) { + for (int j = 0; j < _tags.Count; j++) { + if (TextRange.ContainsInclusiveEnd(node, _tags[j]) || node.Parent == null) { start = Math.Min(start, _tags[j].Start); end = Math.Max(end, _tags[j].End); @@ -293,8 +254,7 @@ private void ProcessPendingNodeRemoval() } } - if (start != Int32.MaxValue) - { + if (start != Int32.MaxValue) { if (_removedRange != null) _removedRange = TextRange.Union(_removedRange, start, end - start); else @@ -302,9 +262,7 @@ private void ProcessPendingNodeRemoval() } } } - } - finally - { + } finally { Interlocked.Exchange(ref _taskRunning, 0); } } diff --git a/src/R/Editor/Impl/Validation/TreeValidator.cs b/src/R/Editor/Impl/Validation/TreeValidator.cs index f9e6d6a41..2d5c327eb 100644 --- a/src/R/Editor/Impl/Validation/TreeValidator.cs +++ b/src/R/Editor/Impl/Validation/TreeValidator.cs @@ -1,285 +1,285 @@ -using System; -using System.Collections.Concurrent; -using System.Diagnostics; -using Microsoft.Languages.Core.Text; -using Microsoft.Languages.Core.Utility; -using Microsoft.Languages.Editor.Services; -using Microsoft.Languages.Editor.Shell; -using Microsoft.R.Core.AST.Definitions; -using Microsoft.R.Core.Parser; -using Microsoft.R.Core.Tokens; -using Microsoft.R.Editor.Document; -using Microsoft.R.Editor.Document.Definitions; -using Microsoft.R.Editor.Settings; -using Microsoft.R.Editor.Tree; -using Microsoft.R.Editor.Tree.Definitions; -using Microsoft.R.Editor.Validation.Definitions; -using Microsoft.R.Editor.Validation.Errors; -using Microsoft.VisualStudio.Text; - -namespace Microsoft.R.Editor.Validation -{ - /// - /// Main R validator: performs syntax check, produces results that - /// then sent to the task list and tagger that creates squiggles. - /// Works asynchronously except the final part that pushes actual - /// task items to the IDE. Additional validators can be added - /// via MEF exports. - /// - /// Validator listens to tree updated events and schedules validation - /// passes for next idle slot. Next idle validation thread starts - /// and performas its work. Validator also listens to changes in - /// settings which may turn validation on or off. - /// - public sealed class TreeValidator - { - private static BooleanSwitch _traceValidation = - new BooleanSwitch("traceRValidation", "Trace R validation events in debug window."); - public BooleanSwitch TraceValidation - { - get { return _traceValidation; } - } - - private static int _validationDelay = 200; - - /// - /// Queue of validation results. Typically accessed from the main - /// thread that pushes errors/warning into a task list window. - /// Code that places items on the task list should be checking if - /// node that produced the error is still exist in the document. - /// - internal ConcurrentQueue ValidationResults { get; private set; } - - private IEditorTree _editorTree; - private bool _syntaxCheckEnabled = false; - private bool _validationStarted = false; - - private bool _advisedToIdleTime = false; - private DateTime _idleRequestTime = DateTime.UtcNow; - - /// - /// Fires when validator is cleared. Typically when validation was switched off - /// so task list and all error tags must be removed from the editor. - /// - public event EventHandler Cleared; - - #region Constructors - public TreeValidator(IEditorTree editorTree) - { -#if DEBUG - TraceValidation.Enabled = false; -#endif - - _editorTree = editorTree; - - _editorTree.NodesRemoved += OnNodesRemoved; - _editorTree.UpdateCompleted += OnTreeUpdateCompleted; - _editorTree.Closing += OnTreeClose; - - _syntaxCheckEnabled = IsSyntaxCheckEnabled(_editorTree.TextBuffer); - - // Advise to settings changed *after* accessing the RSettings, - // since accessing the host application (VS) settings object may - // cause it fire Changed notification in some cases. - REditorSettings.Changed += OnSettingsChanged; - - // We don't want to start validation right away since it may - // interfere with the editor perceived startup performance. - - StartValidationNextIdle(); - ValidationResults = new ConcurrentQueue(); - - ServiceManager.AddService(this, editorTree.TextBuffer); - } - #endregion - - /// - /// Retrieves (or creates) the validator (syntax checker) - /// for the document that is associated with the text buffer - /// - /// Text buffer - public static TreeValidator EnsureFromTextBuffer(ITextBuffer textBuffer, IEditorTree editorTree) - { - TreeValidator validator = ServiceManager.GetService(textBuffer); - if (validator == null) - { - validator = new TreeValidator(editorTree); - } - - return validator; - } - - /// - /// Determines if background validation is currently in - /// progress (i.e. validation thread is running). - /// - public bool IsValidationInProgress - { - get { return _syntaxCheckEnabled && _validationStarted; } - } - - private void StartValidationNextIdle() - { - if (_syntaxCheckEnabled) - { - AdviseToIdle(); - } - } - - #region Idle time handler - private void OnIdle(object sender, EventArgs e) - { - // Throttle validator idle a bit - if (TimeUtility.MillisecondsSinceUtc(_idleRequestTime) > _validationDelay) - { - UnadviseFromIdle(); - StartValidation(); - } - } - - private void AdviseToIdle() - { - if (!_advisedToIdleTime) - { - _idleRequestTime = DateTime.UtcNow; - - EditorShell.Current.Idle += OnIdle; - _advisedToIdleTime = true; - } - } - - private void UnadviseFromIdle() - { - if (_advisedToIdleTime) - { - EditorShell.Current.Idle -= OnIdle; - _advisedToIdleTime = false; - } - } - - #endregion - - #region Settings change handler - private void OnSettingsChanged(object sender, EventArgs e) - { - bool syntaxCheckWasEnabled = _syntaxCheckEnabled; - - _syntaxCheckEnabled = IsSyntaxCheckEnabled(_editorTree.TextBuffer); - - if (syntaxCheckWasEnabled && !_syntaxCheckEnabled) - { - StopValidation(); - } - else if (!syntaxCheckWasEnabled && _syntaxCheckEnabled) - { - StartValidation(); - } - - if (Cleared != null) - Cleared(this, EventArgs.Empty); - } - #endregion - - public static bool IsSyntaxCheckEnabled(ITextBuffer textBuffer) - { - IREditorDocument document = REditorDocument.FromTextBuffer(textBuffer); - if (document != null) - { - return document.IsTransient ? REditorSettings.SyntaxCheckInRepl : REditorSettings.SyntaxCheck; - } - - return false; - } - - private void StartValidation() - { - if (_syntaxCheckEnabled && _editorTree != null) - { - _validationStarted = true; - QueueTreeForValidation(); - } - } - - private void StopValidation() - { - _validationStarted = false; - - // Empty the results queue - while (!ValidationResults.IsEmpty) - { - IValidationError error; - ValidationResults.TryDequeue(out error); - } - - UnadviseFromIdle(); - } - - #region Tree event handlers - - /// - /// Listens to 'nodes removed' event which fires when user - /// deletes text that generated AST nodes or pastes over - /// new content. This allows validator to remove related - /// errors from the task list quickly so they don't linger - /// until the next validation pass. - /// - /// - /// - private void OnNodesRemoved(object sender, TreeNodesRemovedEventArgs e) - { - if (_syntaxCheckEnabled) - { - foreach (var node in e.Nodes) - { - ClearResultsForNode(node); - } - } - } - - private void OnTreeUpdateCompleted(object sender, TreeUpdatedEventArgs e) - { - if (e.UpdateType == TreeUpdateType.NewTree) - { - StopValidation(); - StartValidationNextIdle(); - } - } - - private void OnTreeClose(object sender, EventArgs e) - { - ServiceManager.RemoveService(_editorTree.TextBuffer); - - StopValidation(); - - _editorTree.NodesRemoved -= OnNodesRemoved; - _editorTree.UpdateCompleted -= OnTreeUpdateCompleted; - _editorTree.Closing -= OnTreeClose; - - _editorTree = null; - - REditorSettings.Changed -= OnSettingsChanged; - } - #endregion - - private void ClearResultsForNode(IAstNode node) - { - foreach (var child in node.Children) - { - ClearResultsForNode(child); - } - - // Adding sentinel will cause task list handler - // to remove all results from the task list - ValidationResults.Enqueue(new ValidationSentinel()); - } - - private void QueueTreeForValidation() - { - // Transfer available errors from the tree right away - foreach (ParseError e in _editorTree.AstRoot.Errors) - { - ValidationResults.Enqueue(new ValidationError(e, ErrorText.GetText(e.ErrorType), e.Location)); - } - } - } -} +using System; +using System.Collections.Concurrent; +using System.Diagnostics; +using Microsoft.Languages.Core.Text; +using Microsoft.Languages.Core.Utility; +using Microsoft.Languages.Editor.Services; +using Microsoft.Languages.Editor.Shell; +using Microsoft.R.Core.AST.Definitions; +using Microsoft.R.Core.Parser; +using Microsoft.R.Core.Tokens; +using Microsoft.R.Editor.Document; +using Microsoft.R.Editor.Document.Definitions; +using Microsoft.R.Editor.Settings; +using Microsoft.R.Editor.Tree; +using Microsoft.R.Editor.Tree.Definitions; +using Microsoft.R.Editor.Validation.Definitions; +using Microsoft.R.Editor.Validation.Errors; +using Microsoft.VisualStudio.Text; + +namespace Microsoft.R.Editor.Validation +{ + /// + /// Main R validator: performs syntax check, produces results that + /// then sent to the task list and tagger that creates squiggles. + /// Works asynchronously except the final part that pushes actual + /// task items to the IDE. Additional validators can be added + /// via MEF exports. + /// + /// Validator listens to tree updated events and schedules validation + /// passes for next idle slot. Next idle validation thread starts + /// and performas its work. Validator also listens to changes in + /// settings which may turn validation on or off. + /// + public sealed class TreeValidator + { + private static BooleanSwitch _traceValidation = + new BooleanSwitch("traceRValidation", "Trace R validation events in debug window."); + public BooleanSwitch TraceValidation + { + get { return _traceValidation; } + } + + private static int _validationDelay = 200; + + /// + /// Queue of validation results. Typically accessed from the main + /// thread that pushes errors/warning into a task list window. + /// Code that places items on the task list should be checking if + /// node that produced the error is still exist in the document. + /// + internal ConcurrentQueue ValidationResults { get; private set; } + + private IEditorTree _editorTree; + private bool _syntaxCheckEnabled = false; + private bool _validationStarted = false; + + private bool _advisedToIdleTime = false; + private DateTime _idleRequestTime = DateTime.UtcNow; + + /// + /// Fires when validator is cleared. Typically when validation was switched off + /// so task list and all error tags must be removed from the editor. + /// + public event EventHandler Cleared; + + #region Constructors + public TreeValidator(IEditorTree editorTree) + { +#if DEBUG + TraceValidation.Enabled = false; +#endif + + _editorTree = editorTree; + + _editorTree.NodesRemoved += OnNodesRemoved; + _editorTree.UpdateCompleted += OnTreeUpdateCompleted; + _editorTree.Closing += OnTreeClose; + + _syntaxCheckEnabled = IsSyntaxCheckEnabled(_editorTree.TextBuffer); + + // Advise to settings changed *after* accessing the RSettings, + // since accessing the host application (VS) settings object may + // cause it fire Changed notification in some cases. + REditorSettings.Changed += OnSettingsChanged; + + // We don't want to start validation right away since it may + // interfere with the editor perceived startup performance. + + StartValidationNextIdle(); + ValidationResults = new ConcurrentQueue(); + + ServiceManager.AddService(this, editorTree.TextBuffer); + } + #endregion + + /// + /// Retrieves (or creates) the validator (syntax checker) + /// for the document that is associated with the text buffer + /// + /// Text buffer + public static TreeValidator EnsureFromTextBuffer(ITextBuffer textBuffer, IEditorTree editorTree) + { + TreeValidator validator = ServiceManager.GetService(textBuffer); + if (validator == null) + { + validator = new TreeValidator(editorTree); + } + + return validator; + } + + /// + /// Determines if background validation is currently in + /// progress (i.e. validation thread is running). + /// + public bool IsValidationInProgress + { + get { return _syntaxCheckEnabled && _validationStarted; } + } + + private void StartValidationNextIdle() + { + if (_syntaxCheckEnabled) + { + AdviseToIdle(); + } + } + + #region Idle time handler + private void OnIdle(object sender, EventArgs e) + { + // Throttle validator idle a bit + if (TimeUtility.MillisecondsSinceUtc(_idleRequestTime) > _validationDelay) + { + UnadviseFromIdle(); + StartValidation(); + } + } + + private void AdviseToIdle() + { + if (!_advisedToIdleTime) + { + _idleRequestTime = DateTime.UtcNow; + + EditorShell.Current.Idle += OnIdle; + _advisedToIdleTime = true; + } + } + + private void UnadviseFromIdle() + { + if (_advisedToIdleTime) + { + EditorShell.Current.Idle -= OnIdle; + _advisedToIdleTime = false; + } + } + + #endregion + + #region Settings change handler + private void OnSettingsChanged(object sender, EventArgs e) + { + bool syntaxCheckWasEnabled = _syntaxCheckEnabled; + + _syntaxCheckEnabled = IsSyntaxCheckEnabled(_editorTree.TextBuffer); + + if (syntaxCheckWasEnabled && !_syntaxCheckEnabled) + { + StopValidation(); + } + else if (!syntaxCheckWasEnabled && _syntaxCheckEnabled) + { + StartValidation(); + } + + if (Cleared != null) + Cleared(this, EventArgs.Empty); + } + #endregion + + public static bool IsSyntaxCheckEnabled(ITextBuffer textBuffer) + { + IREditorDocument document = REditorDocument.FromTextBuffer(textBuffer); + if (document != null) + { + return document.IsTransient ? REditorSettings.SyntaxCheckInRepl : REditorSettings.SyntaxCheck; + } + + return false; + } + + private void StartValidation() + { + if (_syntaxCheckEnabled && _editorTree != null) + { + _validationStarted = true; + QueueTreeForValidation(); + } + } + + private void StopValidation() + { + _validationStarted = false; + + // Empty the results queue + while (!ValidationResults.IsEmpty) + { + IValidationError error; + ValidationResults.TryDequeue(out error); + } + + UnadviseFromIdle(); + } + + #region Tree event handlers + + /// + /// Listens to 'nodes removed' event which fires when user + /// deletes text that generated AST nodes or pastes over + /// new content. This allows validator to remove related + /// errors from the task list quickly so they don't linger + /// until the next validation pass. + /// + /// + /// + private void OnNodesRemoved(object sender, TreeNodesRemovedEventArgs e) + { + if (_syntaxCheckEnabled) + { + foreach (var node in e.Nodes) + { + ClearResultsForNode(node); + } + } + } + + private void OnTreeUpdateCompleted(object sender, TreeUpdatedEventArgs e) + { + if (e.UpdateType == TreeUpdateType.NewTree) + { + StopValidation(); + StartValidationNextIdle(); + } + } + + private void OnTreeClose(object sender, EventArgs e) + { + ServiceManager.RemoveService(_editorTree.TextBuffer); + + StopValidation(); + + _editorTree.NodesRemoved -= OnNodesRemoved; + _editorTree.UpdateCompleted -= OnTreeUpdateCompleted; + _editorTree.Closing -= OnTreeClose; + + _editorTree = null; + + REditorSettings.Changed -= OnSettingsChanged; + } + #endregion + + private void ClearResultsForNode(IAstNode node) + { + foreach (var child in node.Children) + { + ClearResultsForNode(child); + } + + // Adding sentinel will cause task list handler + // to remove all results from the task list + ValidationResults.Enqueue(new ValidationSentinel()); + } + + private void QueueTreeForValidation() + { + // Transfer available errors from the tree right away + foreach (ParseError e in _editorTree.AstRoot.Errors) + { + ValidationResults.Enqueue(new ValidationError(e, ErrorText.GetText(e.ErrorType), e.Location)); + } + } + } +} diff --git a/src/R/Editor/Test/Formatting/FormatCommandTest.cs b/src/R/Editor/Test/Formatting/FormatCommandTest.cs index d04882d68..ef0411fd8 100644 --- a/src/R/Editor/Test/Formatting/FormatCommandTest.cs +++ b/src/R/Editor/Test/Formatting/FormatCommandTest.cs @@ -37,7 +37,7 @@ public void FormatDocument() { } [Test] - public void FormatOnPaste() { + public void FormatOnPasteStatus() { ITextBuffer textBuffer = new TextBufferMock(string.Empty, RContentTypeDefinition.ContentType); ITextView textView = new TextViewMock(textBuffer); var clipboard = new ClipboardDataProvider(); @@ -49,10 +49,36 @@ public void FormatOnPaste() { status.Should().Be(CommandStatus.NotSupported); clipboard.Format = DataFormats.UnicodeText; - clipboard.Data = "if(x<1){x<-2}"; + clipboard.Data = "data"; status = command.Status(VSConstants.GUID_VSStandardCommandSet97, (int)VSConstants.VSStd97CmdID.Paste); status.Should().Be(CommandStatus.SupportedAndEnabled); + } + } + + [Test] + public void FormatOnPaste01() { + string actual = FormatFromClipboard("if(x<1){x<-2}"); + actual.Should().Be("if (x < 1) {\r\n x <- 2\r\n}"); + } + + [Test] + public void FormatOnPaste02() { + string content = "\"a\r\nb\r\nc\""; + string actual = FormatFromClipboard(content); + actual.Should().Be(content); + } + + private string FormatFromClipboard(string content) { + ITextBuffer textBuffer = new TextBufferMock(string.Empty, RContentTypeDefinition.ContentType); + ITextView textView = new TextViewMock(textBuffer); + var clipboard = new ClipboardDataProvider(); + + using (var command = new FormatOnPasteCommand(textView, textBuffer)) { + command.ClipboardDataProvider = clipboard; + + clipboard.Format = DataFormats.UnicodeText; + clipboard.Data = content; var ast = RParser.Parse(textBuffer.CurrentSnapshot.GetText()); var document = new EditorDocumentMock(new EditorTreeMock(textBuffer, ast)); @@ -61,8 +87,7 @@ public void FormatOnPaste() { command.Invoke(VSConstants.GUID_VSStandardCommandSet97, (int)VSConstants.VSStd97CmdID.Paste, null, ref o); } - string actual = textBuffer.CurrentSnapshot.GetText(); - actual.Should().Be("if (x < 1) {\r\n x <- 2\r\n}"); + return textBuffer.CurrentSnapshot.GetText(); } class ClipboardDataProvider : IClipboardDataProvider { diff --git a/src/R/Editor/Test/Microsoft.R.Editor.Test.csproj b/src/R/Editor/Test/Microsoft.R.Editor.Test.csproj index 127f145df..ef1f4d15d 100644 --- a/src/R/Editor/Test/Microsoft.R.Editor.Test.csproj +++ b/src/R/Editor/Test/Microsoft.R.Editor.Test.csproj @@ -1,208 +1,208 @@ - - - - Debug - AnyCPU - {9066522E-780E-4409-B410-2525E6A621FC} - Library - Properties - Microsoft.R.Editor.Test - Microsoft.R.Editor.Test - v4.6 - 512 - {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} - 10.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages - False - UnitTest - SAK - SAK - SAK - SAK - - - - $(RootDirectory)\obj\ - $(RootDirectory)\bin\ - $(BaseIntermediateOutputPath)\$(Configuration)\$(AssemblyName)\ - $(BaseOutputPath)\$(Configuration)\ - - - - ..\..\..\..\NugetPackages\FluentAssertions.4.1.1\lib\net45\FluentAssertions.dll - True - - - ..\..\..\..\NugetPackages\FluentAssertions.4.1.1\lib\net45\FluentAssertions.Core.dll - True - - - ..\..\..\..\NugetPackages\Microsoft.VisualStudio.CoreUtility.14.1.24720\lib\net45\Microsoft.VisualStudio.CoreUtility.dll - True - - - - - ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.Data.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.Data.dll - True - - - ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.Logic.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.Logic.dll - True - - - ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.UI.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.UI.dll - True - - - ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.UI.Wpf.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.UI.Wpf.dll - True - - - - - - - - - - - ..\..\..\..\NugetPackages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll - True - - - ..\..\..\..\NugetPackages\xunit.assert.2.2.0-beta1-build3239\lib\dotnet\xunit.assert.dll - True - - - ..\..\..\..\NugetPackages\xunit.extensibility.core.2.2.0-beta1-build3239\lib\dotnet\xunit.core.dll - True - - - ..\..\..\..\NugetPackages\xunit.extensibility.execution.2.2.0-beta1-build3239\lib\net45\xunit.execution.desktop.dll - True - - - - - - - - - - - - - {8d408909-459f-4853-a36c-745118f99869} - Microsoft.Common.Core - - - {FC4AAD0A-13B9-49EE-A59C-F03142958170} - Microsoft.Common.Core.Test - - - {25cd8690-6208-4740-b123-6dbce6b9444a} - Microsoft.Languages.Core - - - {EE2504A4-4666-460B-8552-5B342718CB02} - Microsoft.Languages.Core.Test - - - {62857e49-e586-4baa-ae4d-1232093e7378} - Microsoft.Languages.Editor - - - {5340191E-31E5-43A0-A485-B6678D391B10} - Microsoft.Languages.Editor.Test - - - {5fcb86d5-4b25-4039-858c-b5a06eb702e1} - Microsoft.VisualStudio.Editor.Mocks - - - {5EF2AD64-D6FE-446B-B350-8C7F0DF0834D} - Microsoft.UnitTests.Core - - - {0c4bce1d-3cb8-4e2a-9252-58784d7f26a5} - Microsoft.R.Core - - - {58D86BE4-FA8B-4F59-91FE-A9B348C70ED2} - Microsoft.R.Core.Test - - - {c1957d47-b0b4-42e0-bc08-0d5e96e47fe4} - Microsoft.R.Support - - - {5504F3D6-08D3-401F-8214-409A60735185} - Microsoft.R.Support.Test - - - {d6eeef87-ce3a-4804-a409-39966b96c850} - Microsoft.R.Editor - - - - - - - - - - - - - Properties\GlobalAssemblyInfo.cs - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - Microsoft - StrongName - - - - - - + + + + Debug + AnyCPU + {9066522E-780E-4409-B410-2525E6A621FC} + Library + Properties + Microsoft.R.Editor.Test + Microsoft.R.Editor.Test + v4.6 + 512 + {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages + False + UnitTest + SAK + SAK + SAK + SAK + + + + $(RootDirectory)\obj\ + $(RootDirectory)\bin\ + $(BaseIntermediateOutputPath)\$(Configuration)\$(AssemblyName)\ + $(BaseOutputPath)\$(Configuration)\ + + + + ..\..\..\..\NugetPackages\FluentAssertions.4.1.1\lib\net45\FluentAssertions.dll + True + + + ..\..\..\..\NugetPackages\FluentAssertions.4.1.1\lib\net45\FluentAssertions.Core.dll + True + + + ..\..\..\..\NugetPackages\Microsoft.VisualStudio.CoreUtility.14.1.24720\lib\net45\Microsoft.VisualStudio.CoreUtility.dll + True + + + + + ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.Data.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.Data.dll + True + + + ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.Logic.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.Logic.dll + True + + + ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.UI.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.UI.dll + True + + + ..\..\..\..\NugetPackages\Microsoft.VisualStudio.Text.UI.Wpf.14.1.24720\lib\net45\Microsoft.VisualStudio.Text.UI.Wpf.dll + True + + + + + + + + + + + ..\..\..\..\NugetPackages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll + True + + + ..\..\..\..\NugetPackages\xunit.assert.2.2.0-beta1-build3239\lib\dotnet\xunit.assert.dll + True + + + ..\..\..\..\NugetPackages\xunit.extensibility.core.2.2.0-beta1-build3239\lib\dotnet\xunit.core.dll + True + + + ..\..\..\..\NugetPackages\xunit.extensibility.execution.2.2.0-beta1-build3239\lib\net45\xunit.execution.desktop.dll + True + + + + + + + + + + + + + {8d408909-459f-4853-a36c-745118f99869} + Microsoft.Common.Core + + + {FC4AAD0A-13B9-49EE-A59C-F03142958170} + Microsoft.Common.Core.Test + + + {25cd8690-6208-4740-b123-6dbce6b9444a} + Microsoft.Languages.Core + + + {EE2504A4-4666-460B-8552-5B342718CB02} + Microsoft.Languages.Core.Test + + + {62857e49-e586-4baa-ae4d-1232093e7378} + Microsoft.Languages.Editor + + + {5340191E-31E5-43A0-A485-B6678D391B10} + Microsoft.Languages.Editor.Test + + + {5fcb86d5-4b25-4039-858c-b5a06eb702e1} + Microsoft.VisualStudio.Editor.Mocks + + + {5EF2AD64-D6FE-446B-B350-8C7F0DF0834D} + Microsoft.UnitTests.Core + + + {0c4bce1d-3cb8-4e2a-9252-58784d7f26a5} + Microsoft.R.Core + + + {58D86BE4-FA8B-4F59-91FE-A9B348C70ED2} + Microsoft.R.Core.Test + + + {c1957d47-b0b4-42e0-bc08-0d5e96e47fe4} + Microsoft.R.Support + + + {5504F3D6-08D3-401F-8214-409A60735185} + Microsoft.R.Support.Test + + + {d6eeef87-ce3a-4804-a409-39966b96c850} + Microsoft.R.Editor + + + + + + + + + + + + + Properties\GlobalAssemblyInfo.cs + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Microsoft + StrongName + + + + + + \ No newline at end of file diff --git a/src/R/Editor/Test/Outline/OutlineBuilderTest.cs b/src/R/Editor/Test/Outline/OutlineBuilderTest.cs index 2bc081ccb..9c2f71b47 100644 --- a/src/R/Editor/Test/Outline/OutlineBuilderTest.cs +++ b/src/R/Editor/Test/Outline/OutlineBuilderTest.cs @@ -59,8 +59,7 @@ public void RRegionBuilder_Test02() { string content = @"if (ncol(x) == 1L) { xnames < -1 -} -else { +} else { xnames < -paste0(1, 1L:ncol(x)) } if (intercept) { @@ -70,20 +69,17 @@ public void RRegionBuilder_Test02() { "; OutlineRegionCollection rc = OutlineTest.BuildOutlineRegions(content); - // [0][0...165), Length = 165 - // [1][42...90), Length = 48 - // [2][94...163), Length = 69 rc.Should().HaveCount(3); rc[0].Start.Should().Be(0); - rc[0].Length.Should().Be(90); + rc[0].Length.Should().Be(89); - rc[1].Start.Should().Be(42); - rc[1].End.Should().Be(90); + rc[1].Start.Should().Be(41); + rc[1].End.Should().Be(89); rc[1].DisplayText.Should().Be("else..."); - rc[2].Start.Should().Be(94); - rc[2].End.Should().Be(163); + rc[2].Start.Should().Be(93); + rc[2].End.Should().Be(162); rc[2].DisplayText.Should().Be("if..."); } diff --git a/src/R/Editor/Test/Tree/ProcessChangesTest.cs b/src/R/Editor/Test/Tree/ProcessChangesTest.cs index 7cc30d04f..ce01475c4 100644 --- a/src/R/Editor/Test/Tree/ProcessChangesTest.cs +++ b/src/R/Editor/Test/Tree/ProcessChangesTest.cs @@ -146,7 +146,7 @@ Variable [x] ParserTest.VerifyParse(expected1, expression); EditorTree tree = EditorTreeTest.ApplyTextChange(expression, 17, 0, 1, "\n"); - tree.IsDirty.Should().BeFalse(); + tree.IsDirty.Should().BeTrue(); tree.ProcessChanges(); string expected2 = @@ -166,15 +166,14 @@ Variable [x] TokenNode [<- [12...14)] NumericalValue [1 [15...16)] TokenNode [} [16...17)] - KeywordScopeStatement [] - TokenNode [else [19...23)] - SimpleScope [24...30) - ExpressionStatement [x <- 2] - Expression [x <- 2] - TokenOperator [<- [26...28)] - Variable [x] - TokenNode [<- [26...28)] - NumericalValue [2 [29...30)] + ExpressionStatement [x <- 2] + Expression [x <- 2] + TokenOperator [<- [26...28)] + Variable [x] + TokenNode [<- [26...28)] + NumericalValue [2 [29...30)] + +UnexpectedToken Token [19...23) "; ParserTest.CompareTrees(expected2, tree.AstRoot); } diff --git a/src/R/Support/Impl/Help/FunctionRdDataProvider.cs b/src/R/Support/Impl/Help/FunctionRdDataProvider.cs index a3fdcbeaa..ec68831d0 100644 --- a/src/R/Support/Impl/Help/FunctionRdDataProvider.cs +++ b/src/R/Support/Impl/Help/FunctionRdDataProvider.cs @@ -1,5 +1,6 @@ using System; using System.ComponentModel.Composition; +using System.Threading; using System.Threading.Tasks; using Microsoft.Languages.Editor.Shell; using Microsoft.R.Support.Help.Definitions; @@ -13,6 +14,7 @@ namespace Microsoft.R.Host.Client.Signatures { public sealed class FunctionRdDataProvider : IFunctionRdDataProvider { private static readonly Guid SessionId = new Guid("8BEF9C06-39DC-4A64-B7F3-0C68353362C9"); private IRSession _session; + private SemaphoreSlim _sessionSemaphore = new SemaphoreSlim(1, 1); /// /// Timeout to allow R-Host to start. Typically only needs @@ -47,17 +49,24 @@ public void Dispose() { } private async Task CreateSessionAsync() { - if (_session == null) { - var provider = EditorShell.Current.ExportProvider.GetExportedValue(); - _session = provider.GetOrCreate(SessionId, null); - _session.Disposed += OnSessionDisposed; + await _sessionSemaphore.WaitAsync(); + try { + if (_session == null) { + var provider = EditorShell.Current.ExportProvider.GetExportedValue(); + _session = provider.GetOrCreate(SessionId, null); + _session.Disposed += OnSessionDisposed; + } - int timeout = EditorShell.Current.IsUnitTestEnvironment ? 10000 : 3000; - await _session.StartHostAsync(new RHostStartupInfo { - Name = "RdData", - RBasePath = RToolsSettings.Current.RBasePath, - CranMirrorName = RToolsSettings.Current.CranMirror - }, timeout); + if (!_session.IsHostRunning) { + int timeout = EditorShell.Current.IsUnitTestEnvironment ? 10000 : 3000; + await _session.StartHostAsync(new RHostStartupInfo { + Name = "RdData", + RBasePath = RToolsSettings.Current.RBasePath, + CranMirrorName = RToolsSettings.Current.CranMirror + }, timeout); + } + } finally { + _sessionSemaphore.Release(); } } @@ -70,9 +79,9 @@ private void OnSessionDisposed(object sender, EventArgs e) { private string GetCommandText(string functionName, string packageName) { if (string.IsNullOrEmpty(packageName)) { - return ".rtvs.signature.help1('" + functionName + "')"; + return "rtvs:::signature.help1('" + functionName + "')"; } - return ".rtvs.signature.help2('" + functionName + "', '" + packageName + "')"; + return "rtvs:::signature.help2('" + functionName + "', '" + packageName + "')"; } } } diff --git a/src/R/Support/Impl/Settings/Definitions/IRToolsSettings.cs b/src/R/Support/Impl/Settings/Definitions/IRToolsSettings.cs index 1df34b12f..ebcee89b4 100644 --- a/src/R/Support/Impl/Settings/Definitions/IRToolsSettings.cs +++ b/src/R/Support/Impl/Settings/Definitions/IRToolsSettings.cs @@ -42,5 +42,7 @@ public interface IRToolsSettings { /// to the external default Web browser. /// HelpBrowserType HelpBrowser { get; set; } + + bool ShowDotPrefixedVariables { get; set; } } } diff --git a/src/R/Support/Test/Utility/TestRToolsSettings.cs b/src/R/Support/Test/Utility/TestRToolsSettings.cs index e3b9a96ff..430e2d608 100644 --- a/src/R/Support/Test/Utility/TestRToolsSettings.cs +++ b/src/R/Support/Test/Utility/TestRToolsSettings.cs @@ -71,5 +71,7 @@ public HelpBrowserType HelpBrowser { get { return HelpBrowserType.Automatic; } set { } } + + public bool ShowDotPrefixedVariables { get; set; } } } diff --git a/src/Setup/FileNewTemplates.wxi b/src/Setup/FileNewTemplates.wxi index 5c0f082e7..73695e817 100644 --- a/src/Setup/FileNewTemplates.wxi +++ b/src/Setup/FileNewTemplates.wxi @@ -1,6 +1,6 @@ - + diff --git a/src/Setup/Product.wxs b/src/Setup/Product.wxs index 58d7324dd..c2f32a8c4 100644 --- a/src/Setup/Product.wxs +++ b/src/Setup/Product.wxs @@ -16,13 +16,13 @@ - - + @@ -45,6 +45,8 @@ + + + + + + + + + + Updating Visual Studio project templates... (this may take a few minutes). - + NOT Installed + NOT Installed + NOT Installed @@ -100,6 +126,10 @@ + + + + diff --git a/src/Setup/Profiles.wxi b/src/Setup/Profiles.wxi index 1f40fdc5a..aba97fc97 100644 --- a/src/Setup/Profiles.wxi +++ b/src/Setup/Profiles.wxi @@ -2,8 +2,9 @@ - - + + + diff --git a/src/Setup/RtvsPackage.wxi b/src/Setup/RtvsPackage.wxi index ed0788294..d0a1b03c6 100644 --- a/src/Setup/RtvsPackage.wxi +++ b/src/Setup/RtvsPackage.wxi @@ -10,7 +10,9 @@ + + diff --git a/src/Setup/Setup.wixproj b/src/Setup/Setup.wixproj index 0ae5dc923..3036d970a 100644 --- a/src/Setup/Setup.wixproj +++ b/src/Setup/Setup.wixproj @@ -58,6 +58,24 @@ + + + Microsoft.VisualStudio.R.Package + {26035fe3-25ab-45ec-bb45-7fd0b6c1d545} + True + True + Binaries;Content;Satellites + INSTALLFOLDER + + + SetupCustomActions + {f2149709-a88b-4f36-abca-307ca96e9fd1} + True + True + Binaries;Content;Satellites + INSTALLFOLDER + + diff --git a/src/SetupBundle/Product.wxs b/src/SetupBundle/Product.wxs index 516f906c9..3e9347901 100644 --- a/src/SetupBundle/Product.wxs +++ b/src/SetupBundle/Product.wxs @@ -14,7 +14,6 @@ UpgradeCode="{165B6EF9-D111-4435-8BDC-604AA21BF8C9}" IconSourceFile="..\Setup\RFile.ico"> - + + + + @@ -32,7 +38,6 @@ BuildNumber="$(var.MsiVersion)" /> - + + + + + + + + + - + + + + \ No newline at end of file diff --git a/src/SetupBundle/SetupBundle.wixproj b/src/SetupBundle/SetupBundle.wixproj index a321680c1..05f1ec53d 100644 --- a/src/SetupBundle/SetupBundle.wixproj +++ b/src/SetupBundle/SetupBundle.wixproj @@ -19,7 +19,8 @@ SAK SAK SAK - + + ..\..\bin\$(Configuration)\ diff --git a/src/SetupBundle/VsDetection.wxi b/src/SetupBundle/VsDetection.wxi index 81b25b62a..4699315fc 100644 --- a/src/SetupBundle/VsDetection.wxi +++ b/src/SetupBundle/VsDetection.wxi @@ -1,28 +1,20 @@ - - + - --> + - --> - - diff --git a/src/SetupCustomActions/CustomAction.config b/src/SetupCustomActions/CustomAction.config new file mode 100644 index 000000000..de951b78d --- /dev/null +++ b/src/SetupCustomActions/CustomAction.config @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + diff --git a/src/SetupCustomActions/CustomAction.cs b/src/SetupCustomActions/CustomAction.cs new file mode 100644 index 000000000..bc895b43e --- /dev/null +++ b/src/SetupCustomActions/CustomAction.cs @@ -0,0 +1,143 @@ +using System; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Windows.Forms; +using Microsoft.Deployment.WindowsInstaller; +using Microsoft.R.Actions.Utility; +using Microsoft.Win32; + +namespace SetupCustomActions { + public class CustomActions { + private const string vsVersion = "14.0"; + private const string vsServicingKeyName = @"SOFTWARE\Microsoft\DevDiv\vs\Servicing\" + vsVersion; + + [CustomAction] + public static ActionResult DSProfilePromptAction(Session session) { + ActionResult actionResult = ActionResult.Success; + DialogResult result = DialogResult.No; + string exceptionMessage = null; + bool resetKeyboard = false; + + // Uncomment for debugging + //MessageBox.Show("Custom Action", "Begin!"); + session.Log("Begin Data Science profile import action"); + + string ideFolder = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ProgramFilesX86), @"Microsoft Visual Studio 14.0\Common7\IDE\"); + string installFolder = Path.Combine(ideFolder, @"Extensions\Microsoft\R Tools for Visual Studio\"); + + using (var form = new DSProfilePromptForm()) { + result = form.ShowDialog(new SetupWindowHandle()); + if (result == DialogResult.No) { + session.Log("User said NO"); + actionResult = ActionResult.NotExecuted; + } + resetKeyboard = form.ResetKeyboardShortcuts; + } + + if (result == DialogResult.Yes && actionResult == ActionResult.Success) { + try { + session.Log("Begin importing window layout"); + string settingsFilePath = Path.Combine(ideFolder, @"Profiles\", resetKeyboard ? "RCombined.vssettings" : "R.vssettings"); + + ProcessStartInfo psi = new ProcessStartInfo(); + psi.FileName = Path.Combine(ideFolder, "devenv.exe"); + psi.Arguments = string.Format(CultureInfo.InvariantCulture, "/ResetSettings \"{0}\"", settingsFilePath); + Process.Start(psi); + actionResult = ActionResult.Success; + } catch (Exception ex) { + exceptionMessage = ex.Message; + actionResult = ActionResult.Failure; + } + } + + if (!string.IsNullOrEmpty(exceptionMessage)) { + session.Log("Data Science profile import action failed. Exception: {0}", exceptionMessage); + } + + session.Log("End Data Science profile import action"); + return actionResult; + } + + [CustomAction] + public static ActionResult MROInstallPromptAction(Session session) { + ActionResult actionResult = ActionResult.Success; + DialogResult ds = DialogResult.No; + + session.Log("Begin R detection action"); + session["InstallMRO"] = "No"; + + RInstallData data = RInstallation.GetInstallationData(null, + SupportedRVersionList.MinMajorVersion, SupportedRVersionList.MinMinorVersion, + SupportedRVersionList.MaxMajorVersion, SupportedRVersionList.MaxMinorVersion); + + if (data.Status != RInstallStatus.OK) { + using (var form = new InstallMROForm()) { + ds = form.ShowDialog(new SetupWindowHandle()); + } + } + + if (ds == DialogResult.Yes) { + session["InstallMRO"] = "Yes"; + Process.Start("https://mran.revolutionanalytics.com/download/#download"); + } + + session.Log("End R detection action"); + return actionResult; + } + + [CustomAction] + public static ActionResult VsCommunityInstallAction(Session session) { + ActionResult actionResult = ActionResult.UserExit; + DialogResult ds = DialogResult.No; + bool vsInstalled = false; + string[] vsKeys = new string[] { @"\enterprise", @"\professional", @"\community" }; + + session.Log("Begin VS detection action"); + session["InstallVS"] = "No"; + + using (RegistryKey hklm = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Registry32)) { + foreach (var vsk in vsKeys) { + try { + using (var key = hklm.OpenSubKey(vsServicingKeyName + vsk)) { + object value = key.GetValue("Install"); + if (value != null && ((int)value) == 1) { + vsInstalled = true; + actionResult = ActionResult.Success; + break; + } + } + } catch (Exception) { } + } + } + + if (!vsInstalled) { + using (var form = new InstallVsCommunityForm()) { + ds = form.ShowDialog(new SetupWindowHandle()); + } + } + + if (ds == DialogResult.Yes) { + session["InstallVS"] = "Yes"; + Process.Start("https://www.visualstudio.com/en-us/products/visual-studio-community-vs.aspx"); + } + + session.Log("End VS detection action"); + return actionResult; + } + + class SetupWindowHandle : IWin32Window { + public IntPtr Handle { get; } + + public SetupWindowHandle() { + Process[] procs = Process.GetProcessesByName("rtvs"); + + Process p = procs.FirstOrDefault(x => x.MainWindowHandle != IntPtr.Zero); + if (p != null) { + Handle = p.MainWindowHandle; + } + } + } + } +} diff --git a/src/SetupCustomActions/DSProfilePromptForm.Designer.cs b/src/SetupCustomActions/DSProfilePromptForm.Designer.cs new file mode 100644 index 000000000..119be67b1 --- /dev/null +++ b/src/SetupCustomActions/DSProfilePromptForm.Designer.cs @@ -0,0 +1,128 @@ +namespace SetupCustomActions { + partial class DSProfilePromptForm { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(DSProfilePromptForm)); + this.buttonYes = new System.Windows.Forms.Button(); + this.buttonNo = new System.Windows.Forms.Button(); + this.pictureBox1 = new System.Windows.Forms.PictureBox(); + this.PromptText = new System.Windows.Forms.Label(); + this.keepShortcuts = new System.Windows.Forms.CheckBox(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit(); + this.SuspendLayout(); + // + // buttonYes + // + this.buttonYes.BackColor = System.Drawing.Color.Black; + this.buttonYes.DialogResult = System.Windows.Forms.DialogResult.Yes; + this.buttonYes.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.buttonYes.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204))); + this.buttonYes.ForeColor = System.Drawing.Color.Silver; + this.buttonYes.Location = new System.Drawing.Point(344, 426); + this.buttonYes.Name = "buttonYes"; + this.buttonYes.Size = new System.Drawing.Size(86, 30); + this.buttonYes.TabIndex = 0; + this.buttonYes.Text = "&Yes"; + this.buttonYes.UseVisualStyleBackColor = false; + this.buttonYes.Click += new System.EventHandler(this.buttonYes_Click); + // + // buttonNo + // + this.buttonNo.BackColor = System.Drawing.Color.Black; + this.buttonNo.DialogResult = System.Windows.Forms.DialogResult.No; + this.buttonNo.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.buttonNo.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204))); + this.buttonNo.ForeColor = System.Drawing.Color.Silver; + this.buttonNo.Location = new System.Drawing.Point(465, 426); + this.buttonNo.Name = "buttonNo"; + this.buttonNo.Size = new System.Drawing.Size(89, 30); + this.buttonNo.TabIndex = 1; + this.buttonNo.Text = "&No"; + this.buttonNo.UseVisualStyleBackColor = false; + this.buttonNo.Click += new System.EventHandler(this.buttonNo_Click); + // + // pictureBox1 + // + this.pictureBox1.Image = ((System.Drawing.Image)(resources.GetObject("pictureBox1.Image"))); + this.pictureBox1.InitialImage = ((System.Drawing.Image)(resources.GetObject("pictureBox1.InitialImage"))); + this.pictureBox1.Location = new System.Drawing.Point(18, 18); + this.pictureBox1.Name = "pictureBox1"; + this.pictureBox1.Size = new System.Drawing.Size(536, 328); + this.pictureBox1.SizeMode = System.Windows.Forms.PictureBoxSizeMode.Zoom; + this.pictureBox1.TabIndex = 2; + this.pictureBox1.TabStop = false; + // + // PromptText + // + this.PromptText.AutoSize = true; + this.PromptText.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204))); + this.PromptText.ForeColor = System.Drawing.Color.Silver; + this.PromptText.Location = new System.Drawing.Point(15, 365); + this.PromptText.Name = "PromptText"; + this.PromptText.Size = new System.Drawing.Size(489, 51); + this.PromptText.TabIndex = 3; + this.PromptText.Text = resources.GetString("PromptText.Text"); + // + // keepShortcuts + // + this.keepShortcuts.AutoSize = true; + this.keepShortcuts.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.keepShortcuts.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204))); + this.keepShortcuts.ForeColor = System.Drawing.Color.Silver; + this.keepShortcuts.Location = new System.Drawing.Point(18, 430); + this.keepShortcuts.Name = "keepShortcuts"; + this.keepShortcuts.Size = new System.Drawing.Size(281, 21); + this.keepShortcuts.TabIndex = 4; + this.keepShortcuts.Text = "&Keep existing keyboard shotcuts unchanged"; + this.keepShortcuts.UseVisualStyleBackColor = true; + this.keepShortcuts.CheckedChanged += new System.EventHandler(this.keepShorcuts_CheckedChanged); + // + // DSProfilePromptForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.Color.Black; + this.ClientSize = new System.Drawing.Size(574, 476); + this.Controls.Add(this.keepShortcuts); + this.Controls.Add(this.PromptText); + this.Controls.Add(this.pictureBox1); + this.Controls.Add(this.buttonNo); + this.Controls.Add(this.buttonYes); + this.Name = "DSProfilePromptForm"; + this.Text = "R Tools for Visual Studio"; + ((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Button buttonYes; + private System.Windows.Forms.Button buttonNo; + private System.Windows.Forms.PictureBox pictureBox1; + private System.Windows.Forms.Label PromptText; + private System.Windows.Forms.CheckBox keepShortcuts; + } +} \ No newline at end of file diff --git a/src/SetupCustomActions/DSProfilePromptForm.cs b/src/SetupCustomActions/DSProfilePromptForm.cs new file mode 100644 index 000000000..3c210b05e --- /dev/null +++ b/src/SetupCustomActions/DSProfilePromptForm.cs @@ -0,0 +1,28 @@ +using System; +using System.Windows.Forms; + +namespace SetupCustomActions { + public partial class DSProfilePromptForm : Form { + public bool ResetKeyboardShortcuts { get; private set; } = true; + + public DSProfilePromptForm() { + InitializeComponent(); + this.TopMost = true; + this.CenterToScreen(); + } + + private void buttonYes_Click(object sender, EventArgs e) { + this.DialogResult = DialogResult.Yes; + this.Close(); + } + + private void buttonNo_Click(object sender, EventArgs e) { + this.DialogResult = DialogResult.No; + this.Close(); + } + + private void keepShorcuts_CheckedChanged(object sender, EventArgs e) { + this.ResetKeyboardShortcuts = !this.keepShortcuts.Checked; + } + } +} diff --git a/src/SetupCustomActions/DSProfilePromptForm.resx b/src/SetupCustomActions/DSProfilePromptForm.resx new file mode 100644 index 000000000..5d1f0467f --- /dev/null +++ b/src/SetupCustomActions/DSProfilePromptForm.resx @@ -0,0 +1,2600 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + + 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 + YZjd/uZv/P+v10r1Tg6lZAAAAABJRU5ErkJggg== + + + + + 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 + + + + The Data Scientist Profile provides a window layout and keyboard shortcuts similar +to other IDE’s. Would you like activate this profile? If you want to keep existing +keyboard shotcuts, set the checkbox below to on. + + + \ No newline at end of file diff --git a/src/SetupCustomActions/DSProfileScreen.png b/src/SetupCustomActions/DSProfileScreen.png new file mode 100644 index 000000000..a777e27f0 Binary files /dev/null and b/src/SetupCustomActions/DSProfileScreen.png differ diff --git a/src/SetupCustomActions/InstallMROForm.cs b/src/SetupCustomActions/InstallMROForm.cs new file mode 100644 index 000000000..25d28d4a3 --- /dev/null +++ b/src/SetupCustomActions/InstallMROForm.cs @@ -0,0 +1,6 @@ +namespace SetupCustomActions { + public partial class InstallMROForm : YesNoMessageBox { + public InstallMROForm() : base(Resources.MicrosoftROpenQuestion) { + } + } +} diff --git a/src/SetupCustomActions/InstallVsCommunityForm.cs b/src/SetupCustomActions/InstallVsCommunityForm.cs new file mode 100644 index 000000000..b030156ef --- /dev/null +++ b/src/SetupCustomActions/InstallVsCommunityForm.cs @@ -0,0 +1,6 @@ +namespace SetupCustomActions { + public partial class InstallVsCommunityForm : YesNoMessageBox { + public InstallVsCommunityForm() : base(Resources.VsCommunityQuestion) { + } + } +} diff --git a/src/SetupCustomActions/Properties/AssemblyInfo.cs b/src/SetupCustomActions/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..6935e290f --- /dev/null +++ b/src/SetupCustomActions/Properties/AssemblyInfo.cs @@ -0,0 +1,9 @@ +using System.Runtime.InteropServices; + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("f2149709-a88b-4f36-abca-307ca96e9fd1")] diff --git a/src/SetupCustomActions/Resources.Designer.cs b/src/SetupCustomActions/Resources.Designer.cs new file mode 100644 index 000000000..37cfa2f02 --- /dev/null +++ b/src/SetupCustomActions/Resources.Designer.cs @@ -0,0 +1,118 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace SetupCustomActions { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("SetupCustomActions.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Data Scientist profile provides window layout and keyboard shorcuts similar to RStudio. Would you like setup to apply this profile to the Visual Studio?. + /// + internal static string DSProfilePromptText { + get { + return ResourceManager.GetString("DSProfilePromptText", resourceCulture); + } + } + + /// + /// Looks up a localized resource of type System.Drawing.Bitmap. + /// + internal static System.Drawing.Bitmap DSProfileScreen { + get { + object obj = ResourceManager.GetObject("DSProfileScreen", resourceCulture); + return ((System.Drawing.Bitmap)(obj)); + } + } + + /// + /// Looks up a localized string similar to Setup was unable to locate the R language engine. Would you like setup to open a Web page where you can download and install Microsoft R Open?. + /// + internal static string MicrosoftROpenQuestion { + get { + return ResourceManager.GetString("MicrosoftROpenQuestion", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to No. + /// + internal static string No { + get { + return ResourceManager.GetString("No", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Setup was unable to locate Visual Studio 2015 which is required. Would you like setup to open a Web page where you can download and install free Visual Studio 2015 Community?. + /// + internal static string VsCommunityQuestion { + get { + return ResourceManager.GetString("VsCommunityQuestion", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Yes. + /// + internal static string Yes { + get { + return ResourceManager.GetString("Yes", resourceCulture); + } + } + } +} diff --git a/src/SetupCustomActions/Resources.resx b/src/SetupCustomActions/Resources.resx new file mode 100644 index 000000000..12de1d849 --- /dev/null +++ b/src/SetupCustomActions/Resources.resx @@ -0,0 +1,139 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Data Scientist profile provides window layout and keyboard shorcuts similar to RStudio. Would you like setup to apply this profile to the Visual Studio? + + + + dsprofilescreen.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + + Setup was unable to locate the R language engine. Would you like setup to open a Web page where you can download and install Microsoft R Open? + + + No + + + Setup was unable to locate Visual Studio 2015 which is required. Would you like setup to open a Web page where you can download and install free Visual Studio 2015 Community? + + + Yes + + \ No newline at end of file diff --git a/src/SetupCustomActions/SetupCustomActions.csproj b/src/SetupCustomActions/SetupCustomActions.csproj new file mode 100644 index 000000000..88a25ed91 --- /dev/null +++ b/src/SetupCustomActions/SetupCustomActions.csproj @@ -0,0 +1,127 @@ + + + + + + + + Debug + x86 + 8.0.30703 + 2.0 + {F2149709-A88B-4F36-ABCA-307CA96E9FD1} + Library + Properties + SetupCustomActions + SetupCustomActions + v4.5.2 + 512 + SetupCustomActions + $(MSBuildExtensionsPath)\Microsoft\WiX\v3.x\Wix.CA.targets + + + $(RootDirectory)\obj\ + $(RootDirectory)\bin\ + $(BaseIntermediateOutputPath)\$(Configuration)\$(AssemblyName)\ + $(BaseOutputPath)\$(Configuration)\ + + + + + + + + + + $(WixInstallPath)\sdk\Microsoft.Deployment.WindowsInstaller.dll + True + + + + + Properties\GlobalAssemblyInfo.cs + + + R\RInstallation.cs + + + R\RInstallData.cs + + + R\RInstallStatus.cs + + + R\SupportedRVersionList.cs + + + + Form + + + DSProfilePromptForm.cs + + + Form + + + Form + + + YesNoMessageBox.cs + + + Form + + + + True + True + Resources.resx + + + + + + + DSProfilePromptForm.cs + + + YesNoMessageBox.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + Microsoft + StrongName + + + + + + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + \ No newline at end of file diff --git a/src/SetupCustomActions/YesNoMessageBox.Designer.cs b/src/SetupCustomActions/YesNoMessageBox.Designer.cs new file mode 100644 index 000000000..1caa806b2 --- /dev/null +++ b/src/SetupCustomActions/YesNoMessageBox.Designer.cs @@ -0,0 +1,87 @@ +namespace SetupCustomActions { + partial class YesNoMessageBox { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) { + if (disposing && (components != null)) { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() { + this.buttonYes = new System.Windows.Forms.Button(); + this.buttonNo = new System.Windows.Forms.Button(); + this.messageText = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // buttonYes + // + this.buttonYes.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.buttonYes.Location = new System.Drawing.Point(42, 103); + this.buttonYes.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); + this.buttonYes.Name = "buttonYes"; + this.buttonYes.Size = new System.Drawing.Size(87, 30); + this.buttonYes.TabIndex = 0; + this.buttonYes.Text = "&Yes"; + this.buttonYes.UseVisualStyleBackColor = true; + this.buttonYes.Click += new System.EventHandler(this.buttonYes_Click); + // + // buttonNo + // + this.buttonNo.FlatStyle = System.Windows.Forms.FlatStyle.Flat; + this.buttonNo.Location = new System.Drawing.Point(210, 103); + this.buttonNo.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); + this.buttonNo.Name = "buttonNo"; + this.buttonNo.Size = new System.Drawing.Size(87, 30); + this.buttonNo.TabIndex = 1; + this.buttonNo.Text = "&No"; + this.buttonNo.UseVisualStyleBackColor = true; + this.buttonNo.Click += new System.EventHandler(this.buttonNo_Click); + // + // messageText + // + this.messageText.Location = new System.Drawing.Point(12, 17); + this.messageText.Name = "messageText"; + this.messageText.Size = new System.Drawing.Size(323, 68); + this.messageText.TabIndex = 2; + this.messageText.Text = "Sample message"; + // + // YesNoMessageBox + // + this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.BackColor = System.Drawing.Color.Black; + this.ClientSize = new System.Drawing.Size(347, 154); + this.Controls.Add(this.messageText); + this.Controls.Add(this.buttonNo); + this.Controls.Add(this.buttonYes); + this.Font = new System.Drawing.Font("Segoe UI", 9.75F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); + this.ForeColor = System.Drawing.Color.Silver; + this.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); + this.Name = "YesNoMessageBox"; + this.Text = "R Tools for Visual Studio"; + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Button buttonYes; + private System.Windows.Forms.Button buttonNo; + private System.Windows.Forms.Label messageText; + } +} \ No newline at end of file diff --git a/src/SetupCustomActions/YesNoMessageBox.cs b/src/SetupCustomActions/YesNoMessageBox.cs new file mode 100644 index 000000000..30dfcabd8 --- /dev/null +++ b/src/SetupCustomActions/YesNoMessageBox.cs @@ -0,0 +1,23 @@ +using System; +using System.Windows.Forms; + +namespace SetupCustomActions { + public partial class YesNoMessageBox : Form { + public YesNoMessageBox(string messageText) { + InitializeComponent(); + this.messageText.Text = messageText; + this.TopMost = true; + this.CenterToScreen(); + } + + protected void buttonYes_Click(object sender, EventArgs e) { + this.DialogResult = DialogResult.Yes; + this.Close(); + } + + protected void buttonNo_Click(object sender, EventArgs e) { + this.DialogResult = DialogResult.No; + this.Close(); + } + } +} diff --git a/src/SetupCustomActions/YesNoMessageBox.resx b/src/SetupCustomActions/YesNoMessageBox.resx new file mode 100644 index 000000000..1af7de150 --- /dev/null +++ b/src/SetupCustomActions/YesNoMessageBox.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/UnitTests/Core/Impl/XUnit/Categories.cs b/src/UnitTests/Core/Impl/XUnit/Categories.cs index 7c8802571..7a9203ad8 100644 --- a/src/UnitTests/Core/Impl/XUnit/Categories.cs +++ b/src/UnitTests/Core/Impl/XUnit/Categories.cs @@ -38,6 +38,11 @@ public class PlotsAttribute : CategoryAttribute { public PlotsAttribute() : base("Plots") { } } + [ExcludeFromCodeCoverage] + public class TelemetryAttribute : CategoryAttribute { + public TelemetryAttribute() : base("Telemetry") { } + } + [ExcludeFromCodeCoverage] public static class Project { [ExcludeFromCodeCoverage] @@ -108,6 +113,12 @@ public class PackageAttribute : CategoryAttribute { public PackageAttribute() : base("R.Package") { } } + [ExcludeFromCodeCoverage] + public class DebuggerAttribute : CategoryAttribute { + public DebuggerAttribute() : base("R.Debugger") { } + } + + [ExcludeFromCodeCoverage] public class SettingsAttribute : CategoryAttribute { public SettingsAttribute() : base("R.Settings") {}