Archived
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:
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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');
|
||||
|
||||
@@ -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 [];
|
||||
}
|
||||
|
||||
@@ -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[] = [];
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationToken, Connection, Diagnostic, Disposable, DocumentDiagnosticParams, DocumentDiagnosticReport, DocumentDiagnosticReportKind, TextDocuments } from 'vscode-languageserver';
|
||||
import { TextDocument } from 'vscode-css-languageservice';
|
||||
import { formatError, runSafeAsync } from './runner';
|
||||
import { RuntimeEnvironment } from '../cssServer';
|
||||
|
||||
export type Validator = (textDocument: TextDocument) => Promise<Diagnostic[]>;
|
||||
export type DiagnosticsSupport = {
|
||||
dispose(): void;
|
||||
requestRefresh(): void;
|
||||
};
|
||||
|
||||
export function registerDiagnosticsPushSupport(documents: TextDocuments<TextDocument>, connection: Connection, runtime: RuntimeEnvironment, validate: Validator): DiagnosticsSupport {
|
||||
|
||||
const pendingValidationRequests: { [uri: string]: Disposable } = {};
|
||||
const validationDelayMs = 500;
|
||||
|
||||
const disposables: Disposable[] = [];
|
||||
|
||||
// The content of a text document has changed. This event is emitted
|
||||
// when the text document first opened or when its content has changed.
|
||||
documents.onDidChangeContent(change => {
|
||||
triggerValidation(change.document);
|
||||
}, undefined, disposables);
|
||||
|
||||
// a document has closed: clear all diagnostics
|
||||
documents.onDidClose(event => {
|
||||
cleanPendingValidation(event.document);
|
||||
connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] });
|
||||
}, undefined, disposables);
|
||||
|
||||
function cleanPendingValidation(textDocument: TextDocument): void {
|
||||
const request = pendingValidationRequests[textDocument.uri];
|
||||
if (request) {
|
||||
request.dispose();
|
||||
delete pendingValidationRequests[textDocument.uri];
|
||||
}
|
||||
}
|
||||
|
||||
function triggerValidation(textDocument: TextDocument): void {
|
||||
cleanPendingValidation(textDocument);
|
||||
const request = pendingValidationRequests[textDocument.uri] = runtime.timer.setTimeout(async () => {
|
||||
if (request === pendingValidationRequests[textDocument.uri]) {
|
||||
try {
|
||||
const diagnostics = await validate(textDocument);
|
||||
if (request === pendingValidationRequests[textDocument.uri]) {
|
||||
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
|
||||
}
|
||||
delete pendingValidationRequests[textDocument.uri];
|
||||
} catch (e) {
|
||||
connection.console.error(formatError(`Error while validating ${textDocument.uri}`, e));
|
||||
}
|
||||
}
|
||||
}, validationDelayMs);
|
||||
}
|
||||
|
||||
return {
|
||||
requestRefresh: () => {
|
||||
documents.all().forEach(triggerValidation);
|
||||
},
|
||||
dispose: () => {
|
||||
disposables.forEach(d => d.dispose());
|
||||
disposables.length = 0;
|
||||
const keys = Object.keys(pendingValidationRequests);
|
||||
for (const key of keys) {
|
||||
pendingValidationRequests[key].dispose();
|
||||
delete pendingValidationRequests[key];
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function registerDiagnosticsPullSupport(documents: TextDocuments<TextDocument>, connection: Connection, runtime: RuntimeEnvironment, validate: Validator): DiagnosticsSupport {
|
||||
|
||||
function newDocumentDiagnosticReport(diagnostics: Diagnostic[]): DocumentDiagnosticReport {
|
||||
return {
|
||||
kind: DocumentDiagnosticReportKind.Full,
|
||||
items: diagnostics
|
||||
};
|
||||
}
|
||||
|
||||
const registration = connection.languages.diagnostics.on(async (params: DocumentDiagnosticParams, token: CancellationToken) => {
|
||||
return runSafeAsync(runtime, async () => {
|
||||
const document = documents.get(params.textDocument.uri);
|
||||
if (document) {
|
||||
return newDocumentDiagnosticReport(await validate(document));
|
||||
}
|
||||
return newDocumentDiagnosticReport([]);
|
||||
|
||||
}, newDocumentDiagnosticReport([]), `Error while computing diagnostics for ${params.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
function requestRefresh(): void {
|
||||
connection.languages.diagnostics.refresh();
|
||||
}
|
||||
|
||||
return {
|
||||
requestRefresh,
|
||||
dispose: () => {
|
||||
registration.dispose();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
declare module 'markdown-it-named-headers' { }
|
||||
declare module 'markdown-it-named-headers' { }
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"git": {
|
||||
"name": "ionide/ionide-fsgrammar",
|
||||
"repositoryUrl": "https://github.com/ionide/ionide-fsgrammar",
|
||||
"commitHash": "8825a76681cdc14801b5d9490372ff67ec6b9711"
|
||||
"commitHash": "e177bd7f9d3402f70d2f1fb42c74057ed1ccf6fa"
|
||||
}
|
||||
},
|
||||
"license": "MIT",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"If you want to provide a fix or improvement, please create a pull request against the original repository.",
|
||||
"Once accepted there, we are happy to receive an update request."
|
||||
],
|
||||
"version": "https://github.com/ionide/ionide-fsgrammar/commit/8825a76681cdc14801b5d9490372ff67ec6b9711",
|
||||
"version": "https://github.com/ionide/ionide-fsgrammar/commit/e177bd7f9d3402f70d2f1fb42c74057ed1ccf6fa",
|
||||
"name": "fsharp",
|
||||
"scopeName": "source.fsharp",
|
||||
"patterns": [
|
||||
@@ -958,6 +958,26 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "binding.fsharp",
|
||||
"begin": "\\b(use|use\\!|and|and!)\\s*(\\[[^-=]*\\]|[_[:alpha:]]([_[:alpha:]0-9\\._]+)*|``[_[:alpha:]]([_[:alpha:]0-9\\._`\\s]+|(?<=,)\\s)*)?",
|
||||
"end": "\\s*(=)",
|
||||
"beginCaptures": {
|
||||
"1": {
|
||||
"name": "keyword.fsharp"
|
||||
}
|
||||
},
|
||||
"endCaptures": {
|
||||
"1": {
|
||||
"name": "keyword.fsharp"
|
||||
}
|
||||
},
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#common_binding_definition"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "binding.fsharp",
|
||||
"begin": "(?<=with|and)\\s*\\b((get|set)\\s*(?=\\())(\\[[^-=]*\\]|[_[:alpha:]]([_[:alpha:]0-9\\._]+)*|``[_[:alpha:]]([_[:alpha:]0-9\\._`\\s]+|(?<=,)\\s)*)?",
|
||||
@@ -1275,11 +1295,11 @@
|
||||
},
|
||||
{
|
||||
"name": "constant.character.string.escape.fsharp",
|
||||
"match": "\\\\([\\\\''ntbr]|x[a-fA-F0-9]{2}|u[a-fA-F0-9]{4}|u[a-fA-F0-9]{8})"
|
||||
"match": "\\\\(['\"\\\\abfnrtv]|([01][0-9][0-9]|2[0-4][0-9]|25[0-5])|(x[0-9a-fA-F]{2})|(u[0-9a-fA-F]{4})|(U00(0[0-9a-fA-F]|10)[0-9a-fA-F]{4}))"
|
||||
},
|
||||
{
|
||||
"name": "invalid.illeagal.character.string.fsharp",
|
||||
"match": "\\\\(?![\\\\''ntbr]|x[a-fA-F0-9]{2}|u[a-fA-F0-9]{4}|u[a-fA-F0-9]{8})."
|
||||
"name": "invalid.illegal.character.string.fsharp",
|
||||
"match": "\\\\(([0-9]{1,3})|(x[^\\s]{0,2})|(u[^\\s]{0,4})|(U[^\\s]{0,8})|[^\\s])"
|
||||
},
|
||||
{
|
||||
"include": "#string_formatter"
|
||||
|
||||
@@ -49,11 +49,12 @@ class RemoteSourceProviderQuickPick {
|
||||
@throttle
|
||||
private async query(): Promise<void> {
|
||||
try {
|
||||
const remoteSources = await this.provider.getRemoteSources(this.quickpick?.value) || [];
|
||||
|
||||
this.ensureQuickPick();
|
||||
this.quickpick!.busy = true;
|
||||
this.quickpick!.show();
|
||||
|
||||
const remoteSources = await this.provider.getRemoteSources(this.quickpick?.value) || [];
|
||||
|
||||
if (remoteSources.length === 0) {
|
||||
this.quickpick!.items = [{
|
||||
label: localize('none found', "No remote repositories found."),
|
||||
@@ -69,7 +70,7 @@ class RemoteSourceProviderQuickPick {
|
||||
}));
|
||||
}
|
||||
} catch (err) {
|
||||
this.quickpick!.items = [{ label: localize('error', "$(error) Error: {0}", err.message), alwaysShow: true }];
|
||||
this.quickpick!.items = [{ label: localize('error', "{0} Error: {1}", '$(error)', err.message), alwaysShow: true }];
|
||||
console.error(err);
|
||||
} finally {
|
||||
this.quickpick!.busy = false;
|
||||
|
||||
@@ -13,6 +13,7 @@ module.exports = withDefaults({
|
||||
context: __dirname,
|
||||
entry: {
|
||||
main: './src/main.ts',
|
||||
['askpass-main']: './src/askpass-main.ts'
|
||||
['askpass-main']: './src/askpass-main.ts',
|
||||
['git-editor-main']: './src/git-editor-main.ts'
|
||||
}
|
||||
});
|
||||
|
||||
+315
-79
@@ -8,13 +8,15 @@
|
||||
"engines": {
|
||||
"vscode": "^1.5.0"
|
||||
},
|
||||
"aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217",
|
||||
"aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255",
|
||||
"enabledApiProposals": [
|
||||
"diffCommand",
|
||||
"contribMergeEditorToolbar",
|
||||
"contribViewsWelcome",
|
||||
"scmActionButton",
|
||||
"scmSelectedProvider",
|
||||
"scmValidation",
|
||||
"tabInputTextMerge",
|
||||
"timeline"
|
||||
],
|
||||
"categories": [
|
||||
@@ -22,7 +24,8 @@
|
||||
],
|
||||
"activationEvents": [
|
||||
"*",
|
||||
"onFileSystem:git"
|
||||
"onFileSystem:git",
|
||||
"onFileSystem:git-show"
|
||||
],
|
||||
"extensionDependencies": [
|
||||
"vscode.git-base"
|
||||
@@ -211,82 +214,110 @@
|
||||
"command": "git.commit",
|
||||
"title": "%command.commit%",
|
||||
"category": "Git",
|
||||
"icon": "$(check)"
|
||||
"icon": "$(check)",
|
||||
"enablement": "!commitInProgress"
|
||||
},
|
||||
{
|
||||
"command": "git.commitStaged",
|
||||
"title": "%command.commitStaged%",
|
||||
"category": "Git"
|
||||
"category": "Git",
|
||||
"enablement": "!commitInProgress"
|
||||
},
|
||||
{
|
||||
"command": "git.commitEmpty",
|
||||
"title": "%command.commitEmpty%",
|
||||
"category": "Git"
|
||||
"category": "Git",
|
||||
"enablement": "!commitInProgress"
|
||||
},
|
||||
{
|
||||
"command": "git.commitStagedSigned",
|
||||
"title": "%command.commitStagedSigned%",
|
||||
"category": "Git"
|
||||
"category": "Git",
|
||||
"enablement": "!commitInProgress"
|
||||
},
|
||||
{
|
||||
"command": "git.commitStagedAmend",
|
||||
"title": "%command.commitStagedAmend%",
|
||||
"category": "Git"
|
||||
"category": "Git",
|
||||
"enablement": "!commitInProgress"
|
||||
},
|
||||
{
|
||||
"command": "git.commitAll",
|
||||
"title": "%command.commitAll%",
|
||||
"category": "Git"
|
||||
"category": "Git",
|
||||
"enablement": "!commitInProgress"
|
||||
},
|
||||
{
|
||||
"command": "git.commitAllSigned",
|
||||
"title": "%command.commitAllSigned%",
|
||||
"category": "Git"
|
||||
"category": "Git",
|
||||
"enablement": "!commitInProgress"
|
||||
},
|
||||
{
|
||||
"command": "git.commitAllAmend",
|
||||
"title": "%command.commitAllAmend%",
|
||||
"category": "Git"
|
||||
"category": "Git",
|
||||
"enablement": "!commitInProgress"
|
||||
},
|
||||
{
|
||||
"command": "git.commitNoVerify",
|
||||
"title": "%command.commitNoVerify%",
|
||||
"category": "Git",
|
||||
"icon": "$(check)"
|
||||
"icon": "$(check)",
|
||||
"enablement": "!commitInProgress"
|
||||
},
|
||||
{
|
||||
"command": "git.commitStagedNoVerify",
|
||||
"title": "%command.commitStagedNoVerify%",
|
||||
"category": "Git"
|
||||
"category": "Git",
|
||||
"enablement": "!commitInProgress"
|
||||
},
|
||||
{
|
||||
"command": "git.commitEmptyNoVerify",
|
||||
"title": "%command.commitEmptyNoVerify%",
|
||||
"category": "Git"
|
||||
"category": "Git",
|
||||
"enablement": "!commitInProgress"
|
||||
},
|
||||
{
|
||||
"command": "git.commitStagedSignedNoVerify",
|
||||
"title": "%command.commitStagedSignedNoVerify%",
|
||||
"category": "Git"
|
||||
"category": "Git",
|
||||
"enablement": "!commitInProgress"
|
||||
},
|
||||
{
|
||||
"command": "git.commitStagedAmendNoVerify",
|
||||
"title": "%command.commitStagedAmendNoVerify%",
|
||||
"category": "Git"
|
||||
"category": "Git",
|
||||
"enablement": "!commitInProgress"
|
||||
},
|
||||
{
|
||||
"command": "git.commitAllNoVerify",
|
||||
"title": "%command.commitAllNoVerify%",
|
||||
"category": "Git"
|
||||
"category": "Git",
|
||||
"enablement": "!commitInProgress"
|
||||
},
|
||||
{
|
||||
"command": "git.commitAllSignedNoVerify",
|
||||
"title": "%command.commitAllSignedNoVerify%",
|
||||
"category": "Git"
|
||||
"category": "Git",
|
||||
"enablement": "!commitInProgress"
|
||||
},
|
||||
{
|
||||
"command": "git.commitAllAmendNoVerify",
|
||||
"title": "%command.commitAllAmendNoVerify%",
|
||||
"category": "Git",
|
||||
"enablement": "!commitInProgress"
|
||||
},
|
||||
{
|
||||
"command": "git.commitMessageAccept",
|
||||
"title": "%command.commitMessageAccept%",
|
||||
"icon": "$(check)",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.commitMessageDiscard",
|
||||
"title": "%command.commitMessageDiscard%",
|
||||
"icon": "$(discard)",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
@@ -297,7 +328,8 @@
|
||||
{
|
||||
"command": "git.undoCommit",
|
||||
"title": "%command.undoCommit%",
|
||||
"category": "Git"
|
||||
"category": "Git",
|
||||
"enablement": "!commitInProgress"
|
||||
},
|
||||
{
|
||||
"command": "git.checkout",
|
||||
@@ -459,6 +491,21 @@
|
||||
"title": "%command.revealInExplorer%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.revealFileInOS.linux",
|
||||
"title": "%command.revealFileInOS.linux%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.revealFileInOS.mac",
|
||||
"title": "%command.revealFileInOS.mac%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.revealFileInOS.windows",
|
||||
"title": "%command.revealFileInOS.windows%",
|
||||
"category": "Git"
|
||||
},
|
||||
{
|
||||
"command": "git.stashIncludeUntracked",
|
||||
"title": "%command.stashIncludeUntracked%",
|
||||
@@ -549,6 +596,12 @@
|
||||
"command": "git.api.getRemoteSources",
|
||||
"title": "%command.api.getRemoteSources%",
|
||||
"category": "Git API"
|
||||
},
|
||||
{
|
||||
"command": "git.acceptMerge",
|
||||
"title": "%command.git.acceptMerge%",
|
||||
"category": "Git",
|
||||
"enablement": "isMergeEditor && mergeEditorResultUri in git.mergeChanges"
|
||||
}
|
||||
],
|
||||
"keybindings": [
|
||||
@@ -757,10 +810,30 @@
|
||||
"command": "git.restoreCommitTemplate",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "git.commitMessageAccept",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "git.commitMessageDiscard",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "git.revealInExplorer",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "git.revealFileInOS.linux",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "git.revealFileInOS.mac",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "git.revealFileInOS.windows",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "git.undoCommit",
|
||||
"when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0"
|
||||
@@ -1211,7 +1284,22 @@
|
||||
{
|
||||
"command": "git.revealInExplorer",
|
||||
"when": "scmProvider == git && scmResourceGroup == merge",
|
||||
"group": "2_view"
|
||||
"group": "2_view@1"
|
||||
},
|
||||
{
|
||||
"command": "git.revealFileInOS.linux",
|
||||
"when": "scmProvider == git && scmResourceGroup == merge && remoteName == '' && isLinux",
|
||||
"group": "2_view@2"
|
||||
},
|
||||
{
|
||||
"command": "git.revealFileInOS.mac",
|
||||
"when": "scmProvider == git && scmResourceGroup == merge && remoteName == '' && isMac",
|
||||
"group": "2_view@2"
|
||||
},
|
||||
{
|
||||
"command": "git.revealFileInOS.windows",
|
||||
"when": "scmProvider == git && scmResourceGroup == merge && remoteName == '' && isWindows",
|
||||
"group": "2_view@2"
|
||||
},
|
||||
{
|
||||
"command": "git.openFile2",
|
||||
@@ -1251,7 +1339,22 @@
|
||||
{
|
||||
"command": "git.revealInExplorer",
|
||||
"when": "scmProvider == git && scmResourceGroup == index",
|
||||
"group": "2_view"
|
||||
"group": "2_view@1"
|
||||
},
|
||||
{
|
||||
"command": "git.revealFileInOS.linux",
|
||||
"when": "scmProvider == git && scmResourceGroup == index && remoteName == '' && isLinux",
|
||||
"group": "2_view@2"
|
||||
},
|
||||
{
|
||||
"command": "git.revealFileInOS.mac",
|
||||
"when": "scmProvider == git && scmResourceGroup == index && remoteName == '' && isMac",
|
||||
"group": "2_view@2"
|
||||
},
|
||||
{
|
||||
"command": "git.revealFileInOS.windows",
|
||||
"when": "scmProvider == git && scmResourceGroup == index && remoteName == '' && isWindows",
|
||||
"group": "2_view@2"
|
||||
},
|
||||
{
|
||||
"command": "git.openFile2",
|
||||
@@ -1316,7 +1419,22 @@
|
||||
{
|
||||
"command": "git.revealInExplorer",
|
||||
"when": "scmProvider == git && scmResourceGroup == workingTree",
|
||||
"group": "2_view"
|
||||
"group": "2_view@1"
|
||||
},
|
||||
{
|
||||
"command": "git.revealFileInOS.linux",
|
||||
"when": "scmProvider == git && scmResourceGroup == workingTree && remoteName == '' && isLinux",
|
||||
"group": "2_view@2"
|
||||
},
|
||||
{
|
||||
"command": "git.revealFileInOS.mac",
|
||||
"when": "scmProvider == git && scmResourceGroup == workingTree && remoteName == '' && isMac",
|
||||
"group": "2_view@2"
|
||||
},
|
||||
{
|
||||
"command": "git.revealFileInOS.windows",
|
||||
"when": "scmProvider == git && scmResourceGroup == workingTree && remoteName == '' && isWindows",
|
||||
"group": "2_view@2"
|
||||
},
|
||||
{
|
||||
"command": "git.openChange",
|
||||
@@ -1383,7 +1501,17 @@
|
||||
{
|
||||
"command": "git.openChange",
|
||||
"group": "navigation",
|
||||
"when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && !isInDiffEditor && resourceScheme == file && scmActiveResourceHasChanges"
|
||||
"when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && !isInDiffEditor && !isMergeEditor && resourceScheme == file && scmActiveResourceHasChanges"
|
||||
},
|
||||
{
|
||||
"command": "git.commitMessageAccept",
|
||||
"group": "navigation",
|
||||
"when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && editorLangId == git-commit && commitInProgress"
|
||||
},
|
||||
{
|
||||
"command": "git.commitMessageDiscard",
|
||||
"group": "navigation",
|
||||
"when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && editorLangId == git-commit && commitInProgress"
|
||||
},
|
||||
{
|
||||
"command": "git.stageSelectedRanges",
|
||||
@@ -1418,6 +1546,12 @@
|
||||
"when": "isInDiffRightEditor && !isInEmbeddedDiffEditor && config.git.enabled && !git.missing && gitOpenRepositoryCount != 0 && isInDiffEditor && resourceScheme =~ /^git$|^file$/"
|
||||
}
|
||||
],
|
||||
"merge/toolbar": [
|
||||
{
|
||||
"command": "git.acceptMerge",
|
||||
"when": "isMergeEditor && mergeEditorBaseUri =~ /^(git|file):/ && mergeEditorResultUri in git.mergeChanges"
|
||||
}
|
||||
],
|
||||
"scm/change/title": [
|
||||
{
|
||||
"command": "git.stageChange",
|
||||
@@ -1777,6 +1911,37 @@
|
||||
"markdownDescription": "%config.autofetchPeriod%",
|
||||
"default": 180
|
||||
},
|
||||
"git.branchPrefix": {
|
||||
"type": "string",
|
||||
"description": "%config.branchPrefix%",
|
||||
"default": "",
|
||||
"scope": "resource"
|
||||
},
|
||||
"git.branchProtection": {
|
||||
"type": "array",
|
||||
"markdownDescription": "%config.branchProtection%",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"default": [],
|
||||
"scope": "resource"
|
||||
},
|
||||
"git.branchProtectionPrompt": {
|
||||
"type": "string",
|
||||
"description": "%config.branchProtectionPrompt%",
|
||||
"enum": [
|
||||
"alwaysCommit",
|
||||
"alwaysCommitToNewBranch",
|
||||
"alwaysPrompt"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"%config.branchProtectionPrompt.alwaysCommit%",
|
||||
"%config.branchProtectionPrompt.alwaysCommitToNewBranch%",
|
||||
"%config.branchProtectionPrompt.alwaysPrompt%"
|
||||
],
|
||||
"default": "alwaysPrompt",
|
||||
"scope": "resource"
|
||||
},
|
||||
"git.branchValidationRegex": {
|
||||
"type": "string",
|
||||
"description": "%config.branchValidationRegex%",
|
||||
@@ -1787,6 +1952,38 @@
|
||||
"description": "%config.branchWhitespaceChar%",
|
||||
"default": "-"
|
||||
},
|
||||
"git.branchRandomName.enable": {
|
||||
"type": "boolean",
|
||||
"description": "%config.branchRandomNameEnable%",
|
||||
"default": false,
|
||||
"scope": "resource"
|
||||
},
|
||||
"git.branchRandomName.dictionary": {
|
||||
"type": "array",
|
||||
"markdownDescription": "%config.branchRandomNameDictionary%",
|
||||
"items": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"adjectives",
|
||||
"animals",
|
||||
"colors",
|
||||
"numbers"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"%config.branchRandomNameDictionary.adjectives%",
|
||||
"%config.branchRandomNameDictionary.animals%",
|
||||
"%config.branchRandomNameDictionary.colors%",
|
||||
"%config.branchRandomNameDictionary.numbers%"
|
||||
]
|
||||
},
|
||||
"minItems": 1,
|
||||
"maxItems": 5,
|
||||
"default": [
|
||||
"adjectives",
|
||||
"animals"
|
||||
],
|
||||
"scope": "resource"
|
||||
},
|
||||
"git.confirmSync": {
|
||||
"type": "boolean",
|
||||
"description": "%config.confirmSync%",
|
||||
@@ -1865,6 +2062,17 @@
|
||||
"scope": "machine",
|
||||
"description": "%config.defaultCloneDirectory%"
|
||||
},
|
||||
"git.useEditorAsCommitInput": {
|
||||
"type": "boolean",
|
||||
"description": "%config.useEditorAsCommitInput%",
|
||||
"default": true
|
||||
},
|
||||
"git.verboseCommit": {
|
||||
"type": "boolean",
|
||||
"scope": "resource",
|
||||
"markdownDescription": "%config.verboseCommit%",
|
||||
"default": false
|
||||
},
|
||||
"git.enableSmartCommit": {
|
||||
"type": "boolean",
|
||||
"scope": "resource",
|
||||
@@ -2173,7 +2381,8 @@
|
||||
"git.requireGitUserConfig": {
|
||||
"type": "boolean",
|
||||
"description": "%config.requireGitUserConfig%",
|
||||
"default": true
|
||||
"default": true,
|
||||
"scope": "resource"
|
||||
},
|
||||
"git.showCommitInput": {
|
||||
"type": "boolean",
|
||||
@@ -2183,10 +2392,14 @@
|
||||
},
|
||||
"git.terminalAuthentication": {
|
||||
"type": "boolean",
|
||||
"scope": "resource",
|
||||
"default": true,
|
||||
"description": "%config.terminalAuthentication%"
|
||||
},
|
||||
"git.terminalGitEditor": {
|
||||
"type": "boolean",
|
||||
"default": false,
|
||||
"description": "%config.terminalGitEditor%"
|
||||
},
|
||||
"git.useCommitInputAsStashMessage": {
|
||||
"type": "boolean",
|
||||
"scope": "resource",
|
||||
@@ -2227,20 +2440,29 @@
|
||||
"description": "%config.timeline.showUncommitted%",
|
||||
"scope": "window"
|
||||
},
|
||||
"git.showUnpublishedCommitsButton": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"always",
|
||||
"whenEmpty",
|
||||
"never"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"%config.showUnpublishedCommitsButton.always%",
|
||||
"%config.showUnpublishedCommitsButton.whenEmpty%",
|
||||
"%config.showUnpublishedCommitsButton.never%"
|
||||
],
|
||||
"default": "whenEmpty",
|
||||
"description": "%config.showUnpublishedCommitsButton%",
|
||||
"git.showActionButton": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"description": "%config.showActionButton%",
|
||||
"properties": {
|
||||
"commit": {
|
||||
"type": "boolean",
|
||||
"description": "%config.showActionButton.commit%"
|
||||
},
|
||||
"publish": {
|
||||
"type": "boolean",
|
||||
"description": "%config.showActionButton.publish%"
|
||||
},
|
||||
"sync": {
|
||||
"type": "boolean",
|
||||
"description": "%config.showActionButton.sync%"
|
||||
}
|
||||
},
|
||||
"default": {
|
||||
"commit": true,
|
||||
"publish": true,
|
||||
"sync": true
|
||||
},
|
||||
"scope": "resource"
|
||||
},
|
||||
"git.statusLimit": {
|
||||
@@ -2249,19 +2471,6 @@
|
||||
"default": 10000,
|
||||
"description": "%config.statusLimit%"
|
||||
},
|
||||
"git.experimental.installGuide": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"default",
|
||||
"download"
|
||||
],
|
||||
"tags": [
|
||||
"experimental"
|
||||
],
|
||||
"scope": "machine",
|
||||
"description": "%config.experimental.installGuide%",
|
||||
"default": "default"
|
||||
},
|
||||
"git.repositoryScanIgnoredFolders": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
@@ -2285,8 +2494,37 @@
|
||||
"type": "string"
|
||||
},
|
||||
"default": [],
|
||||
"scope": "resource",
|
||||
"markdownDescription": "%config.commandsToLog%"
|
||||
},
|
||||
"git.logLevel": {
|
||||
"type": "string",
|
||||
"default": "Info",
|
||||
"enum": [
|
||||
"Trace",
|
||||
"Debug",
|
||||
"Info",
|
||||
"Warning",
|
||||
"Error",
|
||||
"Critical",
|
||||
"Off"
|
||||
],
|
||||
"enumDescriptions": [
|
||||
"%config.logLevel.trace%",
|
||||
"%config.logLevel.debug%",
|
||||
"%config.logLevel.info%",
|
||||
"%config.logLevel.warn%",
|
||||
"%config.logLevel.error%",
|
||||
"%config.logLevel.critical%",
|
||||
"%config.logLevel.off%"
|
||||
],
|
||||
"markdownDescription": "%config.logLevel%",
|
||||
"scope": "window"
|
||||
},
|
||||
"git.mergeEditor": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"markdownDescription": "%config.mergeEditor%",
|
||||
"scope": "window"
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -2297,7 +2535,7 @@
|
||||
"defaults": {
|
||||
"light": "#587c0c",
|
||||
"dark": "#81b88b",
|
||||
"highContrast": "#1b5225",
|
||||
"highContrast": "#a1e3ad",
|
||||
"highContrastLight": "#374e06"
|
||||
}
|
||||
},
|
||||
@@ -2409,56 +2647,51 @@
|
||||
"contents": "%view.workbench.scm.disabled%",
|
||||
"when": "!config.git.enabled"
|
||||
},
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.missing.guide%",
|
||||
"when": "config.git.enabled && git.missing && config.git.experimental.installGuide == download"
|
||||
},
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.missing.guide.mac%",
|
||||
"when": "config.git.enabled && git.missing && config.git.experimental.installGuide == download && isMac"
|
||||
},
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.missing.guide.windows%",
|
||||
"when": "config.git.enabled && git.missing && config.git.experimental.installGuide == download && isWindows"
|
||||
},
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.missing.guide.linux%",
|
||||
"when": "config.git.enabled && git.missing && config.git.experimental.installGuide == download && isLinux"
|
||||
},
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.missing%",
|
||||
"when": "config.git.enabled && git.missing && config.git.experimental.installGuide == default"
|
||||
"when": "config.git.enabled && git.missing"
|
||||
},
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.missing.mac%",
|
||||
"when": "config.git.enabled && git.missing && isMac"
|
||||
},
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.missing.windows%",
|
||||
"when": "config.git.enabled && git.missing && isWindows"
|
||||
},
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.missing.linux%",
|
||||
"when": "config.git.enabled && git.missing && isLinux"
|
||||
},
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.empty%",
|
||||
"when": "config.git.enabled && workbenchState == empty",
|
||||
"when": "config.git.enabled && !git.missing && workbenchState == empty",
|
||||
"enablement": "git.state == initialized",
|
||||
"group": "2_open@1"
|
||||
},
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.folder%",
|
||||
"when": "config.git.enabled && workbenchState == folder",
|
||||
"when": "config.git.enabled && !git.missing && workbenchState == folder",
|
||||
"enablement": "git.state == initialized",
|
||||
"group": "5_scm@1"
|
||||
},
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.workspace%",
|
||||
"when": "config.git.enabled && workbenchState == workspace && workspaceFolderCount != 0",
|
||||
"when": "config.git.enabled && !git.missing && workbenchState == workspace && workspaceFolderCount != 0",
|
||||
"enablement": "git.state == initialized",
|
||||
"group": "5_scm@1"
|
||||
},
|
||||
{
|
||||
"view": "scm",
|
||||
"contents": "%view.workbench.scm.emptyWorkspace%",
|
||||
"when": "config.git.enabled && workbenchState == workspace && workspaceFolderCount == 0",
|
||||
"when": "config.git.enabled && !git.missing && workbenchState == workspace && workspaceFolderCount == 0",
|
||||
"enablement": "git.state == initialized",
|
||||
"group": "2_open@1"
|
||||
},
|
||||
@@ -2479,11 +2712,13 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@vscode/extension-telemetry": "0.4.10",
|
||||
"@joaomoreno/unique-names-generator": "5.0.0",
|
||||
"@vscode/extension-telemetry": "0.6.2",
|
||||
"@vscode/iconv-lite-umd": "0.7.0",
|
||||
"byline": "^5.0.0",
|
||||
"file-type": "16.5.4",
|
||||
"jschardet": "3.0.0",
|
||||
"picomatch": "2.3.1",
|
||||
"vscode-nls": "^4.0.0",
|
||||
"vscode-uri": "^2.0.0",
|
||||
"which": "^1.3.0"
|
||||
@@ -2492,6 +2727,7 @@
|
||||
"@types/byline": "4.2.31",
|
||||
"@types/mocha": "^9.1.1",
|
||||
"@types/node": "16.x",
|
||||
"@types/picomatch": "2.3.0",
|
||||
"@types/which": "^1.0.28"
|
||||
},
|
||||
"repository": {
|
||||
|
||||
@@ -46,6 +46,8 @@
|
||||
"command.commitAllNoVerify": "Commit All (No Verify)",
|
||||
"command.commitAllSignedNoVerify": "Commit All (Signed Off, No Verify)",
|
||||
"command.commitAllAmendNoVerify": "Commit All (Amend, No Verify)",
|
||||
"command.commitMessageAccept": "Accept Commit Message",
|
||||
"command.commitMessageDiscard": "Discard Commit Message",
|
||||
"command.restoreCommitTemplate": "Restore Commit Template",
|
||||
"command.undoCommit": "Undo Last Commit",
|
||||
"command.checkout": "Checkout to...",
|
||||
@@ -80,6 +82,9 @@
|
||||
"command.showOutput": "Show Git Output",
|
||||
"command.ignore": "Add to .gitignore",
|
||||
"command.revealInExplorer": "Reveal in Explorer View",
|
||||
"command.revealFileInOS.linux": "Open Containing Folder",
|
||||
"command.revealFileInOS.mac": "Reveal in Finder",
|
||||
"command.revealFileInOS.windows": "Reveal in File Explorer",
|
||||
"command.rebaseAbort": "Abort Rebase",
|
||||
"command.stashIncludeUntracked": "Stash (Include Untracked)",
|
||||
"command.stash": "Stash",
|
||||
@@ -97,6 +102,7 @@
|
||||
"command.api.getRepositories": "Get Repositories",
|
||||
"command.api.getRepositoryState": "Get Repository State",
|
||||
"command.api.getRemoteSources": "Get Remote Sources",
|
||||
"command.git.acceptMerge": "Accept Merge",
|
||||
"config.enabled": "Whether git is enabled.",
|
||||
"config.path": "Path and filename of the git executable, e.g. `C:\\Program Files\\Git\\bin\\git.exe` (Windows). This can also be an array of string values containing multiple paths to look up.",
|
||||
"config.autoRepositoryDetection": "Configures when repositories should be automatically detected.",
|
||||
@@ -116,14 +122,28 @@
|
||||
"config.checkoutType.local": "Local branches",
|
||||
"config.checkoutType.tags": "Tags",
|
||||
"config.checkoutType.remote": "Remote branches",
|
||||
"config.branchPrefix": "Prefix used when creating a new branch.",
|
||||
"config.branchProtection": "List of protected branches. By default, a prompt is shown before changes are committed to a protected branch. The prompt can be controlled using the `#git.branchProtectionPrompt#` setting.",
|
||||
"config.branchProtectionPrompt": "Controls whether a prompt is being before changes are committed to a protected branch.",
|
||||
"config.branchProtectionPrompt.alwaysCommit": "Always commit changes to the protected branch.",
|
||||
"config.branchProtectionPrompt.alwaysCommitToNewBranch": "Always commit changes to a new branch.",
|
||||
"config.branchProtectionPrompt.alwaysPrompt": "Always prompt before changes are committed to a protected branch.",
|
||||
"config.branchRandomNameDictionary": "List of dictionaries used for the randomly generated branch name. Each value represents the dictionary used to generate the segment of the branch name. Supported dictionaries: `adjectives`, `animals`, `colors` and `numbers`.",
|
||||
"config.branchRandomNameDictionary.adjectives": "A random adjective",
|
||||
"config.branchRandomNameDictionary.animals": "A random animal name",
|
||||
"config.branchRandomNameDictionary.colors": "A random color name",
|
||||
"config.branchRandomNameDictionary.numbers": "A random number between 100 and 999",
|
||||
"config.branchRandomNameEnable": "Controls whether a random name is generated when creating a new branch.",
|
||||
"config.branchValidationRegex": "A regular expression to validate new branch names.",
|
||||
"config.branchWhitespaceChar": "The character to replace whitespace in new branch names.",
|
||||
"config.branchWhitespaceChar": "The character to replace whitespace in new branch names, and to separate segments of a randomly generated branch name.",
|
||||
"config.ignoreLegacyWarning": "Ignores the legacy Git warning.",
|
||||
"config.ignoreMissingGitWarning": "Ignores the warning when Git is missing.",
|
||||
"config.ignoreWindowsGit27Warning": "Ignores the warning when Git 2.25 - 2.26 is installed on Windows.",
|
||||
"config.ignoreLimitWarning": "Ignores the warning when there are too many changes in a repository.",
|
||||
"config.ignoreRebaseWarning": "Ignores the warning when it looks like the branch might have been rebased when pulling.",
|
||||
"config.defaultCloneDirectory": "The default location to clone a git repository.",
|
||||
"config.useEditorAsCommitInput": "Controls whether a full text editor will be used to author commit messages, whenever no message is provided in the commit input box.",
|
||||
"config.verboseCommit": "Enable verbose output when `#git.useEditorAsCommitInput#` is enabled.",
|
||||
"config.enableSmartCommit": "Commit all changes when there are no staged changes.",
|
||||
"config.smartCommitChanges": "Control which changes are automatically staged by Smart Commit.",
|
||||
"config.smartCommitChanges.all": "Automatically stage all changes.",
|
||||
@@ -193,22 +213,39 @@
|
||||
"config.untrackedChanges.hidden": "Untracked changes are hidden and excluded from several actions.",
|
||||
"config.requireGitUserConfig": "Controls whether to require explicit Git user configuration or allow Git to guess if missing.",
|
||||
"config.showCommitInput": "Controls whether to show the commit input in the Git source control panel.",
|
||||
"config.terminalAuthentication": "Controls whether to enable Azure Data Studio to be the authentication handler for git processes spawned in the integrated terminal. Note: terminals need to be restarted to pick up a change in this setting.",
|
||||
"config.terminalAuthentication": "Controls whether to enable VS Code to be the authentication handler for git processes spawned in the integrated terminal. Note: terminals need to be restarted to pick up a change in this setting.",
|
||||
"config.terminalGitEditor": "Controls whether to enable VS Code to be git editor for git processes spawned in the integrated terminal. Note: terminals need to be restarted to pick up a change in this setting.",
|
||||
"config.timeline.showAuthor": "Controls whether to show the commit author in the Timeline view.",
|
||||
"config.timeline.showUncommitted": "Controls whether to show uncommitted changes in the Timeline view.",
|
||||
"config.timeline.date": "Controls which date to use for items in the Timeline view.",
|
||||
"config.timeline.date.committed": "Use the committed date",
|
||||
"config.timeline.date.authored": "Use the authored date",
|
||||
"config.useCommitInputAsStashMessage": "Controls whether to use the message from the commit input box as the default stash message.",
|
||||
"config.showUnpublishedCommitsButton": "Controls whether to show an action button to sync or publish, if there are unpublished commits.",
|
||||
"config.showUnpublishedCommitsButton.always": "Always shows the action button, if there are unpublished commits.",
|
||||
"config.showUnpublishedCommitsButton.whenEmpty": "Only shows the action button if there are no other changes and there are unpublished commits.",
|
||||
"config.showUnpublishedCommitsButton.never": "Never shows the action button.",
|
||||
"config.showActionButton": "Controls whether an action button is shown in the Source Control view.",
|
||||
"config.showActionButton.commit": "Show an action button to commit changes when the local branch has modified files ready to be committed.",
|
||||
"config.showActionButton.publish": "Show an action button to publish the local branch when it does not have a tracking remote branch.",
|
||||
"config.showActionButton.sync": "Show an action button to synchronize changes when the local branch is either ahead or behind the remote branch.",
|
||||
"config.statusLimit": "Controls how to limit the number of changes that can be parsed from Git status command. Can be set to 0 for no limit.",
|
||||
"config.experimental.installGuide": "Experimental improvements for the git setup flow.",
|
||||
"config.repositoryScanIgnoredFolders": "List of folders that are ignored while scanning for Git repositories when `#git.autoRepositoryDetection#` is set to `true` or `subFolders`.",
|
||||
"config.repositoryScanMaxDepth": "Controls the depth used when scanning workspace folders for Git repositories when `#git.autoRepositoryDetection#` is set to `true` or `subFolders`. Can be set to `-1` for no limit.",
|
||||
"config.useIntegratedAskPass": "Controls whether GIT_ASKPASS should be overwritten to use the integrated version.",
|
||||
"config.logLevel": {
|
||||
"message": "Specifies how much information (if any) to log to the [git output](command:git.showOutput).",
|
||||
"comment": [
|
||||
"{Locked='](command:git.showOutput'}",
|
||||
"Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code",
|
||||
"Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links"
|
||||
]
|
||||
},
|
||||
"config.logLevel.trace": "Log all information",
|
||||
"config.logLevel.debug": "Log only debug, information, warning, error, and critical information",
|
||||
"config.logLevel.info": "Log only information, warning, error, and critical information",
|
||||
"config.logLevel.warn": "Log only warning, error, and critical information",
|
||||
"config.logLevel.error": "Log only error, and critical information",
|
||||
"config.logLevel.critical": "Log only critical information",
|
||||
"config.logLevel.off": "Log nothing",
|
||||
"config.mergeEditor": "Open the merge editor for files that are currently under conflict.",
|
||||
"submenu.explorer": "Git",
|
||||
"submenu.commit": "Commit",
|
||||
"submenu.commit.amend": "Amend",
|
||||
@@ -229,15 +266,7 @@
|
||||
"colors.ignored": "Color for ignored resources.",
|
||||
"colors.conflict": "Color for resources with conflicts.",
|
||||
"colors.submodule": "Color for submodule resources.",
|
||||
"view.workbench.scm.missing": {
|
||||
"message": "A valid git installation was not detected, more details can be found in the [git output](command:git.showOutput).\nPlease [install git](https://git-scm.com/), or learn more about how to use git and source control in Azure Data Studio in [our docs](https://aka.ms/vscode-scm).\nIf you're using a different version control system, you can [search the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22) for additional extensions.",
|
||||
"comment": [
|
||||
"{Locked='](command:git.showOutput'}",
|
||||
"Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for Azure Data Studio",
|
||||
"Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links"
|
||||
]
|
||||
},
|
||||
"view.workbench.scm.missing.guide.windows": {
|
||||
"view.workbench.scm.missing.windows": {
|
||||
"message": "[Download Git for Windows](https://git-scm.com/download/win)\nAfter installing, please [reload](command:workbench.action.reloadWindow) (or [troubleshoot](command:git.showOutput)). Additional source control providers can be installed [from the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22).",
|
||||
"comment": [
|
||||
"{Locked='](command:workbench.action.reloadWindow'}",
|
||||
@@ -245,7 +274,7 @@
|
||||
"Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links"
|
||||
]
|
||||
},
|
||||
"view.workbench.scm.missing.guide.mac": {
|
||||
"view.workbench.scm.missing.mac": {
|
||||
"message": "[Download Git for macOS](https://git-scm.com/download/mac)\nAfter installing, please [reload](command:workbench.action.reloadWindow) (or [troubleshoot](command:git.showOutput)). Additional source control providers can be installed [from the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22).",
|
||||
"comment": [
|
||||
"{Locked='](command:workbench.action.reloadWindow'}",
|
||||
@@ -253,7 +282,7 @@
|
||||
"Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links"
|
||||
]
|
||||
},
|
||||
"view.workbench.scm.missing.guide.linux": {
|
||||
"view.workbench.scm.missing.linux": {
|
||||
"message": "Source control depends on Git being installed.\n[Download Git for Linux](https://git-scm.com/download/linux)\nAfter installing, please [reload](command:workbench.action.reloadWindow) (or [troubleshoot](command:git.showOutput)). Additional source control providers can be installed [from the Marketplace](command:workbench.extensions.search?%22%40category%3A%5C%22scm%20providers%5C%22%22).",
|
||||
"comment": [
|
||||
"{Locked='](command:workbench.action.reloadWindow'}",
|
||||
@@ -261,7 +290,7 @@
|
||||
"Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links"
|
||||
]
|
||||
},
|
||||
"view.workbench.scm.missing.guide": "Install Git, a popular source control system, to track code changes and collaborate with others. Learn more in our [Git guides](https://aka.ms/vscode-scm).",
|
||||
"view.workbench.scm.missing": "Install Git, a popular source control system, to track code changes and collaborate with others. Learn more in our [Git guides](https://aka.ms/vscode-scm).",
|
||||
"view.workbench.scm.disabled": {
|
||||
"message": "If you would like to use git features, please enable git in your [settings](command:workbench.action.openSettings?%5B%22git.enabled%22%5D).\nTo learn more about how to use git and source control in VS Code [read our docs](https://aka.ms/vscode-scm).",
|
||||
"comment": [
|
||||
|
||||
@@ -3,18 +3,23 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable, Event, EventEmitter, SourceControlActionButton, Uri, workspace } from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { Command, Disposable, Event, EventEmitter, SourceControlActionButton, Uri, workspace } from 'vscode';
|
||||
import { ApiRepository } from './api/api1';
|
||||
import { Branch, Status } from './api/git';
|
||||
import { IPostCommitCommandsProviderRegistry } from './postCommitCommands';
|
||||
import { Repository, Operation } from './repository';
|
||||
import { dispose } from './util';
|
||||
import { Branch } from './api/git';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
interface ActionButtonState {
|
||||
readonly HEAD: Branch | undefined;
|
||||
readonly isSyncRunning: boolean;
|
||||
readonly repositoryHasNoChanges: boolean;
|
||||
readonly isCommitInProgress: boolean;
|
||||
readonly isMergeInProgress: boolean;
|
||||
readonly isRebaseInProgress: boolean;
|
||||
readonly isSyncInProgress: boolean;
|
||||
readonly repositoryHasChangesToCommit: boolean;
|
||||
}
|
||||
|
||||
export class ActionButtonCommand {
|
||||
@@ -32,81 +37,263 @@ export class ActionButtonCommand {
|
||||
|
||||
private disposables: Disposable[] = [];
|
||||
|
||||
constructor(readonly repository: Repository) {
|
||||
this._state = { HEAD: undefined, isSyncRunning: false, repositoryHasNoChanges: false };
|
||||
constructor(
|
||||
readonly repository: Repository,
|
||||
readonly postCommitCommandsProviderRegistry: IPostCommitCommandsProviderRegistry) {
|
||||
this._state = {
|
||||
HEAD: undefined,
|
||||
isCommitInProgress: false,
|
||||
isMergeInProgress: false,
|
||||
isRebaseInProgress: false,
|
||||
isSyncInProgress: false,
|
||||
repositoryHasChangesToCommit: false
|
||||
};
|
||||
|
||||
repository.onDidRunGitStatus(this.onDidRunGitStatus, this, this.disposables);
|
||||
repository.onDidChangeOperations(this.onDidChangeOperations, this, this.disposables);
|
||||
|
||||
this.disposables.push(postCommitCommandsProviderRegistry.onDidChangePostCommitCommandsProviders(() => this._onDidChange.fire()));
|
||||
|
||||
const root = Uri.file(repository.root);
|
||||
this.disposables.push(workspace.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('git.enableSmartCommit', root) ||
|
||||
e.affectsConfiguration('git.smartCommitChanges', root) ||
|
||||
e.affectsConfiguration('git.suggestSmartCommit', root)) {
|
||||
this.onDidChangeSmartCommitSettings();
|
||||
}
|
||||
|
||||
if (e.affectsConfiguration('git.branchProtection', root) ||
|
||||
e.affectsConfiguration('git.branchProtectionPrompt', root) ||
|
||||
e.affectsConfiguration('git.postCommitCommand', root) ||
|
||||
e.affectsConfiguration('git.showActionButton', root)) {
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
get button(): SourceControlActionButton | undefined {
|
||||
if (!this.state.HEAD || !this.state.HEAD.name || !this.state.HEAD.commit) { return undefined; }
|
||||
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
|
||||
const showActionButton = config.get<string>('showUnpublishedCommitsButton', 'whenEmpty');
|
||||
const postCommitCommand = config.get<string>('postCommitCommand');
|
||||
const noPostCommitCommand = postCommitCommand !== 'sync' && postCommitCommand !== 'push';
|
||||
if (!this.state.HEAD) { return undefined; }
|
||||
|
||||
let actionButton: SourceControlActionButton | undefined;
|
||||
if (showActionButton === 'always' || (showActionButton === 'whenEmpty' && this.state.repositoryHasNoChanges && noPostCommitCommand)) {
|
||||
if (this.state.HEAD.upstream) {
|
||||
if (this.state.HEAD.ahead) {
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
|
||||
const rebaseWhenSync = config.get<string>('rebaseWhenSync');
|
||||
|
||||
const ahead = `${this.state.HEAD.ahead}$(arrow-up)`;
|
||||
const behind = this.state.HEAD.behind ? `${this.state.HEAD.behind}$(arrow-down) ` : '';
|
||||
const icon = this.state.isSyncRunning ? '$(sync~spin)' : '$(sync)';
|
||||
if (this.state.repositoryHasChangesToCommit) {
|
||||
// Commit Changes (enabled)
|
||||
actionButton = this.getCommitActionButton();
|
||||
}
|
||||
|
||||
actionButton = {
|
||||
command: {
|
||||
command: this.state.isSyncRunning ? '' : rebaseWhenSync ? 'git.syncRebase' : 'git.sync',
|
||||
title: localize('scm button sync title', "{0} {1}{2}", icon, behind, ahead),
|
||||
tooltip: this.state.isSyncRunning ?
|
||||
localize('syncing changes', "Synchronizing Changes...")
|
||||
: this.repository.syncTooltip,
|
||||
arguments: [this.repository.sourceControl],
|
||||
},
|
||||
description: localize('scm button sync description', "{0} Sync Changes {1}{2}", icon, behind, ahead)
|
||||
};
|
||||
// Commit Changes (enabled) -> Publish Branch -> Sync Changes -> Commit Changes (disabled)
|
||||
return actionButton ?? this.getPublishBranchActionButton() ?? this.getSyncChangesActionButton() ?? this.getCommitActionButton();
|
||||
}
|
||||
|
||||
private getCommitActionButton(): SourceControlActionButton | undefined {
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
|
||||
const showActionButton = config.get<{ commit: boolean }>('showActionButton', { commit: true });
|
||||
|
||||
// The button is disabled
|
||||
if (!showActionButton.commit) { return undefined; }
|
||||
|
||||
return {
|
||||
command: this.getCommitActionButtonPrimaryCommand(),
|
||||
secondaryCommands: this.getCommitActionButtonSecondaryCommands(),
|
||||
enabled: (this.state.repositoryHasChangesToCommit || this.state.isRebaseInProgress) && !this.state.isCommitInProgress && !this.state.isMergeInProgress
|
||||
};
|
||||
}
|
||||
|
||||
private getCommitActionButtonPrimaryCommand(): Command {
|
||||
// Rebase Continue
|
||||
if (this.state.isRebaseInProgress) {
|
||||
return {
|
||||
command: 'git.commit',
|
||||
title: localize('scm button continue title', "{0} Continue", '$(check)'),
|
||||
tooltip: this.state.isCommitInProgress ? localize('scm button continuing tooltip', "Continuing Rebase...") : localize('scm button continue tooltip', "Continue Rebase"),
|
||||
arguments: [this.repository.sourceControl, '']
|
||||
};
|
||||
}
|
||||
|
||||
// Commit
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
|
||||
const postCommitCommand = config.get<string>('postCommitCommand');
|
||||
|
||||
// Branch protection
|
||||
const isBranchProtected = this.repository.isBranchProtected();
|
||||
const branchProtectionPrompt = config.get<'alwaysCommit' | 'alwaysCommitToNewBranch' | 'alwaysPrompt'>('branchProtectionPrompt')!;
|
||||
const alwaysPrompt = isBranchProtected && branchProtectionPrompt === 'alwaysPrompt';
|
||||
const alwaysCommitToNewBranch = isBranchProtected && branchProtectionPrompt === 'alwaysCommitToNewBranch';
|
||||
|
||||
// Icon
|
||||
const icon = alwaysPrompt ? '$(lock)' : alwaysCommitToNewBranch ? '$(git-branch)' : undefined;
|
||||
|
||||
let commandArg = '';
|
||||
let title = localize('scm button commit title', "{0} Commit", icon ?? '$(check)');
|
||||
let tooltip = this.state.isCommitInProgress ? localize('scm button committing tooltip', "Committing Changes...") : localize('scm button commit tooltip', "Commit Changes");
|
||||
|
||||
// Title, tooltip
|
||||
switch (postCommitCommand) {
|
||||
case 'push': {
|
||||
commandArg = 'git.push';
|
||||
title = localize('scm button commit and push title', "{0} Commit & Push", icon ?? '$(arrow-up)');
|
||||
if (alwaysCommitToNewBranch) {
|
||||
tooltip = this.state.isCommitInProgress ?
|
||||
localize('scm button committing to new branch and pushing tooltip', "Committing to New Branch & Pushing Changes...") :
|
||||
localize('scm button commit to new branch and push tooltip', "Commit to New Branch & Push Changes");
|
||||
} else {
|
||||
tooltip = this.state.isCommitInProgress ?
|
||||
localize('scm button committing and pushing tooltip', "Committing & Pushing Changes...") :
|
||||
localize('scm button commit and push tooltip', "Commit & Push Changes");
|
||||
}
|
||||
} else {
|
||||
actionButton = {
|
||||
command: {
|
||||
command: this.state.isSyncRunning ? '' : 'git.publish',
|
||||
title: localize('scm button publish title', "$(cloud-upload) Publish Branch"),
|
||||
tooltip: this.state.isSyncRunning ?
|
||||
localize('scm button publish branch running', "Publishing Branch...") :
|
||||
localize('scm button publish branch', "Publish Branch"),
|
||||
arguments: [this.repository.sourceControl],
|
||||
}
|
||||
};
|
||||
break;
|
||||
}
|
||||
case 'sync': {
|
||||
commandArg = 'git.sync';
|
||||
title = localize('scm button commit and sync title', "{0} Commit & Sync", icon ?? '$(sync)');
|
||||
if (alwaysCommitToNewBranch) {
|
||||
tooltip = this.state.isCommitInProgress ?
|
||||
localize('scm button committing to new branch and synching tooltip', "Committing to New Branch & Synching Changes...") :
|
||||
localize('scm button commit to new branch and sync tooltip', "Commit to New Branch & Sync Changes");
|
||||
} else {
|
||||
tooltip = this.state.isCommitInProgress ?
|
||||
localize('scm button committing and synching tooltip', "Committing & Synching Changes...") :
|
||||
localize('scm button commit and sync tooltip', "Commit & Sync Changes");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (alwaysCommitToNewBranch) {
|
||||
tooltip = this.state.isCommitInProgress ?
|
||||
localize('scm button committing to new branch tooltip', "Committing Changes to New Branch...") :
|
||||
localize('scm button commit to new branch tooltip', "Commit Changes to New Branch");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return actionButton;
|
||||
return { command: 'git.commit', title, tooltip, arguments: [this.repository.sourceControl, commandArg] };
|
||||
}
|
||||
|
||||
private getCommitActionButtonSecondaryCommands(): Command[][] {
|
||||
const commandGroups: Command[][] = [];
|
||||
|
||||
if (!this.state.isRebaseInProgress) {
|
||||
for (const provider of this.postCommitCommandsProviderRegistry.getPostCommitCommandsProviders()) {
|
||||
const commands = provider.getCommands(new ApiRepository(this.repository));
|
||||
commandGroups.push((commands ?? []).map(c => {
|
||||
return {
|
||||
command: 'git.commit',
|
||||
title: c.title,
|
||||
arguments: [this.repository.sourceControl, c.command]
|
||||
};
|
||||
}));
|
||||
}
|
||||
|
||||
if (commandGroups.length > 0) {
|
||||
commandGroups[0].splice(0, 0, { command: 'git.commit', title: localize('scm secondary button commit', "Commit") });
|
||||
}
|
||||
}
|
||||
|
||||
return commandGroups;
|
||||
}
|
||||
|
||||
private getPublishBranchActionButton(): SourceControlActionButton | undefined {
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
|
||||
const showActionButton = config.get<{ publish: boolean }>('showActionButton', { publish: true });
|
||||
|
||||
// Branch does have an upstream, commit/merge/rebase is in progress, or the button is disabled
|
||||
if (this.state.HEAD?.upstream || this.state.isCommitInProgress || this.state.isMergeInProgress || this.state.isRebaseInProgress || !showActionButton.publish) { return undefined; }
|
||||
|
||||
return {
|
||||
command: {
|
||||
command: 'git.publish',
|
||||
title: localize({ key: 'scm publish branch action button title', comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }, "{0} Publish Branch", '$(cloud-upload)'),
|
||||
tooltip: this.state.isSyncInProgress ?
|
||||
localize({ key: 'scm button publish branch running', comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }, "Publishing Branch...") :
|
||||
localize({ key: 'scm button publish branch', comment: ['{Locked="Branch"}', 'Do not translate "Branch" as it is a git term'] }, "Publish Branch"),
|
||||
arguments: [this.repository.sourceControl],
|
||||
},
|
||||
enabled: !this.state.isSyncInProgress
|
||||
};
|
||||
}
|
||||
|
||||
private getSyncChangesActionButton(): SourceControlActionButton | undefined {
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
|
||||
const showActionButton = config.get<{ sync: boolean }>('showActionButton', { sync: true });
|
||||
const branchIsAheadOrBehind = (this.state.HEAD?.behind ?? 0) > 0 || (this.state.HEAD?.ahead ?? 0) > 0;
|
||||
|
||||
// Branch does not have an upstream, branch is not ahead/behind the remote branch, commit/merge/rebase is in progress, or the button is disabled
|
||||
if (!this.state.HEAD?.upstream || !branchIsAheadOrBehind || this.state.isCommitInProgress || this.state.isMergeInProgress || this.state.isRebaseInProgress || !showActionButton.sync) { return undefined; }
|
||||
|
||||
const ahead = this.state.HEAD.ahead ? ` ${this.state.HEAD.ahead}$(arrow-up)` : '';
|
||||
const behind = this.state.HEAD.behind ? ` ${this.state.HEAD.behind}$(arrow-down)` : '';
|
||||
const icon = this.state.isSyncInProgress ? '$(sync~spin)' : '$(sync)';
|
||||
|
||||
return {
|
||||
command: {
|
||||
command: 'git.sync',
|
||||
title: `${icon}${behind}${ahead}`,
|
||||
tooltip: this.state.isSyncInProgress ?
|
||||
localize('syncing changes', "Synchronizing Changes...")
|
||||
: this.repository.syncTooltip,
|
||||
arguments: [this.repository.sourceControl],
|
||||
},
|
||||
description: localize('scm button sync description', "{0} Sync Changes{1}{2}", icon, behind, ahead),
|
||||
enabled: !this.state.isSyncInProgress
|
||||
};
|
||||
}
|
||||
|
||||
private onDidChangeOperations(): void {
|
||||
const isSyncRunning = this.repository.operations.isRunning(Operation.Sync) ||
|
||||
const isCommitInProgress =
|
||||
this.repository.operations.isRunning(Operation.Commit) ||
|
||||
this.repository.operations.isRunning(Operation.RebaseContinue);
|
||||
|
||||
const isSyncInProgress =
|
||||
this.repository.operations.isRunning(Operation.Sync) ||
|
||||
this.repository.operations.isRunning(Operation.Push) ||
|
||||
this.repository.operations.isRunning(Operation.Pull);
|
||||
|
||||
this.state = { ...this.state, isSyncRunning };
|
||||
this.state = { ...this.state, isCommitInProgress, isSyncInProgress };
|
||||
}
|
||||
|
||||
private onDidChangeSmartCommitSettings(): void {
|
||||
this.state = {
|
||||
...this.state,
|
||||
repositoryHasChangesToCommit: this.repositoryHasChangesToCommit()
|
||||
};
|
||||
}
|
||||
|
||||
private onDidRunGitStatus(): void {
|
||||
this.state = {
|
||||
...this.state,
|
||||
HEAD: this.repository.HEAD,
|
||||
repositoryHasNoChanges:
|
||||
this.repository.indexGroup.resourceStates.length === 0 &&
|
||||
this.repository.mergeGroup.resourceStates.length === 0 &&
|
||||
this.repository.untrackedGroup.resourceStates.length === 0 &&
|
||||
this.repository.workingTreeGroup.resourceStates.length === 0
|
||||
isMergeInProgress: this.repository.mergeGroup.resourceStates.length !== 0,
|
||||
isRebaseInProgress: !!this.repository.rebaseCommit,
|
||||
repositoryHasChangesToCommit: this.repositoryHasChangesToCommit()
|
||||
};
|
||||
}
|
||||
|
||||
private repositoryHasChangesToCommit(): boolean {
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
|
||||
const enableSmartCommit = config.get<boolean>('enableSmartCommit') === true;
|
||||
const suggestSmartCommit = config.get<boolean>('suggestSmartCommit') === true;
|
||||
const smartCommitChanges = config.get<'all' | 'tracked'>('smartCommitChanges', 'all');
|
||||
|
||||
const resources = [...this.repository.indexGroup.resourceStates];
|
||||
|
||||
if (
|
||||
// Smart commit enabled (all)
|
||||
(enableSmartCommit && smartCommitChanges === 'all') ||
|
||||
// Smart commit disabled, smart suggestion enabled
|
||||
(!enableSmartCommit && suggestSmartCommit)
|
||||
) {
|
||||
resources.push(...this.repository.workingTreeGroup.resourceStates);
|
||||
}
|
||||
|
||||
// Smart commit enabled (tracked only)
|
||||
if (enableSmartCommit && smartCommitChanges === 'tracked') {
|
||||
resources.push(...this.repository.workingTreeGroup.resourceStates.filter(r => r.type !== Status.UNTRACKED));
|
||||
}
|
||||
|
||||
return resources.length !== 0;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables = dispose(this.disposables);
|
||||
}
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
|
||||
import { Model } from '../model';
|
||||
import { Repository as BaseRepository, Resource } from '../repository';
|
||||
import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions, RemoteSourceProvider, RemoteSourcePublisher, ICloneOptions } from './git'; // {{SQL CARBON EDIT}} add ICloneOptions
|
||||
import { Event, SourceControlInputBox, Uri, SourceControl, Disposable, commands, CancellationToken } from 'vscode';
|
||||
import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions, RemoteSourceProvider, RemoteSourcePublisher, ICloneOptions, PostCommitCommandsProvider } from './git'; // {{SQL CARBON EDIT}} add ICloneOptions
|
||||
import { Event, SourceControlInputBox, Uri, SourceControl, Disposable, commands, CancellationToken } from 'vscode'; // {{SQL CARBON EDIT}} Add cancellationToken
|
||||
import { combinedDisposable, mapEvent } from '../util';
|
||||
import { toGitUri } from '../uri';
|
||||
import { GitExtensionImpl } from './extension';
|
||||
@@ -57,157 +57,157 @@ export class ApiRepositoryUIState implements RepositoryUIState {
|
||||
|
||||
export class ApiRepository implements Repository {
|
||||
|
||||
readonly rootUri: Uri = Uri.file(this._repository.root);
|
||||
readonly inputBox: InputBox = new ApiInputBox(this._repository.inputBox);
|
||||
readonly state: RepositoryState = new ApiRepositoryState(this._repository);
|
||||
readonly ui: RepositoryUIState = new ApiRepositoryUIState(this._repository.sourceControl);
|
||||
readonly rootUri: Uri = Uri.file(this.repository.root);
|
||||
readonly inputBox: InputBox = new ApiInputBox(this.repository.inputBox);
|
||||
readonly state: RepositoryState = new ApiRepositoryState(this.repository);
|
||||
readonly ui: RepositoryUIState = new ApiRepositoryUIState(this.repository.sourceControl);
|
||||
|
||||
constructor(private _repository: BaseRepository) { }
|
||||
constructor(readonly repository: BaseRepository) { }
|
||||
|
||||
apply(patch: string, reverse?: boolean): Promise<void> {
|
||||
return this._repository.apply(patch, reverse);
|
||||
return this.repository.apply(patch, reverse);
|
||||
}
|
||||
|
||||
getConfigs(): Promise<{ key: string; value: string }[]> {
|
||||
return this._repository.getConfigs();
|
||||
return this.repository.getConfigs();
|
||||
}
|
||||
|
||||
getConfig(key: string): Promise<string> {
|
||||
return this._repository.getConfig(key);
|
||||
return this.repository.getConfig(key);
|
||||
}
|
||||
|
||||
setConfig(key: string, value: string): Promise<string> {
|
||||
return this._repository.setConfig(key, value);
|
||||
return this.repository.setConfig(key, value);
|
||||
}
|
||||
|
||||
getGlobalConfig(key: string): Promise<string> {
|
||||
return this._repository.getGlobalConfig(key);
|
||||
return this.repository.getGlobalConfig(key);
|
||||
}
|
||||
|
||||
getObjectDetails(treeish: string, path: string): Promise<{ mode: string; object: string; size: number }> {
|
||||
return this._repository.getObjectDetails(treeish, path);
|
||||
return this.repository.getObjectDetails(treeish, path);
|
||||
}
|
||||
|
||||
detectObjectType(object: string): Promise<{ mimetype: string; encoding?: string }> {
|
||||
return this._repository.detectObjectType(object);
|
||||
return this.repository.detectObjectType(object);
|
||||
}
|
||||
|
||||
buffer(ref: string, filePath: string): Promise<Buffer> {
|
||||
return this._repository.buffer(ref, filePath);
|
||||
return this.repository.buffer(ref, filePath);
|
||||
}
|
||||
|
||||
show(ref: string, path: string): Promise<string> {
|
||||
return this._repository.show(ref, path);
|
||||
return this.repository.show(ref, path);
|
||||
}
|
||||
|
||||
getCommit(ref: string): Promise<Commit> {
|
||||
return this._repository.getCommit(ref);
|
||||
return this.repository.getCommit(ref);
|
||||
}
|
||||
|
||||
add(paths: string[]) {
|
||||
return this._repository.add(paths.map(p => Uri.file(p)));
|
||||
return this.repository.add(paths.map(p => Uri.file(p)));
|
||||
}
|
||||
|
||||
revert(paths: string[]) {
|
||||
return this._repository.revert(paths.map(p => Uri.file(p)));
|
||||
return this.repository.revert(paths.map(p => Uri.file(p)));
|
||||
}
|
||||
|
||||
clean(paths: string[]) {
|
||||
return this._repository.clean(paths.map(p => Uri.file(p)));
|
||||
return this.repository.clean(paths.map(p => Uri.file(p)));
|
||||
}
|
||||
|
||||
diff(cached?: boolean) {
|
||||
return this._repository.diff(cached);
|
||||
return this.repository.diff(cached);
|
||||
}
|
||||
|
||||
diffWithHEAD(): Promise<Change[]>;
|
||||
diffWithHEAD(path: string): Promise<string>;
|
||||
diffWithHEAD(path?: string): Promise<string | Change[]> {
|
||||
return this._repository.diffWithHEAD(path);
|
||||
return this.repository.diffWithHEAD(path);
|
||||
}
|
||||
|
||||
diffWith(ref: string): Promise<Change[]>;
|
||||
diffWith(ref: string, path: string): Promise<string>;
|
||||
diffWith(ref: string, path?: string): Promise<string | Change[]> {
|
||||
return this._repository.diffWith(ref, path);
|
||||
return this.repository.diffWith(ref, path);
|
||||
}
|
||||
|
||||
diffIndexWithHEAD(): Promise<Change[]>;
|
||||
diffIndexWithHEAD(path: string): Promise<string>;
|
||||
diffIndexWithHEAD(path?: string): Promise<string | Change[]> {
|
||||
return this._repository.diffIndexWithHEAD(path);
|
||||
return this.repository.diffIndexWithHEAD(path);
|
||||
}
|
||||
|
||||
diffIndexWith(ref: string): Promise<Change[]>;
|
||||
diffIndexWith(ref: string, path: string): Promise<string>;
|
||||
diffIndexWith(ref: string, path?: string): Promise<string | Change[]> {
|
||||
return this._repository.diffIndexWith(ref, path);
|
||||
return this.repository.diffIndexWith(ref, path);
|
||||
}
|
||||
|
||||
diffBlobs(object1: string, object2: string): Promise<string> {
|
||||
return this._repository.diffBlobs(object1, object2);
|
||||
return this.repository.diffBlobs(object1, object2);
|
||||
}
|
||||
|
||||
diffBetween(ref1: string, ref2: string): Promise<Change[]>;
|
||||
diffBetween(ref1: string, ref2: string, path: string): Promise<string>;
|
||||
diffBetween(ref1: string, ref2: string, path?: string): Promise<string | Change[]> {
|
||||
return this._repository.diffBetween(ref1, ref2, path);
|
||||
return this.repository.diffBetween(ref1, ref2, path);
|
||||
}
|
||||
|
||||
hashObject(data: string): Promise<string> {
|
||||
return this._repository.hashObject(data);
|
||||
return this.repository.hashObject(data);
|
||||
}
|
||||
|
||||
createBranch(name: string, checkout: boolean, ref?: string | undefined): Promise<void> {
|
||||
return this._repository.branch(name, checkout, ref);
|
||||
return this.repository.branch(name, checkout, ref);
|
||||
}
|
||||
|
||||
deleteBranch(name: string, force?: boolean): Promise<void> {
|
||||
return this._repository.deleteBranch(name, force);
|
||||
return this.repository.deleteBranch(name, force);
|
||||
}
|
||||
|
||||
getBranch(name: string): Promise<Branch> {
|
||||
return this._repository.getBranch(name);
|
||||
return this.repository.getBranch(name);
|
||||
}
|
||||
|
||||
getBranches(query: BranchQuery): Promise<Ref[]> {
|
||||
return this._repository.getBranches(query);
|
||||
return this.repository.getBranches(query);
|
||||
}
|
||||
|
||||
setBranchUpstream(name: string, upstream: string): Promise<void> {
|
||||
return this._repository.setBranchUpstream(name, upstream);
|
||||
return this.repository.setBranchUpstream(name, upstream);
|
||||
}
|
||||
|
||||
getMergeBase(ref1: string, ref2: string): Promise<string> {
|
||||
return this._repository.getMergeBase(ref1, ref2);
|
||||
return this.repository.getMergeBase(ref1, ref2);
|
||||
}
|
||||
|
||||
tag(name: string, upstream: string): Promise<void> {
|
||||
return this._repository.tag(name, upstream);
|
||||
return this.repository.tag(name, upstream);
|
||||
}
|
||||
|
||||
deleteTag(name: string): Promise<void> {
|
||||
return this._repository.deleteTag(name);
|
||||
return this.repository.deleteTag(name);
|
||||
}
|
||||
|
||||
status(): Promise<void> {
|
||||
return this._repository.status();
|
||||
return this.repository.status();
|
||||
}
|
||||
|
||||
checkout(treeish: string): Promise<void> {
|
||||
return this._repository.checkout(treeish);
|
||||
return this.repository.checkout(treeish);
|
||||
}
|
||||
|
||||
addRemote(name: string, url: string): Promise<void> {
|
||||
return this._repository.addRemote(name, url);
|
||||
return this.repository.addRemote(name, url);
|
||||
}
|
||||
|
||||
removeRemote(name: string): Promise<void> {
|
||||
return this._repository.removeRemote(name);
|
||||
return this.repository.removeRemote(name);
|
||||
}
|
||||
|
||||
renameRemote(name: string, newName: string): Promise<void> {
|
||||
return this._repository.renameRemote(name, newName);
|
||||
return this.repository.renameRemote(name, newName);
|
||||
}
|
||||
|
||||
fetch(arg0?: FetchOptions | string | undefined,
|
||||
@@ -216,30 +216,30 @@ export class ApiRepository implements Repository {
|
||||
prune?: boolean | undefined
|
||||
): Promise<void> {
|
||||
if (arg0 !== undefined && typeof arg0 !== 'string') {
|
||||
return this._repository.fetch(arg0);
|
||||
return this.repository.fetch(arg0);
|
||||
}
|
||||
|
||||
return this._repository.fetch({ remote: arg0, ref, depth, prune });
|
||||
return this.repository.fetch({ remote: arg0, ref, depth, prune });
|
||||
}
|
||||
|
||||
pull(unshallow?: boolean): Promise<void> {
|
||||
return this._repository.pull(undefined, unshallow);
|
||||
return this.repository.pull(undefined, unshallow);
|
||||
}
|
||||
|
||||
push(remoteName?: string, branchName?: string, setUpstream: boolean = false, force?: ForcePushMode): Promise<void> {
|
||||
return this._repository.pushTo(remoteName, branchName, setUpstream, force);
|
||||
return this.repository.pushTo(remoteName, branchName, setUpstream, force);
|
||||
}
|
||||
|
||||
blame(path: string): Promise<string> {
|
||||
return this._repository.blame(path);
|
||||
return this.repository.blame(path);
|
||||
}
|
||||
|
||||
log(options?: LogOptions): Promise<Commit[]> {
|
||||
return this._repository.log(options);
|
||||
return this.repository.log(options);
|
||||
}
|
||||
|
||||
commit(message: string, opts?: CommitOptions): Promise<void> {
|
||||
return this._repository.commit(message, opts);
|
||||
return this.repository.commit(message, opts);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -323,6 +323,10 @@ export class ApiImpl implements API {
|
||||
return this._model.registerCredentialsProvider(provider);
|
||||
}
|
||||
|
||||
registerPostCommitCommandsProvider(provider: PostCommitCommandsProvider): Disposable {
|
||||
return this._model.registerPostCommitCommandsProvider(provider);
|
||||
}
|
||||
|
||||
registerPushErrorHandler(handler: PushErrorHandler): Disposable {
|
||||
return this._model.registerPushErrorHandler(handler);
|
||||
}
|
||||
|
||||
Vendored
+11
-2
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Uri, Event, Disposable, ProviderResult, CancellationToken, Progress } from 'vscode'; // {{SQL CARBON EDIT}} add CancellationToken
|
||||
import { Uri, Event, Disposable, ProviderResult, CancellationToken, Progress, Command } from 'vscode'; // {{SQL CARBON EDIT}} add CancellationToken
|
||||
export { ProviderResult } from 'vscode';
|
||||
|
||||
export interface Git {
|
||||
@@ -137,6 +137,9 @@ export interface CommitOptions {
|
||||
empty?: boolean;
|
||||
noVerify?: boolean;
|
||||
requireUserConfig?: boolean;
|
||||
useEditor?: boolean;
|
||||
verbose?: boolean;
|
||||
postCommitCommand?: string;
|
||||
}
|
||||
|
||||
export interface FetchOptions {
|
||||
@@ -251,6 +254,10 @@ export interface CredentialsProvider {
|
||||
getCredentials(host: Uri): ProviderResult<Credentials>;
|
||||
}
|
||||
|
||||
export interface PostCommitCommandsProvider {
|
||||
getCommands(repository: Repository): Command[];
|
||||
}
|
||||
|
||||
export interface PushErrorHandler {
|
||||
handlePushError(repository: Repository, remote: Remote, refspec: string, error: Error & { gitErrorCode: GitErrorCodes }): Promise<boolean>;
|
||||
}
|
||||
@@ -287,6 +294,7 @@ export interface API {
|
||||
registerRemoteSourcePublisher(publisher: RemoteSourcePublisher): Disposable;
|
||||
registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable;
|
||||
registerCredentialsProvider(provider: CredentialsProvider): Disposable;
|
||||
registerPostCommitCommandsProvider(provider: PostCommitCommandsProvider): Disposable;
|
||||
registerPushErrorHandler(handler: PushErrorHandler): Disposable;
|
||||
}
|
||||
|
||||
@@ -298,7 +306,7 @@ export interface GitExtension {
|
||||
/**
|
||||
* Returns a specific API version.
|
||||
*
|
||||
* Throws error if git extension is disabled. You can listed to the
|
||||
* Throws error if git extension is disabled. You can listen to the
|
||||
* [GitExtension.onDidChangeEnablement](#GitExtension.onDidChangeEnablement) event
|
||||
* to know when the extension becomes enabled/disabled.
|
||||
*
|
||||
@@ -344,6 +352,7 @@ export const enum GitErrorCodes {
|
||||
PatchDoesNotApply = 'PatchDoesNotApply',
|
||||
NoPathFound = 'NoPathFound',
|
||||
UnknownPath = 'UnknownPath',
|
||||
EmptyCommitMessage = 'EmptyCommitMessage'
|
||||
}
|
||||
|
||||
// {{SQL CARBON EDIT}} move ICloneOptions from git.ts to here since it's used in clone()
|
||||
|
||||
@@ -3,31 +3,31 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { window, InputBoxOptions, Uri, OutputChannel, Disposable, workspace } from 'vscode';
|
||||
import { IDisposable, EmptyDisposable, toDisposable, logTimestamp } from './util';
|
||||
import { window, InputBoxOptions, Uri, Disposable, workspace } from 'vscode';
|
||||
import { IDisposable, EmptyDisposable, toDisposable } from './util';
|
||||
import * as path from 'path';
|
||||
import { IIPCHandler, IIPCServer, createIPCServer } from './ipc/ipcServer';
|
||||
import { IIPCHandler, IIPCServer } from './ipc/ipcServer';
|
||||
import { CredentialsProvider, Credentials } from './api/git';
|
||||
import { ITerminalEnvironmentProvider } from './terminal';
|
||||
|
||||
export class Askpass implements IIPCHandler {
|
||||
export class Askpass implements IIPCHandler, ITerminalEnvironmentProvider {
|
||||
|
||||
private env: { [key: string]: string };
|
||||
private disposable: IDisposable = EmptyDisposable;
|
||||
private cache = new Map<string, Credentials>();
|
||||
private credentialsProviders = new Set<CredentialsProvider>();
|
||||
|
||||
static async create(outputChannel: OutputChannel, context?: string): Promise<Askpass> {
|
||||
try {
|
||||
return new Askpass(await createIPCServer(context));
|
||||
} catch (err) {
|
||||
outputChannel.appendLine(`${logTimestamp()} [error] Failed to create git askpass IPC: ${err}`);
|
||||
return new Askpass();
|
||||
}
|
||||
}
|
||||
|
||||
private constructor(private ipc?: IIPCServer) {
|
||||
constructor(private ipc?: IIPCServer) {
|
||||
if (ipc) {
|
||||
this.disposable = ipc.registerHandler('askpass', this);
|
||||
}
|
||||
|
||||
this.env = {
|
||||
GIT_ASKPASS: path.join(__dirname, this.ipc ? 'askpass.sh' : 'askpass-empty.sh'),
|
||||
VSCODE_GIT_ASKPASS_NODE: process.execPath,
|
||||
VSCODE_GIT_ASKPASS_EXTRA_ARGS: (process.versions['electron'] && process.versions['microsoft-build']) ? '--ms-enable-electron-run-as-node' : '',
|
||||
VSCODE_GIT_ASKPASS_MAIN: path.join(__dirname, 'askpass-main.js'),
|
||||
};
|
||||
}
|
||||
|
||||
async handle({ request, host }: { request: string; host: string }): Promise<string> {
|
||||
@@ -73,25 +73,13 @@ export class Askpass implements IIPCHandler {
|
||||
}
|
||||
|
||||
getEnv(): { [key: string]: string } {
|
||||
if (!this.ipc) {
|
||||
return {
|
||||
GIT_ASKPASS: path.join(__dirname, 'askpass-empty.sh')
|
||||
};
|
||||
}
|
||||
|
||||
let env: { [key: string]: string } = {
|
||||
...this.ipc.getEnv(),
|
||||
VSCODE_GIT_ASKPASS_NODE: process.execPath,
|
||||
VSCODE_GIT_ASKPASS_EXTRA_ARGS: (process.versions['electron'] && process.versions['microsoft-build']) ? '--ms-enable-electron-run-as-node' : '',
|
||||
VSCODE_GIT_ASKPASS_MAIN: path.join(__dirname, 'askpass-main.js')
|
||||
};
|
||||
|
||||
const config = workspace.getConfiguration('git');
|
||||
if (config.get<boolean>('useIntegratedAskPass')) {
|
||||
env.GIT_ASKPASS = path.join(__dirname, 'askpass.sh');
|
||||
}
|
||||
return config.get<boolean>('useIntegratedAskPass') ? this.env : {};
|
||||
}
|
||||
|
||||
return env;
|
||||
getTerminalEnv(): { [key: string]: string } {
|
||||
const config = workspace.getConfiguration('git');
|
||||
return config.get<boolean>('useIntegratedAskPass') && config.get<boolean>('terminalAuthentication') ? this.env : {};
|
||||
}
|
||||
|
||||
registerCredentialsProvider(provider: CredentialsProvider): Disposable {
|
||||
|
||||
+394
-106
@@ -5,17 +5,18 @@
|
||||
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
import { Command, commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider } from 'vscode';
|
||||
import { Command, commands, Disposable, LineChange, MessageOptions, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider, InputBoxValidationSeverity, TabInputText, TabInputTextMerge } from 'vscode';
|
||||
import TelemetryReporter from '@vscode/extension-telemetry';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { uniqueNamesGenerator, adjectives, animals, colors, NumberDictionary } from '@joaomoreno/unique-names-generator';
|
||||
import { Branch, ForcePushMode, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourcePublisher } from './api/git';
|
||||
import { Git, Stash } from './git';
|
||||
import { Model } from './model';
|
||||
import { Repository, Resource, ResourceGroupType } from './repository';
|
||||
import { applyLineChanges, getModifiedRange, intersectDiffWithRange, invertLineChange, toLineRanges } from './staging';
|
||||
import { fromGitUri, toGitUri, isGitUri } from './uri';
|
||||
import { grep, isDescendant, logTimestamp, pathEquals, relativePath } from './util';
|
||||
import { Log, LogLevel } from './log';
|
||||
import { fromGitUri, toGitUri, isGitUri, toMergeUris } from './uri';
|
||||
import { grep, isDescendant, pathEquals, relativePath } from './util';
|
||||
import { LogLevel, OutputChannelLogger } from './log';
|
||||
import { GitTimelineItem } from './timelineProvider';
|
||||
import { ApiRepository } from './api/api1';
|
||||
import { pickRemoteSource } from './remoteSource';
|
||||
@@ -25,24 +26,26 @@ const localize = nls.loadMessageBundle();
|
||||
class CheckoutItem implements QuickPickItem {
|
||||
|
||||
protected get shortCommit(): string { return (this.ref.commit || '').substr(0, 8); }
|
||||
get label(): string { return this.ref.name || this.shortCommit; }
|
||||
get label(): string { return `${this.repository.isBranchProtected(this.ref.name ?? '') ? '$(lock)' : '$(git-branch)'} ${this.ref.name || this.shortCommit}`; }
|
||||
get description(): string { return this.shortCommit; }
|
||||
get refName(): string | undefined { return this.ref.name; }
|
||||
|
||||
constructor(protected ref: Ref) { }
|
||||
constructor(protected repository: Repository, protected ref: Ref) { }
|
||||
|
||||
async run(repository: Repository, opts?: { detached?: boolean }): Promise<void> {
|
||||
async run(opts?: { detached?: boolean }): Promise<void> {
|
||||
const ref = this.ref.name;
|
||||
|
||||
if (!ref) {
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.checkout(ref, opts);
|
||||
await this.repository.checkout(ref, opts);
|
||||
}
|
||||
}
|
||||
|
||||
class CheckoutTagItem extends CheckoutItem {
|
||||
|
||||
override get label(): string { return `$(tag) ${this.ref.name || this.shortCommit}`; }
|
||||
override get description(): string {
|
||||
return localize('tag at', "Tag at {0}", this.shortCommit);
|
||||
}
|
||||
@@ -50,21 +53,22 @@ class CheckoutTagItem extends CheckoutItem {
|
||||
|
||||
class CheckoutRemoteHeadItem extends CheckoutItem {
|
||||
|
||||
override get label(): string { return `$(cloud) ${this.ref.name || this.shortCommit}`; }
|
||||
override get description(): string {
|
||||
return localize('remote branch at', "Remote branch at {0}", this.shortCommit);
|
||||
}
|
||||
|
||||
override async run(repository: Repository, opts?: { detached?: boolean }): Promise<void> {
|
||||
override async run(opts?: { detached?: boolean }): Promise<void> {
|
||||
if (!this.ref.name) {
|
||||
return;
|
||||
}
|
||||
|
||||
const branches = await repository.findTrackingBranches(this.ref.name);
|
||||
const branches = await this.repository.findTrackingBranches(this.ref.name);
|
||||
|
||||
if (branches.length > 0) {
|
||||
await repository.checkout(branches[0].name!, opts);
|
||||
await this.repository.checkout(branches[0].name!, opts);
|
||||
} else {
|
||||
await repository.checkoutTracking(this.ref.name, opts);
|
||||
await this.repository.checkoutTracking(this.ref.name, opts);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -137,6 +141,7 @@ class HEADItem implements QuickPickItem {
|
||||
get label(): string { return 'HEAD'; }
|
||||
get description(): string { return (this.repository.HEAD && this.repository.HEAD.commit || '').substr(0, 8); }
|
||||
get alwaysShow(): boolean { return true; }
|
||||
get refName(): string { return 'HEAD'; }
|
||||
}
|
||||
|
||||
class AddRemoteItem implements QuickPickItem {
|
||||
@@ -217,7 +222,7 @@ function createCheckoutItems(repository: Repository): CheckoutItem[] {
|
||||
checkoutTypes = checkoutTypeConfig;
|
||||
}
|
||||
|
||||
const processors = checkoutTypes.map(getCheckoutProcessor)
|
||||
const processors = checkoutTypes.map(type => getCheckoutProcessor(repository, type))
|
||||
.filter(p => !!p) as CheckoutProcessor[];
|
||||
|
||||
for (const ref of repository.refs) {
|
||||
@@ -232,8 +237,8 @@ function createCheckoutItems(repository: Repository): CheckoutItem[] {
|
||||
class CheckoutProcessor {
|
||||
|
||||
private refs: Ref[] = [];
|
||||
get items(): CheckoutItem[] { return this.refs.map(r => new this.ctor(r)); }
|
||||
constructor(private type: RefType, private ctor: { new(ref: Ref): CheckoutItem }) { }
|
||||
get items(): CheckoutItem[] { return this.refs.map(r => new this.ctor(this.repository, r)); }
|
||||
constructor(private repository: Repository, private type: RefType, private ctor: { new(repository: Repository, ref: Ref): CheckoutItem }) { }
|
||||
|
||||
onRef(ref: Ref): void {
|
||||
if (ref.type === this.type) {
|
||||
@@ -242,14 +247,14 @@ class CheckoutProcessor {
|
||||
}
|
||||
}
|
||||
|
||||
function getCheckoutProcessor(type: string): CheckoutProcessor | undefined {
|
||||
function getCheckoutProcessor(repository: Repository, type: string): CheckoutProcessor | undefined {
|
||||
switch (type) {
|
||||
case 'local':
|
||||
return new CheckoutProcessor(RefType.Head, CheckoutItem);
|
||||
return new CheckoutProcessor(repository, RefType.Head, CheckoutItem);
|
||||
case 'remote':
|
||||
return new CheckoutProcessor(RefType.RemoteHead, CheckoutRemoteHeadItem);
|
||||
return new CheckoutProcessor(repository, RefType.RemoteHead, CheckoutRemoteHeadItem);
|
||||
case 'tags':
|
||||
return new CheckoutProcessor(RefType.Tag, CheckoutTagItem);
|
||||
return new CheckoutProcessor(repository, RefType.Tag, CheckoutTagItem);
|
||||
}
|
||||
|
||||
return undefined;
|
||||
@@ -310,7 +315,7 @@ export class CommandCenter {
|
||||
constructor(
|
||||
private git: Git,
|
||||
private model: Model,
|
||||
private outputChannel: OutputChannel,
|
||||
private outputChannelLogger: OutputChannelLogger,
|
||||
private telemetryReporter: TelemetryReporter
|
||||
) {
|
||||
this.disposables = Commands.map(({ commandId, key, method, options }) => {
|
||||
@@ -328,11 +333,25 @@ export class CommandCenter {
|
||||
|
||||
@command('git.setLogLevel')
|
||||
async setLogLevel(): Promise<void> {
|
||||
const createItem = (logLevel: LogLevel) => ({
|
||||
label: LogLevel[logLevel],
|
||||
logLevel,
|
||||
description: Log.logLevel === logLevel ? localize('current', "Current") : undefined
|
||||
});
|
||||
const createItem = (logLevel: LogLevel) => {
|
||||
let description: string | undefined;
|
||||
const defaultDescription = localize('default', "Default");
|
||||
const currentDescription = localize('current', "Current");
|
||||
|
||||
if (logLevel === this.outputChannelLogger.defaultLogLevel && logLevel === this.outputChannelLogger.currentLogLevel) {
|
||||
description = `${defaultDescription} & ${currentDescription} `;
|
||||
} else if (logLevel === this.outputChannelLogger.defaultLogLevel) {
|
||||
description = defaultDescription;
|
||||
} else if (logLevel === this.outputChannelLogger.currentLogLevel) {
|
||||
description = currentDescription;
|
||||
}
|
||||
|
||||
return {
|
||||
label: LogLevel[logLevel],
|
||||
logLevel,
|
||||
description
|
||||
};
|
||||
};
|
||||
|
||||
const items = [
|
||||
createItem(LogLevel.Trace),
|
||||
@@ -352,8 +371,7 @@ export class CommandCenter {
|
||||
return;
|
||||
}
|
||||
|
||||
Log.logLevel = choice.logLevel;
|
||||
this.outputChannel.appendLine(localize('changed', "{0} Log level changed to: {1}", logTimestamp(), LogLevel[Log.logLevel]));
|
||||
this.outputChannelLogger.currentLogLevel = choice.logLevel;
|
||||
}
|
||||
|
||||
@command('git.refresh', { repository: true })
|
||||
@@ -390,6 +408,55 @@ export class CommandCenter {
|
||||
}
|
||||
}
|
||||
|
||||
@command('_git.openMergeEditor')
|
||||
async openMergeEditor(uri: unknown) {
|
||||
if (!(uri instanceof Uri)) {
|
||||
return;
|
||||
}
|
||||
const repo = this.model.getRepository(uri);
|
||||
if (!repo) {
|
||||
return;
|
||||
}
|
||||
|
||||
const isRebasing = Boolean(repo.rebaseCommit);
|
||||
|
||||
type InputData = { uri: Uri; title?: string; detail?: string; description?: string };
|
||||
const mergeUris = toMergeUris(uri);
|
||||
const ours: InputData = { uri: mergeUris.ours, title: localize('Yours', 'Yours') };
|
||||
const theirs: InputData = { uri: mergeUris.theirs, title: localize('Theirs', 'Theirs') };
|
||||
|
||||
try {
|
||||
const [head, rebaseOrMergeHead] = await Promise.all([
|
||||
repo.getCommit('HEAD'),
|
||||
isRebasing ? repo.getCommit('REBASE_HEAD') : repo.getCommit('MERGE_HEAD')
|
||||
]);
|
||||
// ours (current branch and commit)
|
||||
ours.detail = head.refNames.map(s => s.replace(/^HEAD ->/, '')).join(', ');
|
||||
ours.description = '$(git-commit) ' + head.hash.substring(0, 7);
|
||||
|
||||
// theirs
|
||||
theirs.detail = rebaseOrMergeHead.refNames.join(', ');
|
||||
theirs.description = '$(git-commit) ' + rebaseOrMergeHead.hash.substring(0, 7);
|
||||
|
||||
} catch (error) {
|
||||
// not so bad, can continue with just uris
|
||||
console.error('FAILED to read HEAD, MERGE_HEAD commits');
|
||||
console.error(error);
|
||||
}
|
||||
|
||||
const options = {
|
||||
base: mergeUris.base,
|
||||
input1: isRebasing ? ours : theirs,
|
||||
input2: isRebasing ? theirs : ours,
|
||||
output: uri
|
||||
};
|
||||
|
||||
await commands.executeCommand(
|
||||
'_open.mergeEditor',
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
async cloneRepository(url?: string, parentPath?: string, options: { recursive?: boolean } = {}): Promise<void> {
|
||||
if (!url || typeof url !== 'string') {
|
||||
url = await pickRemoteSource({
|
||||
@@ -401,7 +468,8 @@ export class CommandCenter {
|
||||
if (!url) {
|
||||
/* __GDPR__
|
||||
"clone" : {
|
||||
"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
"owner": "lszomoru",
|
||||
"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" }
|
||||
}
|
||||
*/
|
||||
this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_URL' });
|
||||
@@ -426,7 +494,8 @@ export class CommandCenter {
|
||||
if (!uris || uris.length === 0) {
|
||||
/* __GDPR__
|
||||
"clone" : {
|
||||
"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
"owner": "lszomoru",
|
||||
"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" }
|
||||
}
|
||||
*/
|
||||
this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'no_directory' });
|
||||
@@ -484,8 +553,9 @@ export class CommandCenter {
|
||||
|
||||
/* __GDPR__
|
||||
"clone" : {
|
||||
"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }
|
||||
"owner": "lszomoru",
|
||||
"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" },
|
||||
"openFolder": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true, "comment": "Indicates whether the folder is opened following the clone operation" }
|
||||
}
|
||||
*/
|
||||
this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'success' }, { openFolder: action === PostCloneAction.Open || action === PostCloneAction.OpenNewWindow ? 1 : 0 });
|
||||
@@ -503,7 +573,8 @@ export class CommandCenter {
|
||||
if (/already exists and is not an empty directory/.test(err && err.stderr || '')) {
|
||||
/* __GDPR__
|
||||
"clone" : {
|
||||
"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
"owner": "lszomoru",
|
||||
"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" }
|
||||
}
|
||||
*/
|
||||
this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'directory_not_empty' });
|
||||
@@ -512,7 +583,8 @@ export class CommandCenter {
|
||||
} else {
|
||||
/* __GDPR__
|
||||
"clone" : {
|
||||
"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
"owner": "lszomoru",
|
||||
"outcome" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The outcome of the git operation" }
|
||||
}
|
||||
*/
|
||||
this.telemetryReporter.sendTelemetryEvent('clone', { outcome: 'error' });
|
||||
@@ -820,14 +892,14 @@ export class CommandCenter {
|
||||
|
||||
@command('git.stage')
|
||||
async stage(...resourceStates: SourceControlResourceState[]): Promise<void> {
|
||||
this.outputChannel.appendLine(`${logTimestamp()} git.stage ${resourceStates.length}`);
|
||||
this.outputChannelLogger.logDebug(`git.stage ${resourceStates.length} `);
|
||||
|
||||
resourceStates = resourceStates.filter(s => !!s);
|
||||
|
||||
if (resourceStates.length === 0 || (resourceStates[0] && !(resourceStates[0].resourceUri instanceof Uri))) {
|
||||
const resource = this.getSCMResource();
|
||||
|
||||
this.outputChannel.appendLine(`${logTimestamp()} git.stage.getSCMResource ${resource ? resource.resourceUri.toString() : null}`);
|
||||
this.outputChannelLogger.logDebug(`git.stage.getSCMResource ${resource ? resource.resourceUri.toString() : null} `);
|
||||
|
||||
if (!resource) {
|
||||
return;
|
||||
@@ -870,7 +942,7 @@ export class CommandCenter {
|
||||
const untracked = selection.filter(s => s.resourceGroupType === ResourceGroupType.Untracked);
|
||||
const scmResources = [...workingTree, ...untracked, ...resolved, ...unresolved];
|
||||
|
||||
this.outputChannel.appendLine(`${logTimestamp()} git.stage.scmResources ${scmResources.length}`);
|
||||
this.outputChannelLogger.logDebug(`git.stage.scmResources ${scmResources.length} `);
|
||||
if (!scmResources.length) {
|
||||
return;
|
||||
}
|
||||
@@ -1020,6 +1092,47 @@ export class CommandCenter {
|
||||
await this._stageChanges(textEditor, selectedChanges);
|
||||
}
|
||||
|
||||
@command('git.acceptMerge')
|
||||
async acceptMerge(uri: Uri | unknown): Promise<void> {
|
||||
if (!(uri instanceof Uri)) {
|
||||
return;
|
||||
}
|
||||
const repository = this.model.getRepository(uri);
|
||||
if (!repository) {
|
||||
console.log(`FAILED to accept merge because uri ${uri.toString()} doesn't belong to any repository`);
|
||||
return;
|
||||
}
|
||||
|
||||
const { activeTab } = window.tabGroups.activeTabGroup;
|
||||
if (!activeTab) {
|
||||
return;
|
||||
}
|
||||
|
||||
// make sure to save the merged document
|
||||
const doc = workspace.textDocuments.find(doc => doc.uri.toString() === uri.toString());
|
||||
if (!doc) {
|
||||
console.log(`FAILED to accept merge because uri ${uri.toString()} doesn't match a document`);
|
||||
return;
|
||||
}
|
||||
if (doc.isDirty) {
|
||||
await doc.save();
|
||||
}
|
||||
|
||||
// find the merge editor tabs for the resource in question and close them all
|
||||
let didCloseTab = false;
|
||||
const mergeEditorTabs = window.tabGroups.all.map(group => group.tabs.filter(tab => tab.input instanceof TabInputTextMerge && tab.input.result.toString() === uri.toString())).flat();
|
||||
if (mergeEditorTabs.includes(activeTab)) {
|
||||
didCloseTab = await window.tabGroups.close(mergeEditorTabs, true);
|
||||
}
|
||||
|
||||
// Only stage if the merge editor has been successfully closed. That means all conflicts have been
|
||||
// handled or unhandled conflicts are OK by the user.
|
||||
if (didCloseTab) {
|
||||
await repository.add([uri]);
|
||||
await commands.executeCommand('workbench.view.scm');
|
||||
}
|
||||
}
|
||||
|
||||
private async _stageChanges(textEditor: TextEditor, changes: LineChange[]): Promise<void> {
|
||||
const modifiedDocument = textEditor.document;
|
||||
const modifiedUri = modifiedDocument.uri;
|
||||
@@ -1339,7 +1452,7 @@ export class CommandCenter {
|
||||
private async smartCommit(
|
||||
repository: Repository,
|
||||
getCommitMessage: () => Promise<string | undefined>,
|
||||
opts?: CommitOptions
|
||||
opts: CommitOptions
|
||||
): Promise<boolean> {
|
||||
const config = workspace.getConfiguration('git', Uri.file(repository.root));
|
||||
let promptToSaveFilesBeforeCommit = config.get<'always' | 'staged' | 'never'>('promptToSaveFilesBeforeCommit');
|
||||
@@ -1385,14 +1498,8 @@ export class CommandCenter {
|
||||
}
|
||||
}
|
||||
|
||||
if (!opts) {
|
||||
opts = { all: noStagedChanges };
|
||||
} else if (!opts.all && noStagedChanges && !opts.empty) {
|
||||
opts = { ...opts, all: true };
|
||||
}
|
||||
|
||||
// no changes, and the user has not configured to commit all in this case
|
||||
if (!noUnstagedChanges && noStagedChanges && !enableSmartCommit && !opts.empty) {
|
||||
if (!noUnstagedChanges && noStagedChanges && !enableSmartCommit && !opts.empty && !opts.all) {
|
||||
const suggestSmartCommit = config.get<boolean>('suggestSmartCommit') === true;
|
||||
|
||||
if (!suggestSmartCommit) {
|
||||
@@ -1416,6 +1523,12 @@ export class CommandCenter {
|
||||
}
|
||||
}
|
||||
|
||||
if (opts.all === undefined) {
|
||||
opts = { ...opts, all: noStagedChanges };
|
||||
} else if (!opts.all && noStagedChanges && !opts.empty) {
|
||||
opts = { ...opts, all: true };
|
||||
}
|
||||
|
||||
// enable signing of commits if configured
|
||||
opts.signCommit = enableCommitSigning;
|
||||
|
||||
@@ -1423,6 +1536,14 @@ export class CommandCenter {
|
||||
opts.signoff = true;
|
||||
}
|
||||
|
||||
if (config.get<boolean>('useEditorAsCommitInput')) {
|
||||
opts.useEditor = true;
|
||||
|
||||
if (config.get<boolean>('verboseCommit')) {
|
||||
opts.verbose = true;
|
||||
}
|
||||
}
|
||||
|
||||
const smartCommitChanges = config.get<'all' | 'tracked'>('smartCommitChanges');
|
||||
|
||||
if (
|
||||
@@ -1437,6 +1558,8 @@ export class CommandCenter {
|
||||
// amend allows changing only the commit message
|
||||
&& !opts.amend
|
||||
&& !opts.empty
|
||||
// rebase not in progress
|
||||
&& repository.rebaseCommit === undefined
|
||||
) {
|
||||
const commitAnyway = localize('commit anyway', "Create Empty Commit");
|
||||
const answer = await window.showInformationMessage(localize('no changes', "There are no changes to commit."), commitAnyway);
|
||||
@@ -1468,9 +1591,9 @@ export class CommandCenter {
|
||||
}
|
||||
}
|
||||
|
||||
let message = await getCommitMessage();
|
||||
const message = await getCommitMessage();
|
||||
|
||||
if (!message && !opts.amend) {
|
||||
if (!message && !opts.amend && !opts.useEditor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1482,29 +1605,55 @@ export class CommandCenter {
|
||||
opts.all = 'tracked';
|
||||
}
|
||||
|
||||
// Branch protection
|
||||
const branchProtectionPrompt = config.get<'alwaysCommit' | 'alwaysCommitToNewBranch' | 'alwaysPrompt'>('branchProtectionPrompt')!;
|
||||
if (repository.isBranchProtected() && (branchProtectionPrompt === 'alwaysPrompt' || branchProtectionPrompt === 'alwaysCommitToNewBranch')) {
|
||||
const commitToNewBranch = localize('commit to branch', "Commit to a New Branch");
|
||||
|
||||
let pick: string | undefined = commitToNewBranch;
|
||||
|
||||
if (branchProtectionPrompt === 'alwaysPrompt') {
|
||||
const message = localize('confirm branch protection commit', "You are trying to commit to a protected branch and you might not have permission to push your commits to the remote.\n\nHow would you like to proceed?");
|
||||
const commit = localize('commit changes', "Commit Anyway");
|
||||
|
||||
pick = await window.showWarningMessage(message, { modal: true }, commitToNewBranch, commit);
|
||||
}
|
||||
|
||||
if (!pick) {
|
||||
return false;
|
||||
} else if (pick === commitToNewBranch) {
|
||||
const branchName = await this.promptForBranchName(repository);
|
||||
|
||||
if (!branchName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await repository.branch(branchName, true);
|
||||
}
|
||||
}
|
||||
|
||||
await repository.commit(message, opts);
|
||||
|
||||
const postCommitCommand = config.get<'none' | 'push' | 'sync'>('postCommitCommand');
|
||||
|
||||
switch (postCommitCommand) {
|
||||
case 'push':
|
||||
await this._push(repository, { pushType: PushType.Push, silent: true });
|
||||
break;
|
||||
case 'sync':
|
||||
await this.sync(repository);
|
||||
break;
|
||||
// Execute post commit command
|
||||
if (opts.postCommitCommand?.length) {
|
||||
await commands.executeCommand(
|
||||
opts.postCommitCommand,
|
||||
new ApiRepository(repository));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private async commitWithAnyInput(repository: Repository, opts?: CommitOptions): Promise<void> {
|
||||
private async commitWithAnyInput(repository: Repository, opts: CommitOptions): Promise<void> {
|
||||
const message = repository.inputBox.value;
|
||||
const root = Uri.file(repository.root);
|
||||
const config = workspace.getConfiguration('git', root);
|
||||
|
||||
const getCommitMessage = async () => {
|
||||
let _message: string | undefined = message;
|
||||
|
||||
if (!_message) {
|
||||
let value: string | undefined = undefined;
|
||||
if (!_message && !config.get<boolean>('useEditorAsCommitInput')) {
|
||||
const value: string | undefined = undefined;
|
||||
|
||||
if (opts && opts.amend && repository.HEAD && repository.HEAD.commit) {
|
||||
return undefined;
|
||||
@@ -1538,8 +1687,8 @@ export class CommandCenter {
|
||||
}
|
||||
|
||||
@command('git.commit', { repository: true })
|
||||
async commit(repository: Repository): Promise<void> {
|
||||
await this.commitWithAnyInput(repository);
|
||||
async commit(repository: Repository, postCommitCommand?: string): Promise<void> {
|
||||
await this.commitWithAnyInput(repository, { postCommitCommand });
|
||||
}
|
||||
|
||||
@command('git.commitStaged', { repository: true })
|
||||
@@ -1572,13 +1721,58 @@ export class CommandCenter {
|
||||
await this.commitWithAnyInput(repository, { all: true, amend: true });
|
||||
}
|
||||
|
||||
@command('git.commitMessageAccept')
|
||||
async commitMessageAccept(arg?: Uri): Promise<void> {
|
||||
if (!arg) { return; }
|
||||
|
||||
// Close the tab
|
||||
this._closeEditorTab(arg);
|
||||
}
|
||||
|
||||
@command('git.commitMessageDiscard')
|
||||
async commitMessageDiscard(arg?: Uri): Promise<void> {
|
||||
if (!arg) { return; }
|
||||
|
||||
// Clear the contents of the editor
|
||||
const editors = window.visibleTextEditors
|
||||
.filter(e => e.document.languageId === 'git-commit' && e.document.uri.toString() === arg.toString());
|
||||
|
||||
if (editors.length !== 1) { return; }
|
||||
|
||||
const commitMsgEditor = editors[0];
|
||||
const commitMsgDocument = commitMsgEditor.document;
|
||||
|
||||
const editResult = await commitMsgEditor.edit(builder => {
|
||||
const firstLine = commitMsgDocument.lineAt(0);
|
||||
const lastLine = commitMsgDocument.lineAt(commitMsgDocument.lineCount - 1);
|
||||
|
||||
builder.delete(new Range(firstLine.range.start, lastLine.range.end));
|
||||
});
|
||||
|
||||
if (!editResult) { return; }
|
||||
|
||||
// Save the document
|
||||
const saveResult = await commitMsgDocument.save();
|
||||
if (!saveResult) { return; }
|
||||
|
||||
// Close the tab
|
||||
this._closeEditorTab(arg);
|
||||
}
|
||||
|
||||
private _closeEditorTab(uri: Uri): void {
|
||||
const tabToClose = window.tabGroups.all.map(g => g.tabs).flat()
|
||||
.filter(t => t.input instanceof TabInputText && t.input.uri.toString() === uri.toString());
|
||||
|
||||
window.tabGroups.close(tabToClose);
|
||||
}
|
||||
|
||||
private async _commitEmpty(repository: Repository, noVerify?: boolean): Promise<void> {
|
||||
const root = Uri.file(repository.root);
|
||||
const config = workspace.getConfiguration('git', root);
|
||||
const shouldPrompt = config.get<boolean>('confirmEmptyCommits') === true;
|
||||
|
||||
if (shouldPrompt) {
|
||||
const message = localize('confirm emtpy commit', "Are you sure you want to create an empty commit?");
|
||||
const message = localize('confirm empty commit', "Are you sure you want to create an empty commit?");
|
||||
const yes = localize('yes', "Yes");
|
||||
const neverAgain = localize('yes never again', "Yes, Don't Show Again");
|
||||
const pick = await window.showWarningMessage(message, { modal: true }, yes, neverAgain);
|
||||
@@ -1725,7 +1919,7 @@ export class CommandCenter {
|
||||
const item = choice as CheckoutItem;
|
||||
|
||||
try {
|
||||
await item.run(repository, opts);
|
||||
await item.run(opts);
|
||||
} catch (err) {
|
||||
if (err.gitErrorCode !== GitErrorCodes.DirtyWorkTree) {
|
||||
throw err;
|
||||
@@ -1737,10 +1931,10 @@ export class CommandCenter {
|
||||
|
||||
if (choice === force) {
|
||||
await this.cleanAll(repository);
|
||||
await item.run(repository, opts);
|
||||
await item.run(opts);
|
||||
} else if (choice === stash) {
|
||||
await this.stash(repository);
|
||||
await item.run(repository, opts);
|
||||
await item.run(opts);
|
||||
await this.stashPopLatest(repository);
|
||||
}
|
||||
}
|
||||
@@ -1759,34 +1953,100 @@ export class CommandCenter {
|
||||
await this._branch(repository, undefined, true);
|
||||
}
|
||||
|
||||
private async promptForBranchName(defaultName?: string, initialValue?: string): Promise<string> {
|
||||
private generateRandomBranchName(repository: Repository, separator: string): string {
|
||||
const config = workspace.getConfiguration('git');
|
||||
const branchRandomNameDictionary = config.get<string[]>('branchRandomName.dictionary')!;
|
||||
|
||||
const dictionaries: string[][] = [];
|
||||
for (const dictionary of branchRandomNameDictionary) {
|
||||
if (dictionary.toLowerCase() === 'adjectives') {
|
||||
dictionaries.push(adjectives);
|
||||
}
|
||||
if (dictionary.toLowerCase() === 'animals') {
|
||||
dictionaries.push(animals);
|
||||
}
|
||||
if (dictionary.toLowerCase() === 'colors') {
|
||||
dictionaries.push(colors);
|
||||
}
|
||||
if (dictionary.toLowerCase() === 'numbers') {
|
||||
dictionaries.push(NumberDictionary.generate({ length: 3 }));
|
||||
}
|
||||
}
|
||||
|
||||
if (dictionaries.length === 0) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// 5 attempts to generate a random branch name
|
||||
for (let index = 0; index < 5; index++) {
|
||||
const randomName = uniqueNamesGenerator({
|
||||
dictionaries,
|
||||
length: dictionaries.length,
|
||||
separator
|
||||
});
|
||||
|
||||
// Check for local ref conflict
|
||||
if (!repository.refs.find(r => r.type === RefType.Head && r.name === randomName)) {
|
||||
return randomName;
|
||||
}
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private async promptForBranchName(repository: Repository, defaultName?: string, initialValue?: string): Promise<string> {
|
||||
const config = workspace.getConfiguration('git');
|
||||
const branchPrefix = config.get<string>('branchPrefix')!;
|
||||
const branchWhitespaceChar = config.get<string>('branchWhitespaceChar')!;
|
||||
const branchValidationRegex = config.get<string>('branchValidationRegex')!;
|
||||
const sanitize = (name: string) => name ?
|
||||
name.trim().replace(/^-+/, '').replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$|\[|\]$/g, branchWhitespaceChar)
|
||||
: name;
|
||||
|
||||
const rawBranchName = defaultName || await window.showInputBox({
|
||||
placeHolder: localize('branch name', "Branch name"),
|
||||
prompt: localize('provide branch name', "Please provide a new branch name"),
|
||||
value: initialValue,
|
||||
ignoreFocusOut: true,
|
||||
validateInput: (name: string) => {
|
||||
const validateName = new RegExp(branchValidationRegex);
|
||||
if (validateName.test(sanitize(name))) {
|
||||
return null;
|
||||
}
|
||||
let rawBranchName = defaultName;
|
||||
|
||||
return localize('branch name format invalid', "Branch name needs to match regex: {0}", branchValidationRegex);
|
||||
if (!rawBranchName) {
|
||||
// Branch name
|
||||
if (!initialValue) {
|
||||
const branchRandomNameEnabled = config.get<boolean>('branchRandomName.enable', false);
|
||||
initialValue = `${branchPrefix}${branchRandomNameEnabled ? this.generateRandomBranchName(repository, branchWhitespaceChar) : ''}`;
|
||||
}
|
||||
});
|
||||
|
||||
// Branch name selection
|
||||
const initialValueSelection: [number, number] | undefined =
|
||||
initialValue.startsWith(branchPrefix) ? [branchPrefix.length, initialValue.length] : undefined;
|
||||
|
||||
rawBranchName = await window.showInputBox({
|
||||
placeHolder: localize('branch name', "Branch name"),
|
||||
prompt: localize('provide branch name', "Please provide a new branch name"),
|
||||
value: initialValue,
|
||||
valueSelection: initialValueSelection,
|
||||
ignoreFocusOut: true,
|
||||
validateInput: (name: string) => {
|
||||
const validateName = new RegExp(branchValidationRegex);
|
||||
const sanitizedName = sanitize(name);
|
||||
if (validateName.test(sanitizedName)) {
|
||||
// If the sanitized name that we will use is different than what is
|
||||
// in the input box, show an info message to the user informing them
|
||||
// the branch name that will be used.
|
||||
return name === sanitizedName
|
||||
? null
|
||||
: {
|
||||
message: localize('branch name does not match sanitized', "The new branch will be '{0}'", sanitizedName),
|
||||
severity: InputBoxValidationSeverity.Info
|
||||
};
|
||||
}
|
||||
|
||||
return localize('branch name format invalid', "Branch name needs to match regex: {0}", branchValidationRegex);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return sanitize(rawBranchName || '');
|
||||
}
|
||||
|
||||
private async _branch(repository: Repository, defaultName?: string, from = false): Promise<void> {
|
||||
const branchName = await this.promptForBranchName(defaultName);
|
||||
const branchName = await this.promptForBranchName(repository, defaultName);
|
||||
|
||||
if (!branchName) {
|
||||
return;
|
||||
@@ -1803,7 +2063,9 @@ export class CommandCenter {
|
||||
return;
|
||||
}
|
||||
|
||||
target = choice.label;
|
||||
if (choice.refName) {
|
||||
target = choice.refName;
|
||||
}
|
||||
}
|
||||
|
||||
await repository.branch(branchName, true, target);
|
||||
@@ -1849,7 +2111,7 @@ export class CommandCenter {
|
||||
@command('git.renameBranch', { repository: true })
|
||||
async renameBranch(repository: Repository): Promise<void> {
|
||||
const currentBranchName = repository.HEAD && repository.HEAD.name;
|
||||
const branchName = await this.promptForBranchName(undefined, currentBranchName);
|
||||
const branchName = await this.promptForBranchName(repository, undefined, currentBranchName);
|
||||
|
||||
if (!branchName) {
|
||||
return;
|
||||
@@ -2315,17 +2577,16 @@ export class CommandCenter {
|
||||
}
|
||||
}
|
||||
|
||||
if (rebase) {
|
||||
await repository.syncRebase(HEAD);
|
||||
} else {
|
||||
await repository.sync(HEAD);
|
||||
}
|
||||
await repository.sync(HEAD, rebase);
|
||||
}
|
||||
|
||||
@command('git.sync', { repository: true })
|
||||
async sync(repository: Repository): Promise<void> {
|
||||
const config = workspace.getConfiguration('git', Uri.file(repository.root));
|
||||
const rebase = config.get<boolean>('rebaseWhenSync', false) === true;
|
||||
|
||||
try {
|
||||
await this._sync(repository, false);
|
||||
await this._sync(repository, rebase);
|
||||
} catch (err) {
|
||||
if (/Cancelled/i.test(err && (err.message || err.stderr || ''))) {
|
||||
return;
|
||||
@@ -2338,13 +2599,16 @@ export class CommandCenter {
|
||||
@command('git._syncAll')
|
||||
async syncAll(): Promise<void> {
|
||||
await Promise.all(this.model.repositories.map(async repository => {
|
||||
const config = workspace.getConfiguration('git', Uri.file(repository.root));
|
||||
const rebase = config.get<boolean>('rebaseWhenSync', false) === true;
|
||||
|
||||
const HEAD = repository.HEAD;
|
||||
|
||||
if (!HEAD || !HEAD.upstream) {
|
||||
return;
|
||||
}
|
||||
|
||||
await repository.sync(HEAD);
|
||||
await repository.sync(HEAD, rebase);
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -2466,6 +2730,21 @@ export class CommandCenter {
|
||||
await commands.executeCommand('revealInExplorer', resourceState.resourceUri);
|
||||
}
|
||||
|
||||
@command('git.revealFileInOS.linux')
|
||||
@command('git.revealFileInOS.mac')
|
||||
@command('git.revealFileInOS.windows')
|
||||
async revealFileInOS(resourceState: SourceControlResourceState): Promise<void> {
|
||||
if (!resourceState) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(resourceState.resourceUri instanceof Uri)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await commands.executeCommand('revealFileInOS', resourceState.resourceUri);
|
||||
}
|
||||
|
||||
private async _stash(repository: Repository, includeUntracked = false): Promise<void> {
|
||||
const noUnstagedChanges = repository.workingTreeGroup.resourceStates.length === 0
|
||||
&& (!includeUntracked || repository.untrackedGroup.resourceStates.length === 0);
|
||||
@@ -2755,13 +3034,7 @@ export class CommandCenter {
|
||||
|
||||
@command('git.closeAllDiffEditors', { repository: true })
|
||||
closeDiffEditors(repository: Repository): void {
|
||||
const resources = [
|
||||
...repository.indexGroup.resourceStates.map(r => r.resourceUri.fsPath),
|
||||
...repository.workingTreeGroup.resourceStates.map(r => r.resourceUri.fsPath),
|
||||
...repository.untrackedGroup.resourceStates.map(r => r.resourceUri.fsPath)
|
||||
];
|
||||
|
||||
repository.closeDiffEditors(resources, resources, true);
|
||||
repository.closeDiffEditors(undefined, undefined, true);
|
||||
}
|
||||
|
||||
private createCommand(id: string, key: string, method: Function, options: ScmCommandOptions): (...args: any[]) => any {
|
||||
@@ -2794,7 +3067,8 @@ export class CommandCenter {
|
||||
|
||||
/* __GDPR__
|
||||
"git.command" : {
|
||||
"command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
"owner": "lszomoru",
|
||||
"command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The command id of the command being executed" }
|
||||
}
|
||||
*/
|
||||
this.telemetryReporter.sendTelemetryEvent('git.command', { command: id });
|
||||
@@ -2805,12 +3079,12 @@ export class CommandCenter {
|
||||
};
|
||||
|
||||
let message: string;
|
||||
let type: 'error' | 'warning' = 'error';
|
||||
let type: 'error' | 'warning' | 'information' = 'error';
|
||||
|
||||
const choices = new Map<string, () => void>();
|
||||
const openOutputChannelChoice = localize('open git log', "Open Git Log");
|
||||
const outputChannel = this.outputChannel as OutputChannel;
|
||||
choices.set(openOutputChannelChoice, () => outputChannel.show());
|
||||
const outputChannelLogger = this.outputChannelLogger;
|
||||
choices.set(openOutputChannelChoice, () => outputChannelLogger.showOutputChannel());
|
||||
|
||||
const showCommandOutputChoice = localize('show command output', "Show Command Output");
|
||||
if (err.stderr) {
|
||||
@@ -2868,6 +3142,12 @@ export class CommandCenter {
|
||||
message = localize('missing user info', "Make sure you configure your 'user.name' and 'user.email' in git.");
|
||||
choices.set(localize('learn more', "Learn More"), () => commands.executeCommand('vscode.open', Uri.parse('https://aka.ms/vscode-setup-git')));
|
||||
break;
|
||||
case GitErrorCodes.EmptyCommitMessage:
|
||||
message = localize('empty commit', "Commit operation was cancelled due to empty commit message.");
|
||||
choices.clear();
|
||||
type = 'information';
|
||||
options.modal = false;
|
||||
break;
|
||||
default: {
|
||||
const hint = (err.stderr || err.message || String(err))
|
||||
.replace(/^error: /mi, '')
|
||||
@@ -2889,17 +3169,25 @@ export class CommandCenter {
|
||||
return;
|
||||
}
|
||||
|
||||
let result: string | undefined;
|
||||
const allChoices = Array.from(choices.keys());
|
||||
const result = type === 'error'
|
||||
? await window.showErrorMessage(message, options, ...allChoices)
|
||||
: await window.showWarningMessage(message, options, ...allChoices);
|
||||
|
||||
switch (type) {
|
||||
case 'error':
|
||||
result = await window.showErrorMessage(message, options, ...allChoices);
|
||||
break;
|
||||
case 'warning':
|
||||
result = await window.showWarningMessage(message, options, ...allChoices);
|
||||
break;
|
||||
case 'information':
|
||||
result = await window.showInformationMessage(message, options, ...allChoices);
|
||||
break;
|
||||
}
|
||||
|
||||
if (result) {
|
||||
const resultFn = choices.get(result);
|
||||
|
||||
if (resultFn) {
|
||||
resultFn();
|
||||
}
|
||||
resultFn?.();
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -2913,10 +3201,10 @@ export class CommandCenter {
|
||||
private getSCMResource(uri?: Uri): Resource | undefined {
|
||||
uri = uri ? uri : (window.activeTextEditor && window.activeTextEditor.document.uri);
|
||||
|
||||
this.outputChannel.appendLine(`${logTimestamp()} git.getSCMResource.uri ${uri && uri.toString()}`);
|
||||
this.outputChannelLogger.logDebug(`git.getSCMResource.uri ${uri && uri.toString()}`);
|
||||
|
||||
for (const r of this.model.repositories.map(r => r.root)) {
|
||||
this.outputChannel.appendLine(`${logTimestamp()} repo root ${r}`);
|
||||
this.outputChannelLogger.logDebug(`repo root ${r}`);
|
||||
}
|
||||
|
||||
if (!uri) {
|
||||
|
||||
@@ -106,7 +106,7 @@ class GitDecorationProvider implements FileDecorationProvider {
|
||||
}
|
||||
|
||||
private onDidRunGitStatus(): void {
|
||||
let newDecorations = new Map<string, FileDecoration>();
|
||||
const newDecorations = new Map<string, FileDecoration>();
|
||||
|
||||
this.collectSubmoduleDecorationData(newDecorations);
|
||||
this.collectDecorationData(this.repository.indexGroup, newDecorations);
|
||||
|
||||
@@ -50,7 +50,7 @@ const JSCHARDET_TO_ICONV_ENCODINGS: { [name: string]: string } = {
|
||||
};
|
||||
|
||||
export function detectEncoding(buffer: Buffer): string | null {
|
||||
let result = detectEncodingByBOM(buffer);
|
||||
const result = detectEncodingByBOM(buffer);
|
||||
|
||||
if (result) {
|
||||
return result;
|
||||
|
||||
Executable
+1
@@ -0,0 +1 @@
|
||||
#!/bin/sh
|
||||
@@ -0,0 +1,21 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import { IPCClient } from './ipc/ipcClient';
|
||||
|
||||
function fatal(err: any): void {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
function main(argv: string[]): void {
|
||||
const ipcClient = new IPCClient('git-editor');
|
||||
const commitMessagePath = argv[argv.length - 1];
|
||||
|
||||
ipcClient.call({ commitMessagePath }).then(() => {
|
||||
setTimeout(() => process.exit(0), 0);
|
||||
}).catch(err => fatal(err));
|
||||
}
|
||||
|
||||
main(process.argv);
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
|
||||
ELECTRON_RUN_AS_NODE="1" \
|
||||
"$VSCODE_GIT_EDITOR_NODE" "$VSCODE_GIT_EDITOR_MAIN" $VSCODE_GIT_EDITOR_EXTRA_ARGS "$@"
|
||||
+52
-23
@@ -354,7 +354,7 @@ function sanitizePath(path: string): string {
|
||||
return path.replace(/^([a-z]):\\/i, (_, letter) => `${letter.toUpperCase()}:\\`);
|
||||
}
|
||||
|
||||
const COMMIT_FORMAT = '%H%n%aN%n%aE%n%at%n%ct%n%P%n%B';
|
||||
const COMMIT_FORMAT = '%H%n%aN%n%aE%n%at%n%ct%n%P%n%D%n%B';
|
||||
|
||||
/*export interface ICloneOptions { {{SQL CARBON EDIT}} moved to git.d.ts
|
||||
readonly parentPath: string;
|
||||
@@ -406,7 +406,7 @@ export class Git {
|
||||
}
|
||||
|
||||
async clone(url: string, options: ICloneOptions, cancellationToken?: CancellationToken): Promise<string> {
|
||||
let baseFolderName = decodeURI(url).replace(/[\/]+$/, '').replace(/^.*[\/\\]/, '').replace(/\.git$/, '') || 'repository';
|
||||
const baseFolderName = decodeURI(url).replace(/[\/]+$/, '').replace(/^.*[\/\\]/, '').replace(/\.git$/, '') || 'repository';
|
||||
let folderName = baseFolderName;
|
||||
let folderPath = path.join(options.parentPath, folderName);
|
||||
let count = 1;
|
||||
@@ -447,7 +447,7 @@ export class Git {
|
||||
};
|
||||
|
||||
try {
|
||||
let command = ['clone', url.includes(' ') ? encodeURI(url) : url, folderPath, '--progress'];
|
||||
const command = ['clone', url.includes(' ') ? encodeURI(url) : url, folderPath, '--progress'];
|
||||
if (options.recursive) {
|
||||
command.push('--recursive');
|
||||
}
|
||||
@@ -475,13 +475,14 @@ export class Git {
|
||||
const repoPath = path.normalize(result.stdout.trimLeft().replace(/[\r\n]+$/, ''));
|
||||
|
||||
if (isWindows) {
|
||||
// On Git 2.25+ if you call `rev-parse --show-toplevel` on a mapped drive, instead of getting the mapped drive path back, you get the UNC path for the mapped drive.
|
||||
// So we will try to normalize it back to the mapped drive path, if possible
|
||||
// On Git 2.25+ if you call `rev-parse --show-toplevel` on a mapped drive, instead of getting the mapped
|
||||
// drive path back, you get the UNC path for the mapped drive. So we will try to normalize it back to the
|
||||
// mapped drive path, if possible
|
||||
const repoUri = Uri.file(repoPath);
|
||||
const pathUri = Uri.file(repositoryPath);
|
||||
if (repoUri.authority.length !== 0 && pathUri.authority.length === 0) {
|
||||
// eslint-disable-next-line code-no-look-behind-regex
|
||||
let match = /(?<=^\/?)([a-zA-Z])(?=:\/)/.exec(pathUri.path);
|
||||
const match = /(?<=^\/?)([a-zA-Z])(?=:\/)/.exec(pathUri.path);
|
||||
if (match !== null) {
|
||||
const [, letter] = match;
|
||||
|
||||
@@ -504,6 +505,13 @@ export class Git {
|
||||
|
||||
return path.normalize(pathUri.fsPath);
|
||||
}
|
||||
|
||||
// On Windows, there are cases in which the normalized path for a mapped folder contains a trailing `\`
|
||||
// character (ex: \\server\folder\) due to the implementation of `path.normalize()`. This behaviour is
|
||||
// by design as documented in https://github.com/nodejs/node/issues/1765.
|
||||
if (repoUri.authority.length !== 0) {
|
||||
return repoPath.replace(/\\$/, '');
|
||||
}
|
||||
}
|
||||
|
||||
return repoPath;
|
||||
@@ -556,9 +564,7 @@ export class Git {
|
||||
private async _exec(args: string[], options: SpawnOptions = {}): Promise<IExecutionResult<string>> {
|
||||
const child = this.spawn(args, options);
|
||||
|
||||
if (options.onSpawn) {
|
||||
options.onSpawn(child);
|
||||
}
|
||||
options.onSpawn?.(child);
|
||||
|
||||
if (options.input) {
|
||||
child.stdin!.end(options.input, 'utf8');
|
||||
@@ -660,6 +666,7 @@ export interface Commit {
|
||||
authorName?: string;
|
||||
authorEmail?: string;
|
||||
commitDate?: Date;
|
||||
refNames: string[];
|
||||
}
|
||||
|
||||
export class GitStatusParser {
|
||||
@@ -790,10 +797,10 @@ export function parseGitmodules(raw: string): Submodule[] {
|
||||
return result;
|
||||
}
|
||||
|
||||
const commitRegex = /([0-9a-f]{40})\n(.*)\n(.*)\n(.*)\n(.*)\n(.*)(?:\n([^]*?))?(?:\x00)/gm;
|
||||
const commitRegex = /([0-9a-f]{40})\n(.*)\n(.*)\n(.*)\n(.*)\n(.*)\n(.*)(?:\n([^]*?))?(?:\x00)/gm;
|
||||
|
||||
export function parseGitCommits(data: string): Commit[] {
|
||||
let commits: Commit[] = [];
|
||||
const commits: Commit[] = [];
|
||||
|
||||
let ref;
|
||||
let authorName;
|
||||
@@ -801,6 +808,7 @@ export function parseGitCommits(data: string): Commit[] {
|
||||
let authorDate;
|
||||
let commitDate;
|
||||
let parents;
|
||||
let refNames;
|
||||
let message;
|
||||
let match;
|
||||
|
||||
@@ -810,7 +818,7 @@ export function parseGitCommits(data: string): Commit[] {
|
||||
break;
|
||||
}
|
||||
|
||||
[, ref, authorName, authorEmail, authorDate, commitDate, parents, message] = match;
|
||||
[, ref, authorName, authorEmail, authorDate, commitDate, parents, refNames, message] = match;
|
||||
|
||||
if (message[message.length - 1] === '\n') {
|
||||
message = message.substr(0, message.length - 1);
|
||||
@@ -825,6 +833,7 @@ export function parseGitCommits(data: string): Commit[] {
|
||||
authorName: ` ${authorName}`.substr(1),
|
||||
authorEmail: ` ${authorEmail}`.substr(1),
|
||||
commitDate: new Date(Number(commitDate) * 1000),
|
||||
refNames: refNames.split(',').map(s => s.trim())
|
||||
});
|
||||
} while (true);
|
||||
|
||||
@@ -1397,20 +1406,37 @@ export class Repository {
|
||||
}
|
||||
|
||||
async commit(message: string | undefined, opts: CommitOptions = Object.create(null)): Promise<void> {
|
||||
const args = ['commit', '--quiet', '--allow-empty-message'];
|
||||
const args = ['commit', '--quiet'];
|
||||
const options: SpawnOptions = {};
|
||||
|
||||
if (message) {
|
||||
options.input = message;
|
||||
args.push('--allow-empty-message', '--file', '-');
|
||||
}
|
||||
|
||||
if (opts.verbose) {
|
||||
args.push('--verbose');
|
||||
}
|
||||
|
||||
if (opts.all) {
|
||||
args.push('--all');
|
||||
}
|
||||
|
||||
if (opts.amend && message) {
|
||||
if (opts.amend) {
|
||||
args.push('--amend');
|
||||
}
|
||||
|
||||
if (opts.amend && !message) {
|
||||
args.push('--amend', '--no-edit');
|
||||
} else {
|
||||
args.push('--file', '-');
|
||||
if (!opts.useEditor) {
|
||||
if (!message) {
|
||||
if (opts.amend) {
|
||||
args.push('--no-edit');
|
||||
} else {
|
||||
options.input = '';
|
||||
args.push('--file', '-');
|
||||
}
|
||||
}
|
||||
|
||||
args.push('--allow-empty-message');
|
||||
}
|
||||
|
||||
if (opts.signoff) {
|
||||
@@ -1435,7 +1461,7 @@ export class Repository {
|
||||
}
|
||||
|
||||
try {
|
||||
await this.exec(args, !opts.amend || message ? { input: message || '' } : {});
|
||||
await this.exec(args, options);
|
||||
} catch (commitErr) {
|
||||
await this.handleCommitError(commitErr);
|
||||
}
|
||||
@@ -1449,7 +1475,7 @@ export class Repository {
|
||||
const args = ['rebase', '--continue'];
|
||||
|
||||
try {
|
||||
await this.exec(args);
|
||||
await this.exec(args, { env: { GIT_EDITOR: 'true' } });
|
||||
} catch (commitErr) {
|
||||
await this.handleCommitError(commitErr);
|
||||
}
|
||||
@@ -1459,6 +1485,9 @@ export class Repository {
|
||||
if (/not possible because you have unmerged files/.test(commitErr.stderr || '')) {
|
||||
commitErr.gitErrorCode = GitErrorCodes.UnmergedChanges;
|
||||
throw commitErr;
|
||||
} else if (/Aborting commit due to empty commit message/.test(commitErr.stderr || '')) {
|
||||
commitErr.gitErrorCode = GitErrorCodes.EmptyCommitMessage;
|
||||
throw commitErr;
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -1540,7 +1569,7 @@ export class Repository {
|
||||
}
|
||||
|
||||
async deleteTag(name: string): Promise<void> {
|
||||
let args = ['tag', '-d', name];
|
||||
const args = ['tag', '-d', name];
|
||||
await this.exec(args);
|
||||
}
|
||||
|
||||
@@ -1764,12 +1793,12 @@ export class Repository {
|
||||
} catch (err) {
|
||||
if (/^error: failed to push some refs to\b/m.test(err.stderr || '')) {
|
||||
err.gitErrorCode = GitErrorCodes.PushRejected;
|
||||
} else if (/Permission.*denied/.test(err.stderr || '')) {
|
||||
err.gitErrorCode = GitErrorCodes.PermissionDenied;
|
||||
} else if (/Could not read from remote repository/.test(err.stderr || '')) {
|
||||
err.gitErrorCode = GitErrorCodes.RemoteConnectionError;
|
||||
} else if (/^fatal: The current branch .* has no upstream branch/.test(err.stderr || '')) {
|
||||
err.gitErrorCode = GitErrorCodes.NoUpstreamBranch;
|
||||
} else if (/Permission.*denied/.test(err.stderr || '')) {
|
||||
err.gitErrorCode = GitErrorCodes.PermissionDenied;
|
||||
}
|
||||
|
||||
throw err;
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
import * as path from 'path';
|
||||
import { TabInputText, Uri, window, workspace } from 'vscode';
|
||||
import { IIPCHandler, IIPCServer } from './ipc/ipcServer';
|
||||
import { ITerminalEnvironmentProvider } from './terminal';
|
||||
import { EmptyDisposable, IDisposable } from './util';
|
||||
|
||||
interface GitEditorRequest {
|
||||
commitMessagePath?: string;
|
||||
}
|
||||
|
||||
export class GitEditor implements IIPCHandler, ITerminalEnvironmentProvider {
|
||||
|
||||
private env: { [key: string]: string };
|
||||
private disposable: IDisposable = EmptyDisposable;
|
||||
|
||||
constructor(ipc?: IIPCServer) {
|
||||
if (ipc) {
|
||||
this.disposable = ipc.registerHandler('git-editor', this);
|
||||
}
|
||||
|
||||
this.env = {
|
||||
GIT_EDITOR: `"${path.join(__dirname, ipc ? 'git-editor.sh' : 'git-editor-empty.sh')}"`,
|
||||
VSCODE_GIT_EDITOR_NODE: process.execPath,
|
||||
VSCODE_GIT_EDITOR_EXTRA_ARGS: (process.versions['electron'] && process.versions['microsoft-build']) ? '--ms-enable-electron-run-as-node' : '',
|
||||
VSCODE_GIT_EDITOR_MAIN: path.join(__dirname, 'git-editor-main.js')
|
||||
};
|
||||
}
|
||||
|
||||
async handle({ commitMessagePath }: GitEditorRequest): Promise<any> {
|
||||
if (commitMessagePath) {
|
||||
const uri = Uri.file(commitMessagePath);
|
||||
const doc = await workspace.openTextDocument(uri);
|
||||
await window.showTextDocument(doc, { preview: false });
|
||||
|
||||
return new Promise((c) => {
|
||||
const onDidClose = window.tabGroups.onDidChangeTabs(async (tabs) => {
|
||||
if (tabs.closed.some(t => t.input instanceof TabInputText && t.input.uri.toString() === uri.toString())) {
|
||||
onDidClose.dispose();
|
||||
return c(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getEnv(): { [key: string]: string } {
|
||||
const config = workspace.getConfiguration('git');
|
||||
return config.get<boolean>('useEditorAsCommitInput') ? this.env : {};
|
||||
}
|
||||
|
||||
getTerminalEnv(): { [key: string]: string } {
|
||||
const config = workspace.getConfiguration('git');
|
||||
return config.get<boolean>('useEditorAsCommitInput') && config.get<boolean>('terminalGitEditor') ? this.env : {};
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposable.dispose();
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Disposable } from 'vscode';
|
||||
import { ITerminalEnvironmentProvider } from '../terminal';
|
||||
import { toDisposable } from '../util';
|
||||
import * as path from 'path';
|
||||
import * as http from 'http';
|
||||
@@ -27,7 +28,7 @@ export interface IIPCHandler {
|
||||
handle(request: any): Promise<any>;
|
||||
}
|
||||
|
||||
export async function createIPCServer(context?: string): Promise<IIPCServer> {
|
||||
export async function createIPCServer(context?: string): Promise<IPCServer> {
|
||||
const server = http.createServer();
|
||||
const hash = crypto.createHash('sha1');
|
||||
|
||||
@@ -65,7 +66,7 @@ export interface IIPCServer extends Disposable {
|
||||
registerHandler(name: string, handler: IIPCHandler): Disposable;
|
||||
}
|
||||
|
||||
class IPCServer implements IIPCServer, Disposable {
|
||||
export class IPCServer implements IIPCServer, ITerminalEnvironmentProvider, Disposable {
|
||||
|
||||
private handlers = new Map<string, IIPCHandler>();
|
||||
get ipcHandlePath(): string { return this._ipcHandlePath; }
|
||||
@@ -110,6 +111,10 @@ class IPCServer implements IIPCServer, Disposable {
|
||||
return { VSCODE_GIT_IPC_HANDLE: this.ipcHandlePath };
|
||||
}
|
||||
|
||||
getTerminalEnv(): { [key: string]: string } {
|
||||
return { VSCODE_GIT_IPC_HANDLE: this.ipcHandlePath };
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.handlers.clear();
|
||||
this.server.close();
|
||||
|
||||
+89
-24
@@ -3,7 +3,11 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event, EventEmitter } from 'vscode';
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { commands, Disposable, Event, EventEmitter, OutputChannel, window, workspace } from 'vscode';
|
||||
import { dispose } from './util';
|
||||
|
||||
/**
|
||||
* The severity level of a log message
|
||||
@@ -18,33 +22,94 @@ export enum LogLevel {
|
||||
Off = 7
|
||||
}
|
||||
|
||||
let _logLevel: LogLevel = LogLevel.Info;
|
||||
const _onDidChangeLogLevel = new EventEmitter<LogLevel>();
|
||||
/**
|
||||
* Output channel logger
|
||||
*/
|
||||
export class OutputChannelLogger {
|
||||
|
||||
export const Log = {
|
||||
/**
|
||||
* Current logging level.
|
||||
*/
|
||||
get logLevel(): LogLevel {
|
||||
return _logLevel;
|
||||
},
|
||||
private _onDidChangeLogLevel = new EventEmitter<LogLevel>();
|
||||
readonly onDidChangeLogLevel: Event<LogLevel> = this._onDidChangeLogLevel.event;
|
||||
|
||||
/**
|
||||
* Current logging level.
|
||||
*/
|
||||
set logLevel(logLevel: LogLevel) {
|
||||
if (_logLevel === logLevel) {
|
||||
private _currentLogLevel!: LogLevel;
|
||||
get currentLogLevel(): LogLevel {
|
||||
return this._currentLogLevel;
|
||||
}
|
||||
set currentLogLevel(value: LogLevel) {
|
||||
if (this._currentLogLevel === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
_logLevel = logLevel;
|
||||
_onDidChangeLogLevel.fire(logLevel);
|
||||
},
|
||||
this._currentLogLevel = value;
|
||||
this._onDidChangeLogLevel.fire(value);
|
||||
|
||||
/**
|
||||
* An [event](#Event) that fires when the log level has changed.
|
||||
*/
|
||||
get onDidChangeLogLevel(): Event<LogLevel> {
|
||||
return _onDidChangeLogLevel.event;
|
||||
this.log(localize('gitLogLevel', "Log level: {0}", LogLevel[value]));
|
||||
}
|
||||
};
|
||||
|
||||
private _defaultLogLevel!: LogLevel;
|
||||
get defaultLogLevel(): LogLevel {
|
||||
return this._defaultLogLevel;
|
||||
}
|
||||
|
||||
private _outputChannel: OutputChannel;
|
||||
private _disposables: Disposable[] = [];
|
||||
|
||||
constructor() {
|
||||
// Output channel
|
||||
this._outputChannel = window.createOutputChannel('Git');
|
||||
commands.registerCommand('git.showOutput', () => this.showOutputChannel());
|
||||
this._disposables.push(this._outputChannel);
|
||||
|
||||
this._disposables.push(workspace.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('git.logLevel')) {
|
||||
this.onLogLevelChange();
|
||||
}
|
||||
}));
|
||||
this.onLogLevelChange();
|
||||
}
|
||||
|
||||
private onLogLevelChange(): void {
|
||||
const config = workspace.getConfiguration('git');
|
||||
const logLevel: keyof typeof LogLevel = config.get('logLevel', 'Info');
|
||||
this.currentLogLevel = this._defaultLogLevel = LogLevel[logLevel] ?? LogLevel.Info;
|
||||
}
|
||||
|
||||
log(message: string, logLevel?: LogLevel): void {
|
||||
if (logLevel && logLevel < this._currentLogLevel) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._outputChannel.appendLine(`[${new Date().toISOString()}]${logLevel ? ` [${LogLevel[logLevel].toLowerCase()}]` : ''} ${message}`);
|
||||
}
|
||||
|
||||
logCritical(message: string): void {
|
||||
this.log(message, LogLevel.Critical);
|
||||
}
|
||||
|
||||
logDebug(message: string): void {
|
||||
this.log(message, LogLevel.Debug);
|
||||
}
|
||||
|
||||
logError(message: string): void {
|
||||
this.log(message, LogLevel.Error);
|
||||
}
|
||||
|
||||
logInfo(message: string): void {
|
||||
this.log(message, LogLevel.Info);
|
||||
}
|
||||
|
||||
logTrace(message: string): void {
|
||||
this.log(message, LogLevel.Trace);
|
||||
}
|
||||
|
||||
logWarning(message: string): void {
|
||||
this.log(message, LogLevel.Warning);
|
||||
}
|
||||
|
||||
showOutputChannel(): void {
|
||||
this._outputChannel.show();
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._disposables = dispose(this._disposables);
|
||||
}
|
||||
}
|
||||
|
||||
+36
-19
@@ -6,14 +6,14 @@
|
||||
import * as nls from 'vscode-nls';
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
import { ExtensionContext, workspace, window, Disposable, commands, OutputChannel } from 'vscode';
|
||||
import { ExtensionContext, workspace, Disposable, commands } from 'vscode';
|
||||
import { findGit, Git } from './git';
|
||||
import { Model } from './model';
|
||||
import { CommandCenter } from './commands';
|
||||
import { GitFileSystemProvider } from './fileSystemProvider';
|
||||
import { GitDecorations } from './decorationProvider';
|
||||
import { Askpass } from './askpass';
|
||||
import { toDisposable, filterEvent, eventToPromise, logTimestamp } from './util';
|
||||
import { toDisposable, filterEvent, eventToPromise } from './util';
|
||||
import TelemetryReporter from '@vscode/extension-telemetry';
|
||||
import { GitExtension } from './api/git';
|
||||
import { GitProtocolHandler } from './protocolHandler';
|
||||
@@ -24,6 +24,9 @@ import * as os from 'os';
|
||||
import { GitTimelineProvider } from './timelineProvider';
|
||||
import { registerAPICommands } from './api/api1';
|
||||
import { TerminalEnvironmentManager } from './terminal';
|
||||
import { OutputChannelLogger } from './log';
|
||||
import { createIPCServer, IPCServer } from './ipc/ipcServer';
|
||||
import { GitEditor } from './gitEditor';
|
||||
|
||||
const deactivateTasks: { (): Promise<any> }[] = [];
|
||||
|
||||
@@ -33,7 +36,7 @@ export async function deactivate(): Promise<any> {
|
||||
}
|
||||
}
|
||||
|
||||
async function createModel(context: ExtensionContext, outputChannel: OutputChannel, telemetryReporter: TelemetryReporter, disposables: Disposable[]): Promise<Model> {
|
||||
async function createModel(context: ExtensionContext, outputChannelLogger: OutputChannelLogger, telemetryReporter: TelemetryReporter, disposables: Disposable[]): Promise<Model> {
|
||||
const pathValue = workspace.getConfiguration('git').get<string | string[]>('path');
|
||||
let pathHints = Array.isArray(pathValue) ? pathValue : pathValue ? [pathValue] : [];
|
||||
|
||||
@@ -46,7 +49,7 @@ async function createModel(context: ExtensionContext, outputChannel: OutputChann
|
||||
}
|
||||
|
||||
const info = await findGit(pathHints, gitPath => {
|
||||
outputChannel.appendLine(localize('validating', "{0} Validating found git in: {1}", logTimestamp(), gitPath));
|
||||
outputChannelLogger.logInfo(localize('validating', "Validating found git in: {0}", gitPath));
|
||||
if (excludes.length === 0) {
|
||||
return true;
|
||||
}
|
||||
@@ -54,18 +57,30 @@ async function createModel(context: ExtensionContext, outputChannel: OutputChann
|
||||
const normalized = path.normalize(gitPath).replace(/[\r\n]+$/, '');
|
||||
const skip = excludes.some(e => normalized.startsWith(e));
|
||||
if (skip) {
|
||||
outputChannel.appendLine(localize('skipped', "{0} Skipped found git in: {1}", logTimestamp(), gitPath));
|
||||
outputChannelLogger.logInfo(localize('skipped', "Skipped found git in: {0}", gitPath));
|
||||
}
|
||||
return !skip;
|
||||
});
|
||||
|
||||
const askpass = await Askpass.create(outputChannel, context.storagePath);
|
||||
let ipcServer: IPCServer | undefined = undefined;
|
||||
|
||||
try {
|
||||
ipcServer = await createIPCServer(context.storagePath);
|
||||
} catch (err) {
|
||||
outputChannelLogger.logError(`Failed to create git IPC: ${err}`);
|
||||
}
|
||||
|
||||
const askpass = new Askpass(ipcServer);
|
||||
disposables.push(askpass);
|
||||
|
||||
const environment = askpass.getEnv();
|
||||
const terminalEnvironmentManager = new TerminalEnvironmentManager(context, environment);
|
||||
const gitEditor = new GitEditor(ipcServer);
|
||||
disposables.push(gitEditor);
|
||||
|
||||
const environment = { ...askpass.getEnv(), ...gitEditor.getEnv(), ...ipcServer?.getEnv() };
|
||||
const terminalEnvironmentManager = new TerminalEnvironmentManager(context, [askpass, gitEditor, ipcServer]);
|
||||
disposables.push(terminalEnvironmentManager);
|
||||
|
||||
outputChannelLogger.logInfo(localize('using git', "Using git {0} from {1}", info.version, info.path));
|
||||
|
||||
const git = new Git({
|
||||
gitPath: info.path,
|
||||
@@ -73,7 +88,7 @@ async function createModel(context: ExtensionContext, outputChannel: OutputChann
|
||||
version: info.version,
|
||||
env: environment,
|
||||
});
|
||||
const model = new Model(git, askpass, context.globalState, outputChannel, telemetryReporter);
|
||||
const model = new Model(git, askpass, context.globalState, outputChannelLogger, telemetryReporter);
|
||||
disposables.push(model);
|
||||
|
||||
const onRepository = () => commands.executeCommand('setContext', 'gitOpenRepositoryCount', `${model.repositories.length}`);
|
||||
@@ -81,8 +96,6 @@ async function createModel(context: ExtensionContext, outputChannel: OutputChann
|
||||
model.onDidCloseRepository(onRepository, null, disposables);
|
||||
onRepository();
|
||||
|
||||
outputChannel.appendLine(localize('using git', "{0} Using git {1} from {2}", logTimestamp(), info.version, info.path));
|
||||
|
||||
const onOutput = (str: string) => {
|
||||
const lines = str.split(/\r?\n/mg);
|
||||
|
||||
@@ -90,12 +103,12 @@ async function createModel(context: ExtensionContext, outputChannel: OutputChann
|
||||
lines.pop();
|
||||
}
|
||||
|
||||
outputChannel.appendLine(`${logTimestamp()} ${lines.join('\n')}`);
|
||||
outputChannelLogger.log(lines.join('\n'));
|
||||
};
|
||||
git.onOutput.addListener('log', onOutput);
|
||||
disposables.push(toDisposable(() => git.onOutput.removeListener('log', onOutput)));
|
||||
|
||||
const cc = new CommandCenter(git, model, outputChannel, telemetryReporter);
|
||||
const cc = new CommandCenter(git, model, outputChannelLogger, telemetryReporter);
|
||||
disposables.push(
|
||||
cc,
|
||||
new GitFileSystemProvider(model),
|
||||
@@ -104,6 +117,9 @@ async function createModel(context: ExtensionContext, outputChannel: OutputChann
|
||||
new GitTimelineProvider(model, cc)
|
||||
);
|
||||
|
||||
// const postCommitCommandsProvider = new GitPostCommitCommandsProvider(); {{SQL CARBON TODO}} lewissanchez - Do we need this?
|
||||
// model.registerPostCommitCommandsProvider(postCommitCommandsProvider); {{SQL CARBON TODO}} lewissanchez - Do we need this?
|
||||
|
||||
// checkGitVersion(info); {{SQL CARBON EDIT}} Don't check git version
|
||||
|
||||
return model;
|
||||
@@ -162,9 +178,8 @@ export async function _activate(context: ExtensionContext): Promise<GitExtension
|
||||
const disposables: Disposable[] = [];
|
||||
context.subscriptions.push(new Disposable(() => Disposable.from(...disposables).dispose()));
|
||||
|
||||
const outputChannel = window.createOutputChannel('Git');
|
||||
commands.registerCommand('git.showOutput', () => outputChannel.show());
|
||||
disposables.push(outputChannel);
|
||||
const outputChannelLogger = new OutputChannelLogger();
|
||||
disposables.push(outputChannelLogger);
|
||||
|
||||
const { name, version, aiKey } = require('../package.json') as { name: string; version: string; aiKey: string };
|
||||
const telemetryReporter = new TelemetryReporter(name, version, aiKey);
|
||||
@@ -178,12 +193,12 @@ export async function _activate(context: ExtensionContext): Promise<GitExtension
|
||||
const onEnabled = filterEvent(onConfigChange, () => workspace.getConfiguration('git', null).get<boolean>('enabled') === true);
|
||||
const result = new GitExtensionImpl();
|
||||
|
||||
eventToPromise(onEnabled).then(async () => result.model = await createModel(context, outputChannel, telemetryReporter, disposables));
|
||||
eventToPromise(onEnabled).then(async () => result.model = await createModel(context, outputChannelLogger, telemetryReporter, disposables));
|
||||
return result;
|
||||
}
|
||||
|
||||
try {
|
||||
const model = await createModel(context, outputChannel, telemetryReporter, disposables);
|
||||
const model = await createModel(context, outputChannelLogger, telemetryReporter, disposables);
|
||||
return new GitExtensionImpl(model);
|
||||
} catch (err) {
|
||||
if (!/Git installation not found/.test(err.message || '')) {
|
||||
@@ -194,7 +209,9 @@ export async function _activate(context: ExtensionContext): Promise<GitExtension
|
||||
// outputChannel.appendLine(err.message); {{SQL CARBON EDIT}} turn-off Git missing prompt
|
||||
|
||||
/* __GDPR__
|
||||
"git.missing" : {}
|
||||
"git.missing" : {
|
||||
"owner": "lszomoru"
|
||||
}
|
||||
*/
|
||||
telemetryReporter.sendTelemetryEvent('git.missing');
|
||||
|
||||
|
||||
+57
-18
@@ -3,22 +3,23 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { workspace, WorkspaceFoldersChangeEvent, Uri, window, Event, EventEmitter, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup, TextEditor, Memento, OutputChannel, commands } from 'vscode';
|
||||
import { workspace, WorkspaceFoldersChangeEvent, Uri, window, Event, EventEmitter, QuickPickItem, Disposable, SourceControl, SourceControlResourceGroup, TextEditor, Memento, commands } from 'vscode';
|
||||
import TelemetryReporter from '@vscode/extension-telemetry';
|
||||
import { Repository, RepositoryState } from './repository';
|
||||
import { memoize, sequentialize, debounce } from './decorators';
|
||||
import { dispose, anyEvent, filterEvent, isDescendant, pathEquals, toDisposable, eventToPromise, logTimestamp } from './util';
|
||||
import { dispose, anyEvent, filterEvent, isDescendant, pathEquals, toDisposable, eventToPromise } from './util';
|
||||
import { Git } from './git';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { fromGitUri } from './uri';
|
||||
import { APIState as State, CredentialsProvider, PushErrorHandler, PublishEvent, RemoteSourcePublisher } from './api/git';
|
||||
import { APIState as State, CredentialsProvider, PushErrorHandler, PublishEvent, RemoteSourcePublisher, PostCommitCommandsProvider } from './api/git';
|
||||
import { Askpass } from './askpass';
|
||||
import { IPushErrorHandlerRegistry } from './pushError';
|
||||
import { ApiRepository } from './api/api1';
|
||||
import { IRemoteSourcePublisherRegistry } from './remotePublisher';
|
||||
import { Log, LogLevel } from './log';
|
||||
import { OutputChannelLogger } from './log';
|
||||
import { IPostCommitCommandsProviderRegistry } from './postCommitCommands';
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
@@ -50,7 +51,7 @@ interface OpenRepository extends Disposable {
|
||||
repository: Repository;
|
||||
}
|
||||
|
||||
export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerRegistry {
|
||||
export class Model implements IRemoteSourcePublisherRegistry, IPostCommitCommandsProviderRegistry, IPushErrorHandlerRegistry {
|
||||
|
||||
private _onDidOpenRepository = new EventEmitter<Repository>();
|
||||
readonly onDidOpenRepository: Event<Repository> = this._onDidOpenRepository.event;
|
||||
@@ -105,12 +106,17 @@ export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerR
|
||||
private _onDidRemoveRemoteSourcePublisher = new EventEmitter<RemoteSourcePublisher>();
|
||||
readonly onDidRemoveRemoteSourcePublisher = this._onDidRemoveRemoteSourcePublisher.event;
|
||||
|
||||
private postCommitCommandsProviders = new Set<PostCommitCommandsProvider>();
|
||||
|
||||
private _onDidChangePostCommitCommandsProviders = new EventEmitter<void>();
|
||||
readonly onDidChangePostCommitCommandsProviders = this._onDidChangePostCommitCommandsProviders.event;
|
||||
|
||||
private showRepoOnHomeDriveRootWarning = true;
|
||||
private pushErrorHandlers = new Set<PushErrorHandler>();
|
||||
|
||||
private disposables: Disposable[] = [];
|
||||
|
||||
constructor(readonly git: Git, private readonly askpass: Askpass, private globalState: Memento, private outputChannel: OutputChannel, private telemetryReporter: TelemetryReporter) {
|
||||
constructor(readonly git: Git, private readonly askpass: Askpass, private globalState: Memento, private outputChannelLogger: OutputChannelLogger, private telemetryReporter: TelemetryReporter) {
|
||||
workspace.onDidChangeWorkspaceFolders(this.onDidChangeWorkspaceFolders, this, this.disposables);
|
||||
window.onDidChangeVisibleTextEditors(this.onDidChangeVisibleTextEditors, this, this.disposables);
|
||||
workspace.onDidChangeConfiguration(this.onDidChangeConfiguration, this, this.disposables);
|
||||
@@ -143,11 +149,7 @@ export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerR
|
||||
private async scanWorkspaceFolders(): Promise<void> {
|
||||
const config = workspace.getConfiguration('git');
|
||||
const autoRepositoryDetection = config.get<boolean | 'subFolders' | 'openEditors'>('autoRepositoryDetection');
|
||||
|
||||
// Log repository scan settings
|
||||
if (Log.logLevel <= LogLevel.Trace) {
|
||||
this.outputChannel.appendLine(`${logTimestamp()} Trace: autoRepositoryDetection="${autoRepositoryDetection}"`);
|
||||
}
|
||||
this.outputChannelLogger.logTrace(`[swsf] Scan workspace sub folders. autoRepositoryDetection=${autoRepositoryDetection}`);
|
||||
|
||||
if (autoRepositoryDetection !== true && autoRepositoryDetection !== 'subFolders') {
|
||||
return;
|
||||
@@ -155,6 +157,7 @@ export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerR
|
||||
|
||||
await Promise.all((workspace.workspaceFolders || []).map(async folder => {
|
||||
const root = folder.uri.fsPath;
|
||||
this.outputChannelLogger.logTrace(`[swsf] Workspace folder: ${root}`);
|
||||
|
||||
// Workspace folder children
|
||||
const repositoryScanMaxDepth = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get<number>('repositoryScanMaxDepth', 1);
|
||||
@@ -164,19 +167,25 @@ export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerR
|
||||
|
||||
// Repository scan folders
|
||||
const scanPaths = (workspace.isTrusted ? workspace.getConfiguration('git', folder.uri) : config).get<string[]>('scanRepositories') || [];
|
||||
this.outputChannelLogger.logTrace(`[swsf] Workspace scan settings: repositoryScanMaxDepth=${repositoryScanMaxDepth}; repositoryScanIgnoredFolders=[${repositoryScanIgnoredFolders.join(', ')}]; scanRepositories=[${scanPaths.join(', ')}]`);
|
||||
|
||||
for (const scanPath of scanPaths) {
|
||||
if (scanPath === '.git') {
|
||||
this.outputChannelLogger.logTrace('[swsf] \'.git\' not supported in \'git.scanRepositories\' setting.');
|
||||
continue;
|
||||
}
|
||||
|
||||
if (path.isAbsolute(scanPath)) {
|
||||
console.warn(localize('not supported', "Absolute paths not supported in 'git.scanRepositories' setting."));
|
||||
const notSupportedMessage = localize('not supported', "Absolute paths not supported in 'git.scanRepositories' setting.");
|
||||
this.outputChannelLogger.logWarning(notSupportedMessage);
|
||||
console.warn(notSupportedMessage);
|
||||
continue;
|
||||
}
|
||||
|
||||
subfolders.add(path.join(root, scanPath));
|
||||
}
|
||||
|
||||
this.outputChannelLogger.logTrace(`[swsf] Workspace scan sub folders: [${[...subfolders].join(', ')}]`);
|
||||
await Promise.all([...subfolders].map(f => this.openRepository(f)));
|
||||
}));
|
||||
}
|
||||
@@ -247,6 +256,7 @@ export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerR
|
||||
.filter(r => !(workspace.workspaceFolders || []).some(f => isDescendant(f.uri.fsPath, r!.repository.root))) as OpenRepository[];
|
||||
|
||||
openRepositoriesToDispose.forEach(r => r.dispose());
|
||||
this.outputChannelLogger.logTrace(`[swf] Scan workspace folders: [${possibleRepositoryFolders.map(p => p.uri.fsPath).join(', ')}]`);
|
||||
await Promise.all(possibleRepositoryFolders.map(p => this.openRepository(p.uri.fsPath)));
|
||||
}
|
||||
|
||||
@@ -260,17 +270,20 @@ export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerR
|
||||
.filter(({ root }) => workspace.getConfiguration('git', root).get<boolean>('enabled') !== true)
|
||||
.map(({ repository }) => repository);
|
||||
|
||||
this.outputChannelLogger.logTrace(`[swf] Scan workspace folders: [${possibleRepositoryFolders.map(p => p.uri.fsPath).join(', ')}]`);
|
||||
possibleRepositoryFolders.forEach(p => this.openRepository(p.uri.fsPath));
|
||||
openRepositoriesToDispose.forEach(r => r.dispose());
|
||||
}
|
||||
|
||||
private async onDidChangeVisibleTextEditors(editors: readonly TextEditor[]): Promise<void> {
|
||||
if (!workspace.isTrusted) {
|
||||
this.outputChannelLogger.logTrace('[svte] Workspace is not trusted.');
|
||||
return;
|
||||
}
|
||||
|
||||
const config = workspace.getConfiguration('git');
|
||||
const autoRepositoryDetection = config.get<boolean | 'subFolders' | 'openEditors'>('autoRepositoryDetection');
|
||||
this.outputChannelLogger.logTrace(`[svte] Scan visible text editors. autoRepositoryDetection=${autoRepositoryDetection}`);
|
||||
|
||||
if (autoRepositoryDetection !== true && autoRepositoryDetection !== 'openEditors') {
|
||||
return;
|
||||
@@ -286,16 +299,20 @@ export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerR
|
||||
const repository = this.getRepository(uri);
|
||||
|
||||
if (repository) {
|
||||
this.outputChannelLogger.logTrace(`[svte] Repository for editor resource ${uri.fsPath} already exists: ${repository.root}`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.outputChannelLogger.logTrace(`[svte] Open repository for editor resource ${uri.fsPath}`);
|
||||
await this.openRepository(path.dirname(uri.fsPath));
|
||||
}));
|
||||
}
|
||||
|
||||
@sequentialize
|
||||
async openRepository(repoPath: string): Promise<void> {
|
||||
this.outputChannelLogger.logTrace(`Opening repository: ${repoPath}`);
|
||||
if (this.getRepository(repoPath)) {
|
||||
this.outputChannelLogger.logTrace(`Repository for path ${repoPath} already exists`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -303,6 +320,7 @@ export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerR
|
||||
const enabled = config.get<boolean>('enabled') === true;
|
||||
|
||||
if (!enabled) {
|
||||
this.outputChannelLogger.logTrace('Git is not enabled');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -312,6 +330,7 @@ export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerR
|
||||
fs.accessSync(path.join(repoPath, 'HEAD'), fs.constants.F_OK);
|
||||
const result = await this.git.exec(repoPath, ['-C', repoPath, 'rev-parse', '--show-cdup']);
|
||||
if (result.stderr.trim() === '' && result.stdout.trim() === '') {
|
||||
this.outputChannelLogger.logTrace(`Bare repository: ${repoPath}`);
|
||||
return;
|
||||
}
|
||||
} catch {
|
||||
@@ -326,12 +345,15 @@ export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerR
|
||||
// case insensitive file systems
|
||||
// https://github.com/microsoft/vscode/issues/33498
|
||||
const repositoryRoot = Uri.file(rawRoot).fsPath;
|
||||
this.outputChannelLogger.logTrace(`Repository root: ${repositoryRoot}`);
|
||||
|
||||
if (this.getRepository(repositoryRoot)) {
|
||||
this.outputChannelLogger.logTrace(`Repository for path ${repositoryRoot} already exists`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.shouldRepositoryBeIgnored(rawRoot)) {
|
||||
this.outputChannelLogger.logTrace(`Repository for path ${repositoryRoot} is ignored`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -347,20 +369,19 @@ export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerR
|
||||
this.showRepoOnHomeDriveRootWarning = false;
|
||||
}
|
||||
|
||||
this.outputChannelLogger.logTrace(`Repository for path ${repositoryRoot} is on the root of the HOMEDRIVE`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const dotGit = await this.git.getRepositoryDotGit(repositoryRoot);
|
||||
const repository = new Repository(this.git.open(repositoryRoot, dotGit), this, this, this.globalState, this.outputChannel, this.telemetryReporter);
|
||||
const repository = new Repository(this.git.open(repositoryRoot, dotGit), this, this, this, this.globalState, this.outputChannelLogger, this.telemetryReporter);
|
||||
|
||||
this.open(repository);
|
||||
repository.status(); // do not await this, we want SCM to know about the repo asap
|
||||
} catch (ex) {
|
||||
// noop
|
||||
if (Log.logLevel <= LogLevel.Trace) {
|
||||
this.outputChannel.appendLine(`${logTimestamp()} Trace: Opening repository for path='${repoPath}' failed; ex=${ex}`);
|
||||
}
|
||||
this.outputChannelLogger.logTrace(`Opening repository for path='${repoPath}' failed; ex=${ex}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -386,7 +407,7 @@ export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerR
|
||||
}
|
||||
|
||||
private open(repository: Repository): void {
|
||||
this.outputChannel.appendLine(`${logTimestamp()} Open repository: ${repository.root}`);
|
||||
this.outputChannelLogger.logInfo(`Open repository: ${repository.root}`);
|
||||
|
||||
const onDidDisappearRepository = filterEvent(repository.onDidChangeState, state => state === RepositoryState.Disposed);
|
||||
const disappearListener = onDidDisappearRepository(() => dispose());
|
||||
@@ -443,7 +464,7 @@ export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerR
|
||||
return;
|
||||
}
|
||||
|
||||
this.outputChannel.appendLine(`${logTimestamp()} Close repository: ${repository.root}`);
|
||||
this.outputChannelLogger.logInfo(`Close repository: ${repository.root}`);
|
||||
openRepository.dispose();
|
||||
}
|
||||
|
||||
@@ -491,6 +512,10 @@ export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerR
|
||||
return this.openRepositories.filter(r => r.repository === hint)[0];
|
||||
}
|
||||
|
||||
if (hint instanceof ApiRepository) {
|
||||
return this.openRepositories.filter(r => r.repository === hint.repository)[0];
|
||||
}
|
||||
|
||||
if (typeof hint === 'string') {
|
||||
hint = Uri.file(hint);
|
||||
}
|
||||
@@ -567,6 +592,20 @@ export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerR
|
||||
return [...this.remoteSourcePublishers.values()];
|
||||
}
|
||||
|
||||
registerPostCommitCommandsProvider(provider: PostCommitCommandsProvider): Disposable {
|
||||
this.postCommitCommandsProviders.add(provider);
|
||||
this._onDidChangePostCommitCommandsProviders.fire();
|
||||
|
||||
return toDisposable(() => {
|
||||
this.postCommitCommandsProviders.delete(provider);
|
||||
this._onDidChangePostCommitCommandsProviders.fire();
|
||||
});
|
||||
}
|
||||
|
||||
getPostCommitCommandsProviders(): PostCommitCommandsProvider[] {
|
||||
return [...this.postCommitCommandsProviders.values()];
|
||||
}
|
||||
|
||||
registerCredentialsProvider(provider: CredentialsProvider): Disposable {
|
||||
return this.askpass.registerCredentialsProvider(provider);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
import { Command, Disposable, Event } from 'vscode';
|
||||
import { PostCommitCommandsProvider } from './api/git';
|
||||
|
||||
export interface IPostCommitCommandsProviderRegistry {
|
||||
readonly onDidChangePostCommitCommandsProviders: Event<void>;
|
||||
|
||||
getPostCommitCommandsProviders(): PostCommitCommandsProvider[];
|
||||
registerPostCommitCommandsProvider(provider: PostCommitCommandsProvider): Disposable;
|
||||
}
|
||||
|
||||
const localize = nls.loadMessageBundle();
|
||||
|
||||
export class GitPostCommitCommandsProvider implements PostCommitCommandsProvider {
|
||||
getCommands(): Command[] {
|
||||
return [
|
||||
{
|
||||
command: 'git.push',
|
||||
title: localize('scm secondary button commit and push', "Commit & Push")
|
||||
},
|
||||
{
|
||||
command: 'git.sync',
|
||||
title: localize('scm secondary button commit and sync', "Commit & Sync")
|
||||
},
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,15 @@ export class GitProtocolHandler implements UriHandler {
|
||||
|
||||
let cloneUri: Uri;
|
||||
try {
|
||||
cloneUri = Uri.parse(Array.isArray(data.url) ? data.url[0] : data.url, true);
|
||||
let rawUri = Array.isArray(data.url) ? data.url[0] : data.url;
|
||||
|
||||
// Handle SSH Uri
|
||||
// Ex: git@github.com:microsoft/vscode.git
|
||||
rawUri = rawUri.replace(/^(git@[^\/:]+)(:)/i, 'ssh://$1/');
|
||||
|
||||
cloneUri = Uri.parse(rawUri, true);
|
||||
|
||||
// Validate against supported schemes
|
||||
if (!schemes.has(cloneUri.scheme.toLowerCase())) {
|
||||
throw new Error('Unsupported scheme.');
|
||||
}
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { CancellationToken, Command, Disposable, Event, EventEmitter, Memento, OutputChannel, ProgressLocation, ProgressOptions, scm, SourceControl, SourceControlInputBox, SourceControlInputBoxValidation, SourceControlInputBoxValidationType, SourceControlResourceDecorations, SourceControlResourceGroup, SourceControlResourceState, ThemeColor, Uri, window, workspace, WorkspaceEdit, FileDecoration, commands, Tab, TabInputTextDiff, TabInputNotebookDiff, RelativePattern } from 'vscode';
|
||||
import * as picomatch from 'picomatch';
|
||||
import { CancellationToken, Command, Disposable, Event, EventEmitter, Memento, ProgressLocation, ProgressOptions, scm, SourceControl, SourceControlInputBox, SourceControlInputBoxValidation, SourceControlInputBoxValidationType, SourceControlResourceDecorations, SourceControlResourceGroup, SourceControlResourceState, ThemeColor, Uri, window, workspace, WorkspaceEdit, FileDecoration, commands, Tab, TabInputTextDiff, TabInputNotebookDiff, RelativePattern } from 'vscode';
|
||||
import TelemetryReporter from '@vscode/extension-telemetry';
|
||||
import * as nls from 'vscode-nls';
|
||||
import { Branch, Change, ForcePushMode, GitErrorCodes, LogOptions, Ref, RefType, Remote, Status, CommitOptions, BranchQuery, FetchOptions } from './api/git';
|
||||
@@ -14,13 +15,14 @@ import { debounce, memoize, throttle } from './decorators';
|
||||
import { Commit, GitError, Repository as BaseRepository, Stash, Submodule, LogFileOptions } from './git';
|
||||
import { StatusBarCommands } from './statusbar';
|
||||
import { toGitUri } from './uri';
|
||||
import { anyEvent, combinedDisposable, debounceEvent, dispose, EmptyDisposable, eventToPromise, filterEvent, find, IDisposable, isDescendant, logTimestamp, onceEvent, pathEquals, relativePath } from './util';
|
||||
import { anyEvent, combinedDisposable, debounceEvent, dispose, EmptyDisposable, eventToPromise, filterEvent, find, IDisposable, isDescendant, onceEvent, pathEquals, relativePath } from './util';
|
||||
import { IFileWatcher, watch } from './watch';
|
||||
import { Log, LogLevel } from './log';
|
||||
import { LogLevel, OutputChannelLogger } from './log';
|
||||
import { IPushErrorHandlerRegistry } from './pushError';
|
||||
import { ApiRepository } from './api/api1';
|
||||
import { IRemoteSourcePublisherRegistry } from './remotePublisher';
|
||||
import { ActionButtonCommand } from './actionButton';
|
||||
import { IPostCommitCommandsProviderRegistry } from './postCommitCommands';
|
||||
|
||||
const timeout = (millis: number) => new Promise(c => setTimeout(c, millis));
|
||||
|
||||
@@ -296,6 +298,10 @@ export class Resource implements SourceControlResourceState {
|
||||
const command = this._commandResolver.resolveChangeCommand(this);
|
||||
await commands.executeCommand<void>(command.command, ...(command.arguments || []));
|
||||
}
|
||||
|
||||
clone() {
|
||||
return new Resource(this._commandResolver, this._resourceGroupType, this._resourceUri, this._type, this._useIcons, this._renameResourceUri);
|
||||
}
|
||||
}
|
||||
|
||||
export const enum Operation {
|
||||
@@ -450,6 +456,13 @@ class ProgressManager {
|
||||
const onDidChange = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git', Uri.file(this.repository.root)));
|
||||
onDidChange(_ => this.updateEnablement());
|
||||
this.updateEnablement();
|
||||
|
||||
this.repository.onDidChangeOperations(() => {
|
||||
const commitInProgress = this.repository.operations.isRunning(Operation.Commit);
|
||||
|
||||
this.repository.sourceControl.inputBox.enabled = !commitInProgress;
|
||||
commands.executeCommand('setContext', 'commitInProgress', commitInProgress);
|
||||
});
|
||||
}
|
||||
|
||||
private updateEnablement(): void {
|
||||
@@ -504,10 +517,10 @@ class FileEventLogger {
|
||||
constructor(
|
||||
private onWorkspaceWorkingTreeFileChange: Event<Uri>,
|
||||
private onDotGitFileChange: Event<Uri>,
|
||||
private outputChannel: OutputChannel
|
||||
private outputChannelLogger: OutputChannelLogger
|
||||
) {
|
||||
this.logLevelDisposable = Log.onDidChangeLogLevel(this.onDidChangeLogLevel, this);
|
||||
this.onDidChangeLogLevel(Log.logLevel);
|
||||
this.logLevelDisposable = outputChannelLogger.onDidChangeLogLevel(this.onDidChangeLogLevel, this);
|
||||
this.onDidChangeLogLevel(outputChannelLogger.currentLogLevel);
|
||||
}
|
||||
|
||||
private onDidChangeLogLevel(level: LogLevel): void {
|
||||
@@ -518,8 +531,8 @@ class FileEventLogger {
|
||||
}
|
||||
|
||||
this.eventDisposable = combinedDisposable([
|
||||
this.onWorkspaceWorkingTreeFileChange(uri => this.outputChannel.appendLine(`${logTimestamp()} [debug] [wt] Change: ${uri.fsPath}`)),
|
||||
this.onDotGitFileChange(uri => this.outputChannel.appendLine(`${logTimestamp()} [debug] [.git] Change: ${uri.fsPath}`))
|
||||
this.onWorkspaceWorkingTreeFileChange(uri => this.outputChannelLogger.logDebug(`[wt] Change: ${uri.fsPath}`)),
|
||||
this.onDotGitFileChange(uri => this.outputChannelLogger.logDebug(`[.git] Change: ${uri.fsPath}`))
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -539,14 +552,14 @@ class DotGitWatcher implements IFileWatcher {
|
||||
|
||||
constructor(
|
||||
private repository: Repository,
|
||||
private outputChannel: OutputChannel
|
||||
private outputChannelLogger: OutputChannelLogger
|
||||
) {
|
||||
const rootWatcher = watch(repository.dotGit.path);
|
||||
this.disposables.push(rootWatcher);
|
||||
|
||||
// Ignore changes to the "index.lock" file, and watchman fsmonitor hook (https://git-scm.com/docs/githooks#_fsmonitor_watchman) cookie files.
|
||||
// Watchman creates a cookie file inside the git directory whenever a query is run (https://facebook.github.io/watchman/docs/cookies.html).
|
||||
const filteredRootWatcher = filterEvent(rootWatcher.event, uri => !/\/\.git(\/index\.lock)?$|\/\.watchman-cookie-/.test(uri.path));
|
||||
const filteredRootWatcher = filterEvent(rootWatcher.event, uri => uri.scheme === 'file' && !/\/\.git(\/index\.lock)?$|\/\.watchman-cookie-/.test(uri.path));
|
||||
this.event = anyEvent(filteredRootWatcher, this.emitter.event);
|
||||
|
||||
repository.onDidRunGitStatus(this.updateTransientWatchers, this, this.disposables);
|
||||
@@ -570,9 +583,7 @@ class DotGitWatcher implements IFileWatcher {
|
||||
this.transientDisposables.push(upstreamWatcher);
|
||||
upstreamWatcher.event(this.emitter.fire, this.emitter, this.transientDisposables);
|
||||
} catch (err) {
|
||||
if (Log.logLevel <= LogLevel.Error) {
|
||||
this.outputChannel.appendLine(`${logTimestamp()} Warning: Failed to watch ref '${upstreamPath}', is most likely packed.`);
|
||||
}
|
||||
this.outputChannelLogger.logWarning(`Failed to watch ref '${upstreamPath}', is most likely packed.`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -605,11 +616,20 @@ class ResourceCommandResolver {
|
||||
const title = this.getTitle(resource);
|
||||
|
||||
if (!resource.leftUri) {
|
||||
return {
|
||||
command: 'vscode.open',
|
||||
title: localize('open', "Open"),
|
||||
arguments: [resource.rightUri, { override: resource.type === Status.BOTH_MODIFIED ? false : undefined }, title]
|
||||
};
|
||||
const bothModified = resource.type === Status.BOTH_MODIFIED;
|
||||
if (resource.rightUri && workspace.getConfiguration('git').get<boolean>('mergeEditor', false) && (bothModified || resource.type === Status.BOTH_ADDED)) {
|
||||
return {
|
||||
command: '_git.openMergeEditor',
|
||||
title: localize('open.merge', "Open Merge"),
|
||||
arguments: [resource.rightUri]
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
command: 'vscode.open',
|
||||
title: localize('open', "Open"),
|
||||
arguments: [resource.rightUri, { override: bothModified ? false : undefined }, title]
|
||||
};
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
command: 'vscode.diff',
|
||||
@@ -849,6 +869,7 @@ export class Repository implements Disposable {
|
||||
private isRepositoryHuge: false | { limit: number } = false;
|
||||
private didWarnAboutLimit = false;
|
||||
|
||||
private isBranchProtectedMatcher: picomatch.Matcher | undefined;
|
||||
private resourceCommandResolver = new ResourceCommandResolver(this);
|
||||
private disposables: Disposable[] = [];
|
||||
|
||||
@@ -856,8 +877,9 @@ export class Repository implements Disposable {
|
||||
private readonly repository: BaseRepository,
|
||||
private pushErrorHandlerRegistry: IPushErrorHandlerRegistry,
|
||||
remoteSourcePublisherRegistry: IRemoteSourcePublisherRegistry,
|
||||
postCommitCommandsProviderRegistry: IPostCommitCommandsProviderRegistry,
|
||||
globalState: Memento,
|
||||
outputChannel: OutputChannel,
|
||||
outputChannelLogger: OutputChannelLogger,
|
||||
private telemetryReporter: TelemetryReporter
|
||||
) {
|
||||
const repositoryWatcher = workspace.createFileSystemWatcher(new RelativePattern(Uri.file(repository.root), '**'));
|
||||
@@ -869,13 +891,11 @@ export class Repository implements Disposable {
|
||||
let onRepositoryDotGitFileChange: Event<Uri>;
|
||||
|
||||
try {
|
||||
const dotGitFileWatcher = new DotGitWatcher(this, outputChannel);
|
||||
const dotGitFileWatcher = new DotGitWatcher(this, outputChannelLogger);
|
||||
onRepositoryDotGitFileChange = dotGitFileWatcher.event;
|
||||
this.disposables.push(dotGitFileWatcher);
|
||||
} catch (err) {
|
||||
if (Log.logLevel <= LogLevel.Error) {
|
||||
outputChannel.appendLine(`${logTimestamp()} Failed to watch path:'${this.dotGit.path}' or commonPath:'${this.dotGit.commonPath}', reverting to legacy API file watched. Some events might be lost.\n${err.stack || err}`);
|
||||
}
|
||||
outputChannelLogger.logError(`Failed to watch path:'${this.dotGit.path}' or commonPath:'${this.dotGit.commonPath}', reverting to legacy API file watched. Some events might be lost.\n${err.stack || err}`);
|
||||
|
||||
onRepositoryDotGitFileChange = filterEvent(onRepositoryFileChange, uri => /\.git($|\/)/.test(uri.path));
|
||||
}
|
||||
@@ -889,7 +909,7 @@ export class Repository implements Disposable {
|
||||
// Relevate repository changes should trigger virtual document change events
|
||||
onRepositoryDotGitFileChange(this._onDidChangeRepository.fire, this._onDidChangeRepository, this.disposables);
|
||||
|
||||
this.disposables.push(new FileEventLogger(onRepositoryWorkingTreeFileChange, onRepositoryDotGitFileChange, outputChannel));
|
||||
this.disposables.push(new FileEventLogger(onRepositoryWorkingTreeFileChange, onRepositoryDotGitFileChange, outputChannelLogger));
|
||||
|
||||
const root = Uri.file(repository.root);
|
||||
this._sourceControl = scm.createSourceControl('git', 'Git', root);
|
||||
@@ -916,13 +936,19 @@ export class Repository implements Disposable {
|
||||
onConfigListener(updateIndexGroupVisibility, this, this.disposables);
|
||||
updateIndexGroupVisibility();
|
||||
|
||||
workspace.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('git.mergeEditor')) {
|
||||
this.mergeGroup.resourceStates = this.mergeGroup.resourceStates.map(r => r.clone());
|
||||
}
|
||||
}, undefined, this.disposables);
|
||||
|
||||
filterEvent(workspace.onDidChangeConfiguration, e =>
|
||||
e.affectsConfiguration('git.branchSortOrder', root)
|
||||
e.affectsConfiguration('git.branchProtection', root)
|
||||
|| e.affectsConfiguration('git.branchSortOrder', root)
|
||||
|| e.affectsConfiguration('git.untrackedChanges', root)
|
||||
|| e.affectsConfiguration('git.ignoreSubmodules', root)
|
||||
|| e.affectsConfiguration('git.openDiffOnClick', root)
|
||||
|| e.affectsConfiguration('git.rebaseWhenSync', root)
|
||||
|| e.affectsConfiguration('git.showUnpublishedCommitsButton', root)
|
||||
|| e.affectsConfiguration('git.showActionButton', root)
|
||||
)(this.updateModelState, this, this.disposables);
|
||||
|
||||
const updateInputBoxVisibility = () => {
|
||||
@@ -963,12 +989,16 @@ export class Repository implements Disposable {
|
||||
}
|
||||
}, null, this.disposables);
|
||||
|
||||
const onDidChangeBranchProtection = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.branchProtection', root));
|
||||
onDidChangeBranchProtection(this.updateBranchProtectionMatcher, this, this.disposables);
|
||||
this.updateBranchProtectionMatcher();
|
||||
|
||||
const statusBar = new StatusBarCommands(this, remoteSourcePublisherRegistry);
|
||||
this.disposables.push(statusBar);
|
||||
statusBar.onDidChange(() => this._sourceControl.statusBarCommands = statusBar.commands, null, this.disposables);
|
||||
this._sourceControl.statusBarCommands = statusBar.commands;
|
||||
|
||||
const actionButton = new ActionButtonCommand(this);
|
||||
const actionButton = new ActionButtonCommand(this, postCommitCommandsProviderRegistry);
|
||||
this.disposables.push(actionButton);
|
||||
actionButton.onDidChange(() => this._sourceControl.actionButton = actionButton.button);
|
||||
this._sourceControl.actionButton = actionButton.button;
|
||||
@@ -1014,7 +1044,7 @@ export class Repository implements Disposable {
|
||||
}
|
||||
|
||||
let lineNumber = 0;
|
||||
let start = 0, end;
|
||||
let start = 0;
|
||||
let match: RegExpExecArray | null;
|
||||
const regex = /\r?\n/g;
|
||||
|
||||
@@ -1023,7 +1053,7 @@ export class Repository implements Disposable {
|
||||
lineNumber++;
|
||||
}
|
||||
|
||||
end = match ? match.index : text.length;
|
||||
const end = match ? match.index : text.length;
|
||||
|
||||
const line = text.substring(start, end);
|
||||
|
||||
@@ -1267,7 +1297,7 @@ export class Repository implements Disposable {
|
||||
});
|
||||
}
|
||||
|
||||
closeDiffEditors(indexResources: string[], workingTreeResources: string[], ignoreSetting: boolean = false): void {
|
||||
closeDiffEditors(indexResources: string[] | undefined, workingTreeResources: string[] | undefined, ignoreSetting: boolean = false): void {
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.root));
|
||||
if (!config.get<boolean>('closeDiffOnOperation', false) && !ignoreSetting) { return; }
|
||||
|
||||
@@ -1276,11 +1306,11 @@ export class Repository implements Disposable {
|
||||
for (const tab of window.tabGroups.all.map(g => g.tabs).flat()) {
|
||||
const { input } = tab;
|
||||
if (input instanceof TabInputTextDiff || input instanceof TabInputNotebookDiff) {
|
||||
if (input.modified.scheme === 'git' && indexResources.some(r => pathEquals(r, input.modified.fsPath))) {
|
||||
if (input.modified.scheme === 'git' && (indexResources === undefined || indexResources.some(r => pathEquals(r, input.modified.fsPath)))) {
|
||||
// Index
|
||||
diffEditorTabsToClose.push(tab);
|
||||
}
|
||||
if (input.modified.scheme === 'file' && input.original.scheme === 'git' && workingTreeResources.some(r => pathEquals(r, input.modified.fsPath))) {
|
||||
if (input.modified.scheme === 'file' && input.original.scheme === 'git' && (workingTreeResources === undefined || workingTreeResources.some(r => pathEquals(r, input.modified.fsPath)))) {
|
||||
// Working Tree
|
||||
diffEditorTabsToClose.push(tab);
|
||||
}
|
||||
@@ -1386,15 +1416,15 @@ export class Repository implements Disposable {
|
||||
}
|
||||
|
||||
@throttle
|
||||
async fetchAll(): Promise<void> {
|
||||
await this._fetch({ all: true });
|
||||
async fetchAll(cancellationToken?: CancellationToken): Promise<void> {
|
||||
await this._fetch({ all: true, cancellationToken });
|
||||
}
|
||||
|
||||
async fetch(options: FetchOptions): Promise<void> {
|
||||
await this._fetch(options);
|
||||
}
|
||||
|
||||
private async _fetch(options: { remote?: string; ref?: string; all?: boolean; prune?: boolean; depth?: number; silent?: boolean } = {}): Promise<void> {
|
||||
private async _fetch(options: { remote?: string; ref?: string; all?: boolean; prune?: boolean; depth?: number; silent?: boolean; cancellationToken?: CancellationToken } = {}): Promise<void> {
|
||||
if (!options.prune) {
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.root));
|
||||
const prune = config.get<boolean>('pruneOnFetch');
|
||||
@@ -1439,7 +1469,7 @@ export class Repository implements Disposable {
|
||||
|
||||
// When fetchOnPull is enabled, fetch all branches when pulling
|
||||
if (fetchOnPull) {
|
||||
await this.repository.fetch({ all: true });
|
||||
await this.fetchAll();
|
||||
}
|
||||
|
||||
if (await this.checkIfMaybeRebased(this.HEAD?.name)) {
|
||||
@@ -1479,13 +1509,8 @@ export class Repository implements Disposable {
|
||||
}
|
||||
|
||||
@throttle
|
||||
sync(head: Branch): Promise<void> {
|
||||
return this._sync(head, false);
|
||||
}
|
||||
|
||||
@throttle
|
||||
async syncRebase(head: Branch): Promise<void> {
|
||||
return this._sync(head, true);
|
||||
sync(head: Branch, rebase: boolean): Promise<void> {
|
||||
return this._sync(head, rebase);
|
||||
}
|
||||
|
||||
private async _sync(head: Branch, rebase: boolean): Promise<void> {
|
||||
@@ -1510,7 +1535,7 @@ export class Repository implements Disposable {
|
||||
const fn = async (cancellationToken?: CancellationToken) => {
|
||||
// When fetchOnPull is enabled, fetch all branches when pulling
|
||||
if (fetchOnPull) {
|
||||
await this.repository.fetch({ all: true, cancellationToken });
|
||||
await this.fetchAll(cancellationToken);
|
||||
}
|
||||
|
||||
if (await this.checkIfMaybeRebased(this.HEAD?.name)) {
|
||||
@@ -1868,9 +1893,10 @@ export class Repository implements Disposable {
|
||||
if (didHitLimit) {
|
||||
/* __GDPR__
|
||||
"statusLimit" : {
|
||||
"ignoreSubmodules": { "classification": "SystemMetaData", "purpose": "FeatureInsight" },
|
||||
"limit": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true },
|
||||
"statusLength": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }
|
||||
"owner": "lszomoru",
|
||||
"ignoreSubmodules": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "Setting indicating whether submodules are ignored" },
|
||||
"limit": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Setting indicating the limit of status entries" },
|
||||
"statusLength": { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true, "comment": "Total number of status entries" }
|
||||
}
|
||||
*/
|
||||
this.telemetryReporter.sendTelemetryEvent('statusLimit', { ignoreSubmodules: String(ignoreSubmodules) }, { limit, statusLength });
|
||||
@@ -2002,6 +2028,9 @@ export class Repository implements Disposable {
|
||||
// set count badge
|
||||
this.setCountBadge();
|
||||
|
||||
// set mergeChanges context
|
||||
commands.executeCommand('setContext', 'git.mergeChanges', merge.map(item => item.resourceUri.toString()));
|
||||
|
||||
this._onDidChangeStatus.fire();
|
||||
|
||||
this._sourceControl.commitTemplate = await this.getInputTemplate();
|
||||
@@ -2188,6 +2217,21 @@ export class Repository implements Disposable {
|
||||
}
|
||||
}
|
||||
|
||||
private updateBranchProtectionMatcher(): void {
|
||||
const scopedConfig = workspace.getConfiguration('git', Uri.file(this.repository.root));
|
||||
const branchProtectionGlobs = scopedConfig.get<string[]>('branchProtection')!.map(bp => bp.trim()).filter(bp => bp !== '');
|
||||
|
||||
if (branchProtectionGlobs.length === 0) {
|
||||
this.isBranchProtectedMatcher = undefined;
|
||||
} else {
|
||||
this.isBranchProtectedMatcher = picomatch(branchProtectionGlobs);
|
||||
}
|
||||
}
|
||||
|
||||
public isBranchProtected(name: string = this.HEAD?.name ?? ''): boolean {
|
||||
return this.isBranchProtectedMatcher ? this.isBranchProtectedMatcher(name) : false;
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables = dispose(this.disposables);
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ export function applyLineChanges(original: TextDocument, modified: TextDocument,
|
||||
const result: string[] = [];
|
||||
let currentLine = 0;
|
||||
|
||||
for (let diff of diffs) {
|
||||
for (const diff of diffs) {
|
||||
const isInsertion = diff.originalEndLineNumber === 0;
|
||||
const isDeletion = diff.modifiedEndLineNumber === 0;
|
||||
|
||||
|
||||
@@ -24,7 +24,8 @@ class CheckoutStatusBar {
|
||||
|
||||
get command(): Command | undefined {
|
||||
const rebasing = !!this.repository.rebaseCommit;
|
||||
const title = `$(git-branch) ${this.repository.headLabel}${rebasing ? ` (${localize('rebasing', 'Rebasing')})` : ''}`;
|
||||
const isBranchProtected = this.repository.isBranchProtected();
|
||||
const title = `${isBranchProtected ? '$(lock)' : '$(git-branch)'} ${this.repository.headLabel}${rebasing ? ` (${localize('rebasing', 'Rebasing')})` : ''}`;
|
||||
|
||||
return {
|
||||
command: 'git.checkout',
|
||||
@@ -144,10 +145,7 @@ class SyncStatusBar {
|
||||
text += this.repository.syncLabel;
|
||||
}
|
||||
|
||||
const config = workspace.getConfiguration('git', Uri.file(this.repository.root));
|
||||
const rebaseWhenSync = config.get<string>('rebaseWhenSync');
|
||||
|
||||
command = rebaseWhenSync ? 'git.syncRebase' : 'git.sync';
|
||||
command = 'git.sync';
|
||||
tooltip = this.repository.syncTooltip;
|
||||
} else {
|
||||
icon = '$(cloud-upload)';
|
||||
|
||||
@@ -6,27 +6,15 @@
|
||||
import { ExtensionContext, workspace } from 'vscode';
|
||||
import { filterEvent, IDisposable } from './util';
|
||||
|
||||
export interface ITerminalEnvironmentProvider {
|
||||
getTerminalEnv(): { [key: string]: string };
|
||||
}
|
||||
|
||||
export class TerminalEnvironmentManager {
|
||||
|
||||
private readonly disposable: IDisposable;
|
||||
|
||||
private _enabled = false;
|
||||
private set enabled(enabled: boolean) {
|
||||
if (this._enabled === enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._enabled = enabled;
|
||||
this.context.environmentVariableCollection.clear();
|
||||
|
||||
if (enabled) {
|
||||
for (const name of Object.keys(this.env)) {
|
||||
this.context.environmentVariableCollection.replace(name, this.env[name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor(private readonly context: ExtensionContext, private readonly env: { [key: string]: string }) {
|
||||
constructor(private readonly context: ExtensionContext, private readonly envProviders: (ITerminalEnvironmentProvider | undefined)[]) {
|
||||
this.disposable = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git'))
|
||||
(this.refresh, this);
|
||||
|
||||
@@ -35,7 +23,19 @@ export class TerminalEnvironmentManager {
|
||||
|
||||
private refresh(): void {
|
||||
const config = workspace.getConfiguration('git', null);
|
||||
this.enabled = config.get<boolean>('enabled', true) && config.get('terminalAuthentication', true);
|
||||
this.context.environmentVariableCollection.clear();
|
||||
|
||||
if (!config.get<boolean>('enabled', true)) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const envProvider of this.envProviders) {
|
||||
const terminalEnv = envProvider?.getTerminalEnv() ?? {};
|
||||
|
||||
for (const name of Object.keys(terminalEnv)) {
|
||||
this.context.environmentVariableCollection.replace(name, terminalEnv[name]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
|
||||
@@ -205,6 +205,7 @@ john.doe@mail.com
|
||||
1580811030
|
||||
1580811031
|
||||
8e5a374372b8393906c7e380dbb09349c5385554
|
||||
main,branch
|
||||
This is a commit message.\x00`;
|
||||
|
||||
assert.deepStrictEqual(parseGitCommits(GIT_OUTPUT_SINGLE_PARENT), [{
|
||||
@@ -215,6 +216,7 @@ This is a commit message.\x00`;
|
||||
authorName: 'John Doe',
|
||||
authorEmail: 'john.doe@mail.com',
|
||||
commitDate: new Date(1580811031000),
|
||||
refNames: ['main', 'branch'],
|
||||
}]);
|
||||
});
|
||||
|
||||
@@ -225,6 +227,7 @@ john.doe@mail.com
|
||||
1580811030
|
||||
1580811031
|
||||
8e5a374372b8393906c7e380dbb09349c5385554 df27d8c75b129ab9b178b386077da2822101b217
|
||||
main
|
||||
This is a commit message.\x00`;
|
||||
|
||||
assert.deepStrictEqual(parseGitCommits(GIT_OUTPUT_MULTIPLE_PARENTS), [{
|
||||
@@ -235,6 +238,7 @@ This is a commit message.\x00`;
|
||||
authorName: 'John Doe',
|
||||
authorEmail: 'john.doe@mail.com',
|
||||
commitDate: new Date(1580811031000),
|
||||
refNames: ['main'],
|
||||
}]);
|
||||
});
|
||||
|
||||
@@ -245,6 +249,7 @@ john.doe@mail.com
|
||||
1580811030
|
||||
1580811031
|
||||
|
||||
main
|
||||
This is a commit message.\x00`;
|
||||
|
||||
assert.deepStrictEqual(parseGitCommits(GIT_OUTPUT_NO_PARENTS), [{
|
||||
@@ -255,6 +260,7 @@ This is a commit message.\x00`;
|
||||
authorName: 'John Doe',
|
||||
authorEmail: 'john.doe@mail.com',
|
||||
commitDate: new Date(1580811031000),
|
||||
refNames: ['main'],
|
||||
}]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -51,3 +51,14 @@ export function toGitUri(uri: Uri, ref: string, options: GitUriOptions = {}): Ur
|
||||
query: JSON.stringify(params)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Assuming `uri` is being merged it creates uris for `base`, `ours`, and `theirs`
|
||||
*/
|
||||
export function toMergeUris(uri: Uri): { base: Uri; ours: Uri; theirs: Uri } {
|
||||
return {
|
||||
base: toGitUri(uri, ':1'),
|
||||
ours: toGitUri(uri, ':2'),
|
||||
theirs: toGitUri(uri, ':3'),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -16,10 +16,6 @@ export function log(...args: any[]): void {
|
||||
console.log.apply(console, ['git:', ...args]);
|
||||
}
|
||||
|
||||
export function logTimestamp(): string {
|
||||
return `[${new Date().toISOString()}]`;
|
||||
}
|
||||
|
||||
export interface IDisposable {
|
||||
dispose(): void;
|
||||
}
|
||||
@@ -55,9 +51,7 @@ export function anyEvent<T>(...events: Event<T>[]): Event<T> {
|
||||
return (listener: (e: T) => any, thisArgs?: any, disposables?: Disposable[]) => {
|
||||
const result = combinedDisposable(events.map(event => event(i => listener.call(thisArgs, i))));
|
||||
|
||||
if (disposables) {
|
||||
disposables.push(result);
|
||||
}
|
||||
disposables?.push(result);
|
||||
|
||||
return result;
|
||||
};
|
||||
@@ -93,7 +87,7 @@ export function eventToPromise<T>(event: Event<T>): Promise<T> {
|
||||
}
|
||||
|
||||
export function once(fn: (...args: any[]) => any): (...args: any[]) => any {
|
||||
let didRun = false;
|
||||
const didRun = false;
|
||||
|
||||
return (...args) => {
|
||||
if (didRun) {
|
||||
@@ -223,11 +217,11 @@ export async function grep(filename: string, pattern: RegExp): Promise<boolean>
|
||||
export function readBytes(stream: Readable, bytes: number): Promise<Buffer> {
|
||||
return new Promise<Buffer>((complete, error) => {
|
||||
let done = false;
|
||||
let buffer = Buffer.allocUnsafe(bytes);
|
||||
const buffer = Buffer.allocUnsafe(bytes);
|
||||
let bytesRead = 0;
|
||||
|
||||
stream.on('data', (data: Buffer) => {
|
||||
let bytesToRead = Math.min(bytes - bytesRead, data.length);
|
||||
const bytesToRead = Math.min(bytes - bytesRead, data.length);
|
||||
data.copy(buffer, bytesRead, 0, bytesToRead);
|
||||
bytesRead += bytesToRead;
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
"../../src/vscode-dts/vscode.proposed.scmActionButton.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.scmSelectedProvider.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.scmValidation.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.tabs.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.tabInputTextMerge.d.ts",
|
||||
"../../src/vscode-dts/vscode.proposed.timeline.d.ts",
|
||||
"../types/lib.textEncoder.d.ts"
|
||||
]
|
||||
|
||||
@@ -2,6 +2,47 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@joaomoreno/unique-names-generator@5.0.0":
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@joaomoreno/unique-names-generator/-/unique-names-generator-5.0.0.tgz#a67fe66e3d825c929fc97abfdf17fd80a72beab0"
|
||||
integrity sha512-3kP6z7aoGEoM3tvhTBZioYa1QFkovOU8uxAlVclnZlXivwF/WTE5EcOzvoDdM+jtjJyWvCMDR1Q4RBjDqupD3A==
|
||||
|
||||
"@microsoft/1ds-core-js@3.2.3", "@microsoft/1ds-core-js@^3.2.3":
|
||||
version "3.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.3.tgz#2217d92ec8b073caa4577a13f40ea3a5c4c4d4e7"
|
||||
integrity sha512-796A8fd90oUKDRO7UXUT9BwZ3G+a9XzJj5v012FcCN/2qRhEsIV3x/0wkx2S08T4FiQEUPkB2uoYHpEjEneM7g==
|
||||
dependencies:
|
||||
"@microsoft/applicationinsights-core-js" "2.8.4"
|
||||
"@microsoft/applicationinsights-shims" "^2.0.1"
|
||||
"@microsoft/dynamicproto-js" "^1.1.6"
|
||||
|
||||
"@microsoft/1ds-post-js@^3.2.3":
|
||||
version "3.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.3.tgz#1fa7d51615a44f289632ae8c588007ba943db216"
|
||||
integrity sha512-tcGJQXXr2LYoBbIXPoUVe1KCF3OtBsuKDFL7BXfmNtuSGtWF0yejm6H83DrR8/cUIGMRMUP9lqNlqFGwDYiwAQ==
|
||||
dependencies:
|
||||
"@microsoft/1ds-core-js" "3.2.3"
|
||||
"@microsoft/applicationinsights-shims" "^2.0.1"
|
||||
"@microsoft/dynamicproto-js" "^1.1.6"
|
||||
|
||||
"@microsoft/applicationinsights-core-js@2.8.4":
|
||||
version "2.8.4"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596"
|
||||
integrity sha512-FoA0FNOsFbJnLyTyQlYs6+HR7HMEa6nAOE6WOm9WVejBHMHQ/Bdb+hfVFi6slxwCimr/ner90jchi4/sIYdnyQ==
|
||||
dependencies:
|
||||
"@microsoft/applicationinsights-shims" "2.0.1"
|
||||
"@microsoft/dynamicproto-js" "^1.1.6"
|
||||
|
||||
"@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1":
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd"
|
||||
integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ==
|
||||
|
||||
"@microsoft/dynamicproto-js@^1.1.6":
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc"
|
||||
integrity sha512-D1Oivw1A4bIXhzBIy3/BBPn3p2On+kpO2NiYt9shICDK7L/w+cR6FFBUsBZ05l6iqzTeL+Jm8lAYn0g6G7DmDg==
|
||||
|
||||
"@tokenizer/token@^0.3.0":
|
||||
version "0.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276"
|
||||
@@ -29,15 +70,23 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae"
|
||||
integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==
|
||||
|
||||
"@types/picomatch@2.3.0":
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/picomatch/-/picomatch-2.3.0.tgz#75db5e75a713c5a83d5b76780c3da84a82806003"
|
||||
integrity sha512-O397rnSS9iQI4OirieAtsDqvCj4+3eY1J+EPdNTKuHuRWIfUoGyzX294o8C4KJYaLqgSrd2o60c5EqCU8Zv02g==
|
||||
|
||||
"@types/which@^1.0.28":
|
||||
version "1.0.28"
|
||||
resolved "https://registry.yarnpkg.com/@types/which/-/which-1.0.28.tgz#016e387629b8817bed653fe32eab5d11279c8df6"
|
||||
integrity sha1-AW44dim4gXvtZT/jLqtdESecjfY=
|
||||
|
||||
"@vscode/extension-telemetry@0.4.10":
|
||||
version "0.4.10"
|
||||
resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.4.10.tgz#be960c05bdcbea0933866346cf244acad6cac910"
|
||||
integrity sha512-XgyUoWWRQExTmd9DynIIUQo1NPex/zIeetdUAXeBjVuW9ioojM1TcDaSqOa/5QLC7lx+oEXwSU1r0XSBgzyz6w==
|
||||
"@vscode/extension-telemetry@0.6.2":
|
||||
version "0.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.2.tgz#b86814ee680615730da94220c2b03ea9c3c14a8e"
|
||||
integrity sha512-yb/wxLuaaCRcBAZtDCjNYSisAXz3FWsSqAha5nhHcYxx2ZPdQdWuZqVXGKq0ZpHVndBWWtK6XqtpCN2/HB4S1w==
|
||||
dependencies:
|
||||
"@microsoft/1ds-core-js" "^3.2.3"
|
||||
"@microsoft/1ds-post-js" "^3.2.3"
|
||||
|
||||
"@vscode/iconv-lite-umd@0.7.0":
|
||||
version "0.7.0"
|
||||
@@ -58,7 +107,6 @@ file-type@16.5.4:
|
||||
strtok3 "^6.2.4"
|
||||
token-types "^4.1.1"
|
||||
|
||||
|
||||
ieee754@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352"
|
||||
@@ -68,6 +116,7 @@ inherits@^2.0.3:
|
||||
version "2.0.4"
|
||||
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
|
||||
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
|
||||
|
||||
isexe@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||
@@ -83,6 +132,11 @@ peek-readable@^4.1.0:
|
||||
resolved "https://registry.yarnpkg.com/peek-readable/-/peek-readable-4.1.0.tgz#4ece1111bf5c2ad8867c314c81356847e8a62e72"
|
||||
integrity sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==
|
||||
|
||||
picomatch@2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
|
||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||
|
||||
readable-stream@^3.6.0:
|
||||
version "3.6.0"
|
||||
resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198"
|
||||
|
||||
@@ -48,7 +48,7 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217",
|
||||
"aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255",
|
||||
"main": "./out/extension.js",
|
||||
"browser": "./dist/browser/extension.js",
|
||||
"scripts": {
|
||||
@@ -61,9 +61,9 @@
|
||||
"dependencies": {
|
||||
"node-fetch": "2.6.7",
|
||||
"uuid": "8.1.0",
|
||||
"@vscode/extension-telemetry": "0.4.10",
|
||||
"@vscode/extension-telemetry": "0.6.2",
|
||||
"vscode-nls": "^5.0.0",
|
||||
"vscode-tas-client": "^0.1.42"
|
||||
"vscode-tas-client": "^0.1.47"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "16.x",
|
||||
|
||||
@@ -51,7 +51,7 @@ export function promiseFromEvent<T, U>(
|
||||
event: Event<T>,
|
||||
adapter: PromiseAdapter<T, U> = passthrough): { promise: Promise<U>; cancel: EventEmitter<void> } {
|
||||
let subscription: Disposable;
|
||||
let cancel = new EventEmitter<void>();
|
||||
const cancel = new EventEmitter<void>();
|
||||
return {
|
||||
promise: new Promise<U>((resolve, reject) => {
|
||||
cancel.event(_ => reject('Cancelled'));
|
||||
|
||||
@@ -153,8 +153,7 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid
|
||||
const scopesSeen = new Set<string>();
|
||||
const sessionPromises = sessionData.map(async (session: SessionData) => {
|
||||
// For GitHub scope list, order doesn't matter so we immediately sort the scopes
|
||||
const sortedScopes = session.scopes.sort();
|
||||
const scopesStr = sortedScopes.join(' ');
|
||||
const scopesStr = [...session.scopes].sort().join(' ');
|
||||
if (scopesSeen.has(scopesStr)) {
|
||||
return undefined;
|
||||
}
|
||||
@@ -181,7 +180,9 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid
|
||||
: userInfo?.accountName ?? '<unknown>',
|
||||
id: session.account?.id ?? userInfo?.id ?? '<unknown>'
|
||||
},
|
||||
scopes: sortedScopes,
|
||||
// we set this to session.scopes to maintain the original order of the scopes requested
|
||||
// by the extension that called getSession()
|
||||
scopes: session.scopes,
|
||||
accessToken: session.accessToken
|
||||
};
|
||||
});
|
||||
@@ -208,22 +209,25 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid
|
||||
|
||||
public async createSession(scopes: string[]): Promise<vscode.AuthenticationSession> {
|
||||
try {
|
||||
// For GitHub scope list, order doesn't matter so we immediately sort the scopes
|
||||
const sortedScopes = scopes.sort();
|
||||
// For GitHub scope list, order doesn't matter so we use a sorted scope to determine
|
||||
// if we've got a session already.
|
||||
const sortedScopes = [...scopes].sort();
|
||||
|
||||
/* __GDPR__
|
||||
"login" : {
|
||||
"scopes": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight" }
|
||||
"owner": "TylerLeonhardt",
|
||||
"comment": "Used to determine how much usage the GitHub Auth Provider gets.",
|
||||
"scopes": { "classification": "PublicNonPersonalData", "purpose": "FeatureInsight", "comment": "Used to determine what scope combinations are being requested." }
|
||||
}
|
||||
*/
|
||||
this._telemetryReporter?.sendTelemetryEvent('login', {
|
||||
scopes: JSON.stringify(sortedScopes),
|
||||
scopes: JSON.stringify(scopes),
|
||||
});
|
||||
|
||||
|
||||
const scopeString = sortedScopes.join(' ');
|
||||
const token = await this._githubServer.login(scopeString);
|
||||
const session = await this.tokenToSession(token, sortedScopes);
|
||||
const session = await this.tokenToSession(token, scopes);
|
||||
this.afterSessionLoad(session);
|
||||
|
||||
const sessions = await this._sessionsPromise;
|
||||
@@ -244,14 +248,14 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid
|
||||
// If login was cancelled, do not notify user.
|
||||
if (e === 'Cancelled' || e.message === 'Cancelled') {
|
||||
/* __GDPR__
|
||||
"loginCancelled" : { }
|
||||
"loginCancelled" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users cancel the login flow." }
|
||||
*/
|
||||
this._telemetryReporter?.sendTelemetryEvent('loginCancelled');
|
||||
throw e;
|
||||
}
|
||||
|
||||
/* __GDPR__
|
||||
"loginFailed" : { }
|
||||
"loginFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users run into an error login flow." }
|
||||
*/
|
||||
this._telemetryReporter?.sendTelemetryEvent('loginFailed');
|
||||
|
||||
@@ -274,7 +278,7 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid
|
||||
public async removeSession(id: string) {
|
||||
try {
|
||||
/* __GDPR__
|
||||
"logout" : { }
|
||||
"logout" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often users log out of an account." }
|
||||
*/
|
||||
this._telemetryReporter?.sendTelemetryEvent('logout');
|
||||
|
||||
@@ -294,7 +298,7 @@ export class GitHubAuthenticationProvider implements vscode.AuthenticationProvid
|
||||
}
|
||||
} catch (e) {
|
||||
/* __GDPR__
|
||||
"logoutFailed" : { }
|
||||
"logoutFailed" : { "owner": "TylerLeonhardt", "comment": "Used to determine how often logging out of an account fails." }
|
||||
*/
|
||||
this._telemetryReporter?.sendTelemetryEvent('logoutFailed');
|
||||
|
||||
|
||||
@@ -496,6 +496,7 @@ export class GitHubServer implements IGitHubServer {
|
||||
|
||||
/* __GDPR__
|
||||
"session" : {
|
||||
"owner": "TylerLeonhardt",
|
||||
"isEdu": { "classification": "NonIdentifiableDemographicInfo", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
@@ -530,6 +531,7 @@ export class GitHubServer implements IGitHubServer {
|
||||
|
||||
/* __GDPR__
|
||||
"ghe-session" : {
|
||||
"owner": "TylerLeonhardt",
|
||||
"version": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
@@ -601,6 +603,7 @@ export class GitHubEnterpriseServer implements IGitHubServer {
|
||||
|
||||
/* __GDPR__
|
||||
"ghe-session" : {
|
||||
"owner": "TylerLeonhardt",
|
||||
"version": { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
|
||||
@@ -2,6 +2,42 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@microsoft/1ds-core-js@3.2.3", "@microsoft/1ds-core-js@^3.2.3":
|
||||
version "3.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.3.tgz#2217d92ec8b073caa4577a13f40ea3a5c4c4d4e7"
|
||||
integrity sha512-796A8fd90oUKDRO7UXUT9BwZ3G+a9XzJj5v012FcCN/2qRhEsIV3x/0wkx2S08T4FiQEUPkB2uoYHpEjEneM7g==
|
||||
dependencies:
|
||||
"@microsoft/applicationinsights-core-js" "2.8.4"
|
||||
"@microsoft/applicationinsights-shims" "^2.0.1"
|
||||
"@microsoft/dynamicproto-js" "^1.1.6"
|
||||
|
||||
"@microsoft/1ds-post-js@^3.2.3":
|
||||
version "3.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.3.tgz#1fa7d51615a44f289632ae8c588007ba943db216"
|
||||
integrity sha512-tcGJQXXr2LYoBbIXPoUVe1KCF3OtBsuKDFL7BXfmNtuSGtWF0yejm6H83DrR8/cUIGMRMUP9lqNlqFGwDYiwAQ==
|
||||
dependencies:
|
||||
"@microsoft/1ds-core-js" "3.2.3"
|
||||
"@microsoft/applicationinsights-shims" "^2.0.1"
|
||||
"@microsoft/dynamicproto-js" "^1.1.6"
|
||||
|
||||
"@microsoft/applicationinsights-core-js@2.8.4":
|
||||
version "2.8.4"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596"
|
||||
integrity sha512-FoA0FNOsFbJnLyTyQlYs6+HR7HMEa6nAOE6WOm9WVejBHMHQ/Bdb+hfVFi6slxwCimr/ner90jchi4/sIYdnyQ==
|
||||
dependencies:
|
||||
"@microsoft/applicationinsights-shims" "2.0.1"
|
||||
"@microsoft/dynamicproto-js" "^1.1.6"
|
||||
|
||||
"@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1":
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd"
|
||||
integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ==
|
||||
|
||||
"@microsoft/dynamicproto-js@^1.1.6":
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc"
|
||||
integrity sha512-D1Oivw1A4bIXhzBIy3/BBPn3p2On+kpO2NiYt9shICDK7L/w+cR6FFBUsBZ05l6iqzTeL+Jm8lAYn0g6G7DmDg==
|
||||
|
||||
"@types/node-fetch@^2.5.7":
|
||||
version "2.5.7"
|
||||
resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.7.tgz#20a2afffa882ab04d44ca786449a276f9f6bbf3c"
|
||||
@@ -25,10 +61,13 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.0.0.tgz#165aae4819ad2174a17476dbe66feebd549556c0"
|
||||
integrity sha512-xSQfNcvOiE5f9dyd4Kzxbof1aTrLobL278pGLKOZI6esGfZ7ts9Ka16CzIN6Y8hFHE1C7jIBZokULhK1bOgjRw==
|
||||
|
||||
"@vscode/extension-telemetry@0.4.10":
|
||||
version "0.4.10"
|
||||
resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.4.10.tgz#be960c05bdcbea0933866346cf244acad6cac910"
|
||||
integrity sha512-XgyUoWWRQExTmd9DynIIUQo1NPex/zIeetdUAXeBjVuW9ioojM1TcDaSqOa/5QLC7lx+oEXwSU1r0XSBgzyz6w==
|
||||
"@vscode/extension-telemetry@0.6.2":
|
||||
version "0.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.2.tgz#b86814ee680615730da94220c2b03ea9c3c14a8e"
|
||||
integrity sha512-yb/wxLuaaCRcBAZtDCjNYSisAXz3FWsSqAha5nhHcYxx2ZPdQdWuZqVXGKq0ZpHVndBWWtK6XqtpCN2/HB4S1w==
|
||||
dependencies:
|
||||
"@microsoft/1ds-core-js" "^3.2.3"
|
||||
"@microsoft/1ds-post-js" "^3.2.3"
|
||||
|
||||
asynckit@^0.4.0:
|
||||
version "0.4.0"
|
||||
@@ -87,10 +126,10 @@ node-fetch@2.6.7:
|
||||
dependencies:
|
||||
whatwg-url "^5.0.0"
|
||||
|
||||
tas-client@0.1.45:
|
||||
version "0.1.45"
|
||||
resolved "https://registry.yarnpkg.com/tas-client/-/tas-client-0.1.45.tgz#83bbf73f8458a0f527f9a389f7e1c37f63a64a76"
|
||||
integrity sha512-IG9UmCpDbDPK23UByQ27rLybkRZYEx2eC9EkieXdwPKKjZPD2zPwfQmyGnZrZet4FUt3yj0ytkwz+liR9Nz/nA==
|
||||
tas-client@0.1.58:
|
||||
version "0.1.58"
|
||||
resolved "https://registry.yarnpkg.com/tas-client/-/tas-client-0.1.58.tgz#67d66bf0e27df5276ebc751105e6ad47791c36d8"
|
||||
integrity sha512-fOWii4wQXuo9Zl0oXgvjBzZWzKc5MmUR6XQWX93WU2c1SaP1plPo/zvXP8kpbZ9fvegFOHdapszYqMTRq/SRtg==
|
||||
dependencies:
|
||||
axios "^0.26.1"
|
||||
|
||||
@@ -109,12 +148,12 @@ vscode-nls@^5.0.0:
|
||||
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840"
|
||||
integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA==
|
||||
|
||||
vscode-tas-client@^0.1.42:
|
||||
version "0.1.47"
|
||||
resolved "https://registry.yarnpkg.com/vscode-tas-client/-/vscode-tas-client-0.1.47.tgz#d66795cbbaa231aba659b6c40d43927d73596375"
|
||||
integrity sha512-SlEPDi+0gwxor4ANzBtXwqROPQdQkClHeVJgnkvdDF5Xnl407htCsabTPAq4Di8muObORtLchqQS/k1ocaGDEg==
|
||||
vscode-tas-client@^0.1.47:
|
||||
version "0.1.63"
|
||||
resolved "https://registry.yarnpkg.com/vscode-tas-client/-/vscode-tas-client-0.1.63.tgz#df89e67e9bf7ecb46471a0fb8a4a522d2aafad65"
|
||||
integrity sha512-TY5TPyibzi6rNmuUB7eRVqpzLzNfQYrrIl/0/F8ukrrbzOrKVvS31hM3urE+tbaVrnT+TMYXL16GhX57vEowhA==
|
||||
dependencies:
|
||||
tas-client "0.1.45"
|
||||
tas-client "0.1.58"
|
||||
|
||||
webidl-conversions@^3.0.0:
|
||||
version "3.0.1"
|
||||
|
||||
@@ -25,11 +25,33 @@
|
||||
"supported": true
|
||||
}
|
||||
},
|
||||
"enabledApiProposals": [
|
||||
"contribShareMenu",
|
||||
"contribEditSessions"
|
||||
],
|
||||
"contributes": {
|
||||
"commands": [
|
||||
{
|
||||
"command": "github.publish",
|
||||
"title": "Publish to GitHub"
|
||||
},
|
||||
{
|
||||
"command": "github.copyVscodeDevLink",
|
||||
"title": "Copy vscode.dev Link"
|
||||
},
|
||||
{
|
||||
"command": "github.copyVscodeDevLinkFile",
|
||||
"title": "Copy vscode.dev Link"
|
||||
},
|
||||
{
|
||||
"command": "github.openOnVscodeDev",
|
||||
"title": "Open on vscode.dev"
|
||||
}
|
||||
],
|
||||
"continueEditSession": [
|
||||
{
|
||||
"command": "github.openOnVscodeDev",
|
||||
"when": "github.hasGitHubRepo"
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
@@ -37,6 +59,30 @@
|
||||
{
|
||||
"command": "github.publish",
|
||||
"when": "git-base.gitEnabled"
|
||||
},
|
||||
{
|
||||
"command": "github.copyVscodeDevLink",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "github.copyVscodeDevLinkFile",
|
||||
"when": "false"
|
||||
},
|
||||
{
|
||||
"command": "github.openOnVscodeDev",
|
||||
"when": "false"
|
||||
}
|
||||
],
|
||||
"file/share": [
|
||||
{
|
||||
"command": "github.copyVscodeDevLinkFile",
|
||||
"when": "github.hasGitHubRepo"
|
||||
}
|
||||
],
|
||||
"editor/context/share": [
|
||||
{
|
||||
"command": "github.copyVscodeDevLink",
|
||||
"when": "github.hasGitHubRepo && resourceScheme != untitled"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
"welcome.publishFolder": {
|
||||
"message": "You can also directly publish this folder to a GitHub repository. Once published, you'll have access to source control features powered by git and GitHub.\n[$(github) Publish to GitHub](command:github.publish)",
|
||||
"comment": [
|
||||
"{Locked='$(github)'}",
|
||||
"Do not translate '$(github)'. It will be rendered as an icon",
|
||||
"{Locked='](command:github.publish'}",
|
||||
"Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code",
|
||||
"Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links"
|
||||
@@ -14,6 +16,8 @@
|
||||
"welcome.publishWorkspaceFolder": {
|
||||
"message": "You can also directly publish a workspace folder to a GitHub repository. Once published, you'll have access to source control features powered by git and GitHub.\n[$(github) Publish to GitHub](command:github.publish)",
|
||||
"comment": [
|
||||
"{Locked='$(github)'}",
|
||||
"Do not translate '$(github)'. It will be rendered as an icon",
|
||||
"{Locked='](command:github.publish'}",
|
||||
"Do not translate the 'command:*' part inside of the '(..)'. It is an internal command syntax for VS Code",
|
||||
"Please make sure there is no space between the right bracket and left parenthesis: ]( this is an internal syntax for links"
|
||||
|
||||
@@ -24,7 +24,7 @@ function getAgent(url: string | undefined = process.env.HTTPS_PROXY): Agent {
|
||||
}
|
||||
}
|
||||
|
||||
const scopes = ['repo', 'workflow'];
|
||||
const scopes = ['repo', 'workflow', 'user:email', 'read:user'];
|
||||
|
||||
export async function getSession(): Promise<AuthenticationSession> {
|
||||
return await authentication.getSession('github', scopes, { createIfNone: true });
|
||||
|
||||
@@ -7,6 +7,32 @@ import * as vscode from 'vscode';
|
||||
import { API as GitAPI } from './typings/git';
|
||||
import { publishRepository } from './publish';
|
||||
import { DisposableStore } from './util';
|
||||
import { getPermalink } from './links';
|
||||
|
||||
function getVscodeDevHost(): string {
|
||||
return `https://${vscode.env.appName.toLowerCase().includes('insiders') ? 'insiders.' : ''}vscode.dev/github`;
|
||||
}
|
||||
|
||||
async function copyVscodeDevLink(gitAPI: GitAPI, useSelection: boolean) {
|
||||
try {
|
||||
const permalink = getPermalink(gitAPI, useSelection, getVscodeDevHost());
|
||||
if (permalink) {
|
||||
return vscode.env.clipboard.writeText(permalink);
|
||||
}
|
||||
} catch (err) {
|
||||
vscode.window.showErrorMessage(err.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function openVscodeDevLink(gitAPI: GitAPI): Promise<vscode.Uri | undefined> {
|
||||
try {
|
||||
const permalink = getPermalink(gitAPI, true, getVscodeDevHost());
|
||||
return permalink ? vscode.Uri.parse(permalink) : undefined;
|
||||
} catch (err) {
|
||||
vscode.window.showErrorMessage(err.message);
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export function registerCommands(gitAPI: GitAPI): vscode.Disposable {
|
||||
const disposables = new DisposableStore();
|
||||
@@ -19,5 +45,17 @@ export function registerCommands(gitAPI: GitAPI): vscode.Disposable {
|
||||
}
|
||||
}));
|
||||
|
||||
disposables.add(vscode.commands.registerCommand('github.copyVscodeDevLink', async () => {
|
||||
return copyVscodeDevLink(gitAPI, true);
|
||||
}));
|
||||
|
||||
disposables.add(vscode.commands.registerCommand('github.copyVscodeDevLinkFile', async () => {
|
||||
return copyVscodeDevLink(gitAPI, false);
|
||||
}));
|
||||
|
||||
disposables.add(vscode.commands.registerCommand('github.openOnVscodeDev', async () => {
|
||||
return openVscodeDevLink(gitAPI);
|
||||
}));
|
||||
|
||||
return disposables;
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
|
||||
import { commands, Disposable, ExtensionContext, extensions } from 'vscode';
|
||||
import { GithubRemoteSourceProvider } from './remoteSourceProvider';
|
||||
import { GitExtension } from './typings/git';
|
||||
import { API, GitExtension } from './typings/git';
|
||||
import { registerCommands } from './commands';
|
||||
import { GithubCredentialProviderManager } from './credentialProvider';
|
||||
import { DisposableStore } from './util';
|
||||
import { DisposableStore, repositoryHasGitHubRemote } from './util';
|
||||
import { GithubPushErrorHandler } from './pushErrorHandler';
|
||||
import { GitBaseExtension } from './typings/git-base';
|
||||
import { GithubRemoteSourcePublisher } from './remoteSourcePublisher';
|
||||
@@ -48,6 +48,21 @@ function initializeGitBaseExtension(): Disposable {
|
||||
return disposables;
|
||||
}
|
||||
|
||||
function setGitHubContext(gitAPI: API, disposables: DisposableStore) {
|
||||
if (gitAPI.repositories.find(repo => repositoryHasGitHubRemote(repo))) {
|
||||
commands.executeCommand('setContext', 'github.hasGitHubRepo', true);
|
||||
} else {
|
||||
const openRepoDisposable = gitAPI.onDidOpenRepository(async e => {
|
||||
await e.status();
|
||||
if (repositoryHasGitHubRemote(e)) {
|
||||
commands.executeCommand('setContext', 'github.hasGitHubRepo', true);
|
||||
openRepoDisposable.dispose();
|
||||
}
|
||||
});
|
||||
disposables.add(openRepoDisposable);
|
||||
}
|
||||
}
|
||||
|
||||
function initializeGitExtension(): Disposable {
|
||||
const disposables = new DisposableStore();
|
||||
|
||||
@@ -64,6 +79,7 @@ function initializeGitExtension(): Disposable {
|
||||
disposables.add(new GithubCredentialProviderManager(gitAPI));
|
||||
disposables.add(gitAPI.registerPushErrorHandler(new GithubPushErrorHandler()));
|
||||
disposables.add(gitAPI.registerRemoteSourcePublisher(new GithubRemoteSourcePublisher(gitAPI)));
|
||||
setGitHubContext(gitAPI, disposables);
|
||||
|
||||
commands.executeCommand('setContext', 'git-base.gitEnabled', true);
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* 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 { API as GitAPI, Repository } from './typings/git';
|
||||
import { getRepositoryFromUrl } from './util';
|
||||
|
||||
export function isFileInRepo(repository: Repository, file: vscode.Uri): boolean {
|
||||
return file.path.toLowerCase() === repository.rootUri.path.toLowerCase() ||
|
||||
(file.path.toLowerCase().startsWith(repository.rootUri.path.toLowerCase()) &&
|
||||
file.path.substring(repository.rootUri.path.length).startsWith('/'));
|
||||
}
|
||||
|
||||
export function getRepositoryForFile(gitAPI: GitAPI, file: vscode.Uri): Repository | undefined {
|
||||
for (const repository of gitAPI.repositories) {
|
||||
if (isFileInRepo(repository, file)) {
|
||||
return repository;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
enum LinkType {
|
||||
File = 1,
|
||||
Notebook = 2
|
||||
}
|
||||
|
||||
interface IFilePosition {
|
||||
type: LinkType.File;
|
||||
uri: vscode.Uri;
|
||||
range: vscode.Range | undefined;
|
||||
}
|
||||
|
||||
interface INotebookPosition {
|
||||
type: LinkType.Notebook;
|
||||
uri: vscode.Uri;
|
||||
cellIndex: number;
|
||||
range: vscode.Range | undefined;
|
||||
}
|
||||
|
||||
function getFileAndPosition(): IFilePosition | INotebookPosition | undefined {
|
||||
let uri: vscode.Uri | undefined;
|
||||
let range: vscode.Range | undefined;
|
||||
if (vscode.window.activeTextEditor) {
|
||||
uri = vscode.window.activeTextEditor.document.uri;
|
||||
|
||||
if (uri.scheme === 'vscode-notebook-cell' && vscode.window.activeNotebookEditor?.notebook.uri.fsPath === uri.fsPath) {
|
||||
// if the active editor is a notebook editor and the focus is inside any a cell text editor
|
||||
// generate deep link for text selection for the notebook cell.
|
||||
const cell = vscode.window.activeNotebookEditor.notebook.getCells().find(cell => cell.document.uri.fragment === uri?.fragment);
|
||||
const cellIndex = cell?.index ?? vscode.window.activeNotebookEditor.selection.start;
|
||||
const range = cell !== undefined ? vscode.window.activeTextEditor.selection : undefined;
|
||||
return { type: LinkType.Notebook, uri, cellIndex, range };
|
||||
} else {
|
||||
// the active editor is a text editor
|
||||
range = vscode.window.activeTextEditor.selection;
|
||||
return { type: LinkType.File, uri, range };
|
||||
}
|
||||
}
|
||||
|
||||
if (vscode.window.activeNotebookEditor) {
|
||||
// if the active editor is a notebook editor but the focus is not inside any cell text editor, generate deep link for the cell selection in the notebook document.
|
||||
return { type: LinkType.Notebook, uri: vscode.window.activeNotebookEditor.notebook.uri, cellIndex: vscode.window.activeNotebookEditor.selection.start, range: undefined };
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function rangeString(range: vscode.Range | undefined) {
|
||||
if (!range) {
|
||||
return '';
|
||||
}
|
||||
let hash = `#L${range.start.line + 1}`;
|
||||
if (range.start.line !== range.end.line) {
|
||||
hash += `-L${range.end.line + 1}`;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
export function notebookCellRangeString(index: number | undefined, range: vscode.Range | undefined) {
|
||||
if (index === undefined) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!range) {
|
||||
return `#C${index + 1}`;
|
||||
}
|
||||
|
||||
let hash = `#C${index + 1}:L${range.start.line + 1}`;
|
||||
if (range.start.line !== range.end.line) {
|
||||
hash += `-L${range.end.line + 1}`;
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
export function getPermalink(gitAPI: GitAPI, useSelection: boolean, hostPrefix?: string): string | undefined {
|
||||
hostPrefix = hostPrefix ?? 'https://github.com';
|
||||
const fileAndPosition = getFileAndPosition();
|
||||
if (!fileAndPosition) {
|
||||
return;
|
||||
}
|
||||
const uri = fileAndPosition.uri;
|
||||
|
||||
// Use the first repo if we cannot determine a repo from the uri.
|
||||
const gitRepo = (uri ? getRepositoryForFile(gitAPI, uri) : gitAPI.repositories[0]) ?? gitAPI.repositories[0];
|
||||
if (!gitRepo) {
|
||||
return;
|
||||
}
|
||||
let repo: { owner: string; repo: string } | undefined;
|
||||
gitRepo.state.remotes.find(remote => {
|
||||
if (remote.fetchUrl) {
|
||||
const foundRepo = getRepositoryFromUrl(remote.fetchUrl);
|
||||
if (foundRepo && (remote.name === gitRepo.state.HEAD?.upstream?.remote)) {
|
||||
repo = foundRepo;
|
||||
return;
|
||||
} else if (foundRepo && !repo) {
|
||||
repo = foundRepo;
|
||||
}
|
||||
}
|
||||
return;
|
||||
});
|
||||
if (!repo) {
|
||||
return;
|
||||
}
|
||||
|
||||
const commitHash = (gitRepo.state.HEAD?.ahead === 0) ? `/blob/${gitRepo.state.HEAD?.commit}` : '';
|
||||
const fileSegments = fileAndPosition.type === LinkType.File
|
||||
? (useSelection ? `${uri.path.substring(gitRepo.rootUri.path.length)}${rangeString(fileAndPosition.range)}` : '')
|
||||
: (useSelection ? `${uri.path.substring(gitRepo.rootUri.path.length)}${notebookCellRangeString(fileAndPosition.cellIndex, fileAndPosition.range)}` : '');
|
||||
|
||||
return `${hostPrefix}/${repo.owner}/${repo.repo}${commitHash
|
||||
}${fileSegments}`;
|
||||
}
|
||||
@@ -21,8 +21,9 @@ export function isInCodespaces(): boolean {
|
||||
async function handlePushError(repository: Repository, remote: Remote, refspec: string, owner: string, repo: string): Promise<void> {
|
||||
const yes = localize('create a fork', "Create Fork");
|
||||
const no = localize('no', "No");
|
||||
const askFork = localize('fork', "You don't have permissions to push to '{0}/{1}' on GitHub. Would you like to create a fork and push to it instead?", owner, repo);
|
||||
|
||||
const answer = await window.showInformationMessage(localize('fork', "You don't have permissions to push to '{0}/{1}' on GitHub. Would you like to create a fork and push to it instead?", owner, repo), yes, no);
|
||||
const answer = await window.showInformationMessage(askFork, yes, no);
|
||||
if (answer !== yes) {
|
||||
return;
|
||||
}
|
||||
@@ -102,18 +103,19 @@ async function handlePushError(repository: Repository, remote: Remote, refspec:
|
||||
let title = `Update ${remoteName}`;
|
||||
const head = repository.state.HEAD?.name;
|
||||
|
||||
let body: string | undefined;
|
||||
|
||||
if (head) {
|
||||
const commit = await repository.getCommit(head);
|
||||
title = commit.message.replace(/\n.*$/m, '');
|
||||
title = commit.message.split('\n')[0];
|
||||
body = commit.message.slice(title.length + 1).trim();
|
||||
}
|
||||
|
||||
let body: string | undefined;
|
||||
|
||||
const templates = await findPullRequestTemplates(repository.rootUri);
|
||||
if (templates.length > 0) {
|
||||
templates.sort((a, b) => a.path.localeCompare(b.path));
|
||||
|
||||
const template = await pickPullRequestTemplate(templates);
|
||||
const template = await pickPullRequestTemplate(repository.rootUri, templates);
|
||||
|
||||
if (template) {
|
||||
body = new TextDecoder('utf-8').decode(await workspace.fs.readFile(template));
|
||||
@@ -190,8 +192,8 @@ export async function findPullRequestTemplates(repositoryRootUri: Uri): Promise<
|
||||
return results.flatMap(x => x.status === 'fulfilled' && x.value || []);
|
||||
}
|
||||
|
||||
export async function pickPullRequestTemplate(templates: Uri[]): Promise<Uri | undefined> {
|
||||
const quickPickItemFromUri = (x: Uri) => ({ label: x.path, template: x });
|
||||
export async function pickPullRequestTemplate(repositoryRootUri: Uri, templates: Uri[]): Promise<Uri | undefined> {
|
||||
const quickPickItemFromUri = (x: Uri) => ({ label: path.relative(repositoryRootUri.path, x.path), template: x });
|
||||
const quickPickItems = [
|
||||
{
|
||||
label: localize('no pr template', "No template"),
|
||||
@@ -201,7 +203,8 @@ export async function pickPullRequestTemplate(templates: Uri[]): Promise<Uri | u
|
||||
...templates.map(quickPickItemFromUri)
|
||||
];
|
||||
const quickPickOptions: QuickPickOptions = {
|
||||
placeHolder: localize('select pr template', "Select the Pull Request template")
|
||||
placeHolder: localize('select pr template', "Select the Pull Request template"),
|
||||
ignoreFocusOut: true
|
||||
};
|
||||
const pickedTemplate = await window.showQuickPick(quickPickItems, quickPickOptions);
|
||||
return pickedTemplate?.template;
|
||||
@@ -219,7 +222,7 @@ export class GithubPushErrorHandler implements PushErrorHandler {
|
||||
return false;
|
||||
}
|
||||
|
||||
const match = /^(?:https:\/\/github\.com\/|git@github\.com:)([^/]+)\/([^/.]+)(?:\.git)?$/i.exec(remoteUrl);
|
||||
const match = /^(?:https:\/\/github\.com\/|git@github\.com:)([^\/]+)\/([^\/.]+)/i.exec(remoteUrl);
|
||||
if (!match) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -7,17 +7,7 @@ import { workspace } from 'vscode';
|
||||
import { RemoteSourceProvider, RemoteSource } from './typings/git-base';
|
||||
import { getOctokit } from './auth';
|
||||
import { Octokit } from '@octokit/rest';
|
||||
|
||||
function getRepositoryFromUrl(url: string): { owner: string; repo: string } | undefined {
|
||||
const match = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\.git/i.exec(url)
|
||||
|| /^git@github\.com:([^/]+)\/([^/]+)\.git/i.exec(url);
|
||||
return match ? { owner: match[1], repo: match[2] } : undefined;
|
||||
}
|
||||
|
||||
function getRepositoryFromQuery(query: string): { owner: string; repo: string } | undefined {
|
||||
const match = /^([^/]+)\/([^/]+)$/i.exec(query);
|
||||
return match ? { owner: match[1], repo: match[2] } : undefined;
|
||||
}
|
||||
import { getRepositoryFromQuery, getRepositoryFromUrl } from './util';
|
||||
|
||||
function asRemoteSource(raw: any): RemoteSource {
|
||||
const protocol = workspace.getConfiguration('github').get<'https' | 'ssh'>('gitProtocol');
|
||||
@@ -107,7 +97,7 @@ export class GithubRemoteSourceProvider implements RemoteSourceProvider {
|
||||
let page = 1;
|
||||
|
||||
while (true) {
|
||||
let res = await octokit.repos.listBranches({ ...repository, per_page: 100, page });
|
||||
const res = await octokit.repos.listBranches({ ...repository, per_page: 100, page });
|
||||
|
||||
if (res.data.length === 0) {
|
||||
break;
|
||||
|
||||
@@ -18,15 +18,15 @@ suite('github smoke test', function () {
|
||||
|
||||
test('should find all templates', async function () {
|
||||
const expectedValuesSorted = [
|
||||
'/PULL_REQUEST_TEMPLATE/a.md',
|
||||
'/PULL_REQUEST_TEMPLATE/b.md',
|
||||
'/docs/PULL_REQUEST_TEMPLATE.md',
|
||||
'/docs/PULL_REQUEST_TEMPLATE/a.md',
|
||||
'/docs/PULL_REQUEST_TEMPLATE/b.md',
|
||||
'/.github/PULL_REQUEST_TEMPLATE.md',
|
||||
'/.github/PULL_REQUEST_TEMPLATE/a.md',
|
||||
'/.github/PULL_REQUEST_TEMPLATE/b.md',
|
||||
'/PULL_REQUEST_TEMPLATE.md'
|
||||
'PULL_REQUEST_TEMPLATE/a.md',
|
||||
'PULL_REQUEST_TEMPLATE/b.md',
|
||||
'docs/PULL_REQUEST_TEMPLATE.md',
|
||||
'docs/PULL_REQUEST_TEMPLATE/a.md',
|
||||
'docs/PULL_REQUEST_TEMPLATE/b.md',
|
||||
'.github/PULL_REQUEST_TEMPLATE.md',
|
||||
'.github/PULL_REQUEST_TEMPLATE/a.md',
|
||||
'.github/PULL_REQUEST_TEMPLATE/b.md',
|
||||
'PULL_REQUEST_TEMPLATE.md'
|
||||
];
|
||||
expectedValuesSorted.sort();
|
||||
|
||||
@@ -43,7 +43,7 @@ suite('github smoke test', function () {
|
||||
const template1 = Uri.file("some-imaginary-template-1");
|
||||
const templates = [template0, template1];
|
||||
|
||||
const pick = pickPullRequestTemplate(templates);
|
||||
const pick = pickPullRequestTemplate(Uri.file("/"), templates);
|
||||
|
||||
await commands.executeCommand('workbench.action.quickOpenSelectNext');
|
||||
await commands.executeCommand('workbench.action.quickOpenSelectNext');
|
||||
@@ -55,7 +55,7 @@ suite('github smoke test', function () {
|
||||
test('selecting first quick-pick item should return undefined', async () => {
|
||||
const templates = [Uri.file("some-imaginary-file")];
|
||||
|
||||
const pick = pickPullRequestTemplate(templates);
|
||||
const pick = pickPullRequestTemplate(Uri.file("/"), templates);
|
||||
|
||||
await commands.executeCommand('workbench.action.quickOpenSelectNext');
|
||||
await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem');
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as vscode from 'vscode';
|
||||
import { Repository } from './typings/git';
|
||||
|
||||
export class DisposableStore {
|
||||
|
||||
@@ -21,3 +22,18 @@ export class DisposableStore {
|
||||
this.disposables.clear();
|
||||
}
|
||||
}
|
||||
|
||||
export function getRepositoryFromUrl(url: string): { owner: string; repo: string } | undefined {
|
||||
const match = /^https:\/\/github\.com\/([^/]+)\/([^/]+?)(\.git)?$/i.exec(url)
|
||||
|| /^git@github\.com:([^/]+)\/([^/]+?)(\.git)?$/i.exec(url);
|
||||
return match ? { owner: match[1], repo: match[2] } : undefined;
|
||||
}
|
||||
|
||||
export function getRepositoryFromQuery(query: string): { owner: string; repo: string } | undefined {
|
||||
const match = /^([^/]+)\/([^/]+)$/i.exec(query);
|
||||
return match ? { owner: match[1], repo: match[2] } : undefined;
|
||||
}
|
||||
|
||||
export function repositoryHasGitHubRemote(repository: Repository) {
|
||||
return !!repository.state.remotes.find(remote => remote.fetchUrl ? getRepositoryFromUrl(remote.fetchUrl) : undefined);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { DocumentSelector } from 'vscode-languageclient';
|
||||
import { Event, EventEmitter, extensions } from 'vscode';
|
||||
|
||||
/**
|
||||
* HTML language participant contribution.
|
||||
*/
|
||||
interface LanguageParticipantContribution {
|
||||
/**
|
||||
* The id of the language which participates with the HTML language server.
|
||||
*/
|
||||
languageId: string;
|
||||
/**
|
||||
* true if the language activates the auto insertion and false otherwise.
|
||||
*/
|
||||
autoInsert?: boolean;
|
||||
}
|
||||
|
||||
export interface LanguageParticipants {
|
||||
readonly onDidChange: Event<void>;
|
||||
readonly documentSelector: DocumentSelector;
|
||||
hasLanguage(languageId: string): boolean;
|
||||
useAutoInsert(languageId: string): boolean;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
export function getLanguageParticipants(): LanguageParticipants {
|
||||
const onDidChangeEmmiter = new EventEmitter<void>();
|
||||
let languages = new Set<string>();
|
||||
let autoInsert = new Set<string>();
|
||||
|
||||
function update() {
|
||||
const oldLanguages = languages, oldAutoInsert = autoInsert;
|
||||
|
||||
languages = new Set();
|
||||
languages.add('html');
|
||||
autoInsert = new Set();
|
||||
autoInsert.add('html');
|
||||
|
||||
for (const extension of extensions.allAcrossExtensionHosts) {
|
||||
const htmlLanguageParticipants = extension.packageJSON?.contributes?.htmlLanguageParticipants as LanguageParticipantContribution[];
|
||||
if (Array.isArray(htmlLanguageParticipants)) {
|
||||
for (const htmlLanguageParticipant of htmlLanguageParticipants) {
|
||||
const languageId = htmlLanguageParticipant.languageId;
|
||||
if (typeof languageId === 'string') {
|
||||
languages.add(languageId);
|
||||
if (htmlLanguageParticipant.autoInsert !== false) {
|
||||
autoInsert.add(languageId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return !isEqualSet(languages, oldLanguages) || !isEqualSet(oldLanguages, oldAutoInsert);
|
||||
}
|
||||
update();
|
||||
|
||||
const changeListener = extensions.onDidChange(_ => {
|
||||
if (update()) {
|
||||
onDidChangeEmmiter.fire();
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
onDidChange: onDidChangeEmmiter.event,
|
||||
get documentSelector() { return Array.from(languages); },
|
||||
hasLanguage(languageId: string) { return languages.has(languageId); },
|
||||
useAutoInsert(languageId: string) { return autoInsert.has(languageId); },
|
||||
dispose: () => changeListener.dispose()
|
||||
};
|
||||
}
|
||||
|
||||
function isEqualSet<T>(s1: Set<T>, s2: Set<T>) {
|
||||
if (s1.size !== s2.size) {
|
||||
return false;
|
||||
}
|
||||
for (const e of s1) {
|
||||
if (!s2.has(e)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationToken, Connection, Diagnostic, Disposable, DocumentDiagnosticParams, DocumentDiagnosticReport, DocumentDiagnosticReportKind, TextDocuments } from 'vscode-languageserver';
|
||||
import { TextDocument } from 'vscode-html-languageservice';
|
||||
import { formatError, runSafe } from './runner';
|
||||
import { RuntimeEnvironment } from '../htmlServer';
|
||||
|
||||
export type Validator = (textDocument: TextDocument) => Promise<Diagnostic[]>;
|
||||
export type DiagnosticsSupport = {
|
||||
dispose(): void;
|
||||
requestRefresh(): void;
|
||||
};
|
||||
|
||||
export function registerDiagnosticsPushSupport(documents: TextDocuments<TextDocument>, connection: Connection, runtime: RuntimeEnvironment, validate: Validator): DiagnosticsSupport {
|
||||
|
||||
const pendingValidationRequests: { [uri: string]: Disposable } = {};
|
||||
const validationDelayMs = 500;
|
||||
|
||||
const disposables: Disposable[] = [];
|
||||
|
||||
// The content of a text document has changed. This event is emitted
|
||||
// when the text document first opened or when its content has changed.
|
||||
documents.onDidChangeContent(change => {
|
||||
triggerValidation(change.document);
|
||||
}, undefined, disposables);
|
||||
|
||||
// a document has closed: clear all diagnostics
|
||||
documents.onDidClose(event => {
|
||||
cleanPendingValidation(event.document);
|
||||
connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] });
|
||||
}, undefined, disposables);
|
||||
|
||||
function cleanPendingValidation(textDocument: TextDocument): void {
|
||||
const request = pendingValidationRequests[textDocument.uri];
|
||||
if (request) {
|
||||
request.dispose();
|
||||
delete pendingValidationRequests[textDocument.uri];
|
||||
}
|
||||
}
|
||||
|
||||
function triggerValidation(textDocument: TextDocument): void {
|
||||
cleanPendingValidation(textDocument);
|
||||
const request = pendingValidationRequests[textDocument.uri] = runtime.timer.setTimeout(async () => {
|
||||
if (request === pendingValidationRequests[textDocument.uri]) {
|
||||
try {
|
||||
const diagnostics = await validate(textDocument);
|
||||
if (request === pendingValidationRequests[textDocument.uri]) {
|
||||
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
|
||||
}
|
||||
delete pendingValidationRequests[textDocument.uri];
|
||||
} catch (e) {
|
||||
connection.console.error(formatError(`Error while validating ${textDocument.uri}`, e));
|
||||
}
|
||||
}
|
||||
}, validationDelayMs);
|
||||
}
|
||||
|
||||
return {
|
||||
requestRefresh: () => {
|
||||
documents.all().forEach(triggerValidation);
|
||||
},
|
||||
dispose: () => {
|
||||
disposables.forEach(d => d.dispose());
|
||||
disposables.length = 0;
|
||||
const keys = Object.keys(pendingValidationRequests);
|
||||
for (const key of keys) {
|
||||
pendingValidationRequests[key].dispose();
|
||||
delete pendingValidationRequests[key];
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function registerDiagnosticsPullSupport(documents: TextDocuments<TextDocument>, connection: Connection, runtime: RuntimeEnvironment, validate: Validator): DiagnosticsSupport {
|
||||
|
||||
function newDocumentDiagnosticReport(diagnostics: Diagnostic[]): DocumentDiagnosticReport {
|
||||
return {
|
||||
kind: DocumentDiagnosticReportKind.Full,
|
||||
items: diagnostics
|
||||
};
|
||||
}
|
||||
|
||||
const registration = connection.languages.diagnostics.on(async (params: DocumentDiagnosticParams, token: CancellationToken) => {
|
||||
return runSafe(runtime, async () => {
|
||||
const document = documents.get(params.textDocument.uri);
|
||||
if (document) {
|
||||
return newDocumentDiagnosticReport(await validate(document));
|
||||
}
|
||||
return newDocumentDiagnosticReport([]);
|
||||
|
||||
}, newDocumentDiagnosticReport([]), `Error while computing diagnostics for ${params.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
function requestRefresh(): void {
|
||||
connection.languages.diagnostics.refresh();
|
||||
}
|
||||
|
||||
return {
|
||||
requestRefresh,
|
||||
dispose: () => {
|
||||
registration.dispose();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -259,7 +259,7 @@
|
||||
firstZoom();
|
||||
}
|
||||
|
||||
let delta = e.deltaY > 0 ? 1 : -1;
|
||||
const delta = e.deltaY > 0 ? 1 : -1;
|
||||
updateScale(scale * (1 - delta * SCALE_PINCH_FACTOR));
|
||||
}, { passive: false });
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"publisher": "vscode",
|
||||
"icon": "icon.png",
|
||||
"license": "MIT",
|
||||
"aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217",
|
||||
"aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255",
|
||||
"engines": {
|
||||
"vscode": "^1.39.0"
|
||||
},
|
||||
@@ -79,7 +79,7 @@
|
||||
"watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vscode/extension-telemetry": "0.4.10",
|
||||
"@vscode/extension-telemetry": "0.6.2",
|
||||
"vscode-nls": "^5.0.0"
|
||||
},
|
||||
"repository": {
|
||||
|
||||
@@ -2,10 +2,49 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@vscode/extension-telemetry@0.4.10":
|
||||
version "0.4.10"
|
||||
resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.4.10.tgz#be960c05bdcbea0933866346cf244acad6cac910"
|
||||
integrity sha512-XgyUoWWRQExTmd9DynIIUQo1NPex/zIeetdUAXeBjVuW9ioojM1TcDaSqOa/5QLC7lx+oEXwSU1r0XSBgzyz6w==
|
||||
"@microsoft/1ds-core-js@3.2.3", "@microsoft/1ds-core-js@^3.2.3":
|
||||
version "3.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.3.tgz#2217d92ec8b073caa4577a13f40ea3a5c4c4d4e7"
|
||||
integrity sha512-796A8fd90oUKDRO7UXUT9BwZ3G+a9XzJj5v012FcCN/2qRhEsIV3x/0wkx2S08T4FiQEUPkB2uoYHpEjEneM7g==
|
||||
dependencies:
|
||||
"@microsoft/applicationinsights-core-js" "2.8.4"
|
||||
"@microsoft/applicationinsights-shims" "^2.0.1"
|
||||
"@microsoft/dynamicproto-js" "^1.1.6"
|
||||
|
||||
"@microsoft/1ds-post-js@^3.2.3":
|
||||
version "3.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.3.tgz#1fa7d51615a44f289632ae8c588007ba943db216"
|
||||
integrity sha512-tcGJQXXr2LYoBbIXPoUVe1KCF3OtBsuKDFL7BXfmNtuSGtWF0yejm6H83DrR8/cUIGMRMUP9lqNlqFGwDYiwAQ==
|
||||
dependencies:
|
||||
"@microsoft/1ds-core-js" "3.2.3"
|
||||
"@microsoft/applicationinsights-shims" "^2.0.1"
|
||||
"@microsoft/dynamicproto-js" "^1.1.6"
|
||||
|
||||
"@microsoft/applicationinsights-core-js@2.8.4":
|
||||
version "2.8.4"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596"
|
||||
integrity sha512-FoA0FNOsFbJnLyTyQlYs6+HR7HMEa6nAOE6WOm9WVejBHMHQ/Bdb+hfVFi6slxwCimr/ner90jchi4/sIYdnyQ==
|
||||
dependencies:
|
||||
"@microsoft/applicationinsights-shims" "2.0.1"
|
||||
"@microsoft/dynamicproto-js" "^1.1.6"
|
||||
|
||||
"@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1":
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd"
|
||||
integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ==
|
||||
|
||||
"@microsoft/dynamicproto-js@^1.1.6":
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc"
|
||||
integrity sha512-D1Oivw1A4bIXhzBIy3/BBPn3p2On+kpO2NiYt9shICDK7L/w+cR6FFBUsBZ05l6iqzTeL+Jm8lAYn0g6G7DmDg==
|
||||
|
||||
"@vscode/extension-telemetry@0.6.2":
|
||||
version "0.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.2.tgz#b86814ee680615730da94220c2b03ea9c3c14a8e"
|
||||
integrity sha512-yb/wxLuaaCRcBAZtDCjNYSisAXz3FWsSqAha5nhHcYxx2ZPdQdWuZqVXGKq0ZpHVndBWWtK6XqtpCN2/HB4S1w==
|
||||
dependencies:
|
||||
"@microsoft/1ds-core-js" "^3.2.3"
|
||||
"@microsoft/1ds-post-js" "^3.2.3"
|
||||
|
||||
vscode-nls@^5.0.0:
|
||||
version "5.0.0"
|
||||
|
||||
@@ -2,3 +2,4 @@ out
|
||||
dist
|
||||
node_modules
|
||||
*.vsix
|
||||
notebook-out
|
||||
|
||||
@@ -6,4 +6,4 @@ extension.webpack.config.js
|
||||
extension-browser.webpack.config.js
|
||||
yarn.lock
|
||||
.gitignore
|
||||
|
||||
esbuild.js
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
//@ts-check
|
||||
|
||||
const path = require('path');
|
||||
const fse = require('fs-extra');
|
||||
const esbuild = require('esbuild');
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
const isWatch = args.indexOf('--watch') >= 0;
|
||||
|
||||
let outputRoot = __dirname;
|
||||
const outputRootIndex = args.indexOf('--outputRoot');
|
||||
if (outputRootIndex >= 0) {
|
||||
outputRoot = args[outputRootIndex + 1];
|
||||
}
|
||||
|
||||
const srcDir = path.join(__dirname, 'src');
|
||||
const outDir = path.join(outputRoot, 'notebook-out');
|
||||
|
||||
async function build() {
|
||||
await esbuild.build({
|
||||
entryPoints: [
|
||||
path.join(srcDir, 'cellAttachmentRenderer.ts'),
|
||||
],
|
||||
bundle: true,
|
||||
minify: false,
|
||||
sourcemap: false,
|
||||
format: 'esm',
|
||||
outdir: outDir,
|
||||
platform: 'browser',
|
||||
target: ['es2020'],
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
build().catch(() => process.exit(1));
|
||||
|
||||
if (isWatch) {
|
||||
const watcher = require('@parcel/watcher');
|
||||
watcher.subscribe(srcDir, () => {
|
||||
return build();
|
||||
});
|
||||
}
|
||||
@@ -52,6 +52,16 @@
|
||||
"priority": "default"
|
||||
}
|
||||
],
|
||||
"notebookRenderer": [
|
||||
{
|
||||
"id": "vscode.markdown-it-cell-attachment-renderer",
|
||||
"displayName": "Markdown it ipynb Cell Attachment renderer",
|
||||
"entrypoint": {
|
||||
"extends": "vscode.markdown-it-renderer",
|
||||
"path": "./notebook-out/cellAttachmentRenderer.js"
|
||||
}
|
||||
}
|
||||
],
|
||||
"menus": {
|
||||
"file/newFile": [
|
||||
{
|
||||
@@ -71,8 +81,9 @@
|
||||
}
|
||||
},
|
||||
"scripts": {
|
||||
"compile": "npx gulp compile-extension:ipynb",
|
||||
"watch": "npx gulp watch-extension:ipynb"
|
||||
"compile": "npx gulp compile-extension:ipynb && npm run build-notebook",
|
||||
"watch": "npx gulp watch-extension:ipynb",
|
||||
"build-notebook": "node ./esbuild"
|
||||
},
|
||||
"dependencies": {
|
||||
"@enonic/fnv-plus": "^1.3.0",
|
||||
@@ -81,6 +92,7 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jupyterlab/nbformat": "^3.2.9",
|
||||
"@types/markdown-it": "12.2.3",
|
||||
"@types/uuid": "^8.3.1"
|
||||
},
|
||||
"repository": {
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import type * as MarkdownIt from 'markdown-it';
|
||||
import type * as MarkdownItToken from 'markdown-it/lib/token';
|
||||
import type { RendererContext } from 'vscode-notebook-renderer';
|
||||
|
||||
interface MarkdownItRenderer {
|
||||
extendMarkdownIt(fn: (md: MarkdownIt) => void): void;
|
||||
}
|
||||
|
||||
export async function activate(ctx: RendererContext<void>) {
|
||||
const markdownItRenderer = (await ctx.getRenderer('vscode.markdown-it-renderer')) as MarkdownItRenderer | any;
|
||||
if (!markdownItRenderer) {
|
||||
throw new Error(`Could not load 'vscode.markdown-it-renderer'`);
|
||||
}
|
||||
|
||||
markdownItRenderer.extendMarkdownIt((md: MarkdownIt) => {
|
||||
const original = md.renderer.rules.image;
|
||||
md.renderer.rules.image = (tokens: MarkdownItToken[], idx: number, options, env, self) => {
|
||||
const token = tokens[idx];
|
||||
const src = token.attrGet('src');
|
||||
const attachments: Record<string, Record<string, string>> = env.outputItem.metadata?.custom?.attachments; // this stores attachment entries for every image in the cell
|
||||
if (attachments && src) {
|
||||
const imageAttachment = attachments[src.replace('attachment:', '')];
|
||||
if (imageAttachment) {
|
||||
// objEntries will always be length 1, with objEntries[0] holding [0]=mime,[1]=b64
|
||||
// if length = 0, something is wrong with the attachment, mime/b64 weren't copied over
|
||||
const objEntries = Object.entries(imageAttachment);
|
||||
if (objEntries.length) {
|
||||
const [attachmentKey, attachmentVal] = objEntries[0];
|
||||
const b64Markdown = 'data:' + attachmentKey + ';base64,' + attachmentVal;
|
||||
token.attrSet('src', b64Markdown);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (original) {
|
||||
return original(tokens, idx, options, env, self);
|
||||
} else {
|
||||
return self.renderToken(tokens, idx, options);
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -8,21 +8,39 @@
|
||||
integrity sha512-BCN9uNWH8AmiP7BXBJqEinUY9KXalmRzo+L0cB/mQsmFfzODxwQrbvxCHXUNH2iP+qKkWYtB4vyy8N62PViMFw==
|
||||
|
||||
"@jupyterlab/nbformat@^3.2.9":
|
||||
version "3.2.9"
|
||||
resolved "https://registry.yarnpkg.com/@jupyterlab/nbformat/-/nbformat-3.2.9.tgz#e7d854719612133498af4280d9a8caa0873205b0"
|
||||
integrity sha512-WSf9OQo8yfFjyodbXRdFoaNwMkaAL5jFZiD6V2f8HqI380ipansWrrV7R9CGzPfgKHpUGZMO1tYKmUwzMhvZ4w==
|
||||
version "3.4.3"
|
||||
resolved "https://registry.yarnpkg.com/@jupyterlab/nbformat/-/nbformat-3.4.3.tgz#cbab1bf507677b7f0f309d8353fc83fe5a973c82"
|
||||
integrity sha512-i/yADrwhhAJJCUOTa+fEBMyJO7fvX9Y73I0B7V6dQhGcrmrEKLC3wk4yOo63+jRntd5+dupbiOtz3w1ncIXwIA==
|
||||
dependencies:
|
||||
"@lumino/coreutils" "^1.5.3"
|
||||
"@lumino/coreutils" "^1.11.0"
|
||||
|
||||
"@lumino/coreutils@^1.5.3":
|
||||
"@lumino/coreutils@^1.11.0":
|
||||
version "1.12.0"
|
||||
resolved "https://registry.yarnpkg.com/@lumino/coreutils/-/coreutils-1.12.0.tgz#fbdef760f736eaf2bd396a5c6fc3a68a4b449b15"
|
||||
integrity sha512-DSglh4ylmLi820CNx9soJmDJCpUgymckdWeGWuN0Ash5g60oQvrQDfosVxEhzmNvtvXv45WZEqSBzDP6E5SEmQ==
|
||||
|
||||
"@types/linkify-it@*":
|
||||
version "3.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/linkify-it/-/linkify-it-3.0.2.tgz#fd2cd2edbaa7eaac7e7f3c1748b52a19143846c9"
|
||||
integrity sha512-HZQYqbiFVWufzCwexrvh694SOim8z2d+xJl5UNamcvQFejLY/2YUtzXHYi3cHdI7PMlS8ejH2slRAOJQ32aNbA==
|
||||
|
||||
"@types/markdown-it@12.2.3":
|
||||
version "12.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-12.2.3.tgz#0d6f6e5e413f8daaa26522904597be3d6cd93b51"
|
||||
integrity sha512-GKMHFfv3458yYy+v/N8gjufHO6MSZKCOXpZc5GXIWWy8uldwfmPn98vp81gZ5f9SVw8YYBctgfJ22a2d7AOMeQ==
|
||||
dependencies:
|
||||
"@types/linkify-it" "*"
|
||||
"@types/mdurl" "*"
|
||||
|
||||
"@types/mdurl@*":
|
||||
version "1.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-1.0.2.tgz#e2ce9d83a613bacf284c7be7d491945e39e1f8e9"
|
||||
integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==
|
||||
|
||||
"@types/uuid@^8.3.1":
|
||||
version "8.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.1.tgz#1a32969cf8f0364b3d8c8af9cc3555b7805df14f"
|
||||
integrity sha512-Y2mHTRAbqfFkpjldbkHGY8JIzRN6XqYRliG8/24FcHm2D2PwW24fl5xMRTVGdrb7iMrwCaIEbLWerGIkXuFWVg==
|
||||
version "8.3.4"
|
||||
resolved "https://registry.yarnpkg.com/@types/uuid/-/uuid-8.3.4.tgz#bd86a43617df0594787d38b735f55c805becf1bc"
|
||||
integrity sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==
|
||||
|
||||
detect-indent@^6.0.0:
|
||||
version "6.1.0"
|
||||
|
||||
@@ -107,7 +107,7 @@
|
||||
}
|
||||
},
|
||||
"wordPattern": {
|
||||
"pattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\@\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>/\\?\\s]+)",
|
||||
"pattern": "(-?\\d*\\.\\d\\w*)|([^\\`\\~\\!\\%\\^\\&\\*\\(\\)\\-\\=\\+\\[\\{\\]\\}\\\\\\|\\;\\:\\'\\\"\\,\\.\\<\\>/\\?\\s]+)",
|
||||
},
|
||||
"indentationRules": {
|
||||
"decreaseIndentPattern": {
|
||||
|
||||
@@ -190,5 +190,14 @@
|
||||
"console.error($1);"
|
||||
],
|
||||
"description": "Log error to the console"
|
||||
},
|
||||
"new Promise": {
|
||||
"prefix": "newpromise",
|
||||
"body": [
|
||||
"new Promise((resolve, reject) => {",
|
||||
"\t$TM_SELECTED_TEXT$0",
|
||||
"})"
|
||||
],
|
||||
"description": "Create a new Promise"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"If you want to provide a fix or improvement, please create a pull request against the original repository.",
|
||||
"Once accepted there, we are happy to receive an update request."
|
||||
],
|
||||
"version": "https://github.com/microsoft/TypeScript-TmLanguage/commit/4d30ff834ec324f56291addd197aa1e423cedfdd",
|
||||
"version": "https://github.com/microsoft/TypeScript-TmLanguage/commit/0dfae8cc4807fecfbf8f1add095d9817df824c95",
|
||||
"name": "JavaScript (with React support)",
|
||||
"scopeName": "source.js",
|
||||
"patterns": [
|
||||
@@ -2271,11 +2271,47 @@
|
||||
"name": "keyword.control.from.js",
|
||||
"match": "\\bfrom\\b"
|
||||
},
|
||||
{
|
||||
"include": "#import-export-assert-clause"
|
||||
},
|
||||
{
|
||||
"include": "#import-export-clause"
|
||||
}
|
||||
]
|
||||
},
|
||||
"import-export-assert-clause": {
|
||||
"begin": "(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(assert)\\s*(\\{)",
|
||||
"beginCaptures": {
|
||||
"1": {
|
||||
"name": "keyword.control.assert.js"
|
||||
},
|
||||
"2": {
|
||||
"name": "punctuation.definition.block.js"
|
||||
}
|
||||
},
|
||||
"end": "\\}",
|
||||
"endCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.definition.block.js"
|
||||
}
|
||||
},
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#comment"
|
||||
},
|
||||
{
|
||||
"include": "#string"
|
||||
},
|
||||
{
|
||||
"name": "meta.object-literal.key.js",
|
||||
"match": "(?:[_$[:alpha:]][_$[:alnum:]]*)\\s*(?=(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*:)"
|
||||
},
|
||||
{
|
||||
"name": "punctuation.separator.key-value.js",
|
||||
"match": ":"
|
||||
}
|
||||
]
|
||||
},
|
||||
"import-export-block": {
|
||||
"name": "meta.block.js",
|
||||
"begin": "\\{",
|
||||
@@ -3914,7 +3950,7 @@
|
||||
},
|
||||
{
|
||||
"name": "storage.modifier.js",
|
||||
"match": "(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(extends)(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))"
|
||||
"match": "(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(extends|in|out)(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))"
|
||||
},
|
||||
{
|
||||
"include": "#type"
|
||||
@@ -5064,7 +5100,7 @@
|
||||
},
|
||||
"directives": {
|
||||
"name": "comment.line.triple-slash.directive.js",
|
||||
"begin": "^(///)\\s*(?=<(reference|amd-dependency|amd-module)(\\s+(path|types|no-default-lib|lib|name)\\s*=\\s*((\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`)))+\\s*/>\\s*$)",
|
||||
"begin": "^(///)\\s*(?=<(reference|amd-dependency|amd-module)(\\s+(path|types|no-default-lib|lib|name|resolution-mode)\\s*=\\s*((\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`)))+\\s*/>\\s*$)",
|
||||
"beginCaptures": {
|
||||
"1": {
|
||||
"name": "punctuation.definition.comment.js"
|
||||
@@ -5092,7 +5128,7 @@
|
||||
"patterns": [
|
||||
{
|
||||
"name": "entity.other.attribute-name.directive.js",
|
||||
"match": "path|types|no-default-lib|lib|name"
|
||||
"match": "path|types|no-default-lib|lib|name|resolution-mode"
|
||||
},
|
||||
{
|
||||
"name": "keyword.operator.assignment.js",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"If you want to provide a fix or improvement, please create a pull request against the original repository.",
|
||||
"Once accepted there, we are happy to receive an update request."
|
||||
],
|
||||
"version": "https://github.com/microsoft/TypeScript-TmLanguage/commit/4d30ff834ec324f56291addd197aa1e423cedfdd",
|
||||
"version": "https://github.com/microsoft/TypeScript-TmLanguage/commit/0dfae8cc4807fecfbf8f1add095d9817df824c95",
|
||||
"name": "JavaScript (with React support)",
|
||||
"scopeName": "source.js.jsx",
|
||||
"patterns": [
|
||||
@@ -2271,11 +2271,47 @@
|
||||
"name": "keyword.control.from.js.jsx",
|
||||
"match": "\\bfrom\\b"
|
||||
},
|
||||
{
|
||||
"include": "#import-export-assert-clause"
|
||||
},
|
||||
{
|
||||
"include": "#import-export-clause"
|
||||
}
|
||||
]
|
||||
},
|
||||
"import-export-assert-clause": {
|
||||
"begin": "(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(assert)\\s*(\\{)",
|
||||
"beginCaptures": {
|
||||
"1": {
|
||||
"name": "keyword.control.assert.js.jsx"
|
||||
},
|
||||
"2": {
|
||||
"name": "punctuation.definition.block.js.jsx"
|
||||
}
|
||||
},
|
||||
"end": "\\}",
|
||||
"endCaptures": {
|
||||
"0": {
|
||||
"name": "punctuation.definition.block.js.jsx"
|
||||
}
|
||||
},
|
||||
"patterns": [
|
||||
{
|
||||
"include": "#comment"
|
||||
},
|
||||
{
|
||||
"include": "#string"
|
||||
},
|
||||
{
|
||||
"name": "meta.object-literal.key.js.jsx",
|
||||
"match": "(?:[_$[:alpha:]][_$[:alnum:]]*)\\s*(?=(\\/\\*([^\\*]|(\\*[^\\/]))*\\*\\/\\s*)*:)"
|
||||
},
|
||||
{
|
||||
"name": "punctuation.separator.key-value.js.jsx",
|
||||
"match": ":"
|
||||
}
|
||||
]
|
||||
},
|
||||
"import-export-block": {
|
||||
"name": "meta.block.js.jsx",
|
||||
"begin": "\\{",
|
||||
@@ -3914,7 +3950,7 @@
|
||||
},
|
||||
{
|
||||
"name": "storage.modifier.js.jsx",
|
||||
"match": "(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(extends)(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))"
|
||||
"match": "(?<![_$[:alnum:]])(?:(?<=\\.\\.\\.)|(?<!\\.))(extends|in|out)(?![_$[:alnum:]])(?:(?=\\.\\.\\.)|(?!\\.))"
|
||||
},
|
||||
{
|
||||
"include": "#type"
|
||||
@@ -5064,7 +5100,7 @@
|
||||
},
|
||||
"directives": {
|
||||
"name": "comment.line.triple-slash.directive.js.jsx",
|
||||
"begin": "^(///)\\s*(?=<(reference|amd-dependency|amd-module)(\\s+(path|types|no-default-lib|lib|name)\\s*=\\s*((\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`)))+\\s*/>\\s*$)",
|
||||
"begin": "^(///)\\s*(?=<(reference|amd-dependency|amd-module)(\\s+(path|types|no-default-lib|lib|name|resolution-mode)\\s*=\\s*((\\'([^\\'\\\\]|\\\\.)*\\')|(\\\"([^\\\"\\\\]|\\\\.)*\\\")|(\\`([^\\`\\\\]|\\\\.)*\\`)))+\\s*/>\\s*$)",
|
||||
"beginCaptures": {
|
||||
"1": {
|
||||
"name": "punctuation.definition.comment.js.jsx"
|
||||
@@ -5092,7 +5128,7 @@
|
||||
"patterns": [
|
||||
{
|
||||
"name": "entity.other.attribute-name.directive.js.jsx",
|
||||
"match": "path|types|no-default-lib|lib|name"
|
||||
"match": "path|types|no-default-lib|lib|name|resolution-mode"
|
||||
},
|
||||
{
|
||||
"name": "keyword.operator.assignment.js.jsx",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ExtensionContext, Uri } from 'vscode';
|
||||
import { LanguageClientOptions } from 'vscode-languageclient';
|
||||
import { BaseLanguageClient, LanguageClientOptions } from 'vscode-languageclient';
|
||||
import { startClient, LanguageClientConstructor, SchemaRequestService } from '../jsonClient';
|
||||
import { LanguageClient } from 'vscode-languageclient/browser';
|
||||
|
||||
@@ -14,8 +14,10 @@ declare const Worker: {
|
||||
|
||||
declare function fetch(uri: string, options: any): any;
|
||||
|
||||
let client: BaseLanguageClient | undefined;
|
||||
|
||||
// this method is called when vs code is activated
|
||||
export function activate(context: ExtensionContext) {
|
||||
export async function activate(context: ExtensionContext) {
|
||||
const serverMain = Uri.joinPath(context.extensionUri, 'server/dist/browser/jsonServerMain.js');
|
||||
try {
|
||||
const worker = new Worker(serverMain.toString());
|
||||
@@ -32,9 +34,16 @@ export function activate(context: ExtensionContext) {
|
||||
}
|
||||
};
|
||||
|
||||
startClient(context, newLanguageClient, { schemaRequests });
|
||||
client = await startClient(context, newLanguageClient, { schemaRequests });
|
||||
|
||||
} catch (e) {
|
||||
console.log(e);
|
||||
}
|
||||
}
|
||||
|
||||
export async function deactivate(): Promise<void> {
|
||||
if (client) {
|
||||
await client.stop();
|
||||
client = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,18 +9,19 @@ const localize = nls.loadMessageBundle();
|
||||
export type JSONLanguageStatus = { schemas: string[] };
|
||||
|
||||
import {
|
||||
workspace, window, languages, commands, ExtensionContext, extensions, Uri,
|
||||
Diagnostic, StatusBarAlignment, TextEditor, TextDocument, FormattingOptions, CancellationToken,
|
||||
ProviderResult, TextEdit, Range, Position, Disposable, CompletionItem, CompletionList, CompletionContext, Hover, MarkdownString,
|
||||
workspace, window, languages, commands, ExtensionContext, extensions, Uri, ColorInformation,
|
||||
Diagnostic, StatusBarAlignment, TextEditor, TextDocument, FormattingOptions, CancellationToken, FoldingRange,
|
||||
ProviderResult, TextEdit, Range, Position, Disposable, CompletionItem, CompletionList, CompletionContext, Hover, MarkdownString, FoldingContext, DocumentSymbol, SymbolInformation
|
||||
} from 'vscode';
|
||||
import {
|
||||
LanguageClientOptions, RequestType, NotificationType,
|
||||
DidChangeConfigurationNotification, HandleDiagnosticsSignature, ResponseError, DocumentRangeFormattingParams,
|
||||
DocumentRangeFormattingRequest, ProvideCompletionItemsSignature, ProvideHoverSignature, CommonLanguageClient
|
||||
DocumentRangeFormattingRequest, ProvideCompletionItemsSignature, ProvideHoverSignature, BaseLanguageClient, ProvideFoldingRangeSignature, ProvideDocumentSymbolsSignature, ProvideDocumentColorsSignature
|
||||
} from 'vscode-languageclient';
|
||||
|
||||
|
||||
import { hash } from './utils/hash';
|
||||
import { createLanguageStatusItem } from './languageStatus';
|
||||
import { createDocumentColorsLimitItem, createDocumentSymbolsLimitItem, createFoldingRangeLimitItem, createLanguageStatusItem, createLimitStatusItem } from './languageStatus';
|
||||
|
||||
namespace VSCodeContentRequest {
|
||||
export const type: RequestType<string, string, any> = new RequestType('vscode/content');
|
||||
@@ -52,14 +53,11 @@ namespace SchemaAssociationNotification {
|
||||
export const type: NotificationType<ISchemaAssociations | ISchemaAssociation[]> = new NotificationType('json/schemaAssociations');
|
||||
}
|
||||
|
||||
namespace ResultLimitReachedNotification {
|
||||
export const type: NotificationType<string> = new NotificationType('json/resultLimitReached');
|
||||
}
|
||||
|
||||
interface Settings {
|
||||
type Settings = {
|
||||
json?: {
|
||||
schemas?: JSONSchemaSettings[];
|
||||
format?: { enable?: boolean };
|
||||
keepLines?: { enable?: boolean };
|
||||
validate?: { enable?: boolean };
|
||||
resultLimit?: number;
|
||||
};
|
||||
@@ -67,25 +65,22 @@ interface Settings {
|
||||
proxy?: string;
|
||||
proxyStrictSSL?: boolean;
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export interface JSONSchemaSettings {
|
||||
export type JSONSchemaSettings = {
|
||||
fileMatch?: string[];
|
||||
url?: string;
|
||||
schema?: any;
|
||||
}
|
||||
};
|
||||
|
||||
namespace SettingIds {
|
||||
export namespace SettingIds {
|
||||
export const enableFormatter = 'json.format.enable';
|
||||
export const enableKeepLines = 'json.format.keepLines';
|
||||
export const enableValidation = 'json.validate.enable';
|
||||
export const enableSchemaDownload = 'json.schemaDownload.enable';
|
||||
export const maxItemsComputed = 'json.maxItemsComputed';
|
||||
}
|
||||
|
||||
namespace StorageIds {
|
||||
export const maxItemsExceededInformation = 'json.maxItemsExceededInformation';
|
||||
}
|
||||
|
||||
export interface TelemetryReporter {
|
||||
sendTelemetryEvent(eventName: string, properties?: {
|
||||
[key: string]: string;
|
||||
@@ -94,7 +89,7 @@ export interface TelemetryReporter {
|
||||
}): void;
|
||||
}
|
||||
|
||||
export type LanguageClientConstructor = (name: string, description: string, clientOptions: LanguageClientOptions) => CommonLanguageClient;
|
||||
export type LanguageClientConstructor = (name: string, description: string, clientOptions: LanguageClientOptions) => BaseLanguageClient;
|
||||
|
||||
export interface Runtime {
|
||||
schemaRequests: SchemaRequestService;
|
||||
@@ -108,7 +103,9 @@ export interface SchemaRequestService {
|
||||
|
||||
export const languageServerDescription = localize('jsonserver.name', 'JSON Language Server');
|
||||
|
||||
export function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime) {
|
||||
let resultLimit = 5000;
|
||||
|
||||
export async function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime): Promise<BaseLanguageClient> {
|
||||
|
||||
const toDispose = context.subscriptions;
|
||||
|
||||
@@ -126,6 +123,11 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua
|
||||
|
||||
let isClientReady = false;
|
||||
|
||||
const foldingRangeLimitStatusBarItem = createLimitStatusItem((limit: number) => createFoldingRangeLimitItem(documentSelector, SettingIds.maxItemsComputed, limit));
|
||||
const documentSymbolsLimitStatusbarItem = createLimitStatusItem((limit: number) => createDocumentSymbolsLimitItem(documentSelector, SettingIds.maxItemsComputed, limit));
|
||||
const documentColorsLimitStatusbarItem = createLimitStatusItem((limit: number) => createDocumentColorsLimitItem(documentSelector, SettingIds.maxItemsComputed, limit));
|
||||
toDispose.push(foldingRangeLimitStatusBarItem, documentSymbolsLimitStatusbarItem, documentColorsLimitStatusbarItem);
|
||||
|
||||
toDispose.push(commands.registerCommand('json.clearCache', async () => {
|
||||
if (isClientReady && runtime.schemaRequests.clearCache) {
|
||||
const cachedSchemas = await runtime.schemaRequests.clearCache();
|
||||
@@ -210,6 +212,60 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua
|
||||
return r.then(updateHover);
|
||||
}
|
||||
return updateHover(r);
|
||||
},
|
||||
provideFoldingRanges(document: TextDocument, context: FoldingContext, token: CancellationToken, next: ProvideFoldingRangeSignature) {
|
||||
function checkLimit(r: FoldingRange[] | null | undefined): FoldingRange[] | null | undefined {
|
||||
if (Array.isArray(r) && r.length > resultLimit) {
|
||||
r.length = resultLimit; // truncate
|
||||
foldingRangeLimitStatusBarItem.update(document, resultLimit);
|
||||
} else {
|
||||
foldingRangeLimitStatusBarItem.update(document, false);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
const r = next(document, context, token);
|
||||
if (isThenable<FoldingRange[] | null | undefined>(r)) {
|
||||
return r.then(checkLimit);
|
||||
}
|
||||
return checkLimit(r);
|
||||
},
|
||||
provideDocumentColors(document: TextDocument, token: CancellationToken, next: ProvideDocumentColorsSignature) {
|
||||
function checkLimit(r: ColorInformation[] | null | undefined): ColorInformation[] | null | undefined {
|
||||
if (Array.isArray(r) && r.length > resultLimit) {
|
||||
r.length = resultLimit; // truncate
|
||||
documentColorsLimitStatusbarItem.update(document, resultLimit);
|
||||
} else {
|
||||
documentColorsLimitStatusbarItem.update(document, false);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
const r = next(document, token);
|
||||
if (isThenable<ColorInformation[] | null | undefined>(r)) {
|
||||
return r.then(checkLimit);
|
||||
}
|
||||
return checkLimit(r);
|
||||
},
|
||||
provideDocumentSymbols(document: TextDocument, token: CancellationToken, next: ProvideDocumentSymbolsSignature) {
|
||||
type T = SymbolInformation[] | DocumentSymbol[];
|
||||
function countDocumentSymbols(symbols: DocumentSymbol[]): number {
|
||||
return symbols.reduce((previousValue, s) => previousValue + 1 + countDocumentSymbols(s.children), 0);
|
||||
}
|
||||
function isDocumentSymbol(r: T): r is DocumentSymbol[] {
|
||||
return r[0] instanceof DocumentSymbol;
|
||||
}
|
||||
function checkLimit(r: T | null | undefined): T | null | undefined {
|
||||
if (Array.isArray(r) && (isDocumentSymbol(r) ? countDocumentSymbols(r) : r.length) > resultLimit) {
|
||||
documentSymbolsLimitStatusbarItem.update(document, resultLimit);
|
||||
} else {
|
||||
documentSymbolsLimitStatusbarItem.update(document, false);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
const r = next(document, token);
|
||||
if (isThenable<T | undefined | null>(r)) {
|
||||
return r.then(checkLimit);
|
||||
}
|
||||
return checkLimit(r);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -218,176 +274,162 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua
|
||||
const client = newLanguageClient('json', languageServerDescription, clientOptions);
|
||||
client.registerProposedFeatures();
|
||||
|
||||
const disposable = client.start();
|
||||
toDispose.push(disposable);
|
||||
client.onReady().then(() => {
|
||||
isClientReady = true;
|
||||
const schemaDocuments: { [uri: string]: boolean } = {};
|
||||
|
||||
const schemaDocuments: { [uri: string]: boolean } = {};
|
||||
|
||||
// handle content request
|
||||
client.onRequest(VSCodeContentRequest.type, (uriPath: string) => {
|
||||
const uri = Uri.parse(uriPath);
|
||||
if (uri.scheme === 'untitled') {
|
||||
return Promise.reject(new ResponseError(3, localize('untitled.schema', 'Unable to load {0}', uri.toString())));
|
||||
}
|
||||
if (uri.scheme !== 'http' && uri.scheme !== 'https') {
|
||||
return workspace.openTextDocument(uri).then(doc => {
|
||||
schemaDocuments[uri.toString()] = true;
|
||||
return doc.getText();
|
||||
}, error => {
|
||||
return Promise.reject(new ResponseError(2, error.toString()));
|
||||
});
|
||||
} else if (schemaDownloadEnabled) {
|
||||
if (runtime.telemetry && uri.authority === 'schema.management.azure.com') {
|
||||
/* __GDPR__
|
||||
"json.schema" : {
|
||||
"schemaURL" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
|
||||
}
|
||||
*/
|
||||
runtime.telemetry.sendTelemetryEvent('json.schema', { schemaURL: uriPath });
|
||||
}
|
||||
return runtime.schemaRequests.getContent(uriPath).catch(e => {
|
||||
return Promise.reject(new ResponseError(4, e.toString()));
|
||||
});
|
||||
} else {
|
||||
return Promise.reject(new ResponseError(1, localize('schemaDownloadDisabled', 'Downloading schemas is disabled through setting \'{0}\'', SettingIds.enableSchemaDownload)));
|
||||
}
|
||||
});
|
||||
|
||||
const handleContentChange = (uriString: string) => {
|
||||
if (schemaDocuments[uriString]) {
|
||||
client.sendNotification(SchemaContentChangeNotification.type, uriString);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const handleActiveEditorChange = (activeEditor?: TextEditor) => {
|
||||
if (!activeEditor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeDocUri = activeEditor.document.uri.toString();
|
||||
|
||||
if (activeDocUri && fileSchemaErrors.has(activeDocUri)) {
|
||||
schemaResolutionErrorStatusBarItem.show();
|
||||
} else {
|
||||
schemaResolutionErrorStatusBarItem.hide();
|
||||
}
|
||||
};
|
||||
|
||||
toDispose.push(workspace.onDidChangeTextDocument(e => handleContentChange(e.document.uri.toString())));
|
||||
toDispose.push(workspace.onDidCloseTextDocument(d => {
|
||||
const uriString = d.uri.toString();
|
||||
if (handleContentChange(uriString)) {
|
||||
delete schemaDocuments[uriString];
|
||||
}
|
||||
fileSchemaErrors.delete(uriString);
|
||||
}));
|
||||
toDispose.push(window.onDidChangeActiveTextEditor(handleActiveEditorChange));
|
||||
|
||||
const handleRetryResolveSchemaCommand = () => {
|
||||
if (window.activeTextEditor) {
|
||||
schemaResolutionErrorStatusBarItem.text = '$(watch)';
|
||||
const activeDocUri = window.activeTextEditor.document.uri.toString();
|
||||
client.sendRequest(ForceValidateRequest.type, activeDocUri).then((diagnostics) => {
|
||||
const schemaErrorIndex = diagnostics.findIndex(isSchemaResolveError);
|
||||
if (schemaErrorIndex !== -1) {
|
||||
// Show schema resolution errors in status bar only; ref: #51032
|
||||
const schemaResolveDiagnostic = diagnostics[schemaErrorIndex];
|
||||
fileSchemaErrors.set(activeDocUri, schemaResolveDiagnostic.message);
|
||||
} else {
|
||||
schemaResolutionErrorStatusBarItem.hide();
|
||||
}
|
||||
schemaResolutionErrorStatusBarItem.text = '$(alert)';
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
toDispose.push(commands.registerCommand('_json.retryResolveSchema', handleRetryResolveSchemaCommand));
|
||||
|
||||
client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociations(context));
|
||||
|
||||
toDispose.push(extensions.onDidChange(_ => {
|
||||
client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociations(context));
|
||||
}));
|
||||
|
||||
// manually register / deregister format provider based on the `json.format.enable` setting avoiding issues with late registration. See #71652.
|
||||
updateFormatterRegistration();
|
||||
toDispose.push({ dispose: () => rangeFormatting && rangeFormatting.dispose() });
|
||||
|
||||
updateSchemaDownloadSetting();
|
||||
|
||||
toDispose.push(workspace.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration(SettingIds.enableFormatter)) {
|
||||
updateFormatterRegistration();
|
||||
} else if (e.affectsConfiguration(SettingIds.enableSchemaDownload)) {
|
||||
updateSchemaDownloadSetting();
|
||||
}
|
||||
}));
|
||||
|
||||
client.onNotification(ResultLimitReachedNotification.type, async message => {
|
||||
const shouldPrompt = context.globalState.get<boolean>(StorageIds.maxItemsExceededInformation) !== false;
|
||||
if (shouldPrompt) {
|
||||
const ok = localize('ok', "OK");
|
||||
const openSettings = localize('goToSetting', 'Open Settings');
|
||||
const neverAgain = localize('yes never again', "Don't Show Again");
|
||||
const pick = await window.showInformationMessage(`${message}\n${localize('configureLimit', 'Use setting \'{0}\' to configure the limit.', SettingIds.maxItemsComputed)}`, ok, openSettings, neverAgain);
|
||||
if (pick === neverAgain) {
|
||||
await context.globalState.update(StorageIds.maxItemsExceededInformation, false);
|
||||
} else if (pick === openSettings) {
|
||||
await commands.executeCommand('workbench.action.openSettings', SettingIds.maxItemsComputed);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
toDispose.push(createLanguageStatusItem(documentSelector, (uri: string) => client.sendRequest(LanguageStatusRequest.type, uri)));
|
||||
|
||||
function updateFormatterRegistration() {
|
||||
const formatEnabled = workspace.getConfiguration().get(SettingIds.enableFormatter);
|
||||
if (!formatEnabled && rangeFormatting) {
|
||||
rangeFormatting.dispose();
|
||||
rangeFormatting = undefined;
|
||||
} else if (formatEnabled && !rangeFormatting) {
|
||||
rangeFormatting = languages.registerDocumentRangeFormattingEditProvider(documentSelector, {
|
||||
provideDocumentRangeFormattingEdits(document: TextDocument, range: Range, options: FormattingOptions, token: CancellationToken): ProviderResult<TextEdit[]> {
|
||||
const filesConfig = workspace.getConfiguration('files', document);
|
||||
const fileFormattingOptions = {
|
||||
trimTrailingWhitespace: filesConfig.get<boolean>('trimTrailingWhitespace'),
|
||||
trimFinalNewlines: filesConfig.get<boolean>('trimFinalNewlines'),
|
||||
insertFinalNewline: filesConfig.get<boolean>('insertFinalNewline'),
|
||||
};
|
||||
const params: DocumentRangeFormattingParams = {
|
||||
textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document),
|
||||
range: client.code2ProtocolConverter.asRange(range),
|
||||
options: client.code2ProtocolConverter.asFormattingOptions(options, fileFormattingOptions)
|
||||
};
|
||||
|
||||
return client.sendRequest(DocumentRangeFormattingRequest.type, params, token).then(
|
||||
client.protocol2CodeConverter.asTextEdits,
|
||||
(error) => {
|
||||
client.handleFailedRequest(DocumentRangeFormattingRequest.type, error, []);
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
// handle content request
|
||||
client.onRequest(VSCodeContentRequest.type, (uriPath: string) => {
|
||||
const uri = Uri.parse(uriPath);
|
||||
if (uri.scheme === 'untitled') {
|
||||
return Promise.reject(new ResponseError(3, localize('untitled.schema', 'Unable to load {0}', uri.toString())));
|
||||
}
|
||||
|
||||
function updateSchemaDownloadSetting() {
|
||||
schemaDownloadEnabled = workspace.getConfiguration().get(SettingIds.enableSchemaDownload) !== false;
|
||||
if (schemaDownloadEnabled) {
|
||||
schemaResolutionErrorStatusBarItem.tooltip = localize('json.schemaResolutionErrorMessage', 'Unable to resolve schema. Click to retry.');
|
||||
schemaResolutionErrorStatusBarItem.command = '_json.retryResolveSchema';
|
||||
handleRetryResolveSchemaCommand();
|
||||
} else {
|
||||
schemaResolutionErrorStatusBarItem.tooltip = localize('json.schemaResolutionDisabledMessage', 'Downloading schemas is disabled. Click to configure.');
|
||||
schemaResolutionErrorStatusBarItem.command = { command: 'workbench.action.openSettings', arguments: [SettingIds.enableSchemaDownload], title: '' };
|
||||
if (uri.scheme !== 'http' && uri.scheme !== 'https') {
|
||||
return workspace.openTextDocument(uri).then(doc => {
|
||||
schemaDocuments[uri.toString()] = true;
|
||||
return doc.getText();
|
||||
}, error => {
|
||||
return Promise.reject(new ResponseError(2, error.toString()));
|
||||
});
|
||||
} else if (schemaDownloadEnabled) {
|
||||
if (runtime.telemetry && uri.authority === 'schema.management.azure.com') {
|
||||
/* __GDPR__
|
||||
"json.schema" : {
|
||||
"owner": "aeschli",
|
||||
"comment": "Measure the use of the Azure resource manager schemas",
|
||||
"schemaURL" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "comment": "The azure schema URL that was requested." }
|
||||
}
|
||||
*/
|
||||
runtime.telemetry.sendTelemetryEvent('json.schema', { schemaURL: uriPath });
|
||||
}
|
||||
return runtime.schemaRequests.getContent(uriPath).catch(e => {
|
||||
return Promise.reject(new ResponseError(4, e.toString()));
|
||||
});
|
||||
} else {
|
||||
return Promise.reject(new ResponseError(1, localize('schemaDownloadDisabled', 'Downloading schemas is disabled through setting \'{0}\'', SettingIds.enableSchemaDownload)));
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
await client.start();
|
||||
|
||||
isClientReady = true;
|
||||
|
||||
const handleContentChange = (uriString: string) => {
|
||||
if (schemaDocuments[uriString]) {
|
||||
client.sendNotification(SchemaContentChangeNotification.type, uriString);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const handleActiveEditorChange = (activeEditor?: TextEditor) => {
|
||||
if (!activeEditor) {
|
||||
return;
|
||||
}
|
||||
|
||||
const activeDocUri = activeEditor.document.uri.toString();
|
||||
|
||||
if (activeDocUri && fileSchemaErrors.has(activeDocUri)) {
|
||||
schemaResolutionErrorStatusBarItem.show();
|
||||
} else {
|
||||
schemaResolutionErrorStatusBarItem.hide();
|
||||
}
|
||||
};
|
||||
|
||||
toDispose.push(workspace.onDidChangeTextDocument(e => handleContentChange(e.document.uri.toString())));
|
||||
toDispose.push(workspace.onDidCloseTextDocument(d => {
|
||||
const uriString = d.uri.toString();
|
||||
if (handleContentChange(uriString)) {
|
||||
delete schemaDocuments[uriString];
|
||||
}
|
||||
fileSchemaErrors.delete(uriString);
|
||||
}));
|
||||
toDispose.push(window.onDidChangeActiveTextEditor(handleActiveEditorChange));
|
||||
|
||||
const handleRetryResolveSchemaCommand = () => {
|
||||
if (window.activeTextEditor) {
|
||||
schemaResolutionErrorStatusBarItem.text = '$(watch)';
|
||||
const activeDocUri = window.activeTextEditor.document.uri.toString();
|
||||
client.sendRequest(ForceValidateRequest.type, activeDocUri).then((diagnostics) => {
|
||||
const schemaErrorIndex = diagnostics.findIndex(isSchemaResolveError);
|
||||
if (schemaErrorIndex !== -1) {
|
||||
// Show schema resolution errors in status bar only; ref: #51032
|
||||
const schemaResolveDiagnostic = diagnostics[schemaErrorIndex];
|
||||
fileSchemaErrors.set(activeDocUri, schemaResolveDiagnostic.message);
|
||||
} else {
|
||||
schemaResolutionErrorStatusBarItem.hide();
|
||||
}
|
||||
schemaResolutionErrorStatusBarItem.text = '$(alert)';
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
toDispose.push(commands.registerCommand('_json.retryResolveSchema', handleRetryResolveSchemaCommand));
|
||||
|
||||
client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociations(context));
|
||||
|
||||
toDispose.push(extensions.onDidChange(_ => {
|
||||
client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociations(context));
|
||||
}));
|
||||
|
||||
// manually register / deregister format provider based on the `json.format.enable` setting avoiding issues with late registration. See #71652.
|
||||
updateFormatterRegistration();
|
||||
toDispose.push({ dispose: () => rangeFormatting && rangeFormatting.dispose() });
|
||||
|
||||
updateSchemaDownloadSetting();
|
||||
|
||||
toDispose.push(workspace.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration(SettingIds.enableFormatter)) {
|
||||
updateFormatterRegistration();
|
||||
} else if (e.affectsConfiguration(SettingIds.enableSchemaDownload)) {
|
||||
updateSchemaDownloadSetting();
|
||||
}
|
||||
}));
|
||||
|
||||
toDispose.push(createLanguageStatusItem(documentSelector, (uri: string) => client.sendRequest(LanguageStatusRequest.type, uri)));
|
||||
|
||||
function updateFormatterRegistration() {
|
||||
const formatEnabled = workspace.getConfiguration().get(SettingIds.enableFormatter);
|
||||
if (!formatEnabled && rangeFormatting) {
|
||||
rangeFormatting.dispose();
|
||||
rangeFormatting = undefined;
|
||||
} else if (formatEnabled && !rangeFormatting) {
|
||||
rangeFormatting = languages.registerDocumentRangeFormattingEditProvider(documentSelector, {
|
||||
provideDocumentRangeFormattingEdits(document: TextDocument, range: Range, options: FormattingOptions, token: CancellationToken): ProviderResult<TextEdit[]> {
|
||||
const filesConfig = workspace.getConfiguration('files', document);
|
||||
const fileFormattingOptions = {
|
||||
trimTrailingWhitespace: filesConfig.get<boolean>('trimTrailingWhitespace'),
|
||||
trimFinalNewlines: filesConfig.get<boolean>('trimFinalNewlines'),
|
||||
insertFinalNewline: filesConfig.get<boolean>('insertFinalNewline'),
|
||||
};
|
||||
const params: DocumentRangeFormattingParams = {
|
||||
textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document),
|
||||
range: client.code2ProtocolConverter.asRange(range),
|
||||
options: client.code2ProtocolConverter.asFormattingOptions(options, fileFormattingOptions)
|
||||
};
|
||||
|
||||
return client.sendRequest(DocumentRangeFormattingRequest.type, params, token).then(
|
||||
client.protocol2CodeConverter.asTextEdits,
|
||||
(error) => {
|
||||
client.handleFailedRequest(DocumentRangeFormattingRequest.type, undefined, error, []);
|
||||
return Promise.resolve([]);
|
||||
}
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function updateSchemaDownloadSetting() {
|
||||
schemaDownloadEnabled = workspace.getConfiguration().get(SettingIds.enableSchemaDownload) !== false;
|
||||
if (schemaDownloadEnabled) {
|
||||
schemaResolutionErrorStatusBarItem.tooltip = localize('json.schemaResolutionErrorMessage', 'Unable to resolve schema. Click to retry.');
|
||||
schemaResolutionErrorStatusBarItem.command = '_json.retryResolveSchema';
|
||||
handleRetryResolveSchemaCommand();
|
||||
} else {
|
||||
schemaResolutionErrorStatusBarItem.tooltip = localize('json.schemaResolutionDisabledMessage', 'Downloading schemas is disabled. Click to configure.');
|
||||
schemaResolutionErrorStatusBarItem.command = { command: 'workbench.action.openSettings', arguments: [SettingIds.enableSchemaDownload], title: '' };
|
||||
}
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
function getSchemaAssociations(_context: ExtensionContext): ISchemaAssociation[] {
|
||||
@@ -430,7 +472,7 @@ function getSettings(): Settings {
|
||||
const configuration = workspace.getConfiguration();
|
||||
const httpSettings = workspace.getConfiguration('http');
|
||||
|
||||
const resultLimit: number = Math.trunc(Math.max(0, Number(workspace.getConfiguration().get(SettingIds.maxItemsComputed)))) || 5000;
|
||||
resultLimit = Math.trunc(Math.max(0, Number(workspace.getConfiguration().get(SettingIds.maxItemsComputed)))) || 5000;
|
||||
|
||||
const settings: Settings = {
|
||||
http: {
|
||||
@@ -440,8 +482,9 @@ function getSettings(): Settings {
|
||||
json: {
|
||||
validate: { enable: configuration.get(SettingIds.enableValidation) },
|
||||
format: { enable: configuration.get(SettingIds.enableFormatter) },
|
||||
keepLines: { enable: configuration.get(SettingIds.enableKeepLines) },
|
||||
schemas: [],
|
||||
resultLimit
|
||||
resultLimit: resultLimit + 1 // ask for one more so we can detect if the limit has been exceeded
|
||||
}
|
||||
};
|
||||
const schemaSettingsById: { [schemaId: string]: JSONSchemaSettings } = Object.create(null);
|
||||
|
||||
@@ -3,7 +3,11 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { window, languages, Uri, LanguageStatusSeverity, Disposable, commands, QuickPickItem, extensions, workspace, Extension, WorkspaceFolder, QuickPickItemKind, ThemeIcon } from 'vscode';
|
||||
import {
|
||||
window, languages, Uri, Disposable, commands, QuickPickItem,
|
||||
extensions, workspace, Extension, WorkspaceFolder, QuickPickItemKind,
|
||||
ThemeIcon, TextDocument, LanguageStatusSeverity
|
||||
} from 'vscode';
|
||||
import { JSONLanguageStatus, JSONSchemaSettings } from './jsonClient';
|
||||
|
||||
import * as nls from 'vscode-nls';
|
||||
@@ -187,13 +191,13 @@ export function createLanguageStatusItem(documentSelector: string[], statusReque
|
||||
statusItem.detail = undefined;
|
||||
if (schemas.length === 0) {
|
||||
statusItem.text = localize('status.noSchema.short', "No Schema Validation");
|
||||
statusItem.detail = localize('status.noSchema', 'No JSON schema configured.');
|
||||
statusItem.detail = localize('status.noSchema', 'no JSON schema configured');
|
||||
} else if (schemas.length === 1) {
|
||||
statusItem.text = localize('status.withSchema.short', "Schema Validated");
|
||||
statusItem.detail = localize('status.singleSchema', 'JSON schema configured.');
|
||||
statusItem.detail = localize('status.singleSchema', 'JSON schema configured');
|
||||
} else {
|
||||
statusItem.text = localize('status.withSchemas.short', "Schema Validated");
|
||||
statusItem.detail = localize('status.multipleSchema', 'Multiple JSON schemas configured.');
|
||||
statusItem.detail = localize('status.multipleSchema', 'multiple JSON schemas configured');
|
||||
}
|
||||
statusItem.command = {
|
||||
command: '_json.showAssociatedSchemaList',
|
||||
@@ -217,3 +221,85 @@ export function createLanguageStatusItem(documentSelector: string[], statusReque
|
||||
return Disposable.from(statusItem, activeEditorListener, showSchemasCommand);
|
||||
}
|
||||
|
||||
export function createLimitStatusItem(newItem: (limit: number) => Disposable) {
|
||||
let statusItem: Disposable | undefined;
|
||||
const activeLimits: Map<TextDocument, number> = new Map();
|
||||
|
||||
const toDispose: Disposable[] = [];
|
||||
toDispose.push(window.onDidChangeActiveTextEditor(textEditor => {
|
||||
statusItem?.dispose();
|
||||
statusItem = undefined;
|
||||
const doc = textEditor?.document;
|
||||
if (doc) {
|
||||
const limit = activeLimits.get(doc);
|
||||
if (limit !== undefined) {
|
||||
statusItem = newItem(limit);
|
||||
}
|
||||
}
|
||||
}));
|
||||
toDispose.push(workspace.onDidCloseTextDocument(document => {
|
||||
activeLimits.delete(document);
|
||||
}));
|
||||
|
||||
function update(document: TextDocument, limitApplied: number | false) {
|
||||
if (limitApplied === false) {
|
||||
activeLimits.delete(document);
|
||||
if (statusItem && document === window.activeTextEditor?.document) {
|
||||
statusItem.dispose();
|
||||
statusItem = undefined;
|
||||
}
|
||||
} else {
|
||||
activeLimits.set(document, limitApplied);
|
||||
if (document === window.activeTextEditor?.document) {
|
||||
if (!statusItem || limitApplied !== activeLimits.get(document)) {
|
||||
statusItem?.dispose();
|
||||
statusItem = newItem(limitApplied);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return {
|
||||
update,
|
||||
dispose() {
|
||||
statusItem?.dispose();
|
||||
toDispose.forEach(d => d.dispose());
|
||||
toDispose.length = 0;
|
||||
statusItem = undefined;
|
||||
activeLimits.clear();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const openSettingsCommand = 'workbench.action.openSettings';
|
||||
const configureSettingsLabel = localize('status.button.configure', "Configure");
|
||||
|
||||
export function createFoldingRangeLimitItem(documentSelector: string[], settingId: string, limit: number): Disposable {
|
||||
const statusItem = languages.createLanguageStatusItem('json.foldingRangesStatus', documentSelector);
|
||||
statusItem.name = localize('foldingRangesStatusItem.name', "JSON Folding Status");
|
||||
statusItem.severity = LanguageStatusSeverity.Warning;
|
||||
statusItem.text = localize('status.limitedFoldingRanges.short', "Folding Ranges Limited");
|
||||
statusItem.detail = localize('status.limitedFoldingRanges.details', 'only {0} folding ranges shown', limit);
|
||||
statusItem.command = { command: openSettingsCommand, arguments: [settingId], title: configureSettingsLabel };
|
||||
return Disposable.from(statusItem);
|
||||
}
|
||||
|
||||
export function createDocumentSymbolsLimitItem(documentSelector: string[], settingId: string, limit: number): Disposable {
|
||||
const statusItem = languages.createLanguageStatusItem('json.documentSymbolsStatus', documentSelector);
|
||||
statusItem.name = localize('documentSymbolsStatusItem.name', "JSON Outline Status");
|
||||
statusItem.severity = LanguageStatusSeverity.Warning;
|
||||
statusItem.text = localize('status.limitedDocumentSymbols.short', "Outline Limited");
|
||||
statusItem.detail = localize('status.limitedDocumentSymbols.details', 'only {0} document symbols shown', limit);
|
||||
statusItem.command = { command: openSettingsCommand, arguments: [settingId], title: configureSettingsLabel };
|
||||
return Disposable.from(statusItem);
|
||||
}
|
||||
|
||||
export function createDocumentColorsLimitItem(documentSelector: string[], settingId: string, limit: number): Disposable {
|
||||
const statusItem = languages.createLanguageStatusItem('json.documentColorsStatus', documentSelector);
|
||||
statusItem.name = localize('documentColorsStatusItem.name', "JSON Color Symbol Status");
|
||||
statusItem.severity = LanguageStatusSeverity.Warning;
|
||||
statusItem.text = localize('status.limitedDocumentColors.short', "Color Symbols Limited");
|
||||
statusItem.detail = localize('status.limitedDocumentColors.details', 'only {0} color decorators shown', limit);
|
||||
statusItem.command = { command: openSettingsCommand, arguments: [settingId], title: configureSettingsLabel };
|
||||
return Disposable.from(statusItem);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { ExtensionContext, OutputChannel, window, workspace } from 'vscode';
|
||||
import { startClient, LanguageClientConstructor, SchemaRequestService, languageServerDescription } from '../jsonClient';
|
||||
import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient } from 'vscode-languageclient/node';
|
||||
import { ServerOptions, TransportKind, LanguageClientOptions, LanguageClient, BaseLanguageClient } from 'vscode-languageclient/node';
|
||||
|
||||
import { promises as fs } from 'fs';
|
||||
import * as path from 'path';
|
||||
@@ -15,6 +15,7 @@ import TelemetryReporter from '@vscode/extension-telemetry';
|
||||
import { JSONSchemaCache } from './schemaCache';
|
||||
|
||||
let telemetry: TelemetryReporter | undefined;
|
||||
let client: BaseLanguageClient | undefined;
|
||||
|
||||
// this method is called when vs code is activated
|
||||
export async function activate(context: ExtensionContext) {
|
||||
@@ -45,11 +46,15 @@ export async function activate(context: ExtensionContext) {
|
||||
|
||||
const schemaRequests = await getSchemaRequestService(context, log);
|
||||
|
||||
startClient(context, newLanguageClient, { schemaRequests, telemetry });
|
||||
client = await startClient(context, newLanguageClient, { schemaRequests, telemetry });
|
||||
}
|
||||
|
||||
export function deactivate(): Promise<any> {
|
||||
return telemetry ? telemetry.dispose() : Promise.resolve(null);
|
||||
export async function deactivate(): Promise<any> {
|
||||
if (client) {
|
||||
await client.stop();
|
||||
client = undefined;
|
||||
}
|
||||
telemetry?.dispose();
|
||||
}
|
||||
|
||||
interface IPackageInfo {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
"version": "1.0.0",
|
||||
"publisher": "vscode",
|
||||
"license": "MIT",
|
||||
"aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217",
|
||||
"aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255",
|
||||
"engines": {
|
||||
"vscode": "0.10.x"
|
||||
},
|
||||
@@ -85,6 +85,12 @@
|
||||
"default": true,
|
||||
"description": "%json.format.enable.desc%"
|
||||
},
|
||||
"json.format.keepLines": {
|
||||
"type": "boolean",
|
||||
"scope": "window",
|
||||
"default": false,
|
||||
"description": "%json.format.keepLines.desc%"
|
||||
},
|
||||
"json.trace.server": {
|
||||
"type": "string",
|
||||
"scope": "window",
|
||||
@@ -147,10 +153,10 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@vscode/extension-telemetry": "0.5.0",
|
||||
"@vscode/extension-telemetry": "0.6.2",
|
||||
"request-light": "^0.5.8",
|
||||
"vscode-languageclient": "^7.0.0",
|
||||
"vscode-nls": "^5.0.0"
|
||||
"vscode-languageclient": "^8.0.2-next.5",
|
||||
"vscode-nls": "^5.0.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "16.x"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
"json.schemas.fileMatch.item.desc": "A file pattern that can contain '*' to match against when resolving JSON files to schemas.",
|
||||
"json.schemas.schema.desc": "The schema definition for the given URL. The schema only needs to be provided to avoid accesses to the schema URL.",
|
||||
"json.format.enable.desc": "Enable/disable default JSON formatter",
|
||||
"json.format.keepLines.desc" : "Keep all existing new lines when formatting.",
|
||||
"json.validate.enable.desc": "Enable/disable JSON validation.",
|
||||
"json.tracing.desc": "Traces the communication between Azure Data Studio and the JSON language server.",
|
||||
"json.colorDecorators.enable.desc": "Enables or disables color decorators",
|
||||
|
||||
@@ -188,12 +188,6 @@ Notification:
|
||||
### Item Limit
|
||||
|
||||
If the setting `resultLimit` is set, the JSON language server will limit the number of folding ranges and document symbols computed.
|
||||
When the limit is reached, a notification `json/resultLimitReached` is sent that can be shown that can be shown to the user.
|
||||
|
||||
Notification:
|
||||
- method: 'json/resultLimitReached'
|
||||
- params: a human readable string to show to the user.
|
||||
|
||||
|
||||
## Try
|
||||
|
||||
|
||||
@@ -12,10 +12,10 @@
|
||||
},
|
||||
"main": "./out/node/jsonServerMain",
|
||||
"dependencies": {
|
||||
"jsonc-parser": "^3.0.0",
|
||||
"jsonc-parser": "^3.1.0",
|
||||
"request-light": "^0.5.8",
|
||||
"vscode-json-languageservice": "^4.2.1",
|
||||
"vscode-languageserver": "^7.0.0",
|
||||
"vscode-json-languageservice": "^5.1.0",
|
||||
"vscode-languageserver": "^8.0.2-next.5",
|
||||
"vscode-uri": "^3.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
import {
|
||||
Connection,
|
||||
TextDocuments, InitializeParams, InitializeResult, NotificationType, RequestType,
|
||||
DocumentRangeFormattingRequest, Disposable, ServerCapabilities, TextDocumentSyncKind, TextEdit, DocumentFormattingRequest, TextDocumentIdentifier, FormattingOptions
|
||||
DocumentRangeFormattingRequest, Disposable, ServerCapabilities, TextDocumentSyncKind, TextEdit, DocumentFormattingRequest, TextDocumentIdentifier, FormattingOptions, Diagnostic
|
||||
} from 'vscode-languageserver';
|
||||
|
||||
import { formatError, runSafe, runSafeAsync } from './utils/runner';
|
||||
import { TextDocument, JSONDocument, JSONSchema, getLanguageService, DocumentLanguageSettings, SchemaConfiguration, ClientCapabilities, Diagnostic, Range, Position } from 'vscode-json-languageservice';
|
||||
import { runSafe, runSafeAsync } from './utils/runner';
|
||||
import { DiagnosticsSupport, registerDiagnosticsPullSupport, registerDiagnosticsPushSupport } from './utils/validation';
|
||||
import { TextDocument, JSONDocument, JSONSchema, getLanguageService, DocumentLanguageSettings, SchemaConfiguration, ClientCapabilities, Range, Position } from 'vscode-json-languageservice';
|
||||
import { getLanguageModelCache } from './languageModelCache';
|
||||
import { Utils, URI } from 'vscode-uri';
|
||||
|
||||
@@ -30,10 +31,6 @@ namespace SchemaContentChangeNotification {
|
||||
export const type: NotificationType<string | string[]> = new NotificationType('json/schemaContent');
|
||||
}
|
||||
|
||||
namespace ResultLimitReachedNotification {
|
||||
export const type: NotificationType<string> = new NotificationType('json/resultLimitReached');
|
||||
}
|
||||
|
||||
namespace ForceValidateRequest {
|
||||
export const type: RequestType<string, Diagnostic[], any> = new RequestType('json/validate');
|
||||
}
|
||||
@@ -68,7 +65,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
|
||||
|
||||
function getSchemaRequestService(handledSchemas: string[] = ['https', 'http', 'file']) {
|
||||
const builtInHandlers: { [protocol: string]: RequestService | undefined } = {};
|
||||
for (let protocol of handledSchemas) {
|
||||
for (const protocol of handledSchemas) {
|
||||
if (protocol === 'file') {
|
||||
builtInHandlers[protocol] = runtime.file;
|
||||
} else if (protocol === 'http' || protocol === 'https') {
|
||||
@@ -113,11 +110,16 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
|
||||
let resultLimit = Number.MAX_VALUE;
|
||||
let formatterMaxNumberOfEdits = Number.MAX_VALUE;
|
||||
|
||||
let diagnosticsSupport: DiagnosticsSupport | undefined;
|
||||
|
||||
|
||||
// After the server has started the client sends an initialize request. The server receives
|
||||
// in the passed params the rootPath of the workspace plus the client capabilities.
|
||||
connection.onInitialize((params: InitializeParams): InitializeResult => {
|
||||
|
||||
const handledProtocols = params.initializationOptions?.handledSchemaProtocols;
|
||||
const initializationOptions = params.initializationOptions as any || {};
|
||||
|
||||
const handledProtocols = initializationOptions?.handledSchemaProtocols;
|
||||
|
||||
languageService = getLanguageService({
|
||||
schemaRequestService: getSchemaRequestService(handledProtocols),
|
||||
@@ -139,10 +141,18 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
|
||||
}
|
||||
|
||||
clientSnippetSupport = getClientCapability('textDocument.completion.completionItem.snippetSupport', false);
|
||||
dynamicFormatterRegistration = getClientCapability('textDocument.rangeFormatting.dynamicRegistration', false) && (typeof params.initializationOptions?.provideFormatter !== 'boolean');
|
||||
dynamicFormatterRegistration = getClientCapability('textDocument.rangeFormatting.dynamicRegistration', false) && (typeof initializationOptions.provideFormatter !== 'boolean');
|
||||
foldingRangeLimitDefault = getClientCapability('textDocument.foldingRange.rangeLimit', Number.MAX_VALUE);
|
||||
hierarchicalDocumentSymbolSupport = getClientCapability('textDocument.documentSymbol.hierarchicalDocumentSymbolSupport', false);
|
||||
formatterMaxNumberOfEdits = params.initializationOptions?.customCapabilities?.rangeFormatting?.editLimit || Number.MAX_VALUE;
|
||||
formatterMaxNumberOfEdits = initializationOptions.customCapabilities?.rangeFormatting?.editLimit || Number.MAX_VALUE;
|
||||
|
||||
const supportsDiagnosticPull = getClientCapability('textDocument.diagnostic', undefined);
|
||||
if (supportsDiagnosticPull === undefined) {
|
||||
diagnosticsSupport = registerDiagnosticsPushSupport(documents, connection, runtime, validateTextDocument);
|
||||
} else {
|
||||
diagnosticsSupport = registerDiagnosticsPullSupport(documents, connection, runtime, validateTextDocument);
|
||||
}
|
||||
|
||||
const capabilities: ServerCapabilities = {
|
||||
textDocumentSync: TextDocumentSyncKind.Incremental,
|
||||
completionProvider: clientSnippetSupport ? {
|
||||
@@ -151,12 +161,17 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
|
||||
} : undefined,
|
||||
hoverProvider: true,
|
||||
documentSymbolProvider: true,
|
||||
documentRangeFormattingProvider: params.initializationOptions?.provideFormatter === true,
|
||||
documentFormattingProvider: params.initializationOptions?.provideFormatter === true,
|
||||
documentRangeFormattingProvider: initializationOptions.provideFormatter === true,
|
||||
documentFormattingProvider: initializationOptions.provideFormatter === true,
|
||||
colorProvider: {},
|
||||
foldingRangeProvider: true,
|
||||
selectionRangeProvider: true,
|
||||
documentLinkProvider: {}
|
||||
documentLinkProvider: {},
|
||||
diagnosticProvider: {
|
||||
documentSelector: null,
|
||||
interFileDependencies: false,
|
||||
workspaceDiagnostics: false
|
||||
}
|
||||
};
|
||||
|
||||
return { capabilities };
|
||||
@@ -169,6 +184,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
|
||||
json?: {
|
||||
schemas?: JSONSchemaSettings[];
|
||||
format?: { enable?: boolean };
|
||||
keepLines?: { enable?: boolean };
|
||||
validate?: { enable?: boolean };
|
||||
resultLimit?: number;
|
||||
};
|
||||
@@ -185,58 +201,20 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
|
||||
}
|
||||
|
||||
|
||||
const limitExceededWarnings = function () {
|
||||
const pendingWarnings: { [uri: string]: { features: { [name: string]: string }; timeout?: Disposable } } = {};
|
||||
|
||||
const showLimitedNotification = (uri: string, resultLimit: number) => {
|
||||
const warning = pendingWarnings[uri];
|
||||
connection.sendNotification(ResultLimitReachedNotification.type, `${Utils.basename(URI.parse(uri))}: For performance reasons, ${Object.keys(warning.features).join(' and ')} have been limited to ${resultLimit} items.`);
|
||||
warning.timeout = undefined;
|
||||
};
|
||||
|
||||
return {
|
||||
cancel(uri: string) {
|
||||
const warning = pendingWarnings[uri];
|
||||
if (warning && warning.timeout) {
|
||||
warning.timeout.dispose();
|
||||
delete pendingWarnings[uri];
|
||||
}
|
||||
},
|
||||
|
||||
onResultLimitExceeded(uri: string, resultLimit: number, name: string) {
|
||||
return () => {
|
||||
let warning = pendingWarnings[uri];
|
||||
if (warning) {
|
||||
if (!warning.timeout) {
|
||||
// already shown
|
||||
return;
|
||||
}
|
||||
warning.features[name] = name;
|
||||
warning.timeout.dispose();
|
||||
warning.timeout = runtime.timer.setTimeout(() => showLimitedNotification(uri, resultLimit), 2000);
|
||||
} else {
|
||||
warning = { features: { [name]: name } };
|
||||
warning.timeout = runtime.timer.setTimeout(() => showLimitedNotification(uri, resultLimit), 2000);
|
||||
pendingWarnings[uri] = warning;
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}();
|
||||
|
||||
let jsonConfigurationSettings: JSONSchemaSettings[] | undefined = undefined;
|
||||
let schemaAssociations: ISchemaAssociations | SchemaConfiguration[] | undefined = undefined;
|
||||
let formatterRegistrations: Thenable<Disposable>[] | null = null;
|
||||
let validateEnabled = true;
|
||||
let keepLinesEnabled = false;
|
||||
|
||||
// The settings have changed. Is send on server activation as well.
|
||||
// The settings have changed. Is sent on server activation as well.
|
||||
connection.onDidChangeConfiguration((change) => {
|
||||
let settings = <Settings>change.settings;
|
||||
if (runtime.configureHttpRequests) {
|
||||
runtime.configureHttpRequests(settings?.http?.proxy, !!settings.http?.proxyStrictSSL);
|
||||
}
|
||||
const settings = <Settings>change.settings;
|
||||
runtime.configureHttpRequests?.(settings?.http?.proxy, !!settings.http?.proxyStrictSSL);
|
||||
jsonConfigurationSettings = settings.json?.schemas;
|
||||
validateEnabled = !!settings.json?.validate?.enable;
|
||||
keepLinesEnabled = settings.json?.keepLines?.enable || false;
|
||||
updateConfiguration();
|
||||
|
||||
foldingRangeLimit = Math.trunc(Math.max(settings.json?.resultLimit || foldingRangeLimitDefault, 0));
|
||||
@@ -279,25 +257,18 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
|
||||
needsRevalidation = languageService.resetSchema(uriOrUris);
|
||||
}
|
||||
if (needsRevalidation) {
|
||||
for (const doc of documents.all()) {
|
||||
triggerValidation(doc);
|
||||
}
|
||||
diagnosticsSupport?.requestRefresh();
|
||||
}
|
||||
});
|
||||
|
||||
// Retry schema validation on all open documents
|
||||
connection.onRequest(ForceValidateRequest.type, uri => {
|
||||
return new Promise<Diagnostic[]>(resolve => {
|
||||
const document = documents.get(uri);
|
||||
if (document) {
|
||||
updateConfiguration();
|
||||
validateTextDocument(document, diagnostics => {
|
||||
resolve(diagnostics);
|
||||
});
|
||||
} else {
|
||||
resolve([]);
|
||||
}
|
||||
});
|
||||
connection.onRequest(ForceValidateRequest.type, async uri => {
|
||||
const document = documents.get(uri);
|
||||
if (document) {
|
||||
updateConfiguration();
|
||||
return await validateTextDocument(document);
|
||||
}
|
||||
return [];
|
||||
});
|
||||
|
||||
connection.onRequest(LanguageStatusRequest.type, async uri => {
|
||||
@@ -343,72 +314,16 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
|
||||
}
|
||||
languageService.configure(languageSettings);
|
||||
|
||||
// Revalidate any open text documents
|
||||
documents.all().forEach(triggerValidation);
|
||||
diagnosticsSupport?.requestRefresh();
|
||||
}
|
||||
|
||||
// The content of a text document has changed. This event is emitted
|
||||
// when the text document first opened or when its content has changed.
|
||||
documents.onDidChangeContent((change) => {
|
||||
limitExceededWarnings.cancel(change.document.uri);
|
||||
triggerValidation(change.document);
|
||||
});
|
||||
|
||||
// a document has closed: clear all diagnostics
|
||||
documents.onDidClose(event => {
|
||||
limitExceededWarnings.cancel(event.document.uri);
|
||||
cleanPendingValidation(event.document);
|
||||
connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] });
|
||||
});
|
||||
|
||||
const pendingValidationRequests: { [uri: string]: Disposable } = {};
|
||||
const validationDelayMs = 300;
|
||||
|
||||
function cleanPendingValidation(textDocument: TextDocument): void {
|
||||
const request = pendingValidationRequests[textDocument.uri];
|
||||
if (request) {
|
||||
request.dispose();
|
||||
delete pendingValidationRequests[textDocument.uri];
|
||||
}
|
||||
}
|
||||
|
||||
function triggerValidation(textDocument: TextDocument): void {
|
||||
cleanPendingValidation(textDocument);
|
||||
if (validateEnabled) {
|
||||
pendingValidationRequests[textDocument.uri] = runtime.timer.setTimeout(() => {
|
||||
delete pendingValidationRequests[textDocument.uri];
|
||||
validateTextDocument(textDocument);
|
||||
}, validationDelayMs);
|
||||
} else {
|
||||
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics: [] });
|
||||
}
|
||||
}
|
||||
|
||||
function validateTextDocument(textDocument: TextDocument, callback?: (diagnostics: Diagnostic[]) => void): void {
|
||||
const respond = (diagnostics: Diagnostic[]) => {
|
||||
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
|
||||
if (callback) {
|
||||
callback(diagnostics);
|
||||
}
|
||||
};
|
||||
async function validateTextDocument(textDocument: TextDocument): Promise<Diagnostic[]> {
|
||||
if (textDocument.getText().length === 0) {
|
||||
respond([]); // ignore empty documents
|
||||
return;
|
||||
return []; // ignore empty documents
|
||||
}
|
||||
const jsonDocument = getJSONDocument(textDocument);
|
||||
const version = textDocument.version;
|
||||
|
||||
const documentSettings: DocumentLanguageSettings = textDocument.languageId === 'jsonc' ? { comments: 'ignore', trailingCommas: 'warning' } : { comments: 'error', trailingCommas: 'error' };
|
||||
languageService.doValidation(textDocument, jsonDocument, documentSettings).then(diagnostics => {
|
||||
runtime.timer.setImmediate(() => {
|
||||
const currDocument = documents.get(textDocument.uri);
|
||||
if (currDocument && currDocument.version === version) {
|
||||
respond(diagnostics); // Send the computed diagnostics to VSCode.
|
||||
}
|
||||
});
|
||||
}, error => {
|
||||
connection.console.error(formatError(`Error while validating ${textDocument.uri}`, error));
|
||||
});
|
||||
return await languageService.doValidation(textDocument, jsonDocument, documentSettings);
|
||||
}
|
||||
|
||||
connection.onDidChangeWatchedFiles((change) => {
|
||||
@@ -420,7 +335,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
|
||||
}
|
||||
});
|
||||
if (hasChanges) {
|
||||
documents.all().forEach(triggerValidation);
|
||||
diagnosticsSupport?.requestRefresh();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -463,11 +378,10 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
|
||||
const document = documents.get(documentSymbolParams.textDocument.uri);
|
||||
if (document) {
|
||||
const jsonDocument = getJSONDocument(document);
|
||||
const onResultLimitExceeded = limitExceededWarnings.onResultLimitExceeded(document.uri, resultLimit, 'document symbols');
|
||||
if (hierarchicalDocumentSymbolSupport) {
|
||||
return languageService.findDocumentSymbols2(document, jsonDocument, { resultLimit, onResultLimitExceeded });
|
||||
return languageService.findDocumentSymbols2(document, jsonDocument, { resultLimit });
|
||||
} else {
|
||||
return languageService.findDocumentSymbols(document, jsonDocument, { resultLimit, onResultLimitExceeded });
|
||||
return languageService.findDocumentSymbols(document, jsonDocument, { resultLimit });
|
||||
}
|
||||
}
|
||||
return [];
|
||||
@@ -475,6 +389,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
|
||||
});
|
||||
|
||||
function onFormat(textDocument: TextDocumentIdentifier, range: Range | undefined, options: FormattingOptions): TextEdit[] {
|
||||
options.keepLines = keepLinesEnabled;
|
||||
const document = documents.get(textDocument.uri);
|
||||
if (document) {
|
||||
const edits = languageService.format(document, range ?? getFullRange(document), options);
|
||||
@@ -499,9 +414,9 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
|
||||
return runSafeAsync(runtime, async () => {
|
||||
const document = documents.get(params.textDocument.uri);
|
||||
if (document) {
|
||||
const onResultLimitExceeded = limitExceededWarnings.onResultLimitExceeded(document.uri, resultLimit, 'document colors');
|
||||
|
||||
const jsonDocument = getJSONDocument(document);
|
||||
return languageService.findDocumentColors(document, jsonDocument, { resultLimit, onResultLimitExceeded });
|
||||
return languageService.findDocumentColors(document, jsonDocument, { resultLimit });
|
||||
}
|
||||
return [];
|
||||
}, [], `Error while computing document colors for ${params.textDocument.uri}`, token);
|
||||
@@ -522,8 +437,7 @@ export function startServer(connection: Connection, runtime: RuntimeEnvironment)
|
||||
return runSafe(runtime, () => {
|
||||
const document = documents.get(params.textDocument.uri);
|
||||
if (document) {
|
||||
const onRangeLimitExceeded = limitExceededWarnings.onResultLimitExceeded(document.uri, foldingRangeLimit, 'folding ranges');
|
||||
return languageService.getFoldingRanges(document, { rangeLimit: foldingRangeLimit, onRangeLimitExceeded });
|
||||
return languageService.getFoldingRanges(document, { rangeLimit: foldingRangeLimit });
|
||||
}
|
||||
return null;
|
||||
}, null, `Error while computing folding ranges for ${params.textDocument.uri}`, token);
|
||||
|
||||
@@ -18,10 +18,10 @@ export function getLanguageModelCache<T>(maxEntries: number, cleanupIntervalTime
|
||||
let cleanupInterval: NodeJS.Timer | undefined = undefined;
|
||||
if (cleanupIntervalTimeInSec > 0) {
|
||||
cleanupInterval = setInterval(() => {
|
||||
let cutoffTime = Date.now() - cleanupIntervalTimeInSec * 1000;
|
||||
let uris = Object.keys(languageModels);
|
||||
for (let uri of uris) {
|
||||
let languageModelInfo = languageModels[uri];
|
||||
const cutoffTime = Date.now() - cleanupIntervalTimeInSec * 1000;
|
||||
const uris = Object.keys(languageModels);
|
||||
for (const uri of uris) {
|
||||
const languageModelInfo = languageModels[uri];
|
||||
if (languageModelInfo.cTime < cutoffTime) {
|
||||
delete languageModels[uri];
|
||||
nModels--;
|
||||
@@ -32,14 +32,14 @@ export function getLanguageModelCache<T>(maxEntries: number, cleanupIntervalTime
|
||||
|
||||
return {
|
||||
get(document: TextDocument): T {
|
||||
let version = document.version;
|
||||
let languageId = document.languageId;
|
||||
let languageModelInfo = languageModels[document.uri];
|
||||
const version = document.version;
|
||||
const languageId = document.languageId;
|
||||
const languageModelInfo = languageModels[document.uri];
|
||||
if (languageModelInfo && languageModelInfo.version === version && languageModelInfo.languageId === languageId) {
|
||||
languageModelInfo.cTime = Date.now();
|
||||
return languageModelInfo.languageModel;
|
||||
}
|
||||
let languageModel = parse(document);
|
||||
const languageModel = parse(document);
|
||||
languageModels[document.uri] = { languageModel, version, languageId, cTime: Date.now() };
|
||||
if (!languageModelInfo) {
|
||||
nModels++;
|
||||
@@ -48,8 +48,8 @@ export function getLanguageModelCache<T>(maxEntries: number, cleanupIntervalTime
|
||||
if (nModels === maxEntries) {
|
||||
let oldestTime = Number.MAX_VALUE;
|
||||
let oldestUri = null;
|
||||
for (let uri in languageModels) {
|
||||
let languageModelInfo = languageModels[uri];
|
||||
for (const uri in languageModels) {
|
||||
const languageModelInfo = languageModels[uri];
|
||||
if (languageModelInfo.cTime < oldestTime) {
|
||||
oldestUri = uri;
|
||||
oldestTime = languageModelInfo.cTime;
|
||||
@@ -64,7 +64,7 @@ export function getLanguageModelCache<T>(maxEntries: number, cleanupIntervalTime
|
||||
|
||||
},
|
||||
onDocumentRemoved(document: TextDocument) {
|
||||
let uri = document.uri;
|
||||
const uri = document.uri;
|
||||
if (languageModels[uri]) {
|
||||
delete languageModels[uri];
|
||||
nModels--;
|
||||
|
||||
@@ -8,7 +8,7 @@ import { RuntimeEnvironment } from '../jsonServer';
|
||||
|
||||
export function formatError(message: string, err: any): string {
|
||||
if (err instanceof Error) {
|
||||
let error = <Error>err;
|
||||
const error = <Error>err;
|
||||
return `${message}: ${error.message}\n${error.stack}`;
|
||||
} else if (typeof err === 'string') {
|
||||
return `${message}: ${err}`;
|
||||
@@ -47,7 +47,7 @@ export function runSafe<T, E>(runtime: RuntimeEnvironment, func: () => T, errorV
|
||||
resolve(cancelValue());
|
||||
} else {
|
||||
try {
|
||||
let result = func();
|
||||
const result = func();
|
||||
if (token.isCancellationRequested) {
|
||||
resolve(cancelValue());
|
||||
return;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* Determines if haystack ends with needle.
|
||||
*/
|
||||
export function endsWith(haystack: string, needle: string): boolean {
|
||||
let diff = haystack.length - needle.length;
|
||||
const diff = haystack.length - needle.length;
|
||||
if (diff > 0) {
|
||||
return haystack.lastIndexOf(needle) === diff;
|
||||
} else if (diff === 0) {
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CancellationToken, Connection, Diagnostic, Disposable, DocumentDiagnosticParams, DocumentDiagnosticReport, DocumentDiagnosticReportKind, TextDocuments } from 'vscode-languageserver';
|
||||
import { TextDocument } from 'vscode-json-languageservice';
|
||||
import { formatError, runSafeAsync } from './runner';
|
||||
import { RuntimeEnvironment } from '../jsonServer';
|
||||
|
||||
export type Validator = (textDocument: TextDocument) => Promise<Diagnostic[]>;
|
||||
export type DiagnosticsSupport = {
|
||||
dispose(): void;
|
||||
requestRefresh(): void;
|
||||
};
|
||||
|
||||
export function registerDiagnosticsPushSupport(documents: TextDocuments<TextDocument>, connection: Connection, runtime: RuntimeEnvironment, validate: Validator): DiagnosticsSupport {
|
||||
|
||||
const pendingValidationRequests: { [uri: string]: Disposable } = {};
|
||||
const validationDelayMs = 500;
|
||||
|
||||
const disposables: Disposable[] = [];
|
||||
|
||||
// The content of a text document has changed. This event is emitted
|
||||
// when the text document first opened or when its content has changed.
|
||||
documents.onDidChangeContent(change => {
|
||||
triggerValidation(change.document);
|
||||
}, undefined, disposables);
|
||||
|
||||
// a document has closed: clear all diagnostics
|
||||
documents.onDidClose(event => {
|
||||
cleanPendingValidation(event.document);
|
||||
connection.sendDiagnostics({ uri: event.document.uri, diagnostics: [] });
|
||||
}, undefined, disposables);
|
||||
|
||||
function cleanPendingValidation(textDocument: TextDocument): void {
|
||||
const request = pendingValidationRequests[textDocument.uri];
|
||||
if (request) {
|
||||
request.dispose();
|
||||
delete pendingValidationRequests[textDocument.uri];
|
||||
}
|
||||
}
|
||||
|
||||
function triggerValidation(textDocument: TextDocument): void {
|
||||
cleanPendingValidation(textDocument);
|
||||
const request = pendingValidationRequests[textDocument.uri] = runtime.timer.setTimeout(async () => {
|
||||
if (request === pendingValidationRequests[textDocument.uri]) {
|
||||
try {
|
||||
const diagnostics = await validate(textDocument);
|
||||
if (request === pendingValidationRequests[textDocument.uri]) {
|
||||
connection.sendDiagnostics({ uri: textDocument.uri, diagnostics });
|
||||
}
|
||||
delete pendingValidationRequests[textDocument.uri];
|
||||
} catch (e) {
|
||||
connection.console.error(formatError(`Error while validating ${textDocument.uri}`, e));
|
||||
}
|
||||
}
|
||||
}, validationDelayMs);
|
||||
}
|
||||
|
||||
return {
|
||||
requestRefresh: () => {
|
||||
documents.all().forEach(triggerValidation);
|
||||
},
|
||||
dispose: () => {
|
||||
disposables.forEach(d => d.dispose());
|
||||
disposables.length = 0;
|
||||
const keys = Object.keys(pendingValidationRequests);
|
||||
for (const key of keys) {
|
||||
pendingValidationRequests[key].dispose();
|
||||
delete pendingValidationRequests[key];
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function registerDiagnosticsPullSupport(documents: TextDocuments<TextDocument>, connection: Connection, runtime: RuntimeEnvironment, validate: Validator): DiagnosticsSupport {
|
||||
|
||||
function newDocumentDiagnosticReport(diagnostics: Diagnostic[]): DocumentDiagnosticReport {
|
||||
return {
|
||||
kind: DocumentDiagnosticReportKind.Full,
|
||||
items: diagnostics
|
||||
};
|
||||
}
|
||||
|
||||
const registration = connection.languages.diagnostics.on(async (params: DocumentDiagnosticParams, token: CancellationToken) => {
|
||||
return runSafeAsync(runtime, async () => {
|
||||
const document = documents.get(params.textDocument.uri);
|
||||
if (document) {
|
||||
return newDocumentDiagnosticReport(await validate(document));
|
||||
}
|
||||
return newDocumentDiagnosticReport([]);
|
||||
|
||||
}, newDocumentDiagnosticReport([]), `Error while computing diagnostics for ${params.textDocument.uri}`, token);
|
||||
});
|
||||
|
||||
function requestRefresh(): void {
|
||||
connection.languages.diagnostics.refresh();
|
||||
}
|
||||
|
||||
return {
|
||||
requestRefresh,
|
||||
dispose: () => {
|
||||
registration.dispose();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
@@ -12,61 +12,66 @@
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae"
|
||||
integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==
|
||||
|
||||
jsonc-parser@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.0.0.tgz#abdd785701c7e7eaca8a9ec8cf070ca51a745a22"
|
||||
integrity sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==
|
||||
jsonc-parser@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.1.0.tgz#73b8f0e5c940b83d03476bc2e51a20ef0932615d"
|
||||
integrity sha512-DRf0QjnNeCUds3xTjKlQQ3DpJD51GvDjJfnxUVWg6PZTo2otSm+slzNAxU/35hF8/oJIKoG9slq30JYOsF2azg==
|
||||
|
||||
request-light@^0.5.8:
|
||||
version "0.5.8"
|
||||
resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.5.8.tgz#8bf73a07242b9e7b601fac2fa5dc22a094abcc27"
|
||||
integrity sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg==
|
||||
|
||||
vscode-json-languageservice@^4.2.1:
|
||||
version "4.2.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-4.2.1.tgz#94b6f471ece193bf4a1ef37f6ab5cce86d50a8b4"
|
||||
integrity sha512-xGmv9QIWs2H8obGbWg+sIPI/3/pFgj/5OWBhNzs00BkYQ9UaB2F6JJaGB/2/YOZJ3BvLXQTC4Q7muqU25QgAhA==
|
||||
vscode-json-languageservice@^5.1.0:
|
||||
version "5.1.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-json-languageservice/-/vscode-json-languageservice-5.1.0.tgz#b1f197a60338cb378189fcb41489a84846724dd9"
|
||||
integrity sha512-D5612D7h/Gh4A0JmdttPveWzT9dur21WXvBHWKPdOt0sLO6ILz8vN6+IzWnvwDOVAEFTpzIAMVMZwbKZkwGGiA==
|
||||
dependencies:
|
||||
jsonc-parser "^3.0.0"
|
||||
vscode-languageserver-textdocument "^1.0.3"
|
||||
vscode-languageserver-types "^3.16.0"
|
||||
vscode-nls "^5.0.0"
|
||||
jsonc-parser "^3.1.0"
|
||||
vscode-languageserver-textdocument "^1.0.4"
|
||||
vscode-languageserver-types "^3.17.1"
|
||||
vscode-nls "^5.0.1"
|
||||
vscode-uri "^3.0.3"
|
||||
|
||||
vscode-jsonrpc@6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz#108bdb09b4400705176b957ceca9e0880e9b6d4e"
|
||||
integrity sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==
|
||||
vscode-jsonrpc@8.0.2-next.1:
|
||||
version "8.0.2-next.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.0.2-next.1.tgz#6bdc39fd194782032e34047eeefce562941259c6"
|
||||
integrity sha512-sbbvGSWja7NVBLHPGawtgezc8DHYJaP4qfr/AaJiyDapWcSFtHyPtm18+LnYMLTmB7bhOUW/lf5PeeuLpP6bKA==
|
||||
|
||||
vscode-languageserver-protocol@3.16.0:
|
||||
version "3.16.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz#34135b61a9091db972188a07d337406a3cdbe821"
|
||||
integrity sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==
|
||||
vscode-languageserver-protocol@3.17.2-next.6:
|
||||
version "3.17.2-next.6"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.2-next.6.tgz#8f1dc0fcb29366b85f623a3f9af726de433b5fcc"
|
||||
integrity sha512-WtsebNOOkWyNn4oFYoAMPC8Q/ZDoJ/K7Ja53OzTixiitvrl/RpXZETrtzH79R8P5kqCyx6VFBPb6KQILJfkDkA==
|
||||
dependencies:
|
||||
vscode-jsonrpc "6.0.0"
|
||||
vscode-languageserver-types "3.16.0"
|
||||
vscode-jsonrpc "8.0.2-next.1"
|
||||
vscode-languageserver-types "3.17.2-next.2"
|
||||
|
||||
vscode-languageserver-textdocument@^1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.3.tgz#879f2649bfa5a6e07bc8b392c23ede2dfbf43eff"
|
||||
integrity sha512-ynEGytvgTb6HVSUwPJIAZgiHQmPCx8bZ8w5um5Lz+q5DjP0Zj8wTFhQpyg8xaMvefDytw2+HH5yzqS+FhsR28A==
|
||||
vscode-languageserver-textdocument@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.4.tgz#3cd56dd14cec1d09e86c4bb04b09a246cb3df157"
|
||||
integrity sha512-/xhqXP/2A2RSs+J8JNXpiiNVvvNM0oTosNVmQnunlKvq9o4mupHOBAnnzH0lwIPKazXKvAKsVp1kr+H/K4lgoQ==
|
||||
|
||||
vscode-languageserver-types@3.16.0, vscode-languageserver-types@^3.16.0:
|
||||
version "3.16.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz#ecf393fc121ec6974b2da3efb3155644c514e247"
|
||||
integrity sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==
|
||||
vscode-languageserver-types@3.17.2-next.2:
|
||||
version "3.17.2-next.2"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2-next.2.tgz#af5d6978eee7682aab87c1419323f5b141ac6596"
|
||||
integrity sha512-TiAkLABgqkVWdAlC3XlOfdhdjIAdVU4YntPUm9kKGbXr+MGwpVnKz2KZMNBcvG0CFx8Hi8qliL0iq+ndPB720w==
|
||||
|
||||
vscode-languageserver@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-7.0.0.tgz#49b068c87cfcca93a356969d20f5d9bdd501c6b0"
|
||||
integrity sha512-60HTx5ID+fLRcgdHfmz0LDZAXYEV68fzwG0JWwEPBode9NuMYTIxuYXPg4ngO8i8+Ou0lM7y6GzaYWbiDL0drw==
|
||||
vscode-languageserver-types@^3.17.1:
|
||||
version "3.17.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.1.tgz#c2d87fa7784f8cac389deb3ff1e2d9a7bef07e16"
|
||||
integrity sha512-K3HqVRPElLZVVPtMeKlsyL9aK0GxGQpvtAUTfX4k7+iJ4mc1M+JM+zQwkgGy2LzY0f0IAafe8MKqIkJrxfGGjQ==
|
||||
|
||||
vscode-languageserver@^8.0.2-next.5:
|
||||
version "8.0.2-next.5"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver/-/vscode-languageserver-8.0.2-next.5.tgz#39a2dd4c504fb88042375e7ac706a714bdaab4e5"
|
||||
integrity sha512-2ZDb7O/4atS9mJKufPPz637z+51kCyZfgnobFW5eSrUdS3c0UB/nMS4Ng1EavYTX84GVaVMKCrmP0f2ceLmR0A==
|
||||
dependencies:
|
||||
vscode-languageserver-protocol "3.16.0"
|
||||
vscode-languageserver-protocol "3.17.2-next.6"
|
||||
|
||||
vscode-nls@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840"
|
||||
integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA==
|
||||
vscode-nls@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.1.tgz#ba23fc4d4420d25e7f886c8e83cbdcec47aa48b2"
|
||||
integrity sha512-hHQV6iig+M21lTdItKPkJAaWrxALQb/nqpVffakO4knJOh3DrU2SXOMzUzNgo1eADPzu3qSsJY1weCzvR52q9A==
|
||||
|
||||
vscode-uri@^3.0.3:
|
||||
version "3.0.3"
|
||||
|
||||
@@ -2,15 +2,54 @@
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
"@microsoft/1ds-core-js@3.2.3", "@microsoft/1ds-core-js@^3.2.3":
|
||||
version "3.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/1ds-core-js/-/1ds-core-js-3.2.3.tgz#2217d92ec8b073caa4577a13f40ea3a5c4c4d4e7"
|
||||
integrity sha512-796A8fd90oUKDRO7UXUT9BwZ3G+a9XzJj5v012FcCN/2qRhEsIV3x/0wkx2S08T4FiQEUPkB2uoYHpEjEneM7g==
|
||||
dependencies:
|
||||
"@microsoft/applicationinsights-core-js" "2.8.4"
|
||||
"@microsoft/applicationinsights-shims" "^2.0.1"
|
||||
"@microsoft/dynamicproto-js" "^1.1.6"
|
||||
|
||||
"@microsoft/1ds-post-js@^3.2.3":
|
||||
version "3.2.3"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/1ds-post-js/-/1ds-post-js-3.2.3.tgz#1fa7d51615a44f289632ae8c588007ba943db216"
|
||||
integrity sha512-tcGJQXXr2LYoBbIXPoUVe1KCF3OtBsuKDFL7BXfmNtuSGtWF0yejm6H83DrR8/cUIGMRMUP9lqNlqFGwDYiwAQ==
|
||||
dependencies:
|
||||
"@microsoft/1ds-core-js" "3.2.3"
|
||||
"@microsoft/applicationinsights-shims" "^2.0.1"
|
||||
"@microsoft/dynamicproto-js" "^1.1.6"
|
||||
|
||||
"@microsoft/applicationinsights-core-js@2.8.4":
|
||||
version "2.8.4"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-core-js/-/applicationinsights-core-js-2.8.4.tgz#607e531bb241a8920d43960f68a7c76a6f9af596"
|
||||
integrity sha512-FoA0FNOsFbJnLyTyQlYs6+HR7HMEa6nAOE6WOm9WVejBHMHQ/Bdb+hfVFi6slxwCimr/ner90jchi4/sIYdnyQ==
|
||||
dependencies:
|
||||
"@microsoft/applicationinsights-shims" "2.0.1"
|
||||
"@microsoft/dynamicproto-js" "^1.1.6"
|
||||
|
||||
"@microsoft/applicationinsights-shims@2.0.1", "@microsoft/applicationinsights-shims@^2.0.1":
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/applicationinsights-shims/-/applicationinsights-shims-2.0.1.tgz#5d72fb7aaf4056c4fda54f9d7c93ccf8ca9bcbfd"
|
||||
integrity sha512-G0MXf6R6HndRbDy9BbEj0zrLeuhwt2nsXk2zKtF0TnYo39KgYqhYC2ayIzKPTm2KAE+xzD7rgyLdZnrcRvt9WQ==
|
||||
|
||||
"@microsoft/dynamicproto-js@^1.1.6":
|
||||
version "1.1.6"
|
||||
resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.6.tgz#6fe03468862861f5f88ac4c3959a652b3797f1bc"
|
||||
integrity sha512-D1Oivw1A4bIXhzBIy3/BBPn3p2On+kpO2NiYt9shICDK7L/w+cR6FFBUsBZ05l6iqzTeL+Jm8lAYn0g6G7DmDg==
|
||||
|
||||
"@types/node@16.x":
|
||||
version "16.11.6"
|
||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae"
|
||||
integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w==
|
||||
|
||||
"@vscode/extension-telemetry@0.5.0":
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.5.0.tgz#8214171e550393d577fc56326fa986c6800b831b"
|
||||
integrity sha512-27FsgeVJvC4zVw7Ar3Ub+7vJswDt8RoBFpbgBwf8Xq/B2gaT8G6a+gkw3s2pQmjWGIqyu7TRA8e9rS8/vxv6NQ==
|
||||
"@vscode/extension-telemetry@0.6.2":
|
||||
version "0.6.2"
|
||||
resolved "https://registry.yarnpkg.com/@vscode/extension-telemetry/-/extension-telemetry-0.6.2.tgz#b86814ee680615730da94220c2b03ea9c3c14a8e"
|
||||
integrity sha512-yb/wxLuaaCRcBAZtDCjNYSisAXz3FWsSqAha5nhHcYxx2ZPdQdWuZqVXGKq0ZpHVndBWWtK6XqtpCN2/HB4S1w==
|
||||
dependencies:
|
||||
"@microsoft/1ds-core-js" "^3.2.3"
|
||||
"@microsoft/1ds-post-js" "^3.2.3"
|
||||
|
||||
balanced-match@^1.0.0:
|
||||
version "1.0.0"
|
||||
@@ -49,44 +88,44 @@ request-light@^0.5.8:
|
||||
resolved "https://registry.yarnpkg.com/request-light/-/request-light-0.5.8.tgz#8bf73a07242b9e7b601fac2fa5dc22a094abcc27"
|
||||
integrity sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg==
|
||||
|
||||
semver@^7.3.4:
|
||||
version "7.3.4"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.4.tgz#27aaa7d2e4ca76452f98d3add093a72c943edc97"
|
||||
integrity sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==
|
||||
semver@^7.3.5:
|
||||
version "7.3.7"
|
||||
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f"
|
||||
integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==
|
||||
dependencies:
|
||||
lru-cache "^6.0.0"
|
||||
|
||||
vscode-jsonrpc@6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-6.0.0.tgz#108bdb09b4400705176b957ceca9e0880e9b6d4e"
|
||||
integrity sha512-wnJA4BnEjOSyFMvjZdpiOwhSq9uDoK8e/kpRJDTaMYzwlkrhG1fwDIZI94CLsLzlCK5cIbMMtFlJlfR57Lavmg==
|
||||
vscode-jsonrpc@8.0.2-next.1:
|
||||
version "8.0.2-next.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-jsonrpc/-/vscode-jsonrpc-8.0.2-next.1.tgz#6bdc39fd194782032e34047eeefce562941259c6"
|
||||
integrity sha512-sbbvGSWja7NVBLHPGawtgezc8DHYJaP4qfr/AaJiyDapWcSFtHyPtm18+LnYMLTmB7bhOUW/lf5PeeuLpP6bKA==
|
||||
|
||||
vscode-languageclient@^7.0.0:
|
||||
version "7.0.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-7.0.0.tgz#b505c22c21ffcf96e167799757fca07a6bad0fb2"
|
||||
integrity sha512-P9AXdAPlsCgslpP9pRxYPqkNYV7Xq8300/aZDpO35j1fJm/ncize8iGswzYlcvFw5DQUx4eVk+KvfXdL0rehNg==
|
||||
vscode-languageclient@^8.0.2-next.5:
|
||||
version "8.0.2-next.5"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageclient/-/vscode-languageclient-8.0.2-next.5.tgz#3238a388585c3119e247f761b4355273cc2fd909"
|
||||
integrity sha512-g87RJLHz0XlRyk6DOTbAk4JHcj8CKggXy4JiFL7OlhETkcYzTOR8d+Qdb4GqZr37PDs1Cl21omtTNK5LyR/RQg==
|
||||
dependencies:
|
||||
minimatch "^3.0.4"
|
||||
semver "^7.3.4"
|
||||
vscode-languageserver-protocol "3.16.0"
|
||||
semver "^7.3.5"
|
||||
vscode-languageserver-protocol "3.17.2-next.6"
|
||||
|
||||
vscode-languageserver-protocol@3.16.0:
|
||||
version "3.16.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.16.0.tgz#34135b61a9091db972188a07d337406a3cdbe821"
|
||||
integrity sha512-sdeUoAawceQdgIfTI+sdcwkiK2KU+2cbEYA0agzM2uqaUy2UpnnGHtWTHVEtS0ES4zHU0eMFRGN+oQgDxlD66A==
|
||||
vscode-languageserver-protocol@3.17.2-next.6:
|
||||
version "3.17.2-next.6"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-protocol/-/vscode-languageserver-protocol-3.17.2-next.6.tgz#8f1dc0fcb29366b85f623a3f9af726de433b5fcc"
|
||||
integrity sha512-WtsebNOOkWyNn4oFYoAMPC8Q/ZDoJ/K7Ja53OzTixiitvrl/RpXZETrtzH79R8P5kqCyx6VFBPb6KQILJfkDkA==
|
||||
dependencies:
|
||||
vscode-jsonrpc "6.0.0"
|
||||
vscode-languageserver-types "3.16.0"
|
||||
vscode-jsonrpc "8.0.2-next.1"
|
||||
vscode-languageserver-types "3.17.2-next.2"
|
||||
|
||||
vscode-languageserver-types@3.16.0:
|
||||
version "3.16.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.16.0.tgz#ecf393fc121ec6974b2da3efb3155644c514e247"
|
||||
integrity sha512-k8luDIWJWyenLc5ToFQQMaSrqCHiLwyKPHKPQZ5zz21vM+vIVUSvsRpcbiECH4WR88K2XZqc4ScRcZ7nk/jbeA==
|
||||
vscode-languageserver-types@3.17.2-next.2:
|
||||
version "3.17.2-next.2"
|
||||
resolved "https://registry.yarnpkg.com/vscode-languageserver-types/-/vscode-languageserver-types-3.17.2-next.2.tgz#af5d6978eee7682aab87c1419323f5b141ac6596"
|
||||
integrity sha512-TiAkLABgqkVWdAlC3XlOfdhdjIAdVU4YntPUm9kKGbXr+MGwpVnKz2KZMNBcvG0CFx8Hi8qliL0iq+ndPB720w==
|
||||
|
||||
vscode-nls@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.0.tgz#99f0da0bd9ea7cda44e565a74c54b1f2bc257840"
|
||||
integrity sha512-u0Lw+IYlgbEJFF6/qAqG2d1jQmJl0eyAGJHoAJqr2HT4M2BNuQYSEiSE75f52pXHSJm8AlTjnLLbBFPrdz2hpA==
|
||||
vscode-nls@^5.0.1:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-5.0.1.tgz#ba23fc4d4420d25e7f886c8e83cbdcec47aa48b2"
|
||||
integrity sha512-hHQV6iig+M21lTdItKPkJAaWrxALQb/nqpVffakO4knJOh3DrU2SXOMzUzNgo1eADPzu3qSsJY1weCzvR52q9A==
|
||||
|
||||
yallist@^4.0.0:
|
||||
version "4.0.0"
|
||||
|
||||
@@ -15,9 +15,8 @@
|
||||
{ "open": "\"", "close": "\"", "notIn": ["string", "comment"] },
|
||||
{ "open": "`", "close": "`", "notIn": ["string", "comment"] }
|
||||
],
|
||||
"wordPattern": "(\"(?:[^\\\\\\\"]*(?:\\\\.)?)*\"?)|[^\\s{}\\[\\],:]+",
|
||||
"indentationRules": {
|
||||
"increaseIndentPattern": "({+(?=([^\"]*\"[^\"]*\")*[^\"}]*$))|(\\[+(?=([^\"]*\"[^\"]*\")*[^\"\\]]*$))",
|
||||
"increaseIndentPattern": "({+(?=((\\\\.|[^\"\\\\])*\"(\\\\.|[^\"\\\\])*\")*[^\"}]*)$)|(\\[+(?=((\\\\.|[^\"\\\\])*\"(\\\\.|[^\"\\\\])*\")*[^\"\\]]*)$)",
|
||||
"decreaseIndentPattern": "^\\s*[}\\]],?\\s*$"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
"git": {
|
||||
"name": "microsoft/vscode-markdown-tm-grammar",
|
||||
"repositoryUrl": "https://github.com/microsoft/vscode-markdown-tm-grammar",
|
||||
"commitHash": "b068fcb2fbfa834e695505bfb02bbcc0b4edab8b"
|
||||
"commitHash": "69d3321b4923ca2d5e8e900018887cc38b5fe04a"
|
||||
}
|
||||
},
|
||||
"license": "MIT",
|
||||
|
||||
@@ -67,6 +67,7 @@
|
||||
"meta.embedded.block.javascript": "javascript",
|
||||
"meta.embedded.block.json": "json",
|
||||
"meta.embedded.block.jsonc": "jsonc",
|
||||
"meta.embedded.block.latex": "latex",
|
||||
"meta.embedded.block.less": "less",
|
||||
"meta.embedded.block.objc": "objc",
|
||||
"meta.embedded.block.scss": "scss",
|
||||
@@ -91,8 +92,8 @@
|
||||
],
|
||||
"configurationDefaults": {
|
||||
"[markdown]": {
|
||||
"editor.unicodeHighlight.ambiguousCharacters": false,
|
||||
"editor.unicodeHighlight.invisibleCharacters": false
|
||||
"editor.unicodeHighlight.ambiguousCharacters": false,
|
||||
"editor.unicodeHighlight.invisibleCharacters": false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"If you want to provide a fix or improvement, please create a pull request against the original repository.",
|
||||
"Once accepted there, we are happy to receive an update request."
|
||||
],
|
||||
"version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/b068fcb2fbfa834e695505bfb02bbcc0b4edab8b",
|
||||
"version": "https://github.com/microsoft/vscode-markdown-tm-grammar/commit/69d3321b4923ca2d5e8e900018887cc38b5fe04a",
|
||||
"name": "Markdown",
|
||||
"scopeName": "text.html.markdown",
|
||||
"patterns": [
|
||||
@@ -2774,47 +2774,50 @@
|
||||
"5": {
|
||||
"name": "punctuation.definition.metadata.markdown"
|
||||
},
|
||||
"6": {
|
||||
"7": {
|
||||
"name": "punctuation.definition.link.markdown"
|
||||
},
|
||||
"7": {
|
||||
"8": {
|
||||
"name": "markup.underline.link.markdown"
|
||||
},
|
||||
"9": {
|
||||
"name": "punctuation.definition.link.markdown"
|
||||
},
|
||||
"10": {
|
||||
"name": "string.other.link.description.title.markdown"
|
||||
},
|
||||
"11": {
|
||||
"name": "punctuation.definition.string.begin.markdown"
|
||||
"name": "markup.underline.link.markdown"
|
||||
},
|
||||
"12": {
|
||||
"name": "punctuation.definition.string.end.markdown"
|
||||
"name": "string.other.link.description.title.markdown"
|
||||
},
|
||||
"13": {
|
||||
"name": "string.other.link.description.title.markdown"
|
||||
"name": "punctuation.definition.string.begin.markdown"
|
||||
},
|
||||
"14": {
|
||||
"name": "punctuation.definition.string.begin.markdown"
|
||||
"name": "punctuation.definition.string.end.markdown"
|
||||
},
|
||||
"15": {
|
||||
"name": "punctuation.definition.string.end.markdown"
|
||||
},
|
||||
"16": {
|
||||
"name": "string.other.link.description.title.markdown"
|
||||
},
|
||||
"17": {
|
||||
"16": {
|
||||
"name": "punctuation.definition.string.begin.markdown"
|
||||
},
|
||||
"18": {
|
||||
"17": {
|
||||
"name": "punctuation.definition.string.end.markdown"
|
||||
},
|
||||
"18": {
|
||||
"name": "string.other.link.description.title.markdown"
|
||||
},
|
||||
"19": {
|
||||
"name": "punctuation.definition.string.begin.markdown"
|
||||
},
|
||||
"20": {
|
||||
"name": "punctuation.definition.string.end.markdown"
|
||||
},
|
||||
"21": {
|
||||
"name": "punctuation.definition.metadata.markdown"
|
||||
}
|
||||
},
|
||||
"match": "(?x)\n (\\[)((?<square>[^\\[\\]\\\\]|\\\\.|\\[\\g<square>*+\\])*+)(\\])\n # Match the link text.\n (\\() # Opening paren for url\n (<?)((?<url>(?>[^\\s()]+)|\\(\\g<url>*\\))*)(>?) # The url\n [ \\t]* # Optional whitespace\n (?:\n ((\\().+?(\\))) # Match title in parens…\n | ((\").+?(\")) # or in double quotes…\n | ((').+?(')) # or in single quotes.\n )? # Title is optional\n \\s* # Optional whitespace\n (\\))\n",
|
||||
"match": "(?x)\n (\\[)((?<square>[^\\[\\]\\\\]|\\\\.|\\[\\g<square>*+\\])*+)(\\])\n # Match the link text.\n (\\() # Opening paren for url\n # The url\n [ \\t]*\n (\n (<)([^<>\\n]*)(>)\n | ((?<url>(?>[^\\s()]+)|\\(\\g<url>*\\))*)\n )\n [ \\t]*\n # The title \n (?:\n ((\\()[^()]*(\\))) # Match title in parens…\n | ((\")[^\"]*(\")) # or in double quotes…\n | ((')[^']*(')) # or in single quotes.\n )? # Title is optional\n \\s* # Optional whitespace\n (\\))\n",
|
||||
"name": "meta.link.inline.markdown"
|
||||
},
|
||||
"link-ref": {
|
||||
@@ -2838,7 +2841,7 @@
|
||||
"name": "punctuation.definition.constant.end.markdown"
|
||||
}
|
||||
},
|
||||
"match": "(\\[)((?<square>[^\\[\\]\\\\]|\\\\.|\\[\\g<square>*+\\])*+)(\\])(\\[)([^\\]]*+)(\\])",
|
||||
"match": "(?<![\\]\\\\])(\\[)((?<square>[^\\[\\]\\\\]|\\\\.|\\[\\g<square>*+\\])*+)(\\])(\\[)([^\\]]*+)(\\])",
|
||||
"name": "meta.link.reference.markdown"
|
||||
},
|
||||
"link-ref-literal": {
|
||||
@@ -2859,7 +2862,7 @@
|
||||
"name": "punctuation.definition.constant.end.markdown"
|
||||
}
|
||||
},
|
||||
"match": "(\\[)((?<square>[^\\[\\]\\\\]|\\\\.|\\[\\g<square>*+\\])*+)(\\])[ ]?(\\[)(\\])",
|
||||
"match": "(?<![\\]\\\\])(\\[)((?<square>[^\\[\\]\\\\]|\\\\.|\\[\\g<square>*+\\])*+)(\\])[ ]?(\\[)(\\])",
|
||||
"name": "meta.link.reference.literal.markdown"
|
||||
},
|
||||
"link-ref-shortcut": {
|
||||
@@ -2874,7 +2877,7 @@
|
||||
"name": "punctuation.definition.link.title.end.markdown"
|
||||
}
|
||||
},
|
||||
"match": "(\\[)(\\S+?)(\\])",
|
||||
"match": "(?<![\\]\\\\])(\\[)(\\S+?)(\\])",
|
||||
"name": "meta.link.reference.markdown"
|
||||
},
|
||||
"raw": {
|
||||
|
||||
@@ -12,5 +12,12 @@ cgmanifest.json
|
||||
yarn.lock
|
||||
preview-src/**
|
||||
webpack.config.js
|
||||
esbuild.js
|
||||
esbuild-notebook.js
|
||||
esbuild-preview.js
|
||||
.gitignore
|
||||
server/src/**
|
||||
server/extension.webpack.config.js
|
||||
server/extension-browser.webpack.config.js
|
||||
server/tsconfig.json
|
||||
server/.vscode/**
|
||||
server/node_modules/**
|
||||
|
||||
@@ -12,7 +12,7 @@ const withBrowserDefaults = require('../shared.webpack.config').browser;
|
||||
module.exports = withBrowserDefaults({
|
||||
context: __dirname,
|
||||
entry: {
|
||||
extension: './src/extension.ts'
|
||||
extension: './src/extension.browser.ts'
|
||||
}
|
||||
}, {
|
||||
configFile: 'tsconfig.browser.json'
|
||||
|
||||
@@ -8,8 +8,109 @@ import MarkdownIt from 'markdown-it';
|
||||
import type * as MarkdownItToken from 'markdown-it/lib/token';
|
||||
import type { ActivationFunction } from 'vscode-notebook-renderer';
|
||||
|
||||
const allowedHtmlTags = Object.freeze([
|
||||
'a',
|
||||
'b',
|
||||
'blockquote',
|
||||
'br',
|
||||
'button',
|
||||
'caption',
|
||||
'center',
|
||||
'code',
|
||||
'col',
|
||||
'colgroup',
|
||||
'details',
|
||||
'div',
|
||||
'em',
|
||||
'font',
|
||||
'h1',
|
||||
'h2',
|
||||
'h3',
|
||||
'h4',
|
||||
'h5',
|
||||
'h6',
|
||||
'hr',
|
||||
'i',
|
||||
'img',
|
||||
'input',
|
||||
'kbd',
|
||||
'label',
|
||||
'li',
|
||||
'ol',
|
||||
'p',
|
||||
'pre',
|
||||
'select',
|
||||
'small',
|
||||
'span',
|
||||
'strong',
|
||||
'sub',
|
||||
'summary',
|
||||
'sup',
|
||||
'table',
|
||||
'tbody',
|
||||
'td',
|
||||
'textarea',
|
||||
'tfoot',
|
||||
'th',
|
||||
'thead',
|
||||
'tr',
|
||||
'tt',
|
||||
'u',
|
||||
'ul',
|
||||
'video',
|
||||
]);
|
||||
|
||||
const allowedSvgTags = Object.freeze([
|
||||
'svg',
|
||||
'a',
|
||||
'altglyph',
|
||||
'altglyphdef',
|
||||
'altglyphitem',
|
||||
'animatecolor',
|
||||
'animatemotion',
|
||||
'animatetransform',
|
||||
'circle',
|
||||
'clippath',
|
||||
'defs',
|
||||
'desc',
|
||||
'ellipse',
|
||||
'filter',
|
||||
'font',
|
||||
'g',
|
||||
'glyph',
|
||||
'glyphref',
|
||||
'hkern',
|
||||
'image',
|
||||
'line',
|
||||
'lineargradient',
|
||||
'marker',
|
||||
'mask',
|
||||
'metadata',
|
||||
'mpath',
|
||||
'path',
|
||||
'pattern',
|
||||
'polygon',
|
||||
'polyline',
|
||||
'radialgradient',
|
||||
'rect',
|
||||
'stop',
|
||||
'style',
|
||||
'switch',
|
||||
'symbol',
|
||||
'text',
|
||||
'textpath',
|
||||
'title',
|
||||
'tref',
|
||||
'tspan',
|
||||
'view',
|
||||
'vkern',
|
||||
]);
|
||||
|
||||
const sanitizerOptions: DOMPurify.Config = {
|
||||
ALLOWED_TAGS: ['a', 'button', 'blockquote', 'code', 'div', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'img', 'input', 'label', 'li', 'p', 'pre', 'select', 'small', 'span', 'strong', 'textarea', 'ul', 'ol'],
|
||||
ALLOWED_TAGS: [
|
||||
...allowedHtmlTags,
|
||||
...allowedSvgTags,
|
||||
],
|
||||
};
|
||||
|
||||
export const activate: ActivationFunction<void> = (ctx) => {
|
||||
@@ -26,6 +127,7 @@ export const activate: ActivationFunction<void> = (ctx) => {
|
||||
markdownIt.linkify.set({ fuzzyLink: false });
|
||||
|
||||
addNamedHeaderRendering(markdownIt);
|
||||
addLinkRenderer(markdownIt);
|
||||
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
@@ -206,7 +308,9 @@ export const activate: ActivationFunction<void> = (ctx) => {
|
||||
previewNode.classList.remove('emptyMarkdownCell');
|
||||
const markdownText = outputInfo.mime.startsWith('text/x-') ? `\`\`\`${outputInfo.mime.substr(7)}\n${text}\n\`\`\``
|
||||
: (outputInfo.mime.startsWith('application/') ? `\`\`\`${outputInfo.mime.substr(12)}\n${text}\n\`\`\`` : text);
|
||||
const unsanitizedRenderedMarkdown = markdownIt.render(markdownText);
|
||||
const unsanitizedRenderedMarkdown = markdownIt.render(markdownText, {
|
||||
outputItem: outputInfo,
|
||||
});
|
||||
previewNode.innerHTML = (ctx.workspace.isTrusted
|
||||
? unsanitizedRenderedMarkdown
|
||||
: DOMPurify.sanitize(unsanitizedRenderedMarkdown, sanitizerOptions)) as string;
|
||||
@@ -225,12 +329,12 @@ function addNamedHeaderRendering(md: InstanceType<typeof MarkdownIt>): void {
|
||||
const originalHeaderOpen = md.renderer.rules.heading_open;
|
||||
md.renderer.rules.heading_open = (tokens: MarkdownItToken[], idx: number, options, env, self) => {
|
||||
const title = tokens[idx + 1].children!.reduce<string>((acc, t) => acc + t.content, '');
|
||||
let slug = slugFromHeading(title);
|
||||
let slug = slugify(title);
|
||||
|
||||
if (slugCounter.has(slug)) {
|
||||
const count = slugCounter.get(slug)!;
|
||||
slugCounter.set(slug, count + 1);
|
||||
slug = slugFromHeading(slug + '-' + (count + 1));
|
||||
slug = slugify(slug + '-' + (count + 1));
|
||||
} else {
|
||||
slugCounter.set(slug, 0);
|
||||
}
|
||||
@@ -251,9 +355,26 @@ function addNamedHeaderRendering(md: InstanceType<typeof MarkdownIt>): void {
|
||||
};
|
||||
}
|
||||
|
||||
function slugFromHeading(heading: string): string {
|
||||
function addLinkRenderer(md: MarkdownIt): void {
|
||||
const original = md.renderer.rules.link_open;
|
||||
|
||||
md.renderer.rules.link_open = (tokens: MarkdownItToken[], idx: number, options, env, self) => {
|
||||
const token = tokens[idx];
|
||||
const href = token.attrGet('href');
|
||||
if (typeof href === 'string' && href.startsWith('#')) {
|
||||
token.attrSet('href', '#' + slugify(href.slice(1)));
|
||||
}
|
||||
if (original) {
|
||||
return original(tokens, idx, options, env, self);
|
||||
} else {
|
||||
return self.renderToken(tokens, idx, options);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function slugify(text: string): string {
|
||||
const slugifiedHeading = encodeURI(
|
||||
heading.trim()
|
||||
text.trim()
|
||||
.toLowerCase()
|
||||
.replace(/\s+/g, '-') // Replace whitespace with -
|
||||
// allow-any-unicode-next-line
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
"icon": "icon.png",
|
||||
"publisher": "vscode",
|
||||
"license": "MIT",
|
||||
"aiKey": "AIF-d9b70cd4-b9f9-4d70-929b-a071c400b217",
|
||||
"aiKey": "0c6ae279ed8443289764825290e4f9e2-1a736e7c-1324-4338-be46-fc2a58ae4d14-7255",
|
||||
"engines": {
|
||||
"vscode": "^1.20.0"
|
||||
},
|
||||
@@ -16,7 +16,7 @@
|
||||
"Programming Languages"
|
||||
],
|
||||
"enabledApiProposals": [
|
||||
"textEditorDrop"
|
||||
"documentPaste"
|
||||
],
|
||||
"activationEvents": [
|
||||
"onLanguage:markdown",
|
||||
@@ -46,7 +46,7 @@
|
||||
"contributes": {
|
||||
"notebookRenderer": [
|
||||
{
|
||||
"id": "markdownItRenderer",
|
||||
"id": "vscode.markdown-it-renderer",
|
||||
"displayName": "Markdown it renderer",
|
||||
"entrypoint": "./notebook-out/index.js",
|
||||
"mimeTypes": [
|
||||
@@ -398,59 +398,116 @@
|
||||
"description": "%configuration.markdown.suggest.paths.enabled.description%",
|
||||
"scope": "resource"
|
||||
},
|
||||
"markdown.trace": {
|
||||
"markdown.trace.extension": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"off",
|
||||
"verbose"
|
||||
],
|
||||
"default": "off",
|
||||
"description": "%markdown.trace.desc%",
|
||||
"description": "%markdown.trace.extension.desc%",
|
||||
"scope": "window"
|
||||
},
|
||||
"markdown.trace.server": {
|
||||
"type": "string",
|
||||
"scope": "window",
|
||||
"enum": [
|
||||
"off",
|
||||
"messages",
|
||||
"verbose"
|
||||
],
|
||||
"default": "off",
|
||||
"description": "%markdown.trace.server.desc%"
|
||||
},
|
||||
"markdown.editor.drop.enabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"markdownDescription": "%configuration.markdown.editor.drop.enabled%",
|
||||
"scope": "resource"
|
||||
},
|
||||
"markdown.experimental.editor.pasteLinks.enabled": {
|
||||
"type": "boolean",
|
||||
"scope": "resource",
|
||||
"markdownDescription": "%configuration.markdown.editor.pasteLinks.enabled%",
|
||||
"default": true,
|
||||
"tags": [
|
||||
"experimental"
|
||||
]
|
||||
},
|
||||
"markdown.experimental.validate.enabled": {
|
||||
"type": "boolean",
|
||||
"scope": "resource",
|
||||
"description": "%configuration.markdown.experimental.validate.enabled.description%",
|
||||
"default": false
|
||||
},
|
||||
"markdown.experimental.validate.referenceLinks": {
|
||||
"type": "string",
|
||||
"scope": "resource",
|
||||
"description": "%configuration.markdown.experimental.validate.referenceLinks.description%",
|
||||
"default": "warning",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
"default": false,
|
||||
"tags": [
|
||||
"experimental"
|
||||
]
|
||||
},
|
||||
"markdown.experimental.validate.headerLinks": {
|
||||
"markdown.experimental.validate.referenceLinks.enabled": {
|
||||
"type": "string",
|
||||
"scope": "resource",
|
||||
"description": "%configuration.markdown.experimental.validate.headerLinks.description%",
|
||||
"markdownDescription": "%configuration.markdown.experimental.validate.referenceLinks.enabled.description%",
|
||||
"default": "warning",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"tags": [
|
||||
"experimental"
|
||||
]
|
||||
},
|
||||
"markdown.experimental.validate.fileLinks": {
|
||||
"markdown.experimental.validate.fragmentLinks.enabled": {
|
||||
"type": "string",
|
||||
"scope": "resource",
|
||||
"description": "%configuration.markdown.experimental.validate.fileLinks.description%",
|
||||
"markdownDescription": "%configuration.markdown.experimental.validate.fragmentLinks.enabled.description%",
|
||||
"default": "warning",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"tags": [
|
||||
"experimental"
|
||||
]
|
||||
},
|
||||
"markdown.experimental.validate.fileLinks.enabled": {
|
||||
"type": "string",
|
||||
"scope": "resource",
|
||||
"markdownDescription": "%configuration.markdown.experimental.validate.fileLinks.enabled.description%",
|
||||
"default": "warning",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"tags": [
|
||||
"experimental"
|
||||
]
|
||||
},
|
||||
"markdown.experimental.validate.fileLinks.markdownFragmentLinks": {
|
||||
"type": "string",
|
||||
"scope": "resource",
|
||||
"markdownDescription": "%configuration.markdown.experimental.validate.fileLinks.markdownFragmentLinks.description%",
|
||||
"default": "ignore",
|
||||
"enum": [
|
||||
"ignore",
|
||||
"warning",
|
||||
"error"
|
||||
],
|
||||
"tags": [
|
||||
"experimental"
|
||||
]
|
||||
},
|
||||
"markdown.experimental.validate.ignoreLinks": {
|
||||
"type": "array",
|
||||
"scope": "resource",
|
||||
"markdownDescription": "%configuration.markdown.experimental.validate.ignoreLinks.description%",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"tags": [
|
||||
"experimental"
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -488,8 +545,8 @@
|
||||
]
|
||||
},
|
||||
"scripts": {
|
||||
"compile": "gulp compile-extension:markdown-language-features && npm run build-preview && npm run build-notebook",
|
||||
"watch": "npm run build-preview && gulp watch-extension:markdown-language-features",
|
||||
"compile": "gulp compile-extension:markdown-language-features-languageService && gulp compile-extension:markdown-language-features-server && gulp compile-extension:markdown-language-features && npm run build-preview && npm run build-notebook",
|
||||
"watch": "npm run build-preview && gulp watch-extension:markdown-language-features watch-extension:markdown-language-features-languageService watch-extension:markdown-language-features-server",
|
||||
"vscode:prepublish": "npm run build-ext && npm run build-preview",
|
||||
"build-ext": "node ../../node_modules/gulp/bin/gulp.js --gulpfile ../../build/gulpfile.extensions.js compile-extension:markdown-language-features ./tsconfig.json",
|
||||
"build-notebook": "node ./esbuild-notebook",
|
||||
@@ -498,12 +555,14 @@
|
||||
"watch-web": "npx webpack-cli --config extension-browser.webpack.config --mode none --watch --info-verbosity verbose"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vscode/extension-telemetry": "0.4.10",
|
||||
"dompurify": "^2.3.1",
|
||||
"@vscode/extension-telemetry": "0.6.2",
|
||||
"dompurify": "^2.3.3",
|
||||
"highlight.js": "^11.4.0",
|
||||
"markdown-it": "^12.3.2",
|
||||
"markdown-it-front-matter": "^0.2.1",
|
||||
"morphdom": "^2.6.1",
|
||||
"picomatch": "^2.3.1",
|
||||
"vscode-languageclient": "^8.0.1",
|
||||
"vscode-languageserver-textdocument": "^1.0.4",
|
||||
"vscode-nls": "^5.0.0",
|
||||
"vscode-uri": "^3.0.3"
|
||||
@@ -512,9 +571,12 @@
|
||||
"@types/dompurify": "^2.3.1",
|
||||
"@types/lodash.throttle": "^4.1.3",
|
||||
"@types/markdown-it": "12.2.3",
|
||||
"@types/picomatch": "^2.3.0",
|
||||
"@types/vscode-notebook-renderer": "^1.60.0",
|
||||
"@types/vscode-webview": "^1.57.0",
|
||||
"lodash.throttle": "^4.1.1"
|
||||
"lodash.throttle": "^4.1.1",
|
||||
"vscode-languageserver-types": "^3.17.2",
|
||||
"vscode-markdown-languageservice": "^0.0.0-alpha.10"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
||||
@@ -17,7 +17,8 @@
|
||||
"markdown.showSource.title": "Show Source",
|
||||
"markdown.styles.dec": "A list of URLs or local paths to CSS style sheets to use from the Markdown preview. Relative paths are interpreted relative to the folder open in the Explorer. If there is no open folder, they are interpreted relative to the location of the Markdown file. All '\\' need to be written as '\\\\'.",
|
||||
"markdown.showPreviewSecuritySelector.title": "Change Preview Security Settings",
|
||||
"markdown.trace.desc": "Enable debug logging for the Markdown extension.",
|
||||
"markdown.trace.extension.desc": "Enable debug logging for the Markdown extension.",
|
||||
"markdown.trace.server.desc": "Traces the communication between VS Code and the Markdown language server.",
|
||||
"markdown.preview.refresh.title": "Refresh Preview",
|
||||
"markdown.preview.toggleLock.title": "Toggle Preview Locking",
|
||||
"markdown.findAllFileReferences": "Find File References",
|
||||
@@ -28,10 +29,13 @@
|
||||
"configuration.markdown.links.openLocation.currentGroup": "Open links in the active editor group.",
|
||||
"configuration.markdown.links.openLocation.beside": "Open links beside the active editor.",
|
||||
"configuration.markdown.suggest.paths.enabled.description": "Enable/disable path suggestions for markdown links",
|
||||
"configuration.markdown.editor.drop.enabled": "Enable/disable dropping into the markdown editor to insert shift. Requires enabling `#workbenck.experimental.editor.dropIntoEditor.enabled#`.",
|
||||
"configuration.markdown.editor.drop.enabled": "Enable/disable dropping into the markdown editor to insert shift. Requires enabling `#editor.dropIntoEditor.enabled#`.",
|
||||
"configuration.markdown.editor.pasteLinks.enabled": "Enable/disable pasting files into a Markdown editor inserts Markdown links. Requires enabling `#editor.experimental.pasteActions.enabled#`.",
|
||||
"configuration.markdown.experimental.validate.enabled.description": "Enable/disable all error reporting in Markdown files.",
|
||||
"configuration.markdown.experimental.validate.referenceLinks.description": "Validate reference links in Markdown files, e.g. `[link][ref]`. Requires enabling `#markdown.experimental.validate.enabled#`.",
|
||||
"configuration.markdown.experimental.validate.headerLinks.description": "Validate links to headers in Markdown files, e.g. `[link](#header)`. Requires enabling `#markdown.experimental.validate.enabled#`.",
|
||||
"configuration.markdown.experimental.validate.fileLinks.description": "Validate links to other files in Markdown files, e.g. `[link](/path/to/file.md)`. This checks that the target files exists. Requires enabling `#markdown.experimental.validate.enabled#`.",
|
||||
"configuration.markdown.experimental.validate.referenceLinks.enabled.description": "Validate reference links in Markdown files, e.g. `[link][ref]`. Requires enabling `#markdown.experimental.validate.enabled#`.",
|
||||
"configuration.markdown.experimental.validate.fragmentLinks.enabled.description": "Validate fragment links to headers in the current Markdown file, e.g. `[link](#header)`. Requires enabling `#markdown.experimental.validate.enabled#`.",
|
||||
"configuration.markdown.experimental.validate.fileLinks.enabled.description": "Validate links to other files in Markdown files, e.g. `[link](/path/to/file.md)`. This checks that the target files exists. Requires enabling `#markdown.experimental.validate.enabled#`.",
|
||||
"configuration.markdown.experimental.validate.fileLinks.markdownFragmentLinks.description": "Validate the fragment part of links to headers in other files in Markdown files, e.g. `[link](/path/to/file.md#header)`. Inherits the setting value from `#markdown.experimental.validate.fragmentLinks.enabled#` by default.",
|
||||
"configuration.markdown.experimental.validate.ignoreLinks.description": "Configure links that should not be validated. For example `/about` would not validate the link `[about](/about)`, while the glob `/assets/**/*.svg` would let you skip validation for any link to `.svg` files under the `assets` directory.",
|
||||
"workspaceTrust": "Required for loading styles configured in the workspace."
|
||||
}
|
||||
|
||||
@@ -29,9 +29,7 @@ export class StyleLoadingMonitor {
|
||||
return;
|
||||
}
|
||||
this.finishedLoading = true;
|
||||
if (this.poster) {
|
||||
this.poster.postMessage('previewStyleLoadError', { unloadedStyles: this.unloadedStyles });
|
||||
}
|
||||
this.poster?.postMessage('previewStyleLoadError', { unloadedStyles: this.unloadedStyles });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
.vscode/
|
||||
.github/
|
||||
out/test/
|
||||
src/
|
||||
.eslintrc.js
|
||||
.gitignore
|
||||
tsconfig*.json
|
||||
*.tsbuildinfo
|
||||
*.map
|
||||
example.cjs
|
||||
CODE_OF_CONDUCT.md
|
||||
SECURITY.md
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user