Merge from vscode merge-base (#22780)

* Revert "Revert "Merge from vscode merge-base (#22769)" (#22779)"

This reverts commit 47a1745180.

* Fix notebook download task

* Remove done call from extensions-ci
This commit is contained in:
Karl Burtram
2023-04-19 21:48:46 -07:00
committed by GitHub
parent decbe8dded
commit e7d3d047ec
2389 changed files with 92155 additions and 42602 deletions

View File

@@ -52,7 +52,8 @@
".devcontainer.json"
],
"filenamePatterns": [
"**/User/snippets/*.json"
"**/User/snippets/*.json",
"**/User/profiles/*/snippets/*.json"
]
}, {
"id": "json",
@@ -78,6 +79,10 @@
"fileMatch": "%APP_SETTINGS_HOME%/settings.json",
"url": "vscode://schemas/settings/user"
},
{
"fileMatch": "%APP_SETTINGS_HOME%/profiles/*/settings.json",
"url": "vscode://schemas/settings/profile"
},
{
"fileMatch": "%MACHINE_SETTINGS_HOME%/settings.json",
"url": "vscode://schemas/settings/machine"

View File

@@ -0,0 +1,189 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"customizations": {
"type": "object",
"properties": {
"codespaces": {
"type": "object",
"description": "Customizations specific to GitHub Codespaces",
"properties": {
"repositories": {
"type": "object",
"description": "Configuration relative to the given repositories, following the format 'owner/repo'.\n A wildcard (*) is permitted for the repo name (eg: 'microsoft/*')",
"patternProperties": {
"^[a-zA-Z0-9-_.]+[.]*\/[a-zA-Z0-9-_*]+[.]*$": {
"type": "object",
"additionalProperties": true,
"oneOf": [
{
"properties": {
"permissions": {
"type": "object",
"description": "Additional repository permissions.\n See https://aka.ms/ghcs/multi-repo-auth for more info.",
"additionalProperties": true,
"anyOf": [
{
"properties": {
"actions": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"checks": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"contents": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"deployments": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"discussions": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"issues": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"packages": {
"type": "string",
"enum": [
"read"
]
}
}
},
{
"properties": {
"pages": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"pull_requests": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"repository_projects": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"statuses": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"workflows": {
"type": "string",
"enum": [
"write"
]
}
}
}
]
}
}
},
{
"properties": {
"permissions": {
"type": "string",
"description": "Additional repository permissions.\n See https://aka.ms/ghcs/multi-repo-auth for more info.",
"enum": [
"read-all",
"write-all"
]
}
}
}
]
}
}
}
}
}
}
},
"codespaces": {
"type": "object",
"additionalProperties": true,
"description": "Codespaces-specific configuration.",
"deprecated": true,
"deprecationMessage": "Use 'customizations/codespaces' instead"
}
}
}

View File

@@ -406,6 +406,7 @@
},
"customizations": {
"type": "object",
"description": "Tool-specific configuration. Each tool should use a JSON object subproperty with a unique name to group its customizations.",
"properties": {
"vscode": {
"type": "object",
@@ -429,13 +430,186 @@
}
},
"additionalProperties": false
},
"codespaces": {
"type": "object",
"description": "Customizations specific to GitHub Codespaces",
"properties": {
"repositories": {
"type": "object",
"description": "Configuration relative to the given repositories, following the format 'owner/repo'.\n A wildcard (*) is permitted for the repo name (eg: 'microsoft/*')",
"patternProperties": {
"^[a-zA-Z0-9-_.]+[.]*/[a-zA-Z0-9-_*]+[.]*$": {
"type": "object",
"additionalProperties": true,
"oneOf": [
{
"properties": {
"permissions": {
"type": "object",
"description": "Additional repository permissions.\n See https://aka.ms/ghcs/multi-repo-auth for more info.",
"additionalProperties": true,
"anyOf": [
{
"properties": {
"actions": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"checks": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"contents": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"deployments": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"discussions": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"issues": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"packages": {
"type": "string",
"enum": [
"read"
]
}
}
},
{
"properties": {
"pages": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"pull_requests": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"repository_projects": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"statuses": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"workflows": {
"type": "string",
"enum": [
"write"
]
}
}
}
]
}
}
},
{
"properties": {
"permissions": {
"type": "string",
"description": "Additional repository permissions.\n See https://aka.ms/ghcs/multi-repo-auth for more info.",
"enum": [
"read-all",
"write-all"
]
}
}
}
]
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
},
"additionalProperties": {
"type": "object",
"additionalProperties": true
},
"description": "Tool-specific configuration. Each tool should use a JSON object subproperty with a unique name to group its customizations."
"additionalProperties": false
},
"additionalProperties": {
"type": "object",
"additionalProperties": true
}
},
"required": [
@@ -842,6 +1016,7 @@
},
"customizations": {
"type": "object",
"description": "Tool-specific configuration. Each tool should use a JSON object subproperty with a unique name to group its customizations.",
"properties": {
"vscode": {
"type": "object",
@@ -865,13 +1040,186 @@
}
},
"additionalProperties": false
},
"codespaces": {
"type": "object",
"description": "Customizations specific to GitHub Codespaces",
"properties": {
"repositories": {
"type": "object",
"description": "Configuration relative to the given repositories, following the format 'owner/repo'.\n A wildcard (*) is permitted for the repo name (eg: 'microsoft/*')",
"patternProperties": {
"^[a-zA-Z0-9-_.]+[.]*/[a-zA-Z0-9-_*]+[.]*$": {
"type": "object",
"additionalProperties": true,
"oneOf": [
{
"properties": {
"permissions": {
"type": "object",
"description": "Additional repository permissions.\n See https://aka.ms/ghcs/multi-repo-auth for more info.",
"additionalProperties": true,
"anyOf": [
{
"properties": {
"actions": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"checks": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"contents": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"deployments": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"discussions": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"issues": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"packages": {
"type": "string",
"enum": [
"read"
]
}
}
},
{
"properties": {
"pages": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"pull_requests": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"repository_projects": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"statuses": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"workflows": {
"type": "string",
"enum": [
"write"
]
}
}
}
]
}
}
},
{
"properties": {
"permissions": {
"type": "string",
"description": "Additional repository permissions.\n See https://aka.ms/ghcs/multi-repo-auth for more info.",
"enum": [
"read-all",
"write-all"
]
}
}
}
]
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
},
"additionalProperties": {
"type": "object",
"additionalProperties": true
},
"description": "Tool-specific configuration. Each tool should use a JSON object subproperty with a unique name to group its customizations."
"additionalProperties": false
},
"additionalProperties": {
"type": "object",
"additionalProperties": true
}
},
"required": [
@@ -1244,6 +1592,7 @@
},
"customizations": {
"type": "object",
"description": "Tool-specific configuration. Each tool should use a JSON object subproperty with a unique name to group its customizations.",
"properties": {
"vscode": {
"type": "object",
@@ -1267,13 +1616,186 @@
}
},
"additionalProperties": false
},
"codespaces": {
"type": "object",
"description": "Customizations specific to GitHub Codespaces",
"properties": {
"repositories": {
"type": "object",
"description": "Configuration relative to the given repositories, following the format 'owner/repo'.\n A wildcard (*) is permitted for the repo name (eg: 'microsoft/*')",
"patternProperties": {
"^[a-zA-Z0-9-_.]+[.]*/[a-zA-Z0-9-_*]+[.]*$": {
"type": "object",
"additionalProperties": true,
"oneOf": [
{
"properties": {
"permissions": {
"type": "object",
"description": "Additional repository permissions.\n See https://aka.ms/ghcs/multi-repo-auth for more info.",
"additionalProperties": true,
"anyOf": [
{
"properties": {
"actions": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"checks": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"contents": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"deployments": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"discussions": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"issues": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"packages": {
"type": "string",
"enum": [
"read"
]
}
}
},
{
"properties": {
"pages": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"pull_requests": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"repository_projects": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"statuses": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"workflows": {
"type": "string",
"enum": [
"write"
]
}
}
}
]
}
}
},
{
"properties": {
"permissions": {
"type": "string",
"description": "Additional repository permissions.\n See https://aka.ms/ghcs/multi-repo-auth for more info.",
"enum": [
"read-all",
"write-all"
]
}
}
}
]
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
},
"additionalProperties": {
"type": "object",
"additionalProperties": true
},
"description": "Tool-specific configuration. Each tool should use a JSON object subproperty with a unique name to group its customizations."
"additionalProperties": false
},
"additionalProperties": {
"type": "object",
"additionalProperties": true
}
},
"required": [
@@ -1620,6 +2142,7 @@
},
"customizations": {
"type": "object",
"description": "Tool-specific configuration. Each tool should use a JSON object subproperty with a unique name to group its customizations.",
"properties": {
"vscode": {
"type": "object",
@@ -1643,13 +2166,186 @@
}
},
"additionalProperties": false
},
"codespaces": {
"type": "object",
"description": "Customizations specific to GitHub Codespaces",
"properties": {
"repositories": {
"type": "object",
"description": "Configuration relative to the given repositories, following the format 'owner/repo'.\n A wildcard (*) is permitted for the repo name (eg: 'microsoft/*')",
"patternProperties": {
"^[a-zA-Z0-9-_.]+[.]*/[a-zA-Z0-9-_*]+[.]*$": {
"type": "object",
"additionalProperties": true,
"oneOf": [
{
"properties": {
"permissions": {
"type": "object",
"description": "Additional repository permissions.\n See https://aka.ms/ghcs/multi-repo-auth for more info.",
"additionalProperties": true,
"anyOf": [
{
"properties": {
"actions": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"checks": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"contents": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"deployments": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"discussions": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"issues": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"packages": {
"type": "string",
"enum": [
"read"
]
}
}
},
{
"properties": {
"pages": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"pull_requests": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"repository_projects": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"statuses": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"workflows": {
"type": "string",
"enum": [
"write"
]
}
}
}
]
}
}
},
{
"properties": {
"permissions": {
"type": "string",
"description": "Additional repository permissions.\n See https://aka.ms/ghcs/multi-repo-auth for more info.",
"enum": [
"read-all",
"write-all"
]
}
}
}
]
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
},
"additionalProperties": {
"type": "object",
"additionalProperties": true
},
"description": "Tool-specific configuration. Each tool should use a JSON object subproperty with a unique name to group its customizations."
"additionalProperties": false
},
"additionalProperties": {
"type": "object",
"additionalProperties": true
}
},
"required": [
@@ -1961,6 +2657,7 @@
},
"customizations": {
"type": "object",
"description": "Tool-specific configuration. Each tool should use a JSON object subproperty with a unique name to group its customizations.",
"properties": {
"vscode": {
"type": "object",
@@ -1984,13 +2681,186 @@
}
},
"additionalProperties": false
},
"codespaces": {
"type": "object",
"description": "Customizations specific to GitHub Codespaces",
"properties": {
"repositories": {
"type": "object",
"description": "Configuration relative to the given repositories, following the format 'owner/repo'.\n A wildcard (*) is permitted for the repo name (eg: 'microsoft/*')",
"patternProperties": {
"^[a-zA-Z0-9-_.]+[.]*/[a-zA-Z0-9-_*]+[.]*$": {
"type": "object",
"additionalProperties": true,
"oneOf": [
{
"properties": {
"permissions": {
"type": "object",
"description": "Additional repository permissions.\n See https://aka.ms/ghcs/multi-repo-auth for more info.",
"additionalProperties": true,
"anyOf": [
{
"properties": {
"actions": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"checks": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"contents": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"deployments": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"discussions": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"issues": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"packages": {
"type": "string",
"enum": [
"read"
]
}
}
},
{
"properties": {
"pages": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"pull_requests": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"repository_projects": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"statuses": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"workflows": {
"type": "string",
"enum": [
"write"
]
}
}
}
]
}
}
},
{
"properties": {
"permissions": {
"type": "string",
"description": "Additional repository permissions.\n See https://aka.ms/ghcs/multi-repo-auth for more info.",
"enum": [
"read-all",
"write-all"
]
}
}
}
]
}
},
"additionalProperties": false
}
},
"additionalProperties": false
}
},
"additionalProperties": {
"type": "object",
"additionalProperties": true
},
"description": "Tool-specific configuration. Each tool should use a JSON object subproperty with a unique name to group its customizations."
"additionalProperties": false
},
"additionalProperties": {
"type": "object",
"additionalProperties": true
}
},
"additionalProperties": false

View File

@@ -309,6 +309,7 @@
},
"customizations": {
"type": "object",
"description": "Tool-specific configuration. Each tool should use a JSON object subproperty with a unique name to group its customizations.",
"properties": {
"vscode": {
"type": "object",
@@ -331,13 +332,183 @@
"description": "The port VS Code can use to connect to its backend."
}
}
},
"codespaces": {
"type": "object",
"description": "Customizations specific to GitHub Codespaces",
"properties": {
"repositories": {
"type": "object",
"description": "Configuration relative to the given repositories, following the format 'owner/repo'.\n A wildcard (*) is permitted for the repo name (eg: 'microsoft/*')",
"patternProperties": {
"^[a-zA-Z0-9-_.]+[.]*\/[a-zA-Z0-9-_*]+[.]*$": {
"type": "object",
"additionalProperties": true,
"oneOf": [
{
"properties": {
"permissions": {
"type": "object",
"description": "Additional repository permissions.\n See https://aka.ms/ghcs/multi-repo-auth for more info.",
"additionalProperties": true,
"anyOf": [
{
"properties": {
"actions": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"checks": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"contents": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"deployments": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"discussions": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"issues": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"packages": {
"type": "string",
"enum": [
"read"
]
}
}
},
{
"properties": {
"pages": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"pull_requests": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"repository_projects": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"statuses": {
"type": "string",
"enum": [
"read",
"write"
]
}
}
},
{
"properties": {
"workflows": {
"type": "string",
"enum": [
"write"
]
}
}
}
]
}
}
},
{
"properties": {
"permissions": {
"type": "string",
"description": "Additional repository permissions.\n See https://aka.ms/ghcs/multi-repo-auth for more info.",
"enum": [
"read-all",
"write-all"
]
}
}
}
]
}
}
}
}
}
},
"additionalProperties": {
"type": "object",
"additionalProperties": true
},
"description": "Tool-specific configuration. Each tool should use a JSON object subproperty with a unique name to group its customizations."
}
},
"additionalProperties": {
"type": "object",
"additionalProperties": true
}
}
},

View File

@@ -0,0 +1,56 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"customizations": {
"type": "object",
"properties": {
"vscode": {
"type": "object",
"properties": {
"extensions": {
"type": "array",
"description": "An array of extensions that should be installed into the container.",
"items": {
"type": "string",
"pattern": "^([a-z0-9A-Z][a-z0-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-Z-]*)(@(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)?$",
"errorMessage": "Expected format: '${publisher}.${name}' or '${publisher}.${name}@${version}'. Example: 'ms-dotnettools.csharp'."
}
},
"settings": {
"$ref": "vscode://schemas/settings/machine",
"description": "Machine specific settings that should be copied into the container. These are only copied when connecting to the container for the first time, rebuilding the container then triggers it again."
},
"devPort": {
"type": "integer",
"description": "The port VS Code can use to connect to its backend."
}
}
}
}
},
"extensions": {
"type": "array",
"description": "An array of extensions that should be installed into the container.",
"items": {
"type": "string",
"pattern": "^([a-z0-9A-Z][a-z0-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-Z-]*)((@(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)|@prerelease)?$",
"errorMessage": "Expected format: '${publisher}.${name}' or '${publisher}.${name}@${version}'. Example: 'ms-dotnettools.csharp'."
},
"deprecated": true,
"deprecationMessage": "Use 'customizations/vscode/extensions' instead"
},
"settings": {
"$ref": "vscode://schemas/settings/machine",
"description": "Machine specific settings that should be copied into the container. These are only copied when connecting to the container for the first time, rebuilding the container then triggers it again.",
"deprecated": true,
"deprecationMessage": "Use 'customizations/vscode/settings' instead"
},
"devPort": {
"type": "integer",
"description": "The port VS Code can use to connect to its backend.",
"deprecated": true,
"deprecationMessage": "Use 'customizations/vscode/devPort' instead"
}
}
}

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { getLocation, JSONPath, parse, visit } from 'jsonc-parser';
import { getLocation, JSONPath, parse, visit, Location } from 'jsonc-parser';
import * as vscode from 'vscode';
import * as nls from 'vscode-nls';
import { SettingsDocument } from './settingsDocumentHelper';
@@ -39,9 +39,11 @@ function registerVariableCompletions(pattern: string): vscode.Disposable {
return vscode.languages.registerCompletionItemProvider({ language: 'jsonc', pattern }, {
provideCompletionItems(document, position, _token) {
const location = getLocation(document.getText(), document.offsetAt(position));
if (!location.isAtPropertyKey && location.previousNode && location.previousNode.type === 'string') {
const indexOf$ = document.lineAt(position.line).text.lastIndexOf('$', position.character);
const startPosition = indexOf$ >= 0 ? new vscode.Position(position.line, indexOf$) : position;
if (isCompletingInsidePropertyStringValue(document, location, position)) {
let range = document.getWordRangeAtPosition(position, /\$\{[^"\}]*\}?/);
if (!range || range.start.isEqual(position) || range.end.isEqual(position) && document.getText(range).endsWith('}')) {
range = new vscode.Range(position, position);
}
return [
{ label: 'workspaceFolder', detail: localize('workspaceFolder', "The path of the folder opened in VS Code") },
@@ -61,7 +63,7 @@ function registerVariableCompletions(pattern: string): vscode.Disposable {
{ label: 'extensionInstallFolder', detail: localize('extensionInstallFolder', "The path where an an extension is installed."), param: 'publisher.extension' },
].map(variable => ({
label: `\${${variable.label}}`,
range: new vscode.Range(startPosition, position),
range,
insertText: variable.param ? new vscode.SnippetString(`\${${variable.label}:`).appendPlaceholder(variable.param).appendText('}') : (`\${${variable.label}}`),
detail: variable.detail
}));
@@ -72,6 +74,18 @@ function registerVariableCompletions(pattern: string): vscode.Disposable {
});
}
function isCompletingInsidePropertyStringValue(document: vscode.TextDocument, location: Location, pos: vscode.Position) {
if (location.isAtPropertyKey) {
return false;
}
const previousNode = location.previousNode;
if (previousNode && previousNode.type === 'string') {
const offset = document.offsetAt(pos);
return offset > previousNode.offset && offset < previousNode.offset + previousNode.length;
}
return false;
}
interface IExtensionsContent {
recommendations: string[];
}
@@ -84,8 +98,8 @@ function registerExtensionsCompletionsInExtensionsDocument(): vscode.Disposable
return vscode.languages.registerCompletionItemProvider({ pattern: '**/extensions.json' }, {
provideCompletionItems(document, position, _token) {
const location = getLocation(document.getText(), document.offsetAt(position));
const range = document.getWordRangeAtPosition(position) || new vscode.Range(position, position);
if (location.path[0] === 'recommendations') {
const range = getReplaceRange(document, location, position);
const extensionsContent = <IExtensionsContent>parse(document.getText());
return provideInstalledExtensionProposals(extensionsContent && extensionsContent.recommendations || [], '', range, false);
}
@@ -98,8 +112,8 @@ function registerExtensionsCompletionsInWorkspaceConfigurationDocument(): vscode
return vscode.languages.registerCompletionItemProvider({ pattern: '**/*.code-workspace' }, {
provideCompletionItems(document, position, _token) {
const location = getLocation(document.getText(), document.offsetAt(position));
const range = document.getWordRangeAtPosition(position) || new vscode.Range(position, position);
if (location.path[0] === 'extensions' && location.path[1] === 'recommendations') {
const range = getReplaceRange(document, location, position);
const extensionsContent = <IExtensionsContent>parse(document.getText())['extensions'];
return provideInstalledExtensionProposals(extensionsContent && extensionsContent.recommendations || [], '', range, false);
}
@@ -108,6 +122,17 @@ function registerExtensionsCompletionsInWorkspaceConfigurationDocument(): vscode
});
}
function getReplaceRange(document: vscode.TextDocument, location: Location, position: vscode.Position) {
const node = location.previousNode;
if (node) {
const nodeStart = document.positionAt(node.offset), nodeEnd = document.positionAt(node.offset + node.length);
if (nodeStart.isBeforeOrEqual(position) && nodeEnd.isAfterOrEqual(position)) {
return new vscode.Range(nodeStart, nodeEnd);
}
}
return new vscode.Range(position, position);
}
vscode.languages.registerDocumentSymbolProvider({ pattern: '**/launch.json', language: 'jsonc' }, {
provideDocumentSymbols(document: vscode.TextDocument, _token: vscode.CancellationToken): vscode.ProviderResult<vscode.SymbolInformation[]> {
const result: vscode.SymbolInformation[] = [];
@@ -180,28 +205,11 @@ function registerContextKeyCompletions(): vscode.Disposable {
}
}
if (!isValidLocation) {
if (!isValidLocation || !isCompletingInsidePropertyStringValue(document, location, position)) {
return;
}
// for JSON everything with quotes is a word
const jsonWord = document.getWordRangeAtPosition(position);
if (!jsonWord || jsonWord.start.isEqual(position) || jsonWord.end.isEqual(position)) {
// we aren't inside a "JSON word" or on its quotes
return;
}
let replacing: vscode.Range | undefined;
if (jsonWord.end.character - jsonWord.start.character === 2 || document.getWordRangeAtPosition(position, /\s+/)) {
// empty json word or on whitespace
replacing = new vscode.Range(position, position);
} else {
replacing = document.getWordRangeAtPosition(position, /[a-zA-Z.]+/);
}
if (!replacing) {
return;
}
const replacing = document.getWordRangeAtPosition(position, /[a-zA-Z.]+/) || new vscode.Range(position, position);
const inserting = replacing.with(undefined, position);
const data = await vscode.commands.executeCommand<ContextKeyInfo[]>('getContextKeyInfo');

View File

@@ -8,7 +8,7 @@ import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
export function provideInstalledExtensionProposals(existing: string[], additionalText: string, range: vscode.Range, includeBuiltinExtensions: boolean): vscode.ProviderResult<vscode.CompletionItem[] | vscode.CompletionList> {
export async function provideInstalledExtensionProposals(existing: string[], additionalText: string, range: vscode.Range, includeBuiltinExtensions: boolean): Promise<vscode.CompletionItem[] | vscode.CompletionList> {
if (Array.isArray(existing)) {
const extensions = includeBuiltinExtensions ? vscode.extensions.all : vscode.extensions.all.filter(e => !(e.id.startsWith('vscode.') || e.id === 'Microsoft.vscode-markdown'));
const knownExtensionProposals = extensions.filter(e => existing.indexOf(e.id) === -1);
@@ -30,10 +30,10 @@ export function provideInstalledExtensionProposals(existing: string[], additiona
return [example];
}
}
return undefined;
return [];
}
export function provideWorkspaceTrustExtensionProposals(existing: string[], range: vscode.Range): vscode.ProviderResult<vscode.CompletionItem[] | vscode.CompletionList> {
export async function provideWorkspaceTrustExtensionProposals(existing: string[], range: vscode.Range): Promise<vscode.CompletionItem[] | vscode.CompletionList> {
if (Array.isArray(existing)) {
const extensions = vscode.extensions.all.filter(e => e.packageJSON.main);
const extensionProposals = extensions.filter(e => existing.indexOf(e.id) === -1);
@@ -56,5 +56,5 @@ export function provideWorkspaceTrustExtensionProposals(existing: string[], rang
}
}
return undefined;
return [];
}

View File

@@ -15,32 +15,27 @@ export class SettingsDocument {
constructor(private document: vscode.TextDocument) { }
public provideCompletionItems(position: vscode.Position, _token: vscode.CancellationToken): vscode.ProviderResult<vscode.CompletionItem[] | vscode.CompletionList> {
public async provideCompletionItems(position: vscode.Position, _token: vscode.CancellationToken): Promise<vscode.CompletionItem[] | vscode.CompletionList> {
const location = getLocation(this.document.getText(), this.document.offsetAt(position));
const range = this.document.getWordRangeAtPosition(position) || new vscode.Range(position, position);
// window.title
if (location.path[0] === 'window.title') {
return this.provideWindowTitleCompletionItems(location, range);
return this.provideWindowTitleCompletionItems(location, position);
}
// files.association
if (location.path[0] === 'files.associations') {
return this.provideFilesAssociationsCompletionItems(location, range);
return this.provideFilesAssociationsCompletionItems(location, position);
}
// files.exclude, search.exclude
if (location.path[0] === 'files.exclude' || location.path[0] === 'search.exclude') {
return this.provideExcludeCompletionItems(location, range);
return this.provideExcludeCompletionItems(location, position);
}
// files.defaultLanguage
if (location.path[0] === 'files.defaultLanguage') {
return this.provideLanguageCompletionItems(location, range).then(items => {
// Add special item '${activeEditorLanguage}'
return [this.newSimpleCompletionItem(JSON.stringify('${activeEditorLanguage}'), range, localize('activeEditor', "Use the language of the currently active text editor if any")), ...items];
});
return this.provideLanguageCompletionItems(location, position);
}
// settingsSync.ignoredExtensions
@@ -49,6 +44,7 @@ export class SettingsDocument {
try {
ignoredExtensions = parse(this.document.getText())['settingsSync.ignoredExtensions'];
} catch (e) {/* ignore error */ }
const range = this.getReplaceRange(location, position);
return provideInstalledExtensionProposals(ignoredExtensions, '', range, true);
}
@@ -58,44 +54,85 @@ export class SettingsDocument {
try {
alreadyConfigured = Object.keys(parse(this.document.getText())['remote.extensionKind']);
} catch (e) {/* ignore error */ }
return provideInstalledExtensionProposals(alreadyConfigured, `: [\n\t"ui"\n]`, range, true);
const range = this.getReplaceRange(location, position);
return provideInstalledExtensionProposals(alreadyConfigured, location.previousNode ? '' : `: [\n\t"ui"\n]`, range, true);
}
// remote.portsAttributes
if (location.path[0] === 'remote.portsAttributes' && location.path.length === 2 && location.isAtPropertyKey) {
return this.providePortsAttributesCompletionItem(range);
return this.providePortsAttributesCompletionItem(this.getReplaceRange(location, position));
}
return this.provideLanguageOverridesCompletionItems(location, position);
}
private provideWindowTitleCompletionItems(_location: Location, range: vscode.Range): vscode.ProviderResult<vscode.CompletionItem[]> {
const completions: vscode.CompletionItem[] = [];
completions.push(this.newSimpleCompletionItem('${activeEditorShort}', range, localize('activeEditorShort', "the file name (e.g. myFile.txt)")));
completions.push(this.newSimpleCompletionItem('${activeEditorMedium}', range, localize('activeEditorMedium', "the path of the file relative to the workspace folder (e.g. myFolder/myFileFolder/myFile.txt)")));
completions.push(this.newSimpleCompletionItem('${activeEditorLong}', range, localize('activeEditorLong', "the full path of the file (e.g. /Users/Development/myFolder/myFileFolder/myFile.txt)")));
completions.push(this.newSimpleCompletionItem('${activeFolderShort}', range, localize('activeFolderShort', "the name of the folder the file is contained in (e.g. myFileFolder)")));
completions.push(this.newSimpleCompletionItem('${activeFolderMedium}', range, localize('activeFolderMedium', "the path of the folder the file is contained in, relative to the workspace folder (e.g. myFolder/myFileFolder)")));
completions.push(this.newSimpleCompletionItem('${activeFolderLong}', range, localize('activeFolderLong', "the full path of the folder the file is contained in (e.g. /Users/Development/myFolder/myFileFolder)")));
completions.push(this.newSimpleCompletionItem('${rootName}', range, localize('rootName', "name of the workspace (e.g. myFolder or myWorkspace)")));
completions.push(this.newSimpleCompletionItem('${rootPath}', range, localize('rootPath', "file path of the workspace (e.g. /Users/Development/myWorkspace)")));
completions.push(this.newSimpleCompletionItem('${folderName}', range, localize('folderName', "name of the workspace folder the file is contained in (e.g. myFolder)")));
completions.push(this.newSimpleCompletionItem('${folderPath}', range, localize('folderPath', "file path of the workspace folder the file is contained in (e.g. /Users/Development/myFolder)")));
completions.push(this.newSimpleCompletionItem('${appName}', range, localize('appName', "e.g. VS Code")));
completions.push(this.newSimpleCompletionItem('${remoteName}', range, localize('remoteName', "e.g. SSH")));
completions.push(this.newSimpleCompletionItem('${dirty}', range, localize('dirty', "an indicator for when the active editor has unsaved changes")));
completions.push(this.newSimpleCompletionItem('${separator}', range, localize('separator', "a conditional separator (' - ') that only shows when surrounded by variables with values")));
return Promise.resolve(completions);
private getReplaceRange(location: Location, position: vscode.Position) {
const node = location.previousNode;
if (node) {
const nodeStart = this.document.positionAt(node.offset), nodeEnd = this.document.positionAt(node.offset + node.length);
if (nodeStart.isBeforeOrEqual(position) && nodeEnd.isAfterOrEqual(position)) {
return new vscode.Range(nodeStart, nodeEnd);
}
}
return new vscode.Range(position, position);
}
private provideFilesAssociationsCompletionItems(location: Location, range: vscode.Range): vscode.ProviderResult<vscode.CompletionItem[]> {
private isCompletingPropertyValue(location: Location, pos: vscode.Position) {
if (location.isAtPropertyKey) {
return false;
}
const previousNode = location.previousNode;
if (previousNode) {
const offset = this.document.offsetAt(pos);
return offset >= previousNode.offset && offset <= previousNode.offset + previousNode.length;
}
return true;
}
private async provideWindowTitleCompletionItems(location: Location, pos: vscode.Position): Promise<vscode.CompletionItem[]> {
const completions: vscode.CompletionItem[] = [];
if (!this.isCompletingPropertyValue(location, pos)) {
return completions;
}
let range = this.document.getWordRangeAtPosition(pos, /\$\{[^"\}]*\}?/);
if (!range || range.start.isEqual(pos) || range.end.isEqual(pos) && this.document.getText(range).endsWith('}')) {
range = new vscode.Range(pos, pos);
}
const getText = (variable: string) => {
const text = '${' + variable + '}';
return location.previousNode ? text : JSON.stringify(text);
};
completions.push(this.newSimpleCompletionItem(getText('activeEditorShort'), range, localize('activeEditorShort', "the file name (e.g. myFile.txt)")));
completions.push(this.newSimpleCompletionItem(getText('activeEditorMedium'), range, localize('activeEditorMedium', "the path of the file relative to the workspace folder (e.g. myFolder/myFileFolder/myFile.txt)")));
completions.push(this.newSimpleCompletionItem(getText('activeEditorLong'), range, localize('activeEditorLong', "the full path of the file (e.g. /Users/Development/myFolder/myFileFolder/myFile.txt)")));
completions.push(this.newSimpleCompletionItem(getText('activeFolderShort'), range, localize('activeFolderShort', "the name of the folder the file is contained in (e.g. myFileFolder)")));
completions.push(this.newSimpleCompletionItem(getText('activeFolderMedium'), range, localize('activeFolderMedium', "the path of the folder the file is contained in, relative to the workspace folder (e.g. myFolder/myFileFolder)")));
completions.push(this.newSimpleCompletionItem(getText('activeFolderLong'), range, localize('activeFolderLong', "the full path of the folder the file is contained in (e.g. /Users/Development/myFolder/myFileFolder)")));
completions.push(this.newSimpleCompletionItem(getText('rootName'), range, localize('rootName', "name of the workspace (e.g. myFolder or myWorkspace)")));
completions.push(this.newSimpleCompletionItem(getText('rootPath'), range, localize('rootPath', "file path of the workspace (e.g. /Users/Development/myWorkspace)")));
completions.push(this.newSimpleCompletionItem(getText('folderName'), range, localize('folderName', "name of the workspace folder the file is contained in (e.g. myFolder)")));
completions.push(this.newSimpleCompletionItem(getText('folderPath'), range, localize('folderPath', "file path of the workspace folder the file is contained in (e.g. /Users/Development/myFolder)")));
completions.push(this.newSimpleCompletionItem(getText('appName'), range, localize('appName', "e.g. VS Code")));
completions.push(this.newSimpleCompletionItem(getText('remoteName'), range, localize('remoteName', "e.g. SSH")));
completions.push(this.newSimpleCompletionItem(getText('dirty'), range, localize('dirty', "an indicator for when the active editor has unsaved changes")));
completions.push(this.newSimpleCompletionItem(getText('separator'), range, localize('separator', "a conditional separator (' - ') that only shows when surrounded by variables with values")));
return completions;
}
private async provideFilesAssociationsCompletionItems(location: Location, position: vscode.Position): Promise<vscode.CompletionItem[]> {
const completions: vscode.CompletionItem[] = [];
if (location.path.length === 2) {
// Key
if (!location.isAtPropertyKey || location.path[1] === '') {
if (location.path[1] === '') {
const range = this.getReplaceRange(location, position);
completions.push(this.newSnippetCompletionItem({
label: localize('assocLabelFile', "Files with Extension"),
documentation: localize('assocDescriptionFile', "Map all files matching the glob pattern in their filename to the language with the given identifier."),
@@ -109,68 +146,68 @@ export class SettingsDocument {
snippet: location.isAtPropertyKey ? '"/${1:path to file}/*.${2:extension}": "${3:language}"' : '{ "/${1:path to file}/*.${2:extension}": "${3:language}" }',
range
}));
} else {
} else if (this.isCompletingPropertyValue(location, position)) {
// Value
return this.provideLanguageCompletionItemsForLanguageOverrides(location, range);
return this.provideLanguageCompletionItemsForLanguageOverrides(this.getReplaceRange(location, position));
}
}
return Promise.resolve(completions);
return completions;
}
private provideExcludeCompletionItems(location: Location, range: vscode.Range): vscode.ProviderResult<vscode.CompletionItem[]> {
private async provideExcludeCompletionItems(location: Location, position: vscode.Position): Promise<vscode.CompletionItem[]> {
const completions: vscode.CompletionItem[] = [];
// Key
if (location.path.length === 1) {
if (location.path.length === 1 || (location.path.length === 2 && location.path[1] === '')) {
const range = this.getReplaceRange(location, position);
completions.push(this.newSnippetCompletionItem({
label: localize('fileLabel', "Files by Extension"),
documentation: localize('fileDescription', "Match all files of a specific file extension."),
snippet: location.isAtPropertyKey ? '"**/*.${1:extension}": true' : '{ "**/*.${1:extension}": true }',
snippet: location.path.length === 2 ? '"**/*.${1:extension}": true' : '{ "**/*.${1:extension}": true }',
range
}));
completions.push(this.newSnippetCompletionItem({
label: localize('filesLabel', "Files with Multiple Extensions"),
documentation: localize('filesDescription', "Match all files with any of the file extensions."),
snippet: location.isAtPropertyKey ? '"**/*.{ext1,ext2,ext3}": true' : '{ "**/*.{ext1,ext2,ext3}": true }',
snippet: location.path.length === 2 ? '"**/*.{ext1,ext2,ext3}": true' : '{ "**/*.{ext1,ext2,ext3}": true }',
range
}));
completions.push(this.newSnippetCompletionItem({
label: localize('derivedLabel', "Files with Siblings by Name"),
documentation: localize('derivedDescription', "Match files that have siblings with the same name but a different extension."),
snippet: location.isAtPropertyKey ? '"**/*.${1:source-extension}": { "when": "$(basename).${2:target-extension}" }' : '{ "**/*.${1:source-extension}": { "when": "$(basename).${2:target-extension}" } }',
snippet: location.path.length === 2 ? '"**/*.${1:source-extension}": { "when": "$(basename).${2:target-extension}" }' : '{ "**/*.${1:source-extension}": { "when": "$(basename).${2:target-extension}" } }',
range
}));
completions.push(this.newSnippetCompletionItem({
label: localize('topFolderLabel', "Folder by Name (Top Level)"),
documentation: localize('topFolderDescription', "Match a top level folder with a specific name."),
snippet: location.isAtPropertyKey ? '"${1:name}": true' : '{ "${1:name}": true }',
snippet: location.path.length === 2 ? '"${1:name}": true' : '{ "${1:name}": true }',
range
}));
completions.push(this.newSnippetCompletionItem({
label: localize('topFoldersLabel', "Folders with Multiple Names (Top Level)"),
documentation: localize('topFoldersDescription', "Match multiple top level folders."),
snippet: location.isAtPropertyKey ? '"{folder1,folder2,folder3}": true' : '{ "{folder1,folder2,folder3}": true }',
snippet: location.path.length === 2 ? '"{folder1,folder2,folder3}": true' : '{ "{folder1,folder2,folder3}": true }',
range
}));
completions.push(this.newSnippetCompletionItem({
label: localize('folderLabel', "Folder by Name (Any Location)"),
documentation: localize('folderDescription', "Match a folder with a specific name in any location."),
snippet: location.isAtPropertyKey ? '"**/${1:name}": true' : '{ "**/${1:name}": true }',
snippet: location.path.length === 2 ? '"**/${1:name}": true' : '{ "**/${1:name}": true }',
range
}));
}
// Value
else {
completions.push(this.newSimpleCompletionItem('false', range, localize('falseDescription', "Disable the pattern.")));
completions.push(this.newSimpleCompletionItem('true', range, localize('trueDescription', "Enable the pattern.")));
else if (location.path.length === 2 && this.isCompletingPropertyValue(location, position)) {
const range = this.getReplaceRange(location, position);
completions.push(this.newSnippetCompletionItem({
label: localize('derivedLabel', "Files with Siblings by Name"),
documentation: localize('siblingsDescription', "Match files that have siblings with the same name but a different extension."),
@@ -179,15 +216,22 @@ export class SettingsDocument {
}));
}
return Promise.resolve(completions);
return completions;
}
private provideLanguageCompletionItems(_location: Location, range: vscode.Range, formatFunc: (string: string) => string = (l) => JSON.stringify(l)): Thenable<vscode.CompletionItem[]> {
return vscode.languages.getLanguages()
.then(languages => languages.map(l => this.newSimpleCompletionItem(formatFunc(l), range)));
private async provideLanguageCompletionItems(location: Location, position: vscode.Position): Promise<vscode.CompletionItem[]> {
if (location.path.length === 1 && this.isCompletingPropertyValue(location, position)) {
const range = this.getReplaceRange(location, position);
const languages = await vscode.languages.getLanguages();
return [
this.newSimpleCompletionItem(JSON.stringify('${activeEditorLanguage}'), range, localize('activeEditor', "Use the language of the currently active text editor if any")),
...languages.map(l => this.newSimpleCompletionItem(JSON.stringify(l), range))
];
}
return [];
}
private async provideLanguageCompletionItemsForLanguageOverrides(_location: Location, range: vscode.Range): Promise<vscode.CompletionItem[]> {
private async provideLanguageCompletionItemsForLanguageOverrides(range: vscode.Range): Promise<vscode.CompletionItem[]> {
const languages = await vscode.languages.getLanguages();
const completionItems = [];
for (const language of languages) {
@@ -200,7 +244,7 @@ export class SettingsDocument {
}
private async provideLanguageOverridesCompletionItems(location: Location, position: vscode.Position): Promise<vscode.CompletionItem[]> {
if (location.path.length === 1 && location.previousNode && typeof location.previousNode.value === 'string' && location.previousNode.value.startsWith('[')) {
if (location.path.length === 1 && location.isAtPropertyKey && location.previousNode && typeof location.previousNode.value === 'string' && location.previousNode.value.startsWith('[')) {
const startPosition = this.document.positionAt(location.previousNode.offset + 1);
const endPosition = startPosition.translate(undefined, location.previousNode.value.length);
const donotSuggestLanguages: string[] = [];

View File

@@ -0,0 +1,594 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import * as assert from 'assert';
import { promises as fs } from 'fs';
import * as path from 'path';
import * as os from 'os';
import 'mocha';
const testFolder = fs.mkdtemp(path.join(os.tmpdir(), 'conf-editing-'));
suite('Completions in settings.json', () => {
const testFile = 'settings.json';
test('window.title', async () => {
{ // inserting after text
const content = [
'{',
' "window.title": "custom|"',
'}',
].join('\n');
const resultText = [
'{',
' "window.title": "custom${activeEditorShort}"',
'}',
].join('\n');
const expected = { label: '${activeEditorShort}', resultText };
await testCompletion(testFile, 'jsonc', content, expected);
}
{ // inserting before a variable
const content = [
'{',
' "window.title": "|${activeEditorShort}"',
'}',
].join('\n');
const resultText = [
'{',
' "window.title": "${folderPath}${activeEditorShort}"',
'}',
].join('\n');
const expected = { label: '${folderPath}', resultText };
await testCompletion(testFile, 'jsonc', content, expected);
}
{ // inserting after a variable
const content = [
'{',
' "window.title": "${activeEditorShort}|"',
'}',
].join('\n');
const resultText = [
'{',
' "window.title": "${activeEditorShort}${folderPath}"',
'}',
].join('\n');
const expected = { label: '${folderPath}', resultText };
await testCompletion(testFile, 'jsonc', content, expected);
}
{ // replacing an variable
const content = [
'{',
' "window.title": "${a|ctiveEditorShort}"',
'}',
].join('\n');
const resultText = [
'{',
' "window.title": "${activeEditorMedium}"',
'}',
].join('\n');
const expected = { label: '${activeEditorMedium}', resultText };
await testCompletion(testFile, 'jsonc', content, expected);
}
{ // replacing a partial variable
const content = [
'{',
' "window.title": "${a|"',
'}',
].join('\n');
const resultText = [
'{',
' "window.title": "${dirty}"',
'}',
].join('\n');
const expected = { label: '${dirty}', resultText };
await testCompletion(testFile, 'jsonc', content, expected);
}
{ // inserting a literal
const content = [
'{',
' "window.title": |',
'}',
].join('\n');
const resultText = [
'{',
' "window.title": "${activeEditorMedium}"',
'}',
].join('\n');
const expected = { label: '"${activeEditorMedium}"', resultText };
await testCompletion(testFile, 'jsonc', content, expected);
}
{ // no proposals after literal
const content = [
'{',
' "window.title": "${activeEditorShort}" |',
'}',
].join('\n');
const expected = { label: '${activeEditorMedium}', notAvailable: true };
await testCompletion(testFile, 'jsonc', content, expected);
}
});
test('files.associations', async () => {
{
const content = [
'{',
' "files.associations": {',
' |',
' }',
'}',
].join('\n');
const resultText = [
'{',
' "files.associations": {',
' "*.${1:extension}": "${2:language}"',
' }',
'}',
].join('\n');
const expected = { label: 'Files with Extension', resultText };
await testCompletion(testFile, 'jsonc', content, expected);
}
{
const content = [
'{',
' "files.associations": {',
' |',
' }',
'}',
].join('\n');
const resultText = [
'{',
' "files.associations": {',
' "/${1:path to file}/*.${2:extension}": "${3:language}"',
' }',
'}',
].join('\n');
const expected = { label: 'Files with Path', resultText };
await testCompletion(testFile, 'jsonc', content, expected);
}
{
const content = [
'{',
' "files.associations": {',
' "*.extension": "|bat"',
' }',
'}',
].join('\n');
const resultText = [
'{',
' "files.associations": {',
' "*.extension": "json"',
' }',
'}',
].join('\n');
const expected = { label: '"json"', resultText };
await testCompletion(testFile, 'jsonc', content, expected);
}
{
const content = [
'{',
' "files.associations": {',
' "*.extension": "bat"|',
' }',
'}',
].join('\n');
const resultText = [
'{',
' "files.associations": {',
' "*.extension": "json"',
' }',
'}',
].join('\n');
const expected = { label: '"json"', resultText };
await testCompletion(testFile, 'jsonc', content, expected);
}
{
const content = [
'{',
' "files.associations": {',
' "*.extension": "bat" |',
' }',
'}',
].join('\n');
const expected = { label: '"json"', notAvailable: true };
await testCompletion(testFile, 'jsonc', content, expected);
}
});
test('files.exclude', async () => {
{
const content = [
'{',
' "files.exclude": {',
' |',
' }',
'}',
].join('\n');
const resultText = [
'{',
' "files.exclude": {',
' "**/*.${1:extension}": true',
' }',
'}',
].join('\n');
const expected = { label: 'Files by Extension', resultText };
await testCompletion(testFile, 'jsonc', content, expected);
}
{
const content = [
'{',
' "files.exclude": {',
' "**/*.extension": |true',
' }',
'}',
].join('\n');
const resultText = [
'{',
' "files.exclude": {',
' "**/*.extension": { "when": "$(basename).${1:extension}" }',
' }',
'}',
].join('\n');
const expected = { label: 'Files with Siblings by Name', resultText };
await testCompletion(testFile, 'jsonc', content, expected);
}
});
test('files.defaultLanguage', async () => {
{
const content = [
'{',
' "files.defaultLanguage": "json|"',
'}',
].join('\n');
const resultText = [
'{',
' "files.defaultLanguage": "jsonc"',
'}',
].join('\n');
const expected = { label: '"jsonc"', resultText };
await testCompletion(testFile, 'jsonc', content, expected);
}
{
const content = [
'{',
' "files.defaultLanguage": |',
'}',
].join('\n');
const resultText = [
'{',
' "files.defaultLanguage": "jsonc"',
'}',
].join('\n');
const expected = { label: '"jsonc"', resultText };
await testCompletion(testFile, 'jsonc', content, expected);
}
});
test('remote.extensionKind', async () => {
{
const content = [
'{',
'\t"remote.extensionKind": {',
'\t\t|',
'\t}',
'}',
].join('\n');
const expected = { label: 'vscode.npm' };
await testCompletion(testFile, 'jsonc', content, expected);
}
});
test('remote.portsAttributes', async () => {
{
const content = [
'{',
' "remote.portsAttributes": {',
' |',
' }',
'}',
].join('\n');
const expected = { label: '"3000"' };
await testCompletion(testFile, 'jsonc', content, expected);
}
});
});
suite('Completions in extensions.json', () => {
const testFile = 'extensions.json';
test('change recommendation', async () => {
{
const content = [
'{',
' "recommendations": [',
' "|a.b"',
' ]',
'}',
].join('\n');
const resultText = [
'{',
' "recommendations": [',
' "ms-vscode.js-debug"',
' ]',
'}',
].join('\n');
const expected = { label: 'ms-vscode.js-debug', resultText };
await testCompletion(testFile, 'jsonc', content, expected);
}
});
test('add recommendation', async () => {
{
const content = [
'{',
' "recommendations": [',
' |',
' ]',
'}',
].join('\n');
const resultText = [
'{',
' "recommendations": [',
' "ms-vscode.js-debug"',
' ]',
'}',
].join('\n');
const expected = { label: 'ms-vscode.js-debug', resultText };
await testCompletion(testFile, 'jsonc', content, expected);
}
});
});
suite('Completions in launch.json', () => {
const testFile = 'launch.json';
test('variable completions', async () => {
{
const content = [
'{',
' "version": "0.2.0",',
' "configurations": [',
' {',
' "name": "Run Extension",',
' "type": "extensionHost",',
' "preLaunchTask": "${|defaultBuildTask}"',
' }',
' ]',
'}',
].join('\n');
const resultText = [
'{',
' "version": "0.2.0",',
' "configurations": [',
' {',
' "name": "Run Extension",',
' "type": "extensionHost",',
' "preLaunchTask": "${cwd}"',
' }',
' ]',
'}',
].join('\n');
const expected = { label: '${cwd}', resultText };
await testCompletion(testFile, 'jsonc', content, expected);
}
{
const content = [
'{',
' "version": "0.2.0",',
' "configurations": [',
' {',
' "name": "Run Extension",',
' "type": "extensionHost",',
' "preLaunchTask": "|${defaultBuildTask}"',
' }',
' ]',
'}',
].join('\n');
const resultText = [
'{',
' "version": "0.2.0",',
' "configurations": [',
' {',
' "name": "Run Extension",',
' "type": "extensionHost",',
' "preLaunchTask": "${cwd}${defaultBuildTask}"',
' }',
' ]',
'}',
].join('\n');
const expected = { label: '${cwd}', resultText };
await testCompletion(testFile, 'jsonc', content, expected);
}
{
const content = [
'{',
' "version": "0.2.0",',
' "configurations": [',
' {',
' "name": "Do It",',
' "program": "${workspace|"',
' }',
' ]',
'}',
].join('\n');
const resultText = [
'{',
' "version": "0.2.0",',
' "configurations": [',
' {',
' "name": "Do It",',
' "program": "${cwd}"',
' }',
' ]',
'}',
].join('\n');
const expected = { label: '${cwd}', resultText };
await testCompletion(testFile, 'jsonc', content, expected);
}
});
});
suite('Completions in tasks.json', () => {
const testFile = 'tasks.json';
test('variable completions', async () => {
{
const content = [
'{',
' "version": "0.2.0",',
' "tasks": [',
' {',
' "type": "shell",',
' "command": "${|defaultBuildTask}"',
' }',
' ]',
'}',
].join('\n');
const resultText = [
'{',
' "version": "0.2.0",',
' "tasks": [',
' {',
' "type": "shell",',
' "command": "${cwd}"',
' }',
' ]',
'}',
].join('\n');
const expected = { label: '${cwd}', resultText };
await testCompletion(testFile, 'jsonc', content, expected);
}
{
const content = [
'{',
' "version": "0.2.0",',
' "tasks": [',
' {',
' "type": "shell",',
' "command": "${defaultBuildTask}|"',
' }',
' ]',
'}',
].join('\n');
const resultText = [
'{',
' "version": "0.2.0",',
' "tasks": [',
' {',
' "type": "shell",',
' "command": "${defaultBuildTask}${cwd}"',
' }',
' ]',
'}',
].join('\n');
const expected = { label: '${cwd}', resultText };
await testCompletion(testFile, 'jsonc', content, expected);
}
});
});
suite('Completions in keybindings.json', () => {
const testFile = 'keybindings.json';
test('context key insertion', async () => {
{
const content = [
'[',
' {',
' "key": "ctrl+k ctrl+,",',
' "command": "editor.jumpToNextFold",',
' "when": "|"',
' }',
']',
].join('\n');
const resultText = [
'[',
' {',
' "key": "ctrl+k ctrl+,",',
' "command": "editor.jumpToNextFold",',
' "when": "resourcePath"',
' }',
']',
].join('\n');
const expected = { label: 'resourcePath', resultText };
await testCompletion(testFile, 'jsonc', content, expected);
}
});
test('context key replace', async () => {
{
const content = [
'[',
' {',
' "key": "ctrl+k ctrl+,",',
' "command": "editor.jumpToNextFold",',
' "when": "resou|rcePath"',
' }',
']',
].join('\n');
const resultText = [
'[',
' {',
' "key": "ctrl+k ctrl+,",',
' "command": "editor.jumpToNextFold",',
' "when": "resource"',
' }',
']',
].join('\n');
const expected = { label: 'resource', resultText };
await testCompletion(testFile, 'jsonc', content, expected);
}
});
});
interface ItemDescription {
label: string;
resultText?: string;
notAvailable?: boolean;
}
async function testCompletion(testFileName: string, languageId: string, content: string, expected: ItemDescription) {
const offset = content.indexOf('|');
content = content.substring(0, offset) + content.substring(offset + 1);
const docUri = vscode.Uri.file(path.join(await testFolder, testFileName));
await fs.writeFile(docUri.fsPath, content);
const editor = await setTestContent(docUri, languageId, content);
const position = editor.document.positionAt(offset);
// Executing the command `vscode.executeCompletionItemProvider` to simulate triggering completion
const actualCompletions = (await vscode.commands.executeCommand('vscode.executeCompletionItemProvider', docUri, position)) as vscode.CompletionList;
const matches = actualCompletions.items.filter(completion => {
return completion.label === expected.label;
});
if (expected.notAvailable) {
assert.strictEqual(matches.length, 0, `${expected.label} should not existing is results`);
} else {
assert.strictEqual(matches.length, 1, `${expected.label} should only existing once: Actual: ${actualCompletions.items.map(c => c.label).join(', ')}`);
if (expected.resultText) {
const match = matches[0];
if (match.range && match.insertText) {
const range = match.range instanceof vscode.Range ? match.range : match.range.replacing;
const text = typeof match.insertText === 'string' ? match.insertText : match.insertText.value;
await editor.edit(eb => eb.replace(range, text));
assert.strictEqual(editor.document.getText(), expected.resultText);
} else {
assert.fail(`Range or insertText missing`);
}
}
}
}
async function setTestContent(docUri: vscode.Uri, languageId: string, content: string): Promise<vscode.TextEditor> {
const ext = vscode.extensions.getExtension('vscode.configuration-editing')!;
await ext.activate();
const doc = await vscode.workspace.openTextDocument(docUri);
await vscode.languages.setTextDocumentLanguage(doc, languageId);
const editor = await vscode.window.showTextDocument(doc);
const fullRange = new vscode.Range(new vscode.Position(0, 0), doc.positionAt(doc.getText().length));
await editor.edit(eb => eb.replace(fullRange, content));
return editor;
}