dotnet / interactive

.NET Interactive combines the power of .NET with many other languages to create notebooks, REPLs, and embedded coding experiences. Share code, explore data, write, and learn across your apps in ways you couldn't before.
MIT License
2.79k stars 371 forks source link

Auto-recall for input prompts #3323

Open jonsequitur opened 7 months ago

jonsequitur commented 7 months ago

The @input and @password prompts today don’t have a way to recall values that were entered on past runs. Adding a gesture to instruct the prompt to save its value could be a helpful ergonomic improvement even for values that aren’t secrets. For example, many tutorial notebooks ask for a file path on disk, and for any given user, that file path is probably not going to change from run to run. Restarting the kernel is a common notebook interaction and this flushes the in-memory prompt results, requiring the user to re-enter them. Enabling the prompt to check for a remembered response and re-use it without re-issuing the prompt to the user could help make many notebook workflows faster and smoother.

There are several ways we could approach implementing this.

Most inputs stored this way would be stored in the user’s profile as plain text files (likely containing JSON), with the exception of values ingested by the @password token. The current best candidate for storing these would be the PowerShell SecretManagement and SecretStore modules, but other possibilities can be evaluated.

Matching could be done based on the complete text of a given magic command. So, for example, the @input prompts for each of these two examples would never access a value stored by the other:

#!one --tenant-id @input:"Enter your tenant id" 
#!two --tenant-id @input:"Enter your tenant id"

There are a few different UX flows that could work for storage and recall:

• The user could be prompted to store the value. • The user could be prompted to recall the value. • There could be a way to make storage and recall the default behavior in order to further reduce redundant interactions. (For example, there could be a separate magic command that sets this preference at the notebook level.)

marckruzik commented 7 months ago

It would be interesting for the developer to be able to set a default or suggested value, especially for input. Something like: #!value --from-value @input:"please enter a filename" --default "readme.txt" This way, the prompt already contains a default value, and you can just validate the prompt by pushing Enter.

This behavior already exists with a lot of dialog windows, such as FileDialog.FileName, to provide a default file name. Those default values would be superseded by the behavior described in the first post.

I'm talking about that now, as it is close to the subject. Please tell me if this suggestion needs its own suggestion post.

jonsequitur commented 7 months ago

@marckruzik, this is a very good suggestion and I think it's fine to discuss here.

The syntax would need to be a little different from your example, I think, since --default doesn't clearly apply to a specific input, and multiple inputs can be specified in a single magic command. Something more like this could work and leave the path open for additional parameters (thouhg I don't love the ergonomics):

#!value --from-value @input:"please enter a filename",default="readme.txt"

The syntax should also consider the possibility of incorporating type hints and widgets.

marckruzik commented 7 months ago

Do you have an example of multiple inputs in a single magic command? I looked at the code and cannot find any example in the tests. I would like to have a better idea of the different uses to suggest something.

For now, if we are free to choose the syntax, I am thinking about the following (not including widgets as I don't know them).

The array of strings-like

@input:["please enter a filename","readme.txt","file"]

It will work only for strings, and relies too heavily on the order.

The anonymous object-like

@input:{prompt="please enter a filename", default="readme.txt", typeHint="file"}

Better than an array, and closer to an anonymous object.

The method-like

@input(prompt:"please enter a filename", default:"readme.txt", typeHint:"file")

Close to named parameters.

About the complex syntax

Of course, we keep the simple syntax @input:"please enter a filename". But when something like @input needs multiple parameters, we should allow another syntax close to named parameters, which allows as many parameters as we need, and is robust thanks to the naming of arguments.

jonsequitur commented 7 months ago

These are all good suggestions and more readable that what I've sketched so far, which looks like this:

#!test @input:"Please enter a value for blah",save,type=file,name=blah  

What's driving this more compressed syntax is that the parser for magic commands (System.CommandLine) follows command line grammar rules, so spaces (unless surrounded by quotes) delineate different arguments. The syntax I've proposed avoids that. But I'm not really satisfied with it.

We're considering a significant change to the grammar here to depart from command line limitations. This would open the door to more readable suggestions like yours as well as the possibility of better support for completions and other language services.

jonsequitur commented 7 months ago

Do you have an example of multiple inputs in a single magic command?

Take a look at #3194.

The idea would be that when there are multiple inputs in a single magic command, to present a form instead of a series of one-at-a-time VS Code text boxes. The type of inputs presented would lean on the type hint system and (as you already showed in your own examples) require a way to specify a type hint for each given @input.

marckruzik commented 6 months ago

Take a look at #3194.

Ok I see.

the parser for magic commands (System.CommandLine) follows command line grammar rules

I just found this post from you talking about the issue. I think this issue is far more bigger than Polyglot, it's more related to CommandLine, and even to all CLI systems.

I already encountered this problem several times, and did not found a library or any standard to answer it. I tested several things over the years, but the easier way I designed was to use a one-line JSON as a string, that I can give as a parameter and parse in the software.

MySoftware.exe --myJson="{\"prompt\":\"please enter a filename\", \"default\":\"readme.txt\", \"typeHint\":\"file\"}"

Quote escaping can be tricky, so sometimes I replaced double quotes by other symbols, such as <> or @, or just simple quotes ' (mainly for html).

I also sometimes put "the parameter" into a configuration file. Here is an example from a Windows batch:

echo {"prompt":"please enter a filename", "default":"readme.txt", "typeHint":"file"} > configuration.json

Some of my work included ways for a web browser to communicate with local executables, so I used a lot of ways for data to work with both a CLI and a browser, and looking the same way to facilitate development.

jonsequitur commented 6 months ago

I just found https://github.com/dotnet/command-line-api/issues/37#issuecomment-574879829 talking about the issue.

System.CommandLine's custom parse delegates solved this issue.

But the ability to have whitespace not be treated as a delimiter between arguments/options is something that could work nicely for magic commands, and can't work for CLI parsers. We're looking into a new magic command parser more tailored to this use case, and what grammar we might prefer in that case.

jonsequitur commented 6 months ago

Another possibility would be to support directly inlining JSON into the magic command in these cases:

@input:{ "prompt":"please enter a filename", "default":"readme.txt", "type":"file", "save": true }
marckruzik commented 6 months ago

I agree inline JSON would be an interesting idea. JSON configuration is widely used in a lot of popular web libraries, such as chartjs. Here is a JSON configuration example.

As a later development, if JSON inlining becomes a thing, I would be interested to use #!value --from-url to get JSON, inline it, and feed it to a magic command. It would make it easier to use complex magic commands, such as a database connection using multiple properties.

jonsequitur commented 2 weeks ago

This work depends on #3567.