diff --git a/build/azure-pipelines/linux/product-build-linux.yml b/build/azure-pipelines/linux/product-build-linux.yml index 0d0b2608757..0d01ba8a608 100644 --- a/build/azure-pipelines/linux/product-build-linux.yml +++ b/build/azure-pipelines/linux/product-build-linux.yml @@ -100,7 +100,7 @@ steps: - script: | set -e - DISPLAY=:10 ./scripts/test.sh --build --tfs --no-sandbox "Unit Tests" + DISPLAY=:10 ./scripts/test.sh --build --tfs "Unit Tests" displayName: Run unit tests condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false')) diff --git a/build/gulpfile.editor.js b/build/gulpfile.editor.js index 7bbf246a8dc..44f38953adb 100644 --- a/build/gulpfile.editor.js +++ b/build/gulpfile.editor.js @@ -45,6 +45,7 @@ var editorResources = [ '!out-build/vs/base/browser/ui/splitview/**/*', '!out-build/vs/base/browser/ui/toolbar/**/*', '!out-build/vs/base/browser/ui/octiconLabel/**/*', + '!out-build/vs/base/browser/ui/codiconLabel/**/*', '!out-build/vs/workbench/**', '!**/test/**' ]; @@ -91,6 +92,7 @@ const extractEditorSrcTask = task.define('extract-editor-src', () => { ], redirects: { 'vs/base/browser/ui/octiconLabel/octiconLabel': 'vs/base/browser/ui/octiconLabel/octiconLabel.mock', + 'vs/base/browser/ui/codiconLabel/codiconLabel': 'vs/base/browser/ui/codiconLabel/codiconLabel.mock', }, shakeLevel: 2, // 0-Files, 1-InnerFile, 2-ClassMembers importIgnorePattern: /(^vs\/css!)|(promise-polyfill\/polyfill)/, diff --git a/build/gulpfile.hygiene.js b/build/gulpfile.hygiene.js index 5c60d5db221..30c98f95d06 100644 --- a/build/gulpfile.hygiene.js +++ b/build/gulpfile.hygiene.js @@ -72,6 +72,7 @@ const indentationFilter = [ // except multiple specific folders '!**/octicons/**', + '!**/codicon/**', '!**/fixtures/**', '!**/lib/**', '!extensions/**/out/**', diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 3e9e2276bfe..9a63fcec936 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -68,6 +68,7 @@ const vscodeResources = [ 'out-build/vs/base/node/languagePacks.js', 'out-build/vs/base/node/{stdForkStart.js,terminateProcess.sh,cpuUsage.sh,ps.sh}', 'out-build/vs/base/browser/ui/octiconLabel/octicons/**', + 'out-build/vs/base/browser/ui/codiconLabel/codicon/**', 'out-build/vs/workbench/browser/media/*-theme.css', 'out-build/vs/workbench/contrib/debug/**/*.json', 'out-build/vs/workbench/contrib/externalTerminal/**/*.scpt', diff --git a/extensions/cpp/test/colorize-results/test-23630_cpp.json b/extensions/cpp/test/colorize-results/test-23630_cpp.json index 08d81e6afff..26da4333e92 100644 --- a/extensions/cpp/test/colorize-results/test-23630_cpp.json +++ b/extensions/cpp/test/colorize-results/test-23630_cpp.json @@ -3,8 +3,8 @@ "c": "#", "t": "source.cpp keyword.control.directive.conditional.ifndef.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -14,8 +14,8 @@ "c": "ifndef", "t": "source.cpp keyword.control.directive.conditional.ifndef.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -36,8 +36,8 @@ "c": "_UCRT", "t": "source.cpp meta.preprocessor.conditional.cpp entity.name.function.preprocessor.cpp", "r": { - "dark_plus": "entity.name.function: #DCDCAA", - "light_plus": "entity.name.function: #795E26", + "dark_plus": "source.cpp entity.name.function.preprocessor: #C586C0", + "light_plus": "source.cpp entity.name.function.preprocessor: #AF00DB", "dark_vs": "meta.preprocessor: #569CD6", "light_vs": "meta.preprocessor: #0000FF", "hc_black": "entity.name.function: #DCDCAA" @@ -58,8 +58,8 @@ "c": "#", "t": "source.cpp meta.preprocessor.macro.cpp keyword.control.directive.define.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -69,8 +69,8 @@ "c": "define", "t": "source.cpp meta.preprocessor.macro.cpp keyword.control.directive.define.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -91,8 +91,8 @@ "c": "_UCRT", "t": "source.cpp meta.preprocessor.macro.cpp entity.name.function.preprocessor.cpp", "r": { - "dark_plus": "entity.name.function: #DCDCAA", - "light_plus": "entity.name.function: #795E26", + "dark_plus": "source.cpp entity.name.function.preprocessor: #C586C0", + "light_plus": "source.cpp entity.name.function.preprocessor: #AF00DB", "dark_vs": "meta.preprocessor: #569CD6", "light_vs": "meta.preprocessor: #0000FF", "hc_black": "entity.name.function: #DCDCAA" @@ -102,8 +102,8 @@ "c": "#", "t": "source.cpp keyword.control.directive.endif.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -113,8 +113,8 @@ "c": "endif", "t": "source.cpp keyword.control.directive.endif.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" diff --git a/extensions/cpp/test/colorize-results/test-23850_cpp.json b/extensions/cpp/test/colorize-results/test-23850_cpp.json index 4ba6b59dc9b..9a0466d8c29 100644 --- a/extensions/cpp/test/colorize-results/test-23850_cpp.json +++ b/extensions/cpp/test/colorize-results/test-23850_cpp.json @@ -3,8 +3,8 @@ "c": "#", "t": "source.cpp keyword.control.directive.conditional.ifndef.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -14,8 +14,8 @@ "c": "ifndef", "t": "source.cpp keyword.control.directive.conditional.ifndef.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -36,8 +36,8 @@ "c": "_UCRT", "t": "source.cpp meta.preprocessor.conditional.cpp entity.name.function.preprocessor.cpp", "r": { - "dark_plus": "entity.name.function: #DCDCAA", - "light_plus": "entity.name.function: #795E26", + "dark_plus": "source.cpp entity.name.function.preprocessor: #C586C0", + "light_plus": "source.cpp entity.name.function.preprocessor: #AF00DB", "dark_vs": "meta.preprocessor: #569CD6", "light_vs": "meta.preprocessor: #0000FF", "hc_black": "entity.name.function: #DCDCAA" @@ -47,8 +47,8 @@ "c": "#", "t": "source.cpp meta.preprocessor.macro.cpp keyword.control.directive.define.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -58,8 +58,8 @@ "c": "define", "t": "source.cpp meta.preprocessor.macro.cpp keyword.control.directive.define.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -80,8 +80,8 @@ "c": "_UCRT", "t": "source.cpp meta.preprocessor.macro.cpp entity.name.function.preprocessor.cpp", "r": { - "dark_plus": "entity.name.function: #DCDCAA", - "light_plus": "entity.name.function: #795E26", + "dark_plus": "source.cpp entity.name.function.preprocessor: #C586C0", + "light_plus": "source.cpp entity.name.function.preprocessor: #AF00DB", "dark_vs": "meta.preprocessor: #569CD6", "light_vs": "meta.preprocessor: #0000FF", "hc_black": "entity.name.function: #DCDCAA" @@ -91,8 +91,8 @@ "c": "#", "t": "source.cpp keyword.control.directive.endif.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -102,8 +102,8 @@ "c": "endif", "t": "source.cpp keyword.control.directive.endif.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" diff --git a/extensions/cpp/test/colorize-results/test-78769_cpp.json b/extensions/cpp/test/colorize-results/test-78769_cpp.json index e714cf5e26b..da4cbdd99b2 100644 --- a/extensions/cpp/test/colorize-results/test-78769_cpp.json +++ b/extensions/cpp/test/colorize-results/test-78769_cpp.json @@ -3,8 +3,8 @@ "c": "#", "t": "source.cpp meta.preprocessor.macro.cpp keyword.control.directive.define.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -14,8 +14,8 @@ "c": "define", "t": "source.cpp meta.preprocessor.macro.cpp keyword.control.directive.define.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -36,8 +36,8 @@ "c": "DOCTEST_IMPLEMENT_FIXTURE", "t": "source.cpp meta.preprocessor.macro.cpp entity.name.function.preprocessor.cpp", "r": { - "dark_plus": "entity.name.function: #DCDCAA", - "light_plus": "entity.name.function: #795E26", + "dark_plus": "source.cpp entity.name.function.preprocessor: #C586C0", + "light_plus": "source.cpp entity.name.function.preprocessor: #AF00DB", "dark_vs": "meta.preprocessor: #569CD6", "light_vs": "meta.preprocessor: #0000FF", "hc_black": "entity.name.function: #DCDCAA" @@ -58,8 +58,8 @@ "c": "der", "t": "source.cpp meta.preprocessor.macro.cpp variable.parameter.preprocessor.cpp", "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", + "dark_plus": "source.cpp variable.parameter: #7F7F7F", + "light_plus": "source.cpp variable.parameter: #808080", "dark_vs": "meta.preprocessor: #569CD6", "light_vs": "meta.preprocessor: #0000FF", "hc_black": "variable: #9CDCFE" @@ -91,8 +91,8 @@ "c": "base", "t": "source.cpp meta.preprocessor.macro.cpp variable.parameter.preprocessor.cpp", "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", + "dark_plus": "source.cpp variable.parameter: #7F7F7F", + "light_plus": "source.cpp variable.parameter: #808080", "dark_vs": "meta.preprocessor: #569CD6", "light_vs": "meta.preprocessor: #0000FF", "hc_black": "variable: #9CDCFE" @@ -124,8 +124,8 @@ "c": "func", "t": "source.cpp meta.preprocessor.macro.cpp variable.parameter.preprocessor.cpp", "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", + "dark_plus": "source.cpp variable.parameter: #7F7F7F", + "light_plus": "source.cpp variable.parameter: #808080", "dark_vs": "meta.preprocessor: #569CD6", "light_vs": "meta.preprocessor: #0000FF", "hc_black": "variable: #9CDCFE" @@ -157,8 +157,8 @@ "c": "decorators", "t": "source.cpp meta.preprocessor.macro.cpp variable.parameter.preprocessor.cpp", "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", + "dark_plus": "source.cpp variable.parameter: #7F7F7F", + "light_plus": "source.cpp variable.parameter: #808080", "dark_vs": "meta.preprocessor: #569CD6", "light_vs": "meta.preprocessor: #0000FF", "hc_black": "variable: #9CDCFE" diff --git a/extensions/cpp/test/colorize-results/test_cc.json b/extensions/cpp/test/colorize-results/test_cc.json index 902ef645164..6c3e21a12a3 100644 --- a/extensions/cpp/test/colorize-results/test_cc.json +++ b/extensions/cpp/test/colorize-results/test_cc.json @@ -3,8 +3,8 @@ "c": "#", "t": "source.cpp keyword.control.directive.conditional.if.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -14,8 +14,8 @@ "c": "if", "t": "source.cpp keyword.control.directive.conditional.if.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -36,8 +36,8 @@ "c": "B4G_DEBUG_CHECK", "t": "source.cpp meta.preprocessor.conditional.cpp entity.name.function.preprocessor.cpp", "r": { - "dark_plus": "entity.name.function: #DCDCAA", - "light_plus": "entity.name.function: #795E26", + "dark_plus": "source.cpp entity.name.function.preprocessor: #C586C0", + "light_plus": "source.cpp entity.name.function.preprocessor: #AF00DB", "dark_vs": "meta.preprocessor: #569CD6", "light_vs": "meta.preprocessor: #0000FF", "hc_black": "entity.name.function: #DCDCAA" @@ -652,8 +652,8 @@ "c": "#", "t": "source.cpp keyword.control.directive.endif.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -663,8 +663,8 @@ "c": "endif", "t": "source.cpp keyword.control.directive.endif.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -751,8 +751,8 @@ "c": "obj", "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp variable.parameter.cpp", "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", + "dark_plus": "source.cpp variable.parameter: #7F7F7F", + "light_plus": "source.cpp variable.parameter: #808080", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", "hc_black": "variable: #9CDCFE" @@ -1037,7 +1037,7 @@ "c": "x", "t": "source.cpp meta.function.definition.cpp meta.body.function.definition.cpp variable.other.property.cpp", "r": { - "dark_plus": "variable: #9CDCFE", + "dark_plus": "source.cpp variable.other.property: #DADADA", "light_plus": "variable: #001080", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", diff --git a/extensions/cpp/test/colorize-results/test_cpp.json b/extensions/cpp/test/colorize-results/test_cpp.json index f84d916afa3..9a6fe977b57 100644 --- a/extensions/cpp/test/colorize-results/test_cpp.json +++ b/extensions/cpp/test/colorize-results/test_cpp.json @@ -25,8 +25,8 @@ "c": "#", "t": "source.cpp meta.preprocessor.include.cpp keyword.control.directive.include.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -36,8 +36,8 @@ "c": "include", "t": "source.cpp meta.preprocessor.include.cpp keyword.control.directive.include.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -157,8 +157,8 @@ "c": "#", "t": "source.cpp meta.preprocessor.macro.cpp keyword.control.directive.define.cpp punctuation.definition.directive.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -168,8 +168,8 @@ "c": "define", "t": "source.cpp meta.preprocessor.macro.cpp keyword.control.directive.define.cpp", "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", + "dark_plus": "source.cpp keyword.control.directive: #9B9B9B", + "light_plus": "source.cpp keyword.control.directive: #808080", "dark_vs": "keyword.control: #569CD6", "light_vs": "keyword.control: #0000FF", "hc_black": "keyword.control: #C586C0" @@ -190,8 +190,8 @@ "c": "EXTERN_C", "t": "source.cpp meta.preprocessor.macro.cpp entity.name.function.preprocessor.cpp", "r": { - "dark_plus": "entity.name.function: #DCDCAA", - "light_plus": "entity.name.function: #795E26", + "dark_plus": "source.cpp entity.name.function.preprocessor: #C586C0", + "light_plus": "source.cpp entity.name.function.preprocessor: #AF00DB", "dark_vs": "meta.preprocessor: #569CD6", "light_vs": "meta.preprocessor: #0000FF", "hc_black": "entity.name.function: #DCDCAA" @@ -817,8 +817,8 @@ "c": "x", "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp variable.parameter.cpp", "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", + "dark_plus": "source.cpp variable.parameter: #7F7F7F", + "light_plus": "source.cpp variable.parameter: #808080", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", "hc_black": "variable: #9CDCFE" @@ -872,8 +872,8 @@ "c": "y", "t": "source.cpp meta.function.definition.cpp meta.head.function.definition.cpp meta.function.definition.parameters.cpp meta.parameter.cpp variable.parameter.cpp", "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", + "dark_plus": "source.cpp variable.parameter: #7F7F7F", + "light_plus": "source.cpp variable.parameter: #808080", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", "hc_black": "variable: #9CDCFE" diff --git a/extensions/theme-defaults/themes/dark_plus.json b/extensions/theme-defaults/themes/dark_plus.json index f12d3d7633e..3efcc124ff0 100644 --- a/extensions/theme-defaults/themes/dark_plus.json +++ b/extensions/theme-defaults/themes/dark_plus.json @@ -170,6 +170,73 @@ "settings": { "foreground": "#d7ba7d" } + }, + // Scopes that are potentially C++ only follow + { + "scope": "source.cpp entity.name", + "settings": { + "foreground": "#C8C8C8" + } + }, + { + "scope": "source.cpp keyword.control.directive", + "settings": { + "foreground": "#9B9B9B" + } + }, + { + "scope": "source.cpp entity.name.function.operator", + "settings": { + "foreground": "#B4B4B4" + } + }, + { + "scope": "source.cpp entity.name.function.preprocessor", + "settings": { + "foreground": "#C586C0" + } + }, + { + "scope": "source.cpp entity.name.label", + "settings": { + "foreground": "#C8C8C8" + } + }, + { + "scope": "source.cpp entity.name.operator.custom-literal", + "settings": { + "foreground": "#DADADA" + } + }, + { + "scope": "source.cpp entity.name.operator.custom-literal.number", + "settings": { + "foreground": "#B5CEA8" + } + }, + { + "scope": "source.cpp entity.name.operator.custom-literal.string", + "settings": { + "foreground": "#ce9178" + } + }, + { + "scope": "source.cpp variable.other.enummember", + "settings": { + "foreground": "#B8D7A3" + } + }, + { + "scope": "source.cpp variable.other.property", + "settings": { + "foreground": "#DADADA" + } + }, + { + "scope": "source.cpp variable.parameter", + "settings": { + "foreground": "#7F7F7F" + } } ] } diff --git a/extensions/theme-defaults/themes/light_plus.json b/extensions/theme-defaults/themes/light_plus.json index 3d30c5d17fb..e6a4c922357 100644 --- a/extensions/theme-defaults/themes/light_plus.json +++ b/extensions/theme-defaults/themes/light_plus.json @@ -170,7 +170,55 @@ "settings": { "foreground": "#ff0000" } + }, + // Scopes that are potentially C++ only follow + { + "scope": "source.cpp entity.name", + "settings": { + "foreground": "#000000" + } + }, + { + "scope": "source.cpp keyword.control.directive", + "settings": { + "foreground": "#808080" + } + }, + { + "scope": "source.cpp entity.name.function.operator", + "settings": { + "foreground": "#008080" + } + }, + { + "scope": "source.cpp entity.name.function.preprocessor", + "settings": { + "foreground": "#AF00DB" + } + }, + { + "scope": "source.cpp entity.name.label", + "settings": { + "foreground": "#000000" + } + }, + { + "scope": "source.cpp entity.name.operator.custom-literal.string", + "settings": { + "foreground": "#0451a5" + } + }, + { + "scope": "source.cpp variable.other.enummember", + "settings": { + "foreground": "#2F4F4F" + } + }, + { + "scope": "source.cpp variable.parameter", + "settings": { + "foreground": "#808080" + } } - ] } diff --git a/package.json b/package.json index 673662926a8..a03e9dd1f40 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.39.0", - "distro": "e892717dc1ae3cea3824697bc2c28d64f8476f93", + "distro": "8ec29a9878d2ca77f73efc02f382f177d200fbb9", "author": { "name": "Microsoft Corporation" }, diff --git a/resources/linux/code-url-handler.desktop b/resources/linux/code-url-handler.desktop index 7106e0e0969..b85525fbd04 100644 --- a/resources/linux/code-url-handler.desktop +++ b/resources/linux/code-url-handler.desktop @@ -2,7 +2,7 @@ Name=@@NAME_LONG@@ - URL Handler Comment=Code Editing. Redefined. GenericName=Text Editor -Exec=@@EXEC@@ --open-url %U +Exec=@@EXEC@@ --no-sandbox --open-url %U Icon=@@ICON@@ Type=Application NoDisplay=true diff --git a/resources/linux/code.desktop b/resources/linux/code.desktop index 1273bb2db7c..b975e1094a2 100644 --- a/resources/linux/code.desktop +++ b/resources/linux/code.desktop @@ -2,7 +2,7 @@ Name=@@NAME_LONG@@ Comment=Code Editing. Redefined. GenericName=Text Editor -Exec=@@EXEC@@ --unity-launch %F +Exec=@@EXEC@@ --no-sandbox --unity-launch %F Icon=@@ICON@@ Type=Application StartupNotify=false @@ -14,5 +14,5 @@ Keywords=vscode; [Desktop Action new-empty-window] Name=New Empty Window -Exec=@@EXEC@@ --new-window %F +Exec=@@EXEC@@ --no-sandbox --new-window %F Icon=@@ICON@@ diff --git a/resources/win32/bin/code.sh b/resources/win32/bin/code.sh index f925851fb95..43aaa25a96e 100644 --- a/resources/win32/bin/code.sh +++ b/resources/win32/bin/code.sh @@ -27,15 +27,15 @@ if grep -qi Microsoft /proc/version; then WSL_EXT_ID="ms-vscode-remote.remote-wsl" if [ $WSL_BUILD -ge 41955 -a $WSL_BUILD -lt 41959 ]; then - # WSL2 in workaround for https://github.com/microsoft/WSL/issues/4337 + # WSL2 workaround for https://github.com/microsoft/WSL/issues/4337 CWD="$(pwd)" cd "$VSCODE_PATH" - cmd.exe /C ".\\bin\\$APP_NAME.cmd --locate-extension $WSL_EXT_ID >remote-wsl-loc.txt" - WSL_EXT_WLOC="$(cat ./remote-wsl-loc.txt)" - rm remote-wsl-loc.txt + cmd.exe /C ".\\bin\\$APP_NAME.cmd --locate-extension $WSL_EXT_ID >%TEMP%\\remote-wsl-loc.txt" + WSL_EXT_WLOC=$(cmd.exe /C type %TEMP%\\remote-wsl-loc.txt) cd "$CWD" else - WSL_EXT_WLOC=$(ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" --locate-extension $WSL_EXT_ID) + ELECTRON_RUN_AS_NODE=1 "$ELECTRON" "$CLI" --locate-extension $WSL_EXT_ID >/tmp/remote-wsl-loc.txt + WSL_EXT_WLOC=$(cat /tmp/remote-wsl-loc.txt) fi if [ -n "$WSL_EXT_WLOC" ]; then # replace \r\n with \n in WSL_EXT_WLOC diff --git a/scripts/test-integration.sh b/scripts/test-integration.sh index 143e2ff4eb4..2c3a571ffc7 100755 --- a/scripts/test-integration.sh +++ b/scripts/test-integration.sh @@ -8,7 +8,7 @@ if [[ "$OSTYPE" == "darwin"* ]]; then else ROOT=$(dirname $(dirname $(readlink -f $0))) VSCODEUSERDATADIR=`mktemp -d 2>/dev/null` - LINUX_NO_SANDBOX="--no-sandbox" # workaround Electron 6 issue on Linux when running tests in container + LINUX_NO_SANDBOX="--no-sandbox" # Electron 6 introduces a chrome-sandbox that requires root to run. This can fail. Disable sandbox via --no-sandbox. fi cd $ROOT @@ -34,7 +34,7 @@ else fi # Integration tests in AMD -./scripts/test.sh $LINUX_NO_SANDBOX --runGlob **/*.integrationTest.js "$@" +./scripts/test.sh --runGlob **/*.integrationTest.js "$@" # Tests in the extension host "$INTEGRATION_TEST_ELECTRON_PATH" $LINUX_NO_SANDBOX $ROOT/extensions/vscode-api-tests/testWorkspace --extensionDevelopmentPath=$ROOT/extensions/vscode-api-tests --extensionTestsPath=$ROOT/extensions/vscode-api-tests/out/singlefolder-tests --disable-telemetry --disable-crash-reporter --disable-updates --disable-extensions --skip-getting-started --disable-inspect --user-data-dir=$VSCODEUSERDATADIR diff --git a/scripts/test.sh b/scripts/test.sh index e1ed5aa65c7..630af4e53e5 100755 --- a/scripts/test.sh +++ b/scripts/test.sh @@ -34,5 +34,5 @@ else cd $ROOT ; \ ELECTRON_ENABLE_LOGGING=1 \ "$CODE" \ - test/electron/index.js "$@" + test/electron/index.js --no-sandbox "$@" # Electron 6 introduces a chrome-sandbox that requires root to run. This can fail. Disable sandbox via --no-sandbox. fi diff --git a/src/vs/base/browser/ui/actionbar/actionbar.css b/src/vs/base/browser/ui/actionbar/actionbar.css index 25795ddb631..f2eff2c7f66 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.css +++ b/src/vs/base/browser/ui/actionbar/actionbar.css @@ -40,7 +40,8 @@ transform: scale(1.272019649, 1.272019649); /* 1.272019649 = √φ */ } -.monaco-action-bar .action-item .icon { +.monaco-action-bar .action-item .icon, +.monaco-action-bar .action-item .codicon { display: inline-block; } @@ -95,4 +96,4 @@ display: flex; align-items: center; justify-content: center; -} \ No newline at end of file +} diff --git a/src/vs/base/browser/ui/actionbar/actionbar.ts b/src/vs/base/browser/ui/actionbar/actionbar.ts index 538f7fa0dbe..38ec0a60a51 100644 --- a/src/vs/base/browser/ui/actionbar/actionbar.ts +++ b/src/vs/base/browser/ui/actionbar/actionbar.ts @@ -311,14 +311,14 @@ export class ActionViewItem extends BaseActionViewItem { if (this.options.icon) { this.cssClass = this.getAction().class; - DOM.addClass(this.label, 'icon'); + DOM.addClass(this.label, 'codicon'); if (this.cssClass) { DOM.addClasses(this.label, this.cssClass); } this.updateEnabled(); } else { - DOM.removeClass(this.label, 'icon'); + DOM.removeClass(this.label, 'codicon'); } } diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon-animations.css b/src/vs/base/browser/ui/codiconLabel/codicon/codicon-animations.css new file mode 100644 index 00000000000..86c223674ec --- /dev/null +++ b/src/vs/base/browser/ui/codiconLabel/codicon/codicon-animations.css @@ -0,0 +1,14 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +@keyframes codicon-spin { + 100% { + transform:rotate(360deg); + } +} + +.codicon-animation-spin { + animation: octicon-spin 1.5s linear infinite; +} diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css new file mode 100644 index 00000000000..417afa0de5d --- /dev/null +++ b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.css @@ -0,0 +1,354 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +@font-face { + font-family: "codicon"; + src: url("./codicon.ttf?e042d2dda15ef7b36b910e3edf539f26") format("truetype"); +} + +.codicon[class*='codicon-'] { + font: normal normal normal 16px/1 codicon; + display: inline-block; + text-decoration: none; + text-rendering: auto; + text-align: center; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + + +.codicon-add:before { content: "\ea60" } +.codicon-plus:before { content: "\ea60" } +.codicon-gist-new:before { content: "\ea60" } +.codicon-repo-create:before { content: "\ea60" } +.codicon-lightbulb:before { content: "\ea61" } +.codicon-light-bulb:before { content: "\ea61" } +.codicon-repo:before { content: "\ea62" } +.codicon-repo-delete:before { content: "\ea62" } +.codicon-gist-fork:before { content: "\ea63" } +.codicon-repo-forked:before { content: "\ea63" } +.codicon-git-pull-request:before { content: "\ea64" } +.codicon-git-pull-request-abandoned:before { content: "\ea64" } +.codicon-record-keys:before { content: "\ea65" } +.codicon-keyboard:before { content: "\ea65" } +.codicon-tag:before { content: "\ea66" } +.codicon-tag-add:before { content: "\ea66" } +.codicon-tag-remove:before { content: "\ea66" } +.codicon-person:before { content: "\ea67" } +.codicon-person-add:before { content: "\ea67" } +.codicon-person-follow:before { content: "\ea67" } +.codicon-person-outline:before { content: "\ea67" } +.codicon-person-filled:before { content: "\ea67" } +.codicon-git-branch:before { content: "\ea68" } +.codicon-git-branch-create:before { content: "\ea68" } +.codicon-git-branch-delete:before { content: "\ea68" } +.codicon-source-control:before { content: "\ea68" } +.codicon-mirror:before { content: "\ea69" } +.codicon-mirror-public:before { content: "\ea69" } +.codicon-star:before { content: "\ea6a" } +.codicon-star-add:before { content: "\ea6a" } +.codicon-star-delete:before { content: "\ea6a" } +.codicon-comment:before { content: "\ea6b" } +.codicon-comment-add:before { content: "\ea6b" } +.codicon-alert:before { content: "\ea6c" } +.codicon-warning:before { content: "\ea6c" } +.codicon-search:before { content: "\ea6d" } +.codicon-search-save:before { content: "\ea6d" } +.codicon-log-out:before { content: "\ea6e" } +.codicon-sign-out:before { content: "\ea6e" } +.codicon-log-in:before { content: "\ea6f" } +.codicon-sign-in:before { content: "\ea6f" } +.codicon-eye:before { content: "\ea70" } +.codicon-eye-unwatch:before { content: "\ea70" } +.codicon-eye-watch:before { content: "\ea70" } +.codicon-circle-filled:before { content: "\ea71" } +.codicon-primitive-dot:before { content: "\ea71" } +.codicon-stop:before { content: "\ea72" } +.codicon-primitive-square:before { content: "\ea72" } +.codicon-edit:before { content: "\ea73" } +.codicon-pencil:before { content: "\ea73" } +.codicon-info:before { content: "\ea74" } +.codicon-issue-opened:before { content: "\ea74" } +.codicon-gist-private:before { content: "\ea75" } +.codicon-git-fork-private:before { content: "\ea75" } +.codicon-lock:before { content: "\ea75" } +.codicon-mirror-private:before { content: "\ea75" } +.codicon-close:before { content: "\ea76" } +.codicon-remove-close:before { content: "\ea76" } +.codicon-x:before { content: "\ea76" } +.codicon-repo-sync:before { content: "\ea77" } +.codicon-sync:before { content: "\ea77" } +.codicon-clone:before { content: "\ea78" } +.codicon-desktop-download:before { content: "\ea78" } +.codicon-beaker:before { content: "\ea79" } +.codicon-microscope:before { content: "\ea79" } +.codicon-vm:before { content: "\ea7a" } +.codicon-device-desktop:before { content: "\ea7a" } +.codicon-file:before { content: "\ea7b" } +.codicon-file-text:before { content: "\ea7b" } +.codicon-more:before { content: "\ea7c" } +.codicon-kebab-horizontal:before { content: "\ea7c" } +.codicon-mail-reply:before { content: "\ea7d" } +.codicon-reply:before { content: "\ea7d" } +.codicon-organization:before { content: "\ea7e" } +.codicon-organization-filled:before { content: "\ea7e" } +.codicon-organization-outline:before { content: "\ea7e" } +.codicon-new-file:before { content: "\ea7f" } +.codicon-file-add:before { content: "\ea7f" } +.codicon-new-folder:before { content: "\ea80" } +.codicon-file-directory-create:before { content: "\ea80" } +.codicon-Vector:before { content: "\f101" } +.codicon-activate-breakpoints:before { content: "\f102" } +.codicon-archive:before { content: "\f103" } +.codicon-array:before { content: "\f104" } +.codicon-arrow-both:before { content: "\f105" } +.codicon-arrow-down:before { content: "\f106" } +.codicon-arrow-left:before { content: "\f107" } +.codicon-arrow-right:before { content: "\f108" } +.codicon-arrow-small-down:before { content: "\f109" } +.codicon-arrow-small-left:before { content: "\f10a" } +.codicon-arrow-small-right:before { content: "\f10b" } +.codicon-arrow-small-up:before { content: "\f10c" } +.codicon-arrow-up:before { content: "\f10d" } +.codicon-bell:before { content: "\f10e" } +.codicon-bold:before { content: "\f10f" } +.codicon-book:before { content: "\f110" } +.codicon-bookmark:before { content: "\f111" } +.codicon-boolean:before { content: "\f112" } +.codicon-breakpoint-conditional-unverified:before { content: "\f113" } +.codicon-breakpoint-conditional:before { content: "\f114" } +.codicon-breakpoint-data-unverified:before { content: "\f115" } +.codicon-breakpoint-data:before { content: "\f116" } +.codicon-breakpoint-log-unverified:before { content: "\f117" } +.codicon-breakpoint-log:before { content: "\f118" } +.codicon-briefcase:before { content: "\f119" } +.codicon-broadcast:before { content: "\f11a" } +.codicon-browser:before { content: "\f11b" } +.codicon-bug:before { content: "\f11c" } +.codicon-calendar:before { content: "\f11d" } +.codicon-case-sensitive:before { content: "\f11e" } +.codicon-check:before { content: "\f11f" } +.codicon-checklist:before { content: "\f120" } +.codicon-chevron-down:before { content: "\f121" } +.codicon-chevron-left:before { content: "\f122" } +.codicon-chevron-right:before { content: "\f123" } +.codicon-chevron-up:before { content: "\f124" } +.codicon-circle-outline:before { content: "\f125" } +.codicon-circle-slash:before { content: "\f126" } +.codicon-circuit-board:before { content: "\f127" } +.codicon-class:before { content: "\f128" } +.codicon-clear-all:before { content: "\f129" } +.codicon-clippy:before { content: "\f12a" } +.codicon-close-all:before { content: "\f12b" } +.codicon-cloud-download:before { content: "\f12c" } +.codicon-cloud-upload:before { content: "\f12d" } +.codicon-code:before { content: "\f12e" } +.codicon-collapse-all:before { content: "\f12f" } +.codicon-color-mode:before { content: "\f130" } +.codicon-color:before { content: "\f131" } +.codicon-comment-discussion:before { content: "\f132" } +.codicon-compare-changes:before { content: "\f133" } +.codicon-console:before { content: "\f134" } +.codicon-constant:before { content: "\f135" } +.codicon-continue:before { content: "\f136" } +.codicon-credit-card:before { content: "\f137" } +.codicon-current-and-breakpoint:before { content: "\f138" } +.codicon-current:before { content: "\f139" } +.codicon-dash:before { content: "\f13a" } +.codicon-dashboard:before { content: "\f13b" } +.codicon-database:before { content: "\f13c" } +.codicon-debug:before { content: "\f13d" } +.codicon-device-camera-video:before { content: "\f13e" } +.codicon-device-camera:before { content: "\f13f" } +.codicon-device-mobile:before { content: "\f140" } +.codicon-diff-added:before { content: "\f141" } +.codicon-diff-ignored:before { content: "\f142" } +.codicon-diff-modified:before { content: "\f143" } +.codicon-diff-removed:before { content: "\f144" } +.codicon-diff-renamed:before { content: "\f145" } +.codicon-diff:before { content: "\f146" } +.codicon-discard:before { content: "\f147" } +.codicon-disconnect-:before { content: "\f148" } +.codicon-editor-layout:before { content: "\f149" } +.codicon-ellipsis:before { content: "\f14a" } +.codicon-empty-window:before { content: "\f14b" } +.codicon-enumerator-member:before { content: "\f14c" } +.codicon-enumerator:before { content: "\f14d" } +.codicon-error:before { content: "\f14e" } +.codicon-event:before { content: "\f14f" } +.codicon-exclude:before { content: "\f150" } +.codicon-extensions:before { content: "\f151" } +.codicon-eye-closed:before { content: "\f152" } +.codicon-field:before { content: "\f153" } +.codicon-file-binary:before { content: "\f154" } +.codicon-file-code:before { content: "\f155" } +.codicon-file-media:before { content: "\f156" } +.codicon-file-pdf:before { content: "\f157" } +.codicon-file-submodule:before { content: "\f158" } +.codicon-file-symlink-directory:before { content: "\f159" } +.codicon-file-symlink-file:before { content: "\f15a" } +.codicon-file-zip:before { content: "\f15b" } +.codicon-files:before { content: "\f15c" } +.codicon-filter:before { content: "\f15d" } +.codicon-flame:before { content: "\f15e" } +.codicon-fold-down:before { content: "\f15f" } +.codicon-fold-up:before { content: "\f160" } +.codicon-fold:before { content: "\f161" } +.codicon-folder-active:before { content: "\f162" } +.codicon-folder-opened:before { content: "\f163" } +.codicon-folder:before { content: "\f164" } +.codicon-gift:before { content: "\f165" } +.codicon-gist-secret:before { content: "\f166" } +.codicon-gist:before { content: "\f167" } +.codicon-git-commit:before { content: "\f168" } +.codicon-git-compare:before { content: "\f169" } +.codicon-git-merge:before { content: "\f16a" } +.codicon-github-action:before { content: "\f16b" } +.codicon-github-alt:before { content: "\f16c" } +.codicon-github:before { content: "\f16d" } +.codicon-globe:before { content: "\f16e" } +.codicon-go-to-file:before { content: "\f16f" } +.codicon-grabber:before { content: "\f170" } +.codicon-graph:before { content: "\f171" } +.codicon-gripper:before { content: "\f172" } +.codicon-heart:before { content: "\f173" } +.codicon-history:before { content: "\f174" } +.codicon-home:before { content: "\f175" } +.codicon-horizontal-rule:before { content: "\f176" } +.codicon-hubot:before { content: "\f177" } +.codicon-inbox:before { content: "\f178" } +.codicon-interface:before { content: "\f179" } +.codicon-issue-closed:before { content: "\f17a" } +.codicon-issue-reopened:before { content: "\f17b" } +.codicon-issues:before { content: "\f17c" } +.codicon-italic:before { content: "\f17d" } +.codicon-jersey:before { content: "\f17e" } +.codicon-json:before { content: "\f17f" } +.codicon-kebab-vertical:before { content: "\f180" } +.codicon-key:before { content: "\f181" } +.codicon-keyword:before { content: "\f182" } +.codicon-law:before { content: "\f183" } +.codicon-lightbulb-autofix:before { content: "\f184" } +.codicon-link-external:before { content: "\f185" } +.codicon-link:before { content: "\f186" } +.codicon-list-ordered:before { content: "\f187" } +.codicon-list-unordered:before { content: "\f188" } +.codicon-live-share:before { content: "\f189" } +.codicon-loading:before { content: "\f18a" } +.codicon-location:before { content: "\f18b" } +.codicon-mail-read:before { content: "\f18c" } +.codicon-mail:before { content: "\f18d" } +.codicon-markdown:before { content: "\f18e" } +.codicon-megaphone:before { content: "\f18f" } +.codicon-mention:before { content: "\f190" } +.codicon-method:before { content: "\f191" } +.codicon-milestone:before { content: "\f192" } +.codicon-misc:before { content: "\f193" } +.codicon-mortar-board:before { content: "\f194" } +.codicon-move:before { content: "\f195" } +.codicon-multiple-windows:before { content: "\f196" } +.codicon-mute:before { content: "\f197" } +.codicon-namespace:before { content: "\f198" } +.codicon-no-newline:before { content: "\f199" } +.codicon-note:before { content: "\f19a" } +.codicon-numeric:before { content: "\f19b" } +.codicon-octoface:before { content: "\f19c" } +.codicon-open-preview:before { content: "\f19d" } +.codicon-operator:before { content: "\f19e" } +.codicon-package:before { content: "\f19f" } +.codicon-paintcan:before { content: "\f1a0" } +.codicon-parameter:before { content: "\f1a1" } +.codicon-pause:before { content: "\f1a2" } +.codicon-pin:before { content: "\f1a3" } +.codicon-play:before { content: "\f1a4" } +.codicon-plug:before { content: "\f1a5" } +.codicon-preserve-case:before { content: "\f1a6" } +.codicon-preview:before { content: "\f1a7" } +.codicon-project:before { content: "\f1a8" } +.codicon-property:before { content: "\f1a9" } +.codicon-pulse:before { content: "\f1aa" } +.codicon-question:before { content: "\f1ab" } +.codicon-quote:before { content: "\f1ac" } +.codicon-radio-tower:before { content: "\f1ad" } +.codicon-reactions:before { content: "\f1ae" } +.codicon-references:before { content: "\f1af" } +.codicon-refresh:before { content: "\f1b0" } +.codicon-regex:before { content: "\f1b1" } +.codicon-remote:before { content: "\f1b2" } +.codicon-remove:before { content: "\f1b3" } +.codicon-replace-all:before { content: "\f1b4" } +.codicon-replace:before { content: "\f1b5" } +.codicon-repo-clone:before { content: "\f1b6" } +.codicon-repo-force-push:before { content: "\f1b7" } +.codicon-repo-pull:before { content: "\f1b8" } +.codicon-repo-push:before { content: "\f1b9" } +.codicon-report:before { content: "\f1ba" } +.codicon-request-changes:before { content: "\f1bb" } +.codicon-restart:before { content: "\f1bc" } +.codicon-rocket:before { content: "\f1bd" } +.codicon-root-folder-opened:before { content: "\f1be" } +.codicon-root-folder:before { content: "\f1bf" } +.codicon-rss:before { content: "\f1c0" } +.codicon-ruby:before { content: "\f1c1" } +.codicon-ruler:before { content: "\f1c2" } +.codicon-save-all:before { content: "\f1c3" } +.codicon-save-as:before { content: "\f1c4" } +.codicon-save:before { content: "\f1c5" } +.codicon-screen-full:before { content: "\f1c6" } +.codicon-screen-normal:before { content: "\f1c7" } +.codicon-search-stop:before { content: "\f1c8" } +.codicon-selection:before { content: "\f1c9" } +.codicon-server:before { content: "\f1ca" } +.codicon-settings:before { content: "\f1cb" } +.codicon-shield:before { content: "\f1cc" } +.codicon-smiley:before { content: "\f1cd" } +.codicon-snippet:before { content: "\f1ce" } +.codicon-sort-precedence:before { content: "\f1cf" } +.codicon-split-horizontal:before { content: "\f1d0" } +.codicon-split-vertical:before { content: "\f1d1" } +.codicon-squirrel:before { content: "\f1d2" } +.codicon-star-empty:before { content: "\f1d3" } +.codicon-star-full:before { content: "\f1d4" } +.codicon-star-half:before { content: "\f1d5" } +.codicon-start:before { content: "\f1d6" } +.codicon-step-into:before { content: "\f1d7" } +.codicon-step-out:before { content: "\f1d8" } +.codicon-step-over:before { content: "\f1d9" } +.codicon-string:before { content: "\f1da" } +.codicon-structure:before { content: "\f1db" } +.codicon-tasklist:before { content: "\f1dc" } +.codicon-telescope:before { content: "\f1dd" } +.codicon-text-size:before { content: "\f1de" } +.codicon-three-bars:before { content: "\f1df" } +.codicon-thumbsdown:before { content: "\f1e0" } +.codicon-thumbsup:before { content: "\f1e1" } +.codicon-tools:before { content: "\f1e2" } +.codicon-trash:before { content: "\f1e3" } +.codicon-triangle-down:before { content: "\f1e4" } +.codicon-triangle-left:before { content: "\f1e5" } +.codicon-triangle-right:before { content: "\f1e6" } +.codicon-triangle-up:before { content: "\f1e7" } +.codicon-twitter:before { content: "\f1e8" } +.codicon-unfold:before { content: "\f1e9" } +.codicon-unlock:before { content: "\f1ea" } +.codicon-unmute:before { content: "\f1eb" } +.codicon-unverified:before { content: "\f1ec" } +.codicon-variable:before { content: "\f1ed" } +.codicon-verified:before { content: "\f1ee" } +.codicon-versions:before { content: "\f1ef" } +.codicon-vm-active:before { content: "\f1f0" } +.codicon-vm-outline:before { content: "\f1f1" } +.codicon-vm-running:before { content: "\f1f2" } +.codicon-watch:before { content: "\f1f3" } +.codicon-whitespace:before { content: "\f1f4" } +.codicon-whole-word:before { content: "\f1f5" } +.codicon-window:before { content: "\f1f6" } +.codicon-word-wrap:before { content: "\f1f7" } +.codicon-zoom-in:before { content: "\f1f8" } +.codicon-zoom-out:before { content: "\f1f9" } diff --git a/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf new file mode 100644 index 00000000000..688b9ffd0bd Binary files /dev/null and b/src/vs/base/browser/ui/codiconLabel/codicon/codicon.ttf differ diff --git a/src/vs/base/browser/ui/codiconLabel/codiconLabel.mock.ts b/src/vs/base/browser/ui/codiconLabel/codiconLabel.mock.ts new file mode 100644 index 00000000000..37862376c2c --- /dev/null +++ b/src/vs/base/browser/ui/codiconLabel/codiconLabel.mock.ts @@ -0,0 +1,24 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { escape } from 'vs/base/common/strings'; + +export function renderCodicons(text: string): string { + return escape(text); +} + +export class CodiconLabel { + + private _container: HTMLElement; + + constructor(container: HTMLElement) { + this._container = container; + } + + set text(text: string) { + this._container.innerHTML = renderCodicons(text || ''); + } + +} diff --git a/src/vs/base/browser/ui/codiconLabel/codiconLabel.ts b/src/vs/base/browser/ui/codiconLabel/codiconLabel.ts new file mode 100644 index 00000000000..42d38948e88 --- /dev/null +++ b/src/vs/base/browser/ui/codiconLabel/codiconLabel.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import 'vs/css!./codicon/codicon'; +import 'vs/css!./codicon/codicon-animations'; +import { escape } from 'vs/base/common/strings'; + +function expand(text: string): string { + return text.replace(/\$\(((.+?)(~(.*?))?)\)/g, (_match, _g1, name, _g3, animation) => { + return ``; + }); +} + +export function renderCodicons(label: string): string { + return expand(escape(label)); +} + +export class CodiconLabel { + + constructor( + private readonly _container: HTMLElement + ) { } + + set text(text: string) { + this._container.innerHTML = renderCodicons(text || ''); + } + + set title(title: string) { + this._container.title = title; + } +} diff --git a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts index 63c805b4237..e7dcf431b0d 100644 --- a/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts +++ b/src/vs/base/browser/ui/highlightedlabel/highlightedLabel.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as objects from 'vs/base/common/objects'; -import { renderOcticons } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; +import { renderCodicons } from 'vs/base/browser/ui/codiconLabel/codiconLabel'; import { escape } from 'vs/base/common/strings'; export interface IHighlight { @@ -65,13 +65,13 @@ export class HighlightedLabel { if (pos < highlight.start) { htmlContent += ''; const substring = this.text.substring(pos, highlight.start); - htmlContent += this.supportOcticons ? renderOcticons(substring) : escape(substring); + htmlContent += this.supportOcticons ? renderCodicons(substring) : escape(substring); htmlContent += ''; pos = highlight.end; } htmlContent += ''; const substring = this.text.substring(highlight.start, highlight.end); - htmlContent += this.supportOcticons ? renderOcticons(substring) : escape(substring); + htmlContent += this.supportOcticons ? renderCodicons(substring) : escape(substring); htmlContent += ''; pos = highlight.end; } @@ -79,7 +79,7 @@ export class HighlightedLabel { if (pos < this.text.length) { htmlContent += ''; const substring = this.text.substring(pos); - htmlContent += this.supportOcticons ? renderOcticons(substring) : escape(substring); + htmlContent += this.supportOcticons ? renderCodicons(substring) : escape(substring); htmlContent += ''; } diff --git a/src/vs/base/browser/ui/inputbox/inputBox.css b/src/vs/base/browser/ui/inputbox/inputBox.css index ae4cd429a07..939d0cb5f01 100644 --- a/src/vs/base/browser/ui/inputbox/inputBox.css +++ b/src/vs/base/browser/ui/inputbox/inputBox.css @@ -123,7 +123,7 @@ margin-left: 2px; } -.monaco-inputbox .monaco-action-bar .action-item .icon { +.monaco-inputbox .monaco-action-bar .action-item .codicon { background-repeat: no-repeat; width: 16px; height: 16px; diff --git a/src/vs/base/browser/ui/splitview/panelview.css b/src/vs/base/browser/ui/splitview/panelview.css index d4def58461f..194ef683029 100644 --- a/src/vs/base/browser/ui/splitview/panelview.css +++ b/src/vs/base/browser/ui/splitview/panelview.css @@ -68,7 +68,8 @@ } /* TODO: actions should be part of the panel, but they aren't yet */ -.monaco-panel-view .panel > .panel-header > .actions .action-label.icon { +.monaco-panel-view .panel > .panel-header > .actions .action-label.icon, +.monaco-panel-view .panel > .panel-header > .actions .action-label.codicon { width: 28px; height: 22px; background-size: 16px; diff --git a/src/vs/base/parts/ipc/common/ipc.ts b/src/vs/base/parts/ipc/common/ipc.ts index e0205092202..392cfeaeae7 100644 --- a/src/vs/base/parts/ipc/common/ipc.ts +++ b/src/vs/base/parts/ipc/common/ipc.ts @@ -32,7 +32,6 @@ export interface IServerChannel { listen(ctx: TContext, event: string, arg?: any): Event; } - export const enum RequestType { Promise = 100, PromiseCancel = 101, diff --git a/src/vs/base/parts/tree/browser/collapse-all-dark.svg b/src/vs/base/parts/tree/browser/collapse-all-dark.svg deleted file mode 100644 index 4862c55dbeb..00000000000 --- a/src/vs/base/parts/tree/browser/collapse-all-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/base/parts/tree/browser/collapse-all-hc.svg b/src/vs/base/parts/tree/browser/collapse-all-hc.svg deleted file mode 100644 index 05f920b29b6..00000000000 --- a/src/vs/base/parts/tree/browser/collapse-all-hc.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/base/parts/tree/browser/collapse-all-light.svg b/src/vs/base/parts/tree/browser/collapse-all-light.svg deleted file mode 100644 index 6359b42e623..00000000000 --- a/src/vs/base/parts/tree/browser/collapse-all-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/base/parts/tree/browser/tree.css b/src/vs/base/parts/tree/browser/tree.css index 2a3cb47ddbb..6eb5f9b7aff 100644 --- a/src/vs/base/parts/tree/browser/tree.css +++ b/src/vs/base/parts/tree/browser/tree.css @@ -110,15 +110,3 @@ .hc-black .monaco-tree .monaco-tree-rows > .monaco-tree-row.has-children.loading > .content:before { background-image: url('loading-hc.svg'); } - -.monaco-tree-action.collapse-all { - background: url('collapse-all-light.svg') center center no-repeat; -} - -.vs-dark .monaco-tree-action.collapse-all { - background: url('collapse-all-dark.svg') center center no-repeat; -} - -.hc-black .monaco-tree-action.collapse-all { - background: url('collapse-all-hc.svg') center center no-repeat; -} diff --git a/src/vs/code/electron-browser/issue/issueReporterMain.ts b/src/vs/code/electron-browser/issue/issueReporterMain.ts index a0f146faa19..6d0693d7657 100644 --- a/src/vs/code/electron-browser/issue/issueReporterMain.ts +++ b/src/vs/code/electron-browser/issue/issueReporterMain.ts @@ -32,7 +32,7 @@ import { EnvironmentService } from 'vs/platform/environment/node/environmentServ import { IssueReporterModel, IssueReporterData as IssueReporterModelData } from 'vs/code/electron-browser/issue/issueReporterModel'; import { IssueReporterData, IssueReporterStyles, IssueType, ISettingsSearchIssueReporterData, IssueReporterFeatures, IssueReporterExtensionData } from 'vs/platform/issue/node/issue'; import BaseHtml from 'vs/code/electron-browser/issue/issueReporterPage'; -import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; +import { LoggerChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; import { ILogService, getLogLevel } from 'vs/platform/log/common/log'; import { OcticonLabel } from 'vs/base/browser/ui/octiconLabel/octiconLabel'; import { normalizeGitHubUrl } from 'vs/code/electron-browser/issue/issueReporterUtil'; @@ -300,8 +300,8 @@ export class IssueReporter extends Disposable { this.environmentService = new EnvironmentService(configuration, configuration.execPath); const logService = new SpdLogService(`issuereporter${configuration.windowId}`, this.environmentService.logsPath, getLogLevel(this.environmentService)); - const logLevelClient = new LogLevelSetterChannelClient(mainProcessService.getChannel('loglevel')); - this.logService = new FollowerLogService(logLevelClient, logService); + const loggerClient = new LoggerChannelClient(mainProcessService.getChannel('logger')); + this.logService = new FollowerLogService(loggerClient, logService); const sharedProcess = (serviceCollection.get(IWindowsService)).whenSharedProcessReady() .then(() => connectNet(this.environmentService.sharedIPCHandle, `window:${configuration.windowId}`)); diff --git a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts index 68b3606086f..dac1b8f7377 100644 --- a/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/electron-browser/sharedProcess/sharedProcessMain.ts @@ -30,7 +30,7 @@ import { IWindowsService, ActiveWindowManager } from 'vs/platform/windows/common import { WindowsService } from 'vs/platform/windows/electron-browser/windowsService'; import { ipcRenderer } from 'electron'; import { ILogService, LogLevel } from 'vs/platform/log/common/log'; -import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; +import { LoggerChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; import { LocalizationsService } from 'vs/platform/localizations/node/localizations'; import { ILocalizationsService } from 'vs/platform/localizations/common/localizations'; import { LocalizationsChannel } from 'vs/platform/localizations/node/localizationsIpc'; @@ -101,8 +101,8 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat const environmentService = new EnvironmentService(initData.args, process.execPath); const mainRouter = new StaticRouter(ctx => ctx === 'main'); - const logLevelClient = new LogLevelSetterChannelClient(server.getChannel('loglevel', mainRouter)); - const logService = new FollowerLogService(logLevelClient, new SpdLogService('sharedprocess', environmentService.logsPath, initData.logLevel)); + const loggerClient = new LoggerChannelClient(server.getChannel('logger', mainRouter)); + const logService = new FollowerLogService(loggerClient, new SpdLogService('sharedprocess', environmentService.logsPath, initData.logLevel)); disposables.add(logService); logService.info('main', JSON.stringify(configuration)); @@ -145,7 +145,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat const services = new ServiceCollection(); const environmentService = accessor.get(IEnvironmentService); const { appRoot, extensionsPath, extensionDevelopmentLocationURI: extensionDevelopmentLocationURI, isBuilt, installSourcePath } = environmentService; - const telemetryLogService = new FollowerLogService(logLevelClient, new SpdLogService('telemetry', environmentService.logsPath, initData.logLevel)); + const telemetryLogService = new FollowerLogService(loggerClient, new SpdLogService('telemetry', environmentService.logsPath, initData.logLevel)); telemetryLogService.info('The below are logs for every telemetry event sent from VS Code once the log level is set to trace.'); telemetryLogService.info('==========================================================='); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 4e2f8842a9e..e7eac8d5f3d 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -33,6 +33,7 @@ import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc import { TelemetryService, ITelemetryServiceConfig } from 'vs/platform/telemetry/common/telemetryService'; import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties'; import { getDelayedChannel, StaticRouter } from 'vs/base/parts/ipc/common/ipc'; +import { SimpleServiceProxyChannel } from 'vs/platform/ipc/node/simpleIpcProxy'; import product from 'vs/platform/product/common/product'; import { ProxyAuthHandler } from 'vs/code/electron-main/auth'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -45,9 +46,8 @@ import { Win32UpdateService } from 'vs/platform/update/electron-main/updateServi import { LinuxUpdateService } from 'vs/platform/update/electron-main/updateService.linux'; import { DarwinUpdateService } from 'vs/platform/update/electron-main/updateService.darwin'; import { IIssueService } from 'vs/platform/issue/node/issue'; -import { IssueChannel } from 'vs/platform/issue/electron-main/issueIpc'; import { IssueMainService } from 'vs/platform/issue/electron-main/issueMainService'; -import { LogLevelSetterChannel } from 'vs/platform/log/common/logIpc'; +import { LoggerChannel } from 'vs/platform/log/common/logIpc'; import { setUnexpectedErrorHandler, onUnexpectedError } from 'vs/base/common/errors'; import { ElectronURLListener } from 'vs/platform/url/electron-main/electronUrlListener'; import { serve as serveDriver } from 'vs/platform/driver/electron-main/driver'; @@ -77,7 +77,7 @@ import { IFileService } from 'vs/platform/files/common/files'; import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider'; import { ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; import { IElectronService } from 'vs/platform/electron/node/electron'; -import { ElectronMainService, ElectronChannel } from 'vs/platform/electron/electron-main/electronMainService'; +import { ElectronMainService } from 'vs/platform/electron/electron-main/electronMainService'; export class CodeApplication extends Disposable { @@ -92,7 +92,7 @@ export class CodeApplication extends Disposable { @IInstantiationService private readonly instantiationService: IInstantiationService, @ILogService private readonly logService: ILogService, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @ILifecycleMainService private readonly lifecycleService: ILifecycleMainService, + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @IConfigurationService private readonly configurationService: IConfigurationService, @IStateService private readonly stateService: IStateService ) { @@ -109,7 +109,7 @@ export class CodeApplication extends Disposable { process.on('unhandledRejection', (reason: unknown) => onUnexpectedError(reason)); // Dispose on shutdown - this.lifecycleService.onWillShutdown(() => this.dispose()); + this.lifecycleMainService.onWillShutdown(() => this.dispose()); // Contextmenu via IPC support registerContextMenuListener(); @@ -255,7 +255,7 @@ export class CodeApplication extends Disposable { this.logService.trace('IPC#vscode:exit', code); this.dispose(); - this.lifecycleService.kill(code); + this.lifecycleMainService.kill(code); }); ipc.on('vscode:fetchShellEnv', async (event: Electron.IpcMainEvent) => { @@ -282,7 +282,7 @@ export class CodeApplication extends Disposable { // Some listeners after window opened (async () => { - await this.lifecycleService.when(LifecycleMainPhase.AfterWindowOpen); + await this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen); // After waking up from sleep (after window opened) powerMonitor.on('resume', () => { @@ -361,7 +361,7 @@ export class CodeApplication extends Disposable { // Spawn shared process after the first window has opened and 3s have passed const sharedProcess = this.instantiationService.createInstance(SharedProcess, machineId, this.userEnv); const sharedProcessClient = sharedProcess.whenReady().then(() => connect(this.environmentService.sharedIPCHandle, 'main')); - this.lifecycleService.when(LifecycleMainPhase.AfterWindowOpen).then(() => { + this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => { this._register(new RunOnceScheduler(async () => { const userEnv = await getShellEnvironment(this.logService, this.environmentService); @@ -461,7 +461,7 @@ export class CodeApplication extends Disposable { const storageMainService = new StorageMainService(this.logService, this.environmentService); services.set(IStorageMainService, storageMainService); - this.lifecycleService.onWillShutdown(e => e.join(storageMainService.close())); + this.lifecycleMainService.onWillShutdown(e => e.join(storageMainService.close())); const backupMainService = new BackupMainService(this.environmentService, this.configurationService, this.logService); services.set(IBackupMainService, backupMainService); @@ -529,8 +529,8 @@ export class CodeApplication extends Disposable { private openFirstWindow(accessor: ServicesAccessor, electronIpcServer: ElectronIPCServer, sharedProcessClient: Promise>): ICodeWindow[] { // Register more Main IPC services - const launchService = accessor.get(ILaunchMainService); - const launchChannel = new LaunchChannel(launchService); + const launchMainService = accessor.get(ILaunchMainService); + const launchChannel = new LaunchChannel(launchMainService); this.mainIpcServer.registerChannel('launch', launchChannel); // Register more Electron IPC services @@ -539,15 +539,15 @@ export class CodeApplication extends Disposable { electronIpcServer.registerChannel('update', updateChannel); const issueService = accessor.get(IIssueService); - const issueChannel = new IssueChannel(issueService); + const issueChannel = new SimpleServiceProxyChannel(issueService); electronIpcServer.registerChannel('issue', issueChannel); const electronService = accessor.get(IElectronService); - const electronChannel = new ElectronChannel(electronService); + const electronChannel = new SimpleServiceProxyChannel(electronService); electronIpcServer.registerChannel('electron', electronChannel); - const workspacesService = accessor.get(IWorkspacesMainService); - const workspacesChannel = new WorkspacesChannel(workspacesService); + const workspacesMainService = accessor.get(IWorkspacesMainService); + const workspacesChannel = new WorkspacesChannel(workspacesMainService); electronIpcServer.registerChannel('workspaces', workspacesChannel); const windowsService = accessor.get(IWindowsService); @@ -567,15 +567,15 @@ export class CodeApplication extends Disposable { const storageChannel = this._register(new GlobalStorageDatabaseChannel(this.logService, storageMainService)); electronIpcServer.registerChannel('storage', storageChannel); - const logLevelChannel = new LogLevelSetterChannel(accessor.get(ILogService)); - electronIpcServer.registerChannel('loglevel', logLevelChannel); - sharedProcessClient.then(client => client.registerChannel('loglevel', logLevelChannel)); + const loggerChannel = new LoggerChannel(accessor.get(ILogService)); + electronIpcServer.registerChannel('logger', loggerChannel); + sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel)); // ExtensionHost Debug broadcast service electronIpcServer.registerChannel(ExtensionHostDebugBroadcastChannel.ChannelName, new ExtensionHostDebugBroadcastChannel()); // Signal phase: ready (services set) - this.lifecycleService.phase = LifecycleMainPhase.Ready; + this.lifecycleMainService.phase = LifecycleMainPhase.Ready; // Propagate to clients const windowsMainService = this.windowsMainService = accessor.get(IWindowsMainService); @@ -685,7 +685,7 @@ export class CodeApplication extends Disposable { private afterWindowOpen(): void { // Signal phase: after window open - this.lifecycleService.phase = LifecycleMainPhase.AfterWindowOpen; + this.lifecycleMainService.phase = LifecycleMainPhase.AfterWindowOpen; // Remote Authorities this.handleRemoteAuthorities(); diff --git a/src/vs/code/electron-main/main.ts b/src/vs/code/electron-main/main.ts index f2d5cf7b88d..534cfddbe4c 100644 --- a/src/vs/code/electron-main/main.ts +++ b/src/vs/code/electron-main/main.ts @@ -116,13 +116,13 @@ class CodeMain { await instantiationService.invokeFunction(async accessor => { const environmentService = accessor.get(IEnvironmentService); const logService = accessor.get(ILogService); - const lifecycleService = accessor.get(ILifecycleMainService); + const lifecycleMainService = accessor.get(ILifecycleMainService); const configurationService = accessor.get(IConfigurationService); - const mainIpcServer = await this.doStartup(logService, environmentService, lifecycleService, instantiationService, true); + const mainIpcServer = await this.doStartup(logService, environmentService, lifecycleMainService, instantiationService, true); bufferLogService.logger = new SpdLogService('main', environmentService.logsPath, bufferLogService.getLevel()); - once(lifecycleService.onWillShutdown)(() => (configurationService as ConfigurationService).dispose()); + once(lifecycleMainService.onWillShutdown)(() => (configurationService as ConfigurationService).dispose()); return instantiationService.createInstance(CodeApplication, mainIpcServer, instanceEnvironment).startup(); }); @@ -189,7 +189,7 @@ class CodeMain { return instanceEnvironment; } - private async doStartup(logService: ILogService, environmentService: IEnvironmentService, lifecycleService: ILifecycleMainService, instantiationService: IInstantiationService, retry: boolean): Promise { + private async doStartup(logService: ILogService, environmentService: IEnvironmentService, lifecycleMainService: ILifecycleMainService, instantiationService: IInstantiationService, retry: boolean): Promise { // Try to setup a server for running. If that succeeds it means // we are the first instance to startup. Otherwise it is likely @@ -197,7 +197,7 @@ class CodeMain { let server: Server; try { server = await serve(environmentService.mainIPCHandle); - once(lifecycleService.onWillShutdown)(() => server.dispose()); + once(lifecycleMainService.onWillShutdown)(() => server.dispose()); } catch (error) { // Handle unexpected errors (the only expected error is EADDRINUSE that @@ -245,7 +245,7 @@ class CodeMain { throw error; } - return this.doStartup(logService, environmentService, lifecycleService, instantiationService, false); + return this.doStartup(logService, environmentService, lifecycleMainService, instantiationService, false); } // Tests from CLI require to be the only instance currently @@ -374,7 +374,7 @@ class CodeMain { private quit(accessor: ServicesAccessor, reason?: ExpectedError | Error): void { const logService = accessor.get(ILogService); - const lifecycleService = accessor.get(ILifecycleMainService); + const lifecycleMainService = accessor.get(ILifecycleMainService); let exitCode = 0; @@ -394,7 +394,7 @@ class CodeMain { } } - lifecycleService.kill(exitCode); + lifecycleMainService.kill(exitCode); } } diff --git a/src/vs/code/electron-main/sharedProcess.ts b/src/vs/code/electron-main/sharedProcess.ts index 29ef3a38ddc..b59ecadb78f 100644 --- a/src/vs/code/electron-main/sharedProcess.ts +++ b/src/vs/code/electron-main/sharedProcess.ts @@ -24,7 +24,7 @@ export class SharedProcess implements ISharedProcess { private readonly machineId: string, private userEnv: NodeJS.ProcessEnv, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @ILifecycleMainService private readonly lifecycleService: ILifecycleMainService, + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @ILogService private readonly logService: ILogService, @IThemeMainService private readonly themeMainService: IThemeMainService ) { } @@ -68,7 +68,7 @@ export class SharedProcess implements ISharedProcess { const disposables = new DisposableStore(); - this.lifecycleService.onWillShutdown(() => { + this.lifecycleMainService.onWillShutdown(() => { disposables.dispose(); // Shut the shared process down when we are quitting diff --git a/src/vs/code/electron-main/windows.ts b/src/vs/code/electron-main/windows.ts index d5ba7ef2ae2..3bb019cd961 100644 --- a/src/vs/code/electron-main/windows.ts +++ b/src/vs/code/electron-main/windows.ts @@ -188,7 +188,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { @ILogService private readonly logService: ILogService, @IStateService private readonly stateService: IStateService, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @ILifecycleMainService private readonly lifecycleService: ILifecycleMainService, + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @IBackupMainService private readonly backupMainService: IBackupMainService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IConfigurationService private readonly configurationService: IConfigurationService, @@ -207,8 +207,8 @@ export class WindowsManager extends Disposable implements IWindowsMainService { this.dialogs = new Dialogs(stateService, this); this.workspacesManager = new WorkspacesManager(workspacesMainService, backupMainService, this); - this.lifecycleService.when(LifecycleMainPhase.Ready).then(() => this.registerListeners()); - this.lifecycleService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this.installWindowsMutex()); + this.lifecycleMainService.when(LifecycleMainPhase.Ready).then(() => this.registerListeners()); + this.lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this.installWindowsMutex()); } private installWindowsMutex(): void { @@ -217,7 +217,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { try { const WindowsMutex = (require.__$__nodeRequire('windows-mutex') as typeof import('windows-mutex')).Mutex; const mutex = new WindowsMutex(win32MutexName); - once(this.lifecycleService.onWillShutdown)(() => mutex.release()); + once(this.lifecycleMainService.onWillShutdown)(() => mutex.release()); } catch (e) { this.logService.error(e); } @@ -251,8 +251,8 @@ export class WindowsManager extends Disposable implements IWindowsMainService { } // Handle various lifecycle events around windows - this.lifecycleService.onBeforeWindowClose(window => this.onBeforeWindowClose(window)); - this.lifecycleService.onBeforeShutdown(() => this.onBeforeShutdown()); + this.lifecycleMainService.onBeforeWindowClose(window => this.onBeforeWindowClose(window)); + this.lifecycleMainService.onBeforeShutdown(() => this.onBeforeShutdown()); this.onWindowsCountChanged(e => { if (e.newCount - e.oldCount > 0) { // clear last closed window state when a new window opens. this helps on macOS where @@ -340,7 +340,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { // See note on #onBeforeShutdown() for details how these events are flowing private onBeforeWindowClose(win: ICodeWindow): void { - if (this.lifecycleService.quitRequested) { + if (this.lifecycleMainService.quitRequested) { return; // during quit, many windows close in parallel so let it be handled in the before-quit handler } @@ -985,7 +985,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { private getRestoreWindowsSetting(): RestoreWindowsSetting { let restoreWindows: RestoreWindowsSetting; - if (this.lifecycleService.wasRestarted) { + if (this.lifecycleMainService.wasRestarted) { restoreWindows = 'all'; // always reopen all windows when an update was applied } else { const windowConfig = this.configurationService.getValue('window'); @@ -1328,7 +1328,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { // Window state is from a previous session: only allow fullscreen when we got updated or user wants to restore else { - allowFullscreen = this.lifecycleService.wasRestarted || (windowConfig && windowConfig.restoreFullscreen); + allowFullscreen = this.lifecycleMainService.wasRestarted || (windowConfig && windowConfig.restoreFullscreen); } if (state.mode === WindowMode.Fullscreen && !allowFullscreen) { @@ -1364,7 +1364,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { window.win.on('closed', () => this.onWindowClosed(window!)); // Lifecycle - (this.lifecycleService as LifecycleMainService).registerWindow(window); + (this.lifecycleMainService as LifecycleMainService).registerWindow(window); } // Existing window @@ -1387,7 +1387,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { // first and only load the new configuration if that was // not vetoed if (window.isReady) { - this.lifecycleService.unload(window, UnloadReason.LOAD).then(veto => { + this.lifecycleMainService.unload(window, UnloadReason.LOAD).then(veto => { if (!veto) { this.doOpenInBrowserWindow(window!, configuration, options); } @@ -1554,7 +1554,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { async reload(win: ICodeWindow, cli?: ParsedArgs): Promise { // Only reload when the window has not vetoed this - const veto = await this.lifecycleService.unload(win, UnloadReason.RELOAD); + const veto = await this.lifecycleMainService.unload(win, UnloadReason.RELOAD); if (!veto) { win.reload(undefined, cli); } @@ -1866,7 +1866,7 @@ export class WindowsManager extends Disposable implements IWindowsMainService { // Otherwise: normal quit else { setTimeout(() => { - this.lifecycleService.quit(); + this.lifecycleMainService.quit(); }, 10 /* delay to unwind callback stack (IPC) */); } } diff --git a/src/vs/code/node/cli.ts b/src/vs/code/node/cli.ts index 84997b65040..6dc1c356bac 100644 --- a/src/vs/code/node/cli.ts +++ b/src/vs/code/node/cli.ts @@ -15,7 +15,7 @@ import { whenDeleted, writeFileSync } from 'vs/base/node/pfs'; import { findFreePort, randomPort } from 'vs/base/node/ports'; import { resolveTerminalEncoding } from 'vs/base/node/encoding'; import * as iconv from 'iconv-lite'; -import { isWindows } from 'vs/base/common/platform'; +import { isWindows, isLinux } from 'vs/base/common/platform'; import { ProfilingSession, Target } from 'v8-inspect-profiler'; import { isString } from 'vs/base/common/types'; @@ -360,6 +360,10 @@ export async function main(argv: string[]): Promise { options['stdio'] = 'ignore'; } + if (isLinux) { + addArg(argv, '--no-sandbox'); // Electron 6 introduces a chrome-sandbox that requires root to run. This can fail. Disable sandbox via --no-sandbox + } + const child = spawn(process.execPath, argv.slice(2), options); if (args.wait && waitMarkerFilePath) { diff --git a/src/vs/editor/browser/services/openerService.ts b/src/vs/editor/browser/services/openerService.ts index c42f82fdb26..8a122c18fc5 100644 --- a/src/vs/editor/browser/services/openerService.ts +++ b/src/vs/editor/browser/services/openerService.ts @@ -13,7 +13,7 @@ import { equalsIgnoreCase } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; -import { IOpener, IOpenerService, IValidator } from 'vs/platform/opener/common/opener'; +import { IOpener, IOpenerService, IValidator, IExternalUriResolver } from 'vs/platform/opener/common/opener'; export class OpenerService extends Disposable implements IOpenerService { @@ -21,6 +21,7 @@ export class OpenerService extends Disposable implements IOpenerService { private readonly _openers = new LinkedList(); private readonly _validators = new LinkedList(); + private readonly _resolvers = new LinkedList(); constructor( @ICodeEditorService private readonly _editorService: ICodeEditorService, @@ -39,6 +40,11 @@ export class OpenerService extends Disposable implements IOpenerService { return { dispose: remove }; } + registerExternalUriResolver(resolver: IExternalUriResolver): IDisposable { + const remove = this._resolvers.push(resolver); + return { dispose: remove }; + } + async open(resource: URI, options?: { openToSide?: boolean, openExternal?: boolean }): Promise { // no scheme ?!? if (!resource.scheme) { @@ -118,7 +124,11 @@ export class OpenerService extends Disposable implements IOpenerService { } } - private _doOpenExternal(resource: URI): Promise { + private async _doOpenExternal(resource: URI): Promise { + for (const resolver of this._resolvers.toArray()) { + resource = await resolver.resolveExternalUri(resource); + } + dom.windowOpenNoOpener(encodeURI(resource.toString(true))); return Promise.resolve(true); diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index 7a409b98a5e..c7bcd9b066c 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -93,6 +93,7 @@ export const enum MenuId { SearchContext, StatusBarWindowIndicatorMenu, TouchBarContext, + TitleBarContext, ViewItemContext, ViewTitle, CommentThreadTitle, diff --git a/src/vs/platform/debug/common/extensionHostDebug.ts b/src/vs/platform/debug/common/extensionHostDebug.ts index 3386e62b25d..3ccb12051b7 100644 --- a/src/vs/platform/debug/common/extensionHostDebug.ts +++ b/src/vs/platform/debug/common/extensionHostDebug.ts @@ -6,6 +6,8 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { Event } from 'vs/base/common/event'; import { IRemoteConsoleLog } from 'vs/base/common/console'; +import { ParsedArgs } from 'vs/platform/environment/common/environment'; +import { IProcessEnvironment } from 'vs/base/common/platform'; export const IExtensionHostDebugService = createDecorator('extensionHostDebugService'); @@ -37,17 +39,19 @@ export interface IExtensionHostDebugService { _serviceBrand: undefined; reload(sessionId: string): void; - onReload: Event; + readonly onReload: Event; close(sessionId: string): void; - onClose: Event; + readonly onClose: Event; attachSession(sessionId: string, port: number, subId?: string): void; - onAttachSession: Event; + readonly onAttachSession: Event; logToSession(sessionId: string, log: IRemoteConsoleLog): void; - onLogToSession: Event; + readonly onLogToSession: Event; terminateSession(sessionId: string, subId?: string): void; - onTerminateSession: Event; -} \ No newline at end of file + readonly onTerminateSession: Event; + + openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise; +} diff --git a/src/vs/platform/debug/common/extensionHostDebugIpc.ts b/src/vs/platform/debug/common/extensionHostDebugIpc.ts index 3511be94950..555d90bfde0 100644 --- a/src/vs/platform/debug/common/extensionHostDebugIpc.ts +++ b/src/vs/platform/debug/common/extensionHostDebugIpc.ts @@ -8,6 +8,8 @@ import { IReloadSessionEvent, ICloseSessionEvent, IAttachSessionEvent, ILogToSes import { Event, Emitter } from 'vs/base/common/event'; import { IRemoteConsoleLog } from 'vs/base/common/console'; import { Disposable } from 'vs/base/common/lifecycle'; +import { ParsedArgs } from 'vs/platform/environment/common/environment'; +import { IProcessEnvironment } from 'vs/base/common/platform'; export class ExtensionHostDebugBroadcastChannel implements IServerChannel { @@ -99,4 +101,10 @@ export class ExtensionHostDebugChannelClient extends Disposable implements IExte get onTerminateSession(): Event { return this.channel.listen('terminate'); } -} \ No newline at end of file + + openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise { + // TODO@Isidor + //return this.channel.call('openExtensionDevelopmentHostWindow', [args, env]); + return Promise.resolve(); + } +} diff --git a/src/vs/platform/driver/electron-main/driver.ts b/src/vs/platform/driver/electron-main/driver.ts index 49404125de3..8966f02836c 100644 --- a/src/vs/platform/driver/electron-main/driver.ts +++ b/src/vs/platform/driver/electron-main/driver.ts @@ -34,7 +34,7 @@ export class Driver implements IDriver, IWindowDriverRegistry { constructor( private windowServer: IPCServer, private options: IDriverOptions, - @IWindowsMainService private readonly windowsService: IWindowsMainService + @IWindowsMainService private readonly windowsMainService: IWindowsMainService ) { } async registerWindowDriver(windowId: number): Promise { @@ -49,7 +49,7 @@ export class Driver implements IDriver, IWindowDriverRegistry { } async getWindowIds(): Promise { - return this.windowsService.getWindows() + return this.windowsMainService.getWindows() .map(w => w.id) .filter(id => this.registeredWindowIds.has(id) && !this.reloadingWindowIds.has(id)); } @@ -57,7 +57,7 @@ export class Driver implements IDriver, IWindowDriverRegistry { async capturePage(windowId: number): Promise { await this.whenUnfrozen(windowId); - const window = this.windowsService.getWindowById(windowId); + const window = this.windowsMainService.getWindowById(windowId); if (!window) { throw new Error('Invalid window'); } @@ -69,16 +69,16 @@ export class Driver implements IDriver, IWindowDriverRegistry { async reloadWindow(windowId: number): Promise { await this.whenUnfrozen(windowId); - const window = this.windowsService.getWindowById(windowId); + const window = this.windowsMainService.getWindowById(windowId); if (!window) { throw new Error('Invalid window'); } this.reloadingWindowIds.add(windowId); - this.windowsService.reload(window); + this.windowsMainService.reload(window); } async exitApplication(): Promise { - return this.windowsService.quit(); + return this.windowsMainService.quit(); } async dispatchKeybinding(windowId: number, keybinding: string): Promise { @@ -96,7 +96,7 @@ export class Driver implements IDriver, IWindowDriverRegistry { throw new Error('ScanCodeBindings not supported'); } - const window = this.windowsService.getWindowById(windowId); + const window = this.windowsMainService.getWindowById(windowId); if (!window) { throw new Error('Invalid window'); } diff --git a/src/vs/platform/electron/electron-browser/electronService.ts b/src/vs/platform/electron/electron-browser/electronService.ts index fa2b5f09b18..0a88c90efc5 100644 --- a/src/vs/platform/electron/electron-browser/electronService.ts +++ b/src/vs/platform/electron/electron-browser/electronService.ts @@ -3,26 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { IElectronService } from 'vs/platform/electron/node/electron'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; +import { createSimpleChannelProxy } from 'vs/platform/ipc/node/simpleIpcProxy'; export class ElectronService { _serviceBrand: undefined; constructor(@IMainProcessService mainProcessService: IMainProcessService) { - const channel = mainProcessService.getChannel('electron'); - - // Proxy: forward any property access to the channel - return new Proxy({}, { - get(_target, propKey, _receiver) { - if (typeof propKey === 'string') { - return function (...args: any[]) { - return channel.call(propKey, ...args); - }; - } - - throw new Error(`Not Implemented in ElectronService: ${String(propKey)}`); - } - }) as ElectronService; + return createSimpleChannelProxy(mainProcessService.getChannel('electron')); } } diff --git a/src/vs/platform/electron/electron-main/electronMainService.ts b/src/vs/platform/electron/electron-main/electronMainService.ts index a8c92d2414b..d51d4b65d3e 100644 --- a/src/vs/platform/electron/electron-main/electronMainService.ts +++ b/src/vs/platform/electron/electron-main/electronMainService.ts @@ -5,9 +5,7 @@ import { IElectronService } from 'vs/platform/electron/node/electron'; import { IWindowsMainService, ICodeWindow } from 'vs/platform/windows/electron-main/windows'; -import { MessageBoxOptions, MessageBoxReturnValue } from 'electron'; -import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { Event } from 'vs/base/common/event'; +import { MessageBoxOptions, MessageBoxReturnValue, shell } from 'electron'; export class ElectronMainService implements IElectronService { @@ -18,10 +16,20 @@ export class ElectronMainService implements IElectronService { ) { } + //#region Window + private get window(): ICodeWindow | undefined { return this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow(); } + async windowCount(): Promise { + return this.windowsMainService.getWindowCount(); + } + + //#endregion + + //#region Other + async showMessageBox(options: MessageBoxOptions): Promise { const result = await this.windowsMainService.showMessageBox(options, this.window); @@ -30,30 +38,10 @@ export class ElectronMainService implements IElectronService { checkboxChecked: !!result.checkboxChecked }; } -} - -export class ElectronChannel implements IServerChannel { - - private service: { [key: string]: unknown }; - - constructor(service: IElectronService) { - this.service = service as unknown as { [key: string]: unknown }; - } - - listen(_: unknown, event: string): Event { - throw new Error(`Event not found: ${event}`); - } - - call(_: unknown, command: string, arg?: any): Promise { - const target = this.service[command]; - if (typeof target === 'function') { - if (Array.isArray(arg)) { - return target.apply(this.service, arg); - } - - return target.call(this.service, arg); - } - - throw new Error(`Call Not Found in ElectronService: ${command}`); - } + + async showItemInFolder(path: string): Promise { + shell.showItemInFolder(path); + } + + //#endregion } diff --git a/src/vs/platform/electron/node/electron.ts b/src/vs/platform/electron/node/electron.ts index 0f6c3a83091..8257bf0eee2 100644 --- a/src/vs/platform/electron/node/electron.ts +++ b/src/vs/platform/electron/node/electron.ts @@ -12,6 +12,12 @@ export interface IElectronService { _serviceBrand: undefined; + // Window + windowCount(): Promise; + // Dialogs showMessageBox(options: MessageBoxOptions): Promise; + + // OS + showItemInFolder(path: string): Promise; } diff --git a/src/vs/platform/history/electron-main/historyMainService.ts b/src/vs/platform/history/electron-main/historyMainService.ts index b67c268b997..a76d56fcc21 100644 --- a/src/vs/platform/history/electron-main/historyMainService.ts +++ b/src/vs/platform/history/electron-main/historyMainService.ts @@ -29,6 +29,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' export const IHistoryMainService = createDecorator('historyMainService'); export interface IHistoryMainService { + _serviceBrand: undefined; onRecentlyOpenedChange: CommonEvent; @@ -68,11 +69,11 @@ export class HistoryMainService implements IHistoryMainService { @ILogService private readonly logService: ILogService, @IWorkspacesMainService private readonly workspacesMainService: IWorkspacesMainService, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @ILifecycleMainService lifecycleService: ILifecycleMainService + @ILifecycleMainService lifecycleMainService: ILifecycleMainService ) { this.macOSRecentDocumentsUpdater = new ThrottledDelayer(800); - lifecycleService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this.handleWindowsJumpList()); + lifecycleMainService.when(LifecycleMainPhase.AfterWindowOpen).then(() => this.handleWindowsJumpList()); } private handleWindowsJumpList(): void { diff --git a/src/vs/platform/ipc/node/simpleIpcProxy.ts b/src/vs/platform/ipc/node/simpleIpcProxy.ts new file mode 100644 index 00000000000..43e4cdbc31f --- /dev/null +++ b/src/vs/platform/ipc/node/simpleIpcProxy.ts @@ -0,0 +1,48 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Event } from 'vs/base/common/event'; +import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; + +// +// Use both `SimpleServiceProxyChannel` and `createSimpleChannelProxy` +// for a very basic process <=> process communication over methods. +// + +export class SimpleServiceProxyChannel implements IServerChannel { + + private service: { [key: string]: unknown }; + + constructor(service: unknown) { + this.service = service as { [key: string]: unknown }; + } + + listen(_: unknown, event: string): Event { + throw new Error(`Events are currently unsupported by SimpleServiceProxyChannel: ${event}`); + } + + call(_: unknown, command: string, args: any[]): Promise { + const target = this.service[command]; + if (typeof target === 'function') { + return target.apply(this.service, args); + } + + throw new Error(`Method not found: ${command}`); + } +} + +export function createSimpleChannelProxy(channel: IChannel): T { + return new Proxy({}, { + get(_target, propKey, _receiver) { + if (typeof propKey === 'string') { + return function (...args: any[]) { + return channel.call(propKey, args); + }; + } + + throw new Error(`Unable to provide main channel proxy implementation for: ${String(propKey)}`); + } + }) as T; +} diff --git a/src/vs/platform/issue/electron-browser/issueService.ts b/src/vs/platform/issue/electron-browser/issueService.ts index 56068fab19e..72502019070 100644 --- a/src/vs/platform/issue/electron-browser/issueService.ts +++ b/src/vs/platform/issue/electron-browser/issueService.ts @@ -3,29 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IChannel } from 'vs/base/parts/ipc/common/ipc'; -import { IIssueService, IssueReporterData, ProcessExplorerData } from 'vs/platform/issue/node/issue'; +import { IIssueService } from 'vs/platform/issue/node/issue'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; +import { createSimpleChannelProxy } from 'vs/platform/ipc/node/simpleIpcProxy'; -export class IssueService implements IIssueService { +export class IssueService { _serviceBrand: undefined; - private channel: IChannel; - constructor(@IMainProcessService mainProcessService: IMainProcessService) { - this.channel = mainProcessService.getChannel('issue'); - } - - openReporter(data: IssueReporterData): Promise { - return this.channel.call('openIssueReporter', data); - } - - openProcessExplorer(data: ProcessExplorerData): Promise { - return this.channel.call('openProcessExplorer', data); - } - - getSystemStatus(): Promise { - return this.channel.call('getSystemStatus'); + return createSimpleChannelProxy(mainProcessService.getChannel('issue')); } } diff --git a/src/vs/platform/issue/electron-main/issueIpc.ts b/src/vs/platform/issue/electron-main/issueIpc.ts deleted file mode 100644 index 271bcf5ceee..00000000000 --- a/src/vs/platform/issue/electron-main/issueIpc.ts +++ /dev/null @@ -1,30 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { IServerChannel } from 'vs/base/parts/ipc/common/ipc'; -import { Event } from 'vs/base/common/event'; -import { IIssueService } from 'vs/platform/issue/node/issue'; - -export class IssueChannel implements IServerChannel { - - constructor(private service: IIssueService) { } - - listen(_: unknown, event: string): Event { - throw new Error(`Event not found: ${event}`); - } - - call(_: unknown, command: string, arg?: any): Promise { - switch (command) { - case 'openIssueReporter': - return this.service.openReporter(arg); - case 'openProcessExplorer': - return this.service.openProcessExplorer(arg); - case 'getSystemStatus': - return this.service.getSystemStatus(); - } - - throw new Error(`Call not found: ${command}`); - } -} \ No newline at end of file diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts index 2278e311a04..55d332040c5 100644 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -31,7 +31,7 @@ export class IssueMainService implements IIssueService { private machineId: string, private userEnv: IProcessEnvironment, @IEnvironmentService private readonly environmentService: IEnvironmentService, - @ILaunchMainService private readonly launchService: ILaunchMainService, + @ILaunchMainService private readonly launchMainService: ILaunchMainService, @ILogService private readonly logService: ILogService, @IDiagnosticsService private readonly diagnosticsService: IDiagnosticsService, @IWindowsService private readonly windowsService: IWindowsService @@ -41,7 +41,7 @@ export class IssueMainService implements IIssueService { private registerListeners(): void { ipcMain.on('vscode:issueSystemInfoRequest', async (event: Electron.IpcMainEvent) => { - Promise.all([this.launchService.getMainProcessInfo(), this.launchService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false })]) + Promise.all([this.launchMainService.getMainProcessInfo(), this.launchMainService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false })]) .then(result => { const [info, remoteData] = result; this.diagnosticsService.getSystemInfo(info, remoteData).then(msg => { @@ -54,9 +54,9 @@ export class IssueMainService implements IIssueService { const processes = []; try { - const mainPid = await this.launchService.getMainProcessId(); + const mainPid = await this.launchMainService.getMainProcessId(); processes.push({ name: localize('local', "Local"), rootProcess: await listProcesses(mainPid) }); - (await this.launchService.getRemoteDiagnostics({ includeProcesses: true })) + (await this.launchMainService.getRemoteDiagnostics({ includeProcesses: true })) .forEach(data => { if (isRemoteDiagnosticError(data)) { processes.push({ @@ -157,7 +157,7 @@ export class IssueMainService implements IIssueService { }); ipcMain.on('windowsInfoRequest', (event: Electron.IpcMainEvent) => { - this.launchService.getMainProcessInfo().then(info => { + this.launchMainService.getMainProcessInfo().then(info => { event.sender.send('vscode:windowsInfoResponse', info.windows); }); }); @@ -268,7 +268,7 @@ export class IssueMainService implements IIssueService { } public async getSystemStatus(): Promise { - return Promise.all([this.launchService.getMainProcessInfo(), this.launchService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false })]) + return Promise.all([this.launchMainService.getMainProcessInfo(), this.launchMainService.getRemoteDiagnostics({ includeProcesses: false, includeWorkspaceMetadata: false })]) .then(result => { const [info, remoteData] = result; return this.diagnosticsService.getDiagnostics(info, remoteData); @@ -345,7 +345,7 @@ export class IssueMainService implements IIssueService { private getPerformanceInfo(): Promise { return new Promise(async (resolve, reject) => { - Promise.all([this.launchService.getMainProcessInfo(), this.launchService.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true })]) + Promise.all([this.launchMainService.getMainProcessInfo(), this.launchMainService.getRemoteDiagnostics({ includeProcesses: true, includeWorkspaceMetadata: true })]) .then(result => { const [info, remoteData] = result; this.diagnosticsService.getPerformanceInfo(info, remoteData) diff --git a/src/vs/platform/log/common/log.ts b/src/vs/platform/log/common/log.ts index 5e6b7300c49..0009b304e53 100644 --- a/src/vs/platform/log/common/log.ts +++ b/src/vs/platform/log/common/log.ts @@ -8,6 +8,7 @@ import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { isWindows } from 'vs/base/common/platform'; import { Event, Emitter } from 'vs/base/common/event'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { LoggerChannelClient } from 'vs/platform/log/common/logIpc'; export const ILogService = createServiceDecorator('logService'); @@ -183,6 +184,54 @@ export class ConsoleLogService extends AbstractLogService implements ILogService dispose(): void { } } +export class ConsoleLogInMainService extends AbstractLogService implements ILogService { + + _serviceBrand: undefined; + + constructor(private readonly client: LoggerChannelClient, logLevel: LogLevel = DEFAULT_LOG_LEVEL) { + super(); + this.setLevel(logLevel); + } + + trace(message: string, ...args: any[]): void { + if (this.getLevel() <= LogLevel.Trace) { + this.client.consoleLog('trace', [message, ...args]); + } + } + + debug(message: string, ...args: any[]): void { + if (this.getLevel() <= LogLevel.Debug) { + this.client.consoleLog('debug', [message, ...args]); + } + } + + info(message: string, ...args: any[]): void { + if (this.getLevel() <= LogLevel.Info) { + this.client.consoleLog('info', [message, ...args]); + } + } + + warn(message: string | Error, ...args: any[]): void { + if (this.getLevel() <= LogLevel.Warning) { + this.client.consoleLog('warn', [message, ...args]); + } + } + + error(message: string, ...args: any[]): void { + if (this.getLevel() <= LogLevel.Error) { + this.client.consoleLog('error', [message, ...args]); + } + } + + critical(message: string, ...args: any[]): void { + if (this.getLevel() <= LogLevel.Critical) { + this.client.consoleLog('critical', [message, ...args]); + } + } + + dispose(): void { } +} + export class MultiplexLogService extends AbstractLogService implements ILogService { _serviceBrand: undefined; @@ -326,4 +375,4 @@ export function getLogLevel(environmentService: IEnvironmentService): LogLevel { } } return DEFAULT_LOG_LEVEL; -} \ No newline at end of file +} diff --git a/src/vs/platform/log/common/logIpc.ts b/src/vs/platform/log/common/logIpc.ts index 5f631b8b9d1..260922cedcb 100644 --- a/src/vs/platform/log/common/logIpc.ts +++ b/src/vs/platform/log/common/logIpc.ts @@ -7,7 +7,7 @@ import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc'; import { LogLevel, ILogService, DelegatedLogService } from 'vs/platform/log/common/log'; import { Event } from 'vs/base/common/event'; -export class LogLevelSetterChannel implements IServerChannel { +export class LoggerChannel implements IServerChannel { onDidChangeLogLevel: Event; @@ -26,13 +26,32 @@ export class LogLevelSetterChannel implements IServerChannel { call(_: unknown, command: string, arg?: any): Promise { switch (command) { case 'setLevel': this.service.setLevel(arg); return Promise.resolve(); + case 'consoleLog': this.consoleLog(arg[0], arg[1]); return Promise.resolve(); } throw new Error(`Call not found: ${command}`); } + + private consoleLog(severity: string, args: string[]): void { + let consoleFn = console.log; + + switch (severity) { + case 'error': + consoleFn = console.error; + break; + case 'warn': + consoleFn = console.warn; + break; + case 'info': + consoleFn = console.info; + break; + } + + consoleFn.call(console, ...args); + } } -export class LogLevelSetterChannelClient { +export class LoggerChannelClient { constructor(private channel: IChannel) { } @@ -43,12 +62,16 @@ export class LogLevelSetterChannelClient { setLevel(level: LogLevel): void { this.channel.call('setLevel', level); } + + consoleLog(severity: string, args: string[]): void { + this.channel.call('consoleLog', [severity, args]); + } } export class FollowerLogService extends DelegatedLogService implements ILogService { _serviceBrand: undefined; - constructor(private master: LogLevelSetterChannelClient, logService: ILogService) { + constructor(private master: LoggerChannelClient, logService: ILogService) { super(logService); this._register(master.onDidChangeLogLevel(level => logService.setLevel(level))); } @@ -56,4 +79,4 @@ export class FollowerLogService extends DelegatedLogService implements ILogServi setLevel(level: LogLevel): void { this.master.setLevel(level); } -} \ No newline at end of file +} diff --git a/src/vs/platform/menubar/electron-main/menubar.ts b/src/vs/platform/menubar/electron-main/menubar.ts index 25139ec68e5..70caae25b47 100644 --- a/src/vs/platform/menubar/electron-main/menubar.ts +++ b/src/vs/platform/menubar/electron-main/menubar.ts @@ -68,7 +68,7 @@ export class Menubar { @ITelemetryService private readonly telemetryService: ITelemetryService, @IHistoryMainService private readonly historyMainService: IHistoryMainService, @IStateService private readonly stateService: IStateService, - @ILifecycleMainService private readonly lifecycleService: ILifecycleMainService, + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @ILogService private readonly logService: ILogService ) { this.menuUpdater = new RunOnceScheduler(() => this.doUpdateMenu(), 0); @@ -160,7 +160,7 @@ export class Menubar { private registerListeners(): void { // Keep flag when app quits - this.lifecycleService.onWillShutdown(() => this.willShutdown = true); + this.lifecycleMainService.onWillShutdown(() => this.willShutdown = true); // // Listen to some events from window service to update menu this.windowsMainService.onWindowsCountChanged(e => this.onWindowsCountChanged(e)); diff --git a/src/vs/platform/opener/common/opener.ts b/src/vs/platform/opener/common/opener.ts index 8563f3fa2af..0c9a243a952 100644 --- a/src/vs/platform/opener/common/opener.ts +++ b/src/vs/platform/opener/common/opener.ts @@ -5,7 +5,7 @@ import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; -import { IDisposable } from 'vs/base/common/lifecycle'; +import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; export const IOpenerService = createDecorator('openerService'); @@ -18,6 +18,10 @@ export interface IValidator { shouldOpen(resource: URI): Promise; } +export interface IExternalUriResolver { + resolveExternalUri(resource: URI): Promise; +} + export interface IOpenerService { _serviceBrand: undefined; @@ -29,10 +33,15 @@ export interface IOpenerService { /** * Register a participant that can validate if the URI resource be opened. - * validators are run before openers. + * Validators are run before openers. */ registerValidator(validator: IValidator): IDisposable; + /** + * Register a participant that can resolve an external URI resource to be opened. + */ + registerExternalUriResolver(resolver: IExternalUriResolver): IDisposable; + /** * Opens a resource, like a webaddress, a document uri, or executes command. * @@ -45,7 +54,8 @@ export interface IOpenerService { export const NullOpenerService: IOpenerService = Object.freeze({ _serviceBrand: undefined, - registerOpener() { return { dispose() { } }; }, - registerValidator() { return { dispose() { } }; }, + registerOpener() { return Disposable.None; }, + registerValidator() { return Disposable.None; }, + registerExternalUriResolver() { return Disposable.None; }, open() { return Promise.resolve(false); }, }); diff --git a/src/vs/platform/theme/common/colorRegistry.ts b/src/vs/platform/theme/common/colorRegistry.ts index f5fc75c9324..b6ec6f9e6f9 100644 --- a/src/vs/platform/theme/common/colorRegistry.ts +++ b/src/vs/platform/theme/common/colorRegistry.ts @@ -187,6 +187,8 @@ export const activeContrastBorder = registerColor('contrastActiveBorder', { ligh export const selectionBackground = registerColor('selection.background', { light: null, dark: null, hc: null }, nls.localize('selectionBackground', "The background color of text selections in the workbench (e.g. for input fields or text areas). Note that this does not apply to selections within the editor.")); +export const iconForeground = registerColor('icon.foreground', { light: '#424242', dark: '#C5C5C5', hc: '#FFFFFF' }, nls.localize('iconForeground', "The default color for icons in the workbench.")); + // ------ text colors export const textSeparatorForeground = registerColor('textSeparator.foreground', { light: '#0000002e', dark: '#ffffff2e', hc: Color.black }, nls.localize('textSeparatorForeground', "Color for text separators.")); diff --git a/src/vs/platform/update/electron-main/abstractUpdateService.ts b/src/vs/platform/update/electron-main/abstractUpdateService.ts index 8847db647a7..276c468a51a 100644 --- a/src/vs/platform/update/electron-main/abstractUpdateService.ts +++ b/src/vs/platform/update/electron-main/abstractUpdateService.ts @@ -44,7 +44,7 @@ export abstract class AbstractUpdateService implements IUpdateService { } constructor( - @ILifecycleMainService private readonly lifecycleService: ILifecycleMainService, + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @IConfigurationService protected configurationService: IConfigurationService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IRequestService protected requestService: IRequestService, @@ -152,7 +152,7 @@ export abstract class AbstractUpdateService implements IUpdateService { this.logService.trace('update#quitAndInstall(): before lifecycle quit()'); - this.lifecycleService.quit(true /* from update */).then(vetod => { + this.lifecycleMainService.quit(true /* from update */).then(vetod => { this.logService.trace(`update#quitAndInstall(): after lifecycle quit() with veto: ${vetod}`); if (vetod) { return; diff --git a/src/vs/platform/update/electron-main/updateService.darwin.ts b/src/vs/platform/update/electron-main/updateService.darwin.ts index cf016a44280..cd73a5cd95a 100644 --- a/src/vs/platform/update/electron-main/updateService.darwin.ts +++ b/src/vs/platform/update/electron-main/updateService.darwin.ts @@ -28,14 +28,14 @@ export class DarwinUpdateService extends AbstractUpdateService { @memoize private get onRawUpdateDownloaded(): Event { return Event.fromNodeEventEmitter(electron.autoUpdater, 'update-downloaded', (_, releaseNotes, version, date) => ({ releaseNotes, version, productVersion: version, date })); } constructor( - @ILifecycleMainService lifecycleService: ILifecycleMainService, + @ILifecycleMainService lifecycleMainService: ILifecycleMainService, @IConfigurationService configurationService: IConfigurationService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IEnvironmentService environmentService: IEnvironmentService, @IRequestService requestService: IRequestService, @ILogService logService: ILogService ) { - super(lifecycleService, configurationService, environmentService, requestService, logService); + super(lifecycleMainService, configurationService, environmentService, requestService, logService); this.onRawError(this.onError, this, this.disposables); this.onRawUpdateAvailable(this.onUpdateAvailable, this, this.disposables); this.onRawUpdateDownloaded(this.onUpdateDownloaded, this, this.disposables); diff --git a/src/vs/platform/update/electron-main/updateService.linux.ts b/src/vs/platform/update/electron-main/updateService.linux.ts index 7adb7e00bf8..8b59c871d09 100644 --- a/src/vs/platform/update/electron-main/updateService.linux.ts +++ b/src/vs/platform/update/electron-main/updateService.linux.ts @@ -20,14 +20,14 @@ export class LinuxUpdateService extends AbstractUpdateService { _serviceBrand: undefined; constructor( - @ILifecycleMainService lifecycleService: ILifecycleMainService, + @ILifecycleMainService lifecycleMainService: ILifecycleMainService, @IConfigurationService configurationService: IConfigurationService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IEnvironmentService environmentService: IEnvironmentService, @IRequestService requestService: IRequestService, @ILogService logService: ILogService ) { - super(lifecycleService, configurationService, environmentService, requestService, logService); + super(lifecycleMainService, configurationService, environmentService, requestService, logService); } protected buildUpdateFeedUrl(quality: string): string { diff --git a/src/vs/platform/update/electron-main/updateService.snap.ts b/src/vs/platform/update/electron-main/updateService.snap.ts index 36db202ff33..75d7c1e187e 100644 --- a/src/vs/platform/update/electron-main/updateService.snap.ts +++ b/src/vs/platform/update/electron-main/updateService.snap.ts @@ -35,7 +35,7 @@ abstract class AbstractUpdateService2 implements IUpdateService { } constructor( - @ILifecycleMainService private readonly lifecycleService: ILifecycleMainService, + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, @IEnvironmentService environmentService: IEnvironmentService, @ILogService protected logService: ILogService, ) { @@ -106,7 +106,7 @@ abstract class AbstractUpdateService2 implements IUpdateService { this.logService.trace('update#quitAndInstall(): before lifecycle quit()'); - this.lifecycleService.quit(true /* from update */).then(vetod => { + this.lifecycleMainService.quit(true /* from update */).then(vetod => { this.logService.trace(`update#quitAndInstall(): after lifecycle quit() with veto: ${vetod}`); if (vetod) { return; @@ -139,12 +139,12 @@ export class SnapUpdateService extends AbstractUpdateService2 { constructor( private snap: string, private snapRevision: string, - @ILifecycleMainService lifecycleService: ILifecycleMainService, + @ILifecycleMainService lifecycleMainService: ILifecycleMainService, @IEnvironmentService environmentService: IEnvironmentService, @ILogService logService: ILogService, @ITelemetryService private readonly telemetryService: ITelemetryService ) { - super(lifecycleService, environmentService, logService); + super(lifecycleMainService, environmentService, logService); const watcher = watch(path.dirname(this.snap)); const onChange = Event.fromNodeEventEmitter(watcher, 'change', (_, fileName: string) => fileName); @@ -152,7 +152,7 @@ export class SnapUpdateService extends AbstractUpdateService2 { const onDebouncedCurrentChange = Event.debounce(onCurrentChange, (_, e) => e, 2000); const listener = onDebouncedCurrentChange(this.checkForUpdates, this); - lifecycleService.onWillShutdown(() => { + lifecycleMainService.onWillShutdown(() => { listener.dispose(); watcher.close(); }); diff --git a/src/vs/platform/update/electron-main/updateService.win32.ts b/src/vs/platform/update/electron-main/updateService.win32.ts index 7395de466da..c053fda3904 100644 --- a/src/vs/platform/update/electron-main/updateService.win32.ts +++ b/src/vs/platform/update/electron-main/updateService.win32.ts @@ -60,7 +60,7 @@ export class Win32UpdateService extends AbstractUpdateService { } constructor( - @ILifecycleMainService lifecycleService: ILifecycleMainService, + @ILifecycleMainService lifecycleMainService: ILifecycleMainService, @IConfigurationService configurationService: IConfigurationService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IEnvironmentService environmentService: IEnvironmentService, @@ -68,7 +68,7 @@ export class Win32UpdateService extends AbstractUpdateService { @ILogService logService: ILogService, @IFileService private readonly fileService: IFileService ) { - super(lifecycleService, configurationService, environmentService, requestService, logService); + super(lifecycleMainService, configurationService, environmentService, requestService, logService); if (getUpdateType() === UpdateType.Setup) { /* __GDPR__ diff --git a/src/vs/platform/url/electron-main/electronUrlListener.ts b/src/vs/platform/url/electron-main/electronUrlListener.ts index 6edd504f66c..f8db65d4e1e 100644 --- a/src/vs/platform/url/electron-main/electronUrlListener.ts +++ b/src/vs/platform/url/electron-main/electronUrlListener.ts @@ -28,7 +28,7 @@ export class ElectronURLListener { constructor( initial: string | string[], @IURLService private readonly urlService: IURLService, - @IWindowsMainService windowsService: IWindowsMainService + @IWindowsMainService windowsMainService: IWindowsMainService ) { const globalBuffer = ((global).getOpenUrls() || []) as string[]; const rawBuffer = [ @@ -58,14 +58,14 @@ export class ElectronURLListener { const onOpenUrl = Event.filter(Event.map(onOpenElectronUrl, uriFromRawUrl), uri => !!uri); onOpenUrl(this.urlService.open, this.urlService, this.disposables); - const isWindowReady = windowsService.getWindows() + const isWindowReady = windowsMainService.getWindows() .filter(w => w.isReady) .length > 0; if (isWindowReady) { flush(); } else { - Event.once(windowsService.onWindowReady)(flush); + Event.once(windowsMainService.onWindowReady)(flush); } } diff --git a/src/vs/platform/windows/common/windows.ts b/src/vs/platform/windows/common/windows.ts index 7f697c0d445..8d713a46ec6 100644 --- a/src/vs/platform/windows/common/windows.ts +++ b/src/vs/platform/windows/common/windows.ts @@ -155,9 +155,6 @@ export interface IWindowsService { openNewWindow(options?: INewWindowOptions): Promise; openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise; getWindows(): Promise<{ id: number; workspace?: IWorkspaceIdentifier; folderUri?: ISingleFolderWorkspaceIdentifier; title: string; filename?: string; }[]>; - getWindowCount(): Promise; - log(severity: string, args: string[]): Promise; - showItemInFolder(path: URI): Promise; getActiveWindowId(): Promise; // This needs to be handled from browser process to prevent diff --git a/src/vs/platform/windows/common/windowsIpc.ts b/src/vs/platform/windows/common/windowsIpc.ts index 5e3c0944d94..a17937f4455 100644 --- a/src/vs/platform/windows/common/windowsIpc.ts +++ b/src/vs/platform/windows/common/windowsIpc.ts @@ -104,13 +104,10 @@ export class WindowsChannel implements IServerChannel { case 'openNewWindow': return this.service.openNewWindow(arg); case 'openExtensionDevelopmentHostWindow': return this.service.openExtensionDevelopmentHostWindow(arg[0], arg[1]); case 'getWindows': return this.service.getWindows(); - case 'getWindowCount': return this.service.getWindowCount(); case 'relaunch': return this.service.relaunch(arg[0]); case 'whenSharedProcessReady': return this.service.whenSharedProcessReady(); case 'toggleSharedProcess': return this.service.toggleSharedProcess(); case 'quit': return this.service.quit(); - case 'log': return this.service.log(arg[0], arg[1]); - case 'showItemInFolder': return this.service.showItemInFolder(URI.revive(arg)); case 'getActiveWindowId': return this.service.getActiveWindowId(); case 'openExternal': return this.service.openExternal(arg); case 'startCrashReporter': return this.service.startCrashReporter(arg); diff --git a/src/vs/platform/windows/electron-browser/windowsService.ts b/src/vs/platform/windows/electron-browser/windowsService.ts index d6089d90776..92808158543 100644 --- a/src/vs/platform/windows/electron-browser/windowsService.ts +++ b/src/vs/platform/windows/electron-browser/windowsService.ts @@ -222,18 +222,6 @@ export class WindowsService implements IWindowsService { return result; } - getWindowCount(): Promise { - return this.channel.call('getWindowCount'); - } - - log(severity: string, args: string[]): Promise { - return this.channel.call('log', [severity, args]); - } - - showItemInFolder(path: URI): Promise { - return this.channel.call('showItemInFolder', path); - } - getActiveWindowId(): Promise { return this.channel.call('getActiveWindowId'); } diff --git a/src/vs/platform/windows/electron-main/windowsService.ts b/src/vs/platform/windows/electron-main/windowsService.ts index c6ee7cfc593..6cdde7c3da8 100644 --- a/src/vs/platform/windows/electron-main/windowsService.ts +++ b/src/vs/platform/windows/electron-main/windowsService.ts @@ -38,15 +38,15 @@ export class WindowsService extends Disposable implements IWindowsService, IURLH Event.filter(Event.fromNodeEventEmitter(app, 'browser-window-focus', (_, w: Electron.BrowserWindow) => w.id), id => !!this.windowsMainService.getWindowById(id)) ); - readonly onRecentlyOpenedChange: Event = this.historyService.onRecentlyOpenedChange; + readonly onRecentlyOpenedChange: Event = this.historyMainService.onRecentlyOpenedChange; constructor( private sharedProcess: ISharedProcess, @IWindowsMainService private readonly windowsMainService: IWindowsMainService, @IEnvironmentService private readonly environmentService: IEnvironmentService, @IURLService urlService: IURLService, - @ILifecycleMainService private readonly lifecycleService: ILifecycleMainService, - @IHistoryMainService private readonly historyService: IHistoryMainService, + @ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService, + @IHistoryMainService private readonly historyMainService: IHistoryMainService, @ILogService private readonly logService: ILogService ) { super(); @@ -157,25 +157,25 @@ export class WindowsService extends Disposable implements IWindowsService, IURLH async addRecentlyOpened(recents: IRecent[]): Promise { this.logService.trace('windowsService#addRecentlyOpened'); - this.historyService.addRecentlyOpened(recents); + this.historyMainService.addRecentlyOpened(recents); } async removeFromRecentlyOpened(paths: URI[]): Promise { this.logService.trace('windowsService#removeFromRecentlyOpened'); - this.historyService.removeFromRecentlyOpened(paths); + this.historyMainService.removeFromRecentlyOpened(paths); } async clearRecentlyOpened(): Promise { this.logService.trace('windowsService#clearRecentlyOpened'); - this.historyService.clearRecentlyOpened(); + this.historyMainService.clearRecentlyOpened(); } async getRecentlyOpened(windowId: number): Promise { this.logService.trace('windowsService#getRecentlyOpened', windowId); - return this.withWindow(windowId, codeWindow => this.historyService.getRecentlyOpened(codeWindow.config.workspace, codeWindow.config.folderUri, codeWindow.config.filesToOpenOrCreate), () => this.historyService.getRecentlyOpened())!; + return this.withWindow(windowId, codeWindow => this.historyMainService.getRecentlyOpened(codeWindow.config.workspace, codeWindow.config.folderUri, codeWindow.config.filesToOpenOrCreate), () => this.historyMainService.getRecentlyOpened())!; } async newWindowTab(): Promise { @@ -336,32 +336,6 @@ export class WindowsService extends Disposable implements IWindowsService, IURLH return this.windowsMainService.getWindows().length; } - async log(severity: string, args: string[]): Promise { - let consoleFn = console.log; - - switch (severity) { - case 'error': - consoleFn = console.error; - break; - case 'warn': - consoleFn = console.warn; - break; - case 'info': - consoleFn = console.info; - break; - } - - consoleFn.call(console, ...args); - } - - async showItemInFolder(resource: URI): Promise { - this.logService.trace('windowsService#showItemInFolder'); - - if (resource.scheme === Schemas.file) { - shell.showItemInFolder(resource.fsPath); - } - } - async getActiveWindowId(): Promise { return this._activeWindowId; } @@ -388,7 +362,7 @@ export class WindowsService extends Disposable implements IWindowsService, IURLH async relaunch(options: { addArgs?: string[], removeArgs?: string[] }): Promise { this.logService.trace('windowsService#relaunch'); - this.lifecycleService.relaunch(options); + this.lifecycleMainService.relaunch(options); } async whenSharedProcessReady(): Promise { diff --git a/src/vs/workbench/api/browser/mainThreadConsole.ts b/src/vs/workbench/api/browser/mainThreadConsole.ts index 1d7a7723404..4a244875f42 100644 --- a/src/vs/workbench/api/browser/mainThreadConsole.ts +++ b/src/vs/workbench/api/browser/mainThreadConsole.ts @@ -6,9 +6,10 @@ import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers'; import { MainContext, MainThreadConsoleShape, IExtHostContext } from 'vs/workbench/api/common/extHost.protocol'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IRemoteConsoleLog, log, parse } from 'vs/base/common/console'; +import { IRemoteConsoleLog, log } from 'vs/base/common/console'; +import { logRemoteEntry } from 'vs/workbench/services/extensions/common/remoteConsoleUtil'; import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { ILogService } from 'vs/platform/log/common/log'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; @extHostNamedCustomer(MainContext.MainThreadConsole) @@ -20,7 +21,7 @@ export class MainThreadConsole implements MainThreadConsoleShape { constructor( extHostContext: IExtHostContext, @IEnvironmentService private readonly _environmentService: IEnvironmentService, - @IWindowsService private readonly _windowsService: IWindowsService, + @ILogService private readonly _logService: ILogService, @IExtensionHostDebugService private readonly _extensionHostDebugService: IExtensionHostDebugService, ) { const devOpts = parseExtensionDevOptions(this._environmentService); @@ -40,7 +41,7 @@ export class MainThreadConsole implements MainThreadConsoleShape { // Log on main side if running tests from cli if (this._isExtensionDevTestFromCli) { - this._windowsService.log(entry.severity, parse(entry).args); + logRemoteEntry(this._logService, entry); } // Broadcast to other windows if we are in development mode diff --git a/src/vs/workbench/api/common/extHostTask.ts b/src/vs/workbench/api/common/extHostTask.ts index a2119063d3d..7667465f6bd 100644 --- a/src/vs/workbench/api/common/extHostTask.ts +++ b/src/vs/workbench/api/common/extHostTask.ts @@ -374,8 +374,9 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { protected _handlers: Map; protected _taskExecutions: Map; protected _providedCustomExecutions2: Map; + private _notProvidedCustomExecutions: Set; // Used for custom executions tasks that are created and run through executeTask. protected _activeCustomExecutions2: Map; - + private _lastStartedTask: string | undefined; protected readonly _onDidExecuteTask: Emitter = new Emitter(); protected readonly _onDidTerminateTask: Emitter = new Emitter(); @@ -399,6 +400,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { this._handlers = new Map(); this._taskExecutions = new Map(); this._providedCustomExecutions2 = new Map(); + this._notProvidedCustomExecutions = new Set(); this._activeCustomExecutions2 = new Map(); } @@ -462,6 +464,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { this._activeCustomExecutions2.set(execution.id, execution2); this._terminalService.attachPtyToTerminal(terminalId, await execution2.callback()); } + this._lastStartedTask = execution.id; this._onDidExecuteTask.fire({ execution: await this.getTaskExecution(execution) @@ -571,7 +574,7 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { } if (CustomExecution2DTO.is(resolvedTaskDTO.execution)) { - await this.addCustomExecution2(resolvedTaskDTO, resolvedTask); + await this.addCustomExecution2(resolvedTaskDTO, resolvedTask, true); } return await this.resolveTaskInternal(resolvedTaskDTO); @@ -585,8 +588,11 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { return this._handleCounter++; } - protected async addCustomExecution2(taskDTO: tasks.TaskDTO, task: vscode.Task2): Promise { + protected async addCustomExecution2(taskDTO: tasks.TaskDTO, task: vscode.Task2, isProvided: boolean): Promise { const taskId = await this._proxy.$createTaskId(taskDTO); + if (!isProvided && !this._providedCustomExecutions2.has(taskId)) { + this._notProvidedCustomExecutions.add(taskId); + } this._providedCustomExecutions2.set(taskId, (task).execution2); } @@ -618,16 +624,22 @@ export abstract class ExtHostTaskBase implements ExtHostTaskShape { this._activeCustomExecutions2.delete(execution.id); } - const lastCustomExecution = this._providedCustomExecutions2.get(execution.id); // Technically we don't really need to do this, however, if an extension // is executing a task through "executeTask" over and over again - // with different properties in the task definition, then this list + // with different properties in the task definition, then the map of executions // could grow indefinitely, something we don't want. - this._providedCustomExecutions2.clear(); - // We do still need to hang on to the last custom execution so that the - // Rerun Task command doesn't choke when it tries to rerun a custom execution - if (lastCustomExecution) { - this._providedCustomExecutions2.set(execution.id, lastCustomExecution); + if (this._notProvidedCustomExecutions.has(execution.id) && (this._lastStartedTask !== execution.id)) { + this._providedCustomExecutions2.delete(execution.id); + this._notProvidedCustomExecutions.delete(execution.id); + } + let iterator = this._notProvidedCustomExecutions.values(); + let iteratorResult = iterator.next(); + while (!iteratorResult.done) { + if (!this._activeCustomExecutions2.has(iteratorResult.value) && (this._lastStartedTask !== iteratorResult.value)) { + this._providedCustomExecutions2.delete(iteratorResult.value); + this._notProvidedCustomExecutions.delete(iteratorResult.value); + } + iteratorResult = iterator.next(); } } @@ -663,7 +675,7 @@ export class WorkerExtHostTask extends ExtHostTaskBase { // in the provided custom execution map that is cleaned up after the // task is executed. if (CustomExecution2DTO.is(dto.execution)) { - await this.addCustomExecution2(dto, task); + await this.addCustomExecution2(dto, task, false); } else { throw new Error('Not implemented'); } @@ -685,7 +697,7 @@ export class WorkerExtHostTask extends ExtHostTaskBase { // The ID is calculated on the main thread task side, so, let's call into it here. // We need the task id's pre-computed for custom task executions because when OnDidStartTask // is invoked, we have to be able to map it back to our data. - taskIdPromises.push(this.addCustomExecution2(taskDTO, task)); + taskIdPromises.push(this.addCustomExecution2(taskDTO, task, true)); } else { console.warn('Only custom execution tasks supported.'); } diff --git a/src/vs/workbench/api/node/extHostTask.ts b/src/vs/workbench/api/node/extHostTask.ts index f1379251ab1..71d8b818b72 100644 --- a/src/vs/workbench/api/node/extHostTask.ts +++ b/src/vs/workbench/api/node/extHostTask.ts @@ -57,7 +57,7 @@ export class ExtHostTask extends ExtHostTaskBase { // in the provided custom execution map that is cleaned up after the // task is executed. if (CustomExecution2DTO.is(dto.execution)) { - await this.addCustomExecution2(dto, task); + await this.addCustomExecution2(dto, task, false); } return this._proxy.$executeTask(dto).then(value => this.getTaskExecution(value, task)); @@ -80,7 +80,7 @@ export class ExtHostTask extends ExtHostTaskBase { // The ID is calculated on the main thread task side, so, let's call into it here. // We need the task id's pre-computed for custom task executions because when OnDidStartTask // is invoked, we have to be able to map it back to our data. - taskIdPromises.push(this.addCustomExecution2(taskDTO, task)); + taskIdPromises.push(this.addCustomExecution2(taskDTO, task, true)); } } } diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-down-dark.svg b/src/vs/workbench/browser/parts/panel/media/chevron-down-dark.svg deleted file mode 100644 index a1df6a8d44a..00000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-down-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-down-hc.svg b/src/vs/workbench/browser/parts/panel/media/chevron-down-hc.svg deleted file mode 100644 index 4f2ec146927..00000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-down-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-down-light.svg b/src/vs/workbench/browser/parts/panel/media/chevron-down-light.svg deleted file mode 100644 index e60e357f573..00000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-down-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-left-dark.svg b/src/vs/workbench/browser/parts/panel/media/chevron-left-dark.svg deleted file mode 100644 index dbc37784a70..00000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-left-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-left-hc.svg b/src/vs/workbench/browser/parts/panel/media/chevron-left-hc.svg deleted file mode 100644 index 3767c200513..00000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-left-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-left-light.svg b/src/vs/workbench/browser/parts/panel/media/chevron-left-light.svg deleted file mode 100644 index 2bd4c96832f..00000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-left-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-right-dark.svg b/src/vs/workbench/browser/parts/panel/media/chevron-right-dark.svg deleted file mode 100644 index f518fc1632a..00000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-right-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-right-hc.svg b/src/vs/workbench/browser/parts/panel/media/chevron-right-hc.svg deleted file mode 100644 index 40ba72b7086..00000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-right-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-right-light.svg b/src/vs/workbench/browser/parts/panel/media/chevron-right-light.svg deleted file mode 100644 index 0d746558a4f..00000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-right-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-up-dark.svg b/src/vs/workbench/browser/parts/panel/media/chevron-up-dark.svg deleted file mode 100644 index 5b9da8932e1..00000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-up-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-up-hc.svg b/src/vs/workbench/browser/parts/panel/media/chevron-up-hc.svg deleted file mode 100644 index 13bad537364..00000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-up-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/chevron-up-light.svg b/src/vs/workbench/browser/parts/panel/media/chevron-up-light.svg deleted file mode 100644 index 5498cda5bc4..00000000000 --- a/src/vs/workbench/browser/parts/panel/media/chevron-up-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/browser/parts/panel/media/close-dark.svg b/src/vs/workbench/browser/parts/panel/media/close-dark.svg deleted file mode 100644 index e0475f7b85a..00000000000 --- a/src/vs/workbench/browser/parts/panel/media/close-dark.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/parts/panel/media/close-hc.svg b/src/vs/workbench/browser/parts/panel/media/close-hc.svg deleted file mode 100644 index 64618b61760..00000000000 --- a/src/vs/workbench/browser/parts/panel/media/close-hc.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/parts/panel/media/close-light.svg b/src/vs/workbench/browser/parts/panel/media/close-light.svg deleted file mode 100644 index 3bd44674699..00000000000 --- a/src/vs/workbench/browser/parts/panel/media/close-light.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/src/vs/workbench/browser/parts/panel/media/panelpart.css b/src/vs/workbench/browser/parts/panel/media/panelpart.css index 9dc9bb2000f..fcb9fc8edfa 100644 --- a/src/vs/workbench/browser/parts/panel/media/panelpart.css +++ b/src/vs/workbench/browser/parts/panel/media/panelpart.css @@ -123,69 +123,3 @@ min-width: 110px; margin-right: 10px; } - -/* Close */ -.monaco-workbench .hide-panel-action { - background: url('close-light.svg') center center no-repeat; -} - -.vs-dark .monaco-workbench .hide-panel-action { - background: url('close-dark.svg') center center no-repeat; -} - -.hc-black .monaco-workbench .hide-panel-action { - background: url('close-hc.svg') center center no-repeat; -} - - -/* Up */ -.monaco-workbench .maximize-panel-action { - background-image: url('chevron-up-light.svg'); -} - -.vs-dark .monaco-workbench .maximize-panel-action { - background-image: url('chevron-up-dark.svg'); -} - -.hc-black .monaco-workbench .maximize-panel-action { - background-image: url('chevron-up-hc.svg'); -} - -/* Down */ -.monaco-workbench .minimize-panel-action { - background-image: url('chevron-down-light.svg'); -} - -.vs-dark .monaco-workbench .minimize-panel-action { - background-image: url('chevron-down-dark.svg'); -} - -.hc-black .monaco-workbench .minimize-panel-action { - background-image: url('chevron-down-hc.svg'); -} - -/* Left */ -.monaco-workbench .panel.right .maximize-panel-action { - background-image: url('chevron-left-light.svg'); -} - -.vs-dark .monaco-workbench .panel.right .maximize-panel-action { - background-image: url('chevron-left-dark.svg'); -} - -.hc-black .monaco-workbench .panel.right .maximize-panel-action { - background-image: url('chevron-left-hc.svg'); -} - -/* Right */ -.monaco-workbench .panel.right .minimize-panel-action { - background-image: url('chevron-right-light.svg'); -} - -.vs-dark .monaco-workbench .panel.right .minimize-panel-action { - background-image: url('chevron-right-dark.svg'); -} - -.hc-black .monaco-workbench .panel.right .minimize-panel-action { - background-image: url('chevron-right-hc.svg'); -} diff --git a/src/vs/workbench/browser/parts/panel/panelActions.ts b/src/vs/workbench/browser/parts/panel/panelActions.ts index 904f8a364ba..c8419eed7e7 100644 --- a/src/vs/workbench/browser/parts/panel/panelActions.ts +++ b/src/vs/workbench/browser/parts/panel/panelActions.ts @@ -28,7 +28,7 @@ export class ClosePanelAction extends Action { name: string, @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService ) { - super(id, name, 'hide-panel-action'); + super(id, name, 'codicon-close'); } run(): Promise { @@ -141,11 +141,11 @@ export class ToggleMaximizedPanelAction extends Action { @IWorkbenchLayoutService private readonly layoutService: IWorkbenchLayoutService, @IEditorGroupsService editorGroupsService: IEditorGroupsService ) { - super(id, label, layoutService.isPanelMaximized() ? 'minimize-panel-action' : 'maximize-panel-action'); + super(id, label, layoutService.isPanelMaximized() ? 'codicon-chevron-down' : 'codicon-chevron-up'); this.toDispose.add(editorGroupsService.onDidLayout(() => { const maximized = this.layoutService.isPanelMaximized(); - this.class = maximized ? 'minimize-panel-action' : 'maximize-panel-action'; + this.class = maximized ? 'codicon-chevron-down' : 'codicon-chevron-up'; this.label = maximized ? ToggleMaximizedPanelAction.RESTORE_LABEL : ToggleMaximizedPanelAction.MAXIMIZE_LABEL; })); } diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index a934e456352..394f522bd67 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -4,18 +4,17 @@ *--------------------------------------------------------------------------------------------*/ import 'vs/css!./media/titlebarpart'; -import { dirname, posix } from 'vs/base/common/path'; import * as resources from 'vs/base/common/resources'; import { Part } from 'vs/workbench/browser/part'; import { ITitleService, ITitleProperties } from 'vs/workbench/services/title/common/titleService'; import { getZoomFactor } from 'vs/base/browser/browser'; -import { IWindowService, IWindowsService, MenuBarVisibility, getTitleBarStyle } from 'vs/platform/windows/common/windows'; +import { IWindowService, MenuBarVisibility, getTitleBarStyle } from 'vs/platform/windows/common/windows'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { IAction, Action } from 'vs/base/common/actions'; +import { IAction } from 'vs/base/common/actions'; import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { DisposableStore, dispose } from 'vs/base/common/lifecycle'; import * as nls from 'vs/nls'; import { EditorInput, toResource, Verbosity, SideBySideEditor } from 'vs/workbench/common/editor'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -29,7 +28,7 @@ import { trim } from 'vs/base/common/strings'; import { EventType, EventHelper, Dimension, isAncestor, hide, show, removeClass, addClass, append, $, addDisposableListener, runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { template, getBaseLabel } from 'vs/base/common/labels'; +import { template } from 'vs/base/common/labels'; import { ILabelService } from 'vs/platform/label/common/label'; import { Event, Emitter } from 'vs/base/common/event'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -37,6 +36,9 @@ import { Parts, IWorkbenchLayoutService } from 'vs/workbench/services/layout/bro import { RunOnceScheduler } from 'vs/base/common/async'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { Schemas } from 'vs/base/common/network'; +import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; +import { IMenuService, IMenu, MenuId } from 'vs/platform/actions/common/actions'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; export class TitlebarPart extends Part implements ITitleService { @@ -71,7 +73,6 @@ export class TitlebarPart extends Part implements ITitleService { private lastLayoutDimensions: Dimension; private pendingTitle: string; - private representedFileName: string; private isInactive: boolean; @@ -80,11 +81,12 @@ export class TitlebarPart extends Part implements ITitleService { private titleUpdater: RunOnceScheduler = this._register(new RunOnceScheduler(() => this.doUpdateTitle(), 0)); + private contextMenu: IMenu; + constructor( @IContextMenuService private readonly contextMenuService: IContextMenuService, @IWindowService private readonly windowService: IWindowService, @IConfigurationService private readonly configurationService: IConfigurationService, - @IWindowsService private readonly windowsService: IWindowsService, @IEditorService private readonly editorService: IEditorService, @IWorkbenchEnvironmentService private readonly environmentService: IWorkbenchEnvironmentService, @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @@ -92,10 +94,14 @@ export class TitlebarPart extends Part implements ITitleService { @IThemeService themeService: IThemeService, @ILabelService private readonly labelService: ILabelService, @IStorageService storageService: IStorageService, - @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService + @IWorkbenchLayoutService layoutService: IWorkbenchLayoutService, + @IMenuService menuService: IMenuService, + @IContextKeyService contextKeyService: IContextKeyService ) { super(Parts.TITLEBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); + this.contextMenu = this._register(menuService.createMenu(MenuId.TitleBarContext, contextKeyService)); + this.registerListeners(); } @@ -181,9 +187,6 @@ export class TitlebarPart extends Part implements ITitleService { // Apply to window this.windowService.setRepresentedFilename(path); - - // Keep for context menu - this.representedFileName = path; } private doUpdateTitle(): void { @@ -376,7 +379,6 @@ export class TitlebarPart extends Part implements ITitleService { if (!isMacintosh && !isWeb) { this.windowControls = append(this.element, $('div.window-controls-container')); - // Minimize const minimizeIconContainer = append(this.windowControls, $('div.window-icon-bg')); const minimizeIcon = append(minimizeIconContainer, $('div.window-icon')); @@ -503,44 +505,16 @@ export class TitlebarPart extends Part implements ITitleService { const event = new StandardMouseEvent(e); const anchor = { x: event.posx, y: event.posy }; - // Show menu - const actions = this.getContextMenuActions(); - if (actions.length) { - this.contextMenuService.showContextMenu({ - getAnchor: () => anchor, - getActions: () => actions, - onHide: () => actions.forEach(a => a.dispose()) - }); - } - } - - private getContextMenuActions(): IAction[] { + // Fill in contributed actions const actions: IAction[] = []; + const actionsDisposable = createAndFillInContextMenuActions(this.contextMenu, undefined, actions, this.contextMenuService); - if (this.representedFileName) { - const segments = this.representedFileName.split(posix.sep); - for (let i = segments.length; i > 0; i--) { - const isFile = (i === segments.length); - - let pathOffset = i; - if (!isFile) { - pathOffset++; // for segments which are not the file name we want to open the folder - } - - const path = segments.slice(0, pathOffset).join(posix.sep); - - let label: string; - if (!isFile) { - label = getBaseLabel(dirname(path)); - } else { - label = getBaseLabel(path); - } - - actions.push(new ShowItemInFolderAction(path, label || posix.sep, this.windowsService)); - } - } - - return actions; + // Show it + this.contextMenuService.showContextMenu({ + getAnchor: () => anchor, + getActions: () => actions, + onHide: () => dispose(actionsDisposable) + }); } private adjustTitleMarginToCenter(): void { @@ -605,17 +579,6 @@ export class TitlebarPart extends Part implements ITitleService { } } -class ShowItemInFolderAction extends Action { - - constructor(private path: string, label: string, private windowsService: IWindowsService) { - super('showItemInFolder.action.id', label); - } - - run(): Promise { - return this.windowsService.showItemInFolder(URI.file(this.path)); - } -} - registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { const titlebarActiveFg = theme.getColor(TITLE_BAR_ACTIVE_FOREGROUND); if (titlebarActiveFg) { diff --git a/src/vs/workbench/browser/style.ts b/src/vs/workbench/browser/style.ts index dbb318045f5..deaaf4953ed 100644 --- a/src/vs/workbench/browser/style.ts +++ b/src/vs/workbench/browser/style.ts @@ -6,11 +6,17 @@ import 'vs/css!./media/style'; import { registerThemingParticipant, ITheme, ICssStyleCollector, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; -import { foreground, selectionBackground, focusBorder, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listHighlightForeground, inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry'; +import { iconForeground, foreground, selectionBackground, focusBorder, scrollbarShadow, scrollbarSliderActiveBackground, scrollbarSliderBackground, scrollbarSliderHoverBackground, listHighlightForeground, inputPlaceholderForeground } from 'vs/platform/theme/common/colorRegistry'; import { WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme'; registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { + // Icon defaults + const iconForegroundColor = theme.getColor(iconForeground); + if (iconForegroundColor) { + collector.addRule(`.monaco-workbench .codicon { color: ${iconForegroundColor}; }`); + } + // Foreground const windowForeground = theme.getColor(foreground); if (windowForeground) { @@ -136,4 +142,5 @@ registerThemingParticipant((theme: ITheme, collector: ICssStyleCollector) => { } `); } + }); diff --git a/src/vs/workbench/browser/web.simpleservices.ts b/src/vs/workbench/browser/web.simpleservices.ts index 86e496ceedd..53fb58d10a2 100644 --- a/src/vs/workbench/browser/web.simpleservices.ts +++ b/src/vs/workbench/browser/web.simpleservices.ts @@ -318,8 +318,6 @@ registerSingleton(IWindowService, SimpleWindowService); export class SimpleWindowsService implements IWindowsService { _serviceBrand: undefined; - windowCount = 1; - readonly onWindowOpen: Event = Event.None; readonly onWindowFocus: Event = Event.None; readonly onWindowBlur: Event = Event.None; @@ -454,49 +452,6 @@ export class SimpleWindowsService implements IWindowsService { } openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise { - - // we pass the "ParsedArgs" as query parameters of the URL - - let newAddress = `${document.location.origin}/?`; - let gotFolder = false; - - const addQueryParameter = (key: string, value: string) => { - const lastChar = newAddress.charAt(newAddress.length - 1); - if (lastChar !== '?' && lastChar !== '&') { - newAddress += '&'; - } - newAddress += `${key}=${encodeURIComponent(value)}`; - }; - - const f = args['folder-uri']; - if (f) { - const u = URI.parse(f[0]); - gotFolder = true; - addQueryParameter('folder', u.path); - } - if (!gotFolder) { - // request empty window - addQueryParameter('ew', 'true'); - } - - const ep = args['extensionDevelopmentPath']; - if (ep) { - let u = ep[0]; - addQueryParameter('edp', u); - } - - const di = args['debugId']; - if (di) { - addQueryParameter('di', di); - } - - const ibe = args['inspect-brk-extensions']; - if (ibe) { - addQueryParameter('ibe', ibe); - } - - window.open(newAddress); - return Promise.resolve(); } @@ -504,18 +459,6 @@ export class SimpleWindowsService implements IWindowsService { return Promise.resolve([]); } - getWindowCount(): Promise { - return Promise.resolve(this.windowCount); - } - - log(_severity: string, _args: string[]): Promise { - return Promise.resolve(); - } - - showItemInFolder(_path: URI): Promise { - return Promise.resolve(); - } - newWindowTab(): Promise { return Promise.resolve(); } diff --git a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts index 776eb29464f..2a5c59429a3 100644 --- a/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts +++ b/src/vs/workbench/contrib/customEditor/browser/customEditorInput.ts @@ -17,7 +17,7 @@ import { WebviewEditorOverlay } from 'vs/workbench/contrib/webview/browser/webvi import { WebviewInput } from 'vs/workbench/contrib/webview/browser/webviewEditorInput'; import { IWebviewEditorService } from 'vs/workbench/contrib/webview/browser/webviewEditorService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { promptSave } from 'vs/workbench/services/textfile/common/textFileService'; +import { promptSave } from 'vs/workbench/services/textfile/browser/textFileService'; export class CustomFileEditorInput extends WebviewInput { diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index 8b613a2058a..fda9ec6e6ea 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -28,12 +28,14 @@ import { getBreakpointMessageAndClassName } from 'vs/workbench/contrib/debug/bro import { generateUuid } from 'vs/base/common/uuid'; import { memoize } from 'vs/base/common/decorators'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; +import { distinct } from 'vs/base/common/arrays'; +import { RunOnceScheduler } from 'vs/base/common/async'; const $ = dom.$; interface IBreakpointDecoration { decorationId: string; - breakpointId: string; + breakpoint: IBreakpoint; range: Range; inlineWidget?: InlineBreakpointWidget; } @@ -84,6 +86,39 @@ function getBreakpointDecorationOptions(model: ITextModel, breakpoint: IBreakpoi }; } +async function createCandidateDecorations(model: ITextModel, breakpointDecorations: IBreakpointDecoration[], debugService: IDebugService): Promise<{ range: Range; options: IModelDecorationOptions; breakpoint: IBreakpoint | undefined }[]> { + const lineNumbers = distinct(breakpointDecorations.map(bpd => bpd.range.startLineNumber)); + const result: { range: Range; options: IModelDecorationOptions; breakpoint: IBreakpoint | undefined }[] = []; + const session = debugService.getViewModel().focusedSession; + if (session && session.capabilities.supportsBreakpointLocationsRequest) { + await Promise.all(lineNumbers.map(async lineNumber => { + const positions = await session.breakpointsLocations(model.uri, lineNumber); + if (positions.length > 1) { + // Do not render candidates if there is only one, since it is already covered by the line breakpoint + positions.forEach(p => { + const range = new Range(p.lineNumber, p.column, p.lineNumber, p.column + 1); + const breakpointAtPosition = breakpointDecorations.filter(bpd => bpd.range.equalsRange(range)).pop(); + if (breakpointAtPosition && breakpointAtPosition.inlineWidget) { + // Space already occupied, do not render candidate. + return; + } + result.push({ + range, + options: { + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + beforeContentClassName: `debug-breakpoint-placeholder` + }, + breakpoint: breakpointAtPosition ? breakpointAtPosition.breakpoint : undefined + }); + }); + } + })); + } + + return result; +} + + class BreakpointEditorContribution implements IBreakpointEditorContribution { private breakpointHintDecoration: string[] = []; @@ -91,8 +126,10 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { private breakpointWidgetVisible: IContextKey; private toDispose: IDisposable[] = []; private ignoreDecorationsChangedEvent = false; - private ignoreFirstBreakpointsChangeEvent = false; + private ignoreBreakpointsChangeEvent = false; private breakpointDecorations: IBreakpointDecoration[] = []; + private candidateDecorations: { decorationId: string, inlineWidget: InlineBreakpointWidget }[] = []; + private setDecorationsScheduler: RunOnceScheduler; constructor( private readonly editor: ICodeEditor, @@ -104,6 +141,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { ) { this.breakpointWidgetVisible = CONTEXT_BREAKPOINT_WIDGET_VISIBLE.bindTo(contextKeyService); this.registerListeners(); + this.setDecorationsScheduler = new RunOnceScheduler(() => this.setDecorations(), 30); } getId(): string { @@ -193,16 +231,14 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { this.ensureBreakpointHintDecoration(-1); })); - this.toDispose.push(this.editor.onDidChangeModel(() => { + this.toDispose.push(this.editor.onDidChangeModel(async () => { this.closeBreakpointWidget(); - this.setDecorations(); + await this.setDecorations(); })); - this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(() => { - if (this.ignoreFirstBreakpointsChangeEvent) { - this.ignoreFirstBreakpointsChangeEvent = false; - return; + this.toDispose.push(this.debugService.getModel().onDidChangeBreakpoints(async () => { + if (!this.ignoreBreakpointsChangeEvent && !this.setDecorationsScheduler.isScheduled()) { + this.setDecorationsScheduler.schedule(); } - this.setDecorations(); })); this.toDispose.push(this.editor.onDidChangeModelDecorations(() => this.onModelDecorationsChanged())); } @@ -274,7 +310,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { nls.localize('addLogPoint', "Add Logpoint..."), undefined, true, - () => Promise.resolve(this.showBreakpointWidget(lineNumber, BreakpointWidgetContext.LOG_MESSAGE)) + () => Promise.resolve(this.showBreakpointWidget(lineNumber, column, BreakpointWidgetContext.LOG_MESSAGE)) )); } @@ -311,7 +347,7 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { this.breakpointHintDecoration = this.editor.deltaDecorations(this.breakpointHintDecoration, newDecoration); } - private setDecorations(): void { + private async setDecorations(): Promise { if (!this.editor.hasModel()) { return; } @@ -319,11 +355,13 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { const activeCodeEditor = this.editor; const model = activeCodeEditor.getModel(); const breakpoints = this.debugService.getModel().getBreakpoints({ uri: model.uri }); - const desiredDecorations = createBreakpointDecorations(model, breakpoints, this.debugService); + const desiredBreakpointDecorations = createBreakpointDecorations(model, breakpoints, this.debugService); try { this.ignoreDecorationsChangedEvent = true; - const decorationIds = activeCodeEditor.deltaDecorations(this.breakpointDecorations.map(bpd => bpd.decorationId), desiredDecorations); + + // Set breakpoint decorations + const decorationIds = activeCodeEditor.deltaDecorations(this.breakpointDecorations.map(bpd => bpd.decorationId), desiredBreakpointDecorations); this.breakpointDecorations.forEach(bpd => { if (bpd.inlineWidget) { bpd.inlineWidget.dispose(); @@ -333,19 +371,37 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { let inlineWidget: InlineBreakpointWidget | undefined = undefined; const breakpoint = breakpoints[index]; if (breakpoint.column) { - inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, desiredDecorations[index].options.glyphMarginClassName, breakpoint, this.debugService, this.contextMenuService, () => this.getContextMenuActions([breakpoint], activeCodeEditor.getModel().uri, breakpoint.lineNumber, breakpoint.column)); + inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, desiredBreakpointDecorations[index].options.glyphMarginClassName, breakpoint, this.debugService, this.contextMenuService, () => this.getContextMenuActions([breakpoint], activeCodeEditor.getModel().uri, breakpoint.lineNumber, breakpoint.column)); } return { decorationId, - breakpointId: breakpoint.getId(), - range: desiredDecorations[index].range, + breakpoint, + range: desiredBreakpointDecorations[index].range, inlineWidget }; }); + } finally { this.ignoreDecorationsChangedEvent = false; } + + // Set breakpoint candidate decorations + const desiredCandidateDecorations = await createCandidateDecorations(this.editor.getModel(), this.breakpointDecorations, this.debugService); + const candidateDecorationIds = this.editor.deltaDecorations(this.candidateDecorations.map(c => c.decorationId), desiredCandidateDecorations); + this.candidateDecorations.forEach(candidate => { + candidate.inlineWidget.dispose(); + }); + this.candidateDecorations = candidateDecorationIds.map((decorationId, index) => { + const candidate = desiredCandidateDecorations[index]; + const cssClass = candidate.breakpoint ? undefined : 'debug-breakpoint-disabled'; + const inlineWidget = new InlineBreakpointWidget(activeCodeEditor, decorationId, cssClass, candidate.breakpoint, this.debugService, this.contextMenuService, () => this.getContextMenuActions([], activeCodeEditor.getModel().uri, candidate.range.startLineNumber, candidate.range.startColumn)); + + return { + decorationId, + inlineWidget + }; + }); } private async onModelDecorationsChanged(): Promise { @@ -370,28 +426,26 @@ class BreakpointEditorContribution implements IBreakpointEditorContribution { } const data = new Map(); - const breakpoints = this.debugService.getModel().getBreakpoints(); for (let i = 0, len = this.breakpointDecorations.length; i < len; i++) { const breakpointDecoration = this.breakpointDecorations[i]; const decorationRange = model.getDecorationRange(breakpointDecoration.decorationId); // check if the line got deleted. if (decorationRange) { - const breakpoint = breakpoints.filter(bp => bp.getId() === breakpointDecoration.breakpointId).pop(); // since we know it is collapsed, it cannot grow to multiple lines - if (breakpoint) { - data.set(breakpoint.getId(), { + if (breakpointDecoration.breakpoint) { + data.set(breakpointDecoration.breakpoint.getId(), { lineNumber: decorationRange.startLineNumber, - column: breakpoint.column ? decorationRange.startColumn : undefined, + column: breakpointDecoration.breakpoint.column ? decorationRange.startColumn : undefined, }); } } } try { - this.ignoreFirstBreakpointsChangeEvent = true; + this.ignoreBreakpointsChangeEvent = true; await this.debugService.updateBreakpoints(model.uri, data, true); } finally { - this.ignoreFirstBreakpointsChangeEvent = false; + this.ignoreBreakpointsChangeEvent = false; } } @@ -495,6 +549,8 @@ class InlineBreakpointWidget implements IContentWidget, IDisposable { if (!this.range) { return null; } + // Workaround: since the content widget can not be placed before the first column we need to force the left position + dom.toggleClass(this.domNode, 'line-start', this.range.startColumn === 1); return { position: { lineNumber: this.range.startLineNumber, column: this.range.startColumn - 1 }, diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 92c18c46873..928e9d322ed 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -22,7 +22,8 @@ import { IWorkspaceFolder, IWorkspaceContextService } from 'vs/platform/workspac import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { RunOnceScheduler } from 'vs/base/common/async'; import { generateUuid } from 'vs/base/common/uuid'; -import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; +import { IWindowService } from 'vs/platform/windows/common/windows'; +import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { normalizeDriveLetter } from 'vs/base/common/labels'; import { Range } from 'vs/editor/common/core/range'; @@ -74,7 +75,7 @@ export class DebugSession implements IDebugSession { @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService, @INotificationService private readonly notificationService: INotificationService, @IProductService private readonly productService: IProductService, - @IWindowsService private readonly windowsService: IWindowsService, + @IExtensionHostDebugService private readonly extensionHostDebugService: IExtensionHostDebugService, @IOpenerService private readonly openerService: IOpenerService ) { this.id = generateUuid(); @@ -186,7 +187,7 @@ export class DebugSession implements IDebugSession { return dbgr.createDebugAdapter(this).then(debugAdapter => { - this.raw = new RawDebugSession(debugAdapter, dbgr, this.telemetryService, customTelemetryService, this.windowsService, this.openerService); + this.raw = new RawDebugSession(debugAdapter, dbgr, this.telemetryService, customTelemetryService, this.extensionHostDebugService, this.openerService); return this.raw.start().then(() => { @@ -375,7 +376,7 @@ export class DebugSession implements IDebugSession { const response = await this.raw.breakpointLocations({ source, line: lineNumber }); const positions = response.body.breakpoints.map(bp => ({ lineNumber: bp.line, column: bp.column || 1 })); - return distinct(positions, p => p.toString()); + return distinct(positions, p => `${p.lineNumber}:${p.column}`); } return Promise.reject(new Error('no debug adapter')); } diff --git a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts index 49b8815ca93..af19c1bbe2e 100644 --- a/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts +++ b/src/vs/workbench/contrib/debug/browser/extensionHostDebugService.ts @@ -5,7 +5,7 @@ import { ExtensionHostDebugChannelClient, ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IEnvironmentService, ParsedArgs } from 'vs/platform/environment/common/environment'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { IDebugHelperService } from 'vs/workbench/contrib/debug/common/debug'; @@ -13,8 +13,10 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { TelemetryService } from 'vs/platform/telemetry/common/telemetryService'; import { IChannel } from 'vs/base/parts/ipc/common/ipc'; import { Event } from 'vs/base/common/event'; +import { IProcessEnvironment } from 'vs/base/common/platform'; +import { URI } from 'vs/base/common/uri'; -class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient { +class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient implements IExtensionHostDebugService { constructor( @IRemoteAgentService remoteAgentService: IRemoteAgentService, @@ -45,6 +47,52 @@ class BrowserExtensionHostDebugService extends ExtensionHostDebugChannelClient { } })); } + + openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise { + // we pass the "ParsedArgs" as query parameters of the URL + + let newAddress = `${document.location.origin}/?`; + let gotFolder = false; + + const addQueryParameter = (key: string, value: string) => { + const lastChar = newAddress.charAt(newAddress.length - 1); + if (lastChar !== '?' && lastChar !== '&') { + newAddress += '&'; + } + newAddress += `${key}=${encodeURIComponent(value)}`; + }; + + const f = args['folder-uri']; + if (f) { + const u = URI.parse(f[0]); + gotFolder = true; + addQueryParameter('folder', u.path); + } + if (!gotFolder) { + // request empty window + addQueryParameter('ew', 'true'); + } + + const ep = args['extensionDevelopmentPath']; + if (ep) { + let u = ep[0]; + addQueryParameter('edp', u); + } + + const di = args['debugId']; + if (di) { + addQueryParameter('di', di); + } + + const ibe = args['inspect-brk-extensions']; + if (ibe) { + addQueryParameter('ibe', ibe); + } + + window.open(newAddress); + + return Promise.resolve(); + } } registerSingleton(IExtensionHostDebugService, BrowserExtensionHostDebugService); diff --git a/src/vs/workbench/contrib/debug/browser/media/clear-dark.svg b/src/vs/workbench/contrib/debug/browser/media/clear-dark.svg deleted file mode 100644 index 04d64ab41ca..00000000000 --- a/src/vs/workbench/contrib/debug/browser/media/clear-dark.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/clear-hc.svg b/src/vs/workbench/contrib/debug/browser/media/clear-hc.svg deleted file mode 100644 index 44a41edd3b3..00000000000 --- a/src/vs/workbench/contrib/debug/browser/media/clear-hc.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/clear-light.svg b/src/vs/workbench/contrib/debug/browser/media/clear-light.svg deleted file mode 100644 index f6a51c856f0..00000000000 --- a/src/vs/workbench/contrib/debug/browser/media/clear-light.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css index 69be074454c..4576abcbe9a 100644 --- a/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css +++ b/src/vs/workbench/contrib/debug/browser/media/debug.contribution.css @@ -21,6 +21,14 @@ background: url('breakpoint-disabled.svg') center center no-repeat; } +.monaco-editor .inline-breakpoint-widget.debug-breakpoint-disabled:hover { + background: url('breakpoint-hint.svg') center center no-repeat; +} + +.monaco-editor .inline-breakpoint-widget.line-start { + left: -0.45em !important; +} + .debug-breakpoint-unverified, .monaco-editor .inline-breakpoint-widget.debug-breakpoint-unverified { background: url('breakpoint-unverified.svg') center center no-repeat; @@ -57,6 +65,7 @@ width: 1.3em; height: 1.3em; margin-left: 0.61em; + cursor: pointer; } .debug-function-breakpoint { diff --git a/src/vs/workbench/contrib/debug/browser/media/repl.css b/src/vs/workbench/contrib/debug/browser/media/repl.css index 9d92c972d48..e7befa20952 100644 --- a/src/vs/workbench/contrib/debug/browser/media/repl.css +++ b/src/vs/workbench/contrib/debug/browser/media/repl.css @@ -88,19 +88,6 @@ font-size: 9px; } -/* Actions */ -.debug-action.clear-repl { - background: url('clear-light.svg') center center no-repeat; -} - -.vs-dark .debug-action.clear-repl { - background: url('clear-dark.svg') center center no-repeat; -} - -.hc-black .debug-action.clear-repl { - background: url('clear-hc.svg') center center no-repeat; -} - /* Output coloring and styling */ .repl .repl-tree .output.expression > .ignore { font-style: italic; diff --git a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts index 53a10ed04c5..45ffb5776a8 100644 --- a/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/rawDebugSession.ts @@ -13,7 +13,7 @@ import { formatPII, isUri } from 'vs/workbench/contrib/debug/common/debugUtils'; import { IDebugAdapter, IConfig, AdapterEndEvent, IDebugger } from 'vs/workbench/contrib/debug/common/debug'; import { createErrorWithActions } from 'vs/base/common/errorsWithActions'; import { ParsedArgs } from 'vs/platform/environment/common/environment'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { URI } from 'vs/base/common/uri'; import { IProcessEnvironment } from 'vs/base/common/platform'; import { env as processEnv } from 'vs/base/common/process'; @@ -79,7 +79,7 @@ export class RawDebugSession implements IDisposable { dbgr: IDebugger, private readonly telemetryService: ITelemetryService, public readonly customTelemetryService: ITelemetryService | undefined, - private readonly windowsService: IWindowsService, + private readonly extensionHostDebugService: IExtensionHostDebugService, private readonly openerService: IOpenerService ) { @@ -632,7 +632,7 @@ export class RawDebugSession implements IDisposable { Object.keys(env).filter(k => env[k] === null).forEach(key => delete env[key]); } - return this.windowsService.openExtensionDevelopmentHostWindow(args, env); + return this.extensionHostDebugService.openExtensionDevelopmentHostWindow(args, env); } private send(command: string, args: any, token?: CancellationToken, timeout?: number): Promise { diff --git a/src/vs/workbench/contrib/debug/browser/repl.ts b/src/vs/workbench/contrib/debug/browser/repl.ts index ba438198881..ec21ca71f2c 100644 --- a/src/vs/workbench/contrib/debug/browser/repl.ts +++ b/src/vs/workbench/contrib/debug/browser/repl.ts @@ -1008,7 +1008,7 @@ export class ClearReplAction extends Action { constructor(id: string, label: string, @IPanelService private readonly panelService: IPanelService ) { - super(id, label, 'debug-action clear-repl'); + super(id, label, 'debug-action codicon-clear-all'); } run(): Promise { diff --git a/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts b/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts index e292a9ecd31..dad63fd4fb0 100644 --- a/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts +++ b/src/vs/workbench/contrib/debug/electron-browser/extensionHostDebugService.ts @@ -7,13 +7,22 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IExtensionHostDebugService } from 'vs/platform/debug/common/extensionHostDebug'; import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService'; import { ExtensionHostDebugChannelClient, ExtensionHostDebugBroadcastChannel } from 'vs/platform/debug/common/extensionHostDebugIpc'; +import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { IProcessEnvironment } from 'vs/base/common/platform'; +import { ParsedArgs } from 'vs/platform/environment/common/environment'; export class ExtensionHostDebugService extends ExtensionHostDebugChannelClient { constructor( - @IMainProcessService readonly windowService: IMainProcessService, + @IMainProcessService readonly mainProcessService: IMainProcessService, + @IWindowsService private readonly windowsService: IWindowsService ) { - super(windowService.getChannel(ExtensionHostDebugBroadcastChannel.ChannelName)); + super(mainProcessService.getChannel(ExtensionHostDebugBroadcastChannel.ChannelName)); + } + + openExtensionDevelopmentHostWindow(args: ParsedArgs, env: IProcessEnvironment): Promise { + // TODO@Isidor use debug IPC channel + return this.windowsService.openExtensionDevelopmentHostWindow(args, env); } } diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index b7bed063f07..db05deb3e72 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -20,7 +20,7 @@ import { ExtensionsWorkbenchService } from 'vs/workbench/contrib/extensions/brow import { OpenExtensionsViewletAction, InstallExtensionsAction, ShowOutdatedExtensionsAction, ShowRecommendedExtensionsAction, ShowRecommendedKeymapExtensionsAction, ShowPopularExtensionsAction, ShowEnabledExtensionsAction, ShowInstalledExtensionsAction, ShowDisabledExtensionsAction, ShowBuiltInExtensionsAction, UpdateAllAction, - EnableAllAction, EnableAllWorkpsaceAction, DisableAllAction, DisableAllWorkpsaceAction, CheckForUpdatesAction, ShowLanguageExtensionsAction, ShowAzureExtensionsAction, EnableAutoUpdateAction, DisableAutoUpdateAction, ConfigureRecommendedExtensionsCommandsContributor, OpenExtensionsFolderAction, InstallVSIXAction, ReinstallAction, InstallSpecificVersionOfExtensionAction + EnableAllAction, EnableAllWorkpsaceAction, DisableAllAction, DisableAllWorkpsaceAction, CheckForUpdatesAction, ShowLanguageExtensionsAction, ShowAzureExtensionsAction, EnableAutoUpdateAction, DisableAutoUpdateAction, ConfigureRecommendedExtensionsCommandsContributor, InstallVSIXAction, ReinstallAction, InstallSpecificVersionOfExtensionAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor } from 'vs/workbench/browser/viewlet'; @@ -43,7 +43,6 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { ExtensionDependencyChecker } from 'vs/workbench/contrib/extensions/browser/extensionsDependencyChecker'; import { CancellationToken } from 'vs/base/common/cancellation'; import { ExtensionType } from 'vs/platform/extensions/common/extensions'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { RemoteExtensionsInstaller } from 'vs/workbench/contrib/extensions/browser/remoteExtensionsInstaller'; import { ExtensionTipsService } from 'vs/workbench/contrib/extensions/browser/extensionTipsService'; @@ -344,7 +343,6 @@ const workbenchRegistry = Registry.as(Workbench class ExtensionsContributions implements IWorkbenchContribution { constructor( - @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService, @IExtensionManagementServerService extensionManagementServerService: IExtensionManagementServerService ) { @@ -362,14 +360,7 @@ class ExtensionsContributions implements IWorkbenchContribution { ) ); } - - if (workbenchEnvironmentService.extensionsPath) { - const openExtensionsFolderActionDescriptor = new SyncActionDescriptor(OpenExtensionsFolderAction, OpenExtensionsFolderAction.ID, OpenExtensionsFolderAction.LABEL); - actionRegistry.registerWorkbenchAction(openExtensionsFolderActionDescriptor, 'Extensions: Open Extensions Folder', ExtensionsLabel); - } - } - } workbenchRegistry.registerWorkbenchContribution(ExtensionsContributions, LifecyclePhase.Starting); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts index b9356f1b35e..a18bcf9b4ba 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsActions.ts @@ -25,7 +25,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { Query } from 'vs/workbench/contrib/extensions/common/extensionQuery'; import { IFileService, IFileContent } from 'vs/platform/files/common/files'; import { IWorkspaceContextService, WorkbenchState, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; +import { IWindowService } from 'vs/platform/windows/common/windows'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { URI } from 'vs/base/common/uri'; import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands'; @@ -44,7 +44,6 @@ import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/w import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; @@ -2777,41 +2776,6 @@ export class EnableAllWorkpsaceAction extends Action { } } -export class OpenExtensionsFolderAction extends Action { - - static readonly ID = 'workbench.extensions.action.openExtensionsFolder'; - static LABEL = localize('openExtensionsFolder', "Open Extensions Folder"); - - constructor( - id: string, - label: string, - @IWindowsService private readonly windowsService: IWindowsService, - @IFileService private readonly fileService: IFileService, - @IEnvironmentService private readonly environmentService: IEnvironmentService - ) { - super(id, label, undefined, true); - } - - run(): Promise { - if (this.environmentService.extensionsPath) { - - const extensionsHome = URI.file(this.environmentService.extensionsPath); - - return Promise.resolve(this.fileService.resolve(extensionsHome)).then(file => { - let itemToShow: URI; - if (file.children && file.children.length > 0) { - itemToShow = file.children[0].resource; - } else { - itemToShow = extensionsHome; - } - - return this.windowsService.showItemInFolder(itemToShow); - }); - } - return Promise.resolve(); - } -} - export class InstallVSIXAction extends Action { static readonly ID = 'workbench.extensions.action.installVSIX'; diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css b/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css index 79d7e01a69c..ed1ba80ba46 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionActions.css @@ -78,7 +78,7 @@ margin: 0.15em; } -.monaco-action-bar .action-item .action-label.system-disable.icon { +.monaco-action-bar .action-item .action-label.system-disable.codicon { opacity: 1; height: 18px; width: 10px; diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts index d47550c32a9..19e5153bb53 100644 --- a/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensions.contribution.ts @@ -8,7 +8,7 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { SyncActionDescriptor, MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; -import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; +import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -22,6 +22,9 @@ import { URI } from 'vs/base/common/uri'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ExtensionsAutoProfiler } from 'vs/workbench/contrib/extensions/electron-browser/extensionsAutoProfiler'; import { registerAndGetAmdImageURL } from 'vs/base/common/amd'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { OpenExtensionsFolderAction } from 'vs/workbench/contrib/extensions/electron-browser/extensionsActions'; +import { ExtensionsLabel } from 'vs/platform/extensionManagement/common/extensionManagement'; // Singletons registerSingleton(IExtensionHostProfileService, ExtensionHostProfileService, true); @@ -57,6 +60,20 @@ const actionRegistry = Registry.as(WorkbenchActionExte actionRegistry.registerWorkbenchAction(new SyncActionDescriptor(ShowRuntimeExtensionsAction, ShowRuntimeExtensionsAction.ID, ShowRuntimeExtensionsAction.LABEL), 'Show Running Extensions', localize('developer', "Developer")); +class ExtensionsContributions implements IWorkbenchContribution { + + constructor( + @IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService + ) { + if (workbenchEnvironmentService.extensionsPath) { + const openExtensionsFolderActionDescriptor = new SyncActionDescriptor(OpenExtensionsFolderAction, OpenExtensionsFolderAction.ID, OpenExtensionsFolderAction.LABEL); + actionRegistry.registerWorkbenchAction(openExtensionsFolderActionDescriptor, 'Extensions: Open Extensions Folder', ExtensionsLabel); + } + } +} + +workbenchRegistry.registerWorkbenchContribution(ExtensionsContributions, LifecyclePhase.Starting); + // Register Commands CommandsRegistry.registerCommand(DebugExtensionHostAction.ID, (accessor: ServicesAccessor) => { diff --git a/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts new file mode 100644 index 00000000000..b4185a6614f --- /dev/null +++ b/src/vs/workbench/contrib/extensions/electron-browser/extensionsActions.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { localize } from 'vs/nls'; +import { Action } from 'vs/base/common/actions'; +import { IFileService } from 'vs/platform/files/common/files'; +import { URI } from 'vs/base/common/uri'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { Schemas } from 'vs/base/common/network'; + +export class OpenExtensionsFolderAction extends Action { + + static readonly ID = 'workbench.extensions.action.openExtensionsFolder'; + static LABEL = localize('openExtensionsFolder', "Open Extensions Folder"); + + constructor( + id: string, + label: string, + @IElectronService private readonly electronService: IElectronService, + @IFileService private readonly fileService: IFileService, + @IEnvironmentService private readonly environmentService: IEnvironmentService + ) { + super(id, label, undefined, true); + } + + async run(): Promise { + if (this.environmentService.extensionsPath) { + const extensionsHome = URI.file(this.environmentService.extensionsPath); + const file = await this.fileService.resolve(extensionsHome); + + let itemToShow: URI; + if (file.children && file.children.length > 0) { + itemToShow = file.children[0].resource; + } else { + itemToShow = extensionsHome; + } + + if (itemToShow.scheme === Schemas.file) { + return this.electronService.showItemInFolder(itemToShow.fsPath); + } + } + } +} + diff --git a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts index ff484b1f19c..47c9912e899 100644 --- a/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts +++ b/src/vs/workbench/contrib/files/browser/fileActions.contribution.ts @@ -10,11 +10,11 @@ import { revertLocalChangesCommand, acceptLocalChangesCommand, CONFLICT_RESOLUTI import { SyncActionDescriptor, MenuId, MenuRegistry, ILocalizedString } from 'vs/platform/actions/common/actions'; import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; -import { openWindowCommand, REVEAL_IN_OS_COMMAND_ID, COPY_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, OPEN_TO_SIDE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_COMMAND_ID, SAVE_FILE_LABEL, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID, OpenEditorsGroupContext, COMPARE_WITH_SAVED_COMMAND_ID, COMPARE_RESOURCE_COMMAND_ID, SELECT_FOR_COMPARE_COMMAND_ID, ResourceSelectedForCompareContext, REVEAL_IN_OS_LABEL, DirtyEditorContext, COMPARE_SELECTED_COMMAND_ID, REMOVE_ROOT_FOLDER_COMMAND_ID, REMOVE_ROOT_FOLDER_LABEL, SAVE_FILES_COMMAND_ID, COPY_RELATIVE_PATH_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_LABEL, newWindowCommand } from 'vs/workbench/contrib/files/browser/fileCommands'; +import { openWindowCommand, COPY_PATH_COMMAND_ID, REVEAL_IN_EXPLORER_COMMAND_ID, OPEN_TO_SIDE_COMMAND_ID, REVERT_FILE_COMMAND_ID, SAVE_FILE_COMMAND_ID, SAVE_FILE_LABEL, SAVE_FILE_AS_COMMAND_ID, SAVE_FILE_AS_LABEL, SAVE_ALL_IN_GROUP_COMMAND_ID, OpenEditorsGroupContext, COMPARE_WITH_SAVED_COMMAND_ID, COMPARE_RESOURCE_COMMAND_ID, SELECT_FOR_COMPARE_COMMAND_ID, ResourceSelectedForCompareContext, DirtyEditorContext, COMPARE_SELECTED_COMMAND_ID, REMOVE_ROOT_FOLDER_COMMAND_ID, REMOVE_ROOT_FOLDER_LABEL, SAVE_FILES_COMMAND_ID, COPY_RELATIVE_PATH_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_COMMAND_ID, SAVE_FILE_WITHOUT_FORMATTING_LABEL, newWindowCommand } from 'vs/workbench/contrib/files/browser/fileCommands'; import { CommandsRegistry, ICommandHandler } from 'vs/platform/commands/common/commands'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { isWindows, isMacintosh } from 'vs/base/common/platform'; +import { isMacintosh } from 'vs/base/common/platform'; import { FilesExplorerFocusCondition, ExplorerRootContext, ExplorerFolderContext, ExplorerResourceNotReadonlyContext, ExplorerResourceCut, IExplorerService, ExplorerResourceMoveableToTrash, ExplorerViewletVisibleContext } from 'vs/workbench/contrib/files/common/files'; import { ADD_ROOT_FOLDER_COMMAND_ID, ADD_ROOT_FOLDER_LABEL } from 'vs/workbench/browser/actions/workspaceCommands'; import { CLOSE_SAVED_EDITORS_COMMAND_ID, CLOSE_EDITORS_IN_GROUP_COMMAND_ID, CLOSE_EDITOR_COMMAND_ID, CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; @@ -49,6 +49,14 @@ registry.registerWorkbenchAction(new SyncActionDescriptor(ToggleAutoSaveAction, const workspacesCategory = nls.localize('workspaces', "Workspaces"); registry.registerWorkbenchAction(new SyncActionDescriptor(OpenWorkspaceAction, OpenWorkspaceAction.ID, OpenWorkspaceAction.LABEL), 'Workspaces: Open Workspace...', workspacesCategory); +const fileCategory = nls.localize('file', "File"); +if (isMacintosh) { + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open...', fileCategory); +} else { + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileAction, OpenFileAction.ID, OpenFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open File...', fileCategory); + registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_O) }), 'File: Open Folder...', fileCategory); +} + // Commands CommandsRegistry.registerCommand('_files.windowOpen', openWindowCommand); CommandsRegistry.registerCommand('_files.newWindow', newWindowCommand); @@ -162,11 +170,9 @@ const copyRelativePathCommand = { // Editor Title Context Menu appendEditorTitleContextMenuItem(COPY_PATH_COMMAND_ID, copyPathCommand.title, ResourceContextKey.IsFileSystemResource, '1_cutcopypaste'); appendEditorTitleContextMenuItem(COPY_RELATIVE_PATH_COMMAND_ID, copyRelativePathCommand.title, ResourceContextKey.IsFileSystemResource, '1_cutcopypaste'); -appendEditorTitleContextMenuItem(REVEAL_IN_OS_COMMAND_ID, REVEAL_IN_OS_LABEL, ResourceContextKey.Scheme.isEqualTo(Schemas.file)); -appendEditorTitleContextMenuItem(REVEAL_IN_OS_COMMAND_ID, REVEAL_IN_OS_LABEL, ContextKeyExpr.and(IsWebContext.toNegated(), ResourceContextKey.Scheme.isEqualTo(Schemas.userData))); appendEditorTitleContextMenuItem(REVEAL_IN_EXPLORER_COMMAND_ID, nls.localize('revealInSideBar', "Reveal in Side Bar"), ResourceContextKey.IsFileSystemResource); -function appendEditorTitleContextMenuItem(id: string, title: string, when: ContextKeyExpr | undefined, group?: string): void { +export function appendEditorTitleContextMenuItem(id: string, title: string, when: ContextKeyExpr | undefined, group?: string): void { // Menu MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { @@ -202,7 +208,7 @@ function appendSaveConflictEditorTitleAction(id: string, title: string, iconLoca // Menu registration - command palette -function appendToCommandPalette(id: string, title: ILocalizedString, category: ILocalizedString, when?: ContextKeyExpr): void { +export function appendToCommandPalette(id: string, title: ILocalizedString, category: ILocalizedString, when?: ContextKeyExpr): void { MenuRegistry.appendMenuItem(MenuId.CommandPalette, { command: { id, @@ -222,7 +228,6 @@ appendToCommandPalette(SAVE_ALL_IN_GROUP_COMMAND_ID, { value: nls.localize('save appendToCommandPalette(SAVE_FILES_COMMAND_ID, { value: nls.localize('saveFiles', "Save All Files"), original: 'Save All Files' }, category); appendToCommandPalette(REVERT_FILE_COMMAND_ID, { value: nls.localize('revert', "Revert File"), original: 'Revert File' }, category); appendToCommandPalette(COMPARE_WITH_SAVED_COMMAND_ID, { value: nls.localize('compareActiveWithSaved', "Compare Active File with Saved"), original: 'Compare Active File with Saved' }, category); -appendToCommandPalette(REVEAL_IN_OS_COMMAND_ID, { value: REVEAL_IN_OS_LABEL, original: isWindows ? 'Reveal in Explorer' : isMacintosh ? 'Reveal in Finder' : 'Open Containing Folder' }, category); appendToCommandPalette(SAVE_FILE_AS_COMMAND_ID, { value: SAVE_FILE_AS_LABEL, original: 'Save As...' }, category); appendToCommandPalette(CLOSE_EDITOR_COMMAND_ID, { value: nls.localize('closeEditor', "Close Editor"), original: 'Close Editor' }, { value: nls.localize('view', "View"), original: 'View' }); appendToCommandPalette(NEW_FILE_COMMAND_ID, { value: NEW_FILE_LABEL, original: 'New File' }, category, WorkspaceFolderCountContext.notEqualsTo('0')); @@ -242,17 +247,6 @@ MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { when: ResourceContextKey.IsFileSystemResource }); -const revealInOsCommand = { - id: REVEAL_IN_OS_COMMAND_ID, - title: isWindows ? nls.localize('revealInWindows', "Reveal in Explorer") : isMacintosh ? nls.localize('revealInMac', "Reveal in Finder") : nls.localize('openContainer', "Open Containing Folder") -}; -MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { - group: 'navigation', - order: 20, - command: revealInOsCommand, - when: ResourceContextKey.IsFileSystemResource -}); - MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { group: '1_cutcopypaste', order: 10, @@ -420,13 +414,6 @@ MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { when: ContextKeyExpr.and(ExplorerFolderContext.toNegated(), ResourceContextKey.HasResource) }); -MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { - group: 'navigation', - order: 20, - command: revealInOsCommand, - when: ResourceContextKey.Scheme.isEqualTo(Schemas.file) -}); - MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { group: '3_compare', order: 20, diff --git a/src/vs/workbench/contrib/files/browser/fileCommands.ts b/src/vs/workbench/contrib/files/browser/fileCommands.ts index 2ee93fec213..98363ee5a05 100644 --- a/src/vs/workbench/contrib/files/browser/fileCommands.ts +++ b/src/vs/workbench/contrib/files/browser/fileCommands.ts @@ -25,9 +25,8 @@ import { IEditorViewState } from 'vs/editor/common/editorCommon'; import { getCodeEditor } from 'vs/editor/browser/editorBrowser'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; -import { isWindows, isMacintosh } from 'vs/base/common/platform'; +import { isWindows } from 'vs/base/common/platform'; import { ITextModelService } from 'vs/editor/common/services/resolverService'; -import { sequence } from 'vs/base/common/async'; import { getResourceForCommand, getMultiSelectedResources } from 'vs/workbench/contrib/files/browser/files'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; import { getMultiSelectedEditorContexts } from 'vs/workbench/browser/parts/editor/editorCommands'; @@ -46,8 +45,6 @@ import { withUndefinedAsNull } from 'vs/base/common/types'; // Commands -export const REVEAL_IN_OS_COMMAND_ID = 'revealFileInOS'; -export const REVEAL_IN_OS_LABEL = isWindows ? nls.localize('revealInWindows', "Reveal in Explorer") : isMacintosh ? nls.localize('revealInMac', "Reveal in Finder") : nls.localize('openContainer', "Open Containing Folder"); export const REVEAL_IN_EXPLORER_COMMAND_ID = 'revealInExplorer'; export const REVERT_FILE_COMMAND_ID = 'workbench.action.files.revert'; export const OPEN_TO_SIDE_COMMAND_ID = 'explorer.openToSide'; @@ -406,44 +403,6 @@ CommandsRegistry.registerCommand({ } }); -function revealResourcesInOS(resources: URI[], windowsService: IWindowsService, notificationService: INotificationService, workspaceContextService: IWorkspaceContextService): void { - if (resources.length) { - sequence(resources.map(r => () => windowsService.showItemInFolder(r.scheme === Schemas.userData ? r.with({ scheme: Schemas.file }) : r))); - } else if (workspaceContextService.getWorkspace().folders.length) { - windowsService.showItemInFolder(workspaceContextService.getWorkspace().folders[0].uri); - } else { - notificationService.info(nls.localize('openFileToReveal', "Open a file first to reveal")); - } -} - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - id: REVEAL_IN_OS_COMMAND_ID, - weight: KeybindingWeight.WorkbenchContrib, - when: EditorContextKeys.focus.toNegated(), - primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_R, - win: { - primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_R - }, - handler: (accessor: ServicesAccessor, resource: URI | object) => { - const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService)); - revealResourcesInOS(resources, accessor.get(IWindowsService), accessor.get(INotificationService), accessor.get(IWorkspaceContextService)); - } -}); - -KeybindingsRegistry.registerCommandAndKeybindingRule({ - weight: KeybindingWeight.WorkbenchContrib, - when: undefined, - primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_R), - id: 'workbench.action.files.revealActiveFileInWindows', - handler: (accessor: ServicesAccessor) => { - const editorService = accessor.get(IEditorService); - const activeInput = editorService.activeEditor; - const resource = activeInput ? activeInput.getResource() : null; - const resources = resource ? [resource] : []; - revealResourcesInOS(resources, accessor.get(IWindowsService), accessor.get(INotificationService), accessor.get(IWorkspaceContextService)); - } -}); - async function resourcesToClipboard(resources: URI[], relative: boolean, clipboardService: IClipboardService, notificationService: INotificationService, labelService: ILabelService): Promise { if (resources.length) { const lineDelimiter = isWindows ? '\r\n' : '\n'; diff --git a/src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts b/src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts new file mode 100644 index 00000000000..20be6820e3f --- /dev/null +++ b/src/vs/workbench/contrib/files/electron-browser/fileActions.contribution.ts @@ -0,0 +1,83 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { URI } from 'vs/base/common/uri'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { isWindows, isMacintosh } from 'vs/base/common/platform'; +import { Schemas } from 'vs/base/common/network'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; +import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { KeyMod, KeyCode, KeyChord } from 'vs/base/common/keyCodes'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { getMultiSelectedResources } from 'vs/workbench/contrib/files/browser/files'; +import { IListService } from 'vs/platform/list/browser/listService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { revealResourcesInOS } from 'vs/workbench/contrib/files/electron-browser/fileCommands'; +import { MenuRegistry, MenuId } from 'vs/platform/actions/common/actions'; +import { ResourceContextKey } from 'vs/workbench/common/resources'; +import { appendToCommandPalette, appendEditorTitleContextMenuItem } from 'vs/workbench/contrib/files/browser/fileActions.contribution'; + +const REVEAL_IN_OS_COMMAND_ID = 'revealFileInOS'; +const REVEAL_IN_OS_LABEL = isWindows ? nls.localize('revealInWindows', "Reveal in Explorer") : isMacintosh ? nls.localize('revealInMac', "Reveal in Finder") : nls.localize('openContainer', "Open Containing Folder"); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + id: REVEAL_IN_OS_COMMAND_ID, + weight: KeybindingWeight.WorkbenchContrib, + when: EditorContextKeys.focus.toNegated(), + primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KEY_R, + win: { + primary: KeyMod.Shift | KeyMod.Alt | KeyCode.KEY_R + }, + handler: (accessor: ServicesAccessor, resource: URI | object) => { + const resources = getMultiSelectedResources(resource, accessor.get(IListService), accessor.get(IEditorService)); + revealResourcesInOS(resources, accessor.get(IElectronService), accessor.get(INotificationService), accessor.get(IWorkspaceContextService)); + } +}); + +KeybindingsRegistry.registerCommandAndKeybindingRule({ + weight: KeybindingWeight.WorkbenchContrib, + when: undefined, + primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.KEY_R), + id: 'workbench.action.files.revealActiveFileInWindows', + handler: (accessor: ServicesAccessor) => { + const editorService = accessor.get(IEditorService); + const activeInput = editorService.activeEditor; + const resource = activeInput ? activeInput.getResource() : null; + const resources = resource ? [resource] : []; + revealResourcesInOS(resources, accessor.get(IElectronService), accessor.get(INotificationService), accessor.get(IWorkspaceContextService)); + } +}); + +appendEditorTitleContextMenuItem(REVEAL_IN_OS_COMMAND_ID, REVEAL_IN_OS_LABEL, ResourceContextKey.Scheme.isEqualTo(Schemas.file)); + +// Menu registration - open editors + +const revealInOsCommand = { + id: REVEAL_IN_OS_COMMAND_ID, + title: isWindows ? nls.localize('revealInWindows', "Reveal in Explorer") : isMacintosh ? nls.localize('revealInMac', "Reveal in Finder") : nls.localize('openContainer', "Open Containing Folder") +}; +MenuRegistry.appendMenuItem(MenuId.OpenEditorsContext, { + group: 'navigation', + order: 20, + command: revealInOsCommand, + when: ResourceContextKey.IsFileSystemResource +}); + +// Menu registration - explorer + +MenuRegistry.appendMenuItem(MenuId.ExplorerContext, { + group: 'navigation', + order: 20, + command: revealInOsCommand, + when: ResourceContextKey.Scheme.isEqualTo(Schemas.file) +}); + +// Command Palette + +const category = { value: nls.localize('filesCategory', "File"), original: 'File' }; +appendToCommandPalette(REVEAL_IN_OS_COMMAND_ID, { value: REVEAL_IN_OS_LABEL, original: isWindows ? 'Reveal in Explorer' : isMacintosh ? 'Reveal in Finder' : 'Open Containing Folder' }, category); diff --git a/src/vs/workbench/contrib/files/electron-browser/fileCommands.ts b/src/vs/workbench/contrib/files/electron-browser/fileCommands.ts new file mode 100644 index 00000000000..0a6e710462c --- /dev/null +++ b/src/vs/workbench/contrib/files/electron-browser/fileCommands.ts @@ -0,0 +1,31 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { URI } from 'vs/base/common/uri'; +import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; +import { sequence } from 'vs/base/common/async'; +import { Schemas } from 'vs/base/common/network'; +import { INotificationService } from 'vs/platform/notification/common/notification'; +import { IElectronService } from 'vs/platform/electron/node/electron'; + +// Commands + +export function revealResourcesInOS(resources: URI[], electronService: IElectronService, notificationService: INotificationService, workspaceContextService: IWorkspaceContextService): void { + if (resources.length) { + sequence(resources.map(r => async () => { + if (r.scheme === Schemas.file) { + electronService.showItemInFolder(r.fsPath); + } + })); + } else if (workspaceContextService.getWorkspace().folders.length) { + const uri = workspaceContextService.getWorkspace().folders[0].uri; + if (uri.scheme === Schemas.file) { + electronService.showItemInFolder(uri.fsPath); + } + } else { + notificationService.info(nls.localize('openFileToReveal', "Open a file first to reveal")); + } +} diff --git a/src/vs/workbench/contrib/logs/common/logs.contribution.ts b/src/vs/workbench/contrib/logs/common/logs.contribution.ts index 3de9142da6b..ab29875c3c3 100644 --- a/src/vs/workbench/contrib/logs/common/logs.contribution.ts +++ b/src/vs/workbench/contrib/logs/common/logs.contribution.ts @@ -8,7 +8,7 @@ import { join } from 'vs/base/common/path'; import { Registry } from 'vs/platform/registry/common/platform'; import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; -import { SetLogLevelAction, OpenLogsFolderAction, OpenWindowSessionLogFileAction } from 'vs/workbench/contrib/logs/common/logsActions'; +import { SetLogLevelAction, OpenWindowSessionLogFileAction } from 'vs/workbench/contrib/logs/common/logsActions'; import * as Constants from 'vs/workbench/contrib/logs/common/logConstants'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; @@ -64,10 +64,6 @@ class LogOutputChannels extends Disposable implements IWorkbenchContribution { }; registerTelemetryChannel(this.logService.getLevel()); this.logService.onDidChangeLogLevel(registerTelemetryChannel); - - const workbenchActionsRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); - const devCategory = nls.localize('developer', "Developer"); - workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenLogsFolderAction, OpenLogsFolderAction.ID, OpenLogsFolderAction.LABEL), 'Developer: Open Logs Folder', devCategory); } private async registerLogChannel(id: string, label: string, file: URI): Promise { diff --git a/src/vs/workbench/contrib/logs/common/logsActions.ts b/src/vs/workbench/contrib/logs/common/logsActions.ts index 00a100d4c64..34e9eed4475 100644 --- a/src/vs/workbench/contrib/logs/common/logsActions.ts +++ b/src/vs/workbench/contrib/logs/common/logsActions.ts @@ -5,9 +5,6 @@ import * as nls from 'vs/nls'; import { Action } from 'vs/base/common/actions'; -import { join } from 'vs/base/common/path'; -import { IEnvironmentService } from 'vs/platform/environment/common/environment'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; import { ILogService, LogLevel, DEFAULT_LOG_LEVEL } from 'vs/platform/log/common/log'; import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; import { URI } from 'vs/base/common/uri'; @@ -16,23 +13,6 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { dirname, basename, isEqual } from 'vs/base/common/resources'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -export class OpenLogsFolderAction extends Action { - - static ID = 'workbench.action.openLogsFolder'; - static LABEL = nls.localize('openLogsFolder', "Open Logs Folder"); - - constructor(id: string, label: string, - @IEnvironmentService private readonly environmentService: IEnvironmentService, - @IWindowsService private readonly windowsService: IWindowsService, - ) { - super(id, label); - } - - run(): Promise { - return this.windowsService.showItemInFolder(URI.file(join(this.environmentService.logsPath, 'main.log'))); - } -} - export class SetLogLevelAction extends Action { static ID = 'workbench.action.setLogLevel'; diff --git a/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts b/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts new file mode 100644 index 00000000000..f6ceef58f71 --- /dev/null +++ b/src/vs/workbench/contrib/logs/electron-browser/logs.contribution.ts @@ -0,0 +1,14 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions'; +import { SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { OpenLogsFolderAction } from 'vs/workbench/contrib/logs/electron-browser/logsActions'; + +const workbenchActionsRegistry = Registry.as(WorkbenchActionExtensions.WorkbenchActions); +const devCategory = nls.localize('developer', "Developer"); +workbenchActionsRegistry.registerWorkbenchAction(new SyncActionDescriptor(OpenLogsFolderAction, OpenLogsFolderAction.ID, OpenLogsFolderAction.LABEL), 'Developer: Open Logs Folder', devCategory); diff --git a/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts b/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts new file mode 100644 index 00000000000..547558b7deb --- /dev/null +++ b/src/vs/workbench/contrib/logs/electron-browser/logsActions.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as nls from 'vs/nls'; +import { Action } from 'vs/base/common/actions'; +import { join } from 'vs/base/common/path'; +import { IEnvironmentService } from 'vs/platform/environment/common/environment'; +import { URI } from 'vs/base/common/uri'; +import { IElectronService } from 'vs/platform/electron/node/electron'; + +export class OpenLogsFolderAction extends Action { + + static ID = 'workbench.action.openLogsFolder'; + static LABEL = nls.localize('openLogsFolder', "Open Logs Folder"); + + constructor(id: string, label: string, + @IEnvironmentService private readonly environmentService: IEnvironmentService, + @IElectronService private readonly electronService: IElectronService, + ) { + super(id, label); + } + + run(): Promise { + return this.electronService.showItemInFolder(URI.file(join(this.environmentService.logsPath, 'main.log')).fsPath); + } +} diff --git a/src/vs/workbench/contrib/markers/browser/markersPanel.ts b/src/vs/workbench/contrib/markers/browser/markersPanel.ts index bbc4392c7f7..01ef11a2cd4 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanel.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanel.ts @@ -117,7 +117,7 @@ export class MarkersPanel extends Panel implements IMarkerFilterController { this.rangeHighlightDecorations = this._register(this.instantiationService.createInstance(RangeHighlightDecorations)); // actions - this.collapseAllAction = this._register(new Action('vs.tree.collapse', localize('collapseAll', "Collapse All"), 'monaco-tree-action collapse-all', true, async () => this.collapseAll())); + this.collapseAllAction = this._register(new Action('vs.tree.collapse', localize('collapseAll', "Collapse All"), 'monaco-tree-action codicon-collapse-all', true, async () => this.collapseAll())); this.filterAction = this._register(this.instantiationService.createInstance(MarkersFilterAction, { filterText: this.panelState['filter'] || '', filterHistory: this.panelState['filterHistory'] || [], useFilesExclude: !!this.panelState['useFilesExclude'] })); } diff --git a/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts b/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts index c45dee82e5d..1bc91c2d7cf 100644 --- a/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts +++ b/src/vs/workbench/contrib/markers/browser/markersPanelActions.ts @@ -213,7 +213,7 @@ export class MarkersFilterActionViewItem extends BaseActionViewItem { private createFilesExcludeCheckbox(container: HTMLElement): void { const filesExcludeFilter = this._register(new Checkbox({ - actionClassName: 'markers-panel-filter-filesExclude', + actionClassName: 'codicon codicon-exclude', title: this.action.useFilesExclude ? Messages.MARKERS_PANEL_ACTION_TOOLTIP_DO_NOT_USE_FILES_EXCLUDE : Messages.MARKERS_PANEL_ACTION_TOOLTIP_USE_FILES_EXCLUDE, isChecked: this.action.useFilesExclude })); diff --git a/src/vs/workbench/contrib/markers/browser/media/exclude-settings-dark.svg b/src/vs/workbench/contrib/markers/browser/media/exclude-settings-dark.svg deleted file mode 100644 index 0b1694dc2f1..00000000000 --- a/src/vs/workbench/contrib/markers/browser/media/exclude-settings-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/markers/browser/media/exclude-settings-hc.svg b/src/vs/workbench/contrib/markers/browser/media/exclude-settings-hc.svg deleted file mode 100644 index ba88235419a..00000000000 --- a/src/vs/workbench/contrib/markers/browser/media/exclude-settings-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/markers/browser/media/exclude-settings-light.svg b/src/vs/workbench/contrib/markers/browser/media/exclude-settings-light.svg deleted file mode 100644 index 114ec3f0fec..00000000000 --- a/src/vs/workbench/contrib/markers/browser/media/exclude-settings-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/markers/browser/media/markers.css b/src/vs/workbench/contrib/markers/browser/media/markers.css index 42321b8dba4..c8e96443126 100644 --- a/src/vs/workbench/contrib/markers/browser/media/markers.css +++ b/src/vs/workbench/contrib/markers/browser/media/markers.css @@ -56,19 +56,6 @@ display: none; } -.vs .markers-panel-action-filter > .markers-panel-filter-controls > .markers-panel-filter-filesExclude { - background: url('exclude-settings-light.svg') center center no-repeat; -} - -.vs-dark .markers-panel-action-filter > .markers-panel-filter-controls > .markers-panel-filter-filesExclude, -.hc-black .markers-panel-action-filter > .markers-panel-filter-controls > .markers-panel-filter-filesExclude { - background: url('exclude-settings-dark.svg') center center no-repeat; -} - -.hc-black .markers-panel-action-filter > .markers-panel-filter-controls > .markers-panel-filter-filesExclude { - background: url('exclude-settings-hc.svg') center center no-repeat; -} - .markers-panel .markers-panel-container { height: 100%; } diff --git a/src/vs/workbench/contrib/output/browser/media/clear-dark.svg b/src/vs/workbench/contrib/output/browser/media/clear-dark.svg deleted file mode 100644 index 04d64ab41ca..00000000000 --- a/src/vs/workbench/contrib/output/browser/media/clear-dark.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/output/browser/media/clear-hc.svg b/src/vs/workbench/contrib/output/browser/media/clear-hc.svg deleted file mode 100644 index 44a41edd3b3..00000000000 --- a/src/vs/workbench/contrib/output/browser/media/clear-hc.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/output/browser/media/clear-light.svg b/src/vs/workbench/contrib/output/browser/media/clear-light.svg deleted file mode 100644 index f6a51c856f0..00000000000 --- a/src/vs/workbench/contrib/output/browser/media/clear-light.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/src/vs/workbench/contrib/output/browser/media/locked-dark.svg b/src/vs/workbench/contrib/output/browser/media/locked-dark.svg deleted file mode 100644 index ebdc1c0078a..00000000000 --- a/src/vs/workbench/contrib/output/browser/media/locked-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/output/browser/media/locked-hc.svg b/src/vs/workbench/contrib/output/browser/media/locked-hc.svg deleted file mode 100644 index 350dc417710..00000000000 --- a/src/vs/workbench/contrib/output/browser/media/locked-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/output/browser/media/locked-light.svg b/src/vs/workbench/contrib/output/browser/media/locked-light.svg deleted file mode 100644 index 03b03513688..00000000000 --- a/src/vs/workbench/contrib/output/browser/media/locked-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/output/browser/media/open-file-dark.svg b/src/vs/workbench/contrib/output/browser/media/open-file-dark.svg deleted file mode 100644 index ed302ae1398..00000000000 --- a/src/vs/workbench/contrib/output/browser/media/open-file-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/output/browser/media/open-file-hc.svg b/src/vs/workbench/contrib/output/browser/media/open-file-hc.svg deleted file mode 100644 index bba63494e6f..00000000000 --- a/src/vs/workbench/contrib/output/browser/media/open-file-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/output/browser/media/open-file-light.svg b/src/vs/workbench/contrib/output/browser/media/open-file-light.svg deleted file mode 100644 index 392a840c5ef..00000000000 --- a/src/vs/workbench/contrib/output/browser/media/open-file-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/output/browser/media/output.css b/src/vs/workbench/contrib/output/browser/media/output.css deleted file mode 100644 index 0f19102cc38..00000000000 --- a/src/vs/workbench/contrib/output/browser/media/output.css +++ /dev/null @@ -1,52 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -.monaco-workbench .output-action.clear-output { - background: url('clear-light.svg') center center no-repeat; -} - -.vs-dark .monaco-workbench .output-action.clear-output { - background: url('clear-dark.svg') center center no-repeat; -} - -.hc-black .monaco-workbench .output-action.clear-output { - background: url('clear-hc.svg') center center no-repeat; -} - -.monaco-workbench .output-action.output-scroll-lock { - background: url('locked-light.svg') center center no-repeat; -} - -.vs-dark .monaco-workbench .output-action.output-scroll-lock { - background: url('locked-dark.svg') center center no-repeat; -} - -.hc-black .monaco-workbench .output-action.output-scroll-lock { - background: url('locked-hc.svg') center center no-repeat; -} - -.monaco-workbench .output-action.output-scroll-unlock { - background: url('unlocked-light.svg') center center no-repeat; -} - -.vs-dark .monaco-workbench .output-action.output-scroll-unlock { - background: url('unlocked-dark.svg') center center no-repeat; -} - -.hc-black .monaco-workbench .output-action.output-scroll-unlock { - background: url('unlocked-hc.svg') center center no-repeat; -} - -.monaco-workbench .output-action.open-log-file { - background: url('open-file-light.svg') center center no-repeat; -} - -.vs-dark .monaco-workbench .output-action.open-log-file { - background: url('open-file-dark.svg') center center no-repeat; -} - -.hc-black .monaco-workbench .output-action.open-log-file { - background: url('open-file-hc.svg') center center no-repeat; -} diff --git a/src/vs/workbench/contrib/output/browser/media/unlocked-dark.svg b/src/vs/workbench/contrib/output/browser/media/unlocked-dark.svg deleted file mode 100644 index 3b7947f9752..00000000000 --- a/src/vs/workbench/contrib/output/browser/media/unlocked-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/output/browser/media/unlocked-hc.svg b/src/vs/workbench/contrib/output/browser/media/unlocked-hc.svg deleted file mode 100644 index 100cfc1385a..00000000000 --- a/src/vs/workbench/contrib/output/browser/media/unlocked-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/output/browser/media/unlocked-light.svg b/src/vs/workbench/contrib/output/browser/media/unlocked-light.svg deleted file mode 100644 index 0c3cb4b93b0..00000000000 --- a/src/vs/workbench/contrib/output/browser/media/unlocked-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/output/browser/outputActions.ts b/src/vs/workbench/contrib/output/browser/outputActions.ts index 561891fc62e..3760d018bf5 100644 --- a/src/vs/workbench/contrib/output/browser/outputActions.ts +++ b/src/vs/workbench/contrib/output/browser/outputActions.ts @@ -45,7 +45,7 @@ export class ClearOutputAction extends Action { id: string, label: string, @IOutputService private readonly outputService: IOutputService ) { - super(id, label, 'output-action clear-output'); + super(id, label, 'output-action codicon-clear-all'); } public run(): Promise { @@ -67,7 +67,7 @@ export class ToggleOrSetOutputScrollLockAction extends Action { public static readonly LABEL = nls.localize({ key: 'toggleOutputScrollLock', comment: ['Turn on / off automatic output scrolling'] }, "Toggle Output Scroll Lock"); constructor(id: string, label: string, @IOutputService private readonly outputService: IOutputService) { - super(id, label, 'output-action output-scroll-unlock'); + super(id, label, 'output-action codicon-unlock'); this._register(this.outputService.onActiveOutputChannel(channel => { const activeChannel = this.outputService.getActiveChannel(); if (activeChannel) { @@ -94,10 +94,10 @@ export class ToggleOrSetOutputScrollLockAction extends Action { private setClassAndLabel(locked: boolean) { if (locked) { - this.class = 'output-action output-scroll-lock'; + this.class = 'output-action codicon-lock'; this.label = nls.localize('outputScrollOn', "Turn Auto Scrolling On"); } else { - this.class = 'output-action output-scroll-unlock'; + this.class = 'output-action codicon-unlock'; this.label = nls.localize('outputScrollOff', "Turn Auto Scrolling Off"); } } @@ -185,7 +185,7 @@ export class OpenLogOutputFile extends Action { @IEditorService private readonly editorService: IEditorService, @IInstantiationService private readonly instantiationService: IInstantiationService ) { - super(OpenLogOutputFile.ID, OpenLogOutputFile.LABEL, 'output-action open-log-file'); + super(OpenLogOutputFile.ID, OpenLogOutputFile.LABEL, 'output-action codicon-go-to-file'); this._register(this.outputService.onActiveOutputChannel(this.update, this)); this.update(); } diff --git a/src/vs/workbench/contrib/output/browser/outputPanel.ts b/src/vs/workbench/contrib/output/browser/outputPanel.ts index f1c0b211d32..9f58f043851 100644 --- a/src/vs/workbench/contrib/output/browser/outputPanel.ts +++ b/src/vs/workbench/contrib/output/browser/outputPanel.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import 'vs/css!./media/output'; import * as nls from 'vs/nls'; import { Action, IAction } from 'vs/base/common/actions'; import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; diff --git a/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts b/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts index daa2adf2ccd..0509b63e0d3 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/startupProfiler.ts @@ -18,6 +18,7 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { URI } from 'vs/base/common/uri'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IElectronService } from 'vs/platform/electron/node/electron'; export class StartupProfiler implements IWorkbenchContribution { @@ -29,7 +30,8 @@ export class StartupProfiler implements IWorkbenchContribution { @IClipboardService private readonly _clipboardService: IClipboardService, @ILifecycleService lifecycleService: ILifecycleService, @IExtensionService extensionService: IExtensionService, - @IOpenerService private readonly _openerService: IOpenerService + @IOpenerService private readonly _openerService: IOpenerService, + @IElectronService private readonly _electronService: IElectronService ) { // wait for everything to be ready Promise.all([ @@ -81,7 +83,7 @@ export class StartupProfiler implements IWorkbenchContribution { }).then(res => { if (res.confirmed) { Promise.all([ - this._windowsService.showItemInFolder(URI.file(join(dir, files[0]))), + this._electronService.showItemInFolder(URI.file(join(dir, files[0])).fsPath), this._createPerfIssue(files) ]).then(() => { // keep window stable until restart is selected diff --git a/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts b/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts index c4d1c7cd4c1..e0c7f7f6bd2 100644 --- a/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts +++ b/src/vs/workbench/contrib/performance/electron-browser/startupTimings.ts @@ -21,6 +21,7 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService'; import { didUseCachedData, ITimerService } from 'vs/workbench/services/timer/electron-browser/timerService'; import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet'; import { getEntries } from 'vs/base/common/performance'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; export class StartupTimings implements IWorkbenchContribution { @@ -34,6 +35,7 @@ export class StartupTimings implements IWorkbenchContribution { @ILifecycleService private readonly _lifecycleService: ILifecycleService, @IUpdateService private readonly _updateService: IUpdateService, @IEnvironmentService private readonly _envService: IEnvironmentService, + @IHostService private readonly _hostService: IHostService ) { // this._report().catch(onUnexpectedError); @@ -91,7 +93,7 @@ export class StartupTimings implements IWorkbenchContribution { if (this._lifecycleService.startupKind !== StartupKind.NewWindow) { return false; } - if (await this._windowsService.getWindowCount() !== 1) { + if (await this._hostService.windowCount !== 1) { return false; } const activeViewlet = this._viewletService.getActiveViewlet(); diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index 8ec38b3795e..1f4297c039f 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -11,7 +11,7 @@ import { OperatingSystem, isWeb } from 'vs/base/common/platform'; import { Schemas } from 'vs/base/common/network'; import { IRemoteAgentService, RemoteExtensionLogFileName } from 'vs/workbench/services/remote/common/remoteAgentService'; import { ILogService } from 'vs/platform/log/common/log'; -import { LogLevelSetterChannelClient } from 'vs/platform/log/common/logIpc'; +import { LoggerChannelClient } from 'vs/platform/log/common/logIpc'; import { IOutputChannelRegistry, Extensions as OutputExt, } from 'vs/workbench/contrib/output/common/output'; import { localize } from 'vs/nls'; import { joinPath } from 'vs/base/common/resources'; @@ -79,9 +79,9 @@ class RemoteChannelsContribution extends Disposable implements IWorkbenchContrib super(); const connection = remoteAgentService.getConnection(); if (connection) { - const logLevelClient = new LogLevelSetterChannelClient(connection.getChannel('loglevel')); - logLevelClient.setLevel(logService.getLevel()); - this._register(logService.onDidChangeLogLevel(level => logLevelClient.setLevel(level))); + const loggerClient = new LoggerChannelClient(connection.getChannel('logger')); + loggerClient.setLevel(logService.getLevel()); + this._register(logService.onDidChangeLogLevel(level => loggerClient.setLevel(level))); } } } diff --git a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts index 37cae45ace2..3ba4103fd34 100644 --- a/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/electron-browser/remote.contribution.ts @@ -12,7 +12,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import { isMacintosh } from 'vs/base/common/platform'; import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { MenuId, IMenuService, MenuItemAction, IMenu, MenuRegistry, SyncActionDescriptor } from 'vs/platform/actions/common/actions'; +import { MenuId, IMenuService, MenuItemAction, IMenu, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchContributionsExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/platform/statusbar/common/statusbar'; @@ -26,15 +26,13 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; import { DialogChannel } from 'vs/platform/dialogs/electron-browser/dialogIpc'; import { DownloadServiceChannel } from 'vs/platform/download/common/downloadIpc'; -import { LogLevelSetterChannel } from 'vs/platform/log/common/logIpc'; +import { LoggerChannel } from 'vs/platform/log/common/logIpc'; import { ipcRenderer as ipc } from 'electron'; import { IDiagnosticInfoOptions, IRemoteDiagnosticInfo } from 'vs/platform/diagnostics/common/diagnostics'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; -import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions'; -import { OpenFileFolderAction, OpenFileAction, OpenFolderAction } from 'vs/workbench/browser/actions/workspaceActions'; import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver'; import { IWindowsService } from 'vs/platform/windows/common/windows'; import { RemoteConnectionState, Deprecated_RemoteAuthorityContext, RemoteFileDialogContext } from 'vs/workbench/browser/contextkeys'; @@ -229,7 +227,7 @@ class RemoteChannelsContribution implements IWorkbenchContribution { if (connection) { connection.registerChannel('dialog', new DialogChannel(dialogService)); connection.registerChannel('download', new DownloadServiceChannel(downloadService)); - connection.registerChannel('loglevel', new LogLevelSetterChannel(logService)); + connection.registerChannel('logger', new LoggerChannel(logService)); } } } @@ -377,11 +375,7 @@ Registry.as(ConfigurationExtensions.Configuration) } }); -const registry = Registry.as(ActionExtensions.WorkbenchActions); -const fileCategory = nls.localize('file', "File"); - if (isMacintosh) { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileFolderAction, OpenFileFolderAction.ID, OpenFileFolderAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open...', fileCategory); KeybindingsRegistry.registerCommandAndKeybindingRule({ id: OpenLocalFileFolderCommand.ID, weight: KeybindingWeight.WorkbenchContrib, @@ -391,8 +385,6 @@ if (isMacintosh) { handler: OpenLocalFileFolderCommand.handler() }); } else { - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFileAction, OpenFileAction.ID, OpenFileAction.LABEL, { primary: KeyMod.CtrlCmd | KeyCode.KEY_O }), 'File: Open File...', fileCategory); - registry.registerWorkbenchAction(new SyncActionDescriptor(OpenFolderAction, OpenFolderAction.ID, OpenFolderAction.LABEL, { primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyMod.CtrlCmd | KeyCode.KEY_O) }), 'File: Open Folder...', fileCategory); KeybindingsRegistry.registerCommandAndKeybindingRule({ id: OpenLocalFileCommand.ID, weight: KeybindingWeight.WorkbenchContrib, diff --git a/src/vs/workbench/contrib/search/browser/media/searchview.css b/src/vs/workbench/contrib/search/browser/media/searchview.css index f530bce968a..decc6173962 100644 --- a/src/vs/workbench/contrib/search/browser/media/searchview.css +++ b/src/vs/workbench/contrib/search/browser/media/searchview.css @@ -86,7 +86,7 @@ height: 25px; } -.search-view .search-widget .replace-container .monaco-action-bar .action-item .icon { +.search-view .search-widget .replace-container .monaco-action-bar .action-item .codicon { background-repeat: no-repeat; width: 20px; height: 25px; diff --git a/src/vs/workbench/contrib/search/browser/searchWidget.ts b/src/vs/workbench/contrib/search/browser/searchWidget.ts index 4a97413453c..254346cc052 100644 --- a/src/vs/workbench/contrib/search/browser/searchWidget.ts +++ b/src/vs/workbench/contrib/search/browser/searchWidget.ts @@ -364,11 +364,12 @@ export class SearchWidget extends Widget { } })); - let controls = document.createElement('div'); + const controls = document.createElement('div'); controls.className = 'controls'; controls.style.display = 'block'; controls.appendChild(this._preserveCase.domNode); replaceBox.appendChild(controls); + this.replaceInput.paddingRight = this._preserveCase.width(); this._register(attachInputBoxStyler(this.replaceInput, this.themeService)); this.onkeydown(this.replaceInput.inputElement, (keyboardEvent) => this.onReplaceInputKeyDown(keyboardEvent)); diff --git a/src/vs/workbench/contrib/terminal/browser/media/kill-dark.svg b/src/vs/workbench/contrib/terminal/browser/media/kill-dark.svg deleted file mode 100644 index 9831c96a166..00000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/kill-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/kill-hc.svg b/src/vs/workbench/contrib/terminal/browser/media/kill-hc.svg deleted file mode 100644 index 656f3bd7a42..00000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/kill-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/kill-light.svg b/src/vs/workbench/contrib/terminal/browser/media/kill-light.svg deleted file mode 100644 index d5ac851860b..00000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/kill-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/new-dark.svg b/src/vs/workbench/contrib/terminal/browser/media/new-dark.svg deleted file mode 100644 index 4d9389336b9..00000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/new-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/new-hc.svg b/src/vs/workbench/contrib/terminal/browser/media/new-hc.svg deleted file mode 100644 index fb50c6c2849..00000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/new-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/new-light.svg b/src/vs/workbench/contrib/terminal/browser/media/new-light.svg deleted file mode 100644 index 01a9de7d5ab..00000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/new-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/split-editor-horizontal-dark.svg b/src/vs/workbench/contrib/terminal/browser/media/split-editor-horizontal-dark.svg deleted file mode 100644 index 8c22a7c5bfe..00000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/split-editor-horizontal-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/split-editor-horizontal-hc.svg b/src/vs/workbench/contrib/terminal/browser/media/split-editor-horizontal-hc.svg deleted file mode 100644 index 82c19d0c8fc..00000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/split-editor-horizontal-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/split-editor-horizontal-light.svg b/src/vs/workbench/contrib/terminal/browser/media/split-editor-horizontal-light.svg deleted file mode 100644 index 2d53ab6d3c2..00000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/split-editor-horizontal-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/split-editor-vertical-dark.svg b/src/vs/workbench/contrib/terminal/browser/media/split-editor-vertical-dark.svg deleted file mode 100644 index 419c21be4f6..00000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/split-editor-vertical-dark.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/split-editor-vertical-hc.svg b/src/vs/workbench/contrib/terminal/browser/media/split-editor-vertical-hc.svg deleted file mode 100644 index 7565fd3c168..00000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/split-editor-vertical-hc.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/split-editor-vertical-light.svg b/src/vs/workbench/contrib/terminal/browser/media/split-editor-vertical-light.svg deleted file mode 100644 index 7e95763b463..00000000000 --- a/src/vs/workbench/contrib/terminal/browser/media/split-editor-vertical-light.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index be6edf706dc..d50dd4b9253 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -144,24 +144,6 @@ opacity: 0 !important; } -/* Light theme */ -.monaco-workbench .terminal-action.kill { background: url('kill-light.svg') center center no-repeat; } -.monaco-workbench .terminal-action.new { background: url('new-light.svg') center center no-repeat; } -.monaco-workbench .terminal-action.split { background: url('split-editor-horizontal-light.svg') center center no-repeat; } -.monaco-workbench .panel.right .terminal-action.split { background: url('split-editor-vertical-light.svg') center center no-repeat; } - -/* Dark theme */ -.vs-dark .monaco-workbench .terminal-action.kill, .hc-black .monaco-workbench .terminal-action.kill { background: url('kill-dark.svg') center center no-repeat; } -.vs-dark .monaco-workbench .terminal-action.new, .hc-black .monaco-workbench .terminal-action.new { background: url('new-dark.svg') center center no-repeat; } -.vs-dark .monaco-workbench .terminal-action.split, .hc-black .monaco-workbench .terminal-action.split { background: url('split-editor-horizontal-dark.svg') center center no-repeat; } -.vs-dark .monaco-workbench .panel.right .terminal-action.split, .hc-black .monaco-workbench .panel.right .terminal-action.split { background: url('split-editor-vertical-dark.svg') center center no-repeat; } - -/* HC theme */ -.hc-black .monaco-workbench .terminal-action.kill, .hc-black .monaco-workbench .terminal-action.kill { background: url('kill-hc.svg') center center no-repeat; } -.hc-black .monaco-workbench .terminal-action.new, .hc-black .monaco-workbench .terminal-action.new { background: url('new-hc.svg') center center no-repeat; } -.hc-black .monaco-workbench .terminal-action.split, .hc-black .monaco-workbench .terminal-action.split { background: url('split-editor-horizontal-hc.svg') center center no-repeat; } -.hc-black .monaco-workbench .panel.right .terminal-action.split, .hc-black .monaco-workbench .panel.right .terminal-action.split { background: url('split-editor-vertical-hc.svg') center center no-repeat; } - .vs-dark .monaco-workbench.mac .panel.integrated-terminal .terminal-outer-container:not(.alt-active) .terminal:not(.enable-mouse-events), .hc-black .monaco-workbench.mac .panel.integrated-terminal .terminal-outer-container:not(.alt-active) .terminal:not(.enable-mouse-events) { cursor: -webkit-image-set(url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAQAAAC1+jfqAAAAL0lEQVQoz2NgCD3x//9/BhBYBWdhgFVAiVW4JBFKGIa4AqD0//9D3pt4I4tAdAMAHTQ/j5Zom30AAAAASUVORK5CYII=') 1x, url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAQAAADZc7J/AAAAz0lEQVRIx2NgYGBY/R8I/vx5eelX3n82IJ9FxGf6tksvf/8FiTMQAcAGQMDvSwu09abffY8QYSAScNk45G198eX//yev73/4///701eh//kZSARckrNBRvz//+8+6ZohwCzjGNjdgQxkAg7B9WADeBjIBqtJCbhRA0YNoIkBSNmaPEMoNmA0FkYNoFKhapJ6FGyAH3nauaSmPfwI0v/3OukVi0CIZ+F25KrtYcx/CTIy0e+rC7R1Z4KMICVTQQ14feVXIbR695u14+Ir4gwAAD49E54wc1kWAAAAAElFTkSuQmCC') 2x) 5 8, text; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 6357326ade2..5c6c751e720 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -105,7 +105,7 @@ export class KillTerminalAction extends Action { id: string, label: string, @ITerminalService private readonly terminalService: ITerminalService ) { - super(id, label, 'terminal-action kill'); + super(id, label, 'terminal-action codicon-trash'); } public run(event?: any): Promise { @@ -336,7 +336,7 @@ export class CreateNewTerminalAction extends Action { @ICommandService private readonly commandService: ICommandService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService ) { - super(id, label, 'terminal-action new'); + super(id, label, 'terminal-action codicon-add'); } public run(event?: any): Promise { @@ -412,7 +412,7 @@ export class SplitTerminalAction extends Action { @ICommandService private readonly commandService: ICommandService, @IWorkspaceContextService private readonly workspaceContextService: IWorkspaceContextService ) { - super(id, label, 'terminal-action split'); + super(id, label, 'terminal-action codicon-split-horizontal'); } public run(event?: any): Promise { diff --git a/src/vs/workbench/contrib/url/common/externalUriResolver.ts b/src/vs/workbench/contrib/url/common/externalUriResolver.ts new file mode 100644 index 00000000000..454d76644b6 --- /dev/null +++ b/src/vs/workbench/contrib/url/common/externalUriResolver.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Disposable } from 'vs/base/common/lifecycle'; +import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; + +export class ExternalUriResolverContribution extends Disposable implements IWorkbenchContribution { + constructor( + @IOpenerService _openerService: IOpenerService, + @IWorkbenchEnvironmentService _workbenchEnvironmentService: IWorkbenchEnvironmentService, + ) { + super(); + + if (_workbenchEnvironmentService.options && _workbenchEnvironmentService.options.resolveExternalUri) { + this._register(_openerService.registerExternalUriResolver({ + resolveExternalUri: async (resource) => { + return _workbenchEnvironmentService.options!.resolveExternalUri!(resource); + } + })); + } + } +} diff --git a/src/vs/workbench/contrib/url/common/url.contribution.ts b/src/vs/workbench/contrib/url/common/url.contribution.ts index f51bd6d3f03..e299262de05 100644 --- a/src/vs/workbench/contrib/url/common/url.contribution.ts +++ b/src/vs/workbench/contrib/url/common/url.contribution.ts @@ -14,9 +14,10 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { IURLService } from 'vs/platform/url/common/url'; import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions'; import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { ExternalUriResolverContribution } from 'vs/workbench/contrib/url/common/externalUriResolver'; import { configureTrustedDomainSettingsCommand } from 'vs/workbench/contrib/url/common/trustedDomains'; -import { OpenerValidatorContributions } from 'vs/workbench/contrib/url/common/trustedDomainsValidator'; import { TrustedDomainsFileSystemProvider } from 'vs/workbench/contrib/url/common/trustedDomainsFileSystemProvider'; +import { OpenerValidatorContributions } from 'vs/workbench/contrib/url/common/trustedDomainsValidator'; export class OpenUrlAction extends Action { static readonly ID = 'workbench.action.url.openUrl'; @@ -65,3 +66,7 @@ Registry.as(WorkbenchExtensions.Workbench).regi TrustedDomainsFileSystemProvider, LifecyclePhase.Ready ); +Registry.as(WorkbenchExtensions.Workbench).registerWorkbenchContribution( + ExternalUriResolverContribution, + LifecyclePhase.Ready +); diff --git a/src/vs/workbench/contrib/welcome/gettingStarted/browser/telemetryOptOut.ts b/src/vs/workbench/contrib/welcome/gettingStarted/browser/telemetryOptOut.ts index 9752407f3f8..c942edef9c9 100644 --- a/src/vs/workbench/contrib/welcome/gettingStarted/browser/telemetryOptOut.ts +++ b/src/vs/workbench/contrib/welcome/gettingStarted/browser/telemetryOptOut.ts @@ -18,6 +18,7 @@ import { language, locale } from 'vs/base/common/platform'; import { IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IProductService } from 'vs/platform/product/common/productService'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; export class TelemetryOptOut implements IWorkbenchContribution { @@ -30,6 +31,7 @@ export class TelemetryOptOut implements IWorkbenchContribution { @INotificationService private readonly notificationService: INotificationService, @IWindowService windowService: IWindowService, @IWindowsService windowsService: IWindowsService, + @IHostService hostService: IHostService, @ITelemetryService private readonly telemetryService: ITelemetryService, @IExperimentService private readonly experimentService: IExperimentService, @IConfigurationService private readonly configurationService: IConfigurationService, @@ -42,7 +44,7 @@ export class TelemetryOptOut implements IWorkbenchContribution { const experimentId = 'telemetryOptOut'; Promise.all([ windowService.isFocused(), - windowsService.getWindowCount(), + hostService.windowCount, experimentService.getExperimentById(experimentId) ]).then(([focused, count, experimentState]) => { if (!focused && count > 1) { diff --git a/src/vs/workbench/electron-browser/desktop.main.ts b/src/vs/workbench/electron-browser/desktop.main.ts index ebf89cda089..076d03a5b1e 100644 --- a/src/vs/workbench/electron-browser/desktop.main.ts +++ b/src/vs/workbench/electron-browser/desktop.main.ts @@ -23,9 +23,9 @@ import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron import { IWindowConfiguration } from 'vs/platform/windows/common/windows'; import { webFrame } from 'electron'; import { ISingleFolderWorkspaceIdentifier, IWorkspaceInitializationPayload, ISingleFolderWorkspaceInitializationPayload, reviveWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces'; -import { ConsoleLogService, MultiplexLogService, ILogService } from 'vs/platform/log/common/log'; +import { ConsoleLogService, MultiplexLogService, ILogService, ConsoleLogInMainService } from 'vs/platform/log/common/log'; import { StorageService } from 'vs/platform/storage/node/storageService'; -import { LogLevelSetterChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; +import { LoggerChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc'; import { Schemas } from 'vs/base/common/network'; import { sanitizeFilePath } from 'vs/base/common/extpath'; import { GlobalStorageDatabaseChannelClient } from 'vs/platform/storage/node/storageIpc'; @@ -345,12 +345,25 @@ class CodeRendererMain extends Disposable { } private createLogService(mainProcessService: IMainProcessService, environmentService: IWorkbenchEnvironmentService): ILogService { - const spdlogService = new SpdLogService(`renderer${this.environmentService.configuration.windowId}`, environmentService.logsPath, this.environmentService.configuration.logLevel); - const consoleLogService = new ConsoleLogService(this.environmentService.configuration.logLevel); - const logService = new MultiplexLogService([consoleLogService, spdlogService]); - const logLevelClient = new LogLevelSetterChannelClient(mainProcessService.getChannel('loglevel')); + const loggerClient = new LoggerChannelClient(mainProcessService.getChannel('logger')); - return new FollowerLogService(logLevelClient, logService); + // Extension development test CLI: forward everything to main side + const loggers: ILogService[] = []; + if (environmentService.isExtensionDevelopment && !!environmentService.extensionTestsLocationURI) { + loggers.push( + new ConsoleLogInMainService(loggerClient, this.environmentService.configuration.logLevel) + ); + } + + // Normal logger: spdylog and console + else { + loggers.push( + new ConsoleLogService(this.environmentService.configuration.logLevel), + new SpdLogService(`renderer${this.environmentService.configuration.windowId}`, environmentService.logsPath, this.environmentService.configuration.logLevel) + ); + } + + return new FollowerLogService(loggerClient, new MultiplexLogService(loggers)); } } diff --git a/src/vs/workbench/electron-browser/window.ts b/src/vs/workbench/electron-browser/window.ts index d650e0a1594..31cb64f2d5b 100644 --- a/src/vs/workbench/electron-browser/window.ts +++ b/src/vs/workbench/electron-browser/window.ts @@ -19,12 +19,12 @@ import { IContextMenuService } from 'vs/platform/contextview/browser/contextView import { ITitleService } from 'vs/workbench/services/title/common/titleService'; import { IWorkbenchThemeService, VS_HC_THEME } from 'vs/workbench/services/themes/common/workbenchThemeService'; import * as browser from 'vs/base/browser/browser'; -import { ICommandService } from 'vs/platform/commands/common/commands'; +import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands'; import { IResourceInput } from 'vs/platform/editor/common/editor'; import { KeyboardMapperFactory } from 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; import { ipcRenderer as ipc, webFrame, crashReporter, Event } from 'electron'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing'; -import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction, SubmenuItemAction } from 'vs/platform/actions/common/actions'; +import { IMenuService, MenuId, IMenu, MenuItemAction, ICommandAction, SubmenuItemAction, MenuRegistry } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -55,6 +55,9 @@ import { IMenubarService, IMenubarData, IMenubarMenu, IMenubarKeybinding, IMenub import { withNullAsUndefined } from 'vs/base/common/types'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { Schemas } from 'vs/base/common/network'; +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { posix, dirname } from 'vs/base/common/path'; +import { getBaseLabel } from 'vs/base/common/labels'; const TextInputActions: IAction[] = [ new Action('undo', nls.localize('undo', "Undo"), undefined, true, () => Promise.resolve(document.execCommand('undo'))), @@ -73,6 +76,8 @@ export class ElectronWindow extends Disposable { private readonly touchBarDisposables = this._register(new DisposableStore()); private lastInstalledTouchedBar: ICommandAction[][] | undefined; + private customTitleContextMenuDisposable = this._register(new DisposableStore()); + private previousConfiguredZoomLevel: number | undefined; private addFoldersScheduler: RunOnceScheduler; @@ -102,7 +107,8 @@ export class ElectronWindow extends Disposable { @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, @ITextFileService private readonly textFileService: ITextFileService, @IInstantiationService private readonly instantiationService: IInstantiationService, - @IOpenerService private readonly openerService: IOpenerService + @IOpenerService private readonly openerService: IOpenerService, + @IElectronService private readonly electronService: IElectronService ) { super(); @@ -243,6 +249,11 @@ export class ElectronWindow extends Disposable { this._register(this.trackClosedWaitFiles(waitMarkerFile, resourcesToWaitFor)); } + + // macOS custom title menu + if (isMacintosh) { + this._register(this.editorService.onDidActiveEditorChange(() => this.provideCustomTitleContextMenu())); + } } private onDidVisibleEditorsChange(): void { @@ -306,6 +317,43 @@ export class ElectronWindow extends Disposable { } } + private provideCustomTitleContextMenu(): void { + + // Clear old menu + this.customTitleContextMenuDisposable.clear(); + + // Provide new menu if a file is opened and we are on a custom title + const fileResource = toResource(this.editorService.activeEditor, { supportSideBySide: SideBySideEditor.MASTER, filterByScheme: Schemas.file }); + if (!fileResource || getTitleBarStyle(this.configurationService, this.environmentService) !== 'custom') { + return; + } + + // Split up filepath into segments + const filePath = fileResource.fsPath; + const segments = filePath.split(posix.sep); + for (let i = segments.length; i > 0; i--) { + const isFile = (i === segments.length); + + let pathOffset = i; + if (!isFile) { + pathOffset++; // for segments which are not the file name we want to open the folder + } + + const path = segments.slice(0, pathOffset).join(posix.sep); + + let label: string; + if (!isFile) { + label = getBaseLabel(dirname(path)); + } else { + label = getBaseLabel(path); + } + + const commandId = `workbench.action.revealPathInFinder${i}`; + this.customTitleContextMenuDisposable.add(CommandsRegistry.registerCommand(commandId, () => this.electronService.showItemInFolder(path))); + this.customTitleContextMenuDisposable.add(MenuRegistry.appendMenuItem(MenuId.TitleBarContext, { command: { id: commandId, title: label || posix.sep }, order: -i })); + } + } + private create(): void { // Native menu controller @@ -371,7 +419,7 @@ export class ElectronWindow extends Disposable { const success = await $this.windowsService.openExternal(encodeURI(resource.toString(true))); if (!success && resource.scheme === Schemas.file) { // if opening failed, and this is a file, we can still try to reveal it - await $this.windowsService.showItemInFolder(resource); + await $this.electronService.showItemInFolder(resource.fsPath); } return true; diff --git a/src/vs/workbench/services/environment/browser/environmentService.ts b/src/vs/workbench/services/environment/browser/environmentService.ts index 6ccc6ffcbbf..b31602466f5 100644 --- a/src/vs/workbench/services/environment/browser/environmentService.ts +++ b/src/vs/workbench/services/environment/browser/environmentService.ts @@ -183,16 +183,17 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment get webviewExternalEndpoint(): string { // TODO: get fallback from product.json - return this.options.webviewEndpoint || 'https://{{uuid}}.vscode-webview-test.com'; - } - - get webviewResourceRoot(): string { - return `${this.webviewExternalEndpoint}/{{commit}}/vscode-resource{{resource}}` + return (this.options.webviewEndpoint || 'https://{{uuid}}.vscode-webview-test.com/{{commit}}') .replace('{{commit}}', product.commit || '211fa02efe8c041fd7baa8ec3dce199d5185aa44'); } + get webviewResourceRoot(): string { + return `${this.webviewExternalEndpoint}/vscode-resource{{resource}}`; + } + get webviewCspSource(): string { - return this.options.webviewEndpoint || `https://*.vscode-webview-test.com`; + return this.webviewExternalEndpoint + .replace('{{uuid}}', '*'); } } diff --git a/src/vs/workbench/services/extensions/common/remoteConsoleUtil.ts b/src/vs/workbench/services/extensions/common/remoteConsoleUtil.ts new file mode 100644 index 00000000000..58ca081f88f --- /dev/null +++ b/src/vs/workbench/services/extensions/common/remoteConsoleUtil.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IRemoteConsoleLog, parse } from 'vs/base/common/console'; +import { ILogService } from 'vs/platform/log/common/log'; + +export function logRemoteEntry(logService: ILogService, entry: IRemoteConsoleLog): void { + const args = parse(entry).args; + const firstArg = args.shift(); + if (typeof firstArg !== 'string') { + return; + } + + if (!entry.severity) { + entry.severity = 'info'; + } + + switch (entry.severity) { + case 'log': + case 'info': + logService.info(firstArg, ...args); + break; + case 'warn': + logService.warn(firstArg, ...args); + break; + case 'error': + logService.error(firstArg, ...args); + break; + } +} diff --git a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts index 5d5e96479a2..4ceaca310a8 100644 --- a/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts +++ b/src/vs/workbench/services/extensions/electron-browser/extensionHost.ts @@ -14,7 +14,8 @@ import { toDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import * as objects from 'vs/base/common/objects'; import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; -import { IRemoteConsoleLog, log, parse } from 'vs/base/common/console'; +import { IRemoteConsoleLog, log } from 'vs/base/common/console'; +import { logRemoteEntry } from 'vs/workbench/services/extensions/common/remoteConsoleUtil'; import { findFreePort, randomPort } from 'vs/base/node/ports'; import { IMessagePassingProtocol } from 'vs/base/parts/ipc/common/ipc'; import { PersistentProtocol } from 'vs/base/parts/ipc/common/ipc.net'; @@ -26,7 +27,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import product from 'vs/platform/product/common/product'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; -import { IWindowService, IWindowsService } from 'vs/platform/windows/common/windows'; +import { IWindowService } from 'vs/platform/windows/common/windows'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IInitData, UIKind } from 'vs/workbench/api/common/extHost.protocol'; import { MessageType, createMessageOfType, isMessageOfType } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; @@ -67,7 +68,6 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { private readonly _extensionHostLogsLocation: URI, @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, @INotificationService private readonly _notificationService: INotificationService, - @IWindowsService private readonly _windowsService: IWindowsService, @IWindowService private readonly _windowService: IWindowService, @ILifecycleService private readonly _lifecycleService: ILifecycleService, @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, @@ -435,7 +435,7 @@ export class ExtensionHostProcessWorker implements IExtensionHostStarter { // Log on main side if running tests from cli if (this._isExtensionDevTestFromCli) { - this._windowsService.log(entry.severity, parse(entry).args); + logRemoteEntry(this._logService, entry); } // Broadcast to other windows if we are in development mode diff --git a/src/vs/workbench/services/host/browser/browserHostService.ts b/src/vs/workbench/services/host/browser/browserHostService.ts new file mode 100644 index 00000000000..a452c20287c --- /dev/null +++ b/src/vs/workbench/services/host/browser/browserHostService.ts @@ -0,0 +1,20 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +export class BrowserHostService implements IHostService { + + _serviceBrand: undefined; + + //#region Window + + readonly windowCount = Promise.resolve(1); + + //#endregion +} + +registerSingleton(IHostService, BrowserHostService, true); diff --git a/src/vs/workbench/services/host/browser/host.ts b/src/vs/workbench/services/host/browser/host.ts new file mode 100644 index 00000000000..735b21869e9 --- /dev/null +++ b/src/vs/workbench/services/host/browser/host.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; + +export const IHostService = createDecorator('hostService'); + +export interface IHostService { + + _serviceBrand: undefined; + + //#region Window + + /** + * The number of windows that belong to the current client session. + */ + readonly windowCount: Promise; + + //#endregion +} diff --git a/src/vs/workbench/services/host/electron-browser/desktopHostService.ts b/src/vs/workbench/services/host/electron-browser/desktopHostService.ts new file mode 100644 index 00000000000..833310b965c --- /dev/null +++ b/src/vs/workbench/services/host/electron-browser/desktopHostService.ts @@ -0,0 +1,23 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { IElectronService } from 'vs/platform/electron/node/electron'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; + +export class DesktopHostService implements IHostService { + + _serviceBrand: undefined; + + constructor(@IElectronService private readonly electronService: IElectronService) { } + + //#region Window + + get windowCount() { return this.electronService.windowCount(); } + + //#endregion +} + +registerSingleton(IHostService, DesktopHostService, true); diff --git a/src/vs/workbench/services/textfile/browser/browserTextFileService.ts b/src/vs/workbench/services/textfile/browser/browserTextFileService.ts new file mode 100644 index 00000000000..4cdfa523db0 --- /dev/null +++ b/src/vs/workbench/services/textfile/browser/browserTextFileService.ts @@ -0,0 +1,62 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { AbstractTextFileService } from 'vs/workbench/services/textfile/browser/textFileService'; +import { ITextFileService, IResourceEncodings, IResourceEncoding, ModelState } from 'vs/workbench/services/textfile/common/textfiles'; +import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; +import { ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; + +export class BrowserTextFileService extends AbstractTextFileService { + + readonly encoding: IResourceEncodings = { + getPreferredWriteEncoding(): IResourceEncoding { + return { encoding: 'utf8', hasBOM: false }; + } + }; + + protected onBeforeShutdown(reason: ShutdownReason): boolean { + // Web: we cannot perform long running in the shutdown phase + // As such we need to check sync if there are any dirty files + // that have not been backed up yet and then prevent the shutdown + // if that is the case. + return this.doBeforeShutdownSync(); + } + + private doBeforeShutdownSync(): boolean { + if (this.models.getAll().some(model => model.hasState(ModelState.PENDING_SAVE) || model.hasState(ModelState.PENDING_AUTO_SAVE))) { + return true; // files are pending to be saved: veto + } + + const dirtyResources = this.getDirty(); + if (!dirtyResources.length) { + return false; // no dirty: no veto + } + + if (!this.isHotExitEnabled) { + return true; // dirty without backup: veto + } + + for (const dirtyResource of dirtyResources) { + let hasBackup = false; + + if (this.fileService.canHandleResource(dirtyResource)) { + const model = this.models.get(dirtyResource); + hasBackup = !!(model && model.hasBackup()); + } else if (dirtyResource.scheme === Schemas.untitled) { + hasBackup = this.untitledEditorService.hasBackup(dirtyResource); + } + + if (!hasBackup) { + console.warn('Unload prevented: pending backups'); + return true; // dirty without backup: veto + } + } + + return false; // dirty with backups: no veto + } +} + +registerSingleton(ITextFileService, BrowserTextFileService); diff --git a/src/vs/workbench/services/textfile/browser/textFileService.ts b/src/vs/workbench/services/textfile/browser/textFileService.ts index ed7f5273046..0925a9f3504 100644 --- a/src/vs/workbench/services/textfile/browser/textFileService.ts +++ b/src/vs/workbench/services/textfile/browser/textFileService.ts @@ -3,60 +3,1067 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { TextFileService } from 'vs/workbench/services/textfile/common/textFileService'; -import { ITextFileService, IResourceEncodings, IResourceEncoding, ModelState } from 'vs/workbench/services/textfile/common/textfiles'; -import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; -import { ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; +import * as nls from 'vs/nls'; +import { URI } from 'vs/base/common/uri'; +import * as errors from 'vs/base/common/errors'; +import * as objects from 'vs/base/common/objects'; +import { Event, Emitter } from 'vs/base/common/event'; +import * as platform from 'vs/base/common/platform'; +import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; +import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, IAutoSaveConfiguration, AutoSaveMode, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ISaveOptions, AutoSaveContext, IWillMoveEvent, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; +import { ConfirmResult, IRevertOptions } from 'vs/workbench/common/editor'; +import { ILifecycleService, ShutdownReason, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; +import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; +import { IFileService, IFilesConfiguration, FileOperationError, FileOperationResult, AutoSaveConfiguration, HotExitConfiguration, IFileStatWithMetadata, ICreateFileOptions } from 'vs/platform/files/common/files'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; +import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel'; +import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ResourceMap } from 'vs/base/common/map'; import { Schemas } from 'vs/base/common/network'; +import { IHistoryService } from 'vs/workbench/services/history/common/history'; +import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { createTextBufferFactoryFromSnapshot, createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; +import { IModelService } from 'vs/editor/common/services/modelService'; +import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; +import { isEqualOrParent, isEqual, joinPath, dirname, extname, basename, toLocalResource } from 'vs/base/common/resources'; +import { getConfirmMessage, IDialogService, IFileDialogService, ISaveDialogOptions, IConfirmation } from 'vs/platform/dialogs/common/dialogs'; +import { IModeService } from 'vs/editor/common/services/modeService'; +import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; +import { coalesce } from 'vs/base/common/arrays'; +import { trim } from 'vs/base/common/strings'; +import { VSBuffer } from 'vs/base/common/buffer'; +import { ITextSnapshot } from 'vs/editor/common/model'; +import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; +import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; -export class BrowserTextFileService extends TextFileService { +/** + * The workbench file service implementation implements the raw file service spec and adds additional methods on top. + */ +export abstract class AbstractTextFileService extends Disposable implements ITextFileService { - readonly encoding: IResourceEncodings = { - getPreferredWriteEncoding(): IResourceEncoding { - return { encoding: 'utf8', hasBOM: false }; - } - }; + _serviceBrand: undefined; - protected onBeforeShutdown(reason: ShutdownReason): boolean { - // Web: we cannot perform long running in the shutdown phase - // As such we need to check sync if there are any dirty files - // that have not been backed up yet and then prevent the shutdown - // if that is the case. - return this.doBeforeShutdownSync(); + private readonly _onAutoSaveConfigurationChange: Emitter = this._register(new Emitter()); + readonly onAutoSaveConfigurationChange: Event = this._onAutoSaveConfigurationChange.event; + + private readonly _onFilesAssociationChange: Emitter = this._register(new Emitter()); + readonly onFilesAssociationChange: Event = this._onFilesAssociationChange.event; + + private readonly _onWillMove = this._register(new Emitter()); + readonly onWillMove: Event = this._onWillMove.event; + + private _models: TextFileEditorModelManager; + get models(): ITextFileEditorModelManager { return this._models; } + + abstract get encoding(): IResourceEncodings; + + private currentFilesAssociationConfig: { [key: string]: string; }; + private configuredAutoSaveDelay?: number; + private configuredAutoSaveOnFocusChange: boolean | undefined; + private configuredAutoSaveOnWindowChange: boolean | undefined; + private configuredHotExit: string | undefined; + private autoSaveContext: IContextKey; + + constructor( + @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, + @IFileService protected readonly fileService: IFileService, + @IUntitledEditorService protected readonly untitledEditorService: IUntitledEditorService, + @ILifecycleService private readonly lifecycleService: ILifecycleService, + @IInstantiationService protected instantiationService: IInstantiationService, + @IConfigurationService private readonly configurationService: IConfigurationService, + @IModeService private readonly modeService: IModeService, + @IModelService private readonly modelService: IModelService, + @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, + @INotificationService private readonly notificationService: INotificationService, + @IBackupFileService private readonly backupFileService: IBackupFileService, + @IHostService private readonly hostService: IHostService, + @IHistoryService private readonly historyService: IHistoryService, + @IContextKeyService contextKeyService: IContextKeyService, + @IDialogService private readonly dialogService: IDialogService, + @IFileDialogService private readonly fileDialogService: IFileDialogService, + @IEditorService private readonly editorService: IEditorService, + @ITextResourceConfigurationService protected readonly textResourceConfigurationService: ITextResourceConfigurationService + ) { + super(); + + this._models = this._register(instantiationService.createInstance(TextFileEditorModelManager)); + this.autoSaveContext = AutoSaveContext.bindTo(contextKeyService); + + const configuration = configurationService.getValue(); + this.currentFilesAssociationConfig = configuration && configuration.files && configuration.files.associations; + + this.onFilesConfigurationChange(configuration); + + this.registerListeners(); } - private doBeforeShutdownSync(): boolean { - if (this.models.getAll().some(model => model.hasState(ModelState.PENDING_SAVE) || model.hasState(ModelState.PENDING_AUTO_SAVE))) { - return true; // files are pending to be saved: veto - } + //#region event handling - const dirtyResources = this.getDirty(); - if (!dirtyResources.length) { - return false; // no dirty: no veto - } + private registerListeners(): void { - if (!this.isHotExitEnabled) { - return true; // dirty without backup: veto - } + // Lifecycle + this.lifecycleService.onBeforeShutdown(event => event.veto(this.onBeforeShutdown(event.reason))); + this.lifecycleService.onShutdown(this.dispose, this); - for (const dirtyResource of dirtyResources) { - let hasBackup = false; + // Files configuration changes + this._register(this.configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration('files')) { + this.onFilesConfigurationChange(this.configurationService.getValue()); + } + })); + } - if (this.fileService.canHandleResource(dirtyResource)) { - const model = this.models.get(dirtyResource); - hasBackup = !!(model && model.hasBackup()); - } else if (dirtyResource.scheme === Schemas.untitled) { - hasBackup = this.untitledEditorService.hasBackup(dirtyResource); + protected onBeforeShutdown(reason: ShutdownReason): boolean | Promise { + + // Dirty files need treatment on shutdown + const dirty = this.getDirty(); + if (dirty.length) { + + // If auto save is enabled, save all files and then check again for dirty files + // We DO NOT run any save participant if we are in the shutdown phase for performance reasons + if (this.getAutoSaveMode() !== AutoSaveMode.OFF) { + return this.saveAll(false /* files only */, { skipSaveParticipants: true }).then(() => { + + // If we still have dirty files, we either have untitled ones or files that cannot be saved + const remainingDirty = this.getDirty(); + if (remainingDirty.length) { + return this.handleDirtyBeforeShutdown(remainingDirty, reason); + } + + return false; + }); } - if (!hasBackup) { - console.warn('Unload prevented: pending backups'); - return true; // dirty without backup: veto + // Auto save is not enabled + return this.handleDirtyBeforeShutdown(dirty, reason); + } + + // No dirty files: no veto + return this.noVeto({ cleanUpBackups: true }); + } + + private handleDirtyBeforeShutdown(dirty: URI[], reason: ShutdownReason): boolean | Promise { + + // If hot exit is enabled, backup dirty files and allow to exit without confirmation + if (this.isHotExitEnabled) { + return this.backupBeforeShutdown(dirty, reason).then(didBackup => { + if (didBackup) { + return this.noVeto({ cleanUpBackups: false }); // no veto and no backup cleanup (since backup was successful) + } + + // since a backup did not happen, we have to confirm for the dirty files now + return this.confirmBeforeShutdown(); + }, error => { + this.notificationService.error(nls.localize('files.backup.failSave', "Files that are dirty could not be written to the backup location (Error: {0}). Try saving your files first and then exit.", error.message)); + + return true; // veto, the backups failed + }); + } + + // Otherwise just confirm from the user what to do with the dirty files + return this.confirmBeforeShutdown(); + } + + private async backupBeforeShutdown(dirtyToBackup: URI[], reason: ShutdownReason): Promise { + const windowCount = await this.hostService.windowCount; + + // When quit is requested skip the confirm callback and attempt to backup all workspaces. + // When quit is not requested the confirm callback should be shown when the window being + // closed is the only VS Code window open, except for on Mac where hot exit is only + // ever activated when quit is requested. + + let doBackup: boolean | undefined; + switch (reason) { + case ShutdownReason.CLOSE: + if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.configuredHotExit === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { + doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured + } else if (windowCount > 1 || platform.isMacintosh) { + doBackup = false; // do not backup if a window is closed that does not cause quitting of the application + } else { + doBackup = true; // backup if last window is closed on win/linux where the application quits right after + } + break; + + case ShutdownReason.QUIT: + doBackup = true; // backup because next start we restore all backups + break; + + case ShutdownReason.RELOAD: + doBackup = true; // backup because after window reload, backups restore + break; + + case ShutdownReason.LOAD: + if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.configuredHotExit === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { + doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured + } else { + doBackup = false; // do not backup because we are switching contexts + } + break; + } + + if (!doBackup) { + return false; + } + + await this.backupAll(dirtyToBackup); + + return true; + } + + private backupAll(dirtyToBackup: URI[]): Promise { + + // split up between files and untitled + const filesToBackup: ITextFileEditorModel[] = []; + const untitledToBackup: URI[] = []; + dirtyToBackup.forEach(dirty => { + if (this.fileService.canHandleResource(dirty)) { + const model = this.models.get(dirty); + if (model) { + filesToBackup.push(model); + } + } else if (dirty.scheme === Schemas.untitled) { + untitledToBackup.push(dirty); + } + }); + + return this.doBackupAll(filesToBackup, untitledToBackup); + } + + private async doBackupAll(dirtyFileModels: ITextFileEditorModel[], untitledResources: URI[]): Promise { + + // Handle file resources first + await Promise.all(dirtyFileModels.map(model => model.backup())); + + // Handle untitled resources + await Promise.all(untitledResources + .filter(untitled => this.untitledEditorService.exists(untitled)) + .map(async untitled => (await this.untitledEditorService.loadOrCreate({ resource: untitled })).backup())); + } + + private async confirmBeforeShutdown(): Promise { + const confirm = await this.confirmSave(); + + // Save + if (confirm === ConfirmResult.SAVE) { + const result = await this.saveAll(true /* includeUntitled */, { skipSaveParticipants: true }); + + if (result.results.some(r => !r.success)) { + return true; // veto if some saves failed + } + + return this.noVeto({ cleanUpBackups: true }); + } + + // Don't Save + else if (confirm === ConfirmResult.DONT_SAVE) { + + // Make sure to revert untitled so that they do not restore + // see https://github.com/Microsoft/vscode/issues/29572 + this.untitledEditorService.revertAll(); + + return this.noVeto({ cleanUpBackups: true }); + } + + // Cancel + else if (confirm === ConfirmResult.CANCEL) { + return true; // veto + } + + return false; + } + + private noVeto(options: { cleanUpBackups: boolean }): boolean | Promise { + if (!options.cleanUpBackups) { + return false; + } + + if (this.lifecycleService.phase < LifecyclePhase.Restored) { + return false; // if editors have not restored, we are not up to speed with backups and thus should not clean them + } + + return this.cleanupBackupsBeforeShutdown().then(() => false, () => false); + } + + protected async cleanupBackupsBeforeShutdown(): Promise { + if (this.environmentService.isExtensionDevelopment) { + return; + } + + await this.backupFileService.discardAllWorkspaceBackups(); + } + + protected onFilesConfigurationChange(configuration: IFilesConfiguration): void { + const wasAutoSaveEnabled = (this.getAutoSaveMode() !== AutoSaveMode.OFF); + + const autoSaveMode = (configuration && configuration.files && configuration.files.autoSave) || AutoSaveConfiguration.OFF; + this.autoSaveContext.set(autoSaveMode); + switch (autoSaveMode) { + case AutoSaveConfiguration.AFTER_DELAY: + this.configuredAutoSaveDelay = configuration && configuration.files && configuration.files.autoSaveDelay; + this.configuredAutoSaveOnFocusChange = false; + this.configuredAutoSaveOnWindowChange = false; + break; + + case AutoSaveConfiguration.ON_FOCUS_CHANGE: + this.configuredAutoSaveDelay = undefined; + this.configuredAutoSaveOnFocusChange = true; + this.configuredAutoSaveOnWindowChange = false; + break; + + case AutoSaveConfiguration.ON_WINDOW_CHANGE: + this.configuredAutoSaveDelay = undefined; + this.configuredAutoSaveOnFocusChange = false; + this.configuredAutoSaveOnWindowChange = true; + break; + + default: + this.configuredAutoSaveDelay = undefined; + this.configuredAutoSaveOnFocusChange = false; + this.configuredAutoSaveOnWindowChange = false; + break; + } + + // Emit as event + this._onAutoSaveConfigurationChange.fire(this.getAutoSaveConfiguration()); + + // save all dirty when enabling auto save + if (!wasAutoSaveEnabled && this.getAutoSaveMode() !== AutoSaveMode.OFF) { + this.saveAll(); + } + + // Check for change in files associations + const filesAssociation = configuration && configuration.files && configuration.files.associations; + if (!objects.equals(this.currentFilesAssociationConfig, filesAssociation)) { + this.currentFilesAssociationConfig = filesAssociation; + this._onFilesAssociationChange.fire(); + } + + // Hot exit + const hotExitMode = configuration && configuration.files && configuration.files.hotExit; + if (hotExitMode === HotExitConfiguration.OFF || hotExitMode === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { + this.configuredHotExit = hotExitMode; + } else { + this.configuredHotExit = HotExitConfiguration.ON_EXIT; + } + } + + //#endregion + + //#region primitives (read, create, move, delete, update) + + async read(resource: URI, options?: IReadTextFileOptions): Promise { + const content = await this.fileService.readFile(resource, options); + + // in case of acceptTextOnly: true, we check the first + // chunk for possibly being binary by looking for 0-bytes + // we limit this check to the first 512 bytes + this.validateBinary(content.value, options); + + return { + ...content, + encoding: 'utf8', + value: content.value.toString() + }; + } + + async readStream(resource: URI, options?: IReadTextFileOptions): Promise { + const stream = await this.fileService.readFileStream(resource, options); + + // in case of acceptTextOnly: true, we check the first + // chunk for possibly being binary by looking for 0-bytes + // we limit this check to the first 512 bytes + let checkedForBinary = false; + const throwOnBinary = (data: VSBuffer): Error | undefined => { + if (!checkedForBinary) { + checkedForBinary = true; + + this.validateBinary(data, options); + } + + return undefined; + }; + + return { + ...stream, + encoding: 'utf8', + value: await createTextBufferFactoryFromStream(stream.value, undefined, options && options.acceptTextOnly ? throwOnBinary : undefined) + }; + } + + private validateBinary(buffer: VSBuffer, options?: IReadTextFileOptions): void { + if (!options || !options.acceptTextOnly) { + return; // no validation needed + } + + // in case of acceptTextOnly: true, we check the first + // chunk for possibly being binary by looking for 0-bytes + // we limit this check to the first 512 bytes + for (let i = 0; i < buffer.byteLength && i < 512; i++) { + if (buffer.readUInt8(i) === 0) { + throw new TextFileOperationError(nls.localize('fileBinaryError', "File seems to be binary and cannot be opened as text"), TextFileOperationResult.FILE_IS_BINARY, options); + } + } + } + + async create(resource: URI, value?: string | ITextSnapshot, options?: ICreateFileOptions): Promise { + const stat = await this.doCreate(resource, value, options); + + // If we had an existing model for the given resource, load + // it again to make sure it is up to date with the contents + // we just wrote into the underlying resource by calling + // revert() + const existingModel = this.models.get(resource); + if (existingModel && !existingModel.isDisposed()) { + await existingModel.revert(); + } + + return stat; + } + + protected doCreate(resource: URI, value?: string | ITextSnapshot, options?: ICreateFileOptions): Promise { + return this.fileService.createFile(resource, toBufferOrReadable(value), options); + } + + async write(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise { + return this.fileService.writeFile(resource, toBufferOrReadable(value), options); + } + + async delete(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Promise { + const dirtyFiles = this.getDirty().filter(dirty => isEqualOrParent(dirty, resource)); + + await this.revertAll(dirtyFiles, { soft: true }); + + return this.fileService.del(resource, options); + } + + async move(source: URI, target: URI, overwrite?: boolean): Promise { + + // await onWillMove event joiners + await this.notifyOnWillMove(source, target); + + // find all models that related to either source or target (can be many if resource is a folder) + const sourceModels: ITextFileEditorModel[] = []; + const conflictingModels: ITextFileEditorModel[] = []; + for (const model of this.getFileModels()) { + const resource = model.getResource(); + + if (isEqualOrParent(resource, target, false /* do not ignorecase, see https://github.com/Microsoft/vscode/issues/56384 */)) { + conflictingModels.push(model); + } + + if (isEqualOrParent(resource, source)) { + sourceModels.push(model); } } - return false; // dirty with backups: no veto + // remember each source model to load again after move is done + // with optional content to restore if it was dirty + type ModelToRestore = { resource: URI; snapshot?: ITextSnapshot }; + const modelsToRestore: ModelToRestore[] = []; + for (const sourceModel of sourceModels) { + const sourceModelResource = sourceModel.getResource(); + + // If the source is the actual model, just use target as new resource + let modelToRestoreResource: URI; + if (isEqual(sourceModelResource, source)) { + modelToRestoreResource = target; + } + + // Otherwise a parent folder of the source is being moved, so we need + // to compute the target resource based on that + else { + modelToRestoreResource = joinPath(target, sourceModelResource.path.substr(source.path.length + 1)); + } + + const modelToRestore: ModelToRestore = { resource: modelToRestoreResource }; + if (sourceModel.isDirty()) { + modelToRestore.snapshot = sourceModel.createSnapshot(); + } + + modelsToRestore.push(modelToRestore); + } + + // in order to move, we need to soft revert all dirty models, + // both from the source as well as the target if any + const dirtyModels = [...sourceModels, ...conflictingModels].filter(model => model.isDirty()); + await this.revertAll(dirtyModels.map(dirtyModel => dirtyModel.getResource()), { soft: true }); + + // now we can rename the source to target via file operation + let stat: IFileStatWithMetadata; + try { + stat = await this.fileService.move(source, target, overwrite); + } catch (error) { + + // in case of any error, ensure to set dirty flag back + dirtyModels.forEach(dirtyModel => dirtyModel.makeDirty()); + + throw error; + } + + // finally, restore models that we had loaded previously + await Promise.all(modelsToRestore.map(async modelToRestore => { + + // restore the model, forcing a reload. this is important because + // we know the file has changed on disk after the move and the + // model might have still existed with the previous state. this + // ensures we are not tracking a stale state. + const restoredModel = await this.models.loadOrCreate(modelToRestore.resource, { reload: { async: false } }); + + // restore previous dirty content if any and ensure to mark + // the model as dirty + if (modelToRestore.snapshot && restoredModel.isResolved()) { + this.modelService.updateModel(restoredModel.textEditorModel, createTextBufferFactoryFromSnapshot(modelToRestore.snapshot)); + + restoredModel.makeDirty(); + } + })); + + return stat; + } + + private async notifyOnWillMove(source: URI, target: URI): Promise { + const waitForPromises: Promise[] = []; + + // fire event + this._onWillMove.fire({ + oldResource: source, + newResource: target, + waitUntil(promise: Promise) { + waitForPromises.push(promise.then(undefined, errors.onUnexpectedError)); + } + }); + + // prevent async waitUntil-calls + Object.freeze(waitForPromises); + + await Promise.all(waitForPromises); + } + + //#endregion + + //#region save/revert + + async save(resource: URI, options?: ISaveOptions): Promise { + + // Run a forced save if we detect the file is not dirty so that save participants can still run + if (options && options.force && this.fileService.canHandleResource(resource) && !this.isDirty(resource)) { + const model = this._models.get(resource); + if (model) { + options.reason = SaveReason.EXPLICIT; + + await model.save(options); + + return !model.isDirty(); + } + } + + const result = await this.saveAll([resource], options); + + return result.results.length === 1 && !!result.results[0].success; + } + + async confirmSave(resources?: URI[]): Promise { + if (this.environmentService.isExtensionDevelopment) { + if (!this.environmentService.args['extension-development-confirm-save']) { + return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests) + } + } + + const resourcesToConfirm = this.getDirty(resources); + if (resourcesToConfirm.length === 0) { + return ConfirmResult.DONT_SAVE; + } + return promptSave(this.dialogService, resourcesToConfirm); + } + + async confirmOverwrite(resource: URI): Promise { + const confirm: IConfirmation = { + message: nls.localize('confirmOverwrite', "'{0}' already exists. Do you want to replace it?", basename(resource)), + detail: nls.localize('irreversible', "A file or folder with the same name already exists in the folder {0}. Replacing it will overwrite its current contents.", basename(dirname(resource))), + primaryButton: nls.localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"), + type: 'warning' + }; + + return (await this.dialogService.confirm(confirm)).confirmed; + } + + saveAll(includeUntitled?: boolean, options?: ISaveOptions): Promise; + saveAll(resources: URI[], options?: ISaveOptions): Promise; + saveAll(arg1?: boolean | URI[], options?: ISaveOptions): Promise { + + // get all dirty + let toSave: URI[] = []; + if (Array.isArray(arg1)) { + toSave = this.getDirty(arg1); + } else { + toSave = this.getDirty(); + } + + // split up between files and untitled + const filesToSave: URI[] = []; + const untitledToSave: URI[] = []; + toSave.forEach(resourceToSave => { + if ((Array.isArray(arg1) || arg1 === true /* includeUntitled */) && resourceToSave.scheme === Schemas.untitled) { + untitledToSave.push(resourceToSave); + } else { + filesToSave.push(resourceToSave); + } + }); + + return this.doSaveAll(filesToSave, untitledToSave, options); + } + + private async doSaveAll(fileResources: URI[], untitledResources: URI[], options?: ISaveOptions): Promise { + + // Handle files first that can just be saved + const result = await this.doSaveAllFiles(fileResources, options); + + // Preflight for untitled to handle cancellation from the dialog + const targetsForUntitled: URI[] = []; + for (const untitled of untitledResources) { + if (this.untitledEditorService.exists(untitled)) { + let targetUri: URI; + + // Untitled with associated file path don't need to prompt + if (this.untitledEditorService.hasAssociatedFilePath(untitled)) { + targetUri = toLocalResource(untitled, this.environmentService.configuration.remoteAuthority); + } + + // Otherwise ask user + else { + const targetPath = await this.promptForPath(untitled, this.suggestFileName(untitled)); + if (!targetPath) { + return { results: [...fileResources, ...untitledResources].map(r => ({ source: r })) }; + } + + targetUri = targetPath; + } + + targetsForUntitled.push(targetUri); + } + } + + // Handle untitled + await Promise.all(targetsForUntitled.map(async (target, index) => { + const uri = await this.saveAs(untitledResources[index], target); + + result.results.push({ + source: untitledResources[index], + target: uri, + success: !!uri + }); + })); + + return result; + } + + protected async promptForPath(resource: URI, defaultUri: URI, availableFileSystems?: string[]): Promise { + + // Help user to find a name for the file by opening it first + await this.editorService.openEditor({ resource, options: { revealIfOpened: true, preserveFocus: true } }); + + return this.fileDialogService.pickFileToSave(this.getSaveDialogOptions(defaultUri, availableFileSystems)); + } + + private getSaveDialogOptions(defaultUri: URI, availableFileSystems?: string[]): ISaveDialogOptions { + const options: ISaveDialogOptions = { + defaultUri, + title: nls.localize('saveAsTitle', "Save As"), + availableFileSystems, + }; + + // Filters are only enabled on Windows where they work properly + if (!platform.isWindows) { + return options; + } + + interface IFilter { name: string; extensions: string[]; } + + // Build the file filter by using our known languages + const ext: string | undefined = defaultUri ? extname(defaultUri) : undefined; + let matchingFilter: IFilter | undefined; + const filters: IFilter[] = coalesce(this.modeService.getRegisteredLanguageNames().map(languageName => { + const extensions = this.modeService.getExtensions(languageName); + if (!extensions || !extensions.length) { + return null; + } + + const filter: IFilter = { name: languageName, extensions: extensions.slice(0, 10).map(e => trim(e, '.')) }; + + if (ext && extensions.indexOf(ext) >= 0) { + matchingFilter = filter; + + return null; // matching filter will be added last to the top + } + + return filter; + })); + + // Filters are a bit weird on Windows, based on having a match or not: + // Match: we put the matching filter first so that it shows up selected and the all files last + // No match: we put the all files filter first + const allFilesFilter = { name: nls.localize('allFiles', "All Files"), extensions: ['*'] }; + if (matchingFilter) { + filters.unshift(matchingFilter); + filters.unshift(allFilesFilter); + } else { + filters.unshift(allFilesFilter); + } + + // Allow to save file without extension + filters.push({ name: nls.localize('noExt', "No Extension"), extensions: [''] }); + + options.filters = filters; + + return options; + } + + private async doSaveAllFiles(resources?: URI[], options: ISaveOptions = Object.create(null)): Promise { + const dirtyFileModels = this.getDirtyFileModels(Array.isArray(resources) ? resources : undefined /* Save All */) + .filter(model => { + if ((model.hasState(ModelState.CONFLICT) || model.hasState(ModelState.ERROR)) && (options.reason === SaveReason.AUTO || options.reason === SaveReason.FOCUS_CHANGE || options.reason === SaveReason.WINDOW_CHANGE)) { + return false; // if model is in save conflict or error, do not save unless save reason is explicit or not provided at all + } + + return true; + }); + + const mapResourceToResult = new ResourceMap(); + dirtyFileModels.forEach(m => { + mapResourceToResult.set(m.getResource(), { + source: m.getResource() + }); + }); + + await Promise.all(dirtyFileModels.map(async model => { + await model.save(options); + + if (!model.isDirty()) { + const result = mapResourceToResult.get(model.getResource()); + if (result) { + result.success = true; + } + } + })); + + return { results: mapResourceToResult.values() }; + } + + private getFileModels(arg1?: URI | URI[]): ITextFileEditorModel[] { + if (Array.isArray(arg1)) { + const models: ITextFileEditorModel[] = []; + arg1.forEach(resource => { + models.push(...this.getFileModels(resource)); + }); + + return models; + } + + return this._models.getAll(arg1); + } + + private getDirtyFileModels(resources?: URI | URI[]): ITextFileEditorModel[] { + return this.getFileModels(resources).filter(model => model.isDirty()); + } + + async saveAs(resource: URI, targetResource?: URI, options?: ISaveOptions): Promise { + + // Get to target resource + if (!targetResource) { + let dialogPath = resource; + if (resource.scheme === Schemas.untitled) { + dialogPath = this.suggestFileName(resource); + } + + targetResource = await this.promptForPath(resource, dialogPath, options ? options.availableFileSystems : undefined); + } + + if (!targetResource) { + return; // user canceled + } + + // Just save if target is same as models own resource + if (resource.toString() === targetResource.toString()) { + await this.save(resource, options); + + return resource; + } + + // Do it + return this.doSaveAs(resource, targetResource, options); + } + + private async doSaveAs(resource: URI, target: URI, options?: ISaveOptions): Promise { + + // Retrieve text model from provided resource if any + let model: ITextFileEditorModel | UntitledEditorModel | undefined; + if (this.fileService.canHandleResource(resource)) { + model = this._models.get(resource); + } else if (resource.scheme === Schemas.untitled && this.untitledEditorService.exists(resource)) { + model = await this.untitledEditorService.loadOrCreate({ resource }); + } + + // We have a model: Use it (can be null e.g. if this file is binary and not a text file or was never opened before) + let result: boolean; + if (model) { + result = await this.doSaveTextFileAs(model, resource, target, options); + } + + // Otherwise we can only copy + else { + await this.fileService.copy(resource, target); + + result = true; + } + + // Return early if the operation was not running + if (!result) { + return target; + } + + // Revert the source + await this.revert(resource); + + return target; + } + + private async doSaveTextFileAs(sourceModel: ITextFileEditorModel | UntitledEditorModel, resource: URI, target: URI, options?: ISaveOptions): Promise { + + // Prefer an existing model if it is already loaded for the given target resource + let targetExists: boolean = false; + let targetModel = this.models.get(target); + if (targetModel && targetModel.isResolved()) { + targetExists = true; + } + + // Otherwise create the target file empty if it does not exist already and resolve it from there + else { + targetExists = await this.fileService.exists(target); + + // create target model adhoc if file does not exist yet + if (!targetExists) { + await this.create(target, ''); + } + + targetModel = await this.models.loadOrCreate(target); + } + + try { + + // Confirm to overwrite if we have an untitled file with associated file where + // the file actually exists on disk and we are instructed to save to that file + // path. This can happen if the file was created after the untitled file was opened. + // See https://github.com/Microsoft/vscode/issues/67946 + let write: boolean; + if (sourceModel instanceof UntitledEditorModel && sourceModel.hasAssociatedFilePath && targetExists && isEqual(target, toLocalResource(sourceModel.getResource(), this.environmentService.configuration.remoteAuthority))) { + write = await this.confirmOverwrite(target); + } else { + write = true; + } + + if (!write) { + return false; + } + + // take over model value, encoding and mode (only if more specific) from source model + targetModel.updatePreferredEncoding(sourceModel.getEncoding()); + if (sourceModel.isResolved() && targetModel.isResolved()) { + this.modelService.updateModel(targetModel.textEditorModel, createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot())); + + const sourceMode = sourceModel.textEditorModel.getLanguageIdentifier(); + const targetMode = targetModel.textEditorModel.getLanguageIdentifier(); + if (sourceMode.language !== PLAINTEXT_MODE_ID && targetMode.language === PLAINTEXT_MODE_ID) { + targetModel.textEditorModel.setMode(sourceMode); // only use if more specific than plain/text + } + } + + // save model + await targetModel.save(options); + + return true; + } catch (error) { + + // binary model: delete the file and run the operation again + if ( + (error).textFileOperationResult === TextFileOperationResult.FILE_IS_BINARY || + (error).fileOperationResult === FileOperationResult.FILE_TOO_LARGE + ) { + await this.fileService.del(target); + + return this.doSaveTextFileAs(sourceModel, resource, target, options); + } + + throw error; + } + } + + private suggestFileName(untitledResource: URI): URI { + const untitledFileName = this.untitledEditorService.suggestFileName(untitledResource); + const remoteAuthority = this.environmentService.configuration.remoteAuthority; + const schemeFilter = remoteAuthority ? Schemas.vscodeRemote : Schemas.file; + + const lastActiveFile = this.historyService.getLastActiveFile(schemeFilter); + if (lastActiveFile) { + const lastDir = dirname(lastActiveFile); + return joinPath(lastDir, untitledFileName); + } + + const lastActiveFolder = this.historyService.getLastActiveWorkspaceRoot(schemeFilter); + if (lastActiveFolder) { + return joinPath(lastActiveFolder, untitledFileName); + } + + return untitledResource.with({ path: untitledFileName }); + } + + async revert(resource: URI, options?: IRevertOptions): Promise { + const result = await this.revertAll([resource], options); + + return result.results.length === 1 && !!result.results[0].success; + } + + async revertAll(resources?: URI[], options?: IRevertOptions): Promise { + + // Revert files first + const revertOperationResult = await this.doRevertAllFiles(resources, options); + + // Revert untitled + const untitledReverted = this.untitledEditorService.revertAll(resources); + untitledReverted.forEach(untitled => revertOperationResult.results.push({ source: untitled, success: true })); + + return revertOperationResult; + } + + private async doRevertAllFiles(resources?: URI[], options?: IRevertOptions): Promise { + const fileModels = options && options.force ? this.getFileModels(resources) : this.getDirtyFileModels(resources); + + const mapResourceToResult = new ResourceMap(); + fileModels.forEach(m => { + mapResourceToResult.set(m.getResource(), { + source: m.getResource() + }); + }); + + await Promise.all(fileModels.map(async model => { + try { + await model.revert(options && options.soft); + + if (!model.isDirty()) { + const result = mapResourceToResult.get(model.getResource()); + if (result) { + result.success = true; + } + } + } catch (error) { + + // FileNotFound means the file got deleted meanwhile, so still record as successful revert + if ((error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND) { + const result = mapResourceToResult.get(model.getResource()); + if (result) { + result.success = true; + } + } + + // Otherwise bubble up the error + else { + throw error; + } + } + })); + + return { results: mapResourceToResult.values() }; + } + + getDirty(resources?: URI[]): URI[] { + + // Collect files + const dirty = this.getDirtyFileModels(resources).map(m => m.getResource()); + + // Add untitled ones + dirty.push(...this.untitledEditorService.getDirty(resources)); + + return dirty; + } + + isDirty(resource?: URI): boolean { + + // Check for dirty file + if (this._models.getAll(resource).some(model => model.isDirty())) { + return true; + } + + // Check for dirty untitled + return this.untitledEditorService.getDirty().some(dirty => !resource || dirty.toString() === resource.toString()); + } + + //#endregion + + //#region config + + getAutoSaveMode(): AutoSaveMode { + if (this.configuredAutoSaveOnFocusChange) { + return AutoSaveMode.ON_FOCUS_CHANGE; + } + + if (this.configuredAutoSaveOnWindowChange) { + return AutoSaveMode.ON_WINDOW_CHANGE; + } + + if (this.configuredAutoSaveDelay && this.configuredAutoSaveDelay > 0) { + return this.configuredAutoSaveDelay <= 1000 ? AutoSaveMode.AFTER_SHORT_DELAY : AutoSaveMode.AFTER_LONG_DELAY; + } + + return AutoSaveMode.OFF; + } + + getAutoSaveConfiguration(): IAutoSaveConfiguration { + return { + autoSaveDelay: this.configuredAutoSaveDelay && this.configuredAutoSaveDelay > 0 ? this.configuredAutoSaveDelay : undefined, + autoSaveFocusChange: !!this.configuredAutoSaveOnFocusChange, + autoSaveApplicationChange: !!this.configuredAutoSaveOnWindowChange + }; + } + + get isHotExitEnabled(): boolean { + return !this.environmentService.isExtensionDevelopment && this.configuredHotExit !== HotExitConfiguration.OFF; + } + + //#endregion + + dispose(): void { + + // Clear all caches + this._models.clear(); + + super.dispose(); + } +} + +export async function promptSave(dialogService: IDialogService, resourcesToConfirm: readonly URI[]) { + const message = resourcesToConfirm.length === 1 + ? nls.localize('saveChangesMessage', "Do you want to save the changes you made to {0}?", basename(resourcesToConfirm[0])) + : getConfirmMessage(nls.localize('saveChangesMessages', "Do you want to save the changes to the following {0} files?", resourcesToConfirm.length), resourcesToConfirm); + + const buttons: string[] = [ + resourcesToConfirm.length > 1 ? nls.localize({ key: 'saveAll', comment: ['&& denotes a mnemonic'] }, "&&Save All") : nls.localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save"), + nls.localize({ key: 'dontSave', comment: ['&& denotes a mnemonic'] }, "Do&&n't Save"), + nls.localize('cancel', "Cancel") + ]; + + const { choice } = await dialogService.show(Severity.Warning, message, buttons, { + cancelId: 2, + detail: nls.localize('saveChangesDetail', "Your changes will be lost if you don't save them.") + }); + + switch (choice) { + case 0: return ConfirmResult.SAVE; + case 1: return ConfirmResult.DONT_SAVE; + default: return ConfirmResult.CANCEL; } } -registerSingleton(ITextFileService, BrowserTextFileService); diff --git a/src/vs/workbench/services/textfile/common/textFileService.ts b/src/vs/workbench/services/textfile/common/textFileService.ts deleted file mode 100644 index 8c619f82fb8..00000000000 --- a/src/vs/workbench/services/textfile/common/textFileService.ts +++ /dev/null @@ -1,1069 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import * as nls from 'vs/nls'; -import { URI } from 'vs/base/common/uri'; -import * as errors from 'vs/base/common/errors'; -import * as objects from 'vs/base/common/objects'; -import { Event, Emitter } from 'vs/base/common/event'; -import * as platform from 'vs/base/common/platform'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; -import { IBackupFileService } from 'vs/workbench/services/backup/common/backup'; -import { IResult, ITextFileOperationResult, ITextFileService, ITextFileStreamContent, IAutoSaveConfiguration, AutoSaveMode, SaveReason, ITextFileEditorModelManager, ITextFileEditorModel, ModelState, ISaveOptions, AutoSaveContext, IWillMoveEvent, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult } from 'vs/workbench/services/textfile/common/textfiles'; -import { ConfirmResult, IRevertOptions } from 'vs/workbench/common/editor'; -import { ILifecycleService, ShutdownReason, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle'; -import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; -import { IFileService, IFilesConfiguration, FileOperationError, FileOperationResult, AutoSaveConfiguration, HotExitConfiguration, IFileStatWithMetadata, ICreateFileOptions } from 'vs/platform/files/common/files'; -import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; -import { Disposable } from 'vs/base/common/lifecycle'; -import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; -import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/untitledEditorService'; -import { UntitledEditorModel } from 'vs/workbench/common/editor/untitledEditorModel'; -import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager'; -import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { ResourceMap } from 'vs/base/common/map'; -import { Schemas } from 'vs/base/common/network'; -import { IHistoryService } from 'vs/workbench/services/history/common/history'; -import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; -import { createTextBufferFactoryFromSnapshot, createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel'; -import { IModelService } from 'vs/editor/common/services/modelService'; -import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; -import { isEqualOrParent, isEqual, joinPath, dirname, extname, basename, toLocalResource } from 'vs/base/common/resources'; -import { getConfirmMessage, IDialogService, IFileDialogService, ISaveDialogOptions, IConfirmation } from 'vs/platform/dialogs/common/dialogs'; -import { IModeService } from 'vs/editor/common/services/modeService'; -import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -import { coalesce } from 'vs/base/common/arrays'; -import { trim } from 'vs/base/common/strings'; -import { VSBuffer } from 'vs/base/common/buffer'; -import { ITextSnapshot } from 'vs/editor/common/model'; -import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration'; -import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry'; - -/** - * The workbench file service implementation implements the raw file service spec and adds additional methods on top. - */ -export abstract class TextFileService extends Disposable implements ITextFileService { - - _serviceBrand: undefined; - - private readonly _onAutoSaveConfigurationChange: Emitter = this._register(new Emitter()); - readonly onAutoSaveConfigurationChange: Event = this._onAutoSaveConfigurationChange.event; - - private readonly _onFilesAssociationChange: Emitter = this._register(new Emitter()); - readonly onFilesAssociationChange: Event = this._onFilesAssociationChange.event; - - private readonly _onWillMove = this._register(new Emitter()); - readonly onWillMove: Event = this._onWillMove.event; - - private _models: TextFileEditorModelManager; - get models(): ITextFileEditorModelManager { return this._models; } - - abstract get encoding(): IResourceEncodings; - - private currentFilesAssociationConfig: { [key: string]: string; }; - private configuredAutoSaveDelay?: number; - private configuredAutoSaveOnFocusChange: boolean | undefined; - private configuredAutoSaveOnWindowChange: boolean | undefined; - private configuredHotExit: string | undefined; - private autoSaveContext: IContextKey; - - constructor( - @IWorkspaceContextService private readonly contextService: IWorkspaceContextService, - @IFileService protected readonly fileService: IFileService, - @IUntitledEditorService protected readonly untitledEditorService: IUntitledEditorService, - @ILifecycleService private readonly lifecycleService: ILifecycleService, - @IInstantiationService protected instantiationService: IInstantiationService, - @IConfigurationService private readonly configurationService: IConfigurationService, - @IModeService private readonly modeService: IModeService, - @IModelService private readonly modelService: IModelService, - @IWorkbenchEnvironmentService protected readonly environmentService: IWorkbenchEnvironmentService, - @INotificationService private readonly notificationService: INotificationService, - @IBackupFileService private readonly backupFileService: IBackupFileService, - @IWindowsService private readonly windowsService: IWindowsService, - @IHistoryService private readonly historyService: IHistoryService, - @IContextKeyService contextKeyService: IContextKeyService, - @IDialogService private readonly dialogService: IDialogService, - @IFileDialogService private readonly fileDialogService: IFileDialogService, - @IEditorService private readonly editorService: IEditorService, - @ITextResourceConfigurationService protected readonly textResourceConfigurationService: ITextResourceConfigurationService - ) { - super(); - - this._models = this._register(instantiationService.createInstance(TextFileEditorModelManager)); - this.autoSaveContext = AutoSaveContext.bindTo(contextKeyService); - - const configuration = configurationService.getValue(); - this.currentFilesAssociationConfig = configuration && configuration.files && configuration.files.associations; - - this.onFilesConfigurationChange(configuration); - - this.registerListeners(); - } - - //#region event handling - - private registerListeners(): void { - - // Lifecycle - this.lifecycleService.onBeforeShutdown(event => event.veto(this.onBeforeShutdown(event.reason))); - this.lifecycleService.onShutdown(this.dispose, this); - - // Files configuration changes - this._register(this.configurationService.onDidChangeConfiguration(e => { - if (e.affectsConfiguration('files')) { - this.onFilesConfigurationChange(this.configurationService.getValue()); - } - })); - } - - protected onBeforeShutdown(reason: ShutdownReason): boolean | Promise { - - // Dirty files need treatment on shutdown - const dirty = this.getDirty(); - if (dirty.length) { - - // If auto save is enabled, save all files and then check again for dirty files - // We DO NOT run any save participant if we are in the shutdown phase for performance reasons - if (this.getAutoSaveMode() !== AutoSaveMode.OFF) { - return this.saveAll(false /* files only */, { skipSaveParticipants: true }).then(() => { - - // If we still have dirty files, we either have untitled ones or files that cannot be saved - const remainingDirty = this.getDirty(); - if (remainingDirty.length) { - return this.handleDirtyBeforeShutdown(remainingDirty, reason); - } - - return false; - }); - } - - // Auto save is not enabled - return this.handleDirtyBeforeShutdown(dirty, reason); - } - - // No dirty files: no veto - return this.noVeto({ cleanUpBackups: true }); - } - - private handleDirtyBeforeShutdown(dirty: URI[], reason: ShutdownReason): boolean | Promise { - - // If hot exit is enabled, backup dirty files and allow to exit without confirmation - if (this.isHotExitEnabled) { - return this.backupBeforeShutdown(dirty, reason).then(didBackup => { - if (didBackup) { - return this.noVeto({ cleanUpBackups: false }); // no veto and no backup cleanup (since backup was successful) - } - - // since a backup did not happen, we have to confirm for the dirty files now - return this.confirmBeforeShutdown(); - }, error => { - this.notificationService.error(nls.localize('files.backup.failSave', "Files that are dirty could not be written to the backup location (Error: {0}). Try saving your files first and then exit.", error.message)); - - return true; // veto, the backups failed - }); - } - - // Otherwise just confirm from the user what to do with the dirty files - return this.confirmBeforeShutdown(); - } - - private async backupBeforeShutdown(dirtyToBackup: URI[], reason: ShutdownReason): Promise { - const windowCount = await this.windowsService.getWindowCount(); - - // When quit is requested skip the confirm callback and attempt to backup all workspaces. - // When quit is not requested the confirm callback should be shown when the window being - // closed is the only VS Code window open, except for on Mac where hot exit is only - // ever activated when quit is requested. - - let doBackup: boolean | undefined; - switch (reason) { - case ShutdownReason.CLOSE: - if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.configuredHotExit === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { - doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured - } else if (windowCount > 1 || platform.isMacintosh) { - doBackup = false; // do not backup if a window is closed that does not cause quitting of the application - } else { - doBackup = true; // backup if last window is closed on win/linux where the application quits right after - } - break; - - case ShutdownReason.QUIT: - doBackup = true; // backup because next start we restore all backups - break; - - case ShutdownReason.RELOAD: - doBackup = true; // backup because after window reload, backups restore - break; - - case ShutdownReason.LOAD: - if (this.contextService.getWorkbenchState() !== WorkbenchState.EMPTY && this.configuredHotExit === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { - doBackup = true; // backup if a folder is open and onExitAndWindowClose is configured - } else { - doBackup = false; // do not backup because we are switching contexts - } - break; - } - - if (!doBackup) { - return false; - } - - await this.backupAll(dirtyToBackup); - - return true; - } - - private backupAll(dirtyToBackup: URI[]): Promise { - - // split up between files and untitled - const filesToBackup: ITextFileEditorModel[] = []; - const untitledToBackup: URI[] = []; - dirtyToBackup.forEach(dirty => { - if (this.fileService.canHandleResource(dirty)) { - const model = this.models.get(dirty); - if (model) { - filesToBackup.push(model); - } - } else if (dirty.scheme === Schemas.untitled) { - untitledToBackup.push(dirty); - } - }); - - return this.doBackupAll(filesToBackup, untitledToBackup); - } - - private async doBackupAll(dirtyFileModels: ITextFileEditorModel[], untitledResources: URI[]): Promise { - - // Handle file resources first - await Promise.all(dirtyFileModels.map(model => model.backup())); - - // Handle untitled resources - await Promise.all(untitledResources - .filter(untitled => this.untitledEditorService.exists(untitled)) - .map(async untitled => (await this.untitledEditorService.loadOrCreate({ resource: untitled })).backup())); - } - - private async confirmBeforeShutdown(): Promise { - const confirm = await this.confirmSave(); - - // Save - if (confirm === ConfirmResult.SAVE) { - const result = await this.saveAll(true /* includeUntitled */, { skipSaveParticipants: true }); - - if (result.results.some(r => !r.success)) { - return true; // veto if some saves failed - } - - return this.noVeto({ cleanUpBackups: true }); - } - - // Don't Save - else if (confirm === ConfirmResult.DONT_SAVE) { - - // Make sure to revert untitled so that they do not restore - // see https://github.com/Microsoft/vscode/issues/29572 - this.untitledEditorService.revertAll(); - - return this.noVeto({ cleanUpBackups: true }); - } - - // Cancel - else if (confirm === ConfirmResult.CANCEL) { - return true; // veto - } - - return false; - } - - private noVeto(options: { cleanUpBackups: boolean }): boolean | Promise { - if (!options.cleanUpBackups) { - return false; - } - - if (this.lifecycleService.phase < LifecyclePhase.Restored) { - return false; // if editors have not restored, we are not up to speed with backups and thus should not clean them - } - - return this.cleanupBackupsBeforeShutdown().then(() => false, () => false); - } - - protected async cleanupBackupsBeforeShutdown(): Promise { - if (this.environmentService.isExtensionDevelopment) { - return; - } - - await this.backupFileService.discardAllWorkspaceBackups(); - } - - protected onFilesConfigurationChange(configuration: IFilesConfiguration): void { - const wasAutoSaveEnabled = (this.getAutoSaveMode() !== AutoSaveMode.OFF); - - const autoSaveMode = (configuration && configuration.files && configuration.files.autoSave) || AutoSaveConfiguration.OFF; - this.autoSaveContext.set(autoSaveMode); - switch (autoSaveMode) { - case AutoSaveConfiguration.AFTER_DELAY: - this.configuredAutoSaveDelay = configuration && configuration.files && configuration.files.autoSaveDelay; - this.configuredAutoSaveOnFocusChange = false; - this.configuredAutoSaveOnWindowChange = false; - break; - - case AutoSaveConfiguration.ON_FOCUS_CHANGE: - this.configuredAutoSaveDelay = undefined; - this.configuredAutoSaveOnFocusChange = true; - this.configuredAutoSaveOnWindowChange = false; - break; - - case AutoSaveConfiguration.ON_WINDOW_CHANGE: - this.configuredAutoSaveDelay = undefined; - this.configuredAutoSaveOnFocusChange = false; - this.configuredAutoSaveOnWindowChange = true; - break; - - default: - this.configuredAutoSaveDelay = undefined; - this.configuredAutoSaveOnFocusChange = false; - this.configuredAutoSaveOnWindowChange = false; - break; - } - - // Emit as event - this._onAutoSaveConfigurationChange.fire(this.getAutoSaveConfiguration()); - - // save all dirty when enabling auto save - if (!wasAutoSaveEnabled && this.getAutoSaveMode() !== AutoSaveMode.OFF) { - this.saveAll(); - } - - // Check for change in files associations - const filesAssociation = configuration && configuration.files && configuration.files.associations; - if (!objects.equals(this.currentFilesAssociationConfig, filesAssociation)) { - this.currentFilesAssociationConfig = filesAssociation; - this._onFilesAssociationChange.fire(); - } - - // Hot exit - const hotExitMode = configuration && configuration.files && configuration.files.hotExit; - if (hotExitMode === HotExitConfiguration.OFF || hotExitMode === HotExitConfiguration.ON_EXIT_AND_WINDOW_CLOSE) { - this.configuredHotExit = hotExitMode; - } else { - this.configuredHotExit = HotExitConfiguration.ON_EXIT; - } - } - - //#endregion - - //#region primitives (read, create, move, delete, update) - - async read(resource: URI, options?: IReadTextFileOptions): Promise { - const content = await this.fileService.readFile(resource, options); - - // in case of acceptTextOnly: true, we check the first - // chunk for possibly being binary by looking for 0-bytes - // we limit this check to the first 512 bytes - this.validateBinary(content.value, options); - - return { - ...content, - encoding: 'utf8', - value: content.value.toString() - }; - } - - async readStream(resource: URI, options?: IReadTextFileOptions): Promise { - const stream = await this.fileService.readFileStream(resource, options); - - // in case of acceptTextOnly: true, we check the first - // chunk for possibly being binary by looking for 0-bytes - // we limit this check to the first 512 bytes - let checkedForBinary = false; - const throwOnBinary = (data: VSBuffer): Error | undefined => { - if (!checkedForBinary) { - checkedForBinary = true; - - this.validateBinary(data, options); - } - - return undefined; - }; - - return { - ...stream, - encoding: 'utf8', - value: await createTextBufferFactoryFromStream(stream.value, undefined, options && options.acceptTextOnly ? throwOnBinary : undefined) - }; - } - - private validateBinary(buffer: VSBuffer, options?: IReadTextFileOptions): void { - if (!options || !options.acceptTextOnly) { - return; // no validation needed - } - - // in case of acceptTextOnly: true, we check the first - // chunk for possibly being binary by looking for 0-bytes - // we limit this check to the first 512 bytes - for (let i = 0; i < buffer.byteLength && i < 512; i++) { - if (buffer.readUInt8(i) === 0) { - throw new TextFileOperationError(nls.localize('fileBinaryError', "File seems to be binary and cannot be opened as text"), TextFileOperationResult.FILE_IS_BINARY, options); - } - } - } - - async create(resource: URI, value?: string | ITextSnapshot, options?: ICreateFileOptions): Promise { - const stat = await this.doCreate(resource, value, options); - - // If we had an existing model for the given resource, load - // it again to make sure it is up to date with the contents - // we just wrote into the underlying resource by calling - // revert() - const existingModel = this.models.get(resource); - if (existingModel && !existingModel.isDisposed()) { - await existingModel.revert(); - } - - return stat; - } - - protected doCreate(resource: URI, value?: string | ITextSnapshot, options?: ICreateFileOptions): Promise { - return this.fileService.createFile(resource, toBufferOrReadable(value), options); - } - - async write(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise { - return this.fileService.writeFile(resource, toBufferOrReadable(value), options); - } - - async delete(resource: URI, options?: { useTrash?: boolean, recursive?: boolean }): Promise { - const dirtyFiles = this.getDirty().filter(dirty => isEqualOrParent(dirty, resource)); - - await this.revertAll(dirtyFiles, { soft: true }); - - return this.fileService.del(resource, options); - } - - async move(source: URI, target: URI, overwrite?: boolean): Promise { - - // await onWillMove event joiners - await this.notifyOnWillMove(source, target); - - // find all models that related to either source or target (can be many if resource is a folder) - const sourceModels: ITextFileEditorModel[] = []; - const conflictingModels: ITextFileEditorModel[] = []; - for (const model of this.getFileModels()) { - const resource = model.getResource(); - - if (isEqualOrParent(resource, target, false /* do not ignorecase, see https://github.com/Microsoft/vscode/issues/56384 */)) { - conflictingModels.push(model); - } - - if (isEqualOrParent(resource, source)) { - sourceModels.push(model); - } - } - - // remember each source model to load again after move is done - // with optional content to restore if it was dirty - type ModelToRestore = { resource: URI; snapshot?: ITextSnapshot }; - const modelsToRestore: ModelToRestore[] = []; - for (const sourceModel of sourceModels) { - const sourceModelResource = sourceModel.getResource(); - - // If the source is the actual model, just use target as new resource - let modelToRestoreResource: URI; - if (isEqual(sourceModelResource, source)) { - modelToRestoreResource = target; - } - - // Otherwise a parent folder of the source is being moved, so we need - // to compute the target resource based on that - else { - modelToRestoreResource = joinPath(target, sourceModelResource.path.substr(source.path.length + 1)); - } - - const modelToRestore: ModelToRestore = { resource: modelToRestoreResource }; - if (sourceModel.isDirty()) { - modelToRestore.snapshot = sourceModel.createSnapshot(); - } - - modelsToRestore.push(modelToRestore); - } - - // in order to move, we need to soft revert all dirty models, - // both from the source as well as the target if any - const dirtyModels = [...sourceModels, ...conflictingModels].filter(model => model.isDirty()); - await this.revertAll(dirtyModels.map(dirtyModel => dirtyModel.getResource()), { soft: true }); - - // now we can rename the source to target via file operation - let stat: IFileStatWithMetadata; - try { - stat = await this.fileService.move(source, target, overwrite); - } catch (error) { - - // in case of any error, ensure to set dirty flag back - dirtyModels.forEach(dirtyModel => dirtyModel.makeDirty()); - - throw error; - } - - // finally, restore models that we had loaded previously - await Promise.all(modelsToRestore.map(async modelToRestore => { - - // restore the model, forcing a reload. this is important because - // we know the file has changed on disk after the move and the - // model might have still existed with the previous state. this - // ensures we are not tracking a stale state. - const restoredModel = await this.models.loadOrCreate(modelToRestore.resource, { reload: { async: false } }); - - // restore previous dirty content if any and ensure to mark - // the model as dirty - if (modelToRestore.snapshot && restoredModel.isResolved()) { - this.modelService.updateModel(restoredModel.textEditorModel, createTextBufferFactoryFromSnapshot(modelToRestore.snapshot)); - - restoredModel.makeDirty(); - } - })); - - return stat; - } - - private async notifyOnWillMove(source: URI, target: URI): Promise { - const waitForPromises: Promise[] = []; - - // fire event - this._onWillMove.fire({ - oldResource: source, - newResource: target, - waitUntil(promise: Promise) { - waitForPromises.push(promise.then(undefined, errors.onUnexpectedError)); - } - }); - - // prevent async waitUntil-calls - Object.freeze(waitForPromises); - - await Promise.all(waitForPromises); - } - - //#endregion - - //#region save/revert - - async save(resource: URI, options?: ISaveOptions): Promise { - - // Run a forced save if we detect the file is not dirty so that save participants can still run - if (options && options.force && this.fileService.canHandleResource(resource) && !this.isDirty(resource)) { - const model = this._models.get(resource); - if (model) { - options.reason = SaveReason.EXPLICIT; - - await model.save(options); - - return !model.isDirty(); - } - } - - const result = await this.saveAll([resource], options); - - return result.results.length === 1 && !!result.results[0].success; - } - - async confirmSave(resources?: URI[]): Promise { - if (this.environmentService.isExtensionDevelopment) { - if (!this.environmentService.args['extension-development-confirm-save']) { - return ConfirmResult.DONT_SAVE; // no veto when we are in extension dev mode because we cannot assume we run interactive (e.g. tests) - } - } - - const resourcesToConfirm = this.getDirty(resources); - if (resourcesToConfirm.length === 0) { - return ConfirmResult.DONT_SAVE; - } - return promptSave(this.dialogService, resourcesToConfirm); - } - - async confirmOverwrite(resource: URI): Promise { - const confirm: IConfirmation = { - message: nls.localize('confirmOverwrite', "'{0}' already exists. Do you want to replace it?", basename(resource)), - detail: nls.localize('irreversible', "A file or folder with the same name already exists in the folder {0}. Replacing it will overwrite its current contents.", basename(dirname(resource))), - primaryButton: nls.localize({ key: 'replaceButtonLabel', comment: ['&& denotes a mnemonic'] }, "&&Replace"), - type: 'warning' - }; - - return (await this.dialogService.confirm(confirm)).confirmed; - } - - saveAll(includeUntitled?: boolean, options?: ISaveOptions): Promise; - saveAll(resources: URI[], options?: ISaveOptions): Promise; - saveAll(arg1?: boolean | URI[], options?: ISaveOptions): Promise { - - // get all dirty - let toSave: URI[] = []; - if (Array.isArray(arg1)) { - toSave = this.getDirty(arg1); - } else { - toSave = this.getDirty(); - } - - // split up between files and untitled - const filesToSave: URI[] = []; - const untitledToSave: URI[] = []; - toSave.forEach(resourceToSave => { - if ((Array.isArray(arg1) || arg1 === true /* includeUntitled */) && resourceToSave.scheme === Schemas.untitled) { - untitledToSave.push(resourceToSave); - } else { - filesToSave.push(resourceToSave); - } - }); - - return this.doSaveAll(filesToSave, untitledToSave, options); - } - - private async doSaveAll(fileResources: URI[], untitledResources: URI[], options?: ISaveOptions): Promise { - - // Handle files first that can just be saved - const result = await this.doSaveAllFiles(fileResources, options); - - // Preflight for untitled to handle cancellation from the dialog - const targetsForUntitled: URI[] = []; - for (const untitled of untitledResources) { - if (this.untitledEditorService.exists(untitled)) { - let targetUri: URI; - - // Untitled with associated file path don't need to prompt - if (this.untitledEditorService.hasAssociatedFilePath(untitled)) { - targetUri = toLocalResource(untitled, this.environmentService.configuration.remoteAuthority); - } - - // Otherwise ask user - else { - const targetPath = await this.promptForPath(untitled, this.suggestFileName(untitled)); - if (!targetPath) { - return { results: [...fileResources, ...untitledResources].map(r => ({ source: r })) }; - } - - targetUri = targetPath; - } - - targetsForUntitled.push(targetUri); - } - } - - // Handle untitled - await Promise.all(targetsForUntitled.map(async (target, index) => { - const uri = await this.saveAs(untitledResources[index], target); - - result.results.push({ - source: untitledResources[index], - target: uri, - success: !!uri - }); - })); - - return result; - } - - protected async promptForPath(resource: URI, defaultUri: URI, availableFileSystems?: string[]): Promise { - - // Help user to find a name for the file by opening it first - await this.editorService.openEditor({ resource, options: { revealIfOpened: true, preserveFocus: true } }); - - return this.fileDialogService.pickFileToSave(this.getSaveDialogOptions(defaultUri, availableFileSystems)); - } - - private getSaveDialogOptions(defaultUri: URI, availableFileSystems?: string[]): ISaveDialogOptions { - const options: ISaveDialogOptions = { - defaultUri, - title: nls.localize('saveAsTitle', "Save As"), - availableFileSystems, - }; - - // Filters are only enabled on Windows where they work properly - if (!platform.isWindows) { - return options; - } - - interface IFilter { name: string; extensions: string[]; } - - // Build the file filter by using our known languages - const ext: string | undefined = defaultUri ? extname(defaultUri) : undefined; - let matchingFilter: IFilter | undefined; - const filters: IFilter[] = coalesce(this.modeService.getRegisteredLanguageNames().map(languageName => { - const extensions = this.modeService.getExtensions(languageName); - if (!extensions || !extensions.length) { - return null; - } - - const filter: IFilter = { name: languageName, extensions: extensions.slice(0, 10).map(e => trim(e, '.')) }; - - if (ext && extensions.indexOf(ext) >= 0) { - matchingFilter = filter; - - return null; // matching filter will be added last to the top - } - - return filter; - })); - - // Filters are a bit weird on Windows, based on having a match or not: - // Match: we put the matching filter first so that it shows up selected and the all files last - // No match: we put the all files filter first - const allFilesFilter = { name: nls.localize('allFiles', "All Files"), extensions: ['*'] }; - if (matchingFilter) { - filters.unshift(matchingFilter); - filters.unshift(allFilesFilter); - } else { - filters.unshift(allFilesFilter); - } - - // Allow to save file without extension - filters.push({ name: nls.localize('noExt', "No Extension"), extensions: [''] }); - - options.filters = filters; - - return options; - } - - private async doSaveAllFiles(resources?: URI[], options: ISaveOptions = Object.create(null)): Promise { - const dirtyFileModels = this.getDirtyFileModels(Array.isArray(resources) ? resources : undefined /* Save All */) - .filter(model => { - if ((model.hasState(ModelState.CONFLICT) || model.hasState(ModelState.ERROR)) && (options.reason === SaveReason.AUTO || options.reason === SaveReason.FOCUS_CHANGE || options.reason === SaveReason.WINDOW_CHANGE)) { - return false; // if model is in save conflict or error, do not save unless save reason is explicit or not provided at all - } - - return true; - }); - - const mapResourceToResult = new ResourceMap(); - dirtyFileModels.forEach(m => { - mapResourceToResult.set(m.getResource(), { - source: m.getResource() - }); - }); - - await Promise.all(dirtyFileModels.map(async model => { - await model.save(options); - - if (!model.isDirty()) { - const result = mapResourceToResult.get(model.getResource()); - if (result) { - result.success = true; - } - } - })); - - return { results: mapResourceToResult.values() }; - } - - private getFileModels(arg1?: URI | URI[]): ITextFileEditorModel[] { - if (Array.isArray(arg1)) { - const models: ITextFileEditorModel[] = []; - arg1.forEach(resource => { - models.push(...this.getFileModels(resource)); - }); - - return models; - } - - return this._models.getAll(arg1); - } - - private getDirtyFileModels(resources?: URI | URI[]): ITextFileEditorModel[] { - return this.getFileModels(resources).filter(model => model.isDirty()); - } - - async saveAs(resource: URI, targetResource?: URI, options?: ISaveOptions): Promise { - - // Get to target resource - if (!targetResource) { - let dialogPath = resource; - if (resource.scheme === Schemas.untitled) { - dialogPath = this.suggestFileName(resource); - } - - targetResource = await this.promptForPath(resource, dialogPath, options ? options.availableFileSystems : undefined); - } - - if (!targetResource) { - return; // user canceled - } - - // Just save if target is same as models own resource - if (resource.toString() === targetResource.toString()) { - await this.save(resource, options); - - return resource; - } - - // Do it - return this.doSaveAs(resource, targetResource, options); - } - - private async doSaveAs(resource: URI, target: URI, options?: ISaveOptions): Promise { - - // Retrieve text model from provided resource if any - let model: ITextFileEditorModel | UntitledEditorModel | undefined; - if (this.fileService.canHandleResource(resource)) { - model = this._models.get(resource); - } else if (resource.scheme === Schemas.untitled && this.untitledEditorService.exists(resource)) { - model = await this.untitledEditorService.loadOrCreate({ resource }); - } - - // We have a model: Use it (can be null e.g. if this file is binary and not a text file or was never opened before) - let result: boolean; - if (model) { - result = await this.doSaveTextFileAs(model, resource, target, options); - } - - // Otherwise we can only copy - else { - await this.fileService.copy(resource, target); - - result = true; - } - - // Return early if the operation was not running - if (!result) { - return target; - } - - // Revert the source - await this.revert(resource); - - return target; - } - - private async doSaveTextFileAs(sourceModel: ITextFileEditorModel | UntitledEditorModel, resource: URI, target: URI, options?: ISaveOptions): Promise { - - // Prefer an existing model if it is already loaded for the given target resource - let targetExists: boolean = false; - let targetModel = this.models.get(target); - if (targetModel && targetModel.isResolved()) { - targetExists = true; - } - - // Otherwise create the target file empty if it does not exist already and resolve it from there - else { - targetExists = await this.fileService.exists(target); - - // create target model adhoc if file does not exist yet - if (!targetExists) { - await this.create(target, ''); - } - - targetModel = await this.models.loadOrCreate(target); - } - - try { - - // Confirm to overwrite if we have an untitled file with associated file where - // the file actually exists on disk and we are instructed to save to that file - // path. This can happen if the file was created after the untitled file was opened. - // See https://github.com/Microsoft/vscode/issues/67946 - let write: boolean; - if (sourceModel instanceof UntitledEditorModel && sourceModel.hasAssociatedFilePath && targetExists && isEqual(target, toLocalResource(sourceModel.getResource(), this.environmentService.configuration.remoteAuthority))) { - write = await this.confirmOverwrite(target); - } else { - write = true; - } - - if (!write) { - return false; - } - - // take over model value, encoding and mode (only if more specific) from source model - targetModel.updatePreferredEncoding(sourceModel.getEncoding()); - if (sourceModel.isResolved() && targetModel.isResolved()) { - this.modelService.updateModel(targetModel.textEditorModel, createTextBufferFactoryFromSnapshot(sourceModel.createSnapshot())); - - const sourceMode = sourceModel.textEditorModel.getLanguageIdentifier(); - const targetMode = targetModel.textEditorModel.getLanguageIdentifier(); - if (sourceMode.language !== PLAINTEXT_MODE_ID && targetMode.language === PLAINTEXT_MODE_ID) { - targetModel.textEditorModel.setMode(sourceMode); // only use if more specific than plain/text - } - } - - // save model - await targetModel.save(options); - - return true; - } catch (error) { - - // binary model: delete the file and run the operation again - if ( - (error).textFileOperationResult === TextFileOperationResult.FILE_IS_BINARY || - (error).fileOperationResult === FileOperationResult.FILE_TOO_LARGE - ) { - await this.fileService.del(target); - - return this.doSaveTextFileAs(sourceModel, resource, target, options); - } - - throw error; - } - } - - private suggestFileName(untitledResource: URI): URI { - const untitledFileName = this.untitledEditorService.suggestFileName(untitledResource); - const remoteAuthority = this.environmentService.configuration.remoteAuthority; - const schemeFilter = remoteAuthority ? Schemas.vscodeRemote : Schemas.file; - - const lastActiveFile = this.historyService.getLastActiveFile(schemeFilter); - if (lastActiveFile) { - const lastDir = dirname(lastActiveFile); - return joinPath(lastDir, untitledFileName); - } - - const lastActiveFolder = this.historyService.getLastActiveWorkspaceRoot(schemeFilter); - if (lastActiveFolder) { - return joinPath(lastActiveFolder, untitledFileName); - } - - return untitledResource.with({ path: untitledFileName }); - } - - async revert(resource: URI, options?: IRevertOptions): Promise { - const result = await this.revertAll([resource], options); - - return result.results.length === 1 && !!result.results[0].success; - } - - async revertAll(resources?: URI[], options?: IRevertOptions): Promise { - - // Revert files first - const revertOperationResult = await this.doRevertAllFiles(resources, options); - - // Revert untitled - const untitledReverted = this.untitledEditorService.revertAll(resources); - untitledReverted.forEach(untitled => revertOperationResult.results.push({ source: untitled, success: true })); - - return revertOperationResult; - } - - private async doRevertAllFiles(resources?: URI[], options?: IRevertOptions): Promise { - const fileModels = options && options.force ? this.getFileModels(resources) : this.getDirtyFileModels(resources); - - const mapResourceToResult = new ResourceMap(); - fileModels.forEach(m => { - mapResourceToResult.set(m.getResource(), { - source: m.getResource() - }); - }); - - await Promise.all(fileModels.map(async model => { - try { - await model.revert(options && options.soft); - - if (!model.isDirty()) { - const result = mapResourceToResult.get(model.getResource()); - if (result) { - result.success = true; - } - } - } catch (error) { - - // FileNotFound means the file got deleted meanwhile, so still record as successful revert - if ((error).fileOperationResult === FileOperationResult.FILE_NOT_FOUND) { - const result = mapResourceToResult.get(model.getResource()); - if (result) { - result.success = true; - } - } - - // Otherwise bubble up the error - else { - throw error; - } - } - })); - - return { results: mapResourceToResult.values() }; - } - - getDirty(resources?: URI[]): URI[] { - - // Collect files - const dirty = this.getDirtyFileModels(resources).map(m => m.getResource()); - - // Add untitled ones - dirty.push(...this.untitledEditorService.getDirty(resources)); - - return dirty; - } - - isDirty(resource?: URI): boolean { - - // Check for dirty file - if (this._models.getAll(resource).some(model => model.isDirty())) { - return true; - } - - // Check for dirty untitled - return this.untitledEditorService.getDirty().some(dirty => !resource || dirty.toString() === resource.toString()); - } - - //#endregion - - //#region config - - getAutoSaveMode(): AutoSaveMode { - if (this.configuredAutoSaveOnFocusChange) { - return AutoSaveMode.ON_FOCUS_CHANGE; - } - - if (this.configuredAutoSaveOnWindowChange) { - return AutoSaveMode.ON_WINDOW_CHANGE; - } - - if (this.configuredAutoSaveDelay && this.configuredAutoSaveDelay > 0) { - return this.configuredAutoSaveDelay <= 1000 ? AutoSaveMode.AFTER_SHORT_DELAY : AutoSaveMode.AFTER_LONG_DELAY; - } - - return AutoSaveMode.OFF; - } - - getAutoSaveConfiguration(): IAutoSaveConfiguration { - return { - autoSaveDelay: this.configuredAutoSaveDelay && this.configuredAutoSaveDelay > 0 ? this.configuredAutoSaveDelay : undefined, - autoSaveFocusChange: !!this.configuredAutoSaveOnFocusChange, - autoSaveApplicationChange: !!this.configuredAutoSaveOnWindowChange - }; - } - - get isHotExitEnabled(): boolean { - return !this.environmentService.isExtensionDevelopment && this.configuredHotExit !== HotExitConfiguration.OFF; - } - - //#endregion - - dispose(): void { - - // Clear all caches - this._models.clear(); - - super.dispose(); - } -} - -export async function promptSave(dialogService: IDialogService, resourcesToConfirm: readonly URI[]) { - const message = resourcesToConfirm.length === 1 - ? nls.localize('saveChangesMessage', "Do you want to save the changes you made to {0}?", basename(resourcesToConfirm[0])) - : getConfirmMessage(nls.localize('saveChangesMessages', "Do you want to save the changes to the following {0} files?", resourcesToConfirm.length), resourcesToConfirm); - - const buttons: string[] = [ - resourcesToConfirm.length > 1 ? nls.localize({ key: 'saveAll', comment: ['&& denotes a mnemonic'] }, "&&Save All") : nls.localize({ key: 'save', comment: ['&& denotes a mnemonic'] }, "&&Save"), - nls.localize({ key: 'dontSave', comment: ['&& denotes a mnemonic'] }, "Do&&n't Save"), - nls.localize('cancel', "Cancel") - ]; - - const { choice } = await dialogService.show(Severity.Warning, message, buttons, { - cancelId: 2, - detail: nls.localize('saveChangesDetail', "Your changes will be lost if you don't save them.") - }); - - switch (choice) { - case 0: return ConfirmResult.SAVE; - case 1: return ConfirmResult.DONT_SAVE; - default: return ConfirmResult.CANCEL; - } -} - diff --git a/src/vs/workbench/services/textfile/node/textFileService.ts b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts similarity index 98% rename from src/vs/workbench/services/textfile/node/textFileService.ts rename to src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts index ed012a4c702..d621479da05 100644 --- a/src/vs/workbench/services/textfile/node/textFileService.ts +++ b/src/vs/workbench/services/textfile/electron-browser/nativeTextFileService.ts @@ -5,7 +5,7 @@ import { tmpdir } from 'os'; import { localize } from 'vs/nls'; -import { TextFileService } from 'vs/workbench/services/textfile/common/textFileService'; +import { AbstractTextFileService } from 'vs/workbench/services/textfile/browser/textFileService'; import { ITextFileService, ITextFileStreamContent, ITextFileContent, IResourceEncodings, IResourceEncoding, IReadTextFileOptions, IWriteTextFileOptions, stringToSnapshot, TextFileOperationResult, TextFileOperationError } from 'vs/workbench/services/textfile/common/textfiles'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { URI } from 'vs/base/common/uri'; @@ -28,7 +28,7 @@ import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textMo import { ITextSnapshot } from 'vs/editor/common/model'; import { nodeReadableToString, streamToNodeReadable, nodeStreamToVSBufferReadable } from 'vs/base/node/stream'; -export class NodeTextFileService extends TextFileService { +export class NativeTextFileService extends AbstractTextFileService { private _encoding!: EncodingOracle; get encoding(): EncodingOracle { @@ -404,4 +404,4 @@ export class EncodingOracle extends Disposable implements IResourceEncodings { } } -registerSingleton(ITextFileService, NodeTextFileService); +registerSingleton(ITextFileService, NativeTextFileService); diff --git a/src/vs/workbench/services/textfile/test/textFileService.io.test.ts b/src/vs/workbench/services/textfile/test/textFileService.io.test.ts index eab223e0d4a..6d8282d7830 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.io.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.io.test.ts @@ -22,7 +22,7 @@ import { generateUuid } from 'vs/base/common/uuid'; import { join, basename } from 'vs/base/common/path'; import { getPathFromAmdModule } from 'vs/base/common/amd'; import { UTF16be, UTF16le, UTF8_with_bom, UTF8 } from 'vs/base/node/encoding'; -import { NodeTextFileService, EncodingOracle, IEncodingOverride } from 'vs/workbench/services/textfile/node/textFileService'; +import { NativeTextFileService, EncodingOracle, IEncodingOverride } from 'vs/workbench/services/textfile/electron-browser/nativeTextFileService'; import { DefaultEndOfLine, ITextSnapshot } from 'vs/editor/common/model'; import { TextModel } from 'vs/editor/common/model/textModel'; import { isWindows } from 'vs/base/common/platform'; @@ -37,7 +37,7 @@ class ServiceAccessor { } } -class TestNodeTextFileService extends NodeTextFileService { +class TestNativeTextFileService extends NativeTextFileService { private _testEncoding: TestEncodingOracle | undefined; get encoding(): TestEncodingOracle { @@ -84,7 +84,7 @@ suite('Files - TextFileService i/o', () => { const collection = new ServiceCollection(); collection.set(IFileService, fileService); - service = instantiationService.createChild(collection).createInstance(TestNodeTextFileService); + service = instantiationService.createChild(collection).createInstance(TestNativeTextFileService); const id = generateUuid(); testDir = join(parentDir, id); diff --git a/src/vs/workbench/services/textfile/test/textFileService.test.ts b/src/vs/workbench/services/textfile/test/textFileService.test.ts index 07676420066..c8e4de0a55a 100644 --- a/src/vs/workbench/services/textfile/test/textFileService.test.ts +++ b/src/vs/workbench/services/textfile/test/textFileService.test.ts @@ -7,7 +7,7 @@ import * as sinon from 'sinon'; import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { ILifecycleService, BeforeShutdownEvent, ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle'; -import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, TestWindowsService, TestContextService, TestFileService } from 'vs/workbench/test/workbenchTestServices'; +import { workbenchInstantiationService, TestLifecycleService, TestTextFileService, TestWindowsService, TestContextService, TestFileService, TestHostService } from 'vs/workbench/test/workbenchTestServices'; import { toResource } from 'vs/base/test/common/utils'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IWindowsService } from 'vs/platform/windows/common/windows'; @@ -21,6 +21,7 @@ import { IWorkspaceContextService, Workspace } from 'vs/platform/workspace/commo import { IModelService } from 'vs/editor/common/services/modelService'; import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl'; import { Schemas } from 'vs/base/common/network'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; class ServiceAccessor { constructor( @@ -30,7 +31,8 @@ class ServiceAccessor { @IWindowsService public windowsService: TestWindowsService, @IWorkspaceContextService public contextService: TestContextService, @IModelService public modelService: ModelServiceImpl, - @IFileService public fileService: TestFileService + @IFileService public fileService: TestFileService, + @IHostService public hostService: TestHostService ) { } } @@ -424,7 +426,7 @@ suite('Files - TextFileService', () => { } // Set multiple windows if required if (multipleWindows) { - accessor.windowsService.windowCount = 2; + accessor.hostService.windowCount = Promise.resolve(2); } // Set cancel to force a veto if hot exit does not trigger service.setConfirmResult(ConfirmResult.CANCEL); diff --git a/src/vs/workbench/services/timer/electron-browser/timerService.ts b/src/vs/workbench/services/timer/electron-browser/timerService.ts index 3bf0c2fee4e..aefb915e2b6 100644 --- a/src/vs/workbench/services/timer/electron-browser/timerService.ts +++ b/src/vs/workbench/services/timer/electron-browser/timerService.ts @@ -7,7 +7,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation' import { virtualMachineHint } from 'vs/base/node/id'; import * as perf from 'vs/base/common/performance'; import * as os from 'os'; -import { IWindowsService } from 'vs/platform/windows/common/windows'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -308,7 +308,7 @@ class TimerService implements ITimerService { private _startupMetrics?: Promise; constructor( - @IWindowsService private readonly _windowsService: IWindowsService, + @IHostService private readonly _hostService: IHostService, @IWorkbenchEnvironmentService private readonly _environmentService: IWorkbenchEnvironmentService, @ILifecycleService private readonly _lifecycleService: ILifecycleService, @IWorkspaceContextService private readonly _contextService: IWorkspaceContextService, @@ -380,7 +380,7 @@ class TimerService implements ITimerService { isLatestVersion: Boolean(await this._updateService.isLatestVersion()), didUseCachedData: didUseCachedData(), windowKind: this._lifecycleService.startupKind, - windowCount: await this._windowsService.getWindowCount(), + windowCount: await this._hostService.windowCount, viewletId: activeViewlet ? activeViewlet.getId() : undefined, editorIds: this._editorService.visibleEditors.map(input => input.getTypeId()), panelId: activePanel ? activePanel.getId() : undefined, diff --git a/src/vs/workbench/services/title/common/titleService.ts b/src/vs/workbench/services/title/common/titleService.ts index b3476f94deb..a827857a797 100644 --- a/src/vs/workbench/services/title/common/titleService.ts +++ b/src/vs/workbench/services/title/common/titleService.ts @@ -14,6 +14,7 @@ export interface ITitleProperties { } export interface ITitleService { + _serviceBrand: undefined; /** @@ -25,4 +26,4 @@ export interface ITitleService { * Update some environmental title properties. */ updateProperties(properties: ITitleProperties): void; -} \ No newline at end of file +} diff --git a/src/vs/workbench/services/workspace/browser/workspaceEditingService.ts b/src/vs/workbench/services/workspace/browser/workspaceEditingService.ts index bffc453c49b..a97ea749b8a 100644 --- a/src/vs/workbench/services/workspace/browser/workspaceEditingService.ts +++ b/src/vs/workbench/services/workspace/browser/workspaceEditingService.ts @@ -31,6 +31,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ILabelService } from 'vs/platform/label/common/label'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; export class WorkspaceEditingService implements IWorkspaceEditingService { @@ -54,7 +55,8 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { @IFileDialogService private readonly fileDialogService: IFileDialogService, @IDialogService private readonly dialogService: IDialogService, @ILifecycleService readonly lifecycleService: ILifecycleService, - @ILabelService readonly labelService: ILabelService + @ILabelService readonly labelService: ILabelService, + @IHostService private readonly hostService: IHostService ) { this.registerListeners(); } @@ -82,7 +84,7 @@ export class WorkspaceEditingService implements IWorkspaceEditingService { return false; // only care about untitled workspaces to ask for saving } - const windowCount = await this.windowsService.getWindowCount(); + const windowCount = await this.hostService.windowCount; if (reason === ShutdownReason.CLOSE && !isMacintosh && windowCount === 1) { return false; // Windows/Linux: quits when last window is closed, so do not ask then diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 6c4add507aa..4ed07ea15ed 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -82,10 +82,11 @@ import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedPr import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; import { WorkbenchEnvironmentService } from 'vs/workbench/services/environment/node/environmentService'; import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer'; -import { NodeTextFileService } from 'vs/workbench/services/textfile/node/textFileService'; +import { NativeTextFileService } from 'vs/workbench/services/textfile/electron-browser/nativeTextFileService'; import { Schemas } from 'vs/base/common/network'; import { IProductService } from 'vs/platform/product/common/productService'; import product from 'vs/platform/product/common/product'; +import { IHostService } from 'vs/workbench/services/host/browser/host'; export function createFileInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput { return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined); @@ -180,7 +181,7 @@ export class TestContextService implements IWorkspaceContextService { } } -export class TestTextFileService extends NodeTextFileService { +export class TestTextFileService extends NativeTextFileService { public cleanupBackupsBeforeShutdownCalled: boolean; private promptPath: URI; @@ -199,7 +200,7 @@ export class TestTextFileService extends NodeTextFileService { @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, @INotificationService notificationService: INotificationService, @IBackupFileService backupFileService: IBackupFileService, - @IWindowsService windowsService: IWindowsService, + @IHostService hostService: IHostService, @IHistoryService historyService: IHistoryService, @IContextKeyService contextKeyService: IContextKeyService, @IDialogService dialogService: IDialogService, @@ -219,7 +220,7 @@ export class TestTextFileService extends NodeTextFileService { environmentService, notificationService, backupFileService, - windowsService, + hostService, historyService, contextKeyService, dialogService, @@ -311,6 +312,7 @@ export function workbenchInstantiationService(): IInstantiationService { instantiationService.stub(IDecorationsService, new TestDecorationsService()); instantiationService.stub(IExtensionService, new TestExtensionService()); instantiationService.stub(IWindowsService, new TestWindowsService()); + instantiationService.stub(IHostService, instantiationService.createInstance(TestHostService)); instantiationService.stub(ITextFileService, instantiationService.createInstance(TestTextFileService)); instantiationService.stub(ITextModelService, instantiationService.createInstance(TextModelResolverService)); instantiationService.stub(IThemeService, new TestThemeService()); @@ -1349,8 +1351,6 @@ export class TestWindowsService implements IWindowsService { _serviceBrand: undefined; - public windowCount = 1; - readonly onWindowOpen: Event = Event.None; readonly onWindowFocus: Event = Event.None; readonly onWindowBlur: Event = Event.None; @@ -1490,18 +1490,6 @@ export class TestWindowsService implements IWindowsService { throw new Error('not implemented'); } - getWindowCount(): Promise { - return Promise.resolve(this.windowCount); - } - - log(_severity: string, _args: string[]): Promise { - return Promise.resolve(); - } - - showItemInFolder(_path: URI): Promise { - return Promise.resolve(); - } - newWindowTab(): Promise { return Promise.resolve(); } @@ -1642,3 +1630,10 @@ export class RemoteFileSystemProvider implements IFileSystemProvider { } export const productService: IProductService = { _serviceBrand: undefined, ...product }; + +export class TestHostService implements IHostService { + + _serviceBrand: undefined; + + windowCount = Promise.resolve(1); +} diff --git a/src/vs/workbench/workbench.common.main.ts b/src/vs/workbench/workbench.common.main.ts index 4526dbac1b2..8b65d5e4942 100644 --- a/src/vs/workbench/workbench.common.main.ts +++ b/src/vs/workbench/workbench.common.main.ts @@ -81,6 +81,7 @@ import 'vs/workbench/services/notification/common/notificationService'; import 'vs/workbench/services/extensions/common/staticExtensions'; import 'vs/workbench/services/userDataSync/common/settingsMergeService'; import 'vs/workbench/services/workspace/browser/workspaceEditingService'; +import 'vs/workbench/services/host/browser/browserHostService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { ExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionGalleryService'; diff --git a/src/vs/workbench/workbench.desktop.main.ts b/src/vs/workbench/workbench.desktop.main.ts index e864ab64498..7369bc83800 100644 --- a/src/vs/workbench/workbench.desktop.main.ts +++ b/src/vs/workbench/workbench.desktop.main.ts @@ -31,7 +31,7 @@ import 'vs/workbench/services/integrity/node/integrityService'; import 'vs/workbench/services/textMate/electron-browser/textMateService'; import 'vs/workbench/services/search/node/searchService'; import 'vs/workbench/services/output/node/outputChannelModelService'; -import 'vs/workbench/services/textfile/node/textFileService'; +import 'vs/workbench/services/textfile/electron-browser/nativeTextFileService'; import 'vs/workbench/services/dialogs/electron-browser/dialogService'; import 'vs/workbench/services/keybinding/electron-browser/nativeKeymapService'; import 'vs/workbench/services/keybinding/electron-browser/keybinding.contribution'; @@ -50,6 +50,7 @@ import 'vs/workbench/services/credentials/node/credentialsService'; import 'vs/workbench/services/url/electron-browser/urlService'; import 'vs/workbench/services/workspace/electron-browser/workspacesService'; import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncService'; +import 'vs/workbench/services/host/electron-browser/desktopHostService'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; @@ -91,6 +92,9 @@ registerSingleton(IElectronService, ElectronService, true); // Localizations import 'vs/workbench/contrib/localizations/browser/localizations.contribution'; +// Logs +import 'vs/workbench/contrib/logs/electron-browser/logs.contribution'; + // Stats import 'vs/workbench/contrib/stats/electron-browser/workspaceStatsService'; import 'vs/workbench/contrib/stats/electron-browser/stats.contribution'; @@ -98,6 +102,9 @@ import 'vs/workbench/contrib/stats/electron-browser/stats.contribution'; // Rapid Render Splash import 'vs/workbench/contrib/splash/electron-browser/partsSplash.contribution'; +// Explorer +import 'vs/workbench/contrib/files/electron-browser/fileActions.contribution'; + // Debug import 'vs/workbench/contrib/debug/node/debugHelperService'; import 'vs/workbench/contrib/debug/electron-browser/extensionHostDebugService'; diff --git a/src/vs/workbench/workbench.web.api.ts b/src/vs/workbench/workbench.web.api.ts index cd4351d6016..38bb092333d 100644 --- a/src/vs/workbench/workbench.web.api.ts +++ b/src/vs/workbench/workbench.web.api.ts @@ -90,6 +90,11 @@ interface IWorkbenchConstructionOptions { * Experimental: Support for update reporting. */ updateProvider?: IUpdateProvider; + + /** + * Experimental: Resolves an external uri before it is opened. + */ + readonly resolveExternalUri?: (uri: URI) => Promise; } /** diff --git a/src/vs/workbench/workbench.web.main.ts b/src/vs/workbench/workbench.web.main.ts index 65078676b4e..be0ad2cf0b1 100644 --- a/src/vs/workbench/workbench.web.main.ts +++ b/src/vs/workbench/workbench.web.main.ts @@ -30,7 +30,7 @@ import 'vs/workbench/services/integrity/browser/integrityService'; import 'vs/workbench/services/textMate/browser/textMateService'; import 'vs/workbench/services/search/common/searchService'; import 'vs/workbench/services/output/common/outputChannelModelService'; -import 'vs/workbench/services/textfile/browser/textFileService'; +import 'vs/workbench/services/textfile/browser/browserTextFileService'; import 'vs/workbench/services/keybinding/browser/keymapService'; import 'vs/workbench/services/extensions/browser/extensionService'; import 'vs/workbench/services/extensionManagement/common/extensionManagementServerService'; diff --git a/test/automation/src/keybindings.ts b/test/automation/src/keybindings.ts index 9544b5992db..70be12b2378 100644 --- a/test/automation/src/keybindings.ts +++ b/test/automation/src/keybindings.ts @@ -24,7 +24,7 @@ export class KeybindingsEditor { await this.code.waitAndClick('.keybindings-list-container .monaco-list-row.keybinding-item'); await this.code.waitForElement('.keybindings-list-container .monaco-list-row.keybinding-item.focused.selected'); - await this.code.waitAndClick('.keybindings-list-container .monaco-list-row.keybinding-item .action-item .icon.add'); + await this.code.waitAndClick('.keybindings-list-container .monaco-list-row.keybinding-item .action-item .codicon.add'); await this.code.waitForActiveElement('.defineKeybindingWidget .monaco-inputbox input'); await this.code.dispatchKeybinding(keybinding);