Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 7 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@

## What is .NET Interactive?

.NET Interactive is an engine that can run multiple languages and share variables between them. Languages currently supported include:
.NET Interactive is an engine and API for running code in multiple languages and coordinating among them, including support for sharing data and variables, running code and getting its results remotely, and evaluating code in order to provide language services such as completions and diagnostics. While typically associated with notebooks such as Jupyter and the Polyglot Notebooks extension for Visual Studio Code, .NET Interactive has other uses as well, such as building REPLs and script engines.

The languages currently supported by .NET Interactive include:

- C#
- F#
Expand All @@ -13,32 +15,21 @@
- HTML*
- [Mermaid](https://mermaid.js.org/intro/)*

*Variable sharing not available

## What can .NET Interactive be used for?

As a powerful and versatile engine, .NET Interactive can be used to create and power a number of tools and experiences such as:

- Polyglot Notebooks
- REPLs
- Embeddable script engines
(* _Variable sharing not available._)

### Polyglot Notebooks

Since .NET Interactive is capable of running as a kernel for notebooks, it enables a polyglot (multi-language) notebook experience. When using the .NET Interactive kernel, you can use different languages from one cell to the next, share variables between languages, and dynamically connect new languages and remote kernels within a notebook. There's no need to install different Jupyter kernels, use wrapper libraries, or install different tools to get the best experience for the language of your choice. You can always use the best language for the job and seamlessly transition between different stages of your workflow, all within one notebook.

For the best experience when working with multi-language notebooks, we recommend installing the [Polyglot Notebooks](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.dotnet-interactive-vscode) extension for Visual Studio Code. While the full .NET Interactive feature set is available in Jupyter, many features are only usable via code, whereas the Polyglot Notebooks extension provides additional features including a language/kernel picker for each cell, enhanced language services, a multi-kernel variable viewer, and more.

### Jupyter and nteract

There are several ways to get started using .NET Interactive with Jupyter, including Jupyter Notebook, JupyterLab, and nteract.

* [Create and run notebooks on your machine](docs/NotebookswithJupyter.md).
### Jupyter

The most popular notebook platform is Jupyter, and .NET Interactive is a fully supported Jupyter kernel that you can use with Jupyter Notebook, JupyterLab, nteract, and other Jupyter frontends. You can read more about using .NET Interactive as a Jupyter kernel [here](docs/NotebookswithJupyter.md).

### REPLs

.NET Interactive can be used as the execution engine for REPLs as well. The experimental [.NET REPL](https://github.com/jonsequitur/dotnet-repl) is one example of a command line REPL built on .NET Interactive. In addition, .NET REPL can actually be used to set up automation for your Polyglot Notebooks.
.NET Interactive can be used as the execution engine for REPLs as well. The experimental [.NET REPL](https://github.com/jonsequitur/dotnet-repl) is one example of a command line REPL built on .NET Interactive. In addition, .NET REPL can be used for automated command line execution of notebooks.

### Small factor devices

Expand Down
18 changes: 14 additions & 4 deletions docs/variable-sharing.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
# Variable sharing

.NET Interactive enables you to write code in multiple languages within a single notebook and in order to take advantage of those languages' different strengths, you might find it useful to share data between them. By default, .NET Interactive provides [subkernels](kernels-overview.md) for three different languages within the same process. You can share variables between .NET subkernels using the `#!share` magic command.
The .NET Interactive kernel enables you to write code in multiple languages within a single notebook. In order to take advantage of the different strengths of each language, you'll find it useful to share data between them. By default, .NET Interactive supports a number of different languages and most of them allow sharing using the `#!set` and `#!share` magic commands.

<img src="https://user-images.githubusercontent.com/547415/82468160-55d48c00-9a77-11ea-89f6-6b167d4cf8a2.png" width="40%">
> *&nbsp;_The `#!share` magic command has been in place since the earliest days of .NET Interactive while `#!set` is newer and provides a superset of the capabilities of `#!share`._

Variables are shared by reference for reference types. A consequence of this is that if you share a mutable object, changes to its state will be visible across subkernels:





## Sharing by reference

In certain specific cases, variables can be shared by reference for reference types. This comes with a number of caveats.

* The source and destination kernels must be running in the same process.

One consequence of this is that if you share a mutable object, changes to its state will be visible across subkernels:

<img src="https://user-images.githubusercontent.com/547415/82737074-009cb280-9ce3-11ea-82a2-8ef509cb7122.png" width="40%">

Expand All @@ -26,7 +37,6 @@ If the data you want to read into your notebook is stored in a file, you can use

<img src="https://user-images.githubusercontent.com/547415/89600459-fdf82680-d816-11ea-8ba6-1d5ec4e2a7e7.png" width="40%">


### 3. From a URL

You can pull data into your notebook from a URL as well, using the `--from-url` option.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -672,7 +672,6 @@ public async Task RequestCompletions_prevents_RequestDiagnostics_from_producing_

var requestCompletionsCommand = new RequestCompletions(output, new LinePosition(line, column));

// var results = await Task.WhenAll(
var diagnosticsResultTask = kernel.SendAsync(requestDiagnosticsCommand);
await kernel.SendAsync(requestCompletionsCommand);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

using System;
using System.Collections.Generic;
using System.CommandLine;
using System.CommandLine.NamingConventionBinder;
Expand Down Expand Up @@ -309,6 +310,28 @@ public async Task Share_suggests_kernel_names()
.Contain(new[] { "fsharp", "pwsh" });
}

[Fact]
public async Task Set_suggests_kernel_qualified_variable_name()
{
var kernel = CreateCompositeKernel();
await kernel.SubmitCodeAsync("var x = 123;");

var shareFrom = "#!set --name y --value ";

var result = await kernel
.FindKernelByName("fsharp")
.SendAsync(new RequestCompletions(shareFrom, new LinePosition(0, shareFrom.Length)));

result.Events
.Should()
.ContainSingle<CompletionsProduced>()
.Which
.Completions
.Select(i => i.DisplayText)
.Should()
.Contain(new[] { "@csharp:x" });
}

[Fact]
public async Task Share_suggests_variable_names()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ public static IKernelCommandEnvelope Deserialize(JsonElement json)
}

var command = (KernelCommand)JsonSerializer.Deserialize(commandJson, commandType, Serializer.JsonSerializerOptions);

if (commandId is not null)
{
command.SetId(commandId);
Expand Down
53 changes: 42 additions & 11 deletions src/Microsoft.DotNet.Interactive/KernelExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -272,38 +272,69 @@ public static T UseValueSharing<T>(this T kernel) where T : Kernel

private static void ConfigureAndAddSetMagicCommand<T>(T kernel) where T : Kernel
{
var fromValueOption = new Option<string>(
var valueOption = new Option<string>(
"--value",
description: "Specifies a value to be stored directly. Specifying @input:value allows you to prompt the user for this value.")
description:
"The value to be set. @input:user_prompt allows you to prompt the user for this value. Values can be requested from other kernels by name, for example @csharp:variableName.")
{
IsRequired = true
IsRequired = true,
ArgumentHelpName = "@source:sourceValueName"
};

var mimeTypeOption = new Option<string>("--mime-type", "Share the value as a string formatted to the specified MIME type.")
valueOption.AddCompletions(_ =>
{
// FIX: (ConfigureAndAddSetMagicCommand) why is this getting invoked this option wasn't specified?

if (kernel.ParentKernel is { } composite)
{
var getValueTasks = composite.ChildKernels
.Where(
k => k != kernel &&
k.KernelInfo.SupportsCommand(nameof(RequestValueInfos)))
.Select(async k => await k.SendAsync(new RequestValueInfos()));

var tasks = Task.WhenAll(getValueTasks).GetAwaiter().GetResult();

return tasks
.Select(t => t.Events.OfType<ValueInfosProduced>())
.SelectMany(events => events.Select(e => new { e.Command.TargetKernelName, e.ValueInfos }))
.SelectMany(x => x.ValueInfos.Select(i => $"@{x.TargetKernelName}:{i.Name}"))
.OrderBy(x => x)
.Select(n => new CompletionItem(n))
.ToArray();
}

return Array.Empty<CompletionItem>();
});

var mimeTypeOption = new Option<string>(
"--mime-type",
"The MIME type by which the value should be represented. This will often determine how an object will be formatted into a string.")
{
ArgumentHelpName = "MIME-TYPE"
}
.AddCompletions(
JsonFormatter.MimeType,
HtmlFormatter.MimeType,
PlainTextFormatter.MimeType);

var nameOption = new Option<string>(
"--name",
description: "This is the name used to declare and set the value in the kernel."

)
description: "The name of the value")
{
IsRequired = true
};

var set = new Command("#!set")
var set = new Command("#!set", "Sets a value in the current kernel")
{
nameOption,
fromValueOption,
valueOption,
mimeTypeOption
};

set.SetHandler(cmdLineContext =>
{
HandleSetMagicCommand(kernel, cmdLineContext, nameOption, fromValueOption, mimeTypeOption);
HandleSetMagicCommand(kernel, cmdLineContext, nameOption, valueOption, mimeTypeOption);
});

kernel.AddDirective(set);
Expand All @@ -313,7 +344,7 @@ private static void ConfigureAndAddShareMagicCommand<T>(T kernel) where T : Kern
{
var sourceValueNameArg = new Argument<string>(
"name",
"The name of the value to share. (This is also the default name value created in the destination kernel, unless --as is used to specify a different one.)");
"The name of the value to share. (This is also the default name of the value created in the destination kernel, unless --as is used to specify a different one.)");

sourceValueNameArg.AddCompletions(_ =>
{
Expand Down
2 changes: 1 addition & 1 deletion src/polyglot-notebooks-vscode-common/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ export async function activate(context: vscode.ExtensionContext) {
value = (requestInput.inputTypeHint === "file")
? await vscode.window.showOpenDialog({ canSelectFiles: true, canSelectFolders: false, title: prompt, canSelectMany: false })
.then(v => typeof v?.[0].fsPath === 'undefined' ? null : v[0].fsPath)
: await vscode.window.showInputBox({ prompt, password });
: await vscode.window.showInputBox({ prompt, password, ignoreFocusOut: true });
}

if (!value) {
Expand Down