diff --git a/.eslintrc.json b/.eslintrc.json index a54964c17b6..63fb6b80ac5 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -220,6 +220,19 @@ "**/vs/platform/*/test/common/**" ] }, + { + "target": "**/vs/platform/*/test/browser/**", + "restrictions": [ + "assert", + "sinon", + "vs/nls", + "**/vs/base/{common,browser}/**", + "**/vs/base/parts/*/{common,browser}/**", + "**/vs/base/test/{common,browser}/**", + "**/vs/platform/*/{common,browser}/**", + "**/vs/platform/*/test/{common,browser}/**" + ] + }, { "target": "**/vs/platform/*/browser/**", "restrictions": [ diff --git a/.vscode/notebooks/my-work.github-issues b/.vscode/notebooks/my-work.github-issues index 77ca0e0443b..082d44887c8 100644 --- a/.vscode/notebooks/my-work.github-issues +++ b/.vscode/notebooks/my-work.github-issues @@ -7,11 +7,11 @@ { "kind": 2, "language": "github-issues", - "value": "// list of repos we work in\n$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog\n\n// current milestone name\n$milestone=milestone:\"May 2021\"" + "value": "// list of repos we work in\n$repos=repo:microsoft/vscode repo:microsoft/vscode-remote-release repo:microsoft/vscode-js-debug repo:microsoft/vscode-pull-request-github repo:microsoft/vscode-github-issue-notebooks repo:microsoft/vscode-internalbacklog\n\n// current milestone name\n$milestone=milestone:\"June 2021\"" }, { "kind": 1, - "language": "github-issues", + "language": "markdown", "value": "## Milestone Work" }, { @@ -21,7 +21,7 @@ }, { "kind": 1, - "language": "github-issues", + "language": "markdown", "value": "## Bugs, Debt, Features..." }, { diff --git a/build/package.json b/build/package.json index 6e12a9c4abc..f683c3241dc 100644 --- a/build/package.json +++ b/build/package.json @@ -42,7 +42,7 @@ "colors": "^1.4.0", "commander": "^7.0.0", "electron-osx-sign": "^0.4.16", - "esbuild": "^0.12.1", + "esbuild": "^0.12.6", "fs-extra": "^9.1.0", "got": "11.8.1", "iconv-lite-umd": "0.6.8", diff --git a/build/yarn.lock b/build/yarn.lock index 07536a575ac..b0449db06ee 100644 --- a/build/yarn.lock +++ b/build/yarn.lock @@ -992,10 +992,10 @@ entities@^1.1.1, entities@~1.1.1: resolved "https://registry.yarnpkg.com/entities/-/entities-1.1.2.tgz#bdfa735299664dfafd34529ed4f8522a275fea56" integrity sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w== -esbuild@^0.12.1: - version "0.12.1" - resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.1.tgz#f652d5b3b9432dbb42fc2c034ddd62360296e03d" - integrity sha512-WfQ00MKm/Y4ysz1u9PCUAsV66k5lbrcEvS6aG9jhBIavpB94FBdaWeBkaZXxCZB4w+oqh+j4ozJFWnnFprOXbg== +esbuild@^0.12.6: + version "0.12.6" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.12.6.tgz#85bc755c7cf3005d4f34b4f10f98049ce0ee67ce" + integrity sha512-RDvVLvAjsq/kIZJoneMiUOH7EE7t2QaW7T3Q7EdQij14+bZbDq5sndb0tTanmHIFSqZVMBMMyqzVHkS3dJobeA== eslint-scope@^5.0.0: version "5.0.0" diff --git a/extensions/configuration-editing/schemas/devContainer.schema.generated.json b/extensions/configuration-editing/schemas/devContainer.schema.generated.json index 095a0e355f1..112b4e48279 100644 --- a/extensions/configuration-editing/schemas/devContainer.schema.generated.json +++ b/extensions/configuration-editing/schemas/devContainer.schema.generated.json @@ -357,6 +357,28 @@ "type": "object", "additionalProperties": true, "description": "Codespaces-specific configuration." + }, + "hostRequirements": { + "type": "object", + "description": "Host hardware requirements.", + "properties": { + "cpus": { + "type": "integer", + "minimum": 1, + "description": "Number of required CPUs." + }, + "memory": { + "type": "string", + "pattern": "^\\d+([tgmk]b)?$", + "description": "Amount of required RAM in bytes. Supports units tb, gb, mb and kb." + }, + "storage": { + "type": "string", + "pattern": "^\\d+([tgmk]b)?$", + "description": "Amount of required disk space in bytes. Supports units tb, gb, mb and kb." + } + }, + "additionalProperties": false } }, "required": [ @@ -714,6 +736,28 @@ "type": "object", "additionalProperties": true, "description": "Codespaces-specific configuration." + }, + "hostRequirements": { + "type": "object", + "description": "Host hardware requirements.", + "properties": { + "cpus": { + "type": "integer", + "minimum": 1, + "description": "Number of required CPUs." + }, + "memory": { + "type": "string", + "pattern": "^\\d+([tgmk]b)?$", + "description": "Amount of required RAM in bytes. Supports units tb, gb, mb and kb." + }, + "storage": { + "type": "string", + "pattern": "^\\d+([tgmk]b)?$", + "description": "Amount of required disk space in bytes. Supports units tb, gb, mb and kb." + } + }, + "additionalProperties": false } }, "required": [ @@ -1047,6 +1091,28 @@ "type": "object", "additionalProperties": true, "description": "Codespaces-specific configuration." + }, + "hostRequirements": { + "type": "object", + "description": "Host hardware requirements.", + "properties": { + "cpus": { + "type": "integer", + "minimum": 1, + "description": "Number of required CPUs." + }, + "memory": { + "type": "string", + "pattern": "^\\d+([tgmk]b)?$", + "description": "Amount of required RAM in bytes. Supports units tb, gb, mb and kb." + }, + "storage": { + "type": "string", + "pattern": "^\\d+([tgmk]b)?$", + "description": "Amount of required disk space in bytes. Supports units tb, gb, mb and kb." + } + }, + "additionalProperties": false } }, "required": [ @@ -1346,6 +1412,28 @@ "type": "object", "additionalProperties": true, "description": "Codespaces-specific configuration." + }, + "hostRequirements": { + "type": "object", + "description": "Host hardware requirements.", + "properties": { + "cpus": { + "type": "integer", + "minimum": 1, + "description": "Number of required CPUs." + }, + "memory": { + "type": "string", + "pattern": "^\\d+([tgmk]b)?$", + "description": "Amount of required RAM in bytes. Supports units tb, gb, mb and kb." + }, + "storage": { + "type": "string", + "pattern": "^\\d+([tgmk]b)?$", + "description": "Amount of required disk space in bytes. Supports units tb, gb, mb and kb." + } + }, + "additionalProperties": false } }, "required": [ @@ -1614,6 +1702,28 @@ "type": "object", "additionalProperties": true, "description": "Codespaces-specific configuration." + }, + "hostRequirements": { + "type": "object", + "description": "Host hardware requirements.", + "properties": { + "cpus": { + "type": "integer", + "minimum": 1, + "description": "Number of required CPUs." + }, + "memory": { + "type": "string", + "pattern": "^\\d+([tgmk]b)?$", + "description": "Amount of required RAM in bytes. Supports units tb, gb, mb and kb." + }, + "storage": { + "type": "string", + "pattern": "^\\d+([tgmk]b)?$", + "description": "Amount of required disk space in bytes. Supports units tb, gb, mb and kb." + } + }, + "additionalProperties": false } }, "additionalProperties": false diff --git a/extensions/configuration-editing/schemas/devContainer.schema.src.json b/extensions/configuration-editing/schemas/devContainer.schema.src.json index 3d1536fa743..14c13a958b3 100644 --- a/extensions/configuration-editing/schemas/devContainer.schema.src.json +++ b/extensions/configuration-editing/schemas/devContainer.schema.src.json @@ -256,6 +256,32 @@ "type": "object", "additionalProperties": true, "description": "Codespaces-specific configuration." + }, + "hostRequirements": { + "type": "object", + "description": "Host hardware requirements.", + "allOf": [ + { + "type": "object", + "properties": { + "cpus": { + "type": "integer", + "minimum": 1, + "description": "Number of required CPUs." + }, + "memory": { + "type": "string", + "pattern": "^\\d+([tgmk]b)?$", + "description": "Amount of required RAM in bytes. Supports units tb, gb, mb and kb." + }, + "storage": { + "type": "string", + "pattern": "^\\d+([tgmk]b)?$", + "description": "Amount of required disk space in bytes. Supports units tb, gb, mb and kb." + } + } + } + ] } } }, diff --git a/extensions/extension-editing/src/extensionLinter.ts b/extensions/extension-editing/src/extensionLinter.ts index 3434bfbb4dc..0a66243c6d7 100644 --- a/extensions/extension-editing/src/extensionLinter.ts +++ b/extensions/extension-editing/src/extensionLinter.ts @@ -59,6 +59,7 @@ export class ExtensionLinter { private readmeQ = new Set(); private timer: NodeJS.Timer | undefined; private markdownIt: MarkdownItType.MarkdownIt | undefined; + private parse5: typeof import('parse5') | undefined; constructor() { this.disposables.push( @@ -202,8 +203,10 @@ export class ExtensionLinter { let svgStart: Diagnostic; for (const tnp of tokensAndPositions) { if (tnp.token.type === 'text' && tnp.token.content) { - const parse5 = await import('parse5'); - const parser = new parse5.SAXParser({ locationInfo: true }); + if (!this.parse5) { + this.parse5 = await import('parse5'); + } + const parser = new this.parse5.SAXParser({ locationInfo: true }); parser.on('startTag', (name, attrs, _selfClosing, location) => { if (name === 'img') { const src = attrs.find(a => a.name === 'src'); diff --git a/extensions/theme-defaults/themes/dark_plus.json b/extensions/theme-defaults/themes/dark_plus.json index 4fd89793214..e928b63cfb2 100644 --- a/extensions/theme-defaults/themes/dark_plus.json +++ b/extensions/theme-defaults/themes/dark_plus.json @@ -91,7 +91,8 @@ "variable", "meta.definition.variable.name", "support.variable", - "entity.name.variable" + "entity.name.variable", + "constant.other.placeholder", // placeholders in strings ], "settings": { "foreground": "#9CDCFE" diff --git a/extensions/theme-defaults/themes/light_plus.json b/extensions/theme-defaults/themes/light_plus.json index cbae5efd227..62f15133ea3 100644 --- a/extensions/theme-defaults/themes/light_plus.json +++ b/extensions/theme-defaults/themes/light_plus.json @@ -91,7 +91,9 @@ "variable", "meta.definition.variable.name", "support.variable", - "entity.name.variable" + "entity.name.variable", + "constant.other.placeholder", // placeholders in strings + ], "settings": { "foreground": "#001080" diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_c.json b/extensions/vscode-colorize-tests/test/colorize-results/test_c.json index 30fd5393c47..0aa8431e452 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_c.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_c.json @@ -641,8 +641,8 @@ "c": "%f%f%f", "t": "source.c meta.block.c meta.function-call.c string.quoted.double.c constant.other.placeholder.c", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.placeholder: #9CDCFE", + "light_plus": "constant.other.placeholder: #001080", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178" @@ -1499,8 +1499,8 @@ "c": "%.2f", "t": "source.c meta.block.c meta.function-call.c string.quoted.double.c constant.other.placeholder.c", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.placeholder: #9CDCFE", + "light_plus": "constant.other.placeholder: #001080", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178" @@ -1521,8 +1521,8 @@ "c": "%.2f", "t": "source.c meta.block.c meta.function-call.c string.quoted.double.c constant.other.placeholder.c", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.placeholder: #9CDCFE", + "light_plus": "constant.other.placeholder: #001080", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178" @@ -1972,8 +1972,8 @@ "c": "%.2f", "t": "source.c meta.block.c meta.function-call.c string.quoted.double.c constant.other.placeholder.c", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.placeholder: #9CDCFE", + "light_plus": "constant.other.placeholder: #001080", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178" @@ -1994,8 +1994,8 @@ "c": "%.2f", "t": "source.c meta.block.c meta.function-call.c string.quoted.double.c constant.other.placeholder.c", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.placeholder: #9CDCFE", + "light_plus": "constant.other.placeholder: #001080", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178" @@ -2500,8 +2500,8 @@ "c": "%.2f", "t": "source.c meta.block.c meta.function-call.c string.quoted.double.c constant.other.placeholder.c", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.placeholder: #9CDCFE", + "light_plus": "constant.other.placeholder: #001080", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178" @@ -2522,8 +2522,8 @@ "c": "%.2f", "t": "source.c meta.block.c meta.function-call.c string.quoted.double.c constant.other.placeholder.c", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.placeholder: #9CDCFE", + "light_plus": "constant.other.placeholder: #001080", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178" @@ -2544,8 +2544,8 @@ "c": "%.2f", "t": "source.c meta.block.c meta.function-call.c string.quoted.double.c constant.other.placeholder.c", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.placeholder: #9CDCFE", + "light_plus": "constant.other.placeholder: #001080", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178" @@ -2566,8 +2566,8 @@ "c": "%.2f", "t": "source.c meta.block.c meta.function-call.c string.quoted.double.c constant.other.placeholder.c", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.placeholder: #9CDCFE", + "light_plus": "constant.other.placeholder: #001080", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178" @@ -2793,4 +2793,4 @@ "hc_black": "default: #FFFFFF" } } -] +] \ No newline at end of file diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_cc.json b/extensions/vscode-colorize-tests/test/colorize-results/test_cc.json index cb2fb192e1d..4b04cef1c5d 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_cc.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_cc.json @@ -124,8 +124,8 @@ "c": "%d", "t": "source.cpp string.quoted.double.cpp constant.other.placeholder", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.placeholder: #9CDCFE", + "light_plus": "constant.other.placeholder: #001080", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178" @@ -432,8 +432,8 @@ "c": "%d", "t": "source.cpp string.quoted.double.cpp constant.other.placeholder", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.placeholder: #9CDCFE", + "light_plus": "constant.other.placeholder: #001080", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178" @@ -1466,8 +1466,8 @@ "c": "%s", "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp string.quoted.double.cpp constant.other.placeholder", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.placeholder: #9CDCFE", + "light_plus": "constant.other.placeholder: #001080", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178" @@ -1488,8 +1488,8 @@ "c": "%s", "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp string.quoted.double.cpp constant.other.placeholder", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.placeholder: #9CDCFE", + "light_plus": "constant.other.placeholder: #001080", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178" diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_cu.json b/extensions/vscode-colorize-tests/test/colorize-results/test_cu.json index 21c8a0613c3..6a652e7a3e9 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_cu.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_cu.json @@ -806,8 +806,8 @@ "c": "%s", "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp constant.other.placeholder", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.placeholder: #9CDCFE", + "light_plus": "constant.other.placeholder: #001080", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178" @@ -839,8 +839,8 @@ "c": "%s", "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp constant.other.placeholder", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.placeholder: #9CDCFE", + "light_plus": "constant.other.placeholder: #001080", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178" @@ -861,8 +861,8 @@ "c": "%d", "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp constant.other.placeholder", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.placeholder: #9CDCFE", + "light_plus": "constant.other.placeholder: #001080", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178" @@ -1752,8 +1752,8 @@ "c": "%s", "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp constant.other.placeholder", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.placeholder: #9CDCFE", + "light_plus": "constant.other.placeholder: #001080", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178" @@ -1785,8 +1785,8 @@ "c": "%x", "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp constant.other.placeholder", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.placeholder: #9CDCFE", + "light_plus": "constant.other.placeholder: #001080", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178" @@ -1807,8 +1807,8 @@ "c": "%s", "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp constant.other.placeholder", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.placeholder: #9CDCFE", + "light_plus": "constant.other.placeholder: #001080", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178" @@ -1829,8 +1829,8 @@ "c": "%d", "t": "source.cuda-cpp meta.preprocessor.macro.cuda-cpp meta.block.cpp meta.block.cpp string.quoted.double.cpp constant.other.placeholder", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.placeholder: #9CDCFE", + "light_plus": "constant.other.placeholder: #001080", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178" @@ -11344,8 +11344,8 @@ "c": "%u", "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp string.quoted.double.cuda-cpp constant.other.placeholder", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.placeholder: #9CDCFE", + "light_plus": "constant.other.placeholder: #001080", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178" @@ -11366,8 +11366,8 @@ "c": "%u", "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp string.quoted.double.cuda-cpp constant.other.placeholder", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.placeholder: #9CDCFE", + "light_plus": "constant.other.placeholder: #001080", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178" @@ -11388,8 +11388,8 @@ "c": "%u", "t": "source.cuda-cpp meta.function.definition.cuda-cpp meta.body.function.definition.cuda-cpp meta.block.cuda-cpp meta.block.cuda-cpp string.quoted.double.cuda-cpp constant.other.placeholder", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.placeholder: #9CDCFE", + "light_plus": "constant.other.placeholder: #001080", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178" diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_go.json b/extensions/vscode-colorize-tests/test/colorize-results/test_go.json index 14cd6ef4230..73169efcc2c 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_go.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_go.json @@ -1257,8 +1257,8 @@ "c": "%s", "t": "source.go string.quoted.double.go constant.other.placeholder.go", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.placeholder: #9CDCFE", + "light_plus": "constant.other.placeholder: #001080", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178" @@ -1279,8 +1279,8 @@ "c": "%s", "t": "source.go string.quoted.double.go constant.other.placeholder.go", "r": { - "dark_plus": "string: #CE9178", - "light_plus": "string: #A31515", + "dark_plus": "constant.other.placeholder: #9CDCFE", + "light_plus": "constant.other.placeholder: #001080", "dark_vs": "string: #CE9178", "light_vs": "string: #A31515", "hc_black": "string: #CE9178" diff --git a/package.json b/package.json index e37c72176bb..86e209c5cac 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", - "version": "1.57.0", - "distro": "3177d279bd5ffa543013fb6e96d20304feb7723f", + "version": "1.58.0", + "distro": "33b93a117b5bff11c3fe9d4a6fa49e9895d38c08", "author": { "name": "Microsoft Corporation" }, diff --git a/scripts/test-integration.bat b/scripts/test-integration.bat index 0ce9c1ba30b..a28bb307fbd 100644 --- a/scripts/test-integration.bat +++ b/scripts/test-integration.bat @@ -46,7 +46,7 @@ if %errorlevel% neq 0 exit /b %errorlevel% :: Tests in the extension host -set ALL_PLATFORMS_API_TESTS_EXTRA_ARGS=--disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --no-cached-data --disable-updates --disable-keytar --disable-extensions --user-data-dir=%VSCODEUSERDATADIR% +set ALL_PLATFORMS_API_TESTS_EXTRA_ARGS=--disable-telemetry --crash-reporter-directory=%VSCODECRASHDIR% --no-cached-data --disable-updates --disable-keytar --disable-extensions --disable-workspace-trust --user-data-dir=%VSCODEUSERDATADIR% call "%INTEGRATION_TEST_ELECTRON_PATH%" %~dp0\..\extensions\vscode-api-tests\testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=%~dp0\..\extensions\vscode-api-tests --extensionTestsPath=%~dp0\..\extensions\vscode-api-tests\out\singlefolder-tests %ALL_PLATFORMS_API_TESTS_EXTRA_ARGS% if %errorlevel% neq 0 exit /b %errorlevel% diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh index 0b2fe97b17d..e147156da33 100755 --- a/scripts/test-integration.sh +++ b/scripts/test-integration.sh @@ -62,7 +62,7 @@ after_suite # Tests in the extension host -ALL_PLATFORMS_API_TESTS_EXTRA_ARGS="--disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-keytar --disable-extensions --user-data-dir=$VSCODEUSERDATADIR" +ALL_PLATFORMS_API_TESTS_EXTRA_ARGS="--disable-telemetry --crash-reporter-directory=$VSCODECRASHDIR --no-cached-data --disable-updates --disable-keytar --disable-extensions --disable-workspace-trust --user-data-dir=$VSCODEUSERDATADIR" "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_EXTRA_ARGS $ROOT/extensions/vscode-api-tests/testWorkspace --enable-proposed-api=vscode.vscode-api-tests --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/singlefolder-tests $ALL_PLATFORMS_API_TESTS_EXTRA_ARGS after_suite diff --git a/src/vs/base/browser/ui/dialog/dialog.ts b/src/vs/base/browser/ui/dialog/dialog.ts index 6df00351bfc..84757ba8cc8 100644 --- a/src/vs/base/browser/ui/dialog/dialog.ts +++ b/src/vs/base/browser/ui/dialog/dialog.ts @@ -87,7 +87,7 @@ export class Dialog extends Disposable { private readonly inputs: InputBox[]; private readonly buttons: string[]; - constructor(private container: HTMLElement, private message: string, buttons: string[], private options: IDialogOptions) { + constructor(private container: HTMLElement, private message: string, buttons: string[] | undefined, private options: IDialogOptions) { super(); this.modalElement = this.container.appendChild($(`.monaco-dialog-modal-block.dimmed`)); @@ -96,7 +96,7 @@ export class Dialog extends Disposable { this.element.setAttribute('role', 'dialog'); hide(this.element); - this.buttons = buttons.length ? buttons : [nls.localize('ok', "OK")]; // If no button is provided, default to OK + this.buttons = Array.isArray(buttons) && buttons.length ? buttons : [nls.localize('ok', "OK")]; // If no button is provided, default to OK const buttonsRowElement = this.element.appendChild($('.dialog-buttons-row')); this.buttonsContainer = buttonsRowElement.appendChild($('.dialog-buttons')); diff --git a/src/vs/base/common/diff/diff.ts b/src/vs/base/common/diff/diff.ts index a9903ece9a8..63c095d3078 100644 --- a/src/vs/base/common/diff/diff.ts +++ b/src/vs/base/common/diff/diff.ts @@ -27,6 +27,7 @@ export function stringDiff(original: string, modified: string, pretty: boolean): export interface ISequence { getElements(): Int32Array | number[] | string[]; + getStrictElement?(index: number): string; } export interface IDiffChange { @@ -231,6 +232,8 @@ export class LcsDiff { private readonly ContinueProcessingPredicate: IContinueProcessingPredicate | null; + private readonly _originalSequence: ISequence; + private readonly _modifiedSequence: ISequence; private readonly _hasStrings: boolean; private readonly _originalStringElements: string[]; private readonly _originalElementsOrHash: Int32Array; @@ -246,6 +249,9 @@ export class LcsDiff { constructor(originalSequence: ISequence, modifiedSequence: ISequence, continueProcessingPredicate: IContinueProcessingPredicate | null = null) { this.ContinueProcessingPredicate = continueProcessingPredicate; + this._originalSequence = originalSequence; + this._modifiedSequence = modifiedSequence; + const [originalStringElements, originalElementsOrHash, originalHasStrings] = LcsDiff._getElements(originalSequence); const [modifiedStringElements, modifiedElementsOrHash, modifiedHasStrings] = LcsDiff._getElements(modifiedSequence); @@ -288,6 +294,22 @@ export class LcsDiff { return (this._hasStrings ? this._originalStringElements[originalIndex] === this._modifiedStringElements[newIndex] : true); } + private ElementsAreStrictEqual(originalIndex: number, newIndex: number): boolean { + if (!this.ElementsAreEqual(originalIndex, newIndex)) { + return false; + } + const originalElement = LcsDiff._getStrictElement(this._originalSequence, originalIndex); + const modifiedElement = LcsDiff._getStrictElement(this._modifiedSequence, newIndex); + return (originalElement === modifiedElement); + } + + private static _getStrictElement(sequence: ISequence, index: number): string | null { + if (typeof sequence.getStrictElement === 'function') { + return sequence.getStrictElement(index); + } + return null; + } + private OriginalElementsAreEqual(index1: number, index2: number): boolean { if (this._originalElementsOrHash[index1] !== this._originalElementsOrHash[index2]) { return false; @@ -813,10 +835,18 @@ export class LcsDiff { const checkOriginal = change.originalLength > 0; const checkModified = change.modifiedLength > 0; - while (change.originalStart + change.originalLength < originalStop && - change.modifiedStart + change.modifiedLength < modifiedStop && - (!checkOriginal || this.OriginalElementsAreEqual(change.originalStart, change.originalStart + change.originalLength)) && - (!checkModified || this.ModifiedElementsAreEqual(change.modifiedStart, change.modifiedStart + change.modifiedLength))) { + while ( + change.originalStart + change.originalLength < originalStop + && change.modifiedStart + change.modifiedLength < modifiedStop + && (!checkOriginal || this.OriginalElementsAreEqual(change.originalStart, change.originalStart + change.originalLength)) + && (!checkModified || this.ModifiedElementsAreEqual(change.modifiedStart, change.modifiedStart + change.modifiedLength)) + ) { + const startStrictEqual = this.ElementsAreStrictEqual(change.originalStart, change.modifiedStart); + const endStrictEqual = this.ElementsAreStrictEqual(change.originalStart + change.originalLength, change.modifiedStart + change.modifiedLength); + if (endStrictEqual && !startStrictEqual) { + // moving the change down would create an equal change, but the elements are not strict equal + break; + } change.originalStart++; change.modifiedStart++; } diff --git a/src/vs/base/common/filters.ts b/src/vs/base/common/filters.ts index 5677cbebebb..f939880a97e 100644 --- a/src/vs/base/common/filters.ts +++ b/src/vs/base/common/filters.ts @@ -526,7 +526,7 @@ const enum Arrow { Diag = 1, Left = 2, LeftLeft = 3 } * 3. `` * 4. `` etc */ -export type FuzzyScore = [score: number, wordStart: number, ...matches: number[]];// [number, number, number]; +export type FuzzyScore = [score: number, wordStart: number, ...matches: number[]]; export namespace FuzzyScore { /** diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index 82b3adfab0b..f3168ad6e0e 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -283,6 +283,31 @@ export abstract class ReferenceCollection { protected abstract destroyReferencedObject(key: string, object: T): void; } +/** + * Unwraps a reference collection of promised values. Makes sure + * references are disposed whenever promises get rejected. + */ +export class AsyncReferenceCollection { + + constructor(private referenceCollection: ReferenceCollection>) { } + + async acquire(key: string, ...args: any[]): Promise> { + const ref = this.referenceCollection.acquire(key, ...args); + + try { + const object = await ref.object; + + return { + object, + dispose: () => ref.dispose() + }; + } catch (error) { + ref.dispose(); + throw error; + } + } +} + export class ImmortalReference implements IReference { constructor(public object: T) { } dispose(): void { /* noop */ } diff --git a/src/vs/base/common/map.ts b/src/vs/base/common/map.ts index a552d658aac..4694b618aa5 100644 --- a/src/vs/base/common/map.ts +++ b/src/vs/base/common/map.ts @@ -497,25 +497,28 @@ export class TernarySearchTree { yield* this._entries(this._root); } - private *_entries(node: TernarySearchTreeNode | undefined, i: number = 0): IterableIterator<[K, V]> { - if (i > 5000) { - console.log('potential CYCLE detected', new Error().stack); + private *_entries(node: TernarySearchTreeNode | undefined): IterableIterator<[K, V]> { + // DFS + if (!node) { return; } - if (node) { - // left - yield* this._entries(node.left, i++); - - // node - if (node.value) { - // callback(node.value, this._iter.join(parts)); - yield [node.key, node.value]; + const stack = [node]; + while (stack.length > 0) { + const node = stack.pop(); + if (node) { + if (node.value) { + yield [node.key, node.value]; + } + if (node.left) { + stack.push(node.left); + } + if (node.mid) { + stack.push(node.mid); + } + if (node.right) { + stack.push(node.right); + } } - // mid - yield* this._entries(node.mid, i++); - - // right - yield* this._entries(node.right, i++); } } } diff --git a/src/vs/base/node/pfs.ts b/src/vs/base/node/pfs.ts index 40a81246b02..aeb5af4a669 100644 --- a/src/vs/base/node/pfs.ts +++ b/src/vs/base/node/pfs.ts @@ -100,15 +100,15 @@ export interface IDirent { * for converting from macOS NFD unicon form to NFC * (https://github.com/nodejs/node/issues/2165) */ -export async function readdir(path: string): Promise; -export async function readdir(path: string, options: { withFileTypes: true }): Promise; -export async function readdir(path: string, options?: { withFileTypes: true }): Promise<(string | IDirent)[]> { - return handleDirectoryChildren(await (options ? safeReaddirWithFileTypes(path) : Promises.readdir(path))); +async function readdir(path: string): Promise; +async function readdir(path: string, options: { withFileTypes: true }): Promise; +async function readdir(path: string, options?: { withFileTypes: true }): Promise<(string | IDirent)[]> { + return handleDirectoryChildren(await (options ? safeReaddirWithFileTypes(path) : promisify(fs.readdir)(path))); } async function safeReaddirWithFileTypes(path: string): Promise { try { - return await Promises.readdir(path, { withFileTypes: true }); + return await promisify(fs.readdir)(path, { withFileTypes: true }); } catch (error) { console.warn('[node.js fs] readdir with filetypes failed with error: ', error); } @@ -360,11 +360,11 @@ const writeQueues = new ResourceQueue(); * * In addition, multiple writes to the same path are queued. */ -export function writeFile(path: string, data: string, options?: IWriteFileOptions): Promise; -export function writeFile(path: string, data: Buffer, options?: IWriteFileOptions): Promise; -export function writeFile(path: string, data: Uint8Array, options?: IWriteFileOptions): Promise; -export function writeFile(path: string, data: string | Buffer | Uint8Array, options?: IWriteFileOptions): Promise; -export function writeFile(path: string, data: string | Buffer | Uint8Array, options?: IWriteFileOptions): Promise { +function writeFile(path: string, data: string, options?: IWriteFileOptions): Promise; +function writeFile(path: string, data: Buffer, options?: IWriteFileOptions): Promise; +function writeFile(path: string, data: Uint8Array, options?: IWriteFileOptions): Promise; +function writeFile(path: string, data: string | Buffer | Uint8Array, options?: IWriteFileOptions): Promise; +function writeFile(path: string, data: string | Buffer | Uint8Array, options?: IWriteFileOptions): Promise { return writeQueues.queueFor(URI.file(path), extUriBiasedIgnorePathCase).queue(() => { const ensuredOptions = ensureWriteOptions(options); @@ -372,7 +372,7 @@ export function writeFile(path: string, data: string | Buffer | Uint8Array, opti }); } -export interface IWriteFileOptions { +interface IWriteFileOptions { mode?: number; flag?: string; } @@ -627,25 +627,11 @@ async function doCopySymlink(source: string, target: string, payload: ICopyPaylo //#endregion -//#region Async FS Methods - -export async function exists(path: string): Promise { - try { - await Promises.access(path); - - return true; - } catch { - return false; - } -} - -//#endregion - //#region Promise based fs methods /** - * Note: prefer this namespace over the `fs.promises` API - * to enable `graceful-fs` to function properly. Given issue + * Prefer this helper class over the `fs.promises` API to + * enable `graceful-fs` to function properly. Given issue * https://github.com/isaacs/node-graceful-fs/issues/160 it * is evident that the module only takes care of the non-promise * based fs methods. @@ -653,43 +639,68 @@ export async function exists(path: string): Promise { * Another reason is `realpath` being entirely different in * the promise based implementation compared to the other * one (https://github.com/microsoft/vscode/issues/118562) + * + * Note: using getters for a reason, since `graceful-fs` + * patching might kick in later after modules have been + * loaded we need to defer access to fs methods. + * (https://github.com/microsoft/vscode/issues/124176) */ -export namespace Promises { - export const access = promisify(fs.access); +export const Promises = new class { - export const stat = promisify(fs.stat); - export const lstat = promisify(fs.lstat); - export const utimes = promisify(fs.utimes); + //#region Implemented by node.js - export const read = promisify(fs.read); - export const readFile = promisify(fs.readFile); + get access() { return promisify(fs.access); } - export const write = promisify(fs.write); - export const writeFile = promisify(fs.writeFile); + get stat() { return promisify(fs.stat); } + get lstat() { return promisify(fs.lstat); } + get utimes() { return promisify(fs.utimes); } - export const appendFile = promisify(fs.appendFile); + get read() { return promisify(fs.read); } + get readFile() { return promisify(fs.readFile); } - export const fdatasync = promisify(fs.fdatasync); - export const truncate = promisify(fs.truncate); + get write() { return promisify(fs.write); } - export const rename = promisify(fs.rename); - export const copyFile = promisify(fs.copyFile); + get appendFile() { return promisify(fs.appendFile); } - export const open = promisify(fs.open); - export const close = promisify(fs.close); + get fdatasync() { return promisify(fs.fdatasync); } + get truncate() { return promisify(fs.truncate); } - export const symlink = promisify(fs.symlink); - export const readlink = promisify(fs.readlink); + get rename() { return promisify(fs.rename); } + get copyFile() { return promisify(fs.copyFile); } - export const chmod = promisify(fs.chmod); + get open() { return promisify(fs.open); } + get close() { return promisify(fs.close); } - export const readdir = promisify(fs.readdir); - export const mkdir = promisify(fs.mkdir); + get symlink() { return promisify(fs.symlink); } + get readlink() { return promisify(fs.readlink); } - export const unlink = promisify(fs.unlink); - export const rmdir = promisify(fs.rmdir); + get chmod() { return promisify(fs.chmod); } - export const realpath = promisify(fs.realpath); -} + get mkdir() { return promisify(fs.mkdir); } + + get unlink() { return promisify(fs.unlink); } + get rmdir() { return promisify(fs.rmdir); } + + get realpath() { return promisify(fs.realpath); } + + //#endregion + + //#region Implemented by us + + async exists(path: string): Promise { + try { + await Promises.access(path); + + return true; + } catch { + return false; + } + } + + get readdir() { return readdir; } + get writeFile() { return writeFile; } + + //#endregion +}; //#endregion diff --git a/src/vs/base/node/powershell.ts b/src/vs/base/node/powershell.ts index 77d74f796fa..6ea3646d690 100644 --- a/src/vs/base/node/powershell.ts +++ b/src/vs/base/node/powershell.ts @@ -137,7 +137,7 @@ async function findPSCoreWindowsInstallation( let highestSeenVersion: number = -1; let pwshExePath: string | null = null; - for (const item of await pfs.readdir(powerShellInstallBaseDir)) { + for (const item of await pfs.Promises.readdir(powerShellInstallBaseDir)) { let currentVersion: number = -1; if (findPreview) { @@ -210,7 +210,7 @@ async function findPSCoreMsix({ findPreview }: { findPreview?: boolean } = {}): : { pwshMsixDirRegex: PwshMsixRegex, pwshMsixName: 'PowerShell (Store)' }; // We should find only one such application, so return on the first one - for (const subdir of await pfs.readdir(msixAppDir)) { + for (const subdir of await pfs.Promises.readdir(msixAppDir)) { if (pwshMsixDirRegex.test(subdir)) { const pwshMsixPath = path.join(msixAppDir, subdir, 'pwsh.exe'); return new PossiblePowerShellExe(pwshMsixPath, pwshMsixName); diff --git a/src/vs/base/node/processes.ts b/src/vs/base/node/processes.ts index f1b1b398212..369c4146354 100644 --- a/src/vs/base/node/processes.ts +++ b/src/vs/base/node/processes.ts @@ -456,7 +456,7 @@ export namespace win32 { } async function fileExists(path: string): Promise { - if (await pfs.exists(path)) { + if (await pfs.Promises.exists(path)) { return !((await pfs.Promises.stat(path)).isDirectory()); } return false; diff --git a/src/vs/base/node/watcher.ts b/src/vs/base/node/watcher.ts index a15daf9ce1b..1266c3e111e 100644 --- a/src/vs/base/node/watcher.ts +++ b/src/vs/base/node/watcher.ts @@ -8,7 +8,7 @@ import { watch } from 'fs'; import { isMacintosh } from 'vs/base/common/platform'; import { normalizeNFC } from 'vs/base/common/normalization'; import { toDisposable, IDisposable, dispose } from 'vs/base/common/lifecycle'; -import { exists, readdir } from 'vs/base/node/pfs'; +import { Promises } from 'vs/base/node/pfs'; export function watchFile(path: string, onChange: (type: 'added' | 'changed' | 'deleted', path: string) => void, onError: (error: string) => void): IDisposable { return doWatchNonRecursive({ path, isDirectory: false }, onChange, onError); @@ -42,7 +42,7 @@ function doWatchNonRecursive(file: { path: string, isDirectory: boolean }, onCha // Folder: resolve children to emit proper events const folderChildren: Set = new Set(); if (file.isDirectory) { - readdir(file.path).then(children => children.forEach(child => folderChildren.add(child))); + Promises.readdir(file.path).then(children => children.forEach(child => folderChildren.add(child))); } watcher.on('error', (code: number, signal: string) => { @@ -87,7 +87,7 @@ function doWatchNonRecursive(file: { path: string, isDirectory: boolean }, onCha // does indeed not exist anymore. const timeoutHandle = setTimeout(async () => { - const fileExists = await exists(changedFilePath); + const fileExists = await Promises.exists(changedFilePath); if (disposed) { return; // ignore if disposed by now @@ -131,7 +131,7 @@ function doWatchNonRecursive(file: { path: string, isDirectory: boolean }, onCha const timeoutHandle = setTimeout(async () => { mapPathToStatDisposable.delete(changedFilePath); - const fileExists = await exists(changedFilePath); + const fileExists = await Promises.exists(changedFilePath); if (disposed) { return; // ignore if disposed by now @@ -177,7 +177,7 @@ function doWatchNonRecursive(file: { path: string, isDirectory: boolean }, onCha } }); } catch (error) { - exists(file.path).then(exists => { + Promises.exists(file.path).then(exists => { if (exists && !disposed) { onError(`Failed to watch ${file.path} for changes using fs.watch() (${error.toString()})`); } diff --git a/src/vs/base/parts/storage/test/node/storage.test.ts b/src/vs/base/parts/storage/test/node/storage.test.ts index 07846cf4a4f..fd5865b5655 100644 --- a/src/vs/base/parts/storage/test/node/storage.test.ts +++ b/src/vs/base/parts/storage/test/node/storage.test.ts @@ -8,7 +8,7 @@ import { Storage, IStorageDatabase, IStorageItemsChangeEvent } from 'vs/base/par import { join } from 'vs/base/common/path'; import { tmpdir } from 'os'; import { strictEqual, ok } from 'assert'; -import { writeFile, exists, rimraf, Promises } from 'vs/base/node/pfs'; +import { rimraf, Promises } from 'vs/base/node/pfs'; import { timeout } from 'vs/base/common/async'; import { Event, Emitter } from 'vs/base/common/event'; import { isWindows } from 'vs/base/common/platform'; @@ -262,7 +262,7 @@ flakySuite('Storage Library', function () { await storage.set('bar', 'foo'); - await writeFile(storageFile, 'This is a broken DB'); + await Promises.writeFile(storageFile, 'This is a broken DB'); await storage.set('foo', 'bar'); @@ -385,7 +385,7 @@ flakySuite('SQLite Storage Library', function () { test('basics (corrupt DB falls back to empty DB)', async () => { const corruptDBPath = join(testdir, 'broken.db'); - await writeFile(corruptDBPath, 'This is a broken DB'); + await Promises.writeFile(corruptDBPath, 'This is a broken DB'); let expectedError: any; await testDBBasics(corruptDBPath, error => { @@ -407,7 +407,7 @@ flakySuite('SQLite Storage Library', function () { await storage.updateItems({ insert: items }); await storage.close(); - await writeFile(storagePath, 'This is now a broken DB'); + await Promises.writeFile(storagePath, 'This is now a broken DB'); storage = new SQLiteStorageDatabase(storagePath); @@ -439,8 +439,8 @@ flakySuite('SQLite Storage Library', function () { await storage.updateItems({ insert: items }); await storage.close(); - await writeFile(storagePath, 'This is now a broken DB'); - await writeFile(`${storagePath}.backup`, 'This is now also a broken DB'); + await Promises.writeFile(storagePath, 'This is now a broken DB'); + await Promises.writeFile(`${storagePath}.backup`, 'This is now also a broken DB'); storage = new SQLiteStorageDatabase(storagePath); @@ -463,12 +463,12 @@ flakySuite('SQLite Storage Library', function () { await storage.close(); const backupPath = `${storagePath}.backup`; - strictEqual(await exists(backupPath), true); + strictEqual(await Promises.exists(backupPath), true); storage = new SQLiteStorageDatabase(storagePath); await storage.getItems(); - await writeFile(storagePath, 'This is now a broken DB'); + await Promises.writeFile(storagePath, 'This is now a broken DB'); // we still need to trigger a check to the DB so that we get to know that // the DB is corrupt. We have no extra code on shutdown that checks for the @@ -486,7 +486,7 @@ flakySuite('SQLite Storage Library', function () { }); strictEqual(recoveryCalled, true); - strictEqual(await exists(backupPath), true); + strictEqual(await Promises.exists(backupPath), true); storage = new SQLiteStorageDatabase(storagePath); diff --git a/src/vs/base/test/common/map.test.ts b/src/vs/base/test/common/map.test.ts index 2e67001c17b..48ebcb86522 100644 --- a/src/vs/base/test/common/map.test.ts +++ b/src/vs/base/test/common/map.test.ts @@ -762,6 +762,9 @@ suite('Map', () => { iter = map.findSuperstr(URI.file('/'))!; item = iter.next(); + assert.strictEqual(item.value[1], 4); + assert.strictEqual(item.done, false); + item = iter.next(); assert.strictEqual(item.value[1], 2); assert.strictEqual(item.done, false); item = iter.next(); @@ -771,9 +774,6 @@ suite('Map', () => { assert.strictEqual(item.value[1], 3); assert.strictEqual(item.done, false); item = iter.next(); - assert.strictEqual(item.value[1], 4); - assert.strictEqual(item.done, false); - item = iter.next(); assert.strictEqual(item.value, undefined); assert.strictEqual(item.done, true); diff --git a/src/vs/base/test/common/testUtils.ts b/src/vs/base/test/common/testUtils.ts new file mode 100644 index 00000000000..1e291422f41 --- /dev/null +++ b/src/vs/base/test/common/testUtils.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export function flakySuite(title: string, fn: () => void) /* Suite */ { + return suite(title, function () { + + // Flaky suites need retries and timeout to complete + // e.g. because they access browser features which can + // be unreliable depending on the environment. + this.retries(3); + this.timeout(1000 * 20); + + // Invoke suite ensuring that `this` is + // properly wired in. + fn.call(this); + }); +} diff --git a/src/vs/base/test/node/crypto.test.ts b/src/vs/base/test/node/crypto.test.ts index f1b7ef70b94..d8614d21a6a 100644 --- a/src/vs/base/test/node/crypto.test.ts +++ b/src/vs/base/test/node/crypto.test.ts @@ -6,7 +6,7 @@ import { checksum } from 'vs/base/node/crypto'; import { join } from 'vs/base/common/path'; import { tmpdir } from 'os'; -import { Promises, rimraf, writeFile } from 'vs/base/node/pfs'; +import { Promises, rimraf } from 'vs/base/node/pfs'; import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; flakySuite('Crypto', () => { @@ -25,7 +25,7 @@ flakySuite('Crypto', () => { test('checksum', async () => { const testFile = join(testDir, 'checksum.txt'); - await writeFile(testFile, 'Hello World'); + await Promises.writeFile(testFile, 'Hello World'); await checksum(testFile, '0a4d55a8d778e5022fab701977c5d840bbc486d0'); }); diff --git a/src/vs/base/test/node/pfs/pfs.test.ts b/src/vs/base/test/node/pfs/pfs.test.ts index 2139f6cbe44..6181a88b4d2 100644 --- a/src/vs/base/test/node/pfs/pfs.test.ts +++ b/src/vs/base/test/node/pfs/pfs.test.ts @@ -8,7 +8,7 @@ import * as fs from 'fs'; import { tmpdir } from 'os'; import { join, sep } from 'vs/base/common/path'; import { generateUuid } from 'vs/base/common/uuid'; -import { copy, exists, move, Promises, readdir, readDirsInDir, rimraf, RimRafMode, rimrafSync, SymlinkSupport, writeFile, writeFileSync } from 'vs/base/node/pfs'; +import { copy, move, Promises, readDirsInDir, rimraf, RimRafMode, rimrafSync, SymlinkSupport, writeFileSync } from 'vs/base/node/pfs'; import { timeout } from 'vs/base/common/async'; import { canNormalize } from 'vs/base/common/normalization'; import { VSBuffer } from 'vs/base/common/buffer'; @@ -32,9 +32,9 @@ flakySuite('PFS', function () { test('writeFile', async () => { const testFile = join(testDir, 'writefile.txt'); - assert.ok(!(await exists(testFile))); + assert.ok(!(await Promises.exists(testFile))); - await writeFile(testFile, 'Hello World', (null!)); + await Promises.writeFile(testFile, 'Hello World', (null!)); assert.strictEqual((await Promises.readFile(testFile)).toString(), 'Hello World'); }); @@ -47,11 +47,11 @@ flakySuite('PFS', function () { const testFile5 = join(testDir, 'writefile5.txt'); await Promise.all([ - writeFile(testFile1, 'Hello World 1', (null!)), - writeFile(testFile2, 'Hello World 2', (null!)), - writeFile(testFile3, 'Hello World 3', (null!)), - writeFile(testFile4, 'Hello World 4', (null!)), - writeFile(testFile5, 'Hello World 5', (null!)) + Promises.writeFile(testFile1, 'Hello World 1', (null!)), + Promises.writeFile(testFile2, 'Hello World 2', (null!)), + Promises.writeFile(testFile3, 'Hello World 3', (null!)), + Promises.writeFile(testFile4, 'Hello World 4', (null!)), + Promises.writeFile(testFile5, 'Hello World 5', (null!)) ]); assert.strictEqual(fs.readFileSync(testFile1).toString(), 'Hello World 1'); assert.strictEqual(fs.readFileSync(testFile2).toString(), 'Hello World 2'); @@ -64,11 +64,11 @@ flakySuite('PFS', function () { const testFile = join(testDir, 'writefile.txt'); await Promise.all([ - writeFile(testFile, 'Hello World 1', undefined), - writeFile(testFile, 'Hello World 2', undefined), - timeout(10).then(() => writeFile(testFile, 'Hello World 3', undefined)), - writeFile(testFile, 'Hello World 4', undefined), - timeout(10).then(() => writeFile(testFile, 'Hello World 5', undefined)) + Promises.writeFile(testFile, 'Hello World 1', undefined), + Promises.writeFile(testFile, 'Hello World 2', undefined), + timeout(10).then(() => Promises.writeFile(testFile, 'Hello World 3', undefined)), + Promises.writeFile(testFile, 'Hello World 4', undefined), + timeout(10).then(() => Promises.writeFile(testFile, 'Hello World 5', undefined)) ]); assert.strictEqual(fs.readFileSync(testFile).toString(), 'Hello World 5'); }); @@ -254,7 +254,7 @@ flakySuite('PFS', function () { const sourceLinkMD5JSFolder = join(sourceLinkTestFolder, 'md5'); // copy-test/link-test/md5 const sourceLinkMD5JSFile = join(sourceLinkMD5JSFolder, 'md5.js'); // copy-test/link-test/md5/md5.js await Promises.mkdir(sourceLinkMD5JSFolder, { recursive: true }); - await writeFile(sourceLinkMD5JSFile, 'Hello from MD5'); + await Promises.writeFile(sourceLinkMD5JSFile, 'Hello from MD5'); const sourceLinkMD5JSFolderLinked = join(sourceLinkTestFolder, 'md5-linked'); // copy-test/link-test/md5-linked fs.symlinkSync(sourceLinkMD5JSFolder, sourceLinkMD5JSFolderLinked, 'junction'); @@ -354,7 +354,7 @@ flakySuite('PFS', function () { assert.ok(fs.existsSync(newDir)); - const children = await readdir(join(testDir, 'pfs', id)); + const children = await Promises.readdir(join(testDir, 'pfs', id)); assert.strictEqual(children.some(n => n === 'öäü'), true); // Mac always converts to NFD, so } }); @@ -364,11 +364,11 @@ flakySuite('PFS', function () { const newDir = join(testDir, 'öäü'); await Promises.mkdir(newDir, { recursive: true }); - await writeFile(join(testDir, 'somefile.txt'), 'contents'); + await Promises.writeFile(join(testDir, 'somefile.txt'), 'contents'); assert.ok(fs.existsSync(newDir)); - const children = await readdir(testDir, { withFileTypes: true }); + const children = await Promises.readdir(testDir, { withFileTypes: true }); assert.strictEqual(children.some(n => n.name === 'öäü'), true); // Mac always converts to NFD, so assert.strictEqual(children.some(n => n.isDirectory()), true); @@ -409,10 +409,10 @@ flakySuite('PFS', function () { assert.ok(fs.existsSync(testDir)); - await writeFile(testFile, smallData); + await Promises.writeFile(testFile, smallData); assert.strictEqual(fs.readFileSync(testFile).toString(), smallDataValue); - await writeFile(testFile, bigData); + await Promises.writeFile(testFile, bigData); assert.strictEqual(fs.readFileSync(testFile).toString(), bigDataValue); } @@ -423,7 +423,7 @@ flakySuite('PFS', function () { let expectedError: Error | undefined; try { - await writeFile(testFile, 'Hello World'); + await Promises.writeFile(testFile, 'Hello World'); } catch (error) { expectedError = error; } diff --git a/src/vs/base/test/node/testUtils.ts b/src/vs/base/test/node/testUtils.ts index c425bd85626..fb2bc77ad9c 100644 --- a/src/vs/base/test/node/testUtils.ts +++ b/src/vs/base/test/node/testUtils.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import type { Suite } from 'mocha'; import { join } from 'vs/base/common/path'; import { URI } from 'vs/base/common/uri'; import { generateUuid } from 'vs/base/common/uuid'; +import * as testUtils from 'vs/base/test/common/testUtils'; export function getRandomTestPath(tmpdir: string, ...segments: string[]): string { return join(tmpdir, ...segments, generateUuid()); @@ -16,17 +16,4 @@ export function getPathFromAmdModule(requirefn: typeof require, relativePath: st return URI.parse(requirefn.toUrl(relativePath)).fsPath; } -export function flakySuite(title: string, fn: (this: Suite) => void): Suite { - return suite(title, function () { - - // Flaky suites need retries and timeout to complete - // e.g. because they access the file system which can - // be unreliable depending on the environment. - this.retries(3); - this.timeout(1000 * 20); - - // Invoke suite ensuring that `this` is - // properly wired in. - fn.call(this); - }); -} +export import flakySuite = testUtils.flakySuite; diff --git a/src/vs/base/test/node/zip/zip.test.ts b/src/vs/base/test/node/zip/zip.test.ts index 5e5470ce155..dae282580b5 100644 --- a/src/vs/base/test/node/zip/zip.test.ts +++ b/src/vs/base/test/node/zip/zip.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import * as path from 'vs/base/common/path'; import { tmpdir } from 'os'; import { extract } from 'vs/base/node/zip'; -import { rimraf, exists, Promises } from 'vs/base/node/pfs'; +import { rimraf, Promises } from 'vs/base/node/pfs'; import { createCancelablePromise } from 'vs/base/common/async'; import { getRandomTestPath, getPathFromAmdModule } from 'vs/base/test/node/testUtils'; @@ -30,7 +30,7 @@ suite('Zip', () => { const fixture = path.join(fixtures, 'extract.zip'); await createCancelablePromise(token => extract(fixture, testDir, {}, token)); - const doesExist = await exists(path.join(testDir, 'extension')); + const doesExist = await Promises.exists(path.join(testDir, 'extension')); assert(doesExist); }); }); diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts index b76d7668802..1cdbee91f35 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/languagePackCachedDataCleaner.ts @@ -61,11 +61,11 @@ export class LanguagePackCachedDataCleaner extends Disposable { } // Cleanup entries for language packs that aren't installed anymore const cacheDir = path.join(this._environmentService.userDataPath, 'clp'); - const exists = await pfs.exists(cacheDir); + const exists = await pfs.Promises.exists(cacheDir); if (!exists) { return; } - for (let entry of await pfs.readdir(cacheDir)) { + for (let entry of await pfs.Promises.readdir(cacheDir)) { if (installed[entry]) { this._logService.info(`Skipping directory ${entry}. Language pack still in use.`); continue; @@ -77,7 +77,7 @@ export class LanguagePackCachedDataCleaner extends Disposable { const now = Date.now(); for (let packEntry of Object.keys(installed)) { const folder = path.join(cacheDir, packEntry); - for (let entry of await pfs.readdir(folder)) { + for (let entry of await pfs.Promises.readdir(folder)) { if (entry === 'tcf.json') { continue; } diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner.ts index b9ee08d4cd1..9922c70edc2 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/logsDataCleaner.ts @@ -5,7 +5,7 @@ import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { join, dirname, basename } from 'vs/base/common/path'; -import { readdir, rimraf } from 'vs/base/node/pfs'; +import { Promises as FSPromises, rimraf } from 'vs/base/node/pfs'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { Promises } from 'vs/base/common/async'; @@ -27,7 +27,7 @@ export class LogsDataCleaner extends Disposable { const currentLog = basename(this.environmentService.logsPath); const logsRoot = dirname(this.environmentService.logsPath); - readdir(logsRoot).then(children => { + FSPromises.readdir(logsRoot).then(children => { const allSessions = children.filter(name => /^\d{8}T\d{6}$/.test(name)); const oldSessions = allSessions.sort().filter((d, i) => d !== currentLog); const toDelete = oldSessions.slice(0, Math.max(0, oldSessions.length - 9)); diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts index a6afc697617..1408053418f 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/nodeCachedDataCleaner.ts @@ -6,7 +6,7 @@ import { basename, dirname, join } from 'vs/base/common/path'; import { onUnexpectedError } from 'vs/base/common/errors'; import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; -import { Promises, readdir, rimraf } from 'vs/base/node/pfs'; +import { Promises, rimraf } from 'vs/base/node/pfs'; import { IProductService } from 'vs/platform/product/common/productService'; export class NodeCachedDataCleaner { @@ -44,7 +44,7 @@ export class NodeCachedDataCleaner { let handle: NodeJS.Timeout | undefined = setTimeout(() => { handle = undefined; - readdir(nodeCachedDataRootDir).then(entries => { + Promises.readdir(nodeCachedDataRootDir).then(entries => { const now = Date.now(); const deletes: Promise[] = []; diff --git a/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts b/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts index 12055754970..ee8426cdeff 100644 --- a/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts +++ b/src/vs/code/electron-browser/sharedProcess/contrib/storageDataCleaner.ts @@ -5,7 +5,7 @@ import { INativeEnvironmentService } from 'vs/platform/environment/common/environment'; import { join } from 'vs/base/common/path'; -import { Promises, readdir, rimraf } from 'vs/base/node/pfs'; +import { Promises, rimraf } from 'vs/base/node/pfs'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; import { IBackupWorkspacesFormat } from 'vs/platform/backup/node/backup'; @@ -38,7 +38,7 @@ export class StorageDataCleaner extends Disposable { const emptyWorkspaces = workspaces.emptyWorkspaceInfos.map(info => info.backupFolder); // Read all workspace storage folders that exist - const storageFolders = await readdir(this.environmentService.workspaceStorageHome.fsPath); + const storageFolders = await Promises.readdir(this.environmentService.workspaceStorageHome.fsPath); const deletes: Promise[] = []; storageFolders.forEach(storageFolder => { diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 630c577a640..c3e48687980 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -71,7 +71,7 @@ import { withNullAsUndefined } from 'vs/base/common/types'; import { mnemonicButtonLabel, getPathLabel } from 'vs/base/common/labels'; import { WebviewMainService } from 'vs/platform/webview/electron-main/webviewMainService'; import { IWebviewManagerService } from 'vs/platform/webview/common/webviewManagerService'; -import { FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files'; +import { IFileService } from 'vs/platform/files/common/files'; import { stripComments } from 'vs/base/common/json'; import { generateUuid } from 'vs/base/common/uuid'; import { VSBuffer } from 'vs/base/common/buffer'; @@ -148,37 +148,6 @@ export class CodeApplication extends Disposable { // // !!! DO NOT CHANGE without consulting the documentation !!! // - app.on('remote-require', (event, sender, module) => { - this.logService.trace('app#on(remote-require): prevented'); - - event.preventDefault(); - }); - app.on('remote-get-global', (event, sender, module) => { - this.logService.trace(`app#on(remote-get-global): prevented on ${module}`); - - event.preventDefault(); - }); - app.on('remote-get-builtin', (event, sender, module) => { - this.logService.trace(`app#on(remote-get-builtin): prevented on ${module}`); - - if (module !== 'clipboard') { - event.preventDefault(); - } - }); - app.on('remote-get-current-window', event => { - this.logService.trace(`app#on(remote-get-current-window): prevented`); - - event.preventDefault(); - }); - app.on('remote-get-current-web-contents', event => { - if (this.environmentMainService.args.driver) { - return; // the driver needs access to web contents - } - - this.logService.trace(`app#on(remote-get-current-web-contents): prevented`); - - event.preventDefault(); - }); app.on('web-contents-created', (event, contents) => { contents.on('will-attach-webview', (event, webPreferences, params) => { @@ -222,19 +191,19 @@ export class CodeApplication extends Disposable { event.preventDefault(); }); - contents.on('new-window', (event, url) => { - event.preventDefault(); // prevent code that wants to open links - + contents.setWindowOpenHandler(({ url }) => { this.nativeHostMainService?.openExternal(undefined, url); + + return { action: 'deny' }; }); - const isUrlFromWebview = (requestingUrl: string) => - requestingUrl.startsWith(`${Schemas.vscodeWebview}://`); + const isUrlFromWebview = (requestingUrl: string) => requestingUrl.startsWith(`${Schemas.vscodeWebview}://`); session.defaultSession.setPermissionRequestHandler((_webContents, permission /* 'media' | 'geolocation' | 'notifications' | 'midiSysex' | 'pointerLock' | 'fullscreen' | 'openExternal' */, callback, details) => { if (isUrlFromWebview(details.requestingUrl)) { return callback(permission === 'clipboard-read'); } + return callback(false); }); @@ -242,6 +211,7 @@ export class CodeApplication extends Disposable { if (isUrlFromWebview(details.requestingUrl)) { return permission === 'clipboard-read'; } + return false; }); }); @@ -379,18 +349,6 @@ export class CodeApplication extends Disposable { this.logService.debug(`from: ${this.environmentMainService.appRoot}`); this.logService.debug('args:', this.environmentMainService.args); - // TODO@bpasero TODO@deepak1556 workaround for #120655 - try { - const cachedDataPath = URI.file(this.environmentMainService.chromeCachedDataDir); - this.logService.trace(`Deleting Chrome cached data path: ${cachedDataPath.fsPath}`); - - await this.fileService.del(cachedDataPath, { recursive: true }); - } catch (error) { - if ((error).fileOperationResult !== FileOperationResult.FILE_NOT_FOUND) { - this.logService.error(error); - } - } - // Make sure we associate the program with the app user model id // This will help Windows to associate the running program with // any shortcut that is pinned to the taskbar and prevent showing @@ -678,16 +636,18 @@ export class CodeApplication extends Disposable { // Check for initial URLs to handle from protocol link invocations const pendingWindowOpenablesFromProtocolLinks: IWindowOpenable[] = []; const pendingProtocolLinksToHandle = [ + // Windows/Linux: protocol handler invokes CLI with --open-url ...this.environmentMainService.args['open-url'] ? this.environmentMainService.args._urls || [] : [], // macOS: open-url events ...((global).getOpenUrls() || []) as string[] + ].map(url => { try { return { uri: URI.parse(url), url }; } catch { - return null; + return undefined; } }).filter((obj): obj is { uri: URI, url: string } => { if (!obj) { @@ -741,7 +701,7 @@ export class CodeApplication extends Disposable { cli: { ...environmentService.args }, urisToOpen: [windowOpenableFromProtocolLink], gotoLineMode: true - /* remoteAuthority will be determined based on windowOpenableFromProtocolLink */ + // remoteAuthority: will be determined based on windowOpenableFromProtocolLink }); window.focus(); // this should help ensuring that the right window gets focus when multiple are opened @@ -804,7 +764,7 @@ export class CodeApplication extends Disposable { urisToOpen: pendingWindowOpenablesFromProtocolLinks, gotoLineMode: true, initialStartup: true - /* remoteAuthority will be determined based on pendingWindowOpenablesFromProtocolLinks */ + // remoteAuthority: will be determined based on pendingWindowOpenablesFromProtocolLinks }); } @@ -831,7 +791,7 @@ export class CodeApplication extends Disposable { noRecentEntry, waitMarkerFileURI, initialStartup: true, - /* remoteAuthority will be determined based on macOpenFiles */ + // remoteAuthority: will be determined based on macOpenFiles }); } diff --git a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts index 6e93aa1e4b8..26c871d3449 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts @@ -140,6 +140,7 @@ export class IssueReporter extends Disposable { this.applyStyles(configuration.data.styles); this.handleExtensionData(configuration.data.enabledExtensions); this.updateExperimentsInfo(configuration.data.experiments); + this.updateRestrictedMode(configuration.data.restrictedMode); } render(): void { @@ -1159,6 +1160,10 @@ export class IssueReporter extends Disposable { } } + private updateRestrictedMode(restrictedMode: boolean) { + this.issueReporterModel.update({ restrictedMode }); + } + private updateExperimentsInfo(experimentInfo: string | undefined) { this.issueReporterModel.update({ experimentInfo }); const target = document.querySelector('.block-experiments .block-info'); diff --git a/src/vs/code/electron-sandbox/issue/issueReporterModel.ts b/src/vs/code/electron-sandbox/issue/issueReporterModel.ts index 04a8ac509e5..63de037bc44 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterModel.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterModel.ts @@ -32,6 +32,7 @@ export interface IssueReporterData { query?: string; filterResultCount?: number; experimentInfo?: string; + restrictedMode?: boolean; } export class IssueReporterModel { @@ -67,6 +68,7 @@ ${this._data.issueDescription} ${this.getExtensionVersion()} VS Code version: ${this._data.versionInfo && this._data.versionInfo.vscodeVersion} OS version: ${this._data.versionInfo && this._data.versionInfo.os} +Restricted Mode: ${this._data.restrictedMode ? 'Yes' : 'No'} ${this.getRemoteOSes()} ${this.getInfos()} `; diff --git a/src/vs/code/electron-sandbox/issue/test/testReporterModel.test.ts b/src/vs/code/electron-sandbox/issue/test/testReporterModel.test.ts index 769fcd02ac8..5e8cfb05f6e 100644 --- a/src/vs/code/electron-sandbox/issue/test/testReporterModel.test.ts +++ b/src/vs/code/electron-sandbox/issue/test/testReporterModel.test.ts @@ -33,6 +33,7 @@ undefined VS Code version: undefined OS version: undefined +Restricted Mode: No Extensions: none `); @@ -63,6 +64,7 @@ undefined VS Code version: undefined OS version: undefined +Restricted Mode: No
System Info @@ -106,6 +108,7 @@ undefined VS Code version: undefined OS version: undefined +Restricted Mode: No
System Info @@ -160,6 +163,7 @@ undefined VS Code version: undefined OS version: undefined +Restricted Mode: No
System Info @@ -216,6 +220,7 @@ undefined VS Code version: undefined OS version: undefined +Restricted Mode: No Remote OS version: Linux x64 4.18.0
@@ -264,6 +269,7 @@ undefined VS Code version: undefined OS version: undefined +Restricted Mode: No
System Info diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index 2e7b324ed1d..9c7ad6268b6 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -56,7 +56,7 @@ export async function main(argv: string[]): Promise { // Extensions Management else if (shouldSpawnCliProcess(args)) { - const cli = await new Promise((c, e) => require(['vs/code/node/cliProcessMain'], c, e)); + const cli = await new Promise((resolve, reject) => require(['vs/code/node/cliProcessMain'], resolve, reject)); await cli.main(args); return; diff --git a/src/vs/code/node/cliProcessMain.ts b/src/vs/code/node/cliProcessMain.ts index ade2dd62737..c7a53c16d64 100644 --- a/src/vs/code/node/cliProcessMain.ts +++ b/src/vs/code/node/cliProcessMain.ts @@ -220,7 +220,7 @@ class CliMain extends Disposable { // Telemetry else if (this.argv['telemetry']) { - console.log(buildTelemetryMessage(environmentService.appRoot, environmentService.extensionsPath)); + console.log(await buildTelemetryMessage(environmentService.appRoot, environmentService.extensionsPath)); } } diff --git a/src/vs/editor/browser/viewParts/lines/viewLine.ts b/src/vs/editor/browser/viewParts/lines/viewLine.ts index edec7f7a041..3745a64d68d 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLine.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLine.ts @@ -12,7 +12,7 @@ import { IStringBuilder } from 'vs/editor/common/core/stringBuilder'; import { IConfiguration } from 'vs/editor/common/editorCommon'; import { HorizontalRange, VisibleRanges } from 'vs/editor/common/view/renderingContext'; import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations'; -import { CharacterMapping, ForeignElementType, RenderLineInput, renderViewLine, LineRange } from 'vs/editor/common/viewLayout/viewLineRenderer'; +import { CharacterMapping, ForeignElementType, RenderLineInput, renderViewLine, LineRange, DomPosition } from 'vs/editor/common/viewLayout/viewLineRenderer'; import { ViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; import { InlineDecorationType } from 'vs/editor/common/viewModel/viewModel'; import { ColorScheme } from 'vs/platform/theme/common/theme'; @@ -430,12 +430,8 @@ class FastRenderedViewLine implements IRenderedViewLine { } private _getCharPosition(column: number): number { - const charOffset = this._characterMapping.getAbsoluteOffsets(); - if (charOffset.length === 0) { - // No characters on this line - return 0; - } - return Math.round(this._charWidth * charOffset[column - 1]); + const charOffset = this._characterMapping.getAbsoluteOffset(column); + return Math.round(this._charWidth * charOffset); } public getColumnOfNodeOffset(lineNumber: number, spanNode: HTMLElement, offset: number): number { @@ -447,8 +443,7 @@ class FastRenderedViewLine implements IRenderedViewLine { spanIndex++; } - const charOffset = this._characterMapping.partDataToCharOffset(spanIndex, spanNodeTextContentLength, offset); - return charOffset + 1; + return this._characterMapping.getColumn(new DomPosition(spanIndex, offset), spanNodeTextContentLength); } } @@ -606,18 +601,16 @@ class RenderedViewLine implements IRenderedViewLine { return this.getWidth(); } - const partData = this._characterMapping.charOffsetToPartData(column - 1); - const partIndex = CharacterMapping.getPartIndex(partData); - const charOffsetInPart = CharacterMapping.getCharIndex(partData); + const domPosition = this._characterMapping.getDomPosition(column); - const r = RangeUtil.readHorizontalRanges(this._getReadingTarget(domNode), partIndex, charOffsetInPart, partIndex, charOffsetInPart, context.clientRectDeltaLeft, context.endNode); + const r = RangeUtil.readHorizontalRanges(this._getReadingTarget(domNode), domPosition.partIndex, domPosition.charIndex, domPosition.partIndex, domPosition.charIndex, context.clientRectDeltaLeft, context.endNode); if (!r || r.length === 0) { return -1; } const result = r[0].left; if (this.input.isBasicASCII) { - const charOffset = this._characterMapping.getAbsoluteOffsets(); - const expectedResult = Math.round(this.input.spaceWidth * charOffset[column - 1]); + const charOffset = this._characterMapping.getAbsoluteOffset(column); + const expectedResult = Math.round(this.input.spaceWidth * charOffset); if (Math.abs(expectedResult - result) <= 1) { return expectedResult; } @@ -633,15 +626,10 @@ class RenderedViewLine implements IRenderedViewLine { return [new HorizontalRange(0, this.getWidth())]; } - const startPartData = this._characterMapping.charOffsetToPartData(startColumn - 1); - const startPartIndex = CharacterMapping.getPartIndex(startPartData); - const startCharOffsetInPart = CharacterMapping.getCharIndex(startPartData); + const startDomPosition = this._characterMapping.getDomPosition(startColumn); + const endDomPosition = this._characterMapping.getDomPosition(endColumn); - const endPartData = this._characterMapping.charOffsetToPartData(endColumn - 1); - const endPartIndex = CharacterMapping.getPartIndex(endPartData); - const endCharOffsetInPart = CharacterMapping.getCharIndex(endPartData); - - return RangeUtil.readHorizontalRanges(this._getReadingTarget(domNode), startPartIndex, startCharOffsetInPart, endPartIndex, endCharOffsetInPart, context.clientRectDeltaLeft, context.endNode); + return RangeUtil.readHorizontalRanges(this._getReadingTarget(domNode), startDomPosition.partIndex, startDomPosition.charIndex, endDomPosition.partIndex, endDomPosition.charIndex, context.clientRectDeltaLeft, context.endNode); } /** @@ -656,8 +644,7 @@ class RenderedViewLine implements IRenderedViewLine { spanIndex++; } - const charOffset = this._characterMapping.partDataToCharOffset(spanIndex, spanNodeTextContentLength, offset); - return charOffset + 1; + return this._characterMapping.getColumn(new DomPosition(spanIndex, offset), spanNodeTextContentLength); } } diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index ec7c270000f..c4a791e9ab3 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -2513,8 +2513,7 @@ class InlineViewZonesComputer extends ViewZonesComputer { marginDomNode.appendChild(marginElement); } - const absoluteOffsets = output.characterMapping.getAbsoluteOffsets(); - return absoluteOffsets.length > 0 ? absoluteOffsets[absoluteOffsets.length - 1] : 0; + return output.characterMapping.getAbsoluteOffset(output.characterMapping.length); } } diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index d3a46c0ad6f..4d45c26f8fa 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -382,6 +382,7 @@ export interface IEditorOptions { * Suggest options. */ suggest?: ISuggestOptions; + inlineSuggest?: IInlineSuggestOptions; /** * Smart select options. */ @@ -3140,6 +3141,51 @@ class EditorScrollbar extends BaseEditorOption>; + +/** + * Configuration options for inline suggestions + */ +class InlineEditorSuggest extends BaseEditorOption { + constructor() { + const defaults: InternalInlineSuggestOptions = { + enabled: false + }; + + super( + EditorOption.inlineSuggest, 'inlineSuggest', defaults, + { + 'editor.inlineSuggest.enabled': { + type: 'boolean', + default: defaults.enabled, + description: nls.localize('inlineSuggest.enabled', "Controls whether to automatically show inline suggestions in the editor.") + }, + } + ); + } + + public validate(_input: any): InternalInlineSuggestOptions { + if (!_input || typeof _input !== 'object') { + return this.defaultValue; + } + const input = _input as IInlineSuggestOptions; + return { + enabled: boolean(input.enabled, this.defaultValue.enabled), + }; + } +} + +//#endregion + //#region suggest /** @@ -3177,16 +3223,7 @@ export interface ISuggestOptions { /** * Enable or disable the rendering of the suggestion preview. */ - showSuggestionPreview?: boolean; - /** - * Enable or disable the rendering of automatic inline completions. - */ - showInlineCompletions?: boolean; - /** - * Enable or disable the default expansion of the ghost text as used - * by the suggestion preview or the inline completions. - */ - ghostTextExpanded?: boolean; + preview?: boolean; /** * Show details inline with the label. Defaults to true. */ @@ -3318,9 +3355,7 @@ class EditorSuggest extends BaseEditorOption>> CharacterMappingConstants.PART_INDEX_OFFSET; } - public static getCharIndex(partData: number): number { + private static getCharIndex(partData: number): number { return (partData & CharacterMappingConstants.CHAR_INDEX_MASK) >>> CharacterMappingConstants.CHAR_INDEX_OFFSET; } @@ -240,20 +247,24 @@ export class CharacterMapping { this._absoluteOffsets = new Uint32Array(this.length); } - public setPartData(charOffset: number, partIndex: number, charIndex: number, partAbsoluteOffset: number): void { - let partData = ( + public setColumnInfo(column: number, partIndex: number, charIndex: number, partAbsoluteOffset: number): void { + const partData = ( (partIndex << CharacterMappingConstants.PART_INDEX_OFFSET) | (charIndex << CharacterMappingConstants.CHAR_INDEX_OFFSET) ) >>> 0; - this._data[charOffset] = partData; - this._absoluteOffsets[charOffset] = partAbsoluteOffset + charIndex; + this._data[column - 1] = partData; + this._absoluteOffsets[column - 1] = partAbsoluteOffset + charIndex; } - public getAbsoluteOffsets(): Uint32Array { - return this._absoluteOffsets; + public getAbsoluteOffset(column: number): number { + if (this._absoluteOffsets.length === 0) { + // No characters on this line + return 0; + } + return this._absoluteOffsets[column - 1]; } - public charOffsetToPartData(charOffset: number): number { + private charOffsetToPartData(charOffset: number): number { if (this.length === 0) { return 0; } @@ -266,7 +277,19 @@ export class CharacterMapping { return this._data[charOffset]; } - public partDataToCharOffset(partIndex: number, partLength: number, charIndex: number): number { + public getDomPosition(column: number): DomPosition { + const partData = this.charOffsetToPartData(column - 1); + const partIndex = CharacterMapping.getPartIndex(partData); + const charIndex = CharacterMapping.getCharIndex(partData); + return new DomPosition(partIndex, charIndex); + } + + public getColumn(domPosition: DomPosition, partLength: number): number { + const charOffset = this.partDataToCharOffset(domPosition.partIndex, partLength, domPosition.charIndex); + return charOffset + 1; + } + + private partDataToCharOffset(partIndex: number, partLength: number, charIndex: number): number { if (this.length === 0) { return 0; } @@ -377,7 +400,7 @@ export function renderViewLine(input: RenderLineInput, sb: IStringBuilder): Rend sb.appendASCIIString(``); const characterMapping = new CharacterMapping(1, beforeCount + afterCount); - characterMapping.setPartData(0, beforeCount, 0, 0); + characterMapping.setColumnInfo(1, beforeCount, 0, 0); return new RenderLineOutput( characterMapping, @@ -884,7 +907,7 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render sb.appendASCII(CharCode.GreaterThan); for (; charIndex < partEndIndex; charIndex++) { - characterMapping.setPartData(charIndex, partIndex - partDisplacement, charOffsetInPart, partAbsoluteOffset); + characterMapping.setColumnInfo(charIndex + 1, partIndex - partDisplacement, charOffsetInPart, partAbsoluteOffset); partDisplacement = 0; const charCode = lineContent.charCodeAt(charIndex); let charWidth: number; @@ -922,7 +945,7 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render sb.appendASCII(CharCode.GreaterThan); for (; charIndex < partEndIndex; charIndex++) { - characterMapping.setPartData(charIndex, partIndex - partDisplacement, charOffsetInPart, partAbsoluteOffset); + characterMapping.setColumnInfo(charIndex + 1, partIndex - partDisplacement, charOffsetInPart, partAbsoluteOffset); partDisplacement = 0; const charCode = lineContent.charCodeAt(charIndex); @@ -1003,7 +1026,7 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render if (charIndex >= len && !lastCharacterMappingDefined && part.isPseudoAfter()) { lastCharacterMappingDefined = true; - characterMapping.setPartData(charIndex, partIndex, charOffsetInPart, partAbsoluteOffset); + characterMapping.setColumnInfo(charIndex + 1, partIndex, charOffsetInPart, partAbsoluteOffset); } sb.appendASCIIString(''); @@ -1013,7 +1036,7 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render if (!lastCharacterMappingDefined) { // When getting client rects for the last character, we will position the // text range at the end of the span, insteaf of at the beginning of next span - characterMapping.setPartData(len, parts.length - 1, charOffsetInPart, partAbsoluteOffset); + characterMapping.setColumnInfo(len + 1, parts.length - 1, charOffsetInPart, partAbsoluteOffset); } if (isOverflowing) { diff --git a/src/vs/editor/contrib/hover/markdownHoverParticipant.ts b/src/vs/editor/contrib/hover/markdownHoverParticipant.ts index ee67c5c3e42..ae3052eb3fa 100644 --- a/src/vs/editor/contrib/hover/markdownHoverParticipant.ts +++ b/src/vs/editor/contrib/hover/markdownHoverParticipant.ts @@ -19,6 +19,7 @@ import { HoverProviderRegistry } from 'vs/editor/common/modes'; import { getHover } from 'vs/editor/contrib/hover/getHover'; import { Position } from 'vs/editor/common/core/position'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; const $ = dom.$; @@ -46,12 +47,10 @@ export class MarkdownHoverParticipant implements IEditorHoverParticipant('editor.maxTokenizationLineLength'); + if (lineLength >= maxTokenizationLineLength) { + result.push(new MarkdownHover(this, new Range(lineNumber, 1, lineNumber, lineLength + 1), [{ + value: nls.localize('too many characters', "Tokenization is skipped for long lines for performance reasons. This can be configured via `editor.maxTokenizationLineLength`.") + }])); + } + return result; } diff --git a/src/vs/editor/contrib/inlineCompletions/ghostTextController.ts b/src/vs/editor/contrib/inlineCompletions/ghostTextController.ts index eb047374487..9c8bc623d9e 100644 --- a/src/vs/editor/contrib/inlineCompletions/ghostTextController.ts +++ b/src/vs/editor/contrib/inlineCompletions/ghostTextController.ts @@ -19,8 +19,8 @@ import { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/c import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; export class GhostTextController extends Disposable { - public static readonly inlineCompletionsVisible = new RawContextKey('inlineCompletionsVisible ', false, nls.localize('inlineCompletionsVisible', "Whether inline suggestions are visible")); - public static readonly inlineCompletionSuggestsIndentation = new RawContextKey('inlineCompletionSuggestsIndentation', false, nls.localize('inlineCompletionSuggestsIndentation', "Whether the inline suggestion suggests extending indentation")); + public static readonly inlineSuggestionVisible = new RawContextKey('inlineSuggestionVisible ', false, nls.localize('inlineSuggestionVisible', "Whether an inline suggestion is visible")); + public static readonly inlineSuggestionHasIndentation = new RawContextKey('inlineSuggestionHasIndentation', false, nls.localize('inlineSuggestionHasIndentation', "Whether the inline suggestion starts with whitespace")); static ID = 'editor.contrib.ghostTextController'; @@ -34,7 +34,7 @@ export class GhostTextController extends Disposable { private triggeredExplicitly = false; constructor( - private readonly editor: ICodeEditor, + public readonly editor: ICodeEditor, @IInstantiationService private readonly instantiationService: IInstantiationService, @IContextKeyService contextKeyService: IContextKeyService, ) { @@ -57,11 +57,12 @@ export class GhostTextController extends Disposable { // Don't call this method when not neccessary. It will recreate the activeController. private updateModelController(): void { const suggestOptions = this.editor.getOption(EditorOption.suggest); + const inlineSuggestOptions = this.editor.getOption(EditorOption.inlineSuggest); this.activeController.value = undefined; // ActiveGhostTextController is only created if one of those settings is set or if the inline completions are triggered explicitly. this.activeController.value = - this.editor.hasModel() && (suggestOptions.showSuggestionPreview || suggestOptions.showInlineCompletions || this.triggeredExplicitly) + this.editor.hasModel() && (suggestOptions.preview || inlineSuggestOptions.enabled || this.triggeredExplicitly) ? this.instantiationService.createInstance( ActiveGhostTextController, this.editor, @@ -109,10 +110,10 @@ export class GhostTextController extends Disposable { // Currently the global state is updated directly, which may lead to conflicts if multiple ghost texts are active. class GhostTextContextKeys { private lastInlineCompletionVisibleValue = false; - private readonly inlineCompletionVisible = GhostTextController.inlineCompletionsVisible.bindTo(this.contextKeyService); + private readonly inlineCompletionVisible = GhostTextController.inlineSuggestionVisible.bindTo(this.contextKeyService); private lastInlineCompletionSuggestsIndentationValue = false; - private readonly inlineCompletionSuggestsIndentation = GhostTextController.inlineCompletionSuggestsIndentation.bindTo(this.contextKeyService); + private readonly inlineCompletionSuggestsIndentation = GhostTextController.inlineSuggestionHasIndentation.bindTo(this.contextKeyService); constructor(private readonly contextKeyService: IContextKeyService) { } @@ -158,6 +159,9 @@ export class ActiveGhostTextController extends Disposable { this._register(this.suggestWidgetAdapterModel.onDidChange(() => { this.updateModel(); + // When the suggest widget becomes inactive and an inline completion + // becomes visible, we need to update the context keys. + this.updateContextKeys(); })); this.updateModel(); @@ -237,11 +241,11 @@ export class ActiveGhostTextController extends Disposable { const GhostTextCommand = EditorCommand.bindToContribution(GhostTextController.get); -registerEditorCommand(new GhostTextCommand({ - id: 'commitInlineCompletion', +export const commitInlineSuggestionAction = new GhostTextCommand({ + id: 'editor.action.inlineSuggest.commit', precondition: ContextKeyExpr.and( - GhostTextController.inlineCompletionsVisible, - GhostTextController.inlineCompletionSuggestsIndentation.toNegated(), + GhostTextController.inlineSuggestionVisible, + GhostTextController.inlineSuggestionHasIndentation.toNegated(), EditorContextKeys.tabMovesFocus.toNegated() ), kbOpts: { @@ -250,12 +254,14 @@ registerEditorCommand(new GhostTextCommand({ }, handler(x) { x.commit(); + x.editor.focus(); } -})); +}); +registerEditorCommand(commitInlineSuggestionAction); registerEditorCommand(new GhostTextCommand({ - id: 'hideInlineCompletion', - precondition: GhostTextController.inlineCompletionsVisible, + id: 'editor.action.inlineSuggest.hide', + precondition: GhostTextController.inlineSuggestionVisible, kbOpts: { weight: 100, primary: KeyCode.Escape, @@ -265,13 +271,13 @@ registerEditorCommand(new GhostTextCommand({ } })); -export class ShowNextInlineCompletionAction extends EditorAction { - public static ID = 'editor.action.showNextInlineCompletion'; +export class ShowNextInlineSuggestionAction extends EditorAction { + public static ID = 'editor.action.inlineSuggest.showNext'; constructor() { super({ - id: ShowNextInlineCompletionAction.ID, - label: nls.localize('showNextInlineCompletion', "Show Next Inline Completion"), - alias: 'Show Next Inline Completion', + id: ShowNextInlineSuggestionAction.ID, + label: nls.localize('action.inlineSuggest.showNext', "Show Next Inline Suggestion"), + alias: 'Show Next Inline Suggestion', precondition: EditorContextKeys.writable, kbOpts: { weight: 100, @@ -289,13 +295,13 @@ export class ShowNextInlineCompletionAction extends EditorAction { } } -export class ShowPreviousInlineCompletionAction extends EditorAction { - public static ID = 'editor.action.showPreviousInlineCompletion'; +export class ShowPreviousInlineSuggestionAction extends EditorAction { + public static ID = 'editor.action.inlineSuggest.showPrevious'; constructor() { super({ - id: ShowPreviousInlineCompletionAction.ID, - label: nls.localize('showPreviousInlineCompletion', "Show Previous Inline Completion"), - alias: 'Show Previous Inline Completion', + id: ShowPreviousInlineSuggestionAction.ID, + label: nls.localize('action.inlineSuggest.showPrevious', "Show Previous Inline Suggestion"), + alias: 'Show Previous Inline Suggestion', precondition: EditorContextKeys.writable, kbOpts: { weight: 100, @@ -313,12 +319,12 @@ export class ShowPreviousInlineCompletionAction extends EditorAction { } } -export class TriggerInlineCompletionsAction extends EditorAction { +export class TriggerInlineSuggestionAction extends EditorAction { constructor() { super({ - id: 'editor.action.triggerInlineCompletions', - label: nls.localize('triggerInlineCompletionsAction', "Trigger Inline Completions"), - alias: 'Trigger Inline Completions', + id: 'editor.action.inlineSuggest.trigger', + label: nls.localize('action.inlineSuggest.trigger', "Trigger Inline Suggestion"), + alias: 'Trigger Inline Suggestion', precondition: EditorContextKeys.writable }); } @@ -332,6 +338,6 @@ export class TriggerInlineCompletionsAction extends EditorAction { } registerEditorContribution(GhostTextController.ID, GhostTextController); -registerEditorAction(TriggerInlineCompletionsAction); -registerEditorAction(ShowNextInlineCompletionAction); -registerEditorAction(ShowPreviousInlineCompletionAction); +registerEditorAction(TriggerInlineSuggestionAction); +registerEditorAction(ShowNextInlineSuggestionAction); +registerEditorAction(ShowPreviousInlineSuggestionAction); diff --git a/src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts b/src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts index 31b16107d49..c2e22b18008 100644 --- a/src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts +++ b/src/vs/editor/contrib/inlineCompletions/ghostTextWidget.ts @@ -52,7 +52,9 @@ export abstract class BaseGhostTextWidgetModel extends Disposable implements Gho public get expanded() { if (this._expanded === undefined) { - return this.editor.getOption(EditorOption.suggest).ghostTextExpanded; + // TODO this should use a global hidden setting. + // See https://github.com/microsoft/vscode/issues/125037. + return true; } return this._expanded; } diff --git a/src/vs/editor/contrib/inlineCompletions/inlineCompletionsHoverParticipant.ts b/src/vs/editor/contrib/inlineCompletions/inlineCompletionsHoverParticipant.ts index e3d89be22cf..a7ec7b3aa39 100644 --- a/src/vs/editor/contrib/inlineCompletions/inlineCompletionsHoverParticipant.ts +++ b/src/vs/editor/contrib/inlineCompletions/inlineCompletionsHoverParticipant.ts @@ -9,7 +9,7 @@ import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/brows import { Range } from 'vs/editor/common/core/range'; import { IModelDecoration } from 'vs/editor/common/model'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { GhostTextController, ShowNextInlineCompletionAction, ShowPreviousInlineCompletionAction } from 'vs/editor/contrib/inlineCompletions/ghostTextController'; +import { commitInlineSuggestionAction, GhostTextController, ShowNextInlineSuggestionAction, ShowPreviousInlineSuggestionAction } from 'vs/editor/contrib/inlineCompletions/ghostTextController'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { IMenuService, MenuId, MenuItemAction } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; @@ -82,14 +82,19 @@ export class InlineCompletionsHoverParticipant implements IEditorHoverParticipan ); statusBar.addAction({ - label: nls.localize('showNextInlineCompletion', "Next"), - commandId: ShowNextInlineCompletionAction.ID, - run: () => this._commandService.executeCommand(ShowNextInlineCompletionAction.ID) + label: nls.localize('showNextInlineSuggestion', "Next"), + commandId: ShowNextInlineSuggestionAction.ID, + run: () => this._commandService.executeCommand(ShowNextInlineSuggestionAction.ID) }); statusBar.addAction({ - label: nls.localize('showPreviousInlineCompletion', "Previous"), - commandId: ShowPreviousInlineCompletionAction.ID, - run: () => this._commandService.executeCommand(ShowPreviousInlineCompletionAction.ID) + label: nls.localize('showPreviousInlineSuggestion', "Previous"), + commandId: ShowPreviousInlineSuggestionAction.ID, + run: () => this._commandService.executeCommand(ShowPreviousInlineSuggestionAction.ID) + }); + statusBar.addAction({ + label: nls.localize('acceptInlineSuggestion', "Accept"), + commandId: commitInlineSuggestionAction.id, + run: () => this._commandService.executeCommand(commitInlineSuggestionAction.id) }); for (const [_, group] of menu.getActions()) { diff --git a/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts index 89e0bfd7577..6f73b7b64f4 100644 --- a/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/inlineCompletionsModel.ts @@ -19,6 +19,8 @@ import { EditOperation } from 'vs/editor/common/core/editOperation'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { MutableDisposable } from 'vs/editor/contrib/inlineCompletions/utils'; +import { RedoCommand, UndoCommand } from 'vs/editor/browser/editorExtensions'; +import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands'; export class InlineCompletionsModel extends Disposable implements GhostTextWidgetModel { protected readonly onDidChangeEmitter = new Emitter(); @@ -36,7 +38,13 @@ export class InlineCompletionsModel extends Disposable implements GhostTextWidge this._register(commandService.onDidExecuteCommand(e => { // These commands don't trigger onDidType. - const commands = new Set(['undo', 'redo', 'tab']); + const commands = new Set([ + UndoCommand.id, + RedoCommand.id, + CoreEditingCommands.Tab.id, + CoreEditingCommands.DeleteLeft.id, + CoreEditingCommands.DeleteRight.id + ]); if (commands.has(e.commandId) && editor.hasTextFocus()) { this.handleUserInput(); } @@ -91,8 +99,8 @@ export class InlineCompletionsModel extends Disposable implements GhostTextWidge } private startSessionIfTriggered(): void { - const suggestOptions = this.editor.getOption(EditorOption.suggest); - if (!suggestOptions.showInlineCompletions) { + const suggestOptions = this.editor.getOption(EditorOption.inlineSuggest); + if (!suggestOptions.enabled) { return; } @@ -286,6 +294,9 @@ class InlineCompletionsSession extends BaseGhostTextWidgetModel { } public scheduleAutomaticUpdate(): void { + // Since updateSoon debounces, starvation can happen. + // To prevent stale cache, we clear the current update operation. + this.updateOperation.clear(); this.updateSoon.schedule(); } @@ -346,7 +357,7 @@ class InlineCompletionsSession extends BaseGhostTextWidgetModel { const cache = this.cache.replace(undefined); this.editor.executeEdits( - 'inlineCompletions.accept', + 'inlineSuggestion.accept', [ EditOperation.replaceMove(completion.range, completion.text) ] @@ -358,6 +369,8 @@ class InlineCompletionsSession extends BaseGhostTextWidgetModel { cache?.dispose(); }) .then(undefined, onUnexpectedExternalError); + } else { + cache?.dispose(); } this.onDidChangeEmitter.fire(); @@ -467,15 +480,20 @@ export function inlineCompletionToGhostText(inlineCompletion: NormalizedInlineCo // "\t\tfoo" -> "\t\t\tfoobar" (+"\t", +"bar") // "\t\tfoo" -> "\tfoobar" (-"\t", +"\bar") + const firstNonWsCol = textModel.getLineFirstNonWhitespaceColumn(inlineCompletion.range.startLineNumber); + if (inlineCompletion.text.startsWith(valueToBeReplaced)) { remainingInsertText = inlineCompletion.text.substr(valueToBeReplaced.length); - } else { + } else if (firstNonWsCol === 0 || inlineCompletion.range.startColumn < firstNonWsCol) { + // Only allow ignoring leading whitespace in indentation. const valueToBeReplacedTrimmed = leftTrim(valueToBeReplaced); const insertTextTrimmed = leftTrim(inlineCompletion.text); if (!insertTextTrimmed.startsWith(valueToBeReplacedTrimmed)) { return undefined; } remainingInsertText = insertTextTrimmed.substr(valueToBeReplacedTrimmed.length); + } else { + return undefined; } const position = inlineCompletion.range.getEndPosition(); diff --git a/src/vs/editor/contrib/inlineCompletions/suggestWidgetAdapterModel.ts b/src/vs/editor/contrib/inlineCompletions/suggestWidgetAdapterModel.ts index 75a7c065350..eac1ad30f53 100644 --- a/src/vs/editor/contrib/inlineCompletions/suggestWidgetAdapterModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/suggestWidgetAdapterModel.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { RunOnceScheduler } from 'vs/base/common/async'; import { Event } from 'vs/base/common/event'; import { toDisposable } from 'vs/base/common/lifecycle'; import { IActiveCodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -20,10 +21,21 @@ import { ISelectedSuggestion } from 'vs/editor/contrib/suggest/suggestWidget'; export class SuggestWidgetAdapterModel extends BaseGhostTextWidgetModel { private isSuggestWidgetVisible: boolean = false; private currentGhostText: GhostText | undefined = undefined; + private _isActive: boolean = false; public override minReservedLineCount: number = 0; - public get isActive() { return this.isSuggestWidgetVisible; } + public get isActive() { return this._isActive; } + + // This delay fixes an suggest widget issue when typing "." immediately restarts the suggestion session. + private setInactiveDelayed = this._register(new RunOnceScheduler(() => { + if (!this.isSuggestWidgetVisible) { + if (this.isActive) { + this._isActive = false; + this.onDidChangeEmitter.fire(); + } + } + }, 100)); constructor( editor: IActiveCodeEditor @@ -41,15 +53,18 @@ export class SuggestWidgetAdapterModel extends BaseGhostTextWidgetModel { this._register(suggestController.widget.value.onDidShow(() => { this.isSuggestWidgetVisible = true; + this._isActive = true; this.updateFromSuggestion(); })); this._register(suggestController.widget.value.onDidHide(() => { this.isSuggestWidgetVisible = false; + this.setInactiveDelayed.schedule(); this.minReservedLineCount = 0; this.updateFromSuggestion(); })); this._register(suggestController.widget.value.onDidFocus(() => { this.isSuggestWidgetVisible = true; + this._isActive = true; this.updateFromSuggestion(); })); }; @@ -75,7 +90,7 @@ export class SuggestWidgetAdapterModel extends BaseGhostTextWidgetModel { private isSuggestionPreviewEnabled(): boolean { const suggestOptions = this.editor.getOption(EditorOption.suggest); - return suggestOptions.showSuggestionPreview; + return suggestOptions.preview; } private updateFromSuggestion(): void { diff --git a/src/vs/editor/test/common/diff/diffComputer.test.ts b/src/vs/editor/test/common/diff/diffComputer.test.ts index a620a8c3ec5..c63da46f810 100644 --- a/src/vs/editor/test/common/diff/diffComputer.test.ts +++ b/src/vs/editor/test/common/diff/diffComputer.test.ts @@ -916,4 +916,55 @@ suite('Editor Diff - DiffComputer', () => { ]; assertDiff(original, modified, expected, false, false, false); }); + + test('issue #121436: Diff chunk contains an unchanged line part 1', () => { + const original = [ + 'if (cond) {', + ' cmd', + '}', + ]; + const modified = [ + 'if (cond) {', + ' if (other_cond) {', + ' cmd', + ' }', + '}', + ]; + const expected = [ + createLineChange( + 1, 0, 2, 2 + ), + createLineChange( + 2, 0, 4, 4 + ) + ]; + assertDiff(original, modified, expected, false, false, true); + }); + + test('issue #121436: Diff chunk contains an unchanged line part 2', () => { + const original = [ + 'if (cond) {', + ' cmd', + '}', + ]; + const modified = [ + 'if (cond) {', + ' if (other_cond) {', + ' cmd', + ' }', + '}', + ]; + const expected = [ + createLineChange( + 1, 0, 2, 2 + ), + createLineChange( + 2, 2, 3, 3 + ), + createLineChange( + 2, 0, 4, 4 + ) + ]; + assertDiff(original, modified, expected, false, false, false); + }); }); diff --git a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts index ed5e03b5bd8..27657cf1cc8 100644 --- a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts +++ b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts @@ -9,7 +9,7 @@ import * as strings from 'vs/base/common/strings'; import { IViewLineTokens } from 'vs/editor/common/core/lineTokens'; import { MetadataConsts } from 'vs/editor/common/modes'; import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations'; -import { CharacterMapping, RenderLineInput, renderViewLine2 as renderViewLine, LineRange } from 'vs/editor/common/viewLayout/viewLineRenderer'; +import { CharacterMapping, RenderLineInput, renderViewLine2 as renderViewLine, LineRange, DomPosition } from 'vs/editor/common/viewLayout/viewLineRenderer'; import { InlineDecorationType } from 'vs/editor/common/viewModel/viewModel'; import { ViewLineToken, ViewLineTokens } from 'vs/editor/test/common/core/viewLineToken'; @@ -25,8 +25,8 @@ function createPart(endIndex: number, foreground: number): ViewLineToken { suite('viewLineRenderer.renderLine', () => { - function assertCharacterReplacement(lineContent: string, tabSize: number, expected: string, expectedCharOffsetInPart: number[][], expectedPartLengts: number[]): void { - let _actual = renderViewLine(new RenderLineInput( + function assertCharacterReplacement(lineContent: string, tabSize: number, expected: string, expectedCharOffsetInPart: number[]): void { + const _actual = renderViewLine(new RenderLineInput( false, true, lineContent, @@ -49,36 +49,37 @@ suite('viewLineRenderer.renderLine', () => { )); assert.strictEqual(_actual.html, '' + expected + ''); - assertCharacterMapping(_actual.characterMapping, expectedCharOffsetInPart, expectedPartLengts); + const info = expectedCharOffsetInPart.map((absoluteOffset) => [absoluteOffset, [0, absoluteOffset]]); + assertCharacterMapping3(_actual.characterMapping, info); } test('replaces spaces', () => { - assertCharacterReplacement(' ', 4, '\u00a0', [[0, 1]], [1]); - assertCharacterReplacement(' ', 4, '\u00a0\u00a0', [[0, 1, 2]], [2]); - assertCharacterReplacement('a b', 4, 'a\u00a0\u00a0b', [[0, 1, 2, 3, 4]], [4]); + assertCharacterReplacement(' ', 4, '\u00a0', [0, 1]); + assertCharacterReplacement(' ', 4, '\u00a0\u00a0', [0, 1, 2]); + assertCharacterReplacement('a b', 4, 'a\u00a0\u00a0b', [0, 1, 2, 3, 4]); }); test('escapes HTML markup', () => { - assertCharacterReplacement('ab', 4, 'a>b', [[0, 1, 2, 3]], [3]); - assertCharacterReplacement('a&b', 4, 'a&b', [[0, 1, 2, 3]], [3]); + assertCharacterReplacement('ab', 4, 'a>b', [0, 1, 2, 3]); + assertCharacterReplacement('a&b', 4, 'a&b', [0, 1, 2, 3]); }); test('replaces some bad characters', () => { - assertCharacterReplacement('a\0b', 4, 'a�b', [[0, 1, 2, 3]], [3]); - assertCharacterReplacement('a' + String.fromCharCode(CharCode.UTF8_BOM) + 'b', 4, 'a\ufffdb', [[0, 1, 2, 3]], [3]); - assertCharacterReplacement('a\u2028b', 4, 'a\ufffdb', [[0, 1, 2, 3]], [3]); + assertCharacterReplacement('a\0b', 4, 'a�b', [0, 1, 2, 3]); + assertCharacterReplacement('a' + String.fromCharCode(CharCode.UTF8_BOM) + 'b', 4, 'a\ufffdb', [0, 1, 2, 3]); + assertCharacterReplacement('a\u2028b', 4, 'a\ufffdb', [0, 1, 2, 3]); }); test('handles tabs', () => { - assertCharacterReplacement('\t', 4, '\u00a0\u00a0\u00a0\u00a0', [[0, 4]], [4]); - assertCharacterReplacement('x\t', 4, 'x\u00a0\u00a0\u00a0', [[0, 1, 4]], [4]); - assertCharacterReplacement('xx\t', 4, 'xx\u00a0\u00a0', [[0, 1, 2, 4]], [4]); - assertCharacterReplacement('xxx\t', 4, 'xxx\u00a0', [[0, 1, 2, 3, 4]], [4]); - assertCharacterReplacement('xxxx\t', 4, 'xxxx\u00a0\u00a0\u00a0\u00a0', [[0, 1, 2, 3, 4, 8]], [8]); + assertCharacterReplacement('\t', 4, '\u00a0\u00a0\u00a0\u00a0', [0, 4]); + assertCharacterReplacement('x\t', 4, 'x\u00a0\u00a0\u00a0', [0, 1, 4]); + assertCharacterReplacement('xx\t', 4, 'xx\u00a0\u00a0', [0, 1, 2, 4]); + assertCharacterReplacement('xxx\t', 4, 'xxx\u00a0', [0, 1, 2, 3, 4]); + assertCharacterReplacement('xxxx\t', 4, 'xxxx\u00a0\u00a0\u00a0\u00a0', [0, 1, 2, 3, 4, 8]); }); - function assertParts(lineContent: string, tabSize: number, parts: ViewLineToken[], expected: string, expectedCharOffsetInPart: number[][], expectedPartLengts: number[]): void { + function assertParts(lineContent: string, tabSize: number, parts: ViewLineToken[], expected: string, info: CharacterMappingInfo[]): void { let _actual = renderViewLine(new RenderLineInput( false, true, @@ -102,23 +103,23 @@ suite('viewLineRenderer.renderLine', () => { )); assert.strictEqual(_actual.html, '' + expected + ''); - assertCharacterMapping(_actual.characterMapping, expectedCharOffsetInPart, expectedPartLengts); + assertCharacterMapping3(_actual.characterMapping, info); } test('empty line', () => { - assertParts('', 4, [], '', [], []); + assertParts('', 4, [], '', []); }); test('uses part type', () => { - assertParts('x', 4, [createPart(1, 10)], 'x', [[0, 1]], [1]); - assertParts('x', 4, [createPart(1, 20)], 'x', [[0, 1]], [1]); - assertParts('x', 4, [createPart(1, 30)], 'x', [[0, 1]], [1]); + assertParts('x', 4, [createPart(1, 10)], 'x', [[0, [0, 0]], [1, [0, 1]]]); + assertParts('x', 4, [createPart(1, 20)], 'x', [[0, [0, 0]], [1, [0, 1]]]); + assertParts('x', 4, [createPart(1, 30)], 'x', [[0, [0, 0]], [1, [0, 1]]]); }); test('two parts', () => { - assertParts('xy', 4, [createPart(1, 1), createPart(2, 2)], 'xy', [[0], [0, 1]], [1, 1]); - assertParts('xyz', 4, [createPart(1, 1), createPart(3, 2)], 'xyz', [[0], [0, 1, 2]], [1, 2]); - assertParts('xyz', 4, [createPart(2, 1), createPart(3, 2)], 'xyz', [[0, 1], [0, 1]], [2, 1]); + assertParts('xy', 4, [createPart(1, 1), createPart(2, 2)], 'xy', [[0, [0, 0]], [1, [1, 0]], [2, [1, 1]]]); + assertParts('xyz', 4, [createPart(1, 1), createPart(3, 2)], 'xyz', [[0, [0, 0]], [1, [1, 0]], [2, [1, 1]], [3, [1, 2]]]); + assertParts('xyz', 4, [createPart(2, 1), createPart(3, 2)], 'xyz', [[0, [0, 0]], [1, [0, 1]], [2, [1, 0]], [3, [1, 1]]]); }); test('overflow', () => { @@ -168,16 +169,17 @@ suite('viewLineRenderer.renderLine', () => { ].join(''); assert.strictEqual(_actual.html, '' + expectedOutput + ''); - assertCharacterMapping(_actual.characterMapping, + assertCharacterMapping3( + _actual.characterMapping, [ - [0], - [0], - [0], - [0], - [0], - [0, 1], - ], - [1, 1, 1, 1, 1, 1] + [0, [0, 0]], + [1, [1, 0]], + [2, [2, 0]], + [3, [3, 0]], + [4, [4, 0]], + [5, [5, 0]], + [6, [5, 1]], + ] ); }); @@ -213,24 +215,25 @@ suite('viewLineRenderer.renderLine', () => { '\u00b7\u00b7', '\u00b7\u00b7\u00b7' ].join(''); - let expectedOffsetsArr = [ - [0], - [0, 1, 2, 3], - [0, 1, 2, 3, 4, 5], - [0], - [0, 1, 2, 3, 4], - [0], - [0, 1, 2, 3], - [0], - [0], - [0], - [0, 1, 2], - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14], - [0, 1], - [0, 1, 2, 3], + + const info: CharacterMappingInfo[] = [ + [0, [0, 0]], + [4, [1, 0]], [5, [1, 1]], [6, [1, 2]], [7, [1, 3]], + [8, [2, 0]], [9, [2, 1]], [10, [2, 2]], [11, [2, 3]], [12, [2, 4]], [13, [2, 5]], + [14, [3, 0]], + [15, [4, 0]], [16, [4, 1]], [17, [4, 2]], [18, [4, 3]], [19, [4, 4]], + [20, [5, 0]], + [21, [6, 0]], [22, [6, 1]], [23, [6, 2]], [24, [6, 3]], + [25, [7, 0]], + [26, [8, 0]], + [27, [9, 0]], + [28, [10, 0]], [29, [10, 1]], [30, [10, 2]], + [31, [11, 0]], [32, [11, 1]], [33, [11, 2]], [34, [11, 3]], [35, [11, 4]], [36, [11, 5]], [37, [11, 6]], [38, [11, 7]], [39, [11, 8]], [40, [11, 9]], [41, [11, 10]], [42, [11, 11]], [43, [11, 12]], [44, [11, 13]], [45, [11, 14]], + [46, [12, 0]], [47, [12, 1]], + [48, [13, 0]], [49, [13, 1]], [50, [13, 2]], [51, [13, 3]], ]; - let _actual = renderViewLine(new RenderLineInput( + const _actual = renderViewLine(new RenderLineInput( false, true, lineText, @@ -253,7 +256,7 @@ suite('viewLineRenderer.renderLine', () => { )); assert.strictEqual(_actual.html, '' + expectedOutput + ''); - assertCharacterMapping(_actual.characterMapping, expectedOffsetsArr, [4, 4, 6, 1, 5, 1, 4, 1, 1, 1, 3, 15, 2, 3]); + assertCharacterMapping3(_actual.characterMapping, info); }); test('issue #2255: Weird line rendering part 1', () => { @@ -283,20 +286,21 @@ suite('viewLineRenderer.renderLine', () => { ')', ',', ].join(''); - let expectedOffsetsArr = [ - [0, 4, 8], // 3 chars - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], // 12 chars - [0, 4, 8, 12, 16, 20], // 6 chars - [0], // 1 char - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], // 21 chars - [0, 1], // 2 chars - [0], // 1 char - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], // 20 chars - [0], // 1 char - [0, 1] // 2 chars + + const info: CharacterMappingInfo[] = [ + [0, [0, 0]], [4, [0, 4]], [8, [0, 8]], + [12, [1, 0]], [13, [1, 1]], [14, [1, 2]], [15, [1, 3]], [16, [1, 4]], [17, [1, 5]], [18, [1, 6]], [19, [1, 7]], [20, [1, 8]], [21, [1, 9]], [22, [1, 10]], [23, [1, 11]], + [24, [2, 0]], [28, [2, 4]], [32, [2, 8]], [36, [2, 12]], [40, [2, 16]], [44, [2, 20]], + [48, [3, 0]], + [49, [4, 0]], [50, [4, 1]], [51, [4, 2]], [52, [4, 3]], [53, [4, 4]], [54, [4, 5]], [55, [4, 6]], [56, [4, 7]], [57, [4, 8]], [58, [4, 9]], [59, [4, 10]], [60, [4, 11]], [61, [4, 12]], [62, [4, 13]], [63, [4, 14]], [64, [4, 15]], [65, [4, 16]], [66, [4, 17]], [67, [4, 18]], [68, [4, 19]], [69, [4, 20]], + [70, [5, 0]], [71, [5, 1]], + [72, [6, 0]], + [73, [7, 0]], [74, [7, 1]], [75, [7, 2]], [76, [7, 3]], [77, [7, 4]], [78, [7, 5]], [79, [7, 6]], [80, [7, 7]], [81, [7, 8]], [82, [7, 9]], [83, [7, 10]], [84, [7, 11]], [85, [7, 12]], [86, [7, 13]], [87, [7, 14]], [88, [7, 15]], [89, [7, 16]], [90, [7, 17]], [91, [7, 18]], [92, [7, 19]], + [93, [8, 0]], + [94, [9, 0]], [95, [9, 1]], ]; - let _actual = renderViewLine(new RenderLineInput( + const _actual = renderViewLine(new RenderLineInput( false, true, lineText, @@ -319,7 +323,7 @@ suite('viewLineRenderer.renderLine', () => { )); assert.strictEqual(_actual.html, '' + expectedOutput + ''); - assertCharacterMapping(_actual.characterMapping, expectedOffsetsArr, [12, 12, 24, 1, 21, 2, 1, 20, 1, 1]); + assertCharacterMapping3(_actual.characterMapping, info); }); test('issue #2255: Weird line rendering part 2', () => { @@ -349,20 +353,21 @@ suite('viewLineRenderer.renderLine', () => { ')', ',', ].join(''); - let expectedOffsetsArr = [ - [0, 1, 4, 8], // 4 chars - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], // 12 chars - [0, 4, 8, 12, 16, 20], // 6 chars - [0], // 1 char - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20], // 21 chars - [0, 1], // 2 chars - [0], // 1 char - [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19], // 20 chars - [0], // 1 char - [0, 1] // 2 chars + + const info: CharacterMappingInfo[] = [ + [0, [0, 0]], [1, [0, 1]], [4, [0, 4]], [8, [0, 8]], + [12, [1, 0]], [13, [1, 1]], [14, [1, 2]], [15, [1, 3]], [16, [1, 4]], [17, [1, 5]], [18, [1, 6]], [19, [1, 7]], [20, [1, 8]], [21, [1, 9]], [22, [1, 10]], [23, [1, 11]], + [24, [2, 0]], [28, [2, 4]], [32, [2, 8]], [36, [2, 12]], [40, [2, 16]], [44, [2, 20]], + [48, [3, 0]], + [49, [4, 0]], [50, [4, 1]], [51, [4, 2]], [52, [4, 3]], [53, [4, 4]], [54, [4, 5]], [55, [4, 6]], [56, [4, 7]], [57, [4, 8]], [58, [4, 9]], [59, [4, 10]], [60, [4, 11]], [61, [4, 12]], [62, [4, 13]], [63, [4, 14]], [64, [4, 15]], [65, [4, 16]], [66, [4, 17]], [67, [4, 18]], [68, [4, 19]], [69, [4, 20]], + [70, [5, 0]], [71, [5, 1]], + [72, [6, 0]], + [73, [7, 0]], [74, [7, 1]], [75, [7, 2]], [76, [7, 3]], [77, [7, 4]], [78, [7, 5]], [79, [7, 6]], [80, [7, 7]], [81, [7, 8]], [82, [7, 9]], [83, [7, 10]], [84, [7, 11]], [85, [7, 12]], [86, [7, 13]], [87, [7, 14]], [88, [7, 15]], [89, [7, 16]], [90, [7, 17]], [91, [7, 18]], [92, [7, 19]], + [93, [8, 0]], + [94, [9, 0]], [95, [9, 1]], ]; - let _actual = renderViewLine(new RenderLineInput( + const _actual = renderViewLine(new RenderLineInput( false, true, lineText, @@ -385,7 +390,7 @@ suite('viewLineRenderer.renderLine', () => { )); assert.strictEqual(_actual.html, '' + expectedOutput + ''); - assertCharacterMapping(_actual.characterMapping, expectedOffsetsArr, [12, 12, 24, 1, 21, 2, 1, 20, 1, 1]); + assertCharacterMapping3(_actual.characterMapping, info); }); test('issue #91178: after decoration type shown before cursor', () => { @@ -401,23 +406,23 @@ suite('viewLineRenderer.renderLine', () => { ].join(''); const expectedCharacterMapping = new CharacterMapping(17, 4); - expectedCharacterMapping.setPartData(0, 0, 0, 0); - expectedCharacterMapping.setPartData(1, 0, 1, 0); - expectedCharacterMapping.setPartData(2, 0, 2, 0); - expectedCharacterMapping.setPartData(3, 0, 3, 0); - expectedCharacterMapping.setPartData(4, 0, 4, 0); - expectedCharacterMapping.setPartData(5, 0, 5, 0); - expectedCharacterMapping.setPartData(6, 0, 6, 0); - expectedCharacterMapping.setPartData(7, 0, 7, 0); - expectedCharacterMapping.setPartData(8, 0, 8, 0); - expectedCharacterMapping.setPartData(9, 0, 9, 0); - expectedCharacterMapping.setPartData(10, 0, 10, 0); - expectedCharacterMapping.setPartData(11, 0, 11, 0); - expectedCharacterMapping.setPartData(12, 2, 0, 12); - expectedCharacterMapping.setPartData(13, 3, 1, 12); - expectedCharacterMapping.setPartData(14, 3, 2, 12); - expectedCharacterMapping.setPartData(15, 3, 3, 12); - expectedCharacterMapping.setPartData(16, 3, 4, 12); + expectedCharacterMapping.setColumnInfo(1, 0, 0, 0); + expectedCharacterMapping.setColumnInfo(2, 0, 1, 0); + expectedCharacterMapping.setColumnInfo(3, 0, 2, 0); + expectedCharacterMapping.setColumnInfo(4, 0, 3, 0); + expectedCharacterMapping.setColumnInfo(5, 0, 4, 0); + expectedCharacterMapping.setColumnInfo(6, 0, 5, 0); + expectedCharacterMapping.setColumnInfo(7, 0, 6, 0); + expectedCharacterMapping.setColumnInfo(8, 0, 7, 0); + expectedCharacterMapping.setColumnInfo(9, 0, 8, 0); + expectedCharacterMapping.setColumnInfo(10, 0, 9, 0); + expectedCharacterMapping.setColumnInfo(11, 0, 10, 0); + expectedCharacterMapping.setColumnInfo(12, 0, 11, 0); + expectedCharacterMapping.setColumnInfo(13, 2, 0, 12); + expectedCharacterMapping.setColumnInfo(14, 3, 1, 12); + expectedCharacterMapping.setColumnInfo(15, 3, 2, 12); + expectedCharacterMapping.setColumnInfo(16, 3, 3, 12); + expectedCharacterMapping.setColumnInfo(17, 3, 4, 12); const actual = renderViewLine(new RenderLineInput( true, @@ -792,14 +797,12 @@ suite('viewLineRenderer.renderLine', () => { function decodeCharacterMapping(source: CharacterMapping) { const mapping: ICharMappingData[] = []; for (let charOffset = 0; charOffset < source.length; charOffset++) { - const partData = source.charOffsetToPartData(charOffset); - const partIndex = CharacterMapping.getPartIndex(partData); - const charIndex = CharacterMapping.getCharIndex(partData); - mapping.push({ charOffset, partIndex, charIndex }); + const domPosition = source.getDomPosition(charOffset + 1); + mapping.push({ charOffset, partIndex: domPosition.partIndex, charIndex: domPosition.charIndex }); } const absoluteOffsets: number[] = []; - for (const absoluteOffset of source.getAbsoluteOffsets()) { - absoluteOffsets.push(absoluteOffset); + for (let i = 0; i < source.length; i++) { + absoluteOffsets[i] = source.getAbsoluteOffset(i + 1); } return { mapping, absoluteOffsets }; } @@ -811,60 +814,33 @@ suite('viewLineRenderer.renderLine', () => { } }); -function assertCharacterMapping(actual: CharacterMapping, expectedCharPartOffsets: number[][], expectedPartLengths: number[]): void { +type CharacterMappingInfo = [number, [number, number]]; - assertCharPartOffsets(actual, expectedCharPartOffsets); +function assertCharacterMapping3(actual: CharacterMapping, expectedInfo: CharacterMappingInfo[]): void { + for (let i = 0; i < expectedInfo.length; i++) { + const [absoluteOffset, [partIndex, charIndex]] = expectedInfo[i]; - let expectedCharAbsoluteOffset: number[] = [], currentPartAbsoluteOffset = 0; - for (let partIndex = 0; partIndex < expectedCharPartOffsets.length; partIndex++) { - const part = expectedCharPartOffsets[partIndex]; + const actualDomPosition = actual.getDomPosition(i + 1); + assert.deepStrictEqual(actualDomPosition, new DomPosition(partIndex, charIndex), `getDomPosition(${i + 1})`); - for (const charIndex of part) { - expectedCharAbsoluteOffset.push(currentPartAbsoluteOffset + charIndex); + let partLength = charIndex + 1; + for (let j = i + 1; j < expectedInfo.length; j++) { + const [, [nextPartIndex, nextCharIndex]] = expectedInfo[j]; + if (nextPartIndex === partIndex) { + partLength = nextCharIndex + 1; + } else { + break; + } } - currentPartAbsoluteOffset += expectedPartLengths[partIndex]; + const actualColumn = actual.getColumn(new DomPosition(partIndex, charIndex), partLength); + assert.strictEqual(actualColumn, i + 1, `actual.getColumn(${partIndex}, ${charIndex})`); + + const actualAbsoluteOffset = actual.getAbsoluteOffset(i + 1); + assert.strictEqual(actualAbsoluteOffset, absoluteOffset, `actual.getAbsoluteOffset(${i + 1})`); } - let actualCharOffset: number[] = []; - let tmp = actual.getAbsoluteOffsets(); - for (let i = 0; i < tmp.length; i++) { - actualCharOffset[i] = tmp[i]; - } - assert.deepStrictEqual(actualCharOffset, expectedCharAbsoluteOffset); -} - -function assertCharPartOffsets(actual: CharacterMapping, expected: number[][]): void { - - let charOffset = 0; - for (let partIndex = 0; partIndex < expected.length; partIndex++) { - let part = expected[partIndex]; - for (const charIndex of part) { - // here - let _actualPartData = actual.charOffsetToPartData(charOffset); - let actualPartIndex = CharacterMapping.getPartIndex(_actualPartData); - let actualCharIndex = CharacterMapping.getCharIndex(_actualPartData); - - assert.deepStrictEqual( - { partIndex: actualPartIndex, charIndex: actualCharIndex }, - { partIndex: partIndex, charIndex: charIndex }, - `character mapping for offset ${charOffset}` - ); - - // here - let actualOffset = actual.partDataToCharOffset(partIndex, part[part.length - 1] + 1, charIndex); - - assert.strictEqual( - actualOffset, - charOffset, - `character mapping for part ${partIndex}, ${charIndex}` - ); - - charOffset++; - } - } - - assert.strictEqual(actual.length, charOffset); + assert.strictEqual(actual.length, expectedInfo.length, `length mismatch`); } suite('viewLineRenderer.renderLine 2', () => { @@ -2172,15 +2148,16 @@ suite('viewLineRenderer.renderLine 2', () => { ].join(''); assert.deepStrictEqual(actual.html, expected); - assertCharacterMapping(actual.characterMapping, + assertCharacterMapping3(actual.characterMapping, [ - [0, 1, 2, 3], - [0, 1], - [], - [0], - [], - ], - [4, 2, 0, 0] + [0, [0, 0]], + [1, [0, 1]], + [2, [0, 2]], + [3, [0, 3]], + [4, [1, 0]], + [5, [1, 1]], + [6, [3, 0]], + ] ); }); @@ -2209,9 +2186,8 @@ suite('viewLineRenderer.renderLine 2', () => { )); return (partIndex: number, partLength: number, offset: number, expected: number) => { - let charOffset = renderLineOutput.characterMapping.partDataToCharOffset(partIndex, partLength, offset); - let actual = charOffset + 1; - assert.strictEqual(actual, expected, 'getColumnOfLinePartOffset for ' + partIndex + ' @ ' + offset); + const actualColumn = renderLineOutput.characterMapping.getColumn(new DomPosition(partIndex, offset), partLength); + assert.strictEqual(actualColumn, expected, 'getColumn for ' + partIndex + ', ' + offset); }; } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index 338e9eed7f4..0958d5b1d68 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -2973,6 +2973,7 @@ declare namespace monaco.editor { * Suggest options. */ suggest?: ISuggestOptions; + inlineSuggest?: IInlineSuggestOptions; /** * Smart select options. */ @@ -3812,6 +3813,15 @@ declare namespace monaco.editor { readonly scrollByPage: boolean; } + export interface IInlineSuggestOptions { + /** + * Enable or disable the rendering of automatic inline completions. + */ + enabled?: boolean; + } + + export type InternalInlineSuggestOptions = Readonly>; + /** * Configuration options for editor suggest widget */ @@ -3847,16 +3857,7 @@ declare namespace monaco.editor { /** * Enable or disable the rendering of the suggestion preview. */ - showSuggestionPreview?: boolean; - /** - * Enable or disable the rendering of automatic inline completions. - */ - showInlineCompletions?: boolean; - /** - * Enable or disable the default expansion of the ghost text as used - * by the suggestion preview or the inline completions. - */ - ghostTextExpanded?: boolean; + preview?: boolean; /** * Show details inline with the label. Defaults to true. */ @@ -4065,83 +4066,84 @@ declare namespace monaco.editor { highlightActiveIndentGuide = 49, hover = 50, inDiffEditor = 51, - letterSpacing = 52, - lightbulb = 53, - lineDecorationsWidth = 54, - lineHeight = 55, - lineNumbers = 56, - lineNumbersMinChars = 57, - linkedEditing = 58, - links = 59, - matchBrackets = 60, - minimap = 61, - mouseStyle = 62, - mouseWheelScrollSensitivity = 63, - mouseWheelZoom = 64, - multiCursorMergeOverlapping = 65, - multiCursorModifier = 66, - multiCursorPaste = 67, - occurrencesHighlight = 68, - overviewRulerBorder = 69, - overviewRulerLanes = 70, - padding = 71, - parameterHints = 72, - peekWidgetDefaultFocus = 73, - definitionLinkOpensInPeek = 74, - quickSuggestions = 75, - quickSuggestionsDelay = 76, - readOnly = 77, - renameOnType = 78, - renderControlCharacters = 79, - renderIndentGuides = 80, - renderFinalNewline = 81, - renderLineHighlight = 82, - renderLineHighlightOnlyWhenFocus = 83, - renderValidationDecorations = 84, - renderWhitespace = 85, - revealHorizontalRightPadding = 86, - roundedSelection = 87, - rulers = 88, - scrollbar = 89, - scrollBeyondLastColumn = 90, - scrollBeyondLastLine = 91, - scrollPredominantAxis = 92, - selectionClipboard = 93, - selectionHighlight = 94, - selectOnLineNumbers = 95, - showFoldingControls = 96, - showUnused = 97, - snippetSuggestions = 98, - smartSelect = 99, - smoothScrolling = 100, - stickyTabStops = 101, - stopRenderingLineAfter = 102, - suggest = 103, - suggestFontSize = 104, - suggestLineHeight = 105, - suggestOnTriggerCharacters = 106, - suggestSelection = 107, - tabCompletion = 108, - tabIndex = 109, - unusualLineTerminators = 110, - useShadowDOM = 111, - useTabStops = 112, - wordSeparators = 113, - wordWrap = 114, - wordWrapBreakAfterCharacters = 115, - wordWrapBreakBeforeCharacters = 116, - wordWrapColumn = 117, - wordWrapOverride1 = 118, - wordWrapOverride2 = 119, - wrappingIndent = 120, - wrappingStrategy = 121, - showDeprecated = 122, - inlayHints = 123, - editorClassName = 124, - pixelRatio = 125, - tabFocusMode = 126, - layoutInfo = 127, - wrappingInfo = 128 + inlineSuggest = 52, + letterSpacing = 53, + lightbulb = 54, + lineDecorationsWidth = 55, + lineHeight = 56, + lineNumbers = 57, + lineNumbersMinChars = 58, + linkedEditing = 59, + links = 60, + matchBrackets = 61, + minimap = 62, + mouseStyle = 63, + mouseWheelScrollSensitivity = 64, + mouseWheelZoom = 65, + multiCursorMergeOverlapping = 66, + multiCursorModifier = 67, + multiCursorPaste = 68, + occurrencesHighlight = 69, + overviewRulerBorder = 70, + overviewRulerLanes = 71, + padding = 72, + parameterHints = 73, + peekWidgetDefaultFocus = 74, + definitionLinkOpensInPeek = 75, + quickSuggestions = 76, + quickSuggestionsDelay = 77, + readOnly = 78, + renameOnType = 79, + renderControlCharacters = 80, + renderIndentGuides = 81, + renderFinalNewline = 82, + renderLineHighlight = 83, + renderLineHighlightOnlyWhenFocus = 84, + renderValidationDecorations = 85, + renderWhitespace = 86, + revealHorizontalRightPadding = 87, + roundedSelection = 88, + rulers = 89, + scrollbar = 90, + scrollBeyondLastColumn = 91, + scrollBeyondLastLine = 92, + scrollPredominantAxis = 93, + selectionClipboard = 94, + selectionHighlight = 95, + selectOnLineNumbers = 96, + showFoldingControls = 97, + showUnused = 98, + snippetSuggestions = 99, + smartSelect = 100, + smoothScrolling = 101, + stickyTabStops = 102, + stopRenderingLineAfter = 103, + suggest = 104, + suggestFontSize = 105, + suggestLineHeight = 106, + suggestOnTriggerCharacters = 107, + suggestSelection = 108, + tabCompletion = 109, + tabIndex = 110, + unusualLineTerminators = 111, + useShadowDOM = 112, + useTabStops = 113, + wordSeparators = 114, + wordWrap = 115, + wordWrapBreakAfterCharacters = 116, + wordWrapBreakBeforeCharacters = 117, + wordWrapColumn = 118, + wordWrapOverride1 = 119, + wordWrapOverride2 = 120, + wrappingIndent = 121, + wrappingStrategy = 122, + showDeprecated = 123, + inlayHints = 124, + editorClassName = 125, + pixelRatio = 126, + tabFocusMode = 127, + layoutInfo = 128, + wrappingInfo = 129 } export const EditorOptions: { acceptSuggestionOnCommitCharacter: IEditorOption; @@ -4250,6 +4252,7 @@ declare namespace monaco.editor { smoothScrolling: IEditorOption; stopRenderingLineAfter: IEditorOption; suggest: IEditorOption; + inlineSuggest: IEditorOption; suggestFontSize: IEditorOption; suggestLineHeight: IEditorOption; suggestOnTriggerCharacters: IEditorOption; diff --git a/src/vs/platform/backup/electron-main/backupMainService.ts b/src/vs/platform/backup/electron-main/backupMainService.ts index ac9d7f2a86d..f06b2c9e69b 100644 --- a/src/vs/platform/backup/electron-main/backupMainService.ts +++ b/src/vs/platform/backup/electron-main/backupMainService.ts @@ -7,7 +7,7 @@ import * as fs from 'fs'; import { createHash } from 'crypto'; import { join } from 'vs/base/common/path'; import { isLinux } from 'vs/base/common/platform'; -import { writeFileSync, writeFile, readdir, exists, rimraf, RimRafMode, Promises } from 'vs/base/node/pfs'; +import { writeFileSync, rimraf, RimRafMode, Promises } from 'vs/base/node/pfs'; import { IBackupMainService, IWorkspaceBackupInfo, isWorkspaceBackupInfo } from 'vs/platform/backup/electron-main/backup'; import { IBackupWorkspacesFormat, IEmptyWindowBackupInfo } from 'vs/platform/backup/node/backup'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; @@ -54,21 +54,23 @@ export class BackupMainService implements IBackupMainService { backups = Object.create(null); } - // read empty workspaces backups first - if (backups.emptyWorkspaceInfos) { - this.emptyWindows = await this.validateEmptyWorkspaces(backups.emptyWorkspaceInfos); - } + // validate empty workspaces backups first + this.emptyWindows = await this.validateEmptyWorkspaces(backups.emptyWorkspaceInfos); // read workspace backups let rootWorkspaces: IWorkspaceBackupInfo[] = []; try { if (Array.isArray(backups.rootURIWorkspaces)) { - rootWorkspaces = backups.rootURIWorkspaces.map(workspace => ({ workspace: { id: workspace.id, configPath: URI.parse(workspace.configURIPath) }, remoteAuthority: workspace.remoteAuthority })); + rootWorkspaces = backups.rootURIWorkspaces.map(workspace => ({ + workspace: { id: workspace.id, configPath: URI.parse(workspace.configURIPath) }, + remoteAuthority: workspace.remoteAuthority + })); } } catch (e) { // ignore URI parsing exceptions } + // validate workspace backups this.workspaces = await this.validateWorkspaces(rootWorkspaces); // read folder backups @@ -81,6 +83,7 @@ export class BackupMainService implements IBackupMainService { // ignore URI parsing exceptions } + // validate folder backups this.folders = await this.validateFolders(workspaceFolders); // save again in case some workspaces or folders have been removed @@ -230,7 +233,7 @@ export class BackupMainService implements IBackupMainService { // If the workspace has no backups, ignore it if (hasBackups) { - if (workspace.configPath.scheme !== Schemas.file || await exists(workspace.configPath.fsPath)) { + if (workspace.configPath.scheme !== Schemas.file || await Promises.exists(workspace.configPath.fsPath)) { result.push(workspaceInfo); } else { // If the workspace has backups, but the target workspace is missing, convert backups to empty ones @@ -262,7 +265,7 @@ export class BackupMainService implements IBackupMainService { // If the folder has no backups, ignore it if (hasBackups) { - if (folderURI.scheme !== Schemas.file || await exists(folderURI.fsPath)) { + if (folderURI.scheme !== Schemas.file || await Promises.exists(folderURI.fsPath)) { result.push(folderURI); } else { // If the folder has backups, but the target workspace is missing, convert backups to empty ones @@ -309,7 +312,7 @@ export class BackupMainService implements IBackupMainService { private async deleteStaleBackup(backupPath: string): Promise { try { - if (await exists(backupPath)) { + if (await Promises.exists(backupPath)) { await rimraf(backupPath, RimRafMode.MOVE); } } catch (error) { @@ -402,11 +405,11 @@ export class BackupMainService implements IBackupMainService { private async doHasBackups(backupPath: string): Promise { try { - const backupSchemas = await readdir(backupPath); + const backupSchemas = await Promises.readdir(backupPath); for (const backupSchema of backupSchemas) { try { - const backupSchemaChildren = await readdir(join(backupPath, backupSchema)); + const backupSchemaChildren = await Promises.readdir(join(backupPath, backupSchema)); if (backupSchemaChildren.length > 0) { return true; } @@ -431,7 +434,7 @@ export class BackupMainService implements IBackupMainService { private async save(): Promise { try { - await writeFile(this.workspacesJsonPath, JSON.stringify(this.serializeBackups())); + await Promises.writeFile(this.workspacesJsonPath, JSON.stringify(this.serializeBackups())); } catch (error) { this.logService.error(`Backup: Could not save workspaces.json: ${error.toString()}`); } diff --git a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts index a2101b983e9..535d94a2785 100644 --- a/src/vs/platform/backup/test/electron-main/backupMainService.test.ts +++ b/src/vs/platform/backup/test/electron-main/backupMainService.test.ts @@ -66,7 +66,7 @@ flakySuite('BackupMainService', () => { async function ensureWorkspaceExists(workspace: IWorkspaceIdentifier): Promise { if (!fs.existsSync(workspace.configPath.fsPath)) { - await pfs.writeFile(workspace.configPath.fsPath, 'Hello'); + await pfs.Promises.writeFile(workspace.configPath.fsPath, 'Hello'); } const backupFolder = service.toBackupPath(workspace.id); @@ -79,7 +79,7 @@ flakySuite('BackupMainService', () => { if (!fs.existsSync(backupFolder)) { fs.mkdirSync(backupFolder); fs.mkdirSync(path.join(backupFolder, Schemas.file)); - await pfs.writeFile(path.join(backupFolder, Schemas.file, 'foo.txt'), 'Hello'); + await pfs.Promises.writeFile(path.join(backupFolder, Schemas.file, 'foo.txt'), 'Hello'); } } @@ -443,7 +443,7 @@ flakySuite('BackupMainService', () => { folderURIWorkspaces: [existingTestFolder1.toString(), existingTestFolder1.toString()], emptyWorkspaceInfos: [] }; - await pfs.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson)); + await pfs.Promises.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson)); await service.initialize(); const buffer = await pfs.Promises.readFile(backupWorkspacesPath, 'utf-8'); @@ -460,7 +460,7 @@ flakySuite('BackupMainService', () => { folderURIWorkspaces: [existingTestFolder1.toString(), existingTestFolder1.toString().toLowerCase()], emptyWorkspaceInfos: [] }; - await pfs.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson)); + await pfs.Promises.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson)); await service.initialize(); const buffer = await pfs.Promises.readFile(backupWorkspacesPath, 'utf-8'); const json = JSON.parse(buffer); @@ -481,7 +481,7 @@ flakySuite('BackupMainService', () => { folderURIWorkspaces: [], emptyWorkspaceInfos: [] }; - await pfs.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson)); + await pfs.Promises.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson)); await service.initialize(); const buffer = await pfs.Promises.readFile(backupWorkspacesPath, 'utf-8'); @@ -596,7 +596,7 @@ flakySuite('BackupMainService', () => { await ensureFolderExists(existingTestFolder1); // make sure backup folder exists, so the folder is not removed on loadSync const workspacesJson: IBackupWorkspacesFormat = { rootURIWorkspaces: [], folderURIWorkspaces: [existingTestFolder1.toString()], emptyWorkspaceInfos: [] }; - await pfs.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson)); + await pfs.Promises.writeFile(backupWorkspacesPath, JSON.stringify(workspacesJson)); await service.initialize(); service.unregisterFolderBackupSync(barFile); service.unregisterEmptyWindowBackupSync('test'); diff --git a/src/vs/platform/diagnostics/node/diagnosticsService.ts b/src/vs/platform/diagnostics/node/diagnosticsService.ts index a66ffda0236..52238d6e607 100644 --- a/src/vs/platform/diagnostics/node/diagnosticsService.ts +++ b/src/vs/platform/diagnostics/node/diagnosticsService.ts @@ -5,7 +5,6 @@ import * as osLib from 'os'; import { virtualMachineHint } from 'vs/base/node/id'; import { IDiagnosticsService, IMachineInfo, WorkspaceStats, WorkspaceStatItem, PerformanceInfo, SystemInfo, IRemoteDiagnosticInfo, IRemoteDiagnosticError, isRemoteDiagnosticError, IWorkspaceInformation } from 'vs/platform/diagnostics/common/diagnostics'; -import { exists, readFile } from 'fs'; import { join, basename } from 'vs/base/common/path'; import { parse, ParseError, getNodeType } from 'vs/base/common/json'; import { listProcesses } from 'vs/base/node/ps'; @@ -18,7 +17,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { Iterable } from 'vs/base/common/iterator'; import { Schemas } from 'vs/base/common/network'; import { ByteSize } from 'vs/platform/files/common/files'; -import { IDirent, readdir } from 'vs/base/node/pfs'; +import { IDirent, Promises } from 'vs/base/node/pfs'; export interface VersionInfo { vscodeVersion: string; @@ -70,7 +69,7 @@ export async function collectWorkspaceStats(folder: string, filter: string[]): P return new Promise(async resolve => { let files: IDirent[]; try { - files = await readdir(dir, { withFileTypes: true }); + files = await Promises.readdir(dir, { withFileTypes: true }); } catch (error) { // Ignore folders that can't be read resolve(); @@ -168,45 +167,37 @@ export function getMachineInfo(): IMachineInfo { return machineInfo; } -export function collectLaunchConfigs(folder: string): Promise { - let launchConfigs = new Map(); +export async function collectLaunchConfigs(folder: string): Promise { + try { + const launchConfigs = new Map(); + const launchConfig = join(folder, '.vscode', 'launch.json'); - let launchConfig = join(folder, '.vscode', 'launch.json'); - return new Promise((resolve, reject) => { - exists(launchConfig, (doesExist) => { - if (doesExist) { - readFile(launchConfig, (err, contents) => { - if (err) { - return resolve([]); + const contents = await Promises.readFile(launchConfig); + + const errors: ParseError[] = []; + const json = parse(contents.toString(), errors); + if (errors.length) { + console.log(`Unable to parse ${launchConfig}`); + return []; + } + + if (getNodeType(json) === 'object' && json['configurations']) { + for (const each of json['configurations']) { + const type = each['type']; + if (type) { + if (launchConfigs.has(type)) { + launchConfigs.set(type, launchConfigs.get(type)! + 1); + } else { + launchConfigs.set(type, 1); } - - const errors: ParseError[] = []; - const json = parse(contents.toString(), errors); - if (errors.length) { - console.log(`Unable to parse ${launchConfig}`); - return resolve([]); - } - - if (getNodeType(json) === 'object' && json['configurations']) { - for (const each of json['configurations']) { - const type = each['type']; - if (type) { - if (launchConfigs.has(type)) { - launchConfigs.set(type, launchConfigs.get(type)! + 1); - } else { - launchConfigs.set(type, 1); - } - } - } - } - - return resolve(asSortedItems(launchConfigs)); - }); - } else { - return resolve([]); + } } - }); - }); + } + + return asSortedItems(launchConfigs); + } catch (error) { + return []; + } } export class DiagnosticsService implements IDiagnosticsService { diff --git a/src/vs/platform/dialogs/common/dialogs.ts b/src/vs/platform/dialogs/common/dialogs.ts index 9e3738404d0..ca48f7a7253 100644 --- a/src/vs/platform/dialogs/common/dialogs.ts +++ b/src/vs/platform/dialogs/common/dialogs.ts @@ -31,12 +31,13 @@ export interface IConfirmDialogArgs { export interface IShowDialogArgs { severity: Severity; message: string; - buttons: string[]; + buttons?: string[]; options?: IDialogOptions; } export interface IInputDialogArgs extends IShowDialogArgs { - inputs: IInput[], + buttons: string[]; + inputs: IInput[]; } export interface IDialog { @@ -222,7 +223,7 @@ export interface IDialogHandler { * then a promise with index of `cancelId` option is returned. If there is no such * option then promise with index `0` is returned. */ - show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Promise; + show(severity: Severity, message: string, buttons?: string[], options?: IDialogOptions): Promise; /** * Present a modal dialog to the user asking for input. @@ -262,7 +263,7 @@ export interface IDialogService { * then a promise with index of `cancelId` option is returned. If there is no such * option then promise with index `0` is returned. */ - show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Promise; + show(severity: Severity, message: string, buttons?: string[], options?: IDialogOptions): Promise; /** * Present a modal dialog to the user asking for input. diff --git a/src/vs/platform/dialogs/electron-main/dialogMainService.ts b/src/vs/platform/dialogs/electron-main/dialogMainService.ts index b06fa6789eb..0d97ef6e94c 100644 --- a/src/vs/platform/dialogs/electron-main/dialogMainService.ts +++ b/src/vs/platform/dialogs/electron-main/dialogMainService.ts @@ -10,7 +10,7 @@ import { IStateMainService } from 'vs/platform/state/electron-main/state'; import { isMacintosh } from 'vs/base/common/platform'; import { dirname } from 'vs/base/common/path'; import { normalizeNFC } from 'vs/base/common/normalization'; -import { exists } from 'vs/base/node/pfs'; +import { Promises } from 'vs/base/node/pfs'; import { INativeOpenDialogOptions } from 'vs/platform/dialogs/common/dialogs'; import { withNullAsUndefined } from 'vs/base/common/types'; import { localize } from 'vs/nls'; @@ -194,7 +194,7 @@ export class DialogMainService implements IDialogMainService { // Ensure the path exists (if provided) if (options.defaultPath) { - const pathExists = await exists(options.defaultPath); + const pathExists = await Promises.exists(options.defaultPath); if (!pathExists) { options.defaultPath = undefined; } diff --git a/src/vs/platform/dialogs/test/common/testDialogService.ts b/src/vs/platform/dialogs/test/common/testDialogService.ts index 1d2b2f08f71..bf965945ad8 100644 --- a/src/vs/platform/dialogs/test/common/testDialogService.ts +++ b/src/vs/platform/dialogs/test/common/testDialogService.ts @@ -26,7 +26,7 @@ export class TestDialogService implements IDialogService { return { confirmed: false }; } - async show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Promise { return { choice: 0 }; } + async show(severity: Severity, message: string, buttons?: string[], options?: IDialogOptions): Promise { return { choice: 0 }; } async input(): Promise { { return { choice: 0, values: [] }; } } async about(): Promise { } } diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 7410e3572fc..1c48e5c356b 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -138,7 +138,7 @@ export class ExtensionManagementService extends Disposable implements IExtension private async collectFiles(extension: ILocalExtension): Promise { const collectFilesFromDirectory = async (dir: string): Promise => { - let entries = await pfs.readdir(dir); + let entries = await pfs.Promises.readdir(dir); entries = entries.map(e => path.join(dir, e)); const stats = await Promise.all(entries.map(e => pfs.Promises.stat(e))); let promise: Promise = Promise.resolve([]); @@ -640,7 +640,7 @@ export class ExtensionManagementService extends Disposable implements IExtension } private async preUninstallExtension(extension: ILocalExtension): Promise { - const exists = await pfs.exists(extension.location.fsPath); + const exists = await pfs.Promises.exists(extension.location.fsPath); if (!exists) { throw new Error(nls.localize('notExists', "Could not find extension")); } diff --git a/src/vs/platform/extensionManagement/node/extensionsScanner.ts b/src/vs/platform/extensionManagement/node/extensionsScanner.ts index f489f82362b..6761f4405ce 100644 --- a/src/vs/platform/extensionManagement/node/extensionsScanner.ts +++ b/src/vs/platform/extensionManagement/node/extensionsScanner.ts @@ -161,7 +161,7 @@ export class ExtensionsScanner extends Disposable { const raw = await pfs.Promises.readFile(manifestPath, 'utf8'); const { manifest } = await this.parseManifest(raw); (manifest as ILocalExtensionManifest).__metadata = storedMetadata; - await pfs.writeFile(manifestPath, JSON.stringify(manifest, null, '\t')); + await pfs.Promises.writeFile(manifestPath, JSON.stringify(manifest, null, '\t')); return local; } @@ -208,7 +208,7 @@ export class ExtensionsScanner extends Disposable { if (updateFn) { updateFn(uninstalled); if (Object.keys(uninstalled).length) { - await pfs.writeFile(this.uninstalledPath, JSON.stringify(uninstalled)); + await pfs.Promises.writeFile(this.uninstalledPath, JSON.stringify(uninstalled)); } else { await pfs.rimraf(this.uninstalledPath); } diff --git a/src/vs/platform/externalTerminal/node/externalTerminalService.ts b/src/vs/platform/externalTerminal/node/externalTerminalService.ts index a7ae827eee1..b9183b6b785 100644 --- a/src/vs/platform/externalTerminal/node/externalTerminalService.ts +++ b/src/vs/platform/externalTerminal/node/externalTerminalService.ts @@ -292,7 +292,7 @@ export class LinuxExternalTerminalService extends ExternalTerminalService implem if (!LinuxExternalTerminalService._DEFAULT_TERMINAL_LINUX_READY) { LinuxExternalTerminalService._DEFAULT_TERMINAL_LINUX_READY = new Promise(async r => { if (env.isLinux) { - const isDebian = await pfs.exists('/etc/debian_version'); + const isDebian = await pfs.Promises.exists('/etc/debian_version'); if (isDebian) { r('x-terminal-emulator'); } else if (process.env.DESKTOP_SESSION === 'gnome' || process.env.DESKTOP_SESSION === 'gnome-classic') { diff --git a/src/vs/platform/files/common/files.ts b/src/vs/platform/files/common/files.ts index 774f4e22c00..49c4d58fdc9 100644 --- a/src/vs/platform/files/common/files.ts +++ b/src/vs/platform/files/common/files.ts @@ -775,16 +775,6 @@ export class FileChangesEvent { return !!this.deleted; } - /** - * @deprecated use the `contains()` method to efficiently find out if the event - * relates to a given resource. this method ensures: - * - that there is no expensive lookup needed by using a `TernarySearchTree` - * - correctly handles `FileChangeType.DELETED` events - */ - getUpdated(): IFileChange[] { - return this.getOfType(FileChangeType.UPDATED); - } - /** * Returns if this event contains updated files. */ @@ -804,16 +794,6 @@ export class FileChangesEvent { return changes; } - - /** - * @deprecated use the `contains()` method to efficiently find out if the event - * relates to a given resource. this method ensures: - * - that there is no expensive lookup needed by using a `TernarySearchTree` - * - correctly handles `FileChangeType.DELETED` events - */ - filter(filterFn: (change: IFileChange) => boolean): FileChangesEvent { - return new FileChangesEvent(this.changes.filter(change => filterFn(change)), this.ignorePathCasing); - } } export function isParent(path: string, candidate: string, ignoreCase?: boolean): boolean { diff --git a/src/vs/platform/files/node/diskFileSystemProvider.ts b/src/vs/platform/files/node/diskFileSystemProvider.ts index c950ddc09d4..65af5eb2780 100644 --- a/src/vs/platform/files/node/diskFileSystemProvider.ts +++ b/src/vs/platform/files/node/diskFileSystemProvider.ts @@ -9,7 +9,7 @@ import { FileSystemProviderCapabilities, IFileChange, IWatchOptions, IStat, File import { URI } from 'vs/base/common/uri'; import { Event, Emitter } from 'vs/base/common/event'; import { isLinux, isWindows } from 'vs/base/common/platform'; -import { SymlinkSupport, move, copy, rimraf, RimRafMode, exists, readdir, IDirent, Promises } from 'vs/base/node/pfs'; +import { SymlinkSupport, move, copy, rimraf, RimRafMode, IDirent, Promises } from 'vs/base/node/pfs'; import { normalize, basename, dirname } from 'vs/base/common/path'; import { joinPath } from 'vs/base/common/resources'; import { isEqual } from 'vs/base/common/extpath'; @@ -95,7 +95,7 @@ export class DiskFileSystemProvider extends Disposable implements async readdir(resource: URI): Promise<[string, FileType][]> { try { - const children = await readdir(this.toFilePath(resource), { withFileTypes: true }); + const children = await Promises.readdir(this.toFilePath(resource), { withFileTypes: true }); const result: [string, FileType][] = []; await Promise.all(children.map(async child => { @@ -175,7 +175,7 @@ export class DiskFileSystemProvider extends Disposable implements // Validate target unless { create: true, overwrite: true } if (!opts.create || !opts.overwrite) { - const fileExists = await exists(filePath); + const fileExists = await Promises.exists(filePath); if (fileExists) { if (!opts.overwrite) { throw createFileSystemProviderError(localize('fileExists', "File already exists"), FileSystemProviderErrorCode.FileExists); @@ -510,7 +510,7 @@ export class DiskFileSystemProvider extends Disposable implements } // handle existing target (unless this is a case change) - if (!isSameResourceWithDifferentPathCase && await exists(toFilePath)) { + if (!isSameResourceWithDifferentPathCase && await Promises.exists(toFilePath)) { if (!overwrite) { throw createFileSystemProviderError(localize('fileCopyErrorExists', "File at target already exists"), FileSystemProviderErrorCode.FileExists); } diff --git a/src/vs/platform/files/test/browser/indexedDBFileService.test.ts b/src/vs/platform/files/test/browser/indexedDBFileService.test.ts index a2aaa0faf79..a178527e4e9 100644 --- a/src/vs/platform/files/test/browser/indexedDBFileService.test.ts +++ b/src/vs/platform/files/test/browser/indexedDBFileService.test.ts @@ -14,11 +14,9 @@ import { IIndexedDBFileSystemProvider, IndexedDB, INDEXEDDB_LOGS_OBJECT_STORE, I import { assertIsDefined } from 'vs/base/common/types'; import { basename, joinPath } from 'vs/base/common/resources'; import { bufferToReadable, bufferToStream, VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer'; +import { flakySuite } from 'vs/base/test/common/testUtils'; -suite('IndexedDB File Service', function () { - - // IDB sometimes under pressure in build machines. - this.retries(3); +flakySuite('IndexedDB File Service', function () { const logSchema = 'logs'; diff --git a/src/vs/platform/files/test/common/files.test.ts b/src/vs/platform/files/test/common/files.test.ts index e51c4071476..31e60a2e7c8 100644 --- a/src/vs/platform/files/test/common/files.test.ts +++ b/src/vs/platform/files/test/common/files.test.ts @@ -60,7 +60,6 @@ suite('Files', () => { assert.strictEqual(6, event.changes.length); assert.strictEqual(1, event.getAdded().length); assert.strictEqual(true, event.gotAdded()); - assert.strictEqual(2, event.getUpdated().length); assert.strictEqual(true, event.gotUpdated()); assert.strictEqual(ignorePathCasing ? 2 : 3, event.getDeleted().length); assert.strictEqual(true, event.gotDeleted()); @@ -102,9 +101,6 @@ suite('Files', () => { case FileChangeType.ADDED: assert.strictEqual(8, event.getAdded().length); break; - case FileChangeType.UPDATED: - assert.strictEqual(8, event.getUpdated().length); - break; case FileChangeType.DELETED: assert.strictEqual(8, event.getDeleted().length); break; diff --git a/src/vs/platform/issue/common/issue.ts b/src/vs/platform/issue/common/issue.ts index b080615e4f5..0f398932e6d 100644 --- a/src/vs/platform/issue/common/issue.ts +++ b/src/vs/platform/issue/common/issue.ts @@ -58,6 +58,7 @@ export interface IssueReporterData extends WindowData { issueType?: IssueType; extensionId?: string; experiments?: string; + restrictedMode: boolean; githubAccessToken: string; readonly issueTitle?: string; readonly issueBody?: string; diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts index 1eb0ac224bc..0df99eba03e 100644 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -308,7 +308,6 @@ export class IssueMainService implements ICommonIssueService { additionalArguments: [`--vscode-window-config=${ipcObjectUrl.resource.toString()}`, '--context-isolation' /* TODO@bpasero: Use process.contextIsolateed when 13-x-y is adopted (https://github.com/electron/electron/pull/28030) */], v8CacheOptions: browserCodeLoadingCacheStrategy, enableWebSQL: false, - enableRemoteModule: false, spellcheck: false, nativeWindowOpen: true, zoomFactor: zoomLevelToZoomFactor(options.zoomLevel), diff --git a/src/vs/platform/keybinding/common/keybindingResolver.ts b/src/vs/platform/keybinding/common/keybindingResolver.ts index 753e607ef07..db47d1155a1 100644 --- a/src/vs/platform/keybinding/common/keybindingResolver.ts +++ b/src/vs/platform/keybinding/common/keybindingResolver.ts @@ -183,10 +183,10 @@ export class KeybindingResolver { * Returns true if it is provable `a` implies `b`. */ public static whenIsEntirelyIncluded(a: ContextKeyExpression | null | undefined, b: ContextKeyExpression | null | undefined): boolean { - if (!b) { + if (!b || b.type === ContextKeyExprType.True) { return true; } - if (!a) { + if (!a || a.type === ContextKeyExprType.True) { return false; } diff --git a/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts b/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts index 33472df8a78..a609dc49da0 100644 --- a/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts +++ b/src/vs/platform/keybinding/test/common/keybindingResolver.test.ts @@ -193,16 +193,27 @@ suite('KeybindingResolver', () => { }); test('contextIsEntirelyIncluded', () => { - const assertIsIncluded = (a: string | null, b: string | null) => { - assert.strictEqual(KeybindingResolver.whenIsEntirelyIncluded(ContextKeyExpr.deserialize(a), ContextKeyExpr.deserialize(b)), true); + const toContextKeyExpression = (expr: ContextKeyExpression | string | null) => { + if (typeof expr === 'string' || !expr) { + return ContextKeyExpr.deserialize(expr); + } + return expr; }; - const assertIsNotIncluded = (a: string | null, b: string | null) => { - assert.strictEqual(KeybindingResolver.whenIsEntirelyIncluded(ContextKeyExpr.deserialize(a), ContextKeyExpr.deserialize(b)), false); + const assertIsIncluded = (a: ContextKeyExpression | string | null, b: ContextKeyExpression | string | null) => { + assert.strictEqual(KeybindingResolver.whenIsEntirelyIncluded(toContextKeyExpression(a), toContextKeyExpression(b)), true); + }; + const assertIsNotIncluded = (a: ContextKeyExpression | string | null, b: ContextKeyExpression | string | null) => { + assert.strictEqual(KeybindingResolver.whenIsEntirelyIncluded(toContextKeyExpression(a), toContextKeyExpression(b)), false); }; + assertIsIncluded(null, null); + assertIsIncluded(null, ContextKeyExpr.true()); + assertIsIncluded(ContextKeyExpr.true(), null); + assertIsIncluded(ContextKeyExpr.true(), ContextKeyExpr.true()); assertIsIncluded('key1', null); assertIsIncluded('key1', ''); assertIsIncluded('key1', 'key1'); + assertIsIncluded('key1', ContextKeyExpr.true()); assertIsIncluded('!key1', ''); assertIsIncluded('!key1', '!key1'); assertIsIncluded('key2', ''); diff --git a/src/vs/platform/localizations/node/localizations.ts b/src/vs/platform/localizations/node/localizations.ts index e1ef23c7b36..f8522737271 100644 --- a/src/vs/platform/localizations/node/localizations.ts +++ b/src/vs/platform/localizations/node/localizations.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Promises, writeFile } from 'vs/base/node/pfs'; +import { Promises } from 'vs/base/node/pfs'; import { createHash } from 'crypto'; import { IExtensionManagementService, ILocalExtension, IExtensionIdentifier } from 'vs/platform/extensionManagement/common/extensionManagement'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -171,7 +171,7 @@ class LanguagePacksCache extends Disposable { this.initializedCache = true; const raw = JSON.stringify(this.languagePacks); this.logService.debug('Writing language packs', raw); - return writeFile(this.languagePacksFilePath, raw); + return Promises.writeFile(this.languagePacksFilePath, raw); }) .then(() => result, error => this.logService.error(error)); }); diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts index 692db1dbaa9..a1eea1a4d84 100644 --- a/src/vs/platform/native/electron-main/nativeHostMainService.ts +++ b/src/vs/platform/native/electron-main/nativeHostMainService.ts @@ -19,7 +19,7 @@ import { ISerializableCommandAction } from 'vs/platform/actions/common/actions'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { AddFirstParameterToFunctions } from 'vs/base/common/types'; import { IDialogMainService } from 'vs/platform/dialogs/electron-main/dialogMainService'; -import { exists, Promises, SymlinkSupport } from 'vs/base/node/pfs'; +import { Promises, SymlinkSupport } from 'vs/base/node/pfs'; import { URI } from 'vs/base/common/uri'; import { ITelemetryData, ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; @@ -345,7 +345,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain const source = `/usr/local/bin/${this.productService.applicationName}`; // Ensure source exists - const sourceExists = await exists(target); + const sourceExists = await Promises.exists(target); if (!sourceExists) { throw new Error(localize('sourceMissing', "Unable to find shell script in '{0}'", target)); } diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index 4a0b94187d6..1eafa991bd9 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -54,7 +54,7 @@ else { // Running out of sources if (Object.keys(product).length === 0) { Object.assign(product, { - version: '1.57.0-dev', + version: '1.58.0-dev', nameShort: isWeb ? 'Code Web - OSS Dev' : 'Code - OSS Dev', nameLong: isWeb ? 'Code Web - OSS Dev' : 'Code - OSS Dev', applicationName: 'code-oss', diff --git a/src/vs/platform/quickinput/browser/commandsQuickAccess.ts b/src/vs/platform/quickinput/browser/commandsQuickAccess.ts index e84a282762a..d2cac3a6407 100644 --- a/src/vs/platform/quickinput/browser/commandsQuickAccess.ts +++ b/src/vs/platform/quickinput/browser/commandsQuickAccess.ts @@ -163,7 +163,7 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc await this.commandService.executeCommand(commandPick.commandId); } catch (error) { if (!isPromiseCanceledError(error)) { - this.dialogService.show(Severity.Error, localize('canNotRun', "Command '{0}' resulted in an error ({1})", commandPick.label, toErrorMessage(error)), [localize('ok', 'OK')]); + this.dialogService.show(Severity.Error, localize('canNotRun', "Command '{0}' resulted in an error ({1})", commandPick.label, toErrorMessage(error))); } } } diff --git a/src/vs/platform/remote/common/remoteHosts.ts b/src/vs/platform/remote/common/remoteHosts.ts index ca7c21b3f18..e125df902cd 100644 --- a/src/vs/platform/remote/common/remoteHosts.ts +++ b/src/vs/platform/remote/common/remoteHosts.ts @@ -26,7 +26,7 @@ export function getRemoteName(authority: string | undefined): string | undefined return authority.substr(0, pos); } -function isVirtualResource(resource: URI) { +export function isVirtualResource(resource: URI) { return resource.scheme !== Schemas.file && resource.scheme !== Schemas.vscodeRemote; } diff --git a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts index f62912e56d1..a2d420a49fe 100644 --- a/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts +++ b/src/vs/platform/sharedProcess/electron-main/sharedProcess.ts @@ -170,7 +170,6 @@ export class SharedProcess extends Disposable implements ISharedProcess { nodeIntegration: true, contextIsolation: false, enableWebSQL: false, - enableRemoteModule: false, spellcheck: false, nativeWindowOpen: true, images: false, diff --git a/src/vs/platform/storage/browser/storageService.ts b/src/vs/platform/storage/browser/storageService.ts index c1aae2e7d2e..a308936059a 100644 --- a/src/vs/platform/storage/browser/storageService.ts +++ b/src/vs/platform/storage/browser/storageService.ts @@ -3,17 +3,17 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Disposable } from 'vs/base/common/lifecycle'; -import { Emitter } from 'vs/base/common/event'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; +import { Event } from 'vs/base/common/event'; import { StorageScope, IS_NEW_KEY, AbstractStorageService } from 'vs/platform/storage/common/storage'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IWorkspaceInitializationPayload } from 'vs/platform/workspaces/common/workspaces'; -import { IFileService, FileChangeType } from 'vs/platform/files/common/files'; -import { IStorage, Storage, IStorageDatabase, IStorageItemsChangeEvent, IUpdateRequest } from 'vs/base/parts/storage/common/storage'; -import { URI } from 'vs/base/common/uri'; -import { joinPath } from 'vs/base/common/resources'; +import { IStorage, Storage, IStorageDatabase, IUpdateRequest, InMemoryStorageDatabase } from 'vs/base/parts/storage/common/storage'; import { Promises } from 'vs/base/common/async'; -import { VSBuffer } from 'vs/base/common/buffer'; +import { ILogService } from 'vs/platform/log/common/log'; +import { toErrorMessage } from 'vs/base/common/errorMessage'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IFileService } from 'vs/platform/files/common/files'; +import { joinPath } from 'vs/base/common/resources'; export class BrowserStorageService extends AbstractStorageService { @@ -22,40 +22,41 @@ export class BrowserStorageService extends AbstractStorageService { private globalStorage: IStorage | undefined; private workspaceStorage: IStorage | undefined; - private globalStorageDatabase: FileStorageDatabase | undefined; - private workspaceStorageDatabase: FileStorageDatabase | undefined; - - private globalStorageFile: URI | undefined; - private workspaceStorageFile: URI | undefined; + private globalStorageDatabase: IIndexedDBStorageDatabase | undefined; + private workspaceStorageDatabase: IIndexedDBStorageDatabase | undefined; get hasPendingUpdate(): boolean { - return (!!this.globalStorageDatabase && this.globalStorageDatabase.hasPendingUpdate) || (!!this.workspaceStorageDatabase && this.workspaceStorageDatabase.hasPendingUpdate); + return Boolean(this.globalStorageDatabase?.hasPendingUpdate || this.workspaceStorageDatabase?.hasPendingUpdate); } constructor( private readonly payload: IWorkspaceInitializationPayload, + @ILogService private readonly logService: ILogService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IFileService private readonly fileService: IFileService ) { super({ flushInterval: BrowserStorageService.BROWSER_DEFAULT_FLUSH_INTERVAL }); } + private getId(scope: StorageScope): string { + return scope === StorageScope.GLOBAL ? 'global' : this.payload.id; + } + protected async doInitialize(): Promise { - // Ensure state folder exists - const stateRoot = joinPath(this.environmentService.userRoamingDataHome, 'state'); - await this.fileService.createFolder(stateRoot); + // Create Storage in Parallel + const [workspaceStorageDatabase, globalStorageDatabase] = await Promises.settled([ + IndexedDBStorageDatabase.create(this.getId(StorageScope.WORKSPACE), this.logService), + IndexedDBStorageDatabase.create(this.getId(StorageScope.GLOBAL), this.logService) + ]); // Workspace Storage - this.workspaceStorageFile = joinPath(stateRoot, `${this.payload.id}.json`); - - this.workspaceStorageDatabase = this._register(new FileStorageDatabase(this.workspaceStorageFile, false /* do not watch for external changes */, this.fileService)); + this.workspaceStorageDatabase = this._register(workspaceStorageDatabase); this.workspaceStorage = this._register(new Storage(this.workspaceStorageDatabase)); this._register(this.workspaceStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.WORKSPACE, key))); // Global Storage - this.globalStorageFile = joinPath(stateRoot, 'global.json'); - this.globalStorageDatabase = this._register(new FileStorageDatabase(this.globalStorageFile, true /* watch for external changes */, this.fileService)); + this.globalStorageDatabase = this._register(globalStorageDatabase); this.globalStorage = this._register(new Storage(this.globalStorageDatabase)); this._register(this.globalStorage.onDidChangeStorage(key => this.emitDidChangeValue(StorageScope.GLOBAL, key))); @@ -68,6 +69,7 @@ export class BrowserStorageService extends AbstractStorageService { // Check to see if this is the first time we are "opening" the application const firstOpen = this.globalStorage.getBoolean(IS_NEW_KEY); if (firstOpen === undefined) { + await this.migrateOldStorage(StorageScope.GLOBAL); // TODO@bpasero remove browser storage migration this.globalStorage.set(IS_NEW_KEY, true); } else if (firstOpen) { this.globalStorage.set(IS_NEW_KEY, false); @@ -76,18 +78,49 @@ export class BrowserStorageService extends AbstractStorageService { // Check to see if this is the first time we are "opening" this workspace const firstWorkspaceOpen = this.workspaceStorage.getBoolean(IS_NEW_KEY); if (firstWorkspaceOpen === undefined) { + await this.migrateOldStorage(StorageScope.WORKSPACE); // TODO@bpasero remove browser storage migration this.workspaceStorage.set(IS_NEW_KEY, true); } else if (firstWorkspaceOpen) { this.workspaceStorage.set(IS_NEW_KEY, false); } } + private async migrateOldStorage(scope: StorageScope): Promise { + try { + const stateRoot = joinPath(this.environmentService.userRoamingDataHome, 'state'); + + if (scope === StorageScope.GLOBAL) { + const globalStorageFile = joinPath(stateRoot, 'global.json'); + const globalItemsRaw = await this.fileService.readFile(globalStorageFile); + const globalItems = new Map(JSON.parse(globalItemsRaw.value.toString())); + + for (const [key, value] of globalItems) { + this.globalStorage?.set(key, value); + } + + await this.fileService.del(globalStorageFile); + } else if (scope === StorageScope.WORKSPACE) { + const workspaceStorageFile = joinPath(stateRoot, `${this.payload.id}.json`); + const workspaceItemsRaw = await this.fileService.readFile(workspaceStorageFile); + const workspaceItems = new Map(JSON.parse(workspaceItemsRaw.value.toString())); + + for (const [key, value] of workspaceItems) { + this.workspaceStorage?.set(key, value); + } + + await this.fileService.del(workspaceStorageFile); + } + } catch (error) { + // ignore + } + } + protected getStorage(scope: StorageScope): IStorage | undefined { return scope === StorageScope.GLOBAL ? this.globalStorage : this.workspaceStorage; } protected getLogDetails(scope: StorageScope): string | undefined { - return scope === StorageScope.GLOBAL ? this.globalStorageFile?.toString() : this.workspaceStorageFile?.toString(); + return this.getId(scope); } async migrate(toWorkspace: IWorkspaceInitializationPayload): Promise { @@ -118,144 +151,208 @@ export class BrowserStorageService extends AbstractStorageService { // get triggered in this phase. this.dispose(); } + + async clear(): Promise { + + // Note: used for testing purposes only! + + // Clear DBs + await Promises.settled([ + this.globalStorageDatabase?.clear() ?? Promise.resolve(), + this.workspaceStorageDatabase?.clear() ?? Promise.resolve() + ]); + + // Flush to ensure data has been cleared + await this.flush(); + } } -export class FileStorageDatabase extends Disposable implements IStorageDatabase { +interface IIndexedDBStorageDatabase extends IStorageDatabase, IDisposable { - private readonly _onDidChangeItemsExternal = this._register(new Emitter()); - readonly onDidChangeItemsExternal = this._onDidChangeItemsExternal.event; + /** + * Whether an update in the DB is currently pending + * (either update or delete operation). + */ + readonly hasPendingUpdate: boolean; - private cache: Map | undefined; + /** + * For testing only. + */ + clear(): Promise; +} - private pendingUpdate: Promise = Promise.resolve(); +class InMemoryIndexedDBStorageDatabase extends InMemoryStorageDatabase implements IIndexedDBStorageDatabase { - private _hasPendingUpdate = false; - get hasPendingUpdate(): boolean { - return this._hasPendingUpdate; + readonly hasPendingUpdate = false; + + async clear(): Promise { + (await this.getItems()).clear(); } - private isWatching = false; + dispose(): void { + // No-op + } +} - constructor( - private readonly file: URI, - private readonly watchForExternalChanges: boolean, - @IFileService private readonly fileService: IFileService +export class IndexedDBStorageDatabase extends Disposable implements IIndexedDBStorageDatabase { + + static async create(id: string, logService: ILogService): Promise { + try { + const database = new IndexedDBStorageDatabase(id, logService); + await database.whenConnected; + + return database; + } catch (error) { + logService.error(`[IndexedDB Storage ${id}] create(): ${toErrorMessage(error, true)}`); + + return new InMemoryIndexedDBStorageDatabase(); + } + } + + private static readonly STORAGE_DATABASE_PREFIX = 'vscode-web-state-db-'; + private static readonly STORAGE_OBJECT_STORE = 'ItemTable'; + + readonly onDidChangeItemsExternal = Event.None; // IndexedDB currently does not support observers (https://github.com/w3c/IndexedDB/issues/51) + + private pendingUpdate: Promise | undefined = undefined; + get hasPendingUpdate(): boolean { return !!this.pendingUpdate; } + + private readonly name: string; + private readonly whenConnected: Promise; + + private constructor( + id: string, + private readonly logService: ILogService ) { super(); + + this.name = `${IndexedDBStorageDatabase.STORAGE_DATABASE_PREFIX}${id}`; + this.whenConnected = this.connect(); } - private async ensureWatching(): Promise { - if (this.isWatching || !this.watchForExternalChanges) { - return; - } + private connect(): Promise { + return new Promise((resolve, reject) => { + const request = window.indexedDB.open(this.name); - const exists = await this.fileService.exists(this.file); - if (this.isWatching || !exists) { - return; // file must exist to be watched - } + // Create `ItemTable` object-store when this DB is new + request.onupgradeneeded = () => { + request.result.createObjectStore(IndexedDBStorageDatabase.STORAGE_OBJECT_STORE); + }; - this.isWatching = true; + // IndexedDB opened successfully + request.onsuccess = () => resolve(request.result); - this._register(this.fileService.watch(this.file)); - this._register(this.fileService.onDidFilesChange(e => { - if (document.hasFocus()) { - return; // optimization: ignore changes from ourselves by checking for focus - } - - if (!e.contains(this.file, FileChangeType.UPDATED)) { - return; // not our file - } - - this.onDidStorageChangeExternal(); - })); + // Fail on error (we will then fallback to in-memory DB) + request.onerror = () => reject(request.error); + }); } - private async onDidStorageChangeExternal(): Promise { - const items = await this.doGetItemsFromFile(); + getItems(): Promise> { + return new Promise>(async resolve => { + const items = new Map(); - // pervious cache, diff for changes - let changed = new Map(); - let deleted = new Set(); - if (this.cache) { - items.forEach((value, key) => { - const existingValue = this.cache?.get(key); - if (existingValue !== value) { - changed.set(key, value); + // Open a IndexedDB Cursor to iterate over key/values + const db = await this.whenConnected; + const transaction = db.transaction(IndexedDBStorageDatabase.STORAGE_OBJECT_STORE, 'readonly'); + const objectStore = transaction.objectStore(IndexedDBStorageDatabase.STORAGE_OBJECT_STORE); + const cursor = objectStore.openCursor(); + if (!cursor) { + return resolve(items); // this means the `ItemTable` was empty + } + + // Iterate over rows of `ItemTable` until the end + cursor.onsuccess = () => { + if (cursor.result) { + + // Keep cursor key/value in our map + if (typeof cursor.result.value === 'string') { + items.set(cursor.result.key.toString(), cursor.result.value); + } + + // Advance cursor to next row + cursor.result.continue(); + } else { + resolve(items); // reached end of table } - }); + }; - this.cache.forEach((_, key) => { - if (!items.has(key)) { - deleted.add(key); - } - }); - } + const onError = (error: Error | null) => { + this.logService.error(`[IndexedDB Storage ${this.name}] getItems(): ${toErrorMessage(error, true)}`); - // no previous cache, consider all as changed - else { - changed = items; - } + resolve(items); + }; - // Update cache - this.cache = items; - - // Emit as event as needed - if (changed.size > 0 || deleted.size > 0) { - this._onDidChangeItemsExternal.fire({ changed, deleted }); - } - } - - async getItems(): Promise> { - if (!this.cache) { - try { - this.cache = await this.doGetItemsFromFile(); - } catch (error) { - this.cache = new Map(); - } - } - - return this.cache; - } - - private async doGetItemsFromFile(): Promise> { - await this.pendingUpdate; - - const itemsRaw = await this.fileService.readFile(this.file); - - this.ensureWatching(); // now that the file must exist, ensure we watch it for changes - - return new Map(JSON.parse(itemsRaw.value.toString())); + // Error handlers + cursor.onerror = () => onError(cursor.error); + transaction.onerror = () => onError(transaction.error); + }); } async updateItems(request: IUpdateRequest): Promise { - const items = await this.getItems(); + this.pendingUpdate = this.doUpdateItems(request); + try { + await this.pendingUpdate; + } finally { + this.pendingUpdate = undefined; + } + } - if (request.insert) { - request.insert.forEach((value, key) => items.set(key, value)); + private async doUpdateItems(request: IUpdateRequest): Promise { + + // Return early if the request is empty + const toInsert = request.insert; + const toDelete = request.delete; + if ((!toInsert && !toDelete) || (toInsert?.size === 0 && toDelete?.size === 0)) { + return; } - if (request.delete) { - request.delete.forEach(key => items.delete(key)); - } + // Update `ItemTable` with inserts and/or deletes + return new Promise(async (resolve, reject) => { + const db = await this.whenConnected; + const transaction = db.transaction(IndexedDBStorageDatabase.STORAGE_OBJECT_STORE, 'readwrite'); + transaction.oncomplete = () => resolve(); + transaction.onerror = () => reject(transaction.error); + + const objectStore = transaction.objectStore(IndexedDBStorageDatabase.STORAGE_OBJECT_STORE); + + // Inserts + if (toInsert) { + for (const [key, value] of toInsert) { + objectStore.put(value, key); + } + } + + // Deletes + if (toDelete) { + for (const key of toDelete) { + objectStore.delete(key); + } + } + }); + } + + async close(): Promise { + const db = await this.whenConnected; + + // Wait for pending updates to having finished await this.pendingUpdate; - this.pendingUpdate = (async () => { - try { - this._hasPendingUpdate = true; - - await this.fileService.writeFile(this.file, VSBuffer.fromString(JSON.stringify(Array.from(items.entries())))); - - this.ensureWatching(); // now that the file must exist, ensure we watch it for changes - } finally { - this._hasPendingUpdate = false; - } - })(); - - return this.pendingUpdate; + // Finally, close IndexedDB + return db.close(); } - close(): Promise { - return this.pendingUpdate; + clear(): Promise { + return new Promise(async (resolve, reject) => { + const db = await this.whenConnected; + + const transaction = db.transaction(IndexedDBStorageDatabase.STORAGE_OBJECT_STORE, 'readwrite'); + transaction.oncomplete = () => resolve(); + transaction.onerror = () => reject(transaction.error); + + // Clear every row in the `ItemTable` + const objectStore = transaction.objectStore(IndexedDBStorageDatabase.STORAGE_OBJECT_STORE); + objectStore.clear(); + }); } } diff --git a/src/vs/platform/storage/electron-main/storageMain.ts b/src/vs/platform/storage/electron-main/storageMain.ts index 16ca034a873..d4658a4cd14 100644 --- a/src/vs/platform/storage/electron-main/storageMain.ts +++ b/src/vs/platform/storage/electron-main/storageMain.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { exists, Promises, writeFile } from 'vs/base/node/pfs'; +import { Promises } from 'vs/base/node/pfs'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { ILogService, LogLevel } from 'vs/platform/log/common/log'; @@ -269,7 +269,7 @@ export class WorkspaceStorageMain extends BaseStorageMain implements IStorageMai const workspaceStorageFolderPath = join(this.environmentService.workspaceStorageHome.fsPath, this.workspace.id); const workspaceStorageDatabasePath = join(workspaceStorageFolderPath, WorkspaceStorageMain.WORKSPACE_STORAGE_NAME); - const storageExists = await exists(workspaceStorageFolderPath); + const storageExists = await Promises.exists(workspaceStorageFolderPath); if (storageExists) { return { storageFilePath: workspaceStorageDatabasePath, wasCreated: false }; } @@ -294,9 +294,9 @@ export class WorkspaceStorageMain extends BaseStorageMain implements IStorageMai if (meta) { try { const workspaceStorageMetaPath = join(workspaceStorageFolderPath, WorkspaceStorageMain.WORKSPACE_META_NAME); - const storageExists = await exists(workspaceStorageMetaPath); + const storageExists = await Promises.exists(workspaceStorageMetaPath); if (!storageExists) { - await writeFile(workspaceStorageMetaPath, JSON.stringify(meta, undefined, 2)); + await Promises.writeFile(workspaceStorageMetaPath, JSON.stringify(meta, undefined, 2)); } } catch (error) { this.logService.error(`StorageMain#ensureWorkspaceStorageFolderMeta(): Unable to create workspace storage metadata due to ${error}`); diff --git a/src/vs/platform/storage/test/browser/storageService.test.ts b/src/vs/platform/storage/test/browser/storageService.test.ts index 29b53c113e8..d87c9ffcbf2 100644 --- a/src/vs/platform/storage/test/browser/storageService.test.ts +++ b/src/vs/platform/storage/test/browser/storageService.test.ts @@ -4,18 +4,19 @@ *--------------------------------------------------------------------------------------------*/ import { strictEqual } from 'assert'; -import { BrowserStorageService, FileStorageDatabase } from 'vs/platform/storage/browser/storageService'; +import { BrowserStorageService, IndexedDBStorageDatabase } from 'vs/platform/storage/browser/storageService'; import { NullLogService } from 'vs/platform/log/common/log'; import { Storage } from 'vs/base/parts/storage/common/storage'; -import { URI } from 'vs/base/common/uri'; -import { FileService } from 'vs/platform/files/common/fileService'; import { DisposableStore } from 'vs/base/common/lifecycle'; -import { Schemas } from 'vs/base/common/network'; -import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; import { createSuite } from 'vs/platform/storage/test/common/storageService.test'; +import { flakySuite } from 'vs/base/test/common/testUtils'; +import { FileService } from 'vs/platform/files/common/fileService'; +import { InMemoryFileSystemProvider } from 'vs/platform/files/common/inMemoryFilesystemProvider'; +import { Schemas } from 'vs/base/common/network'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { URI } from 'vs/base/common/uri'; -suite('StorageService (browser)', function () { +flakySuite('StorageService (browser)', function () { const disposables = new DisposableStore(); let storageService: BrowserStorageService; @@ -29,63 +30,81 @@ suite('StorageService (browser)', function () { const userDataProvider = disposables.add(new InMemoryFileSystemProvider()); disposables.add(fileService.registerProvider(Schemas.userData, userDataProvider)); - storageService = disposables.add(new BrowserStorageService({ id: String(Date.now()) }, { userRoamingDataHome: URI.file('/User').with({ scheme: Schemas.userData }) } as unknown as IEnvironmentService, fileService)); + storageService = disposables.add(new BrowserStorageService({ id: 'workspace-storage-test' }, logService, { userRoamingDataHome: URI.file('/User').with({ scheme: Schemas.userData }) } as unknown as IEnvironmentService, fileService)); await storageService.initialize(); return storageService; }, - teardown: async storage => { - await storageService.flush(); + teardown: async () => { + await storageService.clear(); disposables.clear(); } }); }); -suite('FileStorageDatabase (browser)', () => { +flakySuite('IndexDBStorageDatabase (browser)', () => { - let fileService: FileService; + const id = 'workspace-storage-db-test'; + const logService = new NullLogService(); - const disposables = new DisposableStore(); - - setup(async () => { - const logService = new NullLogService(); - - fileService = disposables.add(new FileService(logService)); - - const userDataProvider = disposables.add(new InMemoryFileSystemProvider()); - disposables.add(fileService.registerProvider(Schemas.userData, userDataProvider)); - }); - - teardown(() => { - disposables.clear(); + teardown(async () => { + const storage = await IndexedDBStorageDatabase.create(id, logService); + await storage.clear(); }); test('Basics', async () => { - const testDir = URI.file('/User/storage.json').with({ scheme: Schemas.userData }); - - let storage = new Storage(new FileStorageDatabase(testDir, false, fileService)); + let storage = new Storage(await IndexedDBStorageDatabase.create(id, logService)); await storage.init(); + // Insert initial data storage.set('bar', 'foo'); storage.set('barNumber', 55); storage.set('barBoolean', true); + storage.set('barUndefined', undefined); + storage.set('barNull', null); strictEqual(storage.get('bar'), 'foo'); strictEqual(storage.get('barNumber'), '55'); strictEqual(storage.get('barBoolean'), 'true'); + strictEqual(storage.get('barUndefined'), undefined); + strictEqual(storage.get('barNull'), undefined); await storage.close(); - storage = new Storage(new FileStorageDatabase(testDir, false, fileService)); + storage = new Storage(await IndexedDBStorageDatabase.create(id, logService)); await storage.init(); + // Check initial data still there strictEqual(storage.get('bar'), 'foo'); strictEqual(storage.get('barNumber'), '55'); strictEqual(storage.get('barBoolean'), 'true'); + strictEqual(storage.get('barUndefined'), undefined); + strictEqual(storage.get('barNull'), undefined); + // Update data + storage.set('bar', 'foo2'); + storage.set('barNumber', 552); + + strictEqual(storage.get('bar'), 'foo2'); + strictEqual(storage.get('barNumber'), '552'); + + await storage.close(); + + storage = new Storage(await IndexedDBStorageDatabase.create(id, logService)); + + await storage.init(); + + // Check initial data still there + strictEqual(storage.get('bar'), 'foo2'); + strictEqual(storage.get('barNumber'), '552'); + strictEqual(storage.get('barBoolean'), 'true'); + strictEqual(storage.get('barUndefined'), undefined); + strictEqual(storage.get('barNull'), undefined); + + // Delete data storage.delete('bar'); storage.delete('barNumber'); storage.delete('barBoolean'); @@ -96,7 +115,7 @@ suite('FileStorageDatabase (browser)', () => { await storage.close(); - storage = new Storage(new FileStorageDatabase(testDir, false, fileService)); + storage = new Storage(await IndexedDBStorageDatabase.create(id, logService)); await storage.init(); @@ -104,4 +123,37 @@ suite('FileStorageDatabase (browser)', () => { strictEqual(storage.get('barNumber', 'undefinedNumber'), 'undefinedNumber'); strictEqual(storage.get('barBoolean', 'undefinedBoolean'), 'undefinedBoolean'); }); + + test('Inserts and Deletes at the same time', async () => { + let storage = new Storage(await IndexedDBStorageDatabase.create(id, logService)); + + await storage.init(); + + storage.set('bar', 'foo'); + storage.set('barNumber', 55); + storage.set('barBoolean', true); + + await storage.close(); + + storage = new Storage(await IndexedDBStorageDatabase.create(id, logService)); + + await storage.init(); + + storage.set('bar', 'foobar'); + const largeItem = JSON.stringify({ largeItem: 'Hello World'.repeat(1000) }); + storage.set('largeItem', largeItem); + storage.delete('barNumber'); + storage.delete('barBoolean'); + + await storage.close(); + + storage = new Storage(await IndexedDBStorageDatabase.create(id, logService)); + + await storage.init(); + + strictEqual(storage.get('bar'), 'foobar'); + strictEqual(storage.get('largeItem'), largeItem); + strictEqual(storage.get('barNumber'), undefined); + strictEqual(storage.get('barBoolean'), undefined); + }); }); diff --git a/src/vs/platform/telemetry/node/telemetry.ts b/src/vs/platform/telemetry/node/telemetry.ts index e47fc65c568..6b511a593fc 100644 --- a/src/vs/platform/telemetry/node/telemetry.ts +++ b/src/vs/platform/telemetry/node/telemetry.ts @@ -3,43 +3,52 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { readdirSync } from 'vs/base/node/pfs'; -import { statSync, readFileSync } from 'fs'; +import { Promises } from 'vs/base/node/pfs'; import { join } from 'vs/base/common/path'; -export function buildTelemetryMessage(appRoot: string, extensionsPath?: string): string { +export async function buildTelemetryMessage(appRoot: string, extensionsPath?: string): Promise { const mergedTelemetry = Object.create(null); + // Simple function to merge the telemetry into one json object const mergeTelemetry = (contents: string, dirName: string) => { const telemetryData = JSON.parse(contents); mergedTelemetry[dirName] = telemetryData; }; + if (extensionsPath) { - // Gets all the directories inside the extension directory - const dirs = readdirSync(extensionsPath).filter(files => { - // This handles case where broken symbolic links can cause statSync to throw and error + const dirs: string[] = []; + + const files = await Promises.readdir(extensionsPath); + for (const file of files) { try { - return statSync(join(extensionsPath, files)).isDirectory(); + const fileStat = await Promises.stat(join(extensionsPath, file)); + if (fileStat.isDirectory()) { + dirs.push(file); + } } catch { - return false; + // This handles case where broken symbolic links can cause statSync to throw and error } - }); + } + const telemetryJsonFolders: string[] = []; - dirs.forEach((dir) => { - const files = readdirSync(join(extensionsPath, dir)).filter(file => file === 'telemetry.json'); - // We know it contains a telemetry.json file so we add it to the list of folders which have one + for (const dir of dirs) { + const files = (await Promises.readdir(join(extensionsPath, dir))).filter(file => file === 'telemetry.json'); if (files.length === 1) { - telemetryJsonFolders.push(dir); + telemetryJsonFolders.push(dir); // // We know it contains a telemetry.json file so we add it to the list of folders which have one } - }); - telemetryJsonFolders.forEach((folder) => { - const contents = readFileSync(join(extensionsPath, folder, 'telemetry.json')).toString(); + } + + for (const folder of telemetryJsonFolders) { + const contents = (await Promises.readFile(join(extensionsPath, folder, 'telemetry.json'))).toString(); mergeTelemetry(contents, folder); - }); + } } - let contents = readFileSync(join(appRoot, 'telemetry-core.json')).toString(); + + let contents = (await Promises.readFile(join(appRoot, 'telemetry-core.json'))).toString(); mergeTelemetry(contents, 'vscode-core'); - contents = readFileSync(join(appRoot, 'telemetry-extensions.json')).toString(); + + contents = (await Promises.readFile(join(appRoot, 'telemetry-extensions.json'))).toString(); mergeTelemetry(contents, 'vscode-extensions'); + return JSON.stringify(mergedTelemetry, null, 4); } diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index b52b1622908..6a0a7aa0dd1 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -184,7 +184,7 @@ export interface IOffProcessTerminalService { attachToProcess(id: number): Promise; listProcesses(): Promise; getDefaultSystemShell(osOverride?: OperatingSystem): Promise; - getProfiles(includeDetectedProfiles?: boolean): Promise; + getProfiles(profiles: unknown, defaultProfile: unknown, includeDetectedProfiles?: boolean): Promise; getWslPath(original: string): Promise; getEnvironment(): Promise; getShellEnvironment(): Promise; @@ -258,7 +258,7 @@ export interface IPtyService { updateTitle(id: number, title: string, titleSource: TitleEventSource): Promise; updateIcon(id: number, icon: TerminalIcon, color?: string): Promise; getDefaultSystemShell(osOverride?: OperatingSystem): Promise; - getProfiles?(includeDetectedProfiles?: boolean): Promise; + getProfiles?(profiles: unknown, defaultProfile: unknown, includeDetectedProfiles?: boolean): Promise; getEnvironment(): Promise; getWslPath(original: string): Promise; setTerminalLayoutInfo(args: ISetTerminalLayoutInfoArgs): Promise; diff --git a/src/vs/platform/terminal/node/ptyHostService.ts b/src/vs/platform/terminal/node/ptyHostService.ts index 1e79f19bd82..9e00f75b520 100644 --- a/src/vs/platform/terminal/node/ptyHostService.ts +++ b/src/vs/platform/terminal/node/ptyHostService.ts @@ -222,8 +222,8 @@ export class PtyHostService extends Disposable implements IPtyService { getDefaultSystemShell(osOverride?: OperatingSystem): Promise { return this._proxy.getDefaultSystemShell(osOverride); } - async getProfiles(includeDetectedProfiles: boolean = false): Promise { - return detectAvailableProfiles(includeDetectedProfiles, this._configurationService, undefined, this._logService, this._resolveVariables.bind(this)); + async getProfiles(profiles: unknown, defaultProfile: unknown, includeDetectedProfiles: boolean = false): Promise { + return detectAvailableProfiles(profiles, defaultProfile, includeDetectedProfiles, this._configurationService, undefined, this._logService, this._resolveVariables.bind(this)); } getEnvironment(): Promise { return this._proxy.getEnvironment(); diff --git a/src/vs/platform/terminal/node/terminalEnvironment.ts b/src/vs/platform/terminal/node/terminalEnvironment.ts index 91f40c9d807..3574b0d9c7c 100644 --- a/src/vs/platform/terminal/node/terminalEnvironment.ts +++ b/src/vs/platform/terminal/node/terminalEnvironment.ts @@ -20,7 +20,7 @@ export function getWindowsBuildNumber(): number { return buildNumber; } -export async function findExecutable(command: string, cwd?: string, paths?: string[], env: IProcessEnvironment = process.env as IProcessEnvironment, exists: (path: string) => Promise = pfs.exists): Promise { +export async function findExecutable(command: string, cwd?: string, paths?: string[], env: IProcessEnvironment = process.env as IProcessEnvironment, exists: (path: string) => Promise = pfs.Promises.exists): Promise { // If we have an absolute path then we take it. if (path.isAbsolute(command)) { return await exists(command) ? command : undefined; diff --git a/src/vs/platform/terminal/node/terminalProcess.ts b/src/vs/platform/terminal/node/terminalProcess.ts index b37b6ab2c15..000c9c5b896 100644 --- a/src/vs/platform/terminal/node/terminalProcess.ts +++ b/src/vs/platform/terminal/node/terminalProcess.ts @@ -5,7 +5,6 @@ import * as path from 'vs/base/common/path'; import type * as pty from 'node-pty'; -import * as fs from 'fs'; import * as os from 'os'; import { Event, Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -461,7 +460,7 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess return Promise.resolve(this._initialCwd); } - getCwd(): Promise { + async getCwd(): Promise { if (isMacintosh) { // Disable cwd lookup on macOS Big Sur due to spawn blocking thread (darwin v20 is macOS // Big Sur) https://github.com/Microsoft/vscode/issues/105446 @@ -486,24 +485,18 @@ export class TerminalProcess extends Disposable implements ITerminalChildProcess } if (isLinux) { - return new Promise(resolve => { - if (!this._ptyProcess) { - resolve(this._initialCwd); - return; - } - this._logService.trace('IPty#pid'); - fs.readlink('/proc/' + this._ptyProcess.pid + '/cwd', (err, linkedstr) => { - if (err) { - resolve(this._initialCwd); - } - resolve(linkedstr); - }); - }); + if (!this._ptyProcess) { + return this._initialCwd; + } + this._logService.trace('IPty#pid'); + try { + return await Promises.readlink(`/proc/${this._ptyProcess.pid}/cwd`); + } catch (error) { + return this._initialCwd; + } } - return new Promise(resolve => { - resolve(this._initialCwd); - }); + return this._initialCwd; } getLatency(): Promise { diff --git a/src/vs/platform/terminal/node/terminalProfiles.ts b/src/vs/platform/terminal/node/terminalProfiles.ts index 9ffbff55dad..3096641c5de 100644 --- a/src/vs/platform/terminal/node/terminalProfiles.ts +++ b/src/vs/platform/terminal/node/terminalProfiles.ts @@ -11,7 +11,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import * as pfs from 'vs/base/node/pfs'; import { ITerminalEnvironment, ITerminalProfile, ITerminalProfileObject, ProfileSource, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { Codicon } from 'vs/base/common/codicons'; -import { isMacintosh, isWindows } from 'vs/base/common/platform'; +import { isLinux, isWindows } from 'vs/base/common/platform'; import { ThemeIcon } from 'vs/platform/theme/common/themeService'; import { URI } from 'vs/base/common/uri'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -19,6 +19,8 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur let profileSources: Map | undefined; export function detectAvailableProfiles( + profiles: unknown, + defaultProfile: unknown, includeDetectedProfiles: boolean, configurationService: IConfigurationService, fsProvider?: IFsProvider, @@ -36,8 +38,8 @@ export function detectAvailableProfiles( fsProvider, logService, configurationService.getValue(TerminalSettingId.UseWslProfiles) !== false, - configurationService.getValue(TerminalSettingId.ProfilesWindows), - configurationService.getValue(TerminalSettingId.DefaultProfileWindows), + profiles && typeof profiles === 'object' ? { ...profiles } : configurationService.getValue<{ [key: string]: ITerminalProfileObject }>(TerminalSettingId.ProfilesWindows), + typeof defaultProfile === 'string' ? defaultProfile : configurationService.getValue(TerminalSettingId.DefaultProfileWindows), testPaths, variableResolver ); @@ -46,8 +48,8 @@ export function detectAvailableProfiles( fsProvider, logService, includeDetectedProfiles, - configurationService.getValue(isMacintosh ? TerminalSettingId.ProfilesMacOs : TerminalSettingId.ProfilesLinux), - configurationService.getValue(isMacintosh ? TerminalSettingId.DefaultProfileMacOs : TerminalSettingId.DefaultProfileLinux), + profiles && typeof profiles === 'object' ? { ...profiles } : configurationService.getValue<{ [key: string]: ITerminalProfileObject }>(isLinux ? TerminalSettingId.ProfilesLinux : TerminalSettingId.ProfilesMacOs), + typeof defaultProfile === 'string' ? defaultProfile : configurationService.getValue(isLinux ? TerminalSettingId.DefaultProfileLinux : TerminalSettingId.DefaultProfileMacOs), testPaths, variableResolver ); diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts index 93424cad965..81d832df692 100644 --- a/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -133,7 +133,7 @@ export class Win32UpdateService extends AbstractUpdateService { return this.cleanup(update.version).then(() => { return this.getUpdatePackagePath(update.version).then(updatePackagePath => { - return pfs.exists(updatePackagePath).then(exists => { + return pfs.Promises.exists(updatePackagePath).then(exists => { if (exists) { return Promise.resolve(updatePackagePath); } @@ -191,7 +191,7 @@ export class Win32UpdateService extends AbstractUpdateService { const filter = exceptVersion ? (one: string) => !(new RegExp(`${this.productService.quality}-${exceptVersion}\\.exe$`).test(one)) : () => true; const cachePath = await this.cachePath; - const versions = await pfs.readdir(cachePath); + const versions = await pfs.Promises.readdir(cachePath); const promises = versions.filter(filter).map(async one => { try { @@ -220,7 +220,7 @@ export class Win32UpdateService extends AbstractUpdateService { this.availableUpdate.updateFilePath = path.join(cachePath, `CodeSetup-${this.productService.quality}-${update.version}.flag`); - await pfs.writeFile(this.availableUpdate.updateFilePath, 'flag'); + await pfs.Promises.writeFile(this.availableUpdate.updateFilePath, 'flag'); const child = spawn(this.availableUpdate.packagePath, ['/verysilent', `/update="${this.availableUpdate.updateFilePath}"`, '/nocloseapplications', '/mergetasks=runcode,!desktopicon,!quicklaunchicon'], { detached: true, stdio: ['ignore', 'ignore', 'ignore'], diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index cc5ac005a9a..bc7c196d9c2 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -56,8 +56,7 @@ export interface IOpenedWindow { readonly dirty: boolean; } -export interface IOpenEmptyWindowOptions extends IBaseOpenWindowsOptions { -} +export interface IOpenEmptyWindowOptions extends IBaseOpenWindowsOptions { } export type IWindowOpenable = IWorkspaceToOpen | IFolderToOpen | IFileToOpen; diff --git a/src/vs/platform/windows/electron-main/window.ts b/src/vs/platform/windows/electron-main/window.ts index c597738aa69..9ec6e9fd476 100644 --- a/src/vs/platform/windows/electron-main/window.ts +++ b/src/vs/platform/windows/electron-main/window.ts @@ -186,7 +186,6 @@ export class CodeWindow extends Disposable implements ICodeWindow { [`--vscode-window-config=${this.configObjectUrl.resource.toString()}`], v8CacheOptions: browserCodeLoadingCacheStrategy, enableWebSQL: false, - enableRemoteModule: false, spellcheck: false, nativeWindowOpen: true, webviewTag: true, diff --git a/src/vs/platform/windows/electron-main/windowsMainService.ts b/src/vs/platform/windows/electron-main/windowsMainService.ts index 991c9585ec6..20140404a87 100644 --- a/src/vs/platform/windows/electron-main/windowsMainService.ts +++ b/src/vs/platform/windows/electron-main/windowsMainService.ts @@ -856,6 +856,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic if (isFileToOpen(openable)) { options = { ...options, forceOpenWorkspaceAsFile: true }; } + return this.doResolveFilePath(uri.fsPath, options); } diff --git a/src/vs/platform/workspace/common/workspaceTrust.ts b/src/vs/platform/workspace/common/workspaceTrust.ts index b0fa5450b47..275035ffa39 100644 --- a/src/vs/platform/workspace/common/workspaceTrust.ts +++ b/src/vs/platform/workspace/common/workspaceTrust.ts @@ -46,6 +46,7 @@ export interface IWorkspaceTrustManagementService { acceptsOutOfWorkspaceFiles: boolean; isWorkpaceTrusted(): boolean; + isWorkspaceTrustForced(): boolean; canSetParentFolderTrust(): boolean; setParentFolderTrust(trusted: boolean): Promise; diff --git a/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts index 62c029d242b..237efb1086b 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesHistoryMainService.ts @@ -17,7 +17,7 @@ import { ThrottledDelayer } from 'vs/base/common/async'; import { originalFSPath, basename, extUriBiasedIgnorePathCase } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { Schemas } from 'vs/base/common/network'; -import { exists } from 'vs/base/node/pfs'; +import { Promises } from 'vs/base/node/pfs'; import { ILifecycleMainService, LifecycleMainPhase } from 'vs/platform/lifecycle/electron-main/lifecycleMainService'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -190,7 +190,7 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa const loc = location(mru.workspaces[i]); if (loc.scheme === Schemas.file) { const workspacePath = originalFSPath(loc); - if (await exists(workspacePath)) { + if (await Promises.exists(workspacePath)) { workspaceEntries.push(workspacePath); entries++; } @@ -210,7 +210,7 @@ export class WorkspacesHistoryMainService extends Disposable implements IWorkspa continue; } - if (await exists(filePath)) { + if (await Promises.exists(filePath)) { fileEntries.push(filePath); entries++; } diff --git a/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts b/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts index 47a5be15c90..95156bdad36 100644 --- a/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts +++ b/src/vs/platform/workspaces/electron-main/workspacesManagementMainService.ts @@ -6,7 +6,7 @@ import { toWorkspaceFolders, IWorkspaceIdentifier, hasWorkspaceFileExtension, UNTITLED_WORKSPACE_NAME, IResolvedWorkspace, IStoredWorkspaceFolder, isStoredWorkspaceFolder, IWorkspaceFolderCreationData, IUntitledWorkspaceInfo, getStoredWorkspaceFolder, IEnterWorkspaceResult, isUntitledWorkspace, isWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService'; import { join, dirname } from 'vs/base/common/path'; -import { writeFile, rimrafSync, readdirSync, writeFileSync, Promises } from 'vs/base/node/pfs'; +import { rimrafSync, readdirSync, writeFileSync, Promises } from 'vs/base/node/pfs'; import { readFileSync, existsSync, mkdirSync, statSync, Stats } from 'fs'; import { isLinux, isMacintosh, isWindows } from 'vs/base/common/platform'; import { Event, Emitter } from 'vs/base/common/event'; @@ -143,7 +143,7 @@ export class WorkspacesManagementMainService extends Disposable implements IWork const configPath = workspace.configPath.fsPath; await Promises.mkdir(dirname(configPath), { recursive: true }); - await writeFile(configPath, JSON.stringify(storedWorkspace, null, '\t')); + await Promises.writeFile(configPath, JSON.stringify(storedWorkspace, null, '\t')); return workspace; } diff --git a/src/vs/workbench/api/browser/mainThreadAuthentication.ts b/src/vs/workbench/api/browser/mainThreadAuthentication.ts index 19e704dff86..797e8c2ba2f 100644 --- a/src/vs/workbench/api/browser/mainThreadAuthentication.ts +++ b/src/vs/workbench/api/browser/mainThreadAuthentication.ts @@ -34,7 +34,7 @@ export class MainThreadAuthenticationProvider extends Disposable { const allowedExtensions = readAllowedExtensions(this.storageService, this.id, accountName); if (!allowedExtensions.length) { - this.dialogService.show(Severity.Info, nls.localize('noTrustedExtensions', "This account has not been used by any extensions."), []); + this.dialogService.show(Severity.Info, nls.localize('noTrustedExtensions', "This account has not been used by any extensions.")); return; } diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index f122c7fc99e..e4f4400b6c0 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -321,14 +321,20 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I return extHostUrls.createAppUri(uri); } - if (!matchesScheme(uri, Schemas.http) && !matchesScheme(uri, Schemas.https)) { + const isHttp = matchesScheme(uri, Schemas.http) || matchesScheme(uri, Schemas.https); + + if (!isHttp) { checkProposedApiEnabled(extension); // https://github.com/microsoft/vscode/issues/124263 } try { return await extHostWindow.asExternalUri(uri, { allowTunneling: !!initData.remote.authority }); - } catch { - return uri; + } catch (err) { + if (isHttp) { + return uri; + } + + throw err; } }, get remoteName() { diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index 4fe87e99ca2..6aa039d2b3c 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -323,7 +323,7 @@ const newCommands: ApiCommand[] = [ ), // --- inline hints new ApiCommand( - 'vscode.executeInlayHintProvider', '_executeInlayHintProvider', 'Execute inline hints provider', + 'vscode.executeInlayHintProvider', '_executeInlayHintProvider', 'Execute inlay hints provider', [ApiCommandArgument.Uri, ApiCommandArgument.Range], new ApiCommandResult('A promise that resolves to an array of Inlay objects', result => { return result.map(typeConverters.InlayHint.to); diff --git a/src/vs/workbench/api/common/extHostLanguageFeatures.ts b/src/vs/workbench/api/common/extHostLanguageFeatures.ts index 05ab94c722c..20c4ec75d45 100644 --- a/src/vs/workbench/api/common/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/common/extHostLanguageFeatures.ts @@ -1199,50 +1199,62 @@ class LinkProviderAdapter { private readonly _provider: vscode.DocumentLinkProvider ) { } - provideLinks(resource: URI, token: CancellationToken): Promise { + async provideLinks(resource: URI, token: CancellationToken): Promise { const doc = this._documents.getDocument(resource); - return asPromise(() => this._provider.provideDocumentLinks(doc, token)).then(links => { - if (!Array.isArray(links) || links.length === 0) { - // bad result - return undefined; - } + const links = await asPromise(() => this._provider.provideDocumentLinks(doc, token)); + if (!Array.isArray(links) || links.length === 0) { + // bad result + return undefined; + } + if (token.isCancellationRequested) { + // cancelled -> return without further ado, esp no caching + // of results as they will leak + return undefined; + } + if (typeof this._provider.resolveDocumentLink !== 'function') { + // no resolve -> no caching + return { links: links.filter(LinkProviderAdapter._validateLink).map(typeConvert.DocumentLink.from) }; - if (token.isCancellationRequested) { - // cancelled -> return without further ado, esp no caching - // of results as they will leak - return undefined; - } + } else { + // cache links for future resolving + const pid = this._cache.add(links); + const result: extHostProtocol.ILinksListDto = { links: [], id: pid }; + for (let i = 0; i < links.length; i++) { - if (typeof this._provider.resolveDocumentLink !== 'function') { - // no resolve -> no caching - return { links: links.map(typeConvert.DocumentLink.from) }; - - } else { - // cache links for future resolving - const pid = this._cache.add(links); - const result: extHostProtocol.ILinksListDto = { links: [], id: pid }; - for (let i = 0; i < links.length; i++) { - const dto: extHostProtocol.ILinkDto = typeConvert.DocumentLink.from(links[i]); - dto.cacheId = [pid, i]; - result.links.push(dto); + if (!LinkProviderAdapter._validateLink(links[i])) { + continue; } - return result; + + const dto: extHostProtocol.ILinkDto = typeConvert.DocumentLink.from(links[i]); + dto.cacheId = [pid, i]; + result.links.push(dto); } - }); + return result; + } } - resolveLink(id: extHostProtocol.ChainedCacheId, token: CancellationToken): Promise { + private static _validateLink(link: vscode.DocumentLink): boolean { + if (link.target && link.target.path.length > 50_000) { + console.warn('DROPPING link because it is too long'); + return false; + } + return true; + } + + async resolveLink(id: extHostProtocol.ChainedCacheId, token: CancellationToken): Promise { if (typeof this._provider.resolveDocumentLink !== 'function') { - return Promise.resolve(undefined); + return undefined; } const item = this._cache.get(...id); if (!item) { - return Promise.resolve(undefined); + return undefined; } - return asPromise(() => this._provider.resolveDocumentLink!(item, token)).then(value => { - return value && typeConvert.DocumentLink.from(value) || undefined; - }); + const link = await asPromise(() => this._provider.resolveDocumentLink!(item, token)); + if (!link || !LinkProviderAdapter._validateLink(link)) { + return undefined; + } + return typeConvert.DocumentLink.from(link); } releaseLinks(id: number): any { diff --git a/src/vs/workbench/api/common/menusExtensionPoint.ts b/src/vs/workbench/api/common/menusExtensionPoint.ts index 727ff93374e..513eb79d485 100644 --- a/src/vs/workbench/api/common/menusExtensionPoint.ts +++ b/src/vs/workbench/api/common/menusExtensionPoint.ts @@ -236,7 +236,8 @@ const apiMenus: IAPIMenu[] = [ key: 'editor/inlineCompletions/actions', id: MenuId.InlineCompletionsActions, description: localize('inlineCompletions.actions', "The actions shown when hovering on an inline completion"), - supportsSubmenus: false + supportsSubmenus: false, + proposed: true }, ]; diff --git a/src/vs/workbench/api/node/extHostTunnelService.ts b/src/vs/workbench/api/node/extHostTunnelService.ts index caefe078c1a..569e07d7067 100644 --- a/src/vs/workbench/api/node/extHostTunnelService.ts +++ b/src/vs/workbench/api/node/extHostTunnelService.ts @@ -378,7 +378,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe })); const socketMap = getSockets(procSockets); - const procChildren = await pfs.readdir('/proc'); + const procChildren = await pfs.Promises.readdir('/proc'); const processes: { pid: number, cwd: string, cmd: string }[] = []; diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 3d94cd32bc6..391919a1129 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -625,13 +625,13 @@ class MoveFocusedViewAction extends Action2 { const focusedViewId = viewId || FocusedViewContext.getValue(contextKeyService); if (focusedViewId === undefined || focusedViewId.trim() === '') { - dialogService.show(Severity.Error, localize('moveFocusedView.error.noFocusedView', "There is no view currently focused."), [localize('ok', 'OK')]); + dialogService.show(Severity.Error, localize('moveFocusedView.error.noFocusedView', "There is no view currently focused.")); return; } const viewDescriptor = viewDescriptorService.getViewDescriptorById(focusedViewId); if (!viewDescriptor || !viewDescriptor.canMoveView) { - dialogService.show(Severity.Error, localize('moveFocusedView.error.nonMovableView', "The currently focused view is not movable."), [localize('ok', 'OK')]); + dialogService.show(Severity.Error, localize('moveFocusedView.error.nonMovableView', "The currently focused view is not movable.")); return; } @@ -756,7 +756,7 @@ registerAction2(class extends Action2 { } if (!viewDescriptor) { - dialogService.show(Severity.Error, localize('resetFocusedView.error.noFocusedView', "There is no view currently focused."), [localize('ok', 'OK')]); + dialogService.show(Severity.Error, localize('resetFocusedView.error.noFocusedView', "There is no view currently focused.")); return; } diff --git a/src/vs/workbench/browser/actions/workspaceActions.ts b/src/vs/workbench/browser/actions/workspaceActions.ts index 3824b4d3c4b..edd8116c97a 100644 --- a/src/vs/workbench/browser/actions/workspaceActions.ts +++ b/src/vs/workbench/browser/actions/workspaceActions.ts @@ -124,7 +124,7 @@ export class CloseWorkspaceAction extends Action2 { const environmentService = accessor.get(IWorkbenchEnvironmentService); if (contextService.getWorkbenchState() === WorkbenchState.EMPTY) { - dialogService.show(Severity.Error, localize('noWorkspaceOrFolderOpened', "There is currently no workspace or folder opened in this instance to close."), [localize('ok', 'OK')]); + dialogService.show(Severity.Error, localize('noWorkspaceOrFolderOpened', "There is currently no workspace or folder opened in this instance to close.")); return; } diff --git a/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts b/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts index 84c51a948ad..530ce5e5c28 100644 --- a/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts +++ b/src/vs/workbench/browser/parts/dialogs/dialogHandler.ts @@ -71,7 +71,7 @@ export class BrowserDialogHandler implements IDialogHandler { return (severity === Severity.Info) ? 'question' : (severity === Severity.Error) ? 'error' : (severity === Severity.Warning) ? 'warning' : 'none'; } - async show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Promise { + async show(severity: Severity, message: string, buttons?: string[], options?: IDialogOptions): Promise { this.logService.trace('DialogService#show', message); const result = await this.doShow(this.getDialogType(severity), message, buttons, options?.detail, options?.cancelId, options?.checkbox, undefined, typeof options?.custom === 'object' ? options.custom : undefined); @@ -82,7 +82,7 @@ export class BrowserDialogHandler implements IDialogHandler { }; } - private async doShow(type: 'none' | 'info' | 'error' | 'question' | 'warning' | 'pending' | undefined, message: string, buttons: string[], detail?: string, cancelId?: number, checkbox?: ICheckbox, inputs?: IInput[], customOptions?: ICustomDialogOptions): Promise { + private async doShow(type: 'none' | 'info' | 'error' | 'question' | 'warning' | 'pending' | undefined, message: string, buttons?: string[], detail?: string, cancelId?: number, checkbox?: ICheckbox, inputs?: IInput[], customOptions?: ICustomDialogOptions): Promise { const dialogDisposables = new DisposableStore(); const renderBody = customOptions ? (parent: HTMLElement) => { diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index 10cecdf94a4..ed8c6be050b 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -313,7 +313,7 @@ class DropOverlay extends Themable { // Skip for very large files because this operation is unbuffered if (file.size > DropOverlay.MAX_FILE_UPLOAD_SIZE) { - this.dialogService.show(Severity.Warning, localize('fileTooLarge', "File is too large to open as untitled editor. Please upload it first into the file explorer and then try again."), [localize('ok', 'OK')]); + this.dialogService.show(Severity.Warning, localize('fileTooLarge', "File is too large to open as untitled editor. Please upload it first into the file explorer and then try again.")); continue; } diff --git a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts index 926dc846f47..bf1508b3dde 100644 --- a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts +++ b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts @@ -184,11 +184,23 @@ export class SideBySideEditor extends EditorPane { ]); } - private setNewInput(newInput: SideBySideEditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { - const secondaryEditor = this.doCreateEditor(newInput.secondary, assertIsDefined(this.secondaryEditorContainer)); - const primaryEditor = this.doCreateEditor(newInput.primary, assertIsDefined(this.primaryEditorContainer)); + private async setNewInput(newInput: SideBySideEditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { + this.secondaryEditorPane = this.doCreateEditor(newInput.secondary, assertIsDefined(this.secondaryEditorContainer)); + this.primaryEditorPane = this.doCreateEditor(newInput.primary, assertIsDefined(this.primaryEditorContainer)); - return this.onEditorsCreated(secondaryEditor, primaryEditor, newInput.secondary, newInput.primary, options, context, token); + this.layout(this.dimension); + + this._onDidChangeSizeConstraints.input = Event.any( + Event.map(this.secondaryEditorPane.onDidChangeSizeConstraints, () => undefined), + Event.map(this.primaryEditorPane.onDidChangeSizeConstraints, () => undefined) + ); + + this.onDidCreateEditors.fire(undefined); + + await Promise.all([ + this.secondaryEditorPane.setInput(newInput.secondary, undefined, context, token), + this.primaryEditorPane.setInput(newInput.primary, options, context, token)] + ); } private doCreateEditor(editorInput: EditorInput, container: HTMLElement): EditorPane { @@ -204,23 +216,6 @@ export class SideBySideEditor extends EditorPane { return editor; } - private async onEditorsCreated(secondary: EditorPane, primary: EditorPane, secondaryInput: EditorInput, primaryInput: EditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { - this.secondaryEditorPane = secondary; - this.primaryEditorPane = primary; - - this._onDidChangeSizeConstraints.input = Event.any( - Event.map(secondary.onDidChangeSizeConstraints, () => undefined), - Event.map(primary.onDidChangeSizeConstraints, () => undefined) - ); - - this.onDidCreateEditors.fire(undefined); - - await Promise.all([ - this.secondaryEditorPane.setInput(secondaryInput, undefined, context, token), - this.primaryEditorPane.setInput(primaryInput, options, context, token)] - ); - } - override updateStyles(): void { super.updateStyles(); diff --git a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts index 71a85d0fd79..87dbc5d3204 100644 --- a/src/vs/workbench/browser/parts/titlebar/menubarControl.ts +++ b/src/vs/workbench/browser/parts/titlebar/menubarControl.ts @@ -7,14 +7,14 @@ import { localize } from 'vs/nls'; import { IMenuService, MenuId, IMenu, SubmenuItemAction, registerAction2, Action2, MenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; import { registerThemingParticipant, IThemeService } from 'vs/platform/theme/common/themeService'; import { MenuBarVisibility, getTitleBarStyle, IWindowOpenable, getMenuBarVisibility } from 'vs/platform/windows/common/windows'; -import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IAction, Action, SubmenuAction, Separator } from 'vs/base/common/actions'; import { addDisposableListener, Dimension, EventType } from 'vs/base/browser/dom'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { isMacintosh, isWeb, isIOS, isNative } from 'vs/base/common/platform'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { Event, Emitter } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IRecentlyOpened, isRecentFolder, IRecent, isRecentWorkspace, IWorkspacesService } from 'vs/platform/workspaces/common/workspaces'; import { RunOnceScheduler } from 'vs/base/common/async'; import { MENUBAR_SELECTION_FOREGROUND, MENUBAR_SELECTION_BACKGROUND, MENUBAR_SELECTION_BORDER, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_INACTIVE_FOREGROUND } from 'vs/workbench/common/theme'; @@ -91,16 +91,6 @@ MenuRegistry.appendMenuItem(MenuId.MenubarMainMenu, { order: 5 }); -MenuRegistry.appendMenuItem(MenuId.MenubarMainMenu, { - submenu: MenuId.MenubarDebugMenu, - title: { - value: 'Run', - original: 'Run', - mnemonicTitle: localize({ key: 'mRun', comment: ['&& denotes a mnemonic'] }, "&&Run") - }, - order: 6 -}); - MenuRegistry.appendMenuItem(MenuId.MenubarMainMenu, { submenu: MenuId.MenubarTerminalMenu, title: { @@ -108,7 +98,8 @@ MenuRegistry.appendMenuItem(MenuId.MenubarMainMenu, { original: 'Terminal', mnemonicTitle: localize({ key: 'mTerminal', comment: ['&& denotes a mnemonic'] }, "&&Terminal") }, - order: 7 + order: 7, + when: ContextKeyExpr.has('terminalProcessSupported') }); MenuRegistry.appendMenuItem(MenuId.MenubarMainMenu, { @@ -173,16 +164,29 @@ export abstract class MenubarControl extends Disposable { super(); - const mainMenu = this.menuService.createMenu(MenuId.MenubarMainMenu, this.contextKeyService); - const [, mainMenuActions] = mainMenu.getActions()[0]; - for (const mainMenuAction of mainMenuActions) { - if (mainMenuAction instanceof SubmenuItemAction && typeof mainMenuAction.item.title !== 'string') { - this.menus[mainMenuAction.item.title.original] = this._register(this.menuService.createMenu(mainMenuAction.item.submenu, this.contextKeyService)); - this.topLevelTitles[mainMenuAction.item.title.original] = mainMenuAction.item.title.mnemonicTitle ?? mainMenuAction.item.title.value; - } - } + const mainMenu = this._register(this.menuService.createMenu(MenuId.MenubarMainMenu, this.contextKeyService)); + const mainMenuDisposables = this._register(new DisposableStore()); - mainMenu.dispose(); + const setupMenu = () => { + mainMenuDisposables.clear(); + this.menus = {}; + this.topLevelTitles = {}; + + const [, mainMenuActions] = mainMenu.getActions()[0]; + for (const mainMenuAction of mainMenuActions) { + if (mainMenuAction instanceof SubmenuItemAction && typeof mainMenuAction.item.title !== 'string') { + this.menus[mainMenuAction.item.title.original] = mainMenuDisposables.add(this.menuService.createMenu(mainMenuAction.item.submenu, this.contextKeyService)); + this.topLevelTitles[mainMenuAction.item.title.original] = mainMenuAction.item.title.mnemonicTitle ?? mainMenuAction.item.title.value; + } + } + }; + + setupMenu(); + + mainMenu.onDidChange(() => { + setupMenu(); + this.doUpdateMenubar(true); + }); this.menuUpdater = this._register(new RunOnceScheduler(() => this.doUpdateMenubar(false), 200)); @@ -624,6 +628,7 @@ export class CustomMenubarControl extends MenubarControl { this._onVisibilityChange.fire(visible); } + private reinstallDisposables = this._register(new DisposableStore()); private setupCustomMenubar(firstTime: boolean): void { // If there is no container, we cannot setup the menubar if (!this.container) { @@ -631,14 +636,19 @@ export class CustomMenubarControl extends MenubarControl { } if (firstTime) { - this.menubar = this._register(new MenuBar(this.container, this.getMenuBarOptions())); + // Reset and create new menubar + if (this.menubar) { + this.reinstallDisposables.clear(); + } + + this.menubar = this.reinstallDisposables.add(new MenuBar(this.container, this.getMenuBarOptions())); this.accessibilityService.alwaysUnderlineAccessKeys().then(val => { this.alwaysOnMnemonics = val; this.menubar?.update(this.getMenuBarOptions()); }); - this._register(this.menubar.onFocusStateChange(focused => { + this.reinstallDisposables.add(this.menubar.onFocusStateChange(focused => { this._onFocusStateChange.fire(focused); // When the menubar loses focus, update it to clear any pending updates @@ -648,18 +658,18 @@ export class CustomMenubarControl extends MenubarControl { } })); - this._register(this.menubar.onVisibilityChange(e => this.onDidVisibilityChange(e))); + this.reinstallDisposables.add(this.menubar.onVisibilityChange(e => this.onDidVisibilityChange(e))); // Before we focus the menubar, stop updates to it so that focus-related context keys will work - this._register(addDisposableListener(this.container, EventType.FOCUS_IN, () => { + this.reinstallDisposables.add(addDisposableListener(this.container, EventType.FOCUS_IN, () => { this.focusInsideMenubar = true; })); - this._register(addDisposableListener(this.container, EventType.FOCUS_OUT, () => { + this.reinstallDisposables.add(addDisposableListener(this.container, EventType.FOCUS_OUT, () => { this.focusInsideMenubar = false; })); - this._register(attachMenuStyler(this.menubar, this.themeService)); + this.reinstallDisposables.add(attachMenuStyler(this.menubar, this.themeService)); } else { this.menubar?.update(this.getMenuBarOptions()); } @@ -727,7 +737,7 @@ export class CustomMenubarControl extends MenubarControl { for (const title of Object.keys(this.topLevelTitles)) { const menu = this.menus[title]; if (firstTime && menu) { - this._register(menu.onDidChange(() => { + this.reinstallDisposables.add(menu.onDidChange(() => { if (!this.focusInsideMenubar) { const actions: IAction[] = []; updateActions(menu, actions, title); @@ -739,7 +749,7 @@ export class CustomMenubarControl extends MenubarControl { // For the file menu, we need to update if the web nav menu updates as well if (menu === this.menus.File) { - this._register(this.webNavigationMenu.onDidChange(() => { + this.reinstallDisposables.add(this.webNavigationMenu.onDidChange(() => { if (!this.focusInsideMenubar) { const actions: IAction[] = []; updateActions(menu, actions, title); diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index ef1a454bf1c..3de2e328da5 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -331,7 +331,7 @@ class BrowserMain extends Disposable { } private async createStorageService(payload: IWorkspaceInitializationPayload, environmentService: IWorkbenchEnvironmentService, fileService: IFileService, logService: ILogService): Promise { - const storageService = new BrowserStorageService(payload, environmentService, fileService); + const storageService = new BrowserStorageService(payload, logService, environmentService, fileService); try { await storageService.initialize(); diff --git a/src/vs/workbench/browser/window.ts b/src/vs/workbench/browser/window.ts index 05b9f806f35..e0ed972635c 100644 --- a/src/vs/workbench/browser/window.ts +++ b/src/vs/workbench/browser/window.ts @@ -146,11 +146,24 @@ export class BrowserWindow extends Disposable { if (matchesScheme(href, Schemas.http) || matchesScheme(href, Schemas.https)) { const opened = windowOpenNoOpenerWithSuccess(href); if (!opened) { - const showResult = await this.dialogService.show(Severity.Warning, localize('unableToOpenExternal', "The browser interrupted the opening of a new tab or window. Press 'Open' to open it anyway."), - [localize('open', "Open"), localize('learnMore', "Learn More"), localize('cancel', "Cancel")], { cancelId: 2, detail: href }); + const showResult = await this.dialogService.show( + Severity.Warning, + localize('unableToOpenExternal', "The browser interrupted the opening of a new tab or window. Press 'Open' to open it anyway."), + [ + localize('open', "Open"), + localize('learnMore', "Learn More"), + localize('cancel', "Cancel") + ], + { + cancelId: 2, + detail: href + } + ); + if (showResult.choice === 0) { windowOpenNoOpener(href); } + if (showResult.choice === 1) { await this.openerService.open(URI.parse('https://aka.ms/allow-vscode-popup')); } @@ -165,13 +178,13 @@ export class BrowserWindow extends Disposable { } private registerLabelFormatters() { - this.labelService.registerFormatter({ + this._register(this.labelService.registerFormatter({ scheme: Schemas.userData, priority: true, formatting: { label: '(Settings) ${path}', separator: '/', } - }); + })); } } diff --git a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts index 5964f380e31..ec73360517e 100644 --- a/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts +++ b/src/vs/workbench/contrib/bulkEdit/browser/preview/bulkEditPane.ts @@ -245,7 +245,7 @@ export class BulkEditPane extends ViewPane { message = localize('conflict.N', "Cannot apply refactoring because {0} other files have changed in the meantime.", conflicts.length); } - this._dialogService.show(Severity.Warning, message, []).finally(() => this._done(false)); + this._dialogService.show(Severity.Warning, message).finally(() => this._done(false)); } discard() { diff --git a/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts b/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts index 5f4725e8f56..ce600feb10c 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/saveParticipants.ts @@ -156,12 +156,12 @@ export class TrimFinalNewLinesParticipant implements ITextFileSaveParticipant { } /** - * returns 0 if the entire file is empty or whitespace only + * returns 0 if the entire file is empty */ - private findLastLineWithContent(model: ITextModel): number { + private findLastNonEmptyLine(model: ITextModel): number { for (let lineNumber = model.getLineCount(); lineNumber >= 1; lineNumber--) { const lineContent = model.getLineContent(lineNumber); - if (strings.lastNonWhitespaceIndex(lineContent) !== -1) { + if (lineContent.length > 0) { // this line has content return lineNumber; } @@ -193,8 +193,8 @@ export class TrimFinalNewLinesParticipant implements ITextFileSaveParticipant { } } - const lastLineNumberWithContent = this.findLastLineWithContent(model); - const deleteFromLineNumber = Math.max(lastLineNumberWithContent + 1, cannotTouchLineNumber + 1); + const lastNonEmptyLine = this.findLastNonEmptyLine(model); + const deleteFromLineNumber = Math.max(lastNonEmptyLine + 1, cannotTouchLineNumber + 1); const deletionRange = model.validateRange(new Range(deleteFromLineNumber, 1, lineCount, model.getLineMaxColumn(lineCount))); if (deletionRange.isEmpty()) { diff --git a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts index 634f016ea68..b5a853fa432 100644 --- a/src/vs/workbench/contrib/debug/browser/debug.contribution.ts +++ b/src/vs/workbench/contrib/debug/browser/debug.contribution.ts @@ -50,6 +50,7 @@ import { registerColors } from 'vs/workbench/contrib/debug/browser/debugColors'; import { DebugEditorContribution } from 'vs/workbench/contrib/debug/browser/debugEditorContribution'; import { FileAccess } from 'vs/base/common/network'; import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons'; +import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; const debugCategory = nls.localize('debugCategory', "Debug"); registerColors(); @@ -178,6 +179,17 @@ if (isMacintosh) { // Debug menu +MenuRegistry.appendMenuItem(MenuId.MenubarMainMenu, { + submenu: MenuId.MenubarDebugMenu, + title: { + value: 'Run', + original: 'Run', + mnemonicTitle: nls.localize({ key: 'mRun', comment: ['&& denotes a mnemonic'] }, "&&Run") + }, + when: ContextKeyExpr.or(CONTEXT_DEBUGGERS_AVAILABLE, IsWebContext.toNegated()), + order: 6 +}); + MenuRegistry.appendMenuItem(MenuId.MenubarDebugMenu, { group: '1_debug', command: { diff --git a/src/vs/workbench/contrib/debug/node/debugAdapter.ts b/src/vs/workbench/contrib/debug/node/debugAdapter.ts index d8ba9f71e2c..bfef068fa25 100644 --- a/src/vs/workbench/contrib/debug/node/debugAdapter.ts +++ b/src/vs/workbench/contrib/debug/node/debugAdapter.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { exists } from 'vs/base/node/pfs'; +import { Promises } from 'vs/base/node/pfs'; import * as cp from 'child_process'; import * as stream from 'stream'; import * as nls from 'vs/nls'; @@ -183,7 +183,7 @@ export class ExecutableDebugAdapter extends StreamDebugAdapter { // verify executables asynchronously if (command) { if (path.isAbsolute(command)) { - const commandExists = await exists(command); + const commandExists = await Promises.exists(command); if (!commandExists) { throw new Error(nls.localize('debugAdapterBinNotFound', "Debug adapter executable '{0}' does not exist.", command)); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 4232ee5f9e3..956164ef0a8 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -546,7 +546,7 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi if (outdated.length) { return runAction(this.instantiationService.createInstance(SearchExtensionsAction, '@outdated ')); } else { - return this.dialogService.show(Severity.Info, localize('noUpdatesAvailable', "All extensions are up to date."), []); + return this.dialogService.show(Severity.Info, localize('noUpdatesAvailable', "All extensions are up to date.")); } } }); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index c1dbb44158a..94a81c4d965 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -132,7 +132,7 @@ export class PromptExtensionInstallFailureAction extends Action { } if ([INSTALL_ERROR_INCOMPATIBLE, INSTALL_ERROR_MALICIOUS].includes(this.error.name)) { - await this.dialogService.show(Severity.Info, getErrorMessage(this.error), []); + await this.dialogService.show(Severity.Info, getErrorMessage(this.error)); return; } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 257d5250133..9cba26e4683 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -59,7 +59,6 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { isIOS, isWeb } from 'vs/base/common/platform'; import { installLocalInRemoteIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; import { registerAction2, Action2, MenuId } from 'vs/platform/actions/common/actions'; -import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; import { WorkspaceTrustContext } from 'vs/workbench/services/workspaces/common/workspaceTrust'; const SearchMarketplaceExtensionsContext = new RawContextKey('searchMarketplaceExtensions', false); @@ -472,7 +471,6 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE @IExtensionManagementServerService private readonly extensionManagementServerService: IExtensionManagementServerService, @INotificationService private readonly notificationService: INotificationService, @IViewletService private readonly viewletService: IViewletService, - @IWorkspaceTrustManagementService private readonly workspaceTrustService: IWorkspaceTrustManagementService, @IThemeService themeService: IThemeService, @IConfigurationService configurationService: IConfigurationService, @IStorageService storageService: IStorageService, @@ -530,14 +528,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE const header = append(this.root, $('.header')); const placeholder = localize('searchExtensions', "Search Extensions in Marketplace"); - let searchValue = this.searchViewletState['query.value']; - if (ExtensionsListView.isSearchWorkspaceUnsupportedExtensionsQuery(searchValue)) { - searchValue = undefined; - } - - if (searchValue === undefined || searchValue === '') { - searchValue = this.workspaceTrustService.isWorkpaceTrusted() ? '' : '@workspaceUnsupported'; - } + const searchValue = this.searchViewletState['query.value'] ? this.searchViewletState['query.value'] : ''; this.searchBox = this._register(this.instantiationService.createInstance(SuggestEnabledInput, `${VIEWLET_ID}.searchbox`, header, { triggerCharacters: ['@'], diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsSlowActions.ts b/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsSlowActions.ts index 82b6883f9e5..0c5855b4e3b 100644 --- a/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsSlowActions.ts +++ b/src/vs/workbench/contrib/extensions/electron-sandbox/extensionsSlowActions.ts @@ -152,7 +152,7 @@ class ReportExtensionSlowAction extends Action { this._dialogService.show( Severity.Info, localize('attach.title', "Did you attach the CPU-Profile?"), - [localize('ok', 'OK')], + undefined, { detail: localize('attach.msg', "This is a reminder to make sure that you have not forgotten to attach '{0}' to the issue you have just created.", path) } ); } @@ -186,7 +186,7 @@ class ShowExtensionSlowAction extends Action { this._dialogService.show( Severity.Info, localize('attach.title', "Did you attach the CPU-Profile?"), - [localize('ok', 'OK')], + undefined, { detail: localize('attach.msg2', "This is a reminder to make sure that you have not forgotten to attach '{0}' to an existing performance issue.", path) } ); } diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts index 488441ba5e3..bc013fb733b 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts @@ -21,6 +21,7 @@ import { isEqual } from 'vs/base/common/resources'; import { Event } from 'vs/base/common/event'; import { Schemas } from 'vs/base/common/network'; import { createTextBufferFactory } from 'vs/editor/common/model/textModel'; +import { IPathService } from 'vs/workbench/services/path/common/pathService'; const enum ForceOpenAs { None, @@ -84,7 +85,8 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements @ILabelService labelService: ILabelService, @IFileService fileService: IFileService, @IFilesConfigurationService private readonly filesConfigurationService: IFilesConfigurationService, - @IEditorService editorService: IEditorService + @IEditorService editorService: IEditorService, + @IPathService private readonly pathService: IPathService ) { super(resource, preferredResource, editorService, textFileService, labelService, fileService); @@ -158,7 +160,7 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements setPreferredName(name: string): void { if (!this.allowLabelOverride()) { - return; // block for specific schemes we own + return; // block for specific schemes we consider to be owning } if (this.preferredName !== name) { @@ -169,7 +171,10 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements } private allowLabelOverride(): boolean { - return this.resource.scheme !== Schemas.file && this.resource.scheme !== Schemas.vscodeRemote && this.resource.scheme !== Schemas.userData; + return this.resource.scheme !== this.pathService.defaultUriScheme && + this.resource.scheme !== Schemas.userData && + this.resource.scheme !== Schemas.file && + this.resource.scheme !== Schemas.vscodeRemote; } getPreferredName(): string | undefined { @@ -182,7 +187,7 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements setPreferredDescription(description: string): void { if (!this.allowLabelOverride()) { - return; // block for specific schemes we own + return; // block for specific schemes we consider to be owning } if (this.preferredDescription !== description) { diff --git a/src/vs/workbench/contrib/files/browser/fileActions.ts b/src/vs/workbench/contrib/files/browser/fileActions.ts index 258000894b2..cf5fd76a1bc 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.ts @@ -614,7 +614,7 @@ export class ShowOpenedFileInNewWindow extends Action { if (this.fileService.canHandleResource(fileResource)) { this.hostService.openWindow([{ fileUri: fileResource }], { forceNewWindow: true }); } else { - this.dialogService.show(Severity.Error, nls.localize('openFileToShowInNewWindow.unsupportedschema', "The active editor must contain an openable resource."), [nls.localize('ok', 'OK')]); + this.dialogService.show(Severity.Error, nls.localize('openFileToShowInNewWindow.unsupportedschema', "The active editor must contain an openable resource.")); } } } diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 49095f56fca..b7c0896ab08 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -831,6 +831,10 @@ export class FileDragAndDrop implements ITreeDragAndDrop { if (!containsDragType(originalEvent, DataTransfers.FILES, CodeDataTransfers.FILES, DataTransfers.RESOURCES)) { return false; } + if (isWeb && originalEvent.dataTransfer?.types.indexOf('Files') === -1) { + // DnD from vscode to web is not supported #115535. Only if we are dragging from native finder / explorer then the "Files" data transfer will be set + return false; + } } // Other-Tree DND @@ -984,7 +988,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { await this.handleExplorerDrop(data as ElementsDragAndDropData, resolvedTarget, originalEvent); } } catch (error) { - this.dialogService.show(Severity.Error, toErrorMessage(error), [localize('ok', 'OK')]); + this.dialogService.show(Severity.Error, toErrorMessage(error)); } } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts b/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts index b30fe2a6a25..339665e267e 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/profile/notebookProfile.ts @@ -23,9 +23,9 @@ export enum NotebookProfileType { const profiles = { [NotebookProfileType.default]: { - [FocusIndicator]: 'border', + [FocusIndicator]: 'gutter', [InsertToolbarLocation]: 'both', - [GlobalToolbar]: false, + [GlobalToolbar]: true, [CellToolbarLocation]: { default: 'right' }, [CompactView]: true, [ShowCellStatusBar]: 'visible', diff --git a/src/vs/workbench/contrib/notebook/browser/media/notebook.css b/src/vs/workbench/contrib/notebook/browser/media/notebook.css index 6afc074341c..788360ba69d 100644 --- a/src/vs/workbench/contrib/notebook/browser/media/notebook.css +++ b/src/vs/workbench/contrib/notebook/browser/media/notebook.css @@ -494,7 +494,7 @@ .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .run-button-container { position: absolute; flex-shrink: 0; - z-index: 27; /* Above the drag handle */ + z-index: 29; /* Above the drag handle, output, and toolbars */ } .monaco-workbench .notebookOverlay > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .run-button-container .monaco-toolbar { diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 9e59a81229b..5c000e4c080 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -660,7 +660,7 @@ configurationRegistry.registerConfiguration({ description: nls.localize('notebook.focusIndicator.description', "Control whether to render the focus indicator as cell borders or a highlight bar on the left gutter"), type: 'string', enum: ['border', 'gutter'], - default: 'border', + default: 'gutter', tags: ['notebookLayout'] }, [InsertToolbarLocation]: { @@ -673,7 +673,7 @@ configurationRegistry.registerConfiguration({ [GlobalToolbar]: { description: nls.localize('notebook.globalToolbar.description', "Control whether to render a global toolbar inside the notebook editor."), type: 'boolean', - default: false, + default: true, tags: ['notebookLayout'] }, [ConsolidatedOutputButton]: { diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 75821f80784..e68eac4a35c 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -161,7 +161,7 @@ async function webviewPreloads(style: PreloadStyles, options: PreloadOptions, re const runRenderScript = async (url: string, rendererId: string): Promise => { const text = await loadScriptSource(url); // TODO: Support both the new module based renderers and the old style global renderers - const isModule = /\bexport\b.*\bactivate\b/.test(text); + const isModule = !text.includes('acquireNotebookRendererApi'); if (isModule) { return __import(url); } else { diff --git a/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts b/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts index 17babc5a8f1..0b5f0490b45 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookEditorModelResolverServiceImpl.ts @@ -181,12 +181,15 @@ export class NotebookModelResolverServiceImpl implements INotebookEditorModelRes } const reference = this._data.acquire(resource.toString(), viewType); - const model = await reference.object; - return { - object: model, - dispose() { - reference.dispose(); - } - }; + try { + const model = await reference.object; + return { + object: model, + dispose() { reference.dispose(); } + }; + } catch (err) { + reference.dispose(); + throw err; + } } } diff --git a/src/vs/workbench/contrib/notebook/common/notebookOptions.ts b/src/vs/workbench/contrib/notebook/common/notebookOptions.ts index 8cec9603b18..6fef9035f6b 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookOptions.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookOptions.ts @@ -107,7 +107,7 @@ export class NotebookOptions { constructor(private readonly configurationService: IConfigurationService) { const showCellStatusBar = this.configurationService.getValue(ShowCellStatusBar); - const globalToolbar = this.configurationService.getValue(GlobalToolbar) ?? false; + const globalToolbar = this.configurationService.getValue(GlobalToolbar) ?? true; const consolidatedOutputButton = this.configurationService.getValue(ConsolidatedOutputButton) ?? true; const consolidatedRunButton = this.configurationService.getValue(ConsolidatedRunButton) ?? false; const dragAndDropEnabled = this.configurationService.getValue(DragAndDropEnabled) ?? true; @@ -235,7 +235,7 @@ export class NotebookOptions { } if (globalToolbar) { - configuration.globalToolbar = this.configurationService.getValue(GlobalToolbar) ?? false; + configuration.globalToolbar = this.configurationService.getValue(GlobalToolbar) ?? true; } if (consolidatedOutputButton) { @@ -296,7 +296,7 @@ export class NotebookOptions { } private _computeFocusIndicatorOption() { - return this.configurationService.getValue<'border' | 'gutter'>(FocusIndicator) ?? 'border'; + return this.configurationService.getValue<'border' | 'gutter'>(FocusIndicator) ?? 'gutter'; } getLayoutConfiguration(): NotebookLayoutConfiguration { diff --git a/src/vs/workbench/contrib/outline/browser/outlinePane.ts b/src/vs/workbench/contrib/outline/browser/outlinePane.ts index 230d10664b6..a39d9986014 100644 --- a/src/vs/workbench/contrib/outline/browser/outlinePane.ts +++ b/src/vs/workbench/contrib/outline/browser/outlinePane.ts @@ -299,13 +299,21 @@ export class OutlinePane extends ViewPane { if (!this._outlineViewState.followCursor || !newOutline.activeElement) { return; } - const item = newOutline.activeElement; - const top = tree.getRelativeTop(item); - if (top === null) { - tree.reveal(item, 0.5); + let item = newOutline.activeElement; + while (item) { + const top = tree.getRelativeTop(item); + if (top === null) { + // not visible -> reveal + tree.reveal(item, 0.5); + } + if (tree.getRelativeTop(item) !== null) { + tree.setFocus([item]); + tree.setSelection([item]); + break; + } + // STILL not visible -> try parent + item = tree.getParentElement(item); } - tree.setFocus([item]); - tree.setSelection([item]); }; revealActiveElement(); this._editorDisposables.add(newOutline.onDidChange(revealActiveElement)); diff --git a/src/vs/workbench/contrib/performance/electron-sandbox/startupTimings.ts b/src/vs/workbench/contrib/performance/electron-sandbox/startupTimings.ts index f0dcc668f43..684e9e0d6ec 100644 --- a/src/vs/workbench/contrib/performance/electron-sandbox/startupTimings.ts +++ b/src/vs/workbench/contrib/performance/electron-sandbox/startupTimings.ts @@ -70,10 +70,10 @@ export class StartupTimings implements IWorkbenchContribution { chunks.push(VSBuffer.fromString(`${this._timerService.startupMetrics.ellapsed}\t${this._productService.nameShort}\t${(this._productService.commit || '').slice(0, 10) || '0000000000'}\t${sessionId}\t${standardStartupError === undefined ? 'standard_start' : 'NO_standard_start : ' + standardStartupError}\n`)); await this._fileService.writeFile(uri, VSBuffer.concat(chunks)); }).then(() => { - this._nativeHostService.quit(); + this._nativeHostService.exit(0); }).catch(err => { console.error(err); - this._nativeHostService.quit(); + this._nativeHostService.exit(0); }); } diff --git a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts index 0403cc0f8c6..cd9414e6fa5 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts @@ -12,7 +12,7 @@ import { MenuId, IMenuService, MenuItemAction, MenuRegistry, registerAction2, Ac import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar'; import { ILabelService } from 'vs/platform/label/common/label'; -import { IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService, RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { ICommandService } from 'vs/platform/commands/common/commands'; import { Schemas } from 'vs/base/common/network'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -34,6 +34,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { IExtensionsViewPaneContainer, LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID, VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { RemoteNameContext, VirtualWorkspaceContext } from 'vs/workbench/browser/contextkeys'; type ActionGroup = [string, Array]; @@ -120,7 +121,8 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr id: RemoteStatusIndicator.CLOSE_REMOTE_COMMAND_ID, category, title: { value: nls.localize('remote.close', "Close Remote Connection"), original: 'Close Remote Connection' }, - f1: true + f1: true, + precondition: ContextKeyExpr.or(RemoteNameContext, VirtualWorkspaceContext) }); } run = () => that.hostService.openWindow({ forceReuseWindow: true, remoteAuthority: null }); diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 77dd255b3e1..658576a6733 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -278,9 +278,6 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this._providerTypes = new Map(); this._taskSystemInfos = new Map(); this._register(this.contextService.onDidChangeWorkspaceFolders(() => { - if (!this._taskSystem && !this._workspaceTasksPromise) { - return; - } let folderSetup = this.computeWorkspaceFolderSetup(); if (this.executionEngine !== folderSetup[2]) { this.disposeTaskSystemListeners(); diff --git a/src/vs/workbench/contrib/terminal/browser/remoteTerminalService.ts b/src/vs/workbench/contrib/terminal/browser/remoteTerminalService.ts index 619b98e9212..e59d446dea0 100644 --- a/src/vs/workbench/contrib/terminal/browser/remoteTerminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/remoteTerminalService.ts @@ -226,8 +226,8 @@ export class RemoteTerminalService extends Disposable implements IRemoteTerminal return this._remoteTerminalChannel?.getDefaultSystemShell(osOverride) || ''; } - async getProfiles(includeDetectedProfiles?: boolean): Promise { - return this._remoteTerminalChannel?.getProfiles(includeDetectedProfiles) || []; + async getProfiles(profiles: unknown, defaultProfile: unknown, includeDetectedProfiles?: boolean): Promise { + return this._remoteTerminalChannel?.getProfiles(profiles, defaultProfile, includeDetectedProfiles) || []; } async getEnvironment(): Promise { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 2377fc9bbd0..33ea45275c1 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -68,6 +68,8 @@ const NUMBER_OF_FRAMES_TO_MEASURE = 20; const SHOULD_PROMPT_FOR_PROFILE_MIGRATION_KEY = 'terminals.integrated.profile-migration'; +let migrationMessageShown = false; + const enum Constants { /** * The maximum amount of milliseconds to wait for a container before starting to create the @@ -143,8 +145,6 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { private _hasHadInput: boolean; - messageShown: boolean = false; - readonly statusList: ITerminalStatusList = new TerminalStatusList(); disableLayout: boolean = false; get instanceId(): number { return this._instanceId; } @@ -362,7 +362,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { const shouldMigrateToProfile = (!!this._configurationService.getValue(TerminalSettingPrefix.Shell + platform) || !!this._configurationService.inspect(TerminalSettingPrefix.ShellArgs + platform).userValue) && !!this._configurationService.getValue(TerminalSettingPrefix.DefaultProfile + platform); - if (shouldMigrateToProfile && this._storageService.getBoolean(SHOULD_PROMPT_FOR_PROFILE_MIGRATION_KEY, StorageScope.WORKSPACE, true) && !this.messageShown) { + if (shouldMigrateToProfile && this._storageService.getBoolean(SHOULD_PROMPT_FOR_PROFILE_MIGRATION_KEY, StorageScope.WORKSPACE, true) && !migrationMessageShown) { this._notificationService.prompt( Severity.Info, nls.localize('terminalProfileMigration', "The terminal is using deprecated shell/shellArgs settings, do you want to migrate it to a profile?"), @@ -396,7 +396,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { neverShowAgain: { id: SHOULD_PROMPT_FOR_PROFILE_MIGRATION_KEY, scope: NeverShowAgainScope.WORKSPACE } } ); - this.messageShown = true; + migrationMessageShown = true; } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index 09cd50c3b53..fc6fe60a52a 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -380,7 +380,8 @@ export class TerminalService implements ITerminalService { if (!offProcService) { return this._availableProfiles || []; } - return offProcService?.getProfiles(includeDetectedProfiles); + const platform = await this._getPlatformKey(); + return offProcService?.getProfiles(this._configurationService.getValue(`${TerminalSettingPrefix.Profiles}${platform}`), this._configurationService.getValue(`${TerminalSettingPrefix.DefaultProfile}${platform}`), includeDetectedProfiles); } private _onBeforeShutdown(reason: ShutdownReason): boolean | Promise { diff --git a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts index 633d8c00770..bf0c17a4363 100644 --- a/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts +++ b/src/vs/workbench/contrib/terminal/common/remoteTerminalChannel.ts @@ -238,8 +238,8 @@ export class RemoteTerminalChannelClient { getDefaultSystemShell(osOverride?: OperatingSystem): Promise { return this._channel.call('$getDefaultSystemShell', [osOverride]); } - getProfiles(includeDetectedProfiles?: boolean): Promise { - return this._channel.call('$getProfiles', [includeDetectedProfiles]); + getProfiles(profiles: unknown, defaultProfile: unknown, includeDetectedProfiles?: boolean): Promise { + return this._channel.call('$getProfiles', [profiles, defaultProfile, includeDetectedProfiles]); } acceptPtyHostResolvedVariables(id: number, resolved: string[]) { return this._channel.call('$acceptPtyHostResolvedVariables', [id, resolved]); diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalService.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalService.ts index 18feaabdacd..bb1c9325d3d 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalService.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/localTerminalService.ts @@ -155,8 +155,8 @@ export class LocalTerminalService extends Disposable implements ILocalTerminalSe return this._localPtyService.getDefaultSystemShell(osOverride); } - async getProfiles(includeDetectedProfiles?: boolean) { - return this._localPtyService.getProfiles?.(includeDetectedProfiles) || []; + async getProfiles(profiles: unknown, defaultProfile: unknown, includeDetectedProfiles?: boolean) { + return this._localPtyService.getProfiles?.(profiles, defaultProfile, includeDetectedProfiles) || []; } async getEnvironment(): Promise { diff --git a/src/vs/workbench/contrib/terminal/test/node/terminalProfiles.test.ts b/src/vs/workbench/contrib/terminal/test/node/terminalProfiles.test.ts index c9cffffc429..e1e2ecec867 100644 --- a/src/vs/workbench/contrib/terminal/test/node/terminalProfiles.test.ts +++ b/src/vs/workbench/contrib/terminal/test/node/terminalProfiles.test.ts @@ -45,7 +45,7 @@ suite('Workbench - TerminalProfiles', () => { useWslProfiles: false }; const configurationService = new TestConfigurationService({ terminal: { integrated: config } }); - const profiles = await detectAvailableProfiles(false, configurationService, fsProvider, undefined, undefined, undefined); + const profiles = await detectAvailableProfiles(undefined, undefined, false, configurationService, fsProvider, undefined, undefined, undefined); const expected = [ { profileName: 'Git Bash', path: 'C:\\Program Files\\Git\\bin\\bash.exe', args: ['--login'], isDefault: true } ]; @@ -66,7 +66,7 @@ suite('Workbench - TerminalProfiles', () => { useWslProfiles: false }; const configurationService = new TestConfigurationService({ terminal: { integrated: config } }); - const profiles = await detectAvailableProfiles(false, configurationService, fsProvider, undefined, undefined, undefined); + const profiles = await detectAvailableProfiles(undefined, undefined, false, configurationService, fsProvider, undefined, undefined, undefined); const expected = [ { profileName: 'PowerShell', path: 'C:\\Program Files\\PowerShell\\7\\pwsh.exe', overrideName: true, args: ['-NoProfile'], isDefault: true } ]; @@ -87,7 +87,7 @@ suite('Workbench - TerminalProfiles', () => { useWslProfiles: false }; const configurationService = new TestConfigurationService({ terminal: { integrated: config } }); - const profiles = await detectAvailableProfiles(false, configurationService, fsProvider, undefined, undefined, undefined); + const profiles = await detectAvailableProfiles(undefined, undefined, false, configurationService, fsProvider, undefined, undefined, undefined); const expected = [{ profileName: 'Git Bash', path: 'C:\\Program Files\\Git\\bin\\bash.exe', args: [], isAutoDetected: undefined, overrideName: undefined, isDefault: true }]; profilesEqual(profiles, expected); }); @@ -110,7 +110,7 @@ suite('Workbench - TerminalProfiles', () => { 'C:\\System32\\WindowsPowerShell\\v1.0\\powershell.exe' ]; const configurationService = new TestConfigurationService({ terminal: { integrated: pwshSourceConfig } }); - const profiles = await detectAvailableProfiles(false, configurationService, undefined, undefined, undefined, expectedPaths); + const profiles = await detectAvailableProfiles(undefined, undefined, false, configurationService, undefined, undefined, undefined, expectedPaths); const expected = [ { profileName: 'PowerShell', path: 'C:\\Program Files\\PowerShell\\7\\pwsh.exe', isDefault: true } ]; @@ -124,7 +124,7 @@ suite('Workbench - TerminalProfiles', () => { 'C:\\System32\\WindowsPowerShell\\v1.0\\powershell.exe' ]; const configurationService = new TestConfigurationService({ terminal: { integrated: pwshSourceConfig } }); - const profiles = await detectAvailableProfiles(false, configurationService, undefined, undefined, undefined, expectedPaths); + const profiles = await detectAvailableProfiles(undefined, undefined, false, configurationService, undefined, undefined, undefined, expectedPaths); const expected = [ { profileName: 'PowerShell', path: 'C:\\Program Files\\PowerShell\\7\\pwsh.exe', isDefault: true } ]; @@ -136,7 +136,7 @@ suite('Workbench - TerminalProfiles', () => { 'C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\powershell.exe' ]; const configurationService = new TestConfigurationService({ terminal: { integrated: pwshSourceConfig } }); - const profiles = await detectAvailableProfiles(false, configurationService, undefined, undefined, undefined, expectedPaths); + const profiles = await detectAvailableProfiles(undefined, undefined, false, configurationService, undefined, undefined, undefined, expectedPaths); strictEqual(profiles.length, 1); strictEqual(profiles[0].profileName, 'PowerShell'); }); @@ -181,7 +181,7 @@ suite('Workbench - TerminalProfiles', () => { '/bin/fakeshell3' ]); const configurationService = new TestConfigurationService({ terminal: { integrated: absoluteConfig } }); - const profiles = await detectAvailableProfiles(false, configurationService, fsProvider, undefined, undefined, undefined); + const profiles = await detectAvailableProfiles(undefined, undefined, false, configurationService, fsProvider, undefined, undefined, undefined); const expected: ITerminalProfile[] = [ { profileName: 'fakeshell1', path: '/bin/fakeshell1', isDefault: true }, { profileName: 'fakeshell3', path: '/bin/fakeshell3', isDefault: true } @@ -194,7 +194,7 @@ suite('Workbench - TerminalProfiles', () => { '/bin/fakeshell3' ], '/bin/fakeshell1\n/bin/fakeshell3'); const configurationService = new TestConfigurationService({ terminal: { integrated: onPathConfig } }); - const profiles = await detectAvailableProfiles(true, configurationService, fsProvider, undefined, undefined, undefined); + const profiles = await detectAvailableProfiles(undefined, undefined, true, configurationService, fsProvider, undefined, undefined, undefined); const expected: ITerminalProfile[] = [ { profileName: 'fakeshell1', path: 'fakeshell1', isDefault: true }, { profileName: 'fakeshell3', path: 'fakeshell3', isDefault: true } @@ -207,7 +207,7 @@ suite('Workbench - TerminalProfiles', () => { '/bin/fakeshell1' ], '/bin/fakeshell1\n/bin/fakeshell3'); const configurationService = new TestConfigurationService({ terminal: { integrated: onPathConfig } }); - const profiles = await detectAvailableProfiles(true, configurationService, fsProvider, undefined, undefined, undefined); + const profiles = await detectAvailableProfiles(undefined, undefined, true, configurationService, fsProvider, undefined, undefined, undefined); const expected: ITerminalProfile[] = [ { profileName: 'fakeshell1', path: 'fakeshell1', isDefault: true } ]; diff --git a/src/vs/workbench/contrib/update/browser/update.ts b/src/vs/workbench/contrib/update/browser/update.ts index f648957e6b6..2138c3827ba 100644 --- a/src/vs/workbench/contrib/update/browser/update.ts +++ b/src/vs/workbench/contrib/update/browser/update.ts @@ -306,8 +306,7 @@ export class UpdateContribution extends Disposable implements IWorkbenchContribu private onUpdateNotAvailable(): void { this.dialogService.show( severity.Info, - nls.localize('noUpdatesAvailable', "There are currently no updates available."), - [nls.localize('ok', "OK")] + nls.localize('noUpdatesAvailable', "There are currently no updates available.") ); } diff --git a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncMergesView.ts b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncMergesView.ts index b86deb022dd..e7f00b997c6 100644 --- a/src/vs/workbench/contrib/userDataSync/browser/userDataSyncMergesView.ts +++ b/src/vs/workbench/contrib/userDataSync/browser/userDataSyncMergesView.ts @@ -267,7 +267,7 @@ export class UserDataSyncMergesViewPane extends TreeViewPane { previewResource = this.userDataSyncPreview.resources.find(({ local }) => isEqual(local, previewResource.local))!; await this.reopen(previewResource); if (previewResource.mergeState === MergeState.Conflict) { - await this.dialogService.show(Severity.Warning, localize('conflicts detected', "Conflicts Detected"), [], { + await this.dialogService.show(Severity.Warning, localize('conflicts detected', "Conflicts Detected"), undefined, { detail: localize('resolve', "Unable to merge due to conflicts. Please resolve them to continue.") }); } diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts index e0c32eeb39d..b38ca2cfec4 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStarted.ts @@ -519,7 +519,7 @@ export class GettingStartedPage extends EditorPane { this.stepDisposables.add(toDisposable(() => { isDisposed = true; })); this.stepDisposables.add(webview.onDidClickLink(link => { - if (matchesScheme(link, Schemas.https) || matchesScheme(link, Schemas.https) || (matchesScheme(link, Schemas.command))) { + if (matchesScheme(link, Schemas.https) || matchesScheme(link, Schemas.http) || (matchesScheme(link, Schemas.command))) { this.openerService.open(link, { allowCommands: true }); } })); @@ -901,7 +901,7 @@ export class GettingStartedPage extends EditorPane { $('button.button-link', { 'x-dispatch': 'selectCategory:' + entry.id, - title: entry.description + this.getKeybindingLabel(entry.content.command), + title: entry.description + ' ' + this.getKeybindingLabel(entry.content.command), }, this.iconWidgetFor(entry), $('span', {}, entry.title))); @@ -1078,7 +1078,7 @@ export class GettingStartedPage extends EditorPane { } this.openerService.open(command, { allowCommands: true }); - if (!isCommand && node.href.startsWith('https://')) { + if (!isCommand && (node.href.startsWith('https://') || node.href.startsWith('http://'))) { this.gettingStartedService.progressByEvent('onLink:' + node.href); } diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedService.ts b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedService.ts index 38064396f13..8153b4273bd 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedService.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/gettingStartedService.ts @@ -538,7 +538,7 @@ export class GettingStartedService extends Disposable implements IGettingStarted if (href.startsWith('command:')) { return 'onCommand:' + href.slice('command:'.length, href.includes('?') ? href.indexOf('?') : undefined); } - if (href.startsWith('https://')) { + if (href.startsWith('https://') || href.startsWith('http://')) { return 'onLink:' + href; } return undefined; diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/common/gettingStartedContent.ts b/src/vs/workbench/contrib/welcome/gettingStarted/common/gettingStartedContent.ts index 0c06247fe28..5188d25939c 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/common/gettingStartedContent.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/common/gettingStartedContent.ts @@ -402,7 +402,7 @@ export const walkthroughs: GettingStartedWalkthroughContent = [ title: localize('gettingStarted.notebook.title', "Customize Notebooks"), description: '', icon: setupIcon, - when: 'userHasOpenedNotebook', + when: 'config.notebook.experimental.gettingStarted && userHasOpenedNotebook', content: { type: 'steps', steps: [ diff --git a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts index e2b4e7033de..05d371d2334 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspace.contribution.ts @@ -16,7 +16,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkspaceTrustManagementService, IWorkspaceTrustRequestService, workspaceTrustToString } from 'vs/platform/workspace/common/workspaceTrust'; import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; -import { Codicon, registerCodicon } from 'vs/base/common/codicons'; +import { Codicon } from 'vs/base/common/codicons'; import { ThemeColor } from 'vs/workbench/api/common/extHostTypes'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -35,7 +35,7 @@ import { IsWebContext } from 'vs/platform/contextkey/common/contextkeys'; import { dirname, resolve } from 'vs/base/common/path'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import product from 'vs/platform/product/common/product'; -import { MarkdownString } from 'vs/base/common/htmlContent'; +import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; import { isSingleFolderWorkspaceIdentifier, toWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { Schemas } from 'vs/base/common/network'; import { STATUS_BAR_PROMINENT_ITEM_BACKGROUND, STATUS_BAR_PROMINENT_ITEM_FOREGROUND } from 'vs/workbench/common/theme'; @@ -45,15 +45,11 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IBannerItem, IBannerService } from 'vs/workbench/services/banner/browser/bannerService'; import { isVirtualWorkspace } from 'vs/platform/remote/common/remoteHosts'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID } from 'vs/workbench/contrib/extensions/common/extensions'; const BANNER_RESTRICTED_MODE = 'workbench.banner.restrictedMode'; -const BANNER_VIRTUAL_WORKSPACE = 'workbench.banner.virtualWorkspace'; -const BANNER_VIRTUAL_AND_RESTRICTED = 'workbench.banner.virtualAndRestricted'; const STARTUP_PROMPT_SHOWN_KEY = 'workspace.trust.startupPrompt.shown'; const BANNER_RESTRICTED_MODE_DISMISSED_KEY = 'workbench.banner.restrictedMode.dismissed'; -const BANNER_VIRTUAL_WORKSPACE_DISMISSED_KEY = 'workbench.banner.virtualWorkspace.dismissed'; - -const infoIcon = registerCodicon('workspace-banner-warning-icon', Codicon.info); /* * Trust Request via Service UX handler @@ -125,7 +121,7 @@ export class WorkspaceTrustRequestHandler extends Disposable implements IWorkben break; case 'Manage': this.workspaceTrustRequestService.cancelRequest(); - await this.commandService.executeCommand('workbench.trust.manage'); + await this.commandService.executeCommand(MANAGE_TRUST_COMMAND_ID); break; case 'Cancel': this.workspaceTrustRequestService.cancelRequest(); @@ -309,80 +305,34 @@ export class WorkspaceTrustUXHandler extends Disposable implements IWorkbenchCon this.statusbarService.updateEntryVisibility(this.entryId, false); } - private getBannerItem(isInVirtualWorkspace: boolean, restrictedMode: boolean): IBannerItem | undefined { + private getBannerItem(restrictedMode: boolean): IBannerItem | undefined { - const dismissedVirtual = this.storageService.getBoolean(BANNER_VIRTUAL_WORKSPACE_DISMISSED_KEY, StorageScope.WORKSPACE, false); const dismissedRestricted = this.storageService.getBoolean(BANNER_RESTRICTED_MODE_DISMISSED_KEY, StorageScope.WORKSPACE, false); - // all important info has been dismissed - if (dismissedVirtual && dismissedRestricted) { + // info has been dismissed + if (dismissedRestricted) { return undefined; } - // don't show restricted mode only banner - if (dismissedRestricted && !isInVirtualWorkspace) { - return undefined; - } - - // don't show virtual workspace only banner - if (dismissedVirtual && !restrictedMode) { - return undefined; - } - - const choose = (virtual: any, restricted: any, virtualAndRestricted: any) => { - return (isInVirtualWorkspace && !dismissedVirtual) && (restrictedMode && !dismissedRestricted) ? virtualAndRestricted : ((isInVirtualWorkspace && !dismissedVirtual) ? virtual : restricted); - }; - - const id = choose(BANNER_VIRTUAL_WORKSPACE, BANNER_RESTRICTED_MODE, BANNER_VIRTUAL_AND_RESTRICTED); - const icon = choose(infoIcon, shieldIcon, infoIcon); - - - const [virtualAriaLabel, restrictedModeAriaLabel, virtualAndRestrictedModeAriaLabel] = this.getBannerItemAriaLabels(); - const ariaLabel = choose(virtualAriaLabel, restrictedModeAriaLabel, virtualAndRestrictedModeAriaLabel); - - const [virtualMessage, restrictedModeMessage, virtualAndRestrictedModeMessage] = this.getBannerItemMessages(); - const message = choose(virtualMessage, restrictedModeMessage, virtualAndRestrictedModeMessage); - - const actions = choose( - [ - { - label: localize('virtualBannerLearnMore', "Learn More"), - href: 'https://aka.ms/vscode-virtual-workspaces' - } - ], + const actions = [ { label: localize('restrictedModeBannerManage', "Manage"), - href: 'command:workbench.trust.manage' + href: 'command:' + MANAGE_TRUST_COMMAND_ID }, { label: localize('restrictedModeBannerLearnMore', "Learn More"), href: 'https://aka.ms/vscode-workspace-trust' } - ], - [ - { - label: localize('virtualAndRestrictedModeBannerManage', "Manage Trust"), - href: 'command:workbench.trust.manage' - }, - { - label: localize('virtualBannerLearnMore', "Learn More"), - href: 'https://aka.ms/vscode-virtual-workspaces' - } - ] - ); + ]; return { - id, - icon, - ariaLabel, - message, + id: BANNER_RESTRICTED_MODE, + icon: shieldIcon, + ariaLabel: this.getBannerItemAriaLabels(), + message: this.getBannerItemMessages(), actions, onClose: () => { - if (isInVirtualWorkspace) { - this.storageService.store(BANNER_VIRTUAL_WORKSPACE_DISMISSED_KEY, true, StorageScope.WORKSPACE, StorageTarget.MACHINE); - } - if (restrictedMode) { this.storageService.store(BANNER_RESTRICTED_MODE_DISMISSED_KEY, true, StorageScope.WORKSPACE, StorageTarget.MACHINE); } @@ -390,49 +340,25 @@ export class WorkspaceTrustUXHandler extends Disposable implements IWorkbenchCon }; } - private getBannerItemAriaLabels(): [string, string, string] { + private getBannerItemAriaLabels(): string { switch (this.workspaceContextService.getWorkbenchState()) { case WorkbenchState.EMPTY: - return [ - localize('virtualBannerAriaLabelWindow', "Some features are not available because the current window is backed by a virtual file system. Use navigation keys to access banner actions."), - localize('restrictedModeBannerAriaLabelWindow', "Restricted Mode is intended for safe code browsing. Trust this window to enable all features. Use navigation keys to access banner actions."), - localize('virtualAndRestrictedModeBannerAriaLabelWindow', "Some features are not available because the current window is backed by a virtual file system and is not trusted. You can trust this window to enable some of these features. Use navigation keys to access banner actions.") - ]; + return localize('restrictedModeBannerAriaLabelWindow', "Restricted Mode is intended for safe code browsing. Trust this window to enable all features. Use navigation keys to access banner actions."); case WorkbenchState.FOLDER: - return [ - localize('virtualBannerAriaLabelFolder', "Some features are not available because the current folder is backed by a virtual file system. Use navigation keys to access banner actions."), - localize('restrictedModeBannerAriaLabelFolder', "Restricted Mode is intended for safe code browsing. Trust this folder to enable all features. Use navigation keys to access banner actions."), - localize('virtualAndRestrictedModeBannerAriaLabelFolder', "Some features are not available because the current folder is backed by a virtual file system and is not trusted. You can trust this folder to enable some of these features. Use navigation keys to access banner actions.") - ]; + return localize('restrictedModeBannerAriaLabelFolder', "Restricted Mode is intended for safe code browsing. Trust this folder to enable all features. Use navigation keys to access banner actions."); case WorkbenchState.WORKSPACE: - return [ - localize('virtualBannerAriaLabelWorkspace', "Some features are not available because the current workspace is backed by a virtual file system. Use navigation keys to access banner actions."), - localize('restrictedModeBannerAriaLabelWorkspace', "Restricted Mode is intended for safe code browsing. Trust this workspace to enable all features. Use navigation keys to access banner actions."), - localize('virtualAndRestrictedModeBannerAriaLabelWorkspace', "Some features are not available because the current workspace is backed by a virtual file system and is not trusted. You can trust this workspace to enable some of these features. Use navigation keys to access banner actions.") - ]; + return localize('restrictedModeBannerAriaLabelWorkspace', "Restricted Mode is intended for safe code browsing. Trust this workspace to enable all features. Use navigation keys to access banner actions."); } } - private getBannerItemMessages(): [string, string, string] { + private getBannerItemMessages(): string { switch (this.workspaceContextService.getWorkbenchState()) { case WorkbenchState.EMPTY: - return [ - localize('virtualBannerMessageWindow', "Some features are not available because the current workspace is backed by a virtual file system."), - localize('restrictedModeBannerMessageWindow', "Restricted Mode is intended for safe code browsing. Trust this window to enable all features."), - localize('virtualAndRestrictedModeBannerMessageWindow', "Some features are not available because the current window is backed by a virtual file system and is not trusted. You can trust this window to enable some of these features.") - ]; + return localize('restrictedModeBannerMessageWindow', "Restricted Mode is intended for safe code browsing. Trust this window to enable all features."); case WorkbenchState.FOLDER: - return [ - localize('virtualBannerMessageFolder', "Some features are not available because the current folder is backed by a virtual file system."), - localize('restrictedModeBannerMessageFolder', "Restricted Mode is intended for safe code browsing. Trust this folder to enable all features."), - localize('virtualAndRestrictedModeBannerMessageFolder', "Some features are not available because the current folder is backed by a virtual file system and is not trusted. You can trust this folder to enable some of these features.") - ]; + return localize('restrictedModeBannerMessageFolder', "Restricted Mode is intended for safe code browsing. Trust this folder to enable all features."); case WorkbenchState.WORKSPACE: - return [ - localize('virtualBannerMessageWorkspace', "Some features are not available because the current workspace is backed by a virtual file system."), - localize('restrictedModeBannerMessageWorkspace', "Restricted Mode is intended for safe code browsing. Trust this workspace to enable all features."), - localize('virtualAndRestrictedModeBannerMessageWorkspace', "Some features are not available because the current workspace is backed by a virtual file system and is not trusted. You can trust this workspace to enable some of these features.") - ]; + return localize('restrictedModeBannerMessageWorkspace', "Restricted Mode is intended for safe code browsing. Trust this workspace to enable all features."); } } @@ -442,20 +368,36 @@ export class WorkspaceTrustUXHandler extends Disposable implements IWorkbenchCon const color = new ThemeColor(STATUS_BAR_PROMINENT_ITEM_FOREGROUND); let ariaLabel = ''; + let toolTip: IMarkdownString | string | undefined; switch (this.workspaceContextService.getWorkbenchState()) { case WorkbenchState.EMPTY: { ariaLabel = trusted ? localize('status.ariaTrustedWindow', "This window is trusted.") : localize('status.ariaUntrustedWindow', "Restricted Mode: Some features are disabled because this window is not trusted."); + toolTip = trusted ? ariaLabel : { + value: localize('status.tooltipUntrustedWindow', "Running in Restricted Mode\n\n\Some [features are disabled](command:{0}) because this [window is not trusted](command:{1}).", LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID, MANAGE_TRUST_COMMAND_ID), + isTrusted: true, + supportThemeIcons: true + }; break; } case WorkbenchState.FOLDER: { ariaLabel = trusted ? localize('status.ariaTrustedFolder', "This folder is trusted.") : localize('status.ariaUntrustedFolder', "Restricted Mode: Some features are disabled because this folder is not trusted."); + toolTip = trusted ? ariaLabel : { + value: localize('status.tooltipUntrustedFolder', "Running in Restricted Mode\n\n\Some [features are disabled](command:{0}) because this [folder is not trusted](command:{1}).", LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID, MANAGE_TRUST_COMMAND_ID), + isTrusted: true, + supportThemeIcons: true + }; break; } case WorkbenchState.WORKSPACE: { ariaLabel = trusted ? localize('status.ariaTrustedWorkspace', "This workspace is trusted.") : localize('status.ariaUntrustedWorkspace', "Restricted Mode: Some features are disabled because this workspace is not trusted."); + toolTip = trusted ? ariaLabel : { + value: localize('status.tooltipUntrustedWorkspace', "Running in Restricted Mode\n\n\Some [features are disabled](command:{0}) because this [workspace is not trusted](command:{1}).", LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID, MANAGE_TRUST_COMMAND_ID), + isTrusted: true, + supportThemeIcons: true + }; break; } } @@ -464,8 +406,8 @@ export class WorkspaceTrustUXHandler extends Disposable implements IWorkbenchCon name: localize('status.WorkspaceTrust', "Workspace Trust"), text: trusted ? `$(shield)` : `$(shield) ${text}`, ariaLabel: ariaLabel, - tooltip: ariaLabel, - command: 'workbench.trust.manage', + tooltip: toolTip, + command: MANAGE_TRUST_COMMAND_ID, backgroundColor, color }; @@ -498,7 +440,12 @@ export class WorkspaceTrustUXHandler extends Disposable implements IWorkbenchCon disposable.dispose(); } })); - this.workspaceTrustManagementService.setWorkspaceTrust(this.configurationService.getValue(WORKSPACE_TRUST_EMPTY_WINDOW) ?? false); + // TODO: Consider moving the check into setWorkspaceTrust() + // TODO: Consider moving this into calculateWorkspaceTrust() + if (this.workspaceTrustManagementService.canSetWorkspaceTrust() && + this.configurationService.getValue(WORKSPACE_TRUST_EMPTY_WINDOW)) { + this.workspaceTrustManagementService.setWorkspaceTrust(true); + } } } @@ -512,22 +459,17 @@ export class WorkspaceTrustUXHandler extends Disposable implements IWorkbenchCon } private updateWorkbenchIndicators(trusted: boolean): void { - const isInVirtualWorkspace = isVirtualWorkspace(this.workspaceContextService.getWorkspace()); const isEmptyWorkspace = this.workspaceContextService.getWorkbenchState() === WorkbenchState.EMPTY; - const bannerItem = this.getBannerItem(isInVirtualWorkspace, !trusted); + const bannerItem = this.getBannerItem(!trusted); if (!isEmptyWorkspace || this.showIndicatorsInEmptyWindow) { this.updateStatusbarEntry(trusted); if (bannerItem) { - if (!isInVirtualWorkspace) { - if (!trusted) { - this.bannerService.show(bannerItem); - } else { - this.bannerService.hide(BANNER_RESTRICTED_MODE); - } - } else { + if (!trusted) { this.bannerService.show(bannerItem); + } else { + this.bannerService.hide(BANNER_RESTRICTED_MODE); } } } @@ -617,11 +559,13 @@ Registry.as(EditorExtensions.Editors).registerEditor( * Actions */ +const MANAGE_TRUST_COMMAND_ID = 'workbench.trust.manage'; + // Manage Workspace Trust registerAction2(class extends Action2 { constructor() { super({ - id: 'workbench.trust.manage', + id: MANAGE_TRUST_COMMAND_ID, title: { original: 'Manage Workspace Trust', value: localize('manageWorkspaceTrust', "Manage Workspace Trust") diff --git a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts index 1a4aef28d7f..230f468978c 100644 --- a/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts +++ b/src/vs/workbench/contrib/workspace/browser/workspaceTrustEditor.ts @@ -616,7 +616,7 @@ export class WorkspaceTrustEditor extends EditorPane { private getHeaderTitleText(trusted: boolean): string { if (trusted) { - if (!this.workspaceTrustManagementService.canSetWorkspaceTrust()) { + if (this.workspaceTrustManagementService.isWorkspaceTrustForced()) { return localize('trustedUnsettableWindow', "This window is trusted"); } @@ -921,10 +921,10 @@ export class WorkspaceTrustEditor extends EditorPane { } const textElement = append(parent, $('.workspace-trust-untrusted-description')); - if (this.workspaceTrustManagementService.canSetWorkspaceTrust()) { + if (!this.workspaceTrustManagementService.isWorkspaceTrustForced()) { textElement.innerText = this.workspaceService.getWorkbenchState() === WorkbenchState.WORKSPACE ? localize('untrustedWorkspaceReason', "This workspace is trusted via the bolded entries in the trusted folders below.") : localize('untrustedFolderReason', "This folder is trusted via the bolded entries in the the trusted folders below."); } else { - textElement.innerText = localize('trustedRemoteReason', "This window is trusted as specified by the remote connection."); + textElement.innerText = localize('trustedForcedReason', "This window is trusted by nature of the workspace that is opened."); } } diff --git a/src/vs/workbench/contrib/workspaces/browser/workspaces.contribution.ts b/src/vs/workbench/contrib/workspaces/browser/workspaces.contribution.ts index eee904f92fa..37f7ec40f37 100644 --- a/src/vs/workbench/contrib/workspaces/browser/workspaces.contribution.ts +++ b/src/vs/workbench/contrib/workspaces/browser/workspaces.contribution.ts @@ -16,6 +16,7 @@ import { joinPath } from 'vs/base/common/resources'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { hasWorkspaceFileExtension } from 'vs/platform/workspaces/common/workspaces'; +import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage'; /** * A workbench contribution that will look for `.code-workspace` files in the root of the @@ -28,7 +29,8 @@ export class WorkspacesFinderContribution extends Disposable implements IWorkben @INotificationService private readonly notificationService: INotificationService, @IFileService private readonly fileService: IFileService, @IQuickInputService private readonly quickInputService: IQuickInputService, - @IHostService private readonly hostService: IHostService + @IHostService private readonly hostService: IHostService, + @IStorageService private readonly storageService: IStorageService ) { super(); @@ -60,7 +62,10 @@ export class WorkspacesFinderContribution extends Disposable implements IWorkben this.notificationService.prompt(Severity.Info, localize('workspaceFound', "This folder contains a workspace file '{0}'. Do you want to open it? [Learn more]({1}) about workspace files.", workspaceFile, 'https://go.microsoft.com/fwlink/?linkid=2025315'), [{ label: localize('openWorkspace', "Open Workspace"), run: () => this.hostService.openWindow([{ workspaceUri: joinPath(folder, workspaceFile) }]) - }], { neverShowAgain }); + }], { + neverShowAgain, + silent: !this.storageService.isNew(StorageScope.WORKSPACE) // https://github.com/microsoft/vscode/issues/125315 + }); } // Prompt to select a workspace from many @@ -76,7 +81,10 @@ export class WorkspacesFinderContribution extends Disposable implements IWorkben } }); } - }], { neverShowAgain }); + }], { + neverShowAgain, + silent: !this.storageService.isNew(StorageScope.WORKSPACE) // https://github.com/microsoft/vscode/issues/125315 + }); } } } diff --git a/src/vs/workbench/electron-sandbox/actions/installActions.ts b/src/vs/workbench/electron-sandbox/actions/installActions.ts index 37b8f4130e0..4bcee746dbe 100644 --- a/src/vs/workbench/electron-sandbox/actions/installActions.ts +++ b/src/vs/workbench/electron-sandbox/actions/installActions.ts @@ -37,9 +37,9 @@ export class InstallShellScriptAction extends Action2 { try { await nativeHostService.installShellCommand(); - dialogService.show(Severity.Info, localize('successIn', "Shell command '{0}' successfully installed in PATH.", productService.applicationName), []); + dialogService.show(Severity.Info, localize('successIn', "Shell command '{0}' successfully installed in PATH.", productService.applicationName)); } catch (error) { - dialogService.show(Severity.Error, toErrorMessage(error), [localize('ok', "OK"),]); + dialogService.show(Severity.Error, toErrorMessage(error)); } } } @@ -66,9 +66,9 @@ export class UninstallShellScriptAction extends Action2 { try { await nativeHostService.uninstallShellCommand(); - dialogService.show(Severity.Info, localize('successFrom', "Shell command '{0}' successfully uninstalled from PATH.", productService.applicationName), []); + dialogService.show(Severity.Info, localize('successFrom', "Shell command '{0}' successfully uninstalled from PATH.", productService.applicationName)); } catch (error) { - dialogService.show(Severity.Error, toErrorMessage(error), [localize('ok', "OK"),]); + dialogService.show(Severity.Error, toErrorMessage(error)); } } } diff --git a/src/vs/workbench/electron-sandbox/parts/dialogs/dialogHandler.ts b/src/vs/workbench/electron-sandbox/parts/dialogs/dialogHandler.ts index 3805a175897..40094f38189 100644 --- a/src/vs/workbench/electron-sandbox/parts/dialogs/dialogHandler.ts +++ b/src/vs/workbench/electron-sandbox/parts/dialogs/dialogHandler.ts @@ -90,7 +90,7 @@ export class NativeDialogHandler implements IDialogHandler { return opts; } - async show(severity: Severity, message: string, buttons: string[], dialogOptions?: IDialogOptions): Promise { + async show(severity: Severity, message: string, buttons?: string[], dialogOptions?: IDialogOptions): Promise { this.logService.trace('DialogService#show', message); const { options, buttonIndexMap } = this.massageMessageBoxOptions({ diff --git a/src/vs/workbench/electron-sandbox/window.ts b/src/vs/workbench/electron-sandbox/window.ts index de32fcf338d..a5fe0e7040d 100644 --- a/src/vs/workbench/electron-sandbox/window.ts +++ b/src/vs/workbench/electron-sandbox/window.ts @@ -187,15 +187,8 @@ export class NativeWindow extends Disposable { ipcRenderer.on('vscode:showInfoMessage', (event: unknown, message: string) => this.notificationService.info(message)); // Fullscreen Events - ipcRenderer.on('vscode:enterFullScreen', async () => { - await this.lifecycleService.when(LifecyclePhase.Ready); - setFullscreen(true); - }); - - ipcRenderer.on('vscode:leaveFullScreen', async () => { - await this.lifecycleService.when(LifecyclePhase.Ready); - setFullscreen(false); - }); + ipcRenderer.on('vscode:enterFullScreen', async () => setFullscreen(true)); + ipcRenderer.on('vscode:leaveFullScreen', async () => setFullscreen(false)); // Proxy Login Dialog ipcRenderer.on('vscode:openProxyAuthenticationDialog', async (event: unknown, payload: { authInfo: AuthInfo, username?: string, password?: string, replyChannel: string }) => { @@ -473,7 +466,7 @@ export class NativeWindow extends Disposable { this.logService.error('Error: There is a dependency cycle in the AMD modules that needs to be resolved!'); this.nativeHostService.exit(37); // running on a build machine, just exit without showing a dialog } else { - this.dialogService.show(Severity.Error, localize('loaderCycle', "There is a dependency cycle in the AMD modules that needs to be resolved!"), [localize('ok', "OK")]); + this.dialogService.show(Severity.Error, localize('loaderCycle', "There is a dependency cycle in the AMD modules that needs to be resolved!")); this.nativeHostService.openDevTools(); } } @@ -669,8 +662,6 @@ export class NativeWindow extends Disposable { } private async openResources(resources: Array, diffMode: boolean): Promise { - await this.lifecycleService.when(LifecyclePhase.Ready); - const editors: IBaseResourceEditorInput[] = []; // In diffMode we open 2 resources as diff diff --git a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts index 1b64e412378..ff6daa481c0 100644 --- a/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts +++ b/src/vs/workbench/services/configurationResolver/browser/configurationResolverService.ts @@ -19,6 +19,7 @@ import { IQuickInputService, IInputOptions, IQuickPickItem, IPickOptions } from import { ConfiguredInput } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { ILabelService } from 'vs/platform/label/common/label'; +import { IPathService } from 'vs/workbench/services/path/common/pathService'; export abstract class BaseConfigurationResolverService extends AbstractVariableResolverService { @@ -35,7 +36,8 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR private readonly commandService: ICommandService, private readonly workspaceContextService: IWorkspaceContextService, private readonly quickInputService: IQuickInputService, - private readonly labelService: ILabelService + private readonly labelService: ILabelService, + private readonly pathService: IPathService ) { super({ getFolderUri: (folderName: string): uri | undefined => { @@ -57,7 +59,7 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR getFilePath: (): string | undefined => { const fileResource = EditorResourceAccessor.getOriginalUri(editorService.activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY, - filterByScheme: [Schemas.file, Schemas.userData, Schemas.vscodeRemote] + filterByScheme: [Schemas.file, Schemas.userData, this.pathService.defaultUriScheme] }); if (!fileResource) { return undefined; @@ -67,7 +69,7 @@ export abstract class BaseConfigurationResolverService extends AbstractVariableR getWorkspaceFolderPathForFile: (): string | undefined => { const fileResource = EditorResourceAccessor.getOriginalUri(editorService.activeEditor, { supportSideBySide: SideBySideEditor.PRIMARY, - filterByScheme: [Schemas.file, Schemas.userData, Schemas.vscodeRemote] + filterByScheme: [Schemas.file, Schemas.userData, this.pathService.defaultUriScheme] }); if (!fileResource) { return undefined; @@ -366,9 +368,10 @@ export class ConfigurationResolverService extends BaseConfigurationResolverServi @IWorkspaceContextService workspaceContextService: IWorkspaceContextService, @IQuickInputService quickInputService: IQuickInputService, @ILabelService labelService: ILabelService, + @IPathService pathService: IPathService ) { super({ getAppRoot: () => undefined, getExecPath: () => undefined }, Promise.resolve(Object.create(null)), editorService, configurationService, - commandService, workspaceContextService, quickInputService, labelService); + commandService, workspaceContextService, quickInputService, labelService, pathService); } } diff --git a/src/vs/workbench/services/configurationResolver/electron-sandbox/configurationResolverService.ts b/src/vs/workbench/services/configurationResolver/electron-sandbox/configurationResolverService.ts index cf99451cb3c..b98d3a68357 100644 --- a/src/vs/workbench/services/configurationResolver/electron-sandbox/configurationResolverService.ts +++ b/src/vs/workbench/services/configurationResolver/electron-sandbox/configurationResolverService.ts @@ -14,6 +14,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { BaseConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; import { ILabelService } from 'vs/platform/label/common/label'; import { IShellEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/shellEnvironmentService'; +import { IPathService } from 'vs/workbench/services/path/common/pathService'; export class ConfigurationResolverService extends BaseConfigurationResolverService { @@ -25,7 +26,8 @@ export class ConfigurationResolverService extends BaseConfigurationResolverServi @IWorkspaceContextService workspaceContextService: IWorkspaceContextService, @IQuickInputService quickInputService: IQuickInputService, @ILabelService labelService: ILabelService, - @IShellEnvironmentService shellEnvironmentService: IShellEnvironmentService + @IShellEnvironmentService shellEnvironmentService: IShellEnvironmentService, + @IPathService pathService: IPathService ) { super({ getAppRoot: (): string | undefined => { @@ -34,7 +36,8 @@ export class ConfigurationResolverService extends BaseConfigurationResolverServi getExecPath: (): string | undefined => { return environmentService.execPath; } - }, shellEnvironmentService.getShellEnv(), editorService, configurationService, commandService, workspaceContextService, quickInputService, labelService); + }, shellEnvironmentService.getShellEnv(), editorService, configurationService, commandService, + workspaceContextService, quickInputService, labelService, pathService); } } diff --git a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts index c1b218f8f6d..c56930d52d7 100644 --- a/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts +++ b/src/vs/workbench/services/configurationResolver/test/electron-browser/configurationResolverService.test.ts @@ -6,7 +6,8 @@ import * as assert from 'assert'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; -import { normalize } from 'vs/base/common/path'; +import { Schemas } from 'vs/base/common/network'; +import { IPath, normalize } from 'vs/base/common/path'; import * as platform from 'vs/base/common/platform'; import { isObject } from 'vs/base/common/types'; import { URI as uri } from 'vs/base/common/uri'; @@ -22,6 +23,7 @@ import { IWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; import { BaseConfigurationResolverService } from 'vs/workbench/services/configurationResolver/browser/configurationResolverService'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import { NativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; +import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { TestEditorService, TestProductService, TestQuickInputService } from 'vs/workbench/test/browser/workbenchTestServices'; import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices'; import { TestWorkbenchConfiguration } from 'vs/workbench/test/electron-browser/workbenchTestServices'; @@ -66,6 +68,7 @@ suite('Configuration Resolver Service', () => { let workspace: IWorkspaceFolder; let quickInputService: TestQuickInputService; let labelService: MockLabelService; + let pathService: MockPathService; setup(() => { mockCommandService = new MockCommandService(); @@ -73,9 +76,10 @@ suite('Configuration Resolver Service', () => { quickInputService = new TestQuickInputService(); environmentService = new MockWorkbenchEnvironmentService(envVariables); labelService = new MockLabelService(); + pathService = new MockPathService(); containingWorkspace = testWorkspace(uri.parse('file:///VSCode/workspaceLocation')); workspace = containingWorkspace.folders[0]; - configurationResolverService = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), editorService, new MockInputsConfigurationService(), mockCommandService, new TestContextService(containingWorkspace), quickInputService, labelService); + configurationResolverService = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), editorService, new MockInputsConfigurationService(), mockCommandService, new TestContextService(containingWorkspace), quickInputService, labelService, pathService); }); teardown(() => { @@ -214,7 +218,7 @@ suite('Configuration Resolver Service', () => { } }); - let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService); + let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService, pathService); assert.strictEqual(await service.resolveAsync(workspace, 'abc ${config:editor.fontFamily} xyz'), 'abc foo xyz'); }); @@ -225,7 +229,7 @@ suite('Configuration Resolver Service', () => { } }); - let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService); + let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService, pathService); assert.strictEqual(await service.resolveAsync(undefined, 'abc ${config:editor.fontFamily} xyz'), 'abc foo xyz'); }); @@ -242,7 +246,7 @@ suite('Configuration Resolver Service', () => { } }); - let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService); + let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService, pathService); assert.strictEqual(await service.resolveAsync(workspace, 'abc ${config:editor.fontFamily} ${config:terminal.integrated.fontFamily} xyz'), 'abc foo bar xyz'); }); @@ -259,7 +263,7 @@ suite('Configuration Resolver Service', () => { } }); - let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService); + let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService, pathService); if (platform.isWindows) { assert.strictEqual(await service.resolveAsync(workspace, 'abc ${config:editor.fontFamily} ${workspaceFolder} ${env:key1} xyz'), 'abc foo \\VSCode\\workspaceLocation Value for key1 xyz'); } else { @@ -280,7 +284,7 @@ suite('Configuration Resolver Service', () => { } }); - let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService); + let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService, pathService); if (platform.isWindows) { assert.strictEqual(await service.resolveAsync(workspace, '${config:editor.fontFamily} ${config:terminal.integrated.fontFamily} ${workspaceFolder} - ${workspaceFolder} ${env:key1} - ${env:key2}'), 'foo bar \\VSCode\\workspaceLocation - \\VSCode\\workspaceLocation Value for key1 - Value for key2'); } else { @@ -314,7 +318,7 @@ suite('Configuration Resolver Service', () => { } }); - let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService); + let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService, pathService); assert.strictEqual(await service.resolveAsync(workspace, 'abc ${config:editor.fontFamily} ${config:editor.lineNumbers} ${config:editor.insertSpaces} xyz'), 'abc foo 123 false xyz'); }); @@ -324,7 +328,7 @@ suite('Configuration Resolver Service', () => { editor: {} }); - let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService); + let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService, pathService); assert.strictEqual(await service.resolveAsync(workspace, 'abc ${unknownVariable} xyz'), 'abc ${unknownVariable} xyz'); assert.strictEqual(await service.resolveAsync(workspace, 'abc ${env:unknownVariable} xyz'), 'abc xyz'); }); @@ -337,7 +341,7 @@ suite('Configuration Resolver Service', () => { } }); - let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService); + let service = new TestConfigurationResolverService(nullContext, Promise.resolve(environmentService.userEnv), new TestEditorServiceWithActiveEditor(), configurationService, mockCommandService, new TestContextService(), quickInputService, labelService, pathService); assert.rejects(async () => await service.resolveAsync(workspace, 'abc ${env} xyz')); assert.rejects(async () => await service.resolveAsync(workspace, 'abc ${env:} xyz')); @@ -675,6 +679,21 @@ class MockLabelService implements ILabelService { onDidChangeFormatters: Event = new Emitter().event; } +class MockPathService implements IPathService { + _serviceBrand: undefined; + get path(): Promise { + throw new Error('Property not implemented'); + } + defaultUriScheme: string = Schemas.file; + fileURI(path: string): Promise { + throw new Error('Method not implemented.'); + } + userHome(options?: { preferLocal: boolean; }): Promise { + throw new Error('Method not implemented.'); + } + resolvedUserHome: uri | undefined; +} + class MockInputsConfigurationService extends TestConfigurationService { public override getValue(arg1?: any, arg2?: any): any { let configuration; diff --git a/src/vs/workbench/services/dialogs/common/dialogService.ts b/src/vs/workbench/services/dialogs/common/dialogService.ts index df940085921..b66b8678475 100644 --- a/src/vs/workbench/services/dialogs/common/dialogService.ts +++ b/src/vs/workbench/services/dialogs/common/dialogService.ts @@ -6,26 +6,30 @@ import Severity from 'vs/base/common/severity'; import { Disposable } from 'vs/base/common/lifecycle'; import { IConfirmation, IConfirmationResult, IDialogOptions, IDialogService, IInput, IInputResult, IShowResult } from 'vs/platform/dialogs/common/dialogs'; -import { DialogsModel, IDialogsModel } from 'vs/workbench/common/dialogs'; +import { DialogsModel } from 'vs/workbench/common/dialogs'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; export class DialogService extends Disposable implements IDialogService { - _serviceBrand: undefined; - readonly model: IDialogsModel = this._register(new DialogsModel()); + declare readonly _serviceBrand: undefined; + + readonly model = this._register(new DialogsModel()); async confirm(confirmation: IConfirmation): Promise { const handle = this.model.show({ confirmArgs: { confirmation } }); + return await handle.result as IConfirmationResult; } - async show(severity: Severity, message: string, buttons: string[], options?: IDialogOptions): Promise { + async show(severity: Severity, message: string, buttons?: string[], options?: IDialogOptions): Promise { const handle = this.model.show({ showArgs: { severity, message, buttons, options } }); + return await handle.result as IShowResult; } async input(severity: Severity, message: string, buttons: string[], inputs: IInput[], options?: IDialogOptions): Promise { const handle = this.model.show({ inputArgs: { severity, message, buttons, inputs, options } }); + return await handle.result as IInputResult; } diff --git a/src/vs/workbench/services/extensionManagement/browser/extensionBisect.ts b/src/vs/workbench/services/extensionManagement/browser/extensionBisect.ts index 0684def2708..548f15ad4f8 100644 --- a/src/vs/workbench/services/extensionManagement/browser/extensionBisect.ts +++ b/src/vs/workbench/services/extensionManagement/browser/extensionBisect.ts @@ -291,7 +291,7 @@ registerAction2(class extends Action2 { if (done.bad) { // DONE but nothing found - await dialogService.show(Severity.Info, localize('done.msg', "Extension Bisect"), [], { + await dialogService.show(Severity.Info, localize('done.msg', "Extension Bisect"), undefined, { detail: localize('done.detail2', "Extension Bisect is done but no extension has been identified. This might be a problem with {0}.", productService.nameShort) }); diff --git a/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts b/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts index e7678d5f7ee..521ab664f82 100644 --- a/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts +++ b/src/vs/workbench/services/extensionManagement/browser/extensionEnablementService.ts @@ -429,7 +429,7 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench public async updateEnablementByWorkspaceTrustRequirement(): Promise { const installedExtensions = await this.extensionManagementService.getInstalled(); - const disabledExtensions = installedExtensions.filter(e => this.isEnabled(e) && this.isDisabledByWorkspaceTrust(e)); + const disabledExtensions = installedExtensions.filter(e => this.isDisabledByWorkspaceTrust(e)); if (disabledExtensions.length) { this._onEnablementChanged.fire(disabledExtensions); diff --git a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts index 31798ea4308..8b1903e969b 100644 --- a/src/vs/workbench/services/extensions/common/abstractExtensionService.ts +++ b/src/vs/workbench/services/extensions/common/abstractExtensionService.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { isNonEmptyArray } from 'vs/base/common/arrays'; import { Barrier } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import * as perf from 'vs/base/common/performance'; import { isEqualOrParent } from 'vs/base/common/resources'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -68,6 +68,69 @@ export const enum ExtensionRunningPreference { Remote } +class LockCustomer { + public readonly promise: Promise; + private _resolve!: (value: IDisposable) => void; + + constructor( + public readonly name: string + ) { + this.promise = new Promise((resolve, reject) => { + this._resolve = resolve; + }); + } + + resolve(value: IDisposable): void { + this._resolve(value); + } +} + +class Lock { + private readonly _pendingCustomers: LockCustomer[] = []; + private _isLocked = false; + + public async acquire(customerName: string): Promise { + const customer = new LockCustomer(customerName); + this._pendingCustomers.push(customer); + this._advance(); + return customer.promise; + } + + private _advance(): void { + if (this._isLocked) { + // cannot advance yet + return; + } + if (this._pendingCustomers.length === 0) { + // no more waiting customers + return; + } + + const customer = this._pendingCustomers.shift()!; + + this._isLocked = true; + let customerHoldsLock = true; + + let logLongRunningCustomerTimeout = setTimeout(() => { + if (customerHoldsLock) { + console.warn(`The customer named ${customer.name} has been holding on to the lock for 30s. This might be a problem.`); + } + }, 30 * 1000 /* 30 seconds */); + + const releaseLock = () => { + if (!customerHoldsLock) { + return; + } + clearTimeout(logLongRunningCustomerTimeout); + customerHoldsLock = false; + this._isLocked = false; + this._advance(); + }; + + customer.resolve(toDisposable(releaseLock)); + } +} + export abstract class AbstractExtensionService extends Disposable implements IExtensionService { public _serviceBrand: undefined; @@ -88,6 +151,8 @@ export abstract class AbstractExtensionService extends Disposable implements IEx public readonly onDidChangeResponsiveChange: Event = this._onDidChangeResponsiveChange.event; protected readonly _registry: ExtensionDescriptionRegistry; + private readonly _registryLock: Lock; + private readonly _installedExtensionsReady: Barrier; protected readonly _isDev: boolean; private readonly _extensionsMessages: Map; @@ -98,7 +163,6 @@ export abstract class AbstractExtensionService extends Disposable implements IEx private _deltaExtensionsQueue: DeltaExtensionsQueueItem[]; private _inHandleDeltaExtensions: boolean; - private readonly _onDidFinishHandleDeltaExtensions = this._register(new Emitter()); protected _runningLocation: Map; @@ -130,6 +194,8 @@ export abstract class AbstractExtensionService extends Disposable implements IEx })); this._registry = new ExtensionDescriptionRegistry([]); + this._registryLock = new Lock(); + this._installedExtensionsReady = new Barrier(); this._isDev = !this._environmentService.isBuilt || this._environmentService.isExtensionDevelopment; this._extensionsMessages = new Map(); @@ -207,17 +273,20 @@ export abstract class AbstractExtensionService extends Disposable implements IEx return; } - while (this._deltaExtensionsQueue.length > 0) { - const item = this._deltaExtensionsQueue.shift()!; - try { - this._inHandleDeltaExtensions = true; + let lock: IDisposable | null = null; + try { + this._inHandleDeltaExtensions = true; + lock = await this._registryLock.acquire('handleDeltaExtensions'); + while (this._deltaExtensionsQueue.length > 0) { + const item = this._deltaExtensionsQueue.shift()!; await this._deltaExtensions(item.toAdd, item.toRemove); - } finally { - this._inHandleDeltaExtensions = false; + } + } finally { + this._inHandleDeltaExtensions = false; + if (lock) { + lock.dispose(); } } - - this._onDidFinishHandleDeltaExtensions.fire(); } private async _deltaExtensions(_toAdd: IExtension[], _toRemove: string[] | IExtension[]): Promise { @@ -566,11 +635,17 @@ export abstract class AbstractExtensionService extends Disposable implements IEx public async startExtensionHosts(): Promise { this.stopExtensionHosts(); - if (this._inHandleDeltaExtensions) { - await Event.toPromise(this._onDidFinishHandleDeltaExtensions.event); - } + const lock = await this._registryLock.acquire('startExtensionHosts'); + try { + this._startExtensionHosts(false, Array.from(this._allRequestedActivateEvents.keys())); - this._startExtensionHosts(false, Array.from(this._allRequestedActivateEvents.keys())); + const localProcessExtensionHost = this._getExtensionHostManager(ExtensionHostKind.LocalProcess); + if (localProcessExtensionHost) { + await localProcessExtensionHost.ready(); + } + } finally { + lock.dispose(); + } } public async restartExtensionHost(): Promise { diff --git a/src/vs/workbench/services/extensions/common/extensionHostManager.ts b/src/vs/workbench/services/extensions/common/extensionHostManager.ts index b67841044c3..50ef4572c74 100644 --- a/src/vs/workbench/services/extensions/common/extensionHostManager.ts +++ b/src/vs/workbench/services/extensions/common/extensionHostManager.ts @@ -133,6 +133,10 @@ export class ExtensionHostManager extends Disposable { return p.value; } + public async ready(): Promise { + await this._getProxy(); + } + private async _measureLatency(proxy: ExtHostExtensionServiceShape): Promise { const COUNT = 10; diff --git a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts index aa78078e14e..52bc371a8de 100644 --- a/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts +++ b/src/vs/workbench/services/extensions/electron-browser/cachedExtensionScanner.ts @@ -171,7 +171,7 @@ export class CachedExtensionScanner { } try { - await pfs.writeFile(cacheFile, JSON.stringify(cacheContents)); + await pfs.Promises.writeFile(cacheFile, JSON.stringify(cacheContents)); } catch (err) { // That's ok... } diff --git a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts index a0baef25034..ee925367eb9 100644 --- a/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts +++ b/src/vs/workbench/services/extensions/node/extensionHostProcessSetup.ts @@ -18,7 +18,7 @@ import { MessageType, createMessageOfType, isMessageOfType, IExtHostSocketMessag import { ExtensionHostMain, IExitFn } from 'vs/workbench/services/extensions/common/extensionHostMain'; import { VSBuffer } from 'vs/base/common/buffer'; import { IURITransformer, URITransformer, IRawURITransformer } from 'vs/base/common/uriIpc'; -import { exists } from 'vs/base/node/pfs'; +import { Promises } from 'vs/base/node/pfs'; import { realpath } from 'vs/base/node/extpath'; import { IHostUtils } from 'vs/workbench/api/common/extHostExtensionService'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -326,7 +326,7 @@ export async function startExtensionHostProcess(): Promise { const hostUtils = new class NodeHost implements IHostUtils { declare readonly _serviceBrand: undefined; exit(code: number) { nativeExit(code); } - exists(path: string) { return exists(path); } + exists(path: string) { return Promises.exists(path); } realpath(path: string) { return realpath(path); } }; diff --git a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts index f96114f25a9..684aae6847a 100644 --- a/src/vs/workbench/services/issue/electron-sandbox/issueService.ts +++ b/src/vs/workbench/services/issue/electron-sandbox/issueService.ts @@ -19,6 +19,7 @@ import { IProductService } from 'vs/platform/product/common/productService'; import { ITASExperimentService } from 'vs/workbench/services/experiment/common/experimentService'; import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService'; import { registerMainProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services'; +import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; export class WorkbenchIssueService implements IWorkbenchIssueService { declare readonly _serviceBrand: undefined; @@ -29,6 +30,7 @@ export class WorkbenchIssueService implements IWorkbenchIssueService { @IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService, @IWorkbenchExtensionEnablementService private readonly extensionEnablementService: IWorkbenchExtensionEnablementService, @INativeWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService, + @IWorkspaceTrustManagementService private readonly workspaceTrustManagementService: IWorkspaceTrustManagementService, @IProductService private readonly productService: IProductService, @ITASExperimentService private readonly experimentService: ITASExperimentService, @IAuthenticationService private readonly authenticationService: IAuthenticationService @@ -86,6 +88,7 @@ export class WorkbenchIssueService implements IWorkbenchIssueService { zoomLevel: getZoomLevel(), enabledExtensions: extensionData, experiments: experiments?.join('\n'), + restrictedMode: !this.workspaceTrustManagementService.isWorkpaceTrusted(), githubAccessToken, }, dataOverrides); return this.issueService.openReporter(issueReporterData); diff --git a/src/vs/workbench/services/keybinding/test/electron-browser/keyboardMapperTestUtils.ts b/src/vs/workbench/services/keybinding/test/electron-browser/keyboardMapperTestUtils.ts index d17478838fe..ea7eb112d71 100644 --- a/src/vs/workbench/services/keybinding/test/electron-browser/keyboardMapperTestUtils.ts +++ b/src/vs/workbench/services/keybinding/test/electron-browser/keyboardMapperTestUtils.ts @@ -8,7 +8,7 @@ import * as path from 'vs/base/common/path'; import { getPathFromAmdModule } from 'vs/base/test/node/testUtils'; import { Keybinding, ResolvedKeybinding, SimpleKeybinding } from 'vs/base/common/keyCodes'; import { ScanCodeBinding } from 'vs/base/common/scanCode'; -import { Promises, writeFile } from 'vs/base/node/pfs'; +import { Promises } from 'vs/base/node/pfs'; import { IKeyboardEvent } from 'vs/platform/keybinding/common/keybinding'; import { IKeyboardMapper } from 'vs/platform/keyboardLayout/common/keyboardMapper'; @@ -71,7 +71,7 @@ export function assertMapping(writeFileIfDifferent: boolean, mapper: IKeyboardMa const actual = mapper.dumpDebugInfo().replace(/\r\n/g, '\n'); if (actual !== expected && writeFileIfDifferent) { const destPath = filePath.replace(/vscode[\/\\]out[\/\\]vs/, 'vscode/src/vs'); - writeFile(destPath, actual); + Promises.writeFile(destPath, actual); } assert.deepStrictEqual(actual, expected); }); diff --git a/src/vs/workbench/services/lifecycle/common/lifecycleService.ts b/src/vs/workbench/services/lifecycle/common/lifecycleService.ts index 30ddd666404..e8dca83889e 100644 --- a/src/vs/workbench/services/lifecycle/common/lifecycleService.ts +++ b/src/vs/workbench/services/lifecycle/common/lifecycleService.ts @@ -23,10 +23,10 @@ export abstract class AbstractLifecycleService extends Disposable implements ILi protected readonly _onDidShutdown = this._register(new Emitter()); readonly onDidShutdown = this._onDidShutdown.event; - protected _startupKind: StartupKind = StartupKind.NewWindow; + protected _startupKind = StartupKind.NewWindow; get startupKind(): StartupKind { return this._startupKind; } - private _phase: LifecyclePhase = LifecyclePhase.Starting; + private _phase = LifecyclePhase.Starting; get phase(): LifecyclePhase { return this._phase; } private readonly phaseWhen = new Map(); diff --git a/src/vs/workbench/services/path/browser/pathService.ts b/src/vs/workbench/services/path/browser/pathService.ts index 3b98d439599..5437a621fbd 100644 --- a/src/vs/workbench/services/path/browser/pathService.ts +++ b/src/vs/workbench/services/path/browser/pathService.ts @@ -7,45 +7,22 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { IPathService, AbstractPathService } from 'vs/workbench/services/path/common/pathService'; import { URI } from 'vs/base/common/uri'; -import { Schemas } from 'vs/base/common/network'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { getVirtualWorkspaceScheme } from 'vs/platform/remote/common/remoteHosts'; export class BrowserPathService extends AbstractPathService { - readonly defaultUriScheme = defaultUriScheme(this.environmentService, this.contextService); - constructor( @IRemoteAgentService remoteAgentService: IRemoteAgentService, - @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService + @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, + @IWorkspaceContextService contextService: IWorkspaceContextService ) { - super(URI.from({ scheme: defaultUriScheme(environmentService, contextService), authority: environmentService.remoteAuthority, path: '/' }), remoteAgentService); + super(URI.from({ + scheme: AbstractPathService.findDefaultUriScheme(environmentService, contextService), + authority: environmentService.remoteAuthority, path: '/' + }), + remoteAgentService, environmentService, contextService); } } -function defaultUriScheme(environmentService: IWorkbenchEnvironmentService, contextService: IWorkspaceContextService): string { - if (environmentService.remoteAuthority) { - return Schemas.vscodeRemote; - } - - const virtualWorkspace = getVirtualWorkspaceScheme(contextService.getWorkspace()); - if (virtualWorkspace) { - return virtualWorkspace; - } - - const firstFolder = contextService.getWorkspace().folders[0]; - if (firstFolder) { - return firstFolder.uri.scheme; - } - - const configuration = contextService.getWorkspace().configuration; - if (configuration) { - return configuration.scheme; - } - - return Schemas.file; -} - registerSingleton(IPathService, BrowserPathService, true); diff --git a/src/vs/workbench/services/path/common/pathService.ts b/src/vs/workbench/services/path/common/pathService.ts index 66220d241c3..7a16958bf59 100644 --- a/src/vs/workbench/services/path/common/pathService.ts +++ b/src/vs/workbench/services/path/common/pathService.ts @@ -8,6 +8,9 @@ import { IPath, win32, posix } from 'vs/base/common/path'; import { OperatingSystem, OS } from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; +import { getVirtualWorkspaceScheme } from 'vs/platform/remote/common/remoteHosts'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; export const IPathService = createDecorator('pathService'); @@ -69,11 +72,11 @@ export abstract class AbstractPathService implements IPathService { private resolveUserHome: Promise; private maybeUnresolvedUserHome: URI | undefined; - abstract readonly defaultUriScheme: string; - constructor( private localUserHome: URI, - @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService + @IRemoteAgentService private readonly remoteAgentService: IRemoteAgentService, + @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, + @IWorkspaceContextService private contextService: IWorkspaceContextService ) { // OS @@ -93,6 +96,33 @@ export abstract class AbstractPathService implements IPathService { })(); } + get defaultUriScheme(): string { + return AbstractPathService.findDefaultUriScheme(this.environmentService, this.contextService); + } + + protected static findDefaultUriScheme(environmentService: IWorkbenchEnvironmentService, contextService: IWorkspaceContextService): string { + if (environmentService.remoteAuthority) { + return Schemas.vscodeRemote; + } + + const virtualWorkspace = getVirtualWorkspaceScheme(contextService.getWorkspace()); + if (virtualWorkspace) { + return virtualWorkspace; + } + + const firstFolder = contextService.getWorkspace().folders[0]; + if (firstFolder) { + return firstFolder.uri.scheme; + } + + const configuration = contextService.getWorkspace().configuration; + if (configuration) { + return configuration.scheme; + } + + return Schemas.file; + } + async userHome(options?: { preferLocal: boolean }): Promise { return options?.preferLocal ? this.localUserHome : this.resolveUserHome; } diff --git a/src/vs/workbench/services/path/electron-sandbox/pathService.ts b/src/vs/workbench/services/path/electron-sandbox/pathService.ts index 85c8ca890d9..6d9c3f09b85 100644 --- a/src/vs/workbench/services/path/electron-sandbox/pathService.ts +++ b/src/vs/workbench/services/path/electron-sandbox/pathService.ts @@ -7,36 +7,19 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-sandbox/environmentService'; import { IPathService, AbstractPathService } from 'vs/workbench/services/path/common/pathService'; -import { Schemas } from 'vs/base/common/network'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; -import { getVirtualWorkspaceScheme } from 'vs/platform/remote/common/remoteHosts'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; export class NativePathService extends AbstractPathService { - readonly defaultUriScheme = defaultUriScheme(this.environmentService, this.contextService); - constructor( @IRemoteAgentService remoteAgentService: IRemoteAgentService, - @INativeWorkbenchEnvironmentService private readonly environmentService: INativeWorkbenchEnvironmentService, - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService + @INativeWorkbenchEnvironmentService environmentService: INativeWorkbenchEnvironmentService, + @IWorkspaceContextService contextService: IWorkspaceContextService ) { - super(environmentService.userHome, remoteAgentService); + super(environmentService.userHome, remoteAgentService, environmentService, contextService); } } -function defaultUriScheme(environmentService: IWorkbenchEnvironmentService, contextService: IWorkspaceContextService): string { - if (environmentService.remoteAuthority) { - return Schemas.vscodeRemote; - } - - const virtualWorkspace = getVirtualWorkspaceScheme(contextService.getWorkspace()); - if (virtualWorkspace) { - return virtualWorkspace; - } - - return Schemas.file; -} registerSingleton(IPathService, NativePathService, true); diff --git a/src/vs/workbench/services/remote/common/remoteExplorerService.ts b/src/vs/workbench/services/remote/common/remoteExplorerService.ts index 162f505dc32..bbbf8f7448b 100644 --- a/src/vs/workbench/services/remote/common/remoteExplorerService.ts +++ b/src/vs/workbench/services/remote/common/remoteExplorerService.ts @@ -546,7 +546,7 @@ export class TunnelModel extends Disposable { this.mismatchCooldown = newCooldown; const mismatchString = nls.localize('remote.localPortMismatch.single', "Local port {0} could not be used for forwarding to remote port {1}.\n\nThis usually happens when there is already another process using local port {0}.\n\nPort number {2} has been used instead.", expectedLocal, tunnel.tunnelRemotePort, tunnel.tunnelLocalPort); - return this.dialogService.show(Severity.Info, mismatchString, [nls.localize('remote.localPortMismatch.Ok', "Ok")]); + return this.dialogService.show(Severity.Info, mismatchString); } async forward(remote: { host: string, port: number }, local?: number, name?: string, source?: string, elevateIfNeeded?: boolean, diff --git a/src/vs/workbench/services/search/node/fileSearch.ts b/src/vs/workbench/services/search/node/fileSearch.ts index ab3e5647385..36b36e237d7 100644 --- a/src/vs/workbench/services/search/node/fileSearch.ts +++ b/src/vs/workbench/services/search/node/fileSearch.ts @@ -18,7 +18,7 @@ import { StopWatch } from 'vs/base/common/stopwatch'; import * as strings from 'vs/base/common/strings'; import * as types from 'vs/base/common/types'; import { URI } from 'vs/base/common/uri'; -import { readdir } from 'vs/base/node/pfs'; +import { Promises } from 'vs/base/node/pfs'; import { IFileQuery, IFolderQuery, IProgressMessage, ISearchEngineStats, IRawFileMatch, ISearchEngine, ISearchEngineSuccess, isFilePatternMatch } from 'vs/workbench/services/search/common/search'; import { spawnRipgrepCmd } from './ripgrepFileSearch'; import { prepareQuery } from 'vs/base/common/fuzzyScorer'; @@ -518,7 +518,7 @@ export class FileWalker { this.walkedPaths[realpath] = true; // remember as walked // Continue walking - return readdir(currentAbsolutePath).then(children => { + return Promises.readdir(currentAbsolutePath).then(children => { if (this.isCanceled || this.isLimitHit) { return clb(null); } diff --git a/src/vs/workbench/services/search/node/textSearchManager.ts b/src/vs/workbench/services/search/node/textSearchManager.ts index 84be2f01f98..19171775f7a 100644 --- a/src/vs/workbench/services/search/node/textSearchManager.ts +++ b/src/vs/workbench/services/search/node/textSearchManager.ts @@ -13,7 +13,7 @@ export class NativeTextSearchManager extends TextSearchManager { constructor(query: ITextQuery, provider: TextSearchProvider, _pfs: typeof pfs = pfs) { super(query, provider, { - readdir: resource => _pfs.readdir(resource.fsPath), + readdir: resource => _pfs.Promises.readdir(resource.fsPath), toCanonicalName: name => toCanonicalName(name) }); } diff --git a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts index c73fb058af1..605f26761c4 100644 --- a/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts +++ b/src/vs/workbench/services/textMate/browser/abstractTextMateService.ts @@ -18,8 +18,7 @@ import { nullTokenize2 } from 'vs/editor/common/modes/nullMode'; import { generateTokensCSSForColorMap } from 'vs/editor/common/modes/supports/tokenization'; import { IModeService } from 'vs/editor/common/services/modeService'; import { ILogService } from 'vs/platform/log/common/log'; -import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { INotificationService } from 'vs/platform/notification/common/notification'; import { ExtensionMessageCollector } from 'vs/workbench/services/extensions/common/extensionsRegistry'; import { ITMSyntaxExtensionPoint, grammarsExtPoint } from 'vs/workbench/services/textMate/common/TMGrammars'; import { ITextMateService } from 'vs/workbench/services/textMate/common/textMateService'; @@ -58,7 +57,6 @@ export abstract class AbstractTextMateService extends Disposable implements ITex @INotificationService private readonly _notificationService: INotificationService, @ILogService private readonly _logService: ILogService, @IConfigurationService private readonly _configurationService: IConfigurationService, - @IStorageService private readonly _storageService: IStorageService, @IProgressService private readonly _progressService: IProgressService ) { super(); @@ -283,7 +281,7 @@ export abstract class AbstractTextMateService extends Disposable implements ITex this._onDidEncounterLanguage.fire(languageId); } }); - return new TMTokenizationSupport(r.languageId, tokenization, this._notificationService, this._configurationService, this._storageService); + return new TMTokenizationSupport(r.languageId, tokenization, this._configurationService); } catch (err) { onUnexpectedError(err); return null; @@ -415,24 +413,18 @@ export abstract class AbstractTextMateService extends Disposable implements ITex protected abstract _loadVSCodeOnigurumWASM(): Promise; } -const donotAskUpdateKey = 'editor.maxTokenizationLineLength.donotask'; - class TMTokenizationSupport implements ITokenizationSupport { private readonly _languageId: LanguageId; private readonly _actual: TMTokenization; - private _tokenizationWarningAlreadyShown: boolean; private _maxTokenizationLineLength: number; constructor( languageId: LanguageId, actual: TMTokenization, - @INotificationService private readonly _notificationService: INotificationService, @IConfigurationService private readonly _configurationService: IConfigurationService, - @IStorageService private readonly _storageService: IStorageService ) { this._languageId = languageId; this._actual = actual; - this._tokenizationWarningAlreadyShown = !!(this._storageService.getBoolean(donotAskUpdateKey, StorageScope.GLOBAL)); this._maxTokenizationLineLength = this._configurationService.getValue('editor.maxTokenizationLineLength'); this._configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('editor.maxTokenizationLineLength')) { @@ -456,19 +448,6 @@ class TMTokenizationSupport implements ITokenizationSupport { // Do not attempt to tokenize if a line is too long if (line.length >= this._maxTokenizationLineLength) { - if (!this._tokenizationWarningAlreadyShown) { - this._tokenizationWarningAlreadyShown = true; - this._notificationService.prompt( - Severity.Warning, - nls.localize('too many characters', "Tokenization is skipped for long lines for performance reasons. The length of a long line can be configured via `editor.maxTokenizationLineLength`."), - [{ - label: nls.localize('neverAgain', "Don't Show Again"), - isSecondary: true, - run: () => this._storageService.store(donotAskUpdateKey, true, StorageScope.GLOBAL, StorageTarget.USER) - }] - ); - } - console.log(`Line (${line.substr(0, 15)}...): longer than ${this._maxTokenizationLineLength} characters, tokenization skipped.`); return nullTokenize2(this._languageId, line, state, offsetDelta); } diff --git a/src/vs/workbench/services/textMate/browser/textMateService.ts b/src/vs/workbench/services/textMate/browser/textMateService.ts index 5a594121bf4..3ad434b7de2 100644 --- a/src/vs/workbench/services/textMate/browser/textMateService.ts +++ b/src/vs/workbench/services/textMate/browser/textMateService.ts @@ -6,31 +6,9 @@ import { ITextMateService } from 'vs/workbench/services/textMate/common/textMateService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { AbstractTextMateService } from 'vs/workbench/services/textMate/browser/abstractTextMateService'; -import { IModeService } from 'vs/editor/common/services/modeService'; -import { ILogService } from 'vs/platform/log/common/log'; -import { INotificationService } from 'vs/platform/notification/common/notification'; -import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { IStorageService } from 'vs/platform/storage/common/storage'; -import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; -import { IProgressService } from 'vs/platform/progress/common/progress'; import { FileAccess } from 'vs/base/common/network'; export class TextMateService extends AbstractTextMateService { - - constructor( - @IModeService modeService: IModeService, - @IWorkbenchThemeService themeService: IWorkbenchThemeService, - @IExtensionResourceLoaderService extensionResourceLoaderService: IExtensionResourceLoaderService, - @INotificationService notificationService: INotificationService, - @ILogService logService: ILogService, - @IConfigurationService configurationService: IConfigurationService, - @IStorageService storageService: IStorageService, - @IProgressService progressService: IProgressService - ) { - super(modeService, themeService, extensionResourceLoaderService, notificationService, logService, configurationService, storageService, progressService); - } - protected async _loadVSCodeOnigurumWASM(): Promise { const response = await fetch(FileAccess.asBrowserUri('vscode-oniguruma/../onig.wasm', require).toString(true)); // Using the response directly only works if the server sets the MIME type 'application/wasm'. diff --git a/src/vs/workbench/services/textMate/electron-sandbox/textMateService.ts b/src/vs/workbench/services/textMate/electron-sandbox/textMateService.ts index 59ba481695d..7a4ca03e31d 100644 --- a/src/vs/workbench/services/textMate/electron-sandbox/textMateService.ts +++ b/src/vs/workbench/services/textMate/electron-sandbox/textMateService.ts @@ -22,7 +22,6 @@ import { UriComponents, URI } from 'vs/base/common/uri'; import { MultilineTokensBuilder } from 'vs/editor/common/model/tokensStore'; import { TMGrammarFactory } from 'vs/workbench/services/textMate/common/TMGrammarFactory'; import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvents'; -import { IStorageService } from 'vs/platform/storage/common/storage'; import { IExtensionResourceLoaderService } from 'vs/workbench/services/extensionResourceLoader/common/extensionResourceLoader'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IProgressService } from 'vs/platform/progress/common/progress'; @@ -148,12 +147,11 @@ export class TextMateService extends AbstractTextMateService { @INotificationService notificationService: INotificationService, @ILogService logService: ILogService, @IConfigurationService configurationService: IConfigurationService, - @IStorageService storageService: IStorageService, @IProgressService progressService: IProgressService, @IModelService private readonly _modelService: IModelService, @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, ) { - super(modeService, themeService, extensionResourceLoaderService, notificationService, logService, configurationService, storageService, progressService); + super(modeService, themeService, extensionResourceLoaderService, notificationService, logService, configurationService, progressService); this._worker = null; this._workerProxy = null; this._tokenizers = Object.create(null); diff --git a/src/vs/workbench/services/textfile/test/electron-browser/nativeTextFileService.io.test.ts b/src/vs/workbench/services/textfile/test/electron-browser/nativeTextFileService.io.test.ts index e46be38a1ee..30424488d82 100644 --- a/src/vs/workbench/services/textfile/test/electron-browser/nativeTextFileService.io.test.ts +++ b/src/vs/workbench/services/textfile/test/electron-browser/nativeTextFileService.io.test.ts @@ -9,7 +9,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; import { Schemas } from 'vs/base/common/network'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; -import { rimraf, copy, exists, Promises } from 'vs/base/node/pfs'; +import { rimraf, copy, Promises } from 'vs/base/node/pfs'; import { DisposableStore } from 'vs/base/common/lifecycle'; import { FileService } from 'vs/platform/files/common/fileService'; import { NullLogService } from 'vs/platform/log/common/log'; @@ -68,7 +68,7 @@ flakySuite('Files - NativeTextFileService i/o', function () { return rimraf(testDir); }, - exists, + exists: Promises.exists, stat: Promises.stat, readFile, detectEncodingByBOM diff --git a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts index 0e811562e70..d4748e653fd 100644 --- a/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts +++ b/src/vs/workbench/services/textmodelResolver/common/textModelResolverService.ts @@ -6,7 +6,7 @@ import { URI } from 'vs/base/common/uri'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITextModel } from 'vs/editor/common/model'; -import { IDisposable, toDisposable, IReference, ReferenceCollection, Disposable } from 'vs/base/common/lifecycle'; +import { IDisposable, toDisposable, IReference, ReferenceCollection, Disposable, AsyncReferenceCollection } from 'vs/base/common/lifecycle'; import { IModelService } from 'vs/editor/common/services/modelService'; import { TextResourceEditorModel } from 'vs/workbench/common/editor/textResourceEditorModel'; import { ITextFileService, TextFileResolveReason } from 'vs/workbench/services/textfile/common/textfiles'; @@ -174,6 +174,7 @@ export class TextModelResolverService extends Disposable implements ITextModelSe declare readonly _serviceBrand: undefined; private readonly resourceModelCollection = this.instantiationService.createInstance(ResourceModelCollection); + private readonly asyncModelCollection = new AsyncReferenceCollection(this.resourceModelCollection); constructor( @IInstantiationService private readonly instantiationService: IInstantiationService, @@ -194,20 +195,8 @@ export class TextModelResolverService extends Disposable implements ITextModelSe // with different resource forms (e.g. path casing on Windows) resource = this.uriIdentityService.asCanonicalUri(resource); - const ref = this.resourceModelCollection.acquire(resource.toString()); - - try { - const model = await ref.object; - - return { - object: model as IResolvedTextEditorModel, - dispose: () => ref.dispose() - }; - } catch (error) { - ref.dispose(); - - throw error; - } + const result = await this.asyncModelCollection.acquire(resource.toString()); + return result as IReference; // TODO@Ben: why is this cast here? } registerTextModelContentProvider(scheme: string, provider: ITextModelContentProvider): IDisposable { diff --git a/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts b/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts index 54eadefea64..a6a8e075dd1 100644 --- a/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts +++ b/src/vs/workbench/services/workingCopy/common/workingCopyBackupService.ts @@ -19,7 +19,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Schemas } from 'vs/base/common/network'; import { hash } from 'vs/base/common/hash'; import { isEmptyObject } from 'vs/base/common/types'; -import { IWorkingCopyBackupMeta, IWorkingCopyIdentifier } from 'vs/workbench/services/workingCopy/common/workingCopy'; +import { IWorkingCopyBackupMeta, IWorkingCopyIdentifier, NO_TYPE_ID } from 'vs/workbench/services/workingCopy/common/workingCopy'; export class WorkingCopyBackupsModel { @@ -404,7 +404,7 @@ class NativeWorkingCopyBackupServiceImpl extends Disposable implements IWorkingC } return { - typeId: typeId ?? '', // Fallback for previous backups that do not encode the typeId (TODO@bpasero remove me eventually) + typeId: typeId ?? NO_TYPE_ID, resource: URI.parse(resourcePreamble) }; } diff --git a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts index c158e96b18f..737fac3ad17 100644 --- a/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts +++ b/src/vs/workbench/services/workingCopy/electron-sandbox/workingCopyBackupTracker.ts @@ -143,7 +143,7 @@ export class NativeWorkingCopyBackupTracker extends WorkingCopyBackupTracker imp ? getFileNamesMessage(dirtyWorkingCopies.map(x => x.name)) + '\n' + advice : advice; - this.dialogService.show(Severity.Error, msg, [localize('ok', 'OK')], { detail }); + this.dialogService.show(Severity.Error, msg, undefined, { detail }); this.logService.error(error ? `[backup tracker] ${msg}: ${error}` : `[backup tracker] ${msg}`); } diff --git a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupService.test.ts b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupService.test.ts index 534c8927890..ae1a5c63cf2 100644 --- a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupService.test.ts +++ b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupService.test.ts @@ -12,7 +12,7 @@ import { hash } from 'vs/base/common/hash'; import { isEqual } from 'vs/base/common/resources'; import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs'; import { dirname, join } from 'vs/base/common/path'; -import { Promises, readdirSync, rimraf, writeFile } from 'vs/base/node/pfs'; +import { Promises, readdirSync, rimraf } from 'vs/base/node/pfs'; import { URI } from 'vs/base/common/uri'; import { WorkingCopyBackupsModel, hashIdentifier } from 'vs/workbench/services/workingCopy/common/workingCopyBackupService'; import { createTextModel } from 'vs/editor/test/common/editorTestUtils'; @@ -151,7 +151,7 @@ suite('WorkingCopyBackupService', () => { await Promises.mkdir(backupHome, { recursive: true }); - return writeFile(workspacesJsonPath, ''); + return Promises.writeFile(workspacesJsonPath, ''); }); teardown(() => { diff --git a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts index 0cbfa6c9876..bfdb9444dc9 100644 --- a/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts +++ b/src/vs/workbench/services/workingCopy/test/electron-browser/workingCopyBackupTracker.test.ts @@ -7,7 +7,7 @@ import * as assert from 'assert'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { tmpdir } from 'os'; import { join } from 'vs/base/common/path'; -import { Promises, rimraf, writeFile } from 'vs/base/node/pfs'; +import { Promises, rimraf } from 'vs/base/node/pfs'; import { URI } from 'vs/base/common/uri'; import { flakySuite, getRandomTestPath } from 'vs/base/test/node/testUtils'; import { hash } from 'vs/base/common/hash'; @@ -107,7 +107,7 @@ flakySuite('WorkingCopyBackupTracker (native)', function () { await Promises.mkdir(backupHome, { recursive: true }); await Promises.mkdir(workspaceBackupPath, { recursive: true }); - return writeFile(workspacesJsonPath, ''); + return Promises.writeFile(workspacesJsonPath, ''); }); teardown(async () => { diff --git a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts index 7bfb5124c64..360edf0f75f 100644 --- a/src/vs/workbench/services/workspaces/common/workspaceTrust.ts +++ b/src/vs/workbench/services/workspaces/common/workspaceTrust.ts @@ -18,6 +18,7 @@ import { IContextKey, IContextKeyService, RawContextKey } from 'vs/platform/cont import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IRemoteAuthorityResolverService, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { isVirtualResource } from 'vs/platform/remote/common/remoteHosts'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { IWorkspace, IWorkspaceContextService, IWorkspaceFolder, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { WorkspaceTrustRequestOptions, IWorkspaceTrustManagementService, IWorkspaceTrustInfo, IWorkspaceTrustUriInfo, IWorkspaceTrustRequestService, IWorkspaceTrustTransitionParticipant, WorkspaceTrustUriResponse } from 'vs/platform/workspace/common/workspaceTrust'; @@ -160,8 +161,18 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork } private async getCanonicalUri(uri: URI): Promise { - return this.environmentService.remoteAuthority && uri.scheme === Schemas.vscodeRemote ? - await this.remoteAuthorityResolverService.getCanonicalURI(uri) : uri; + if (this.environmentService.remoteAuthority && uri.scheme === Schemas.vscodeRemote) { + return this.remoteAuthorityResolverService.getCanonicalURI(uri); + } + + if (uri.scheme === 'vscode-vfs') { + const index = uri.authority.indexOf('+'); + if (index !== -1) { + return uri.with({ authority: uri.authority.substr(0, index) }); + } + } + + return uri; } private async resolveCanonicalWorkspaceUris(): Promise { @@ -261,6 +272,11 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork // Update workspace trust this._trustState.isTrusted = trusted; + // Reset acceptsOutOfWorkspaceFiles + if (!trusted) { + this._trustState.acceptsOutOfWorkspaceFiles = false; + } + // Run workspace trust transition participants await this._trustTransitionManager.participate(trusted); @@ -288,6 +304,10 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork return { trusted: true, uri }; } + if (this.isTrustedVirtualResource(uri)) { + return { trusted: true, uri }; + } + let resultState = false; let maxLength = -1; @@ -312,6 +332,10 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork for (const uri of uris) { if (trusted) { + if (this.isTrustedVirtualResource(uri)) { + continue; + } + const foundItem = this._trustStateInfo.uriTrustInfo.find(trustInfo => this.uriIdentityService.extUri.isEqual(trustInfo.uri, uri)); if (!foundItem) { this._trustStateInfo.uriTrustInfo.push({ uri, trusted: true }); @@ -331,6 +355,10 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork } } + private isTrustedVirtualResource(uri: URI): boolean { + return isVirtualResource(uri) && uri.scheme !== 'vscode-vfs'; + } + //#endregion //#region public interface @@ -363,6 +391,21 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork return this._trustState.isTrusted ?? false; } + isWorkspaceTrustForced(): boolean { + // Remote - remote authority explicitly sets workspace trust + if (this.environmentService.remoteAuthority && this._remoteAuthority && this._remoteAuthority.options?.isTrusted !== undefined) { + return true; + } + + // All workspace uris are trusted automatically + const workspaceUris = this.getWorkspaceUris().filter(uri => !this.isTrustedVirtualResource(uri)); + if (workspaceUris.length === 0) { + return true; + } + + return false; + } + canSetParentFolderTrust(): boolean { const workspaceIdentifier = toWorkspaceIdentifier(this._canonicalWorkspace); return isSingleFolderWorkspaceIdentifier(workspaceIdentifier) && workspaceIdentifier.uri.scheme === Schemas.file; @@ -388,6 +431,12 @@ export class WorkspaceTrustManagementService extends Disposable implements IWork return true; } + // All workspace uris are trusted automatically + const workspaceUris = this.getWorkspaceUris().filter(uri => !this.isTrustedVirtualResource(uri)); + if (workspaceUris.length === 0) { + return false; + } + // Untrusted workspace if (!this.isWorkpaceTrusted()) { return true; @@ -698,10 +747,6 @@ class WorkspaceTrustState { set isTrusted(value: boolean | undefined) { this._mementoObject[this._isTrustedKey] = value; - if (!value) { - this._mementoObject[this._acceptsOutOfWorkspaceFilesKey] = value; - } - this._memento.saveMemento(); } } diff --git a/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts b/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts index 32ce1d95e9b..952ba35f3a5 100644 --- a/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspaces/electron-sandbox/workspaceEditingService.ts @@ -147,7 +147,7 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi await this.dialogService.show( Severity.Info, localize('workspaceOpenedMessage', "Unable to save workspace '{0}'", basename(path)), - [localize('ok', "OK")], + undefined, { detail: localize('workspaceOpenedDetail', "The workspace is already opened in another window. Please close that window first and then try again.") } diff --git a/src/vs/workbench/services/workspaces/test/common/testWorkspaceTrustService.ts b/src/vs/workbench/services/workspaces/test/common/testWorkspaceTrustService.ts index be549c1a0ba..0f48473c322 100644 --- a/src/vs/workbench/services/workspaces/test/common/testWorkspaceTrustService.ts +++ b/src/vs/workbench/services/workspaces/test/common/testWorkspaceTrustService.ts @@ -71,6 +71,10 @@ export class TestWorkspaceTrustManagementService implements IWorkspaceTrustManag return this.trusted; } + isWorkspaceTrustForced(): boolean { + return false; + } + get workspaceTrustEnabled(): boolean { return this.enabled; } diff --git a/src/vs/workbench/test/browser/workbenchTestServices.ts b/src/vs/workbench/test/browser/workbenchTestServices.ts index d45d398a7a1..f37d34b28b2 100644 --- a/src/vs/workbench/test/browser/workbenchTestServices.ts +++ b/src/vs/workbench/test/browser/workbenchTestServices.ts @@ -1619,7 +1619,7 @@ export class TestLocalTerminalService implements ILocalTerminalService { async attachToProcess(id: number): Promise { throw new Error('Method not implemented.'); } async listProcesses(): Promise { throw new Error('Method not implemented.'); } getDefaultSystemShell(osOverride?: OperatingSystem): Promise { throw new Error('Method not implemented.'); } - getProfiles(includeDetectedProfiles?: boolean): Promise { throw new Error('Method not implemented.'); } + getProfiles(isWorkspaceTrusted: boolean, includeDetectedProfiles?: boolean): Promise { throw new Error('Method not implemented.'); } getEnvironment(): Promise { throw new Error('Method not implemented.'); } getShellEnvironment(): Promise { throw new Error('Method not implemented.'); } getWslPath(original: string): Promise { throw new Error('Method not implemented.'); } diff --git a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts index f5b55c70feb..97e9723d40e 100644 --- a/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts +++ b/src/vs/workbench/test/electron-browser/api/extHostSearch.test.ts @@ -883,14 +883,16 @@ suite('ExtHostSearch', () => { }); test('basic sibling clause', async () => { - mockPFS.readdir = (_path: string): any => { - if (_path === rootFolderA.fsPath) { - return Promise.resolve([ - 'file1.js', - 'file1.ts' - ]); - } else { - return Promise.reject(new Error('Wrong path')); + (mockPFS as any).Promises = { + readdir: (_path: string): any => { + if (_path === rootFolderA.fsPath) { + return Promise.resolve([ + 'file1.js', + 'file1.ts' + ]); + } else { + return Promise.reject(new Error('Wrong path')); + } } }; @@ -926,21 +928,23 @@ suite('ExtHostSearch', () => { }); test('multiroot sibling clause', async () => { - mockPFS.readdir = (_path: string): any => { - if (_path === joinPath(rootFolderA, 'folder').fsPath) { - return Promise.resolve([ - 'fileA.scss', - 'fileA.css', - 'file2.css' - ]); - } else if (_path === rootFolderB.fsPath) { - return Promise.resolve([ - 'fileB.ts', - 'fileB.js', - 'file3.js' - ]); - } else { - return Promise.reject(new Error('Wrong path')); + (mockPFS as any).Promises = { + readdir: (_path: string): any => { + if (_path === joinPath(rootFolderA, 'folder').fsPath) { + return Promise.resolve([ + 'fileA.scss', + 'fileA.css', + 'file2.css' + ]); + } else if (_path === rootFolderB.fsPath) { + return Promise.resolve([ + 'fileB.ts', + 'fileB.js', + 'file3.js' + ]); + } else { + return Promise.reject(new Error('Wrong path')); + } } }; diff --git a/test/automation/src/application.ts b/test/automation/src/application.ts index aec773a75b0..d620cb05a45 100644 --- a/test/automation/src/application.ts +++ b/test/automation/src/application.ts @@ -71,7 +71,6 @@ export class Application { async start(expectWalkthroughPart = true): Promise { await this._start(); await this.code.waitForElement('.explorer-folders-view'); - await this.dismissTrustDialog(); // https://github.com/microsoft/vscode/issues/118748 // if (expectWalkthroughPart) { @@ -84,20 +83,6 @@ export class Application { await new Promise(c => setTimeout(c, 1000)); await this._start(options.workspaceOrFolder, options.extraArgs); await this.code.waitForElement('.explorer-folders-view'); - await this.dismissTrustDialog(); - } - - private async dismissTrustDialog(): Promise { - if (this.options.web) { - return; - } - - try { - // Dismiss workspace trust dialog, if found - await this.code.waitAndClick(`.monaco-workbench .dialog-buttons-row a:nth-child(1)`, 10, 10, 50); - } catch { - // there musn't be any trust dialog, maybe workspace is trusted? - } } private async _start(workspaceOrFolder = this.workspacePathOrFolder, extraArgs: string[] = []): Promise { diff --git a/test/automation/src/code.ts b/test/automation/src/code.ts index aab14d1f0d5..8333471680a 100644 --- a/test/automation/src/code.ts +++ b/test/automation/src/code.ts @@ -140,6 +140,7 @@ export async function spawn(options: SpawnOptions): Promise { '--disable-updates', '--disable-keytar', '--disable-crash-reporter', + '--disable-workspace-trust', `--extensions-dir=${options.extensionsPath}`, `--user-data-dir=${options.userDataDir}`, '--driver', handle diff --git a/test/smoke/src/main.ts b/test/smoke/src/main.ts index 13023eb50c8..b0e2ccf3fd6 100644 --- a/test/smoke/src/main.ts +++ b/test/smoke/src/main.ts @@ -207,8 +207,9 @@ async function setupRepository(): Promise { cp.spawnSync('git', ['clean', '-xdf'], { cwd: workspacePath }); } - console.log('*** Running yarn...'); - cp.execSync('yarn', { cwd: workspacePath, stdio: 'inherit' }); + // None of the test run the project + // console.log('*** Running yarn...'); + // cp.execSync('yarn', { cwd: workspacePath, stdio: 'inherit' }); } } diff --git a/test/unit/electron/index.js b/test/unit/electron/index.js index 7d85f302c7d..99bb64e92a4 100644 --- a/test/unit/electron/index.js +++ b/test/unit/electron/index.js @@ -168,7 +168,6 @@ app.on('ready', () => { nodeIntegration: true, contextIsolation: false, enableWebSQL: false, - enableRemoteModule: false, spellcheck: false, nativeWindowOpen: true, webviewTag: true