rioj7 / command-variable

Visual Studio Code extension for variable substitution via ${command:commandID}
49 stars 9 forks source link

Command Variable

Visual Studio Code provides variable substitution to be used in launch.json and tasks.json.

One of the variables allows the result of a command to be used with the following syntax: ${command:commandID}

If a command or variable is almost what you need you can use the transform command to perform a regular expression find-replace of the result.

Not all commands are supported yet in the web extension version. Supported commands are marked with : (Web)

Some commands can store/remember the result to be retrieved later in the session.
If you want persistent storage have a look at the commandvariable.remember.persistent.file setting.

Table of contents

Commands

This extension provides a number of commands that give a result based on the current file or the workspace path or that produce a result based on arguments

We can give an extension command arguments with input variables, but for single numeric arguments putting the argument in the command name is simpler.

Usage

An example launch.json :

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Python: Module",
      "type": "python",
      "request": "launch",
      "console": "integratedTerminal",
      "module": "${command:extension.commandvariable.file.relativeFileDotsNoExtension}",
    }
  ]
}

You can use a Task to see the value of a variable substitution.

Settings

The following settings can only be defined in the User settings:

FileAsKey

The command extension.commandvariable.file.fileAsKey makes it possible to select a string based on part of a file path. The file path can be taken from the current active editor or by executing a command.

The keys of the args object are searched for in the path of the active file (directory separator is /).

If you have files with the same name use part of the full path to select the correct one like "/dir1/main.py" and "/dir2/main.py".

The args property can contain a few special keys:

The value strings may contain variables. If you use the variable ${selectedText} you have to embed the properties separator and filterSelection in the variable, example ${selectedText##separator=@@##filterSelection=index%3===1##}.

Example

{
  "version": "2.0.0",
  "tasks": [
    {
      "name": "Python: Current File",
      "type": "python",
      "request": "launch",
      "program": "${file}",
      "console": "integratedTerminal",
      "args" : ["${input:chooseArgs}"]
    }
  ],
  "inputs": [
    {
      "id": "chooseArgs",
      "type": "command",
      "command": "extension.commandvariable.file.fileAsKey",
      "args": {
        "calculation.py": "-n 4224",
        "client.py": "-i calc-out.yaml"
      }
    }
  ]
}

File Content

Sometimes you want to use the result of a shell script (batch file). Setting environment variables will not work because they modify only the child shell.

If you store the content in a file you can retrieve this with the extension.commandvariable.file.content command.

The content of the file is assumed to be encoded with UTF-8.

The supported arguments:

Can be used as variable ${fileContent:name}

With additional arguments it is possible to parse the content of the file types:

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "echo FileContent",
      "type": "shell",
      "command": "echo",
      "args": [
        "${input:fileContent}"
      ],
      "problemMatcher": []
    }
  ],
  "inputs": [
    {
      "id": "fileContent",
      "type": "command",
      "command": "extension.commandvariable.file.content",
      "args": {
        "fileName": "c:\\temp\\result.txt"
      }
    }
  ]
}

File Content Key Value pairs

If you have a file that contains key-value pairs and you want the value for a given key you can use the command extension.commandvariable.file.content.

The supported arguments:

Can be used as variable ${fileContent:name}

Key-Value files

A key-value file consists of lines that contain key-value pairs.

The file can contain comments and empty lines. A comment line starts with # or //. You can have whitespace before the comment characters.

A key-value pair is a line in the file that specifies the key and the value separated by a character. The supported separators are : and =. The line is split with the following regular expression: ^\s*([^:=]+)(?:[:=])(.*)

The non-capturing group (?:) is only needed in this Markdown file to prevent detection of a Markdown link.

Everything, after the starting whitespace, before the first separator is the key, everything after the separator is the value. You can have a separator character in the value. Only the first separator is important.

Example

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "echo FileContentKey",
      "type": "shell",
      "command": "echo",
      "args": [
        "${input:fileContentKey}"
      ],
      "problemMatcher": []
    }
  ],
  "inputs": [
    {
      "id": "fileContentKey",
      "type": "command",
      "command": "extension.commandvariable.file.content",
      "args": {
        "fileName": "${workspaceFolder}/key-values.txt",
        "key": "PLUGIN",
        "default": "special-plugin"
      }
    }
  ]
}

key-values.txt

// a few key values
PLUGIN=cool-pugin
THEME=new-school

File Content JSON Property

If you have a JSON file and you want the value for a given property you can use the command extension.commandvariable.file.content.

The supported arguments:

The JSON file can be an array and you can address the elements with: content[3]

Can be used as variable ${fileContent:name}

Example

You have a JSON configuration file in your workspace:

config.json

{
  "log": "foobar.log",
  "server1": {
    "port": 5011
  },
  "server2": {
    "port": 5023
  }
}

In your tasks.json you want to use the server1 port value.

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "echo Server1Port",
      "type": "shell",
      "command": "echo",
      "args": [
        "${input:configServer1Port}"
      ],
      "problemMatcher": []
    }
  ],
  "inputs": [
    {
      "id": "configServer1Port",
      "type": "command",
      "command": "extension.commandvariable.file.content",
      "args": {
        "fileName": "${workspaceFolder}/config.json",
        "json": "content.server1.port",
        "default": "4321",
        "keyRemember": "ServerPort"
      }
    }
  ]
}

File Content YAML Property

If you have a YAML file and you want the value for a given property you can use the command extension.commandvariable.file.content.

The supported arguments:

Can be used as variable ${fileContent:name}

See File Content JSON Property for examples.

File Content Multiple Key-Values/Properties

If the file contains multiple key-values or properties you want in your task or launch you can remember the picked file and use the same path in another extension.commandvariable.file.content use.

You have the following configuration files in your workspace:

server1-config.json

{
  "log": "foobar1.log",
  "server": {
    "port": 5011,
    "publicCryptKey": "01234abcd"
  }
}

server2-config.json

{
  "log": "foobar2.log",
  "server": {
    "port": 5023,
    "publicCryptKey": "9876zyxw"
  }
}

Use it in your tasks.json:

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "echo ServerPortAndCryptKey",
      "type": "shell",
      "command": "echo",
      "args": [
        "${input:configServerPort}",
        "${input:configServerCryptKey}",
        "${input:serverURL}"
      ],
      "problemMatcher": []
    }
  ],
  "inputs": [
    {
      "id": "configServerPort",
      "type": "command",
      "command": "extension.commandvariable.file.content",
      "args": {
        "fileName": "${pickFile:config}",
        "json": "content.server.port",
        "default": "4321",
        "keyRemember": "ServerPort",
        "pickFile": {
          "config": {
            "include": "**/*.json",
            "exclude": ".vscode/*.json",
            "keyRemember": "configFile"
          }
        }
      }
    },
    {
      "id": "configServerCryptKey",
      "type": "command",
      "command": "extension.commandvariable.file.content",
      "args": {
        "fileName": "${remember:configFile}",
        "json": "content.server.publicCryptKey"
      }
    },
    {
      "id": "serverURL",
      "type": "command",
      "command": "extension.commandvariable.transform",
      "args": { "text": "https://example.org:${remember:ServerPort}/" }
    }
  ]
}

File Content in Editor

If you want the (partial) result of an external program inserted in the editor you can use the command extension.commandvariable.file.contentInEditor. This command uses the same arguments as extension.commandvariable.file.content.

Most likely you want to call the program first to write the output to a file that you read and extract the parts you want. For this you can use the extension multi-command.

  1. Define a task that runs the external command
  2. Define a multi-command that calls the task and then extension.commandvariable.file.contentInEditor
  3. Define a key binding that calls the multi-command

Add to .vscode/tasks.json

    {
      "label": "get Timestamp",
      "type": "shell",
      "command": "echo timestamp=2021-04-01 12:34 >${workspaceFolder}/timequery.txt",
      "problemMatcher": []
    }

Add to .vscode/settings.json

  "multiCommand.commands": [
    {
      "command": "multiCommand.insertTimestamp",
      "interval": 500,
      "sequence": [
        { "command": "workbench.action.tasks.runTask",
          "args": "get Timestamp"
        },
        { "command": "extension.commandvariable.file.contentInEditor",
          "args": {
            "fileName": "${workspaceFolder}/timequery.txt",
            "key": "timestamp",
            "default": "Query failed"
          }
        }
      ]
    }
  ]

Add to keybindings.json

  {
    "key": "F1", // or any other key combo
    "command": "extension.multiCommand.execute",
    "args": { "command": "multiCommand.insertTimestamp" },
    "when": "editorTextFocus"
  }

Config Expression

If you have an array or object as configuration variable content (settings.json) and you want a particular element of the array or the value for a given object property you can use the command extension.commandvariable.config.expression.

Can be used to have a JavaScript expression containing variables.

The supported arguments:

If the configVariable is an array you can address the elements with: content[3]

If the configVariable is an object you can address a property with: content.inputDir

If the configVariable is a single data type (string, number, boolean) set the expression property to content

If you want the value of the configVariable as a JSON string don't set the expression property.

Any expression is allowed that does not have a function call. All arithmetic operators, comparison operators, ...

Can be used as variable: ${configExpression:name}

Example

You have the following variable in settings.json:

{
  "someExt.servers": {
    "log": "foobar.log",
    "server1": {
      "port": 5011
    },
    "server2": {
      "port": 5023
    }
  }
}

In your tasks.json you want to use the server1 port value.

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "echo Server1Port",
      "type": "shell",
      "command": "echo",
      "args": [
        "${input:configServer1Port}"
      ],
      "problemMatcher": []
    }
  ],
  "inputs": [
    {
      "id": "configServer1Port",
      "type": "command",
      "command": "extension.commandvariable.config.expression",
      "args": {
        "configVariable": "someExt.servers",
        "expression": "content.server1.port",
        "default": "4321",
        "keyRemember": "ServerPort"
      }
    }
  ]
}

If you want to select the server from a pick list you can change the inputs part:

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "echo Server1Port",
      "type": "shell",
      "command": "echo",
      "args": [
        "${input:configServerPort}"
      ],
      "problemMatcher": []
    }
  ],
  "inputs": [
    {
      "id": "configServerPort",
      "type": "command",
      "command": "extension.commandvariable.config.expression",
      "args": {
        "configVariable": "someExt.servers",
        "expression": "content.server${pickStringRemember:serverNr}.port",
        "pickStringRemember": {
          "serverNr": {
            "description": "Which server to use?",
            "options": [
              ["development", "1"],
              ["live", "2"]
            ]
          }
        },
        "default": "4321",
        "keyRemember": "ServerPort"
      }
    }
  ]
}

JavaScript Expression

The command extension.commandvariable.js.expression is an alias of extension.commandvariable.config.expression.

You can use it to perform an expression with variables.

Can be used as variable: ${jsExpression:name}

Pick File

See also:

If you want to pick a file and use it in your launch.json or tasks.json you can use the extension.commandvariable.file.pickFile command.

This command uses vscode.workspace.findFiles to get a list of files to show in a Quick Pick selection box.

Specify the start directory path with the fromWorkspace or fromFolder property.
The include Glob Pattern can contain a path relative to the start directory.

If you don't specify fromWorkspace or fromFolder the search will be done over all workspaces.

You can set the following properties to this command:

Example:

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "echo FilePick",
      "type": "shell",
      "command": "echo",
      "args": [
        "${input:filePick}"
      ],
      "problemMatcher": []
    }
  ],
  "inputs": [
    {
      "id": "filePick",
      "type": "command",
      "command": "extension.commandvariable.file.pickFile",
      "args": {
        "include": "**/*.{htm,html,xhtml}",
        "exclude": "**/{scratch,backup}/**"
      }
    }
  ]
}

If you want the directory name of the picked file but using forward slash (on Windows, see issue 47)

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "echo FilePick Dirname Forward Slash",
      "type": "shell",
      "command": "echo",
      "args": [
        "${input:filePickDirnameForwardSlash}"
      ],
      "problemMatcher": []
    }
  ],
  "inputs": [
    {
      "id": "filePickDirnameForwardSlash",
      "type": "command",
      "command": "extension.commandvariable.file.pickFile",
      "args": {
        "include": "**/*.{htm,html,xhtml}",
        "exclude": "**/{scratch,backup}/**",
        "transform": {
          "text": "${fileDirname}",
          "find": "\\\\",  // Reason for four '\': https://stackoverflow.com/a/4025505/2909854
          "replace": "/",
          "flags": "g"
        }
      }
    }
  ]
}

If your project contains file paths like:

testType1/LOG/testName1/src/test.c

and you only want to show the Type and the Name but return the full path use the following input element

    {
      "id": "filePickCTests",
      "type": "command",
      "command": "extension.commandvariable.file.pickFile",
      "args": {
        "display": "transform",
        "description": " Select one test to open",
        "include": "**/test.c",
        "labelTransform": {
          "text": "${relativeFile}",
          "apply": [
            {
              "find": "\\\\",
              "replace": "/",
              "flags": "g"
            },
            {
              "find": "(.*)/LOG/(.*)/src/.*",
              "replace": "$1/$2"
            }
          ]
        }
      }
    }

If using the same file paths as the previous example but you want to show and return the test type folders that have a LOG subdirectory

    {
      "id": "filePickCTests",
      "type": "command",
      "command": "extension.commandvariable.file.pickFile",
      "args": {
        "display": "transform",
        "ydisplay": "relativePath",
        "description": "[my_tests] Select one test to open it",
        "include": "my_tests/**/test.c",
        "labelTransform": "valueTransform",
        "valueTransform": {
          "text": "${relativeFile}",
          "apply": [
            {
              "find": "\\\\",
              "replace": "/",
              "flags": "g"
            },
            {
              "find": "(.*/LOG)/.*",
              "replace": "$1"
            }
          ]
        }
      }
    }

Open Dialog

See also: Pick File

If you want to select a file or directory/folder you can use the command: extension.commandvariable.file.openDialog. It uses the vscode.window.showOpenDialog function of the VSC API.

You can set the following properties to this command:

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "echo Open Dialog",
      "type": "shell",
      "command": "echo",
      "args": [
        "${input:openDialog}"
      ],
      "problemMatcher": []
    }
  ],
  "inputs": [
    {
      "id": "openDialog",
      "type": "command",
      "command": "extension.commandvariable.file.openDialog",
      "args": {
        "canSelect": "files",
        "defaultUri": "${workspaceFolder}",
        "filters": {
          "Images": ["png", "jpg"],
          "TypeScript": ["ts", "tsx"]
        }
      }
    }
  ]
}

Save Dialog

See also: Pick File

If you want to select a file to save some results (it can be a new file name) you can use the command: extension.commandvariable.file.saveDialog. It uses the vscode.window.showSaveDialog function of the VSC API.

You can set the following properties to this command:

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "echo Save Dialog",
      "type": "shell",
      "command": "echo",
      "args": [
        "${input:saveDialog}"
      ],
      "problemMatcher": []
    }
  ],
  "inputs": [
    {
      "id": "saveDialog",
      "type": "command",
      "command": "extension.commandvariable.file.saveDialog",
      "args": {
        "defaultUri": "${workspaceFolder}",
        "saveLabel": "Save",
        "filters": {
          "Images": ["png", "jpg"],
          "TypeScript": ["ts", "tsx"]
        }
      }
    }
  ]
}

number

If you want a different whole number (n ∈ ℤ) in your task or launch config each time you run you can use the command extension.commandvariable.number.

The configuration attributes need to be passed to the command in the args attribute.

The command has the following configuration attributes:

You can get the last value of a named number with the remember command or variable.
You must use a special key format: number-name

Sequence of numbers

The value of step determines the first value returned.

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "echo Number from sequence",
      "type": "shell",
      "command": "echo",
      "args": [
        "${input:numberSeq}"
      ],
      "problemMatcher": []
    }
  ],
  "inputs": [
    {
      "id": "numberSeq",
      "type": "command",
      "command": "extension.commandvariable.number",
      "args": {
        "name": "sequence",
        "range": [0, 20],
        "step": 3
      }
    }
  ]
}

Random number

If you want a random number but it must be unique compared to the previous n numbers you have to set the attribute uniqueCount.

The example is for debugging the Nios ii Embedded Design Suite:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "app",
      "type": "cppdbg",
      "request": "launch",
      "program": "${workspaceFolder}/app/app.elf",
      "stopAtEntry": true,
      "cwd": "${workspaceFolder}",
      "MIMode": "gdb",
      "miDebuggerServerAddress": "localhost:${input:randomPort}",
      "miDebuggerPath": "/home/me/intelFPGA/20.1/nios2eds/bin/gnu/H-x86_64-pc-linux-gnu/bin/nios2-elf-gdb",
      "debugServerPath": "/home/me/intelFPGA/20.1/quartus/bin/nios2-gdb-server",
      "debugServerArgs": "--tcpport ${input:rememberRandomPort} --reset-target --tcptimeout 5",
    }
  ],
  "inputs": [
    {
      "id": "randomPort",
      "type": "command",
      "command": "extension.commandvariable.number",
      "args": {
        "name": "randomPort",
        "range": [1500, 60000],
        "random": true,
        "uniqueCount": 10
      }
    },
    {
      "id": "rememberRandomPort",
      "type": "command",
      "command": "extension.commandvariable.remember",
      "args": { "key": "number-randomPort" }
    }
  ]
}

remember

It can be useful to store key-value pairs to be used later. The value of the key is remembered for this session of Visual Studio Code.

Some commands in this extension can store key-value pairs: pickStringRemember, promptStringRemember, file.content (json, key-value, yaml), file.pickFile.

The stored value is retrieved with a command or a variable. In the same task/launch config or in a different one, or in a keybinding.

The command extension.commandvariable.remember is used to retreive a value for a particular key or store key-value pair(s).

text is not a valid key. It is a property of a string manipulation object and used to determine if an object is a string manipulation object or an object with key-value pair(s).

The args property of this command is an object with the properties:

If you need to construct a new string with the value you can use the variable: ${remember:key}. This can only be used in args properties of commands in this extension. The inputs list of launch.json and tasks.json or in keybindings or extensions that call commands with arguments (Multi Command). You can modify the value with the transform command or the transform property.

If the stored value contains variables and you want them substituted you have to set the transform property. An empty object is enough.

{
  // .....
  "inputs": [
    {
      "id": "remember.path",
      "type": "command",
      "command": "extension.commandvariable.remember",
      "args": { "key": "path", "transform": { } }
    }
  ]
}

The default content of the remember store:

The command pickStringRemember also supports string manipulation objects.

The example is a bit contrived but it shows how you can store key-value pair(s) in a launch config or task without using a stored value, the result of the ${input:rememberConfig} is the empty string. This enables you to store values in a launch config to be used in a prelaunchTask in tasks.json.

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "DoSomething",
      "type": "shell",
      "command": "${config:python.pythonPath}${input:rememberConfig}",
      "args": [
        "my_script.py",
        "${input:remember.path}",
        "${input:remember.name}"
      ],
      "problemMatcher": []
    }
  ],
  "inputs": [
    {
      "id": "rememberConfig",
      "type": "command",
      "command": "extension.commandvariable.remember",
      "args": {
        "store": {"path":"server","name":"boya","user":"Mememe","option":"yeah"}
      }
    },
    {
      "id": "remember.path",
      "type": "command",
      "command": "extension.commandvariable.remember",
      "args": { "key": "path" }
    },
    {
      "id": "remember.name",
      "type": "command",
      "command": "extension.commandvariable.remember",
      "args": { "key": "name" }
    }
  ]
}

If you have picked a file, the key used is sourceFile, and you don't want the full file path you can get certain parts with the transform property:

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "DoSomething2",
      "type": "shell",
      "command": "${config:python.pythonPath}",
      "args": [
        "my_script2.py",
        "${input:remember.showWorkspace}",
        "${input:remember.showBasename}"
      ],
      "problemMatcher": []
    }
  ],
  "inputs": [
    {
      "id": "remember.showWorkspace",
      "type": "command",
      "command": "extension.commandvariable.remember",
      "args": { "key": "sourceFile", "transform": { "text": "${workspaceFolder}" } }
    },
    {
      "id": "remember.showBasename",
      "type": "command",
      "command": "extension.commandvariable.remember",
      "args": { "key": "sourceFile", "transform": { "text": "${fileBasename}" } }
    }
  ]
}

An example of a string manipulation object. If you have a remembered key buildArgs and want to add an argument to get a release build:

{
  "version": "2.0.0",
  "tasks": [
    // .....
  ],
  "inputs": [
    {
      "id": "addReleaseArgument",
      "type": "command",
      "command": "extension.commandvariable.remember",
      "args": {
        "store": {
          "buildArgs": {
            "text": "-c release",
            "action": "append",
            "delimiter": " "
          }
        }
      }
    }
  ]
}

pickStringRemember

The command extension.commandvariable.pickStringRemember look a lot like the Input variable pickString.

The configuration attributes need to be passed to the command in the args attribute.

The command has the following configuration attributes:

(Not in Web) The value string can contain variables, so you can add a pickFile or promptString or .... and use that result.
    ["pick directory", "${pickFile:someDir}"]

If you Escape the UI and a default property is given the UI is not marked as Escaped.
(Not in Web) If the default property contains variables that have a UI they can be Escaped and that will be remembered.

The name and label properties in options and optionGroups must be unique for this pickStringRemember.

dependsOn

The dependsOn property of a group or pick item is a valid JavaScript expression that has a boolean ([ true | false ]) result. The variables allowed in the expression are the names of items or groups.

The value of these name-variables is the selection count in the group (0 ... N) and for a named pick item it is 0 or 1 depending if it is picked.

The value of the name-variables is only calulated once. At the moment of accepting the pickString picked items. If an item in a group is picked but it dependsOn expression results in false the item is still counted in the group selection count. Otherwise the value of the name-variables change by evaluating dependsOn expressions that use the value of name-variables. Will that eventually converge to a stable situation in all cases?

In boolean expressions the number 0 is treated falsy and any other number is treated truthy.

If groupA has a dependsOn with referring to groupB that has a dependsOn on nameC you must include the dependsOn expression of groupB in the groupA's dependsOn expression:

"dependsOn": "((nameC) && groupB)"

() around single variables can be removed. In this example all ()'s can be removed.

Examples

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "Task 1",
      "type": "shell",
      "command": "dostuff1",
      "args": ["-p", "${input:pickPath}"]
    },
    {
      "label": "Task 2",
      "type": "shell",
      "command": "dostuff2",
      "args": ["-p", "${input:rememberPath}"]
    },
    {
      "label": "Do Task 1 and 2",
      "dependsOrder": "sequence",
      "dependsOn": ["Task 1", "Task 2"],
      "problemMatcher": []
    }
  ],
  "inputs": [
    {
      "id": "pickPath",
      "type": "command",
      "command": "extension.commandvariable.pickStringRemember",
      "args": {
        "key": "path",
        "options": [ "path/to/directory/A", "path/to/Z" ],
        "description": "Choose a path"
      }
    },
    {
      "id": "rememberPath",
      "type": "command",
      "command": "extension.commandvariable.remember",
      "args": { "key": "path" }
    }
  ]
}

An example of choosing a port number in a launch configuration:

{
  "version": "0.2.0",
  "configurations": [
    {
      "name": "Service1",
      "type": "python",
      "request": "attach",
      "connect": {
        "host": "127.0.0.1",
        "port": "${input:envType}"
      }
    }
  ],
  "inputs": [
    {
      "id": "envType",
      "type": "command",
      "command": "extension.commandvariable.pickStringRemember",
      "args": {
        "description": "Which env do you want to debug?",
        "options": [
          ["development", "5000"],
          ["staging", "5100"],
          ["live", "5200"]
        ],
        "default": "5000"
      }
    }
  ]
}

If you have additional options in a file:

{
  "version": "0.2.0",
  "configurations": [
    // see previous example
  ],
  "inputs": [
    {
      "id": "envType",
      "type": "command",
      "command": "extension.commandvariable.pickStringRemember",
      "args": {
        "description": "Which env do you want to debug?",
        "options": [
          ["development", "5000"],
          ["staging", "5100"],
          ["live", "5200"]
        ],
        "default": "5000",
        "fileName": "${workspaceFolder}/dynamic-env.txt",
        "pattern": {
          "regexp": "^\\s*(?!#)([^=]+?)\\s*=\\s*(?:(\\{.+\\})|(.+))$",
          "label": "$1",
          "json": "$2",
          "value": "$3"
        }
      }
    }
  ]
}

An example task that stores multiple values:

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "Do some project",
      "type": "process",
      "command": "echo",
      "args": [
        "${input:selectProject.path}",
        "${input:selectProject.name}",
        "${input:selectProject.link}",
        "${input:selectProject.anyOther}"
      ],
      "problemMatcher": []
    }
  ],
  "inputs": [
    {
      "id": "selectProject.path",
      "type": "command",
      "command": "extension.commandvariable.pickStringRemember",
      "args": {
        "key": "path",
        "options": [
          ["project1", {"path":"p1","name":"n1","link":"lnk1","anyOther":"any1"}],
          ["project2", {"path":"p2","name":"n2","link":"lnk2","anyOther":"any2"}]
         ],
        "description": "Pick a project"
      }
    },
    {
      "id": "selectProject.name",
      "type": "command",
      "command": "extension.commandvariable.remember",
      "args": { "key": "name" }
    },
    {
      "id": "selectProject.link",
      "type": "command",
      "command": "extension.commandvariable.remember",
      "args": { "key": "link" }
    },
    {
      "id": "selectProject.anyOther",
      "type": "command",
      "command": "extension.commandvariable.remember",
      "args": { "key": "anyOther" }
    }
  ]
}

Using a string manipulation object you can modify an existing variable:

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "Build project",
      "type": "process",
      "command": "build ${input:buildArgsConstruct}",
      "problemMatcher": []
    }
  ],
  "inputs": [
    {
      "id": "buildArgsConstruct",
      "type": "command",
      "command": "extension.commandvariable.pickStringRemember",
      "args": {
        "description": "Construct buildArgs:",
        "key": "__undefined",
        "options": [
          { "label": "Current value",
            "description": "${remember:buildArgs}",
            "value": "${remember:buildArgs}"
          },
          { "label": "reset", "value": { "buildArgs": "" } },
          { "label": "append: -c release",
            "value": {
              "buildArgs": {
                "text": "-c release",
                "action": "append",
                "delimiter": " "
              }
            }
          },
          { "label": "prepend: -path ${workspaceFolder}",
            "value": {
              "buildArgs": {
                "text": "-path ${workspaceFolder}",
                "action": "prepend",
                "delimiter": " "
              }
            }
          }
        ]
      }
    }
  ]
}

If we use "key": "__undefined" any selected option that uses a key-value pair(s) object will return undefined. This will abort the current task. The remember store is updated.

When you choose the option Current value pickStringRemember returns the value for a given key.


If you have a src directory with a lot of subdirs and you want to run cpplint on all or only on a subdir you can add a pickFile variable as the value of a pickString:

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "cpp lint",
      "type": "shell",
      "command": "cpplint ${input:selectDir}"
    }
  ],
  "inputs": [
    {
      "id": "selectDir",
      "type": "command",
      "command": "extension.commandvariable.pickStringRemember",
      "args": {
        "description": "Which directory to Lint for C++?",
        "options": [
          ["Use previous directory", "${remember:lintPath}"],
          ["All", "all"],
          ["Pick directory", "${pickFile:srcSubDir}"]
        ],
        "rememberTransformed": true,
        "key": "lintPath",
        "pickFile": {
          "srcSubDir": {
            "description": "Which directory?",
            "include": "src/**/*.{cpp,h}",
            "showDirs": true,
            "keyRemember": "srcSubDir"
          }
        }
      }
    }
  ]
}

We can also use the object variant of the options, this allows us to show resolved variables in the pick list.

Possibilities for the Use previous directory are:

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "cpp lint",
      "type": "shell",
      "command": "cpplint ${input:selectDir}"
    }
  ],
  "inputs": [
    {
      "id": "selectDir",
      "type": "command",
      "command": "extension.commandvariable.pickStringRemember",
      "args": {
        "description": "Which directory to Lint for C++?",
        "options": [
          { "label": "Use previous directory",
            "description": "${remember:lintPath}",
            "value": "${remember:lintPath}" },
          { "label": "All", "value": "all" },
          { "label": "Pick directory", "value": "${pickFile:srcSubDir}" }
        ],
        "rememberTransformed": true,
        "key": "lintPath",
        "pickFile": {
          "srcSubDir": {
            "description": "Which directory?",
            "include": "src/**/*.{cpp,h}",
            "showDirs": true,
            "keyRemember": "srcSubDir"
          }
        }
      }
    }
  ]
}

If you have a list of choices and you want to easy select the previous choice you can use the addLabelToTop property:

{
  "version": "2.0.0",
  "tasks": [
    .....
  ],
  "inputs": [
    {
      "id": "robot",
      "type": "command",
      "command": "extension.commandvariable.pickStringRemember",
      "args": {
        "key": "robot",
        "description": "Robot type:",
        "addLabelToTop": "${remember:robot}",
        "options": [
          "standard",
          "hero",
          "sentry"
        ]
      }
    }
  ]
}

If your labels are different from the value add an extra remember item:

{
  "version": "2.0.0",
  "tasks": [
    .....
  ],
  "inputs": [
    {
      "id": "robot",
      "type": "command",
      "command": "extension.commandvariable.pickStringRemember",
      "args": {
        "key": "robot",
        "description": "Robot type:",
        "addLabelToTop": "${remember:robotLabel}",
        "options": [
          {"label": "Option 1", "value": {"robot":"standard", "robotLabel":"Option 1"} },
          {"label": "Option 2", "value": {"robot":"hero", "robotLabel":"Option 2"} },
          {"label": "Option 3", "value": {"robot":"sentry", "robotLabel":"Option 3"} }
        ]
      }
    }
  ]
}

If you have a C++ build task that has many options and you sometimes have to select 1 or more items in a group:

The actual compile task is not shown. It depends on the used compiler. The task uses the variable: ${input:cpp-options-powerAI}

{
  "version": "2.0.0",
  "tasks": [
    .....
  ],
  "inputs": [
    {
      "id": "cpp-options-powerAI",
      "type": "command",
      "command": "extension.commandvariable.pickStringRemember",
      "args": {
        "description": "C++ build options",
        "key": "cpp-build-powerAI",
        "multiPick": true,
        "optionGroups": [
          {
            "label": "Debug / Release",
            "minCount": 1,
            "maxCount": 1,
            "options": [
              ["debug", "-g"],
              ["release", "-O2"]
            ]
          },
          {
            "label": "C++ standard",
            "minCount": 1,
            "maxCount": 1,
            "options": [
              ["C++11", "-std=c++11"],
              ["C++14", "-std=c++14"],
              ["C++17", "-std=c++17"],
              ["C++20", "-std=c++20"]
            ]
          },
          {
            "label": "Log Options",
            "options": [
              ["Input", "-log=input"],
              ["Output", "-log=output"]
            ]
          }
        ]
      }
    }
  ]
}

An example of using dependsOn to select the arguments for an application:

{
  "version": "2.0.0",
  "tasks": [
    .....
  ],
  "inputs": [
    {
      "id": "powerAI",
      "type": "command",
      "command": "extension.commandvariable.pickStringRemember",
      "args": {
        "description": "Execution arguments",
        "key": "run-powerAI",
        "multiPick": true,
        "optionGroups": [
          {
            "label": "Debug / Release",
            "minCount": 1,
            "maxCount": 1,
            "options": [
              {"label": "debug", "value": "--debugg", "name": "debug"},
              {"label": "release", "value": "--release", "name": "release"}
            ]
          },
          {
            "label": "Port Number",
            "minCount": 1,
            "maxCount": 2,
            "dependsOn": "release",
            "options": [
              ["development", "5000"],
              ["staging", "5100"],
              ["live", "5200"]
            ]
          },
          {
            "label": "Logging",
            "name": "logging",
            "dependsOn": "debug",
            "options": [
              ["Logging", "--logging"]
            ]
          },
          {
            "label": "Training",
            "name": "training",
            "options": [
              ["Training", "--training"]
            ]
          },
          {
            "label": "Log Options",
            "minCount": 1,
            "dependsOn": "debug && logging",
            "options": [
              ["Log Input", "--log=input"],
              ["Log Output", "--log=output"],
              {"label": "Log Training", "value": "--log=training", "dependsOn": "training"}
            ]
          }
        ]
      }
    }
  ]
}

Select server from JSON

When you have a JSON file that specifies a list of servers you can use and you want to pick one of the servers and pass some attributes to a task.

The servers are specified in servers.json that is in the root of the workspace:

{
  "Servers": [
    {
      "name": "S1T",
      "description": "Server Test 1",
      "hostname": "st001.test.mycomp.com",
      "port": "1234"
    },
    {
      "name": "S2T",
      "description": "Server Test 2",
      "hostname": "sq003.test.mycomp.com",
      "port": "1235"
    },
    {
      "name": "S1P",
      "description": "Server Prod 1",
      "hostname": "spab.mycomp.com",
      "port": "1236"
    },
    {
      "name": "S2P",
      "description": "Server Prod 2",
      "hostname": "sdef01.cs.mycomp.com",
      "port": "1237"
    }
  ]
}

In tasks.json:

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "Get server progress",
      "type": "shell",
      "command": "progress --server ${input:selectServer} --host ${input:server-hostname} --port ${input:server-port}"
    }
  ],
  "inputs": [
    {
      "id": "selectServer",
      "type": "command",
      "command": "extension.commandvariable.pickStringRemember",
      "args": {
        "description": "Which server?",
        "key": "server-name",
        "fileName": "${workspaceFolder}/servers.json",
        "fileFormat": "json",
        "jsonOption": {
          "label": "content.Servers[__itemIdx__].name",
          "description": "content.Servers[__itemIdx__].description",
          "value": {
            "server-name": "content.Servers[__itemIdx__].name",
            "server-hostname": "content.Servers[__itemIdx__].hostname",
            "server-port": "content.Servers[__itemIdx__].port"
          }
        }
      }
    },
    {
      "id": "server-hostname",
      "type": "command",
      "command": "extension.commandvariable.remember",
      "args": { "key": "server-hostname" }
    },
    {
      "id": "server-port",
      "type": "command",
      "command": "extension.commandvariable.remember",
      "args": { "key": "server-port" }
    }
  ]
}

Select server from pattern

If you have a configuration file where the pick item properties are specified on multiple lines you can construct a Regular Expression that matches each item. You have to use at least the g or y flag.

You have a file ~/.ssh/config in your home directory where you specify a number of hosts you can use:

Host server1
    HostName 1.1.1.1
Host server2
    HostName 2.2.2.2
Host server3
    HostName 3.3.3.3

Maybe there are other attributes specified for each Host.

A totorial for ssh config files.

In tasks.json:

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "Get server progress",
      "type": "shell",
      "command": "progress --server ${input:selectServer}"
    }
  ],
  "inputs": [
    {
      "id": "selectServer",
      "type": "command",
      "command": "extension.commandvariable.pickStringRemember",
      "args": {
        "description": "Which server?",
        "key": "server-ip",
        "fileName": "${env:HOME}/.ssh/config",
        "pattern": {
          "regexp": "Host (\\S+).*?HostName (\\S+)",
          "flags": "gs",
          "match": "find",
          "option": {
            "label": "$1",
            "value": "$2",
            "description": "$2"
          }
        }
      }
    }
  ]
}

You can make the value property as complex as you want, like in the previous example.

If you have Host sections that don't contain a HostName property you have to use the split-regex to prevent using a HostName of a next Host section:

Host server1
    Port 2322
    HostName 1.1.1.1
Host server2
    User daenerys
    HostName 2.2.2.2
Host server3
    HostName 3.3.3.3
Host *
    LogLevel INFO
Host server4
    HostName 4.4.4.4
        "pattern": {
          "regexp": "Host (\\S+).*?HostName (\\S+)",
          "flags": "gs",
          "split-regexp": "^Host ",
          "split-flags": "gm",
          "option": {
            "label": "$1",
            "value": "$2",
            "description": "$2"
          }
        }

The match property is set to split.

promptStringRemember

extension.commandvariable.promptStringRemember has the same configuration attributes as the Input variable promptString. extension.commandvariable.promptStringRemember also has the configuration attributes:

The configuration attributes need to be passed to the command in the args attribute. The key attribute is optional if you only have one prompt to remember or every prompt can use the same key name.

If you have given a key attribute the Input Box will be prefilled with:

The string can later be retrieved with the remember command or variable.

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "Task 1",
      "type": "shell",
      "command": "dostuff1",
      "args": ["-p", "${input:promptPath}"]
    },
    {
      "label": "Task 2",
      "type": "shell",
      "command": "dostuff2",
      "args": ["-p", "${input:rememberPath}"]
    },
    {
      "label": "Do Task 1 and 2",
      "dependsOrder": "sequence",
      "dependsOn": ["Task 1", "Task 2"],
      "problemMatcher": []
    }
  ],
  "inputs": [
    {
      "id": "promptPath",
      "type": "command",
      "command": "extension.commandvariable.promptStringRemember",
      "args": {
        "key": "path",
        "description": "Enter a path"
      }
    },
    {
      "id": "rememberPath",
      "type": "command",
      "command": "extension.commandvariable.remember",
      "args": { "key": "path" }
    }
  ]
}

Multicursor and text

The commands extension.commandvariable.selectedText and extension.commandvariable.currentLineText combine the content in case of multi cursors. The default separator used is "\n".

The selections are sorted in the order they appear in the file.

You can change the separator by specifying an argument object for the command with a property "separator":

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "echo (selected:currentLine) Text",
      "type": "shell",
      "command": "echo",
      "args": [ "${input:multiCursorText}" ],
      "problemMatcher": []
    }
  ],
  "inputs": [
    {
      "id": "multiCursorText",
      "type": "command",
      "command": "extension.commandvariable.selectedText",
      "args": { "separator": "@--@" }
    }
  ]
}

inTerminal

The command extension.commandvariable.inTerminal types the string result of a command in the terminal and optional types a Carriage Return.

The command extension.commandvariable.inTerminal has an argument that is an object with the following properties:

If you want to use the value of a standard variable in the terminal you have to use the command extension.commandvariable.transform in the extension.commandvariable.inTerminal arguments. An example:

  {
    "key": "ctrl+i f5",  // or any other combo
    "command": "extension.commandvariable.inTerminal",
    "args": {
      "command": "extension.commandvariable.transform",
      "args": { "text": "${relativeFile}" }
    }
  }

getClipboard

The command extension.commandvariable.getClipboard gets the content of the clipboard.

VSC has a task/launch variable ${CLIPBOARD} but it returns an empty string in my version of VSC.

  {
    "key": "ctrl+i f5",  // or any other combo
    "command": "extension.commandvariable.getClipboard"
  }

setClipboard

The command extension.commandvariable.setClipboard sets the content of the clipboard with the string property text of the args object.

  {
    "key": "ctrl+i f6",  // or any other combo
    "command": "extension.commandvariable.setClipboard",
    "args": { "text": "This is the new clipboard content" }
  }

Transform

Sometimes you want to modify a variable before you use it. Change the filename of the file in the editor to construct a different filename.

The transform you can apply to fields in snippets is not supported in the variables in the task and launch json files.

With the command extension.commandvariable.transform you can find-replace with Regular Expression a selection of variables combined with static text.

The command can be used with the ${input:} variable and has the following arguments:

Example:

If you want the directory name of the active editor file but using forward slash (on Windows, see issue 47)

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "echo Current File Dirname Forward Slash",
      "type": "shell",
      "command": "my_program",
      "args": [
        "${input:fileDirnameForwardSlash}"
      ],
      "problemMatcher": []
    }
  ],
  "inputs": [
    {
      "id": "fileDirnameForwardSlash",
      "type": "command",
      "command": "extension.commandvariable.transform",
      "args": {
        "text": "${fileDirname}",
        "find": "\\\\",  // Reason for four '\': https://stackoverflow.com/a/4025505/2909854
        "replace": "/",
        "flags": "g"
      }
    }
  ]
}

Custom variables

We can use this command to construct custom variables by setting the text argument and not defining a find argument. The id of the inputs record is the name of the variable.

{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "node",
      "request": "launch",
      "name": "Node",
      "runtimeArgs": ["user", "${input:TEST_USER}"],
    },
    {
      "type": "chrome",
      "request": "launch",
      "name": "Chrome",
      "url": "http://localhost:3000?${input:TEST_USER}",
    }
  ],
  "inputs": [
    {
      "id": "TEST_USER",
      "type": "command",
      "command": "extension.commandvariable.transform",
      "args": { "text": "BobSmith" }
    }
  ]
}

Save to file

If you want to store the result of a transform, pickStringRemember, promptStringRemember, or any other command to a file and pass the path of the file as the result.

You have to wrap the command with a transform command that can save to a file.

You can use an input like:

    {
      "id": "saveToFile",
      "type": "command",
      "command": "extension.commandvariable.transform",
      "args": {
        "saveToFile": "${workspaceFolder}/.vscode/temp-text.txt",
        "text": "${promptStringRemember:getContent}",
        "key": "tmpfile",
        "promptStringRemember": {
          "getContent": {
              "description": "What to store in the file",
              "key": "fileContent"
          }
        }
      }
    }

In the task or launch config you use ${input:saveToFile}. You can use remember with the keys tmpfile for file path, fileContent for the file content. Or the ${remember} variable.

Variables

Many strings of commands support variables.

If the variable substitution is done with a pickFile:transform or remember:transform of a picked file, command or variable, the text "current opened file" should be replaced with "picked file".

VSC does not perform variable substitution in the strings of the inputs fields, so currently only a selection of variables is replicated here:

The variables are processed in the order mentioned. This means that if the selected text contains variable descriptions they are handled as if typed in the text.

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "echo first part fileBaseNameNoExtension",
      "type": "shell",
      "command": "echo",
      "args": [ "${input:firstPart}" ],
      "problemMatcher": []
    }
  ],
  "inputs": [
    {
      "id": "firstPart",
      "type": "command",
      "command": "extension.commandvariable.transform",
      "args": {
        "text": "${fileBasenameNoExtension}",
        "find": "(.*?)-.*",
        "replace": "$1",
      }
    }
  ]
}

Variable Filters

You can pass the result of a variable to 0, 1 or more filters.

The filters are specified after the variable name and possible properties:

You can specify 0, 1 or more filters, each separated with |. They are applied in the order defined.

The following filters are defined:

[!CAUTION] Be aware you don't create an infinite loop. To be able to apply a filter all variables need to be resolved until no variable left. A known possibility is pickStringRemember with "rememberTransformed": false and you want to have the previous picked string but filtered.

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "cpp lint",
      "type": "shell",
      "command": "cpplint ${input:selectDir}"
    }
  ],
  "inputs": [
    {
      "id": "selectDir",
      "type": "command",
      "command": "extension.commandvariable.pickStringRemember",
      "args": {
        "description": "Which directory to Lint for C++?",
        "options": [
          ["Use previous", "${remember:lintPath|upperCase}"], // !!! infinite loop
          ["All", "all"],
          ["dir1", "dir1"],
          ["dir2", "dir2"]
        ],
        "key": "lintPath"
      }
    }
  ]
}

dir1 and dir2 are placeholders and can be in reality as complex as you want and most likely contain some prompt or pick option.

The result of pickStringRemember is always the transformed string (all variables resolved). If "rememberTransformed": false and you pick the Use previous option the remembered value for lintPath is ${remember:lintPath|upperCase} (save the picked value before variable resolution). When we now want to resolve the picked value (${remember:lintPath|upperCase}) because we want to filter we need ${remember:lintPath}.

Solution always use "rememberTransformed": true when you have a Use previous option and add the filter to a ${pickStringRemember} variable:

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "cpp lint",
      "type": "shell",
      "command": "cpplint ${input:selectDir}"
    }
  ],
  "inputs": [
    {
      "id": "selectDir",
      "type": "command",
      "command": "extension.commandvariable.transform",
      "args": {
        "text": "${pickStringRemember:pickDir|upperCase}",
        "pickStringRemember": {
          "pickDir": {
            "description": "Which directory to Lint for C++?",
            "options": [
              ["Use previous", "${remember:lintPath}"],
              ["All", "all"],
              ["dir1", "dir1"],
              ["dir2", "dir2"]
            ],
            "key": "lintPath",
            "rememberTransformed": true
          }
        }
      }
    }
  ]
}

Variables in Javascript expression

User dvirtz has found a nice way to use the result of variables in a JavaScript expression and have that as a result.

{
  "id": "uniqueFolder",
  "type": "command",
  "command": "extension.commandvariable.config.expression",
  "args": {
    "expression": "['${workspaceFolder:b.1:nomsg}', '${workspaceFolder:b.2:nomsg}', '${workspaceFolder:b.3:nomsg}'].find(folder => folder != 'Unknown')"
  }
}

You don't have to specify the configVariable property.

Variable workspaceFolder

The variable ${workspaceFolder} is only valid in certain cases and depends on the URI of a file:

The URI used is:

location ${workspaceFolder} File Open URI
pickFile:transform -- URI of the picked file
remember:transform of a picked file -- URI of the picked file
other No undefined
other Yes URI of the open file

Be aware that "other" also refers to the pickFile:fromFolder property.

URI Workspace ${workspaceFolder}
-- No "Unknown" and Error: "No Folder"
-- Folder Path of the open folder
undefined Multi Root "Unknown" and Error: "Use workspace name"
valid Multi Root Path of the workspace containing URI or first workspace in the list

An example:

${workspaceFolder:server}

The variable ${workspaceFolder:name} is only invalid when there is no folder open.

In most cases the name is the basename of the workspace folder path (last directory name).

If you have 2 workspaces with the same (folder base)name you can't target the second one by name only. You have to use more parts of the directory path to make the name unique. Use the / as path separator on all platforms. The name is tested to be at the end of the workspace folder path (using / as separator).

An example:

${workspaceFolder:/websiteA/server}

Variable workspaceFolderBasename

The variable ${workspaceFolderBasename} uses the same strategy as variable ${workspaceFolder} to determine the workspace to use.

Variable selectedText

If you only have 1 selection you don't need the properties separator and filterSelection.

For the transform command you can define the properties separator and filterSelection in the args property of the command.

      "args": {
        "text": "${selectedText}",
        "separator": "@-@",
        "filterSelection": "index%2===1",
      }

And you can define/overrule the properties by embedding them in the variable:

${selectedText separator properties separator}

All separator's used in a variable need to be the same.

The separator is a string of 1 or more characters that are not part of the a to z alfabet, | or {}, in regular expression [^a-zA-Z{}|]+. Choose a character string that is not used in the values of the properties part. If you need to use more than 1 character do not use all the same character, it can lead to non conformant properties description that is still parsed. The reason is that JavaScript does not have non-backtrack greedy quantifiers. Currently the variable is matched with 1 regular expression. This makes everything easy to implement.

The properties are the properties you want separated with the separator string. Each property is defined as:

propertyName=value

Everyting between = and the next separator is the value

The above example can be written as

      "args": {
        "text": "${selectedText#separator=@-@#filterSelection=index%2===1#}"
      }

A few examples of filterSelection expressions

You can use multiple ${selectedText} variables that have different properties:

      "args": {
        "text": "${selectedText#filterSelection=index===3#} ${selectedText#filterSelection=index===1#}"
      }

Variable pickStringRemember

If you want to add an entry you pick from a list use the variable: ${pickStringRemember:name}

name is the property name of the pickStringRemember property of the args object of the command.

Because the command has no way to determine if it is called from which workspace tasks.json or launch.json file or from a key binding the arguments for pickStringRemember have to be part of the arguments of the command.

See the command extension.commandvariable.pickStringRemember for the arguments you can use.

An example shows faster how it is to be used compared to a lot of text.

"inputs": [
  {
    "id": "appSelect",
    "type": "command",
    "command": "extension.commandvariable.transform",
    "args": {
      "text": "We are using ${pickStringRemember:appName} on port ${pickStringRemember:portNum}",
      "pickStringRemember": {
        "appName": {
            "description": "What APP are you running?",
            "options": [ "client", "server", "stresstest", "pentest", "unittest" ],
            "default": "server"
        },
        "portNum": {
          "description": "What protocol?",
          "options": [
            ["http", "80"],
            ["http over proxy", "8080"],
            ["ftp", "21"]
          ],
          "default": "80"
        }
      }
    }
  }
]

Variable promptStringRemember

The promptStringRemember variable works the same as the pickStringRemember variable. If you want to add an entry you type on the keyboard use the variable: ${promptStringRemember:name}

name is the property name of the promptStringRemember property of the args object of the command.

Because the command has no way to determine if it is called from which workspace tasks.json or launch.json file or from a key binding the arguments for promptStringRemember have to be part of the arguments of the command.

See the command extension.commandvariable.promptStringRemember for the arguments you can use.

Variable pickFile

The pickFile variable works the same as the pickStringRemember variable. If you want a file path use the variable: ${pickFile:name}

name is the property name of the pickFile property of the args object of the command.

Because the command has no way to determine if it is called from which workspace tasks.json or launch.json file or from a key binding the arguments for pickFile have to be part of the arguments of the command.

See the command extension.commandvariable.pickFile for the arguments you can use.

An example: you have a number of key-value files and you want to select which environment to use

{
  "version": "0.2.0",
  "tasks": [
    {
      "label": "echo theme name",
      "type": "shell",
      "command": "echo",
      "args": [ "${input:themeName}" ]
    }
  ],
  "inputs": [
    {
      "id": "themeName",
      "type": "command",
      "command": "extension.commandvariable.file.content",
      "args": {
        "fileName": "${pickFile:environ}",
        "key": "THEME",
        "pickFile": {
          "environ": {
            "description": "Which environment?",
            "include": "**/*environ*",
            "display": "fileName"
          }
        }
      }
    }
  ]
}

Variable openDialog

The openDialog variable works the same as the pickStringRemember variable. If you want a file path use the variable: ${openDialog:name}

name is the property name of the openDialog property of the args object of the command.

Because the command has no way to determine if it is called from which workspace tasks.json or launch.json file or from a key binding the arguments for openDialog have to be part of the arguments of the command.

See the command extension.commandvariable.openDialog for the arguments you can use.

Variable saveDialog

The saveDialog variable works the same as the pickStringRemember variable. If you want a file path use the variable: ${saveDialog:name}

name is the property name of the saveDialog property of the args object of the command.

Because the command has no way to determine if it is called from which workspace tasks.json or launch.json file or from a key binding the arguments for saveDialog have to be part of the arguments of the command.

See the command extension.commandvariable.saveDialog for the arguments you can use.

Variable command

If you want to transform result of a command you use the ${command:name} variable in the text property of the extension.commandvariable.transform command.

name can be a commandID or a named argument object property (like pickStringRemember)

CommandID

If the command does not use arguments you place the commandID directly in the variable.

{
  "version": "0.2.0",
  "tasks": [
    {
      "label": "echo relative file no ext with dots - first dir removed",
      "type": "shell",
      "command": "echo",
      "args": [ "${input:relativeNoExtDotsBaseOff}" ]
    }
  ],
  "inputs": [
    {
      "id": "relativeNoExtDotsBaseOff",
      "type": "command",
      "command": "extension.commandvariable.transform",
      "args": {
        "text": "${command:extension.commandvariable.file.relativeFileDotsNoExtension}",
        "find": "^[^.]+\\."
      }
    }
  ]
}

Named Arguments

If the command uses arguments you have to put these in the arguments of the parent command in the property command. (Just like with the ${pickStringRemember:name} variable)

The named arguments have the following properties:

{
  "version": "0.2.0",
  "tasks": [
    {
      "label": "echo top 2 workspace folder names",
      "type": "shell",
      "command": "echo",
      "args": [ "${input:workspaceTop2Folders}" ]
    }
  ],
  "inputs": [
    {
      "id": "workspaceTop2Folders",
      "type": "command",
      "command": "extension.commandvariable.transform",
      "args": {
        "text": "${command:folderPosix}",
        "find": "^.*/([^/]+/[^/]+)$",
        "replace": "$1",
        "command": {
          "folderPosix": {
            "command": "extension.commandvariable.workspace.folderPosix",
            "args": { "name": "server" }
          }
        }
      }
    }
  ]
}

Next feature and example by Thomas Moore (issue 50)

The following example shows how the variableSubstArgs option can be used to expand variables in a command used as a named argument. In this case, the ${pickStringRemember:pickAnOption} variable is expanded prior to the argument being passed to the shellCommand.execute command (provided by the Tasks Shell Input extension).

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "Get Option String",
      "type": "shell",
      "command": "echo \"The option string is '${input:getOptionString}' and the selection option is '${input:selectedOption}'\"",
      "problemMatcher": []
    }
  ],
  "inputs": [
    {
      "id": "getOptionString",
      "type": "command",
      "command": "extension.commandvariable.transform",
      "args": {
        "key": "optionString",
        "text": "${command:getOptionString}",
        "command": {
          "getOptionString": {
            "command": "shellCommand.execute",
            "variableSubstArgs": true,
            "args": {
              "command": "echo You selected ${pickStringRemember:pickAnOption}",
              "useSingleResult": true,
            },
            "pickStringRemember": {
              "pickAnOption": {
                "key": "selectedOption",
                "description": "Pick an option",
                "options": [
                  { "label": "Previous option:",
                    "value": "${remember:selectedOption}",
                    "description": "${remember:selectedOption}"
                  },
                  "Option A",
                  "Option B",
                  "Option C",
                  "Option D"
                ]
              }
            }
          }
        }
      }
    },
    {
      "id": "selectedOption",
      "type": "command",
      "command": "extension.commandvariable.remember",
      "args": { "key": "selectedOption" }
    }
  ]
}

A realistic example is the execution of different bazel targets and option to use the previous target:

"inputs": [
  {
    "id": "bazelTargetPath",
    "type": "command",
    "command": "extension.commandvariable.transform",
    "args": {
      "key": "selectedBazelTargetPath",
      "text": "${command:getBazelTargetPath}",
      "command": {
        "getBazelTargetPath": {
          "command": "shellCommand.execute",
          "variableSubstArgs": true,
          "args": {
            "command": "bazel cquery --config=${command:cpptools.activeConfigName} --compilation_mode=dbg --output=files ${pickStringRemember:pickBazelTarget}",
            "cwd": "${workspaceFolder}"
          },
          "pickStringRemember": {
            "pickBazelTarget" : {
              "description": "Choose a target",
              "key": "selectedBazelTarget",
              "rememberTransformed": true,
              "options": [
                { "label": "Previous Target:",
                  "value": "${remember:selectedBazelTarget}",
                  "description": "${remember:selectedBazelTarget}"
                },
                { "label": "Select target...", "value": "${command:bazelTargets}" },
              ],
              "command": {
                "bazelTargets": {
                  "command": "shellCommand.execute",
                  "args": {
                    "command": "bazel query 'kind(cc_binary*, //...)'",
                    "cwd": "${workspaceFolder}"
                  }
                }
              }
            }
          }
        }
      }
    }
  }
]

Construct commandID

Sometimes you want to construct the commandID to execute.

Example based on StackOverflow question.

If you want to launch a particular configuration based on the file name of the current editor you can redefine the F5 keybinding:

  {
    "key": "f5",
    "command": "extension.commandvariable.transform",
    "when": "debuggersAvailable && debugState == 'inactive'",
    "args": {
      "text": "${command:launchCommand}",
      "command": {
        "launchCommand": {
          "command": "${transform:launchCommand}",
          "transform": {
            "launchCommand": {
              "text": "${command:launchCommand}",
              "command": {
                "launchCommand": {
                  "command": "extension.commandvariable.file.fileAsKey",
                  "args": {
                    "app.py": "launches.Streamlit",
                    "@default": "launches.OtherPython"
                  }
                }
              }
            }
          }
        }
      }
    }
  }

The command property can't contain a ${command:name} variable, so we have to insert a ${transform:name} variable.

This key binding uses the Launch Configs extention by ArturoDent.

Variable transform

Say you have a command/script that wants a series of numbers and they can be in a single argument. The numbers have to be clean, no other text in between. You also want to be able to select some text in an editor and use that to filter out the numbers.

{
  "version": "0.2.0",
  "tasks": [
    {
      "label": "echo top 2 workspace folder names",
      "type": "shell",
      "command": "myScript",
      "args": [ "${input:numberSequence}" ]
    }
  ],
  "inputs": [
    {
      "id": "numberSequence",
      "type": "command",
      "command": "extension.commandvariable.pickStringRemember",
      "args": {
        "description": "Which number list?",
        "options": [
          "100 200 300",
          "51 99 2",
          ["Use a raw number list", "${transform:removeLeadingTrailingSpaces}"]
        ],
        "rememberTransformed": true,
        "key": "numSeq",
        "transform": {
          "removeLeadingTrailingSpaces": {
            "text": "${transform:nonNumbersToSpace}",
            "find": "^ +| +$",
            "flags": "g",
            "transform": {
              "nonNumbersToSpace": {
                "text": "${pickStringRemember:getRawNumberList}",
                "find": "[^0-9]+",
                "replace": " ",
                "flags": "g",
                "pickStringRemember": {
                  "getRawNumberList": {
                    "description": "Which raw number list?",
                    "options": [
                      "foo 123 bar bar 456      ",
                      "Alice: 10,  Bob: 3",
                      ["Selected text", "${selectedText}"]
                    ]
                  }
                }
              }
            }
          }
        }
      }
    }
  ]
}

The above example can be made more readable with the apply property to define a sequence of find-replace operations (we also replace multiple spaces by 1 space)

  "inputs": [
    {
      "id": "numberSequence",
      "type": "command",
      "command": "extension.commandvariable.pickStringRemember",
      "args": {
        "description": "Which number list?",
        "options": [
          "100 200 300",
          "51 99 2",
          ["Use a raw number list", "${transform:rawNumberList}"]
        ],
        "rememberTransformed": true,
        "key": "numSeq",
        "transform": {
          "rawNumberList": {
            "text": "${pickStringRemember:getRawNumberList}",
            "apply": [
              {
                "find": "[^0-9]+",
                "replace": " ",
                "flags": "g",
              },
              {
                "find": "^ +| +$",
                "flags": "g",
              },
              {
                "find": " {2,}",
                "replace": " ",
                "flags": "g",
              }
            ]
            "pickStringRemember": {
              "getRawNumberList": {
                "description": "Which raw number list?",
                "options": [
                  "foo 123 bar bar 456      ",
                  "Alice: 10,  Bob: 3",
                  ["Selected text", "${selectedText}"]
                ]
              }
            }
          }
        }
      }
    }
  ]

Variable remember

If you want to use one of the stored values you can use the ${remember:name} variable in the text property of the extension.commandvariable.transform command.

Key name

If you only want to retreive a value for a stored key just use the key name ${remember:key_name}.

The value for key serverPortNr was stored with another command.

{
  "version": "0.2.0",
  "tasks": [
    {
      "label": "echo Server Port",
      "type": "shell",
      "command": "echo",
      "args": [ "Attach to port ${input:severPortNr}" ]
    }
  ],
  "inputs": [
    {
      "id": "severPortNr",
      "type": "command",
      "command": "extension.commandvariable.transform",
      "args": {
        "text": "${remember:serverPortNr}"
      }
    }
  ]
}

Named Arguments

If you want to pass more arguments to the remember command you have to put these in the arguments of the parent command in the property remember. (Just like with the ${pickStringRemember:name} variable)

If you have picked a file, the key used is sourceFile, and you don't want the full file path you can get certain parts with the transform property:

{
  "version": "0.2.0",
  "tasks": [
    {
      "label": "echo Source file",
      "type": "shell",
      "command": "echo",
      "args": [ "${input:sourceFileInfo}" ]
    }
  ],
  "inputs": [
    {
      "id": "sourceFileInfo",
      "type": "command",
      "command": "extension.commandvariable.transform",
      "args": {
        "text": "From Workspace ${remember:showWorkspace} we use ${remember:showBasename}",
        "remember": {
          "showWorkspace": {
            "key": "sourceFile",
            "transform": { "text": "${workspaceFolder}" }
          },
          "showBasename": {
            "key": "sourceFile",
            "transform": { "text": "${fileBasename}" }
          }
        }
      }
    }
  ]
}

checkEscapedUI

If you have a task/launch that uses variables that have a UI and you Escape the UI the task/launch is not executed.

If you have a compound task/launch you want to also terminate all following tasks/launches. All UI elements in this extension (pickFile, pickString and promptString), as command or variable, record if they are Escaped. They can test if there has been an Escaped UI and behave as if Escaped themself. Also the remember command and variable can test for an Escaped UI and behave as being an Escaped UI.

You don't add the checkEscapedUI property to the first UI in the compound task/launch because it would check if the previous run was Escaped.

Example

This example uses simple echo tasks to keep it short.

{
  "version": "0.2.0",
  "tasks": [
    {
      "label": "Task 1",
      "type": "shell",
      "command": "echo",
      "args": [ "Task 1 using envType: ${input:envType}" ],
      "problemMatcher": []
    },
    {
      "label": "Task 2",
      "type": "shell",
      "command": "echo",
      "args": [ "Task 2 with envMessage: ${input:envMessage}" ],
      "problemMatcher": []
    },
    {
      "label": "Task 3",
      "type": "shell",
      "command": "echo",
      "args": [ "Task 3 with message: ${input:transformEnvMessage}" ],
      "problemMatcher": []
    },
    {
      "label": "Task Sequence",
      "dependsOrder": "sequence",
      "dependsOn": ["Task 1", "Task 2", "Task 3"],
      "problemMatcher": []
    }
  ],
  "inputs": [
    {
      "id": "envType",
      "type": "command",
      "command": "extension.commandvariable.pickStringRemember",
      "args": {
        "description": "Which env do you want to debug?",
        "key": "envType",
        "options": [
          ["development", "5000"],
          ["staging", "5100"],
          ["live", "5200"]
        ]
      }
    },
    {
      "id": "envMessage",
      "type": "command",
      "command": "extension.commandvariable.promptStringRemember",
      "args": {
        "key": "envMessage",
        "description": "Enter message",
        "checkEscapedUI": true
      }
    },
    {
      "id": "transformEnvMessage",
      "type": "command",
      "command": "extension.commandvariable.transform",
      "args": {
        "text": "${remember:envMessage__checkEscapedUI}",
        "find": "(\\d+)",
        "replace": "Number($1)"
      }
    }
  ]
}

Workspace name in argument

The commands

allow to get the information from a different workspace by specifying the name or last parts of the file path of the workspace directory. This can also be done when there is no editor active.

You supply the name in the arguments of the command. You have to use an ${input} variable.

{
  "version": "0.2.0",
  "tasks": [
    {
      "label": "echo server name",
      "type": "shell",
      "command": "echo",
      "args": [ "${input:server1Up}" ]
    }
  ],
  "inputs": [
    {
      "id": "server1Up",
      "type": "command",
      "command": "extension.commandvariable.workspace.folderBasename1Up",
      "args": { "name": "server" }
    }
  ]
}

If you have 2 workspaces with the same (folder base)name you can't target the second one by name only. You have to use more parts of the directory path to make the name unique. Use the / as path separator on all platforms. The name argument is tested to be at the end of the workspace folder path (using / as separator). An example of an args property is:

"args": { "name": "/websiteA/server" }

UUID

The commands extension.commandvariable.UUID and extension.commandvariable.UUIDInEditor generate a v4 UUID.

It has the following arguments:

In this example the 3 printed UUIDs are all different

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "echo UUIDs",
      "type": "shell",
      "command": "echo",
      "args": [
        "${command:extension.commandvariable.UUID}",
        "${input:uuid-hexnodelim}",
        "${input:uuid-urn}"
      ],
      "problemMatcher": []
    }
  ],
  "inputs": [
    {
      "id": "uuid",
      "type": "command",
      "command": "extension.commandvariable.UUID"
    },
    {
      "id": "uuid-hexnodelim",
      "type": "command",
      "command": "extension.commandvariable.UUID",
      "args": { "output": "hexNoDelim" }
    },
    {
      "id": "uuid-urn",
      "type": "command",
      "command": "extension.commandvariable.UUID",
      "args": { "output": "urn" }
    },
    {
      "id": "uuid-bits",
      "type": "command",
      "command": "extension.commandvariable.UUID",
      "args": { "output": "bitString" }
    }
  ]
}

dateTime

For keybindings.json use extension.commandvariable.dateTimeInEditor.

For launch.json and tasks.json use extension.commandvariable.dateTime.

This command uses Intl.DateTimeFormat to create a language-sensitive format of current date and time.

The locale and options command arguments are the arguments for the Intl.DateTimeFormat constructor and are optional.

The locale command argument can be a single string or an array of strings of language tags. If not specified the browser default locale is used.

The template command argument is an optional template string that uses the same placeholder syntax as the Javascript template strings. You can add as many literal text as needed.

The only expressions valid are the type values returned by the Intl.DateTimeFormat.prototype.formatToParts() method. See first example.

If there is no template command argument the value parts of the Intl.DateTimeFormat.prototype.formatToParts() are joined. See second example.

Example 1

  {
    "key": "ctrl+shift+alt+f4",
    "when": "editorTextFocus",
    "command": "extension.commandvariable.dateTimeInEditor",
    "args": {
      "locale": "en-US",
      "options": {
        "year": "numeric",
        "month": "2-digit",
        "day": "2-digit",
        "hour12": false,
        "hour": "2-digit",
        "minute": "2-digit",
        "second": "2-digit"
      },
      "template": "${year}/${month}/${day}-${hour}:${minute}:${second}"
    }
  }

The result is

2020/03/19-18:01:18

Example 2

You can use a different locale and number system and use the long format:

  {
    "key": "ctrl+shift+alt+f5",
    "when": "editorTextFocus",
    "command": "extension.commandvariable.dateTimeInEditor",
    "args": {
      "locale": "fr-FR-u-nu-deva",
      "options": {
        "dateStyle": "full",
        "timeStyle": "full"
      }
    }
  }

The result is

jeudi १९ mars २०२० à १७:५९:५७ heure normale d’Europe centrale

Example 3

For launch.json and tasks.json use the inputs attribute:

{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "echo date",
      "type": "shell",
      "command": "echo",
      "args": [ "${input:shortDate}" ],
      "problemMatcher": []
    }
  ],
  "inputs": [
    {
      "id": "shortDate",
      "type": "command",
      "command": "extension.commandvariable.dateTime",
      "args": {
        "locale": "es-ES",
        "options": {
          "weekday": "long",
          "year": "numeric",
          "month": "2-digit",
          "day": "2-digit",
          "hour12": false,
          "hour": "2-digit",
          "minute": "2-digit",
          "second": "2-digit"
        },
        "template": "${weekday}__${year}${month}${day}T${hour}${minute}${second}"
      }
    }
  ]
}

The result is:

jueves__20200319T184634

Credits