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