Skip to content
This repository has been archived by the owner on Oct 12, 2022. It is now read-only.

Commit

Permalink
Merge pull request #57 from AlexanderSher/Feature/ReplFixes
Browse files Browse the repository at this point in the history
Added support for Interactive Window Reset button, "q()" command and accidentally terminated RHost process
  • Loading branch information
AlexanderSher committed Sep 29, 2015
2 parents fa59439 + 95c7934 commit af82ecc
Show file tree
Hide file tree
Showing 21 changed files with 373 additions and 151 deletions.
11 changes: 6 additions & 5 deletions src/Host/Client/Impl/IRCallbacks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@ public interface IRCallbacks
{
Task Connected(string rVersion);
Task Disconnected();
Task<string> ReadConsole(IReadOnlyCollection<IRContext> contexts, IRExpressionEvaluator evaluator, string prompt, string buf, int len, bool addToHistory);
Task WriteConsoleEx(IReadOnlyCollection<IRContext> contexts, IRExpressionEvaluator evaluator, string buf, OutputType otype);
Task ShowMessage(IReadOnlyCollection<IRContext> contexts, IRExpressionEvaluator evaluator, string s);
Task<YesNoCancel> YesNoCancel(IReadOnlyCollection<IRContext> contexts, IRExpressionEvaluator evaluator, string s);
Task Busy(IReadOnlyCollection<IRContext> contexts, IRExpressionEvaluator evaluator, bool which);
Task<string> ReadConsole(IReadOnlyCollection<IRContext> contexts, string prompt, string buf, int len, bool addToHistory);
Task WriteConsoleEx(IReadOnlyCollection<IRContext> contexts, string buf, OutputType otype);
Task ShowMessage(IReadOnlyCollection<IRContext> contexts, string s);
Task<YesNoCancel> YesNoCancel(IReadOnlyCollection<IRContext> contexts, string s);
Task Busy(IReadOnlyCollection<IRContext> contexts, bool which);
Task Evaluate(IReadOnlyCollection<IRContext> contexts, IRExpressionEvaluator evaluator);
}
}
18 changes: 9 additions & 9 deletions src/Host/Client/Impl/IRExpressionEvaluator.cs
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Globalization;
using System.Text;
using System.Threading.Tasks;
using Microsoft.Common.Core;

namespace Microsoft.R.Host.Client {
public interface IRExpressionEvaluator {
namespace Microsoft.R.Host.Client
{
public interface IRExpressionEvaluator
{
Task<REvaluationResult> EvaluateAsync(string expression);
}

public enum RParseStatus {
public enum RParseStatus
{
Null,
OK,
Incomplete,
Error,
EOF
}

public struct REvaluationResult {
public struct REvaluationResult
{
public string Result { get; }
public string Error { get; }
public RParseStatus ParseStatus { get; }
Expand Down
6 changes: 5 additions & 1 deletion src/Host/Client/Impl/IRSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,14 @@ public interface IRSession : IDisposable
event EventHandler<RBeforeRequestEventArgs> BeforeRequest;
event EventHandler<RResponseEventArgs> Response;
event EventHandler<RErrorEventArgs> Error;
event EventHandler<EventArgs> Disconnected;

string Prompt { get; }
bool HostIsRunning { get; }

Task<IRSessionInteraction> BeginInteractionAsync(bool isVisible = true);
Task InitializeAsync();
Task<IRSessionEvaluation> BeginEvaluationAsync();
Task StartHostAsync();
Task StopHostAsync();
}
}
12 changes: 12 additions & 0 deletions src/Host/Client/Impl/IRSessionEvaluation.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Microsoft.R.Host.Client
{
public interface IRSessionEvaluation : IDisposable
{
IReadOnlyCollection<IRContext> Contexts { get; }
Task<REvaluationResult> EvaluateAsync(string expression);
}
}
5 changes: 3 additions & 2 deletions src/Host/Client/Impl/IRSessionInteraction.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace Microsoft.R.Host.Client
{
public interface IRSessionInteraction
public interface IRSessionInteraction : IDisposable
{
string Prompt { get; }
int MaxLength { get; }
Expand Down
1 change: 1 addition & 0 deletions src/Host/Client/Impl/Microsoft.R.Host.Client.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@
<Compile Include="IRCallbacks.cs" />
<Compile Include="IRExpressionEvaluator.cs" />
<Compile Include="IRSession.cs" />
<Compile Include="IRSessionEvaluation.cs" />
<Compile Include="IRSessionProvider.cs" />
<Compile Include="IRSessionInteraction.cs" />
<Compile Include="MicrosoftRHostMissingException.cs" />
Expand Down
30 changes: 19 additions & 11 deletions src/Host/Client/Impl/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Microsoft.R.Host.Client {
class Program : IRCallbacks {
private IRExpressionEvaluator _evaluator;

static void Main() {
var host = new RHost(new Program());
host.CreateAndRun().GetAwaiter().GetResult();
Expand All @@ -12,36 +14,42 @@ class Program : IRCallbacks {
public void Dispose() {
}

public Task Busy(IReadOnlyCollection<IRContext> contexts, IRExpressionEvaluator evaluator, bool which) {
public Task Busy(IReadOnlyCollection<IRContext> contexts, bool which) {
return Task.FromResult(true);
}

public Task Evaluate(IReadOnlyCollection<IRContext> contexts, IRExpressionEvaluator evaluator)
{
_evaluator = evaluator;
return Task.CompletedTask;
}

public Task Connected(string rVersion) {
return Task.FromResult(true);
return Task.CompletedTask;
}

public Task Disconnected() {
return Task.FromResult(true);
return Task.CompletedTask;
}

public async Task<string> ReadConsole(IReadOnlyCollection<IRContext> contexts, IRExpressionEvaluator evaluator, string prompt, string buf, int len, bool addToHistory) {
return (await ReadLineAsync(prompt, evaluator)) + "\n";
public async Task<string> ReadConsole(IReadOnlyCollection<IRContext> contexts, string prompt, string buf, int len, bool addToHistory) {
return (await ReadLineAsync(prompt)) + "\n";
}

public async Task ShowMessage(IReadOnlyCollection<IRContext> contexts, IRExpressionEvaluator evaluator, string s) {
public async Task ShowMessage(IReadOnlyCollection<IRContext> contexts, string s) {
await Console.Error.WriteLineAsync(s);
}

public async Task WriteConsoleEx(IReadOnlyCollection<IRContext> contexts, IRExpressionEvaluator evaluator, string buf, OutputType otype) {
public async Task WriteConsoleEx(IReadOnlyCollection<IRContext> contexts, string buf, OutputType otype) {
var writer = otype == OutputType.Output ? Console.Out : Console.Error;
await writer.WriteAsync(buf);
}

public async Task<YesNoCancel> YesNoCancel(IReadOnlyCollection<IRContext> contexts, IRExpressionEvaluator evaluator, string s) {
public async Task<YesNoCancel> YesNoCancel(IReadOnlyCollection<IRContext> contexts, string s) {
await Console.Error.WriteAsync(s);
while (true)
{
string r = await ReadLineAsync(" [yes/no/cancel]> ", evaluator);
string r = await ReadLineAsync(" [yes/no/cancel]> ");

if (r.StartsWith("y", StringComparison.InvariantCultureIgnoreCase))
{
Expand All @@ -60,7 +68,7 @@ class Program : IRCallbacks {
}
}

private async Task<string> ReadLineAsync(string prompt, IRExpressionEvaluator evaluator) {
private async Task<string> ReadLineAsync(string prompt) {
while (true) {
await Console.Out.WriteAsync(prompt);
string s = await Console.In.ReadLineAsync();
Expand All @@ -69,7 +77,7 @@ class Program : IRCallbacks {
s = s.Remove(0, 1);
} else if (s.StartsWith("=", StringComparison.OrdinalIgnoreCase)) {
s = s.Remove(0, 1);
var er = await evaluator.EvaluateAsync(s);
var er = await _evaluator.EvaluateAsync(s);
await Console.Out.WriteLineAsync(er.ToString());
continue;
}
Expand Down
15 changes: 15 additions & 0 deletions src/Host/Client/Impl/RContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,26 @@
{
internal class RContext : IRContext
{
protected bool Equals(RContext other)
{
return other != null && CallFlag == other.CallFlag;
}

public RContext(RContextType callFlag)
{
CallFlag = callFlag;
}

public RContextType CallFlag { get; }

public override bool Equals(object obj)
{
return Equals(obj as RContext);
}

public override int GetHashCode()
{
return (int)CallFlag;
}
}
}
91 changes: 51 additions & 40 deletions src/Host/Client/Impl/RHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;

namespace Microsoft.R.Host.Client {
namespace Microsoft.R.Host.Client
{
public sealed class RHost : IDisposable
{
private class RExpressionEvaluator : IRExpressionEvaluator {
Expand Down Expand Up @@ -53,6 +54,7 @@ private class RExpressionEvaluator : IRExpressionEvaluator {
}

public const int DefaultPort = 5118;
public static IRContext TopLevelContext { get; } = new RContext(RContextType.TopLevel);

private readonly CancellationTokenSource _cts = new CancellationTokenSource();
private readonly IRCallbacks _callbacks;
Expand All @@ -63,8 +65,6 @@ public RHost(IRCallbacks callbacks)
_callbacks = callbacks;
}

public Process Process => _process;

public void Dispose()
{
_cts.Cancel();
Expand Down Expand Up @@ -137,68 +137,81 @@ public async Task AttachAndRun(Uri uri, CancellationToken ct = default(Cancellat
}
}

private async Task Run(WebSocket ws, CancellationToken ct = default(CancellationToken))
private async Task Run(WebSocket webSocket, CancellationToken ct)
{
var buffer = new byte[0x10000];

var wsrr = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), ct);
string s = Encoding.UTF8.GetString(buffer, 0, wsrr.Count);
var obj = JObject.Parse(s);
int protocolVersion = (int)(double)obj["protocol_version"];
Debug.Assert(protocolVersion == 1);
string rVersion = (string)obj["R_version"];
await _callbacks.Connected(rVersion);
try
{
var webSocketReceiveResult = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), ct);
string s = Encoding.UTF8.GetString(buffer, 0, webSocketReceiveResult.Count);
var obj = JObject.Parse(s);
int protocolVersion = (int) (double) obj["protocol_version"];
Debug.Assert(protocolVersion == 1);
string rVersion = (string) obj["R_version"];
await _callbacks.Connected(rVersion);

await RunLoop(webSocket, ct, buffer);
}
finally
{
await _callbacks.Disconnected();
}
}

private async Task RunLoop(WebSocket webSocket, CancellationToken ct, byte[] buffer)
{
for (bool done = false; !done && !ct.IsCancellationRequested;)
{
wsrr = await ws.ReceiveAsync(new ArraySegment<byte>(buffer), ct);
if (wsrr.CloseStatus != null)
var webSocketReceiveResult = await webSocket.ReceiveAsync(new ArraySegment<byte>(buffer), ct);
if (webSocketReceiveResult.CloseStatus != null)
{
break;
}

s = Encoding.UTF8.GetString(buffer, 0, wsrr.Count);
obj = JObject.Parse(s);
var s = Encoding.UTF8.GetString(buffer, 0, webSocketReceiveResult.Count);
var obj = JObject.Parse(s);

var contexts = GetContexts(obj);
var evaluator = new RExpressionEvaluator(ws, buffer, ct);
var evaluator = new RExpressionEvaluator(webSocket, buffer, ct);

var evt = (string)obj["event"];
var evt = (string) obj["event"];
string response = null;

await _callbacks.Evaluate(contexts, evaluator);

switch (evt)
{
case "YesNoCancel":
{
YesNoCancel input = await _callbacks.YesNoCancel(contexts, evaluator, (string)obj["s"]);
response = JsonConvert.SerializeObject((double)input);
break;
}
{
YesNoCancel input = await _callbacks.YesNoCancel(contexts, (string) obj["s"]);
response = JsonConvert.SerializeObject((double) input);
break;
}

case "ReadConsole":
{
string input = await _callbacks.ReadConsole(
contexts,
evaluator,
(string)obj["prompt"],
(string)obj["buf"],
(int)(double)obj["len"],
(bool)obj["addToHistory"]);
input = input.Replace("\r\n", "\n");
response = JsonConvert.SerializeObject(input);
break;
}
{
string input = await _callbacks.ReadConsole(
contexts,
(string) obj["prompt"],
(string) obj["buf"],
(int) (double) obj["len"],
(bool) obj["addToHistory"]);
input = input.Replace("\r\n", "\n");
response = JsonConvert.SerializeObject(input);
break;
}

case "WriteConsoleEx":
await _callbacks.WriteConsoleEx(contexts, evaluator, (string)obj["buf"], (OutputType)(double)obj["otype"]);
await _callbacks.WriteConsoleEx(contexts, (string) obj["buf"], (OutputType) (double) obj["otype"]);
break;

case "ShowMessage":
await _callbacks.ShowMessage(contexts, evaluator, (string)obj["s"]);
await _callbacks.ShowMessage(contexts, (string) obj["s"]);
break;

case "Busy":
await _callbacks.Busy(contexts, evaluator, (bool)obj["which"]);
await _callbacks.Busy(contexts, (bool) obj["which"]);
break;

case "CallBack":
Expand All @@ -215,11 +228,9 @@ private async Task Run(WebSocket ws, CancellationToken ct = default(Cancellation
if (response != null)
{
int count = Encoding.UTF8.GetBytes(response, 0, response.Length, buffer, 0);
await ws.SendAsync(new ArraySegment<byte>(buffer, 0, count), WebSocketMessageType.Text, true, ct);
await webSocket.SendAsync(new ArraySegment<byte>(buffer, 0, count), WebSocketMessageType.Text, true, ct);
}
}

await _callbacks.Disconnected();
}

private static RContext[] GetContexts(JObject obj)
Expand Down
5 changes: 5 additions & 0 deletions src/Package/Impl/Microsoft.VisualStudio.R.Package.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@
<Compile Include="Repl\Commands\ReplShortcutSetting.cs" />
<Compile Include="Repl\ReplWindow.cs" />
<Compile Include="Repl\Session\RSession.cs" />
<Compile Include="Repl\Session\RSessionCommands.cs" />
<Compile Include="Repl\Session\RSessionEvaluation.cs" />
<Compile Include="Repl\Session\RSessionEvaluationSource.cs" />
<Compile Include="Repl\Session\RSessionInteraction.cs" />
<Compile Include="Repl\Session\RSessionRequestSource.cs" />
<Compile Include="Repl\Commands\SendToReplCommand.cs" />
Expand Down Expand Up @@ -189,6 +192,7 @@
<EmbeddedResource Include="Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<EmbeddedResource Include="VSPackage.resx">
<MergeWithCTO>true</MergeWithCTO>
Expand Down Expand Up @@ -496,6 +500,7 @@
<VSIXSourceItem Include="$(OutputPath)\ProjectTemplates\**\*.*">
<VSIXSubPath>ProjectTemplates</VSIXSubPath>
</VSIXSourceItem>
<VSIXSourceItem Include="$(OutputPath)\Microsoft.R.Host.exe" />
</ItemGroup>
</Target>
<Target Name="AfterBuild">
Expand Down

0 comments on commit af82ecc

Please sign in to comment.