Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

draft of how to enable shell integration with commands #348

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
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
287 changes: 287 additions & 0 deletions Draft-Accepted/RFCXXXX-Command-Shell-Integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
---
RFC: RFCNNNN
Author: Steve Lee
Status: Draft
SupercededBy: NA
Version: 1.0
Area: Interactive Shell
Comments Due: June 1, 2023
Plan to implement: Yes
---

# Command Shell Integration

Executables can provide a more integrated experience with a particular shell by indicating features it supports which the shell can leverage.

Shell integration feature areas:

- Structured output and formatting
- Tab completion
- Help
- Predictors (PowerShell)
- Feedback Providers (PowerShell)

Traditionally, POSIX shells have pre-defined filesystem locations where tools would put their tab completion scripts or help content.
However, this typically works for the system and not for a particular user who doesn't have administrative rights to the system.
SteveL-MSFT marked this conversation as resolved.
Show resolved Hide resolved
This can also make it more complicated to uninstall tools that place files across the filesystem.

Although the examples here are focused on PowerShell, the design is intended to be generic and can be used by other shells.

## Motivation

As a tool developer,
I can easily integrate with a shell,
so that users are more productive having a consistent experience within that shell.

## Current Experience

Some tools that are executables and not modules/cmdlets have already reached out on how they can have better integration with PowerShell.
The current experience requires the user to know about the integration scripts and add them to their `$profile` so that it gets loaded.
This greatly decreases usage and thus motivation for tooling to produce integration scripts due to lack of discovery and also
manual effort by the user.

## Enhanced User Experience
SteveL-MSFT marked this conversation as resolved.
Show resolved Hide resolved

In these examples, we'll use a hypothetical version of `az` (Azure CLI) that implements all of the shell integration features.
The user would simply install the tool and it would automatically be integrated with the shell.

### Structured Output with Formatting

```powershell
az group list
```

Current output:

```output
[
{
"id": "/subscriptions/11111111-1111-1111-1111-111111111111/resourceGroups/RG1",
"location": "westcentralus",
"managedBy": null,
"name": "RG1",
"properties": {
"provisioningState": "Succeeded"
},
"tags": {
"RequestID": "1111"
},
"type": "Microsoft.Resources/resourceGroups"
},
{
"id": "/subscriptions/11111111-1111-1111-1111-111111111111/resourceGroups/RG2",
"location": "westcentralus",
"managedBy": null,
"name": "RG2",
"properties": {
"provisioningState": "Succeeded"
},
"tags": {
"RequestID": "1111"
},
"type": "Microsoft.Resources/resourceGroups"
}
]
```

Although in PowerShell, this output can be piped to `ConvertFrom-Json`, it's an additional step for every `az` command execution.
It would be preferrable to have the tool output structured data in a format that PowerShell can automatically convert to an object
and used similarly to a cmdlet.
SteveL-MSFT marked this conversation as resolved.
Show resolved Hide resolved

Proposed output with custom formatting showing the most relevant information as well as a nested property:

```output
id location name provisioningState
-- -------- ---- ----------
/subscriptions/11111111-1111-1111-1111-111111111111/resourceGroups/ERNetwork-PvtApp westcentralus RG1 Succeeded
/subscriptions/11111111-1111-1111-1111-111111111111/resourceGroups/ERNetwork-SVC westcentralus RG2 Succeeded
```

### Tab Completion

```powershell
az g<tab>
```

This should result in:

```powershell
az group
```

As the tab completer would automtically be discovered by PowerShell and used. In this example, there is only one subcommand starting with `g`.

### Help

```powershell
get-help az
```

The help content would preferrably be in markdown with advanced rendering available in the shell.
Normal help searches would also look within the help content defined by the tool.

### Predictors and Feedback Providers

PowerShell has an extensibility model for Predictors and Feedback Providers.
Typically, these are implemented as modules with a cmdlet to register them with the PowerShell engine.
In this case, the extension would still need to be managed code, but would be automatically discovered and loaded by the shell.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure what you are proposing here. It sounds like PowerShell already has an extension mechanism for predictors and other providers (I assume you mean ISubsystem and if so you should probably just say it), and that non-PowerShell shells should have a similar extension mechanism but not managed code?

Copy link
Collaborator

@StevenBucher98 StevenBucher98 May 23, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am also confused what is being proposed here. We ultimately want auto registration of particular feedback providers and predictors right? Is this something shell integration is going to do or how does this related to native commands

Since the user installed the tool, the expectation is they want to use it.
SteveL-MSFT marked this conversation as resolved.
Show resolved Hide resolved
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this statement is exclusive to tools that hope to register with feedbackprovider and or predictors?


Based on user feedback, there can be a configuration file to disable specific extensions.

## Specification

A JSON manifest file would be placed alongside the executable (or technically anywhere within `$env:PATH`) and discovered by the shell.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should make it clear that JSON file is for a individual command line tool. Is there a naming convention that must be followed? How is the manifest file distinguished from other possible json files?

Suggested change
A JSON manifest file would be placed alongside the executable (or technically anywhere within `$env:PATH`) and discovered by the shell.
A tool specific JSON manifest file would be placed alongside the tool executable (or technically anywhere within `$env:PATH`) and discovered by the shell.

All paths are relative to the location of the manifest file and must use the forward slash path separator.

### Command shell integration manifest

The JSON manifest file name would be `<name>.command.json`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe use the name <--->.shell.json

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This manifest is for the command and not the shell, though.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's for shell integration though -- its purpose is for a shell to use it.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe <>.shellintegration.json?

The `<name>` portion is only used to differentiate between multiple manifests and does not need to match the executable name,
but it is recommended to do so.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A JSON manifest file would be placed alongside the executable (or technically anywhere within $env:PATH)

If manifest files don't have to be placed alongside the executable, then the <name> part should be exactly the executable name, otherwise there is no easy way to tell if a xxx.command.json file corresponds to an executable. Trying to read the execuable key from every .command.json file along the way of searching in PATH would be too inefficient.

IMHO, the manifest file name should be <executable-name>.<optional-part-for-differentiating-multiple-manifest>.command.json.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your proposal seems reasonable. The key thing is that at runtime, the executable is from the manifest content and not the filename of the manifest. I don't know how often executable names collide, but it seems like it would be rare. A convention like: <executable>.<org/author>.command.json seems fine.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding this to update


```json
{
"$schema": "https://schemas.microsoft.com/commandshell/2023/05/01/command.json",
"executable": "az",
"version": "1.0.0"
}
```

The `$schema` includes the version of the manifest with the URL pointing to a published JSON schema.
The `executable` is the name of the executable that the manifest is for and expected to be found in `$env:PATH`
The `version` is the version of the manifest for the tool and only used for informational purposes.

### Structured Output and Custom Formatting Registration

Tools can indicate they can output [JSONLines](https://jsonlines.org/) by default and also provide custom formatting.

```json
{
"$schema": "https://schemas.microsoft.com/commandshell/2023/05/01/command.shellintegration.json",
"executable": "az",
"version": "1.0.0",
"structuredOutput": {
"default": "jsonlines"
}
}
```

If a tool indicates it can output structured data in a format supported by the shell,
the shell will set the env var `COMMAND_SHELL_STRUCTURED_OUTPUT` with the value `jsonlines` in this case
in the environment block of the process that is launched.
SteveL-MSFT marked this conversation as resolved.
Show resolved Hide resolved

Tools should check for this environment variable and output structured data in the format indicated if supported.

If the tool calls other executables, it is responsible for removing or propagating the environment variable which
can affect the behavior of child processes.

JSON output should include a `$typeNames` member which is an array of strings that indicate the type hierarchy of the object.
SteveL-MSFT marked this conversation as resolved.
Show resolved Hide resolved
PowerShell would use this as the same as the `TypeNames` member of a PSObject for formatting.
Future enhancement would support a JSON defined formatting schema that could be used by other shells.

### Tab Completion Registration

Within the same manifest, a tool can indicate it supports tab completion.

This can either be an executable with arguments.

Arguments can be built-in variables to provide context for the tab completion.

Currently supported variables:

- `{commandLine}` - The full command line that the user has typed so far.
- `{cursorPosition}` - The current cursor position within the command line, starting at 0 index.

```json
{
"$schema": "https://schemas.microsoft.com/commandshell/2023/05/01/command.shellintegration.json",
"executable": "az",
"version": "1.0.0",
"tabCompletion": {
"command": {
"executable": "az",
"arguments": [
"completion",
"powershell",
"--commandLine",
"{commandLine}",
"--cursorPosition",
"{cursorPosition}"
]
}
}
}
```

or a shell specific script:

```json
{
"$schema": "https://schemas.microsoft.com/commandshell/2023/05/01/command.shellintegration.json",
"executable": "az",
"version": "1.0.0",
"tabCompletion": {
SteveL-MSFT marked this conversation as resolved.
Show resolved Hide resolved
"script": {
"powershell": "az-completion.ps1",
SteveL-MSFT marked this conversation as resolved.
Show resolved Hide resolved
"bash": "az-completion.sh",
"zsh": "az-completion.zsh"
}
}
}
```

Multiple shells can be specified and up to the shell to determine which one to use.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Multiple shells can be specified and up to the shell to determine which one to use.
Multiple shells can be specified and it is up to the shell to determine which one to use.

Both a `command` and `script` can be specified, but only one of each and also up to the shell to determine which one to use.

### Help Registration

The same manifest may define a path to the help content:

```json
{
"$schema": "https://schemas.microsoft.com/commandshell/2023/05/01/command.shellintegration.json",
"executable": "az",
"version": "1.0.0",
"help": {
"tags": [
"azure",
"az"
],
"path": "./help/az-help.md"
}
}
```

Optional `tags` can be specified to help with searching for help content.

### Predictors and Feedback Providers Registration

Specifically for PowerShell, a command may also want to optionally register a Predictor or Feedback Provider.

```json
{
"$schema": "https://schemas.microsoft.com/commandshell/2023/05/01/command.shellintegration.json",
"executable": "az",
"version": "1.0.0",
"predictor": {
SteveL-MSFT marked this conversation as resolved.
Show resolved Hide resolved
"powershell": "az-predictor.dll"
},
"feedbackProvider": {
"powershell": "az-feedbackprovider.dll"
SteveL-MSFT marked this conversation as resolved.
Show resolved Hide resolved
}
}
```

Similar to `tabCompletion`, alternate shells can choose to adopt their own mechanism for registering these extensions
under their shell specific key.

## Alternate Proposals and Considerations

Users may decide they don't want to use a tools shell integration feature by selectively disabling it.
This is shell specific and not part of this proposal.

Given that JSON is typically deeply nested, it may be useful to have PowerShell render _list view_ as YAML instead of
the current flat format.
Additionally, have PowerShell detect and render YAML for a single object and a table for multiple objects, by default.
This would apply to all objects and not just those from a shell integrated tool.