diff --git a/.eslintplugin/code-no-global-document-listener.ts b/.eslintplugin/code-no-global-document-listener.ts new file mode 100644 index 00000000000..6b3e83fe1f5 --- /dev/null +++ b/.eslintplugin/code-no-global-document-listener.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as eslint from 'eslint'; + +export = new class NoGlobalDocumentListener implements eslint.Rule.RuleModule { + + create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener { + return { + CallExpression(node: any) { + if ( + ( + node.callee.name === 'addDisposableListener' || + node.callee.property?.name === 'addDisposableListener' + ) && + node.arguments.length > 0 && + node.arguments[0].type === 'Identifier' && + node.arguments[0].name === 'document' + ) { + context.report({ + node, + message: 'Use .document to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant.', + }); + } + }, + } + } +}; diff --git a/.eslintrc.json b/.eslintrc.json index d7edeea91b8..68ec2cf473f 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -48,25 +48,6 @@ "orientation", "context" ], // non-complete list of globals that are easy to access unintentionally - "no-restricted-syntax": [ - "warn", - { - "selector": "BinaryExpression[operator='instanceof'][right.name='MouseEvent']", - "message": "Use DOM.isMouseEvent() to support multi-window scenarios." - }, - { - "selector": "BinaryExpression[operator='instanceof'][right.name='KeyboardEvent']", - "message": "Use DOM.isKeyboardEvent() to support multi-window scenarios." - }, - { - "selector": "BinaryExpression[operator='instanceof'][right.name='PointerEvent']", - "message": "Use DOM.isPointerEvent() to support multi-window scenarios." - }, - { - "selector": "BinaryExpression[operator='instanceof'][right.name='DragEvent']", - "message": "Use DOM.isDragEvent() to support multi-window scenarios." - } - ], "no-var": "warn", "jsdoc/no-types": "warn", "semi": "off", @@ -275,6 +256,309 @@ "local/code-amd-node-module": "warn" } }, + { + "files": [ + "src/**/{browser,electron-sandbox}/*.ts" + ], + "rules": { + "local/code-no-global-document-listener": "warn", + "no-restricted-syntax": [ + "warn", + { + "selector": "BinaryExpression[operator='instanceof'][right.name='MouseEvent']", + "message": "Use DOM.isMouseEvent() to support multi-window scenarios." + }, + { + "selector": "BinaryExpression[operator='instanceof'][right.name='KeyboardEvent']", + "message": "Use DOM.isKeyboardEvent() to support multi-window scenarios." + }, + { + "selector": "BinaryExpression[operator='instanceof'][right.name='PointerEvent']", + "message": "Use DOM.isPointerEvent() to support multi-window scenarios." + }, + { + "selector": "BinaryExpression[operator='instanceof'][right.name='DragEvent']", + "message": "Use DOM.isDragEvent() to support multi-window scenarios." + }, + { + "selector": "MemberExpression[object.name='document'][property.name='activeElement']", + "message": "Use .document.activeElement to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "selector": "MemberExpression[object.name='document'][property.name='contains']", + "message": "Use .document.contains to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "selector": "MemberExpression[object.name='document'][property.name='styleSheets']", + "message": "Use .document.styleSheets to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "selector": "MemberExpression[object.name='document'][property.name='fullscreenElement']", + "message": "Use .document.fullscreenElement to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "selector": "MemberExpression[object.name='document'][property.name='body']", + "message": "Use .document.body to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "selector": "MemberExpression[object.name='document'][property.name='addEventListener']", + "message": "Use .document.addEventListener to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "selector": "MemberExpression[object.name='document'][property.name='removeEventListener']", + "message": "Use .document.removeEventListener to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "selector": "MemberExpression[object.name='document'][property.name='hasFocus']", + "message": "Use .document.hasFocus to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "selector": "MemberExpression[object.name='document'][property.name='head']", + "message": "Use .document.head to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "selector": "MemberExpression[object.name='document'][property.name='exitFullscreen']", + "message": "Use .document.exitFullscreen to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "selector": "MemberExpression[object.name='document'][property.name='getElementById']", + "message": "Use .document.getElementById to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "selector": "MemberExpression[object.name='document'][property.name='getElementsByClassName']", + "message": "Use .document.getElementsByClassName to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "selector": "MemberExpression[object.name='document'][property.name='getElementsByName']", + "message": "Use .document.getElementsByName to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "selector": "MemberExpression[object.name='document'][property.name='getElementsByTagName']", + "message": "Use .document.getElementsByTagName to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "selector": "MemberExpression[object.name='document'][property.name='getElementsByTagNameNS']", + "message": "Use .document.getElementsByTagNameNS to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "selector": "MemberExpression[object.name='document'][property.name='getSelection']", + "message": "Use .document.getSelection to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "selector": "MemberExpression[object.name='document'][property.name='open']", + "message": "Use .document.open to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "selector": "MemberExpression[object.name='document'][property.name='close']", + "message": "Use .document.close to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "selector": "MemberExpression[object.name='document'][property.name='documentElement']", + "message": "Use .document.documentElement to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "selector": "MemberExpression[object.name='document'][property.name='visibilityState']", + "message": "Use .document.visibilityState to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "selector": "MemberExpression[object.name='document'][property.name='querySelector']", + "message": "Use .document.querySelector to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "selector": "MemberExpression[object.name='document'][property.name='querySelectorAll']", + "message": "Use .document.querySelectorAll to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "selector": "MemberExpression[object.name='document'][property.name='elementFromPoint']", + "message": "Use .document.elementFromPoint to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "selector": "MemberExpression[object.name='document'][property.name='elementsFromPoint']", + "message": "Use .document.elementsFromPoint to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "selector": "MemberExpression[object.name='document'][property.name='onkeydown']", + "message": "Use .document.onkeydown to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "selector": "MemberExpression[object.name='document'][property.name='onkeyup']", + "message": "Use .document.onkeyup to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "selector": "MemberExpression[object.name='document'][property.name='onmousedown']", + "message": "Use .document.onmousedown to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "selector": "MemberExpression[object.name='document'][property.name='onmouseup']", + "message": "Use .document.onmouseup to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "selector": "MemberExpression[object.name='document'][property.name='execCommand']", + "message": "Use .document.execCommand to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + } + ], + "no-restricted-globals": [ + "warn", + "name", + "length", + "event", + "closed", + "external", + "status", + "origin", + "orientation", + "context", + { + "name": "setInterval", + "message": "Use .setInterval to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "clearInterval", + "message": "Use .clearInterval to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "requestAnimationFrame", + "message": "Use .requestAnimationFrame to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "cancelAnimationFrame", + "message": "Use .cancelAnimationFrame to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "requestIdleCallback", + "message": "Use .requestIdleCallback to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "cancelIdleCallback", + "message": "Use .cancelIdleCallback to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "window", + "message": "Use to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "addEventListener", + "message": "Use .addEventListener to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "removeEventListener", + "message": "Use .removeEventListener to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "getComputedStyle", + "message": "Use .getComputedStyle to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "focus", + "message": "Use .focus to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "blur", + "message": "Use .blur to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "close", + "message": "Use .close to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "dispatchEvent", + "message": "Use .dispatchEvent to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "getSelection", + "message": "Use .getSelection to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "matchMedia", + "message": "Use .matchMedia to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "open", + "message": "Use .open to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "parent", + "message": "Use .parent to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "postMessage", + "message": "Use .postMessage to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "devicePixelRatio", + "message": "Use .devicePixelRatio to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "frames", + "message": "Use .frames to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "frameElement", + "message": "Use .frameElement to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "innerHeight", + "message": "Use .innerHeight to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "innerWidth", + "message": "Use .innerWidth to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "outerHeight", + "message": "Use .outerHeight to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "outerWidth", + "message": "Use .outerWidth to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "opener", + "message": "Use .opener to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "origin", + "message": "Use .origin to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "screen", + "message": "Use .screen to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "screenLeft", + "message": "Use .screenLeft to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "screenTop", + "message": "Use .screenTop to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "screenX", + "message": "Use .screenX to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "screenY", + "message": "Use .screenY to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "scrollX", + "message": "Use .scrollX to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "scrollY", + "message": "Use .scrollY to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "top", + "message": "Use .top to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + }, + { + "name": "visualViewport", + "message": "Use .visualViewport to support multi-window scenarios. Resolve targetWindow with DOM.getWindow(element) or DOM.getActiveWindow() or use the predefined mainWindow constant." + } + ] + } + }, { "files": [ "src/**/*.ts" diff --git a/.github/commands.json b/.github/commands.json index ea5f8bc38e1..7b04c7475d7 100644 --- a/.github/commands.json +++ b/.github/commands.json @@ -58,6 +58,13 @@ "reason": "not_planned", "comment": "We closed this issue because we [don't plan to address it](https://aka.ms/vscode-out-of-scope) in the foreseeable future. If you disagree and feel that this issue is crucial: we are happy to listen and to reconsider.\n\nIf you wonder what we are up to, please see our [roadmap](https://aka.ms/vscoderoadmap) and [issue reporting guidelines](https://aka.ms/vscodeissuereporting).\n\nThanks for your understanding, and happy coding!" }, + { + "type": "label", + "name": "wont-fix", + "action": "close", + "reason": "not_planned", + "comment": "We closed this issue because we [don't plan to address it](https://github.com/microsoft/vscode/wiki/Issue-Grooming#wont-fix-bugs).\n\nThanks for your understanding, and happy coding!" + }, { "type": "comment", "name": "causedByExtension", diff --git a/.github/workflows/monaco-editor.yml b/.github/workflows/monaco-editor.yml index eb263b3340c..54012f54e78 100644 --- a/.github/workflows/monaco-editor.yml +++ b/.github/workflows/monaco-editor.yml @@ -47,7 +47,9 @@ jobs: restore-keys: ${{ runner.os }}-yarnCacheDir- - name: Install libkrb5-dev if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} - run: sudo apt install -y libkrb5-dev + run: | + sudo apt update + sudo apt install -y libkrb5-dev - name: Execute yarn if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }} env: diff --git a/.vscode/settings.json b/.vscode/settings.json index 98bd618b14f..417af3874f6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -164,5 +164,14 @@ "vscode-notebook-renderer", "src/vs/workbench/workbench.web.main.ts", "src/vs/workbench/api/common/extHostTypes.ts" - ] + ], + + // Temporarily enabled for self-hosting + "terminal.integrated.enableStickyScroll": true, + + // Temporarily enabled for self-hosting + "scm.experimental.showSyncInformation": { + "incoming": true, + "outgoing": true + } } diff --git a/build/azure-pipelines/linux/product-build-linux-test.yml b/build/azure-pipelines/linux/product-build-linux-test.yml index bb75be37e4b..74ebb91e344 100644 --- a/build/azure-pipelines/linux/product-build-linux-test.yml +++ b/build/azure-pipelines/linux/product-build-linux-test.yml @@ -151,6 +151,10 @@ steps: - script: yarn gulp compile-extension:markdown-language-features compile-extension-media compile-extension:vscode-test-resolver displayName: Build extensions for smoke tests + - script: yarn gulp node + displayName: Download node.js for remote smoke tests + retryCountOnTaskFailure: 3 + - script: yarn smoketest-no-compile --tracing timeoutInMinutes: 20 displayName: Run smoke tests (Electron) diff --git a/build/lib/i18n.resources.json b/build/lib/i18n.resources.json index a29d78b2167..cf74aac44be 100644 --- a/build/lib/i18n.resources.json +++ b/build/lib/i18n.resources.json @@ -74,6 +74,10 @@ "name": "vs/workbench/contrib/dialogs", "project": "vscode-workbench" }, + { + "name": "vs/workbench/contrib/multiDiffEditor", + "project": "vscode-workbench" + }, { "name": "vs/workbench/contrib/emmet", "project": "vscode-workbench" diff --git a/build/lib/layersChecker.js b/build/lib/layersChecker.js index 4dfa1b44d4b..f94674d79d2 100644 --- a/build/lib/layersChecker.js +++ b/build/lib/layersChecker.js @@ -95,6 +95,21 @@ const RULES = [ '@types/node' // no node.js ] }, + // Common: vs/base/common/async.ts + { + target: '**/vs/base/common/async.ts', + allowedTypes: [ + ...CORE_TYPES, + // Safe access to requestIdleCallback & cancelIdleCallback + 'requestIdleCallback', + 'cancelIdleCallback' + ], + disallowedTypes: NATIVE_TYPES, + disallowedDefinitions: [ + 'lib.dom.d.ts', // no DOM + '@types/node' // no node.js + ] + }, // Common: vs/platform/environment/common/* { target: '**/vs/platform/environment/common/*.ts', @@ -125,6 +140,16 @@ const RULES = [ '@types/node' // no node.js ] }, + // Common: vs/platform/native/common/nativeHostService.ts + { + target: '**/vs/platform/native/common/nativeHostService.ts', + allowedTypes: CORE_TYPES, + disallowedTypes: [ /* Ignore native types that are defined from here */], + disallowedDefinitions: [ + 'lib.dom.d.ts', // no DOM + '@types/node' // no node.js + ] + }, // Common: vs/workbench/api/common/extHostExtensionService.ts { target: '**/vs/workbench/api/common/extHostExtensionService.ts', @@ -288,4 +313,4 @@ for (const sourceFile of program.getSourceFiles()) { if (hasErrors) { process.exit(1); } -//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibGF5ZXJzQ2hlY2tlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImxheWVyc0NoZWNrZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOztBQUVoRyxpQ0FBaUM7QUFDakMsMkJBQThDO0FBQzlDLCtCQUE4QztBQUM5Qyx5Q0FBa0M7QUFFbEMsRUFBRTtBQUNGLGdHQUFnRztBQUNoRyxFQUFFO0FBQ0YsK0ZBQStGO0FBQy9GLG1EQUFtRDtBQUNuRCw0RUFBNEU7QUFDNUUsaUVBQWlFO0FBQ2pFLEVBQUU7QUFDRixnR0FBZ0c7QUFDaEcsRUFBRTtBQUNGLGdHQUFnRztBQUNoRyxFQUFFO0FBRUYsbUZBQW1GO0FBQ25GLHdGQUF3RjtBQUN4RixNQUFNLFVBQVUsR0FBRztJQUNsQixTQUFTLEVBQUUsc0JBQXNCO0lBQ2pDLFlBQVk7SUFDWixjQUFjO0lBQ2QsYUFBYTtJQUNiLGVBQWU7SUFDZixTQUFTO0lBQ1QsU0FBUztJQUNULE9BQU87SUFDUCxrQkFBa0I7SUFDbEIsUUFBUTtJQUNSLGFBQWE7SUFDYixhQUFhO0lBQ2IsTUFBTTtJQUNOLGdCQUFnQjtJQUNoQixPQUFPO0lBQ1AsWUFBWTtJQUNaLGFBQWE7SUFDYixhQUFhO0lBQ2IsV0FBVztJQUNYLFlBQVk7SUFDWixZQUFZO0lBQ1osY0FBYztJQUNkLGNBQWM7SUFDZCxtQkFBbUI7SUFDbkIsZ0JBQWdCO0lBQ2hCLGVBQWU7SUFDZixNQUFNO0lBQ04sTUFBTTtJQUNOLGlCQUFpQjtJQUNqQixhQUFhO0lBQ2IsZ0JBQWdCO0lBQ2hCLGFBQWE7SUFDYixLQUFLO0lBQ0wsaUJBQWlCO0lBQ2pCLGVBQWU7SUFDZixPQUFPO0lBQ1AsYUFBYTtJQUNiLGtCQUFrQjtJQUNsQixhQUFhO0lBQ2IsTUFBTTtDQUNOLENBQUM7QUFFRixvRUFBb0U7QUFDcEUsb0VBQW9FO0FBQ3BFLE1BQU0sWUFBWSxHQUFHO0lBQ3BCLGtCQUFrQjtJQUNsQiwyQkFBMkI7SUFDM0Isa0NBQWtDO0lBQ2xDLDRCQUE0QjtJQUM1QiwwQkFBMEI7SUFDMUIsb0JBQW9CO0lBQ3BCLHFCQUFxQjtDQUNyQixDQUFDO0FBRUYsTUFBTSxLQUFLLEdBQVk7SUFFdEIsY0FBYztJQUNkO1FBQ0MsTUFBTSxFQUFFLGtCQUFrQjtRQUMxQixJQUFJLEVBQUUsSUFBSSxDQUFDLHlCQUF5QjtLQUNwQztJQUVELHFDQUFxQztJQUNyQztRQUNDLE1BQU0sRUFBRSwrQkFBK0I7UUFDdkMsWUFBWSxFQUFFO1lBQ2IsR0FBRyxVQUFVO1lBRWIsMkNBQTJDO1lBQzNDLGNBQWM7U0FDZDtRQUNELGVBQWUsRUFBRSxZQUFZO1FBQzdCLHFCQUFxQixFQUFFO1lBQ3RCLGNBQWMsRUFBRSxTQUFTO1lBQ3pCLGFBQWEsQ0FBQyxhQUFhO1NBQzNCO0tBQ0Q7SUFFRCwyQ0FBMkM7SUFDM0M7UUFDQyxNQUFNLEVBQUUsd0NBQXdDO1FBQ2hELFlBQVksRUFBRSxVQUFVO1FBQ3hCLGVBQWUsRUFBRSxFQUFDLG9EQUFvRCxDQUFDO1FBQ3ZFLHFCQUFxQixFQUFFO1lBQ3RCLGNBQWMsRUFBRSxTQUFTO1lBQ3pCLGFBQWEsQ0FBQyxhQUFhO1NBQzNCO0tBQ0Q7SUFFRCw4Q0FBOEM7SUFDOUM7UUFDQyxNQUFNLEVBQUUsd0NBQXdDO1FBQ2hELFlBQVksRUFBRSxVQUFVO1FBQ3hCLGVBQWUsRUFBRSxFQUFDLG9EQUFvRCxDQUFDO1FBQ3ZFLHFCQUFxQixFQUFFO1lBQ3RCLGNBQWMsRUFBRSxTQUFTO1lBQ3pCLGFBQWEsQ0FBQyxhQUFhO1NBQzNCO0tBQ0Q7SUFFRCw4Q0FBOEM7SUFDOUM7UUFDQyxNQUFNLEVBQUUsd0NBQXdDO1FBQ2hELFlBQVksRUFBRSxVQUFVO1FBQ3hCLGVBQWUsRUFBRSxFQUFDLG9EQUFvRCxDQUFDO1FBQ3ZFLHFCQUFxQixFQUFFO1lBQ3RCLGNBQWMsRUFBRSxTQUFTO1lBQ3pCLGFBQWEsQ0FBQyxhQUFhO1NBQzNCO0tBQ0Q7SUFFRCw2REFBNkQ7SUFDN0Q7UUFDQyxNQUFNLEVBQUUsdURBQXVEO1FBQy9ELFlBQVksRUFBRTtZQUNiLEdBQUcsVUFBVTtZQUViLHdCQUF3QjtZQUN4QixRQUFRO1NBQ1I7UUFDRCxlQUFlLEVBQUUsWUFBWTtRQUM3QixxQkFBcUIsRUFBRTtZQUN0QixjQUFjLEVBQUUsU0FBUztZQUN6QixhQUFhLENBQUMsYUFBYTtTQUMzQjtLQUNEO0lBRUQsU0FBUztJQUNUO1FBQ0MsTUFBTSxFQUFFLG9CQUFvQjtRQUM1QixZQUFZLEVBQUUsVUFBVTtRQUN4QixlQUFlLEVBQUUsWUFBWTtRQUM3QixxQkFBcUIsRUFBRTtZQUN0QixjQUFjLEVBQUUsU0FBUztZQUN6QixhQUFhLENBQUMsYUFBYTtTQUMzQjtLQUNEO0lBRUQsVUFBVTtJQUNWO1FBQ0MsTUFBTSxFQUFFLHFCQUFxQjtRQUM3QixZQUFZLEVBQUUsVUFBVTtRQUN4QixlQUFlLEVBQUUsWUFBWTtRQUM3QixrQkFBa0IsRUFBRTtZQUNuQixtQ0FBbUMsQ0FBQyxzRkFBc0Y7U0FDMUg7UUFDRCxxQkFBcUIsRUFBRTtZQUN0QixhQUFhLENBQUMsYUFBYTtTQUMzQjtLQUNEO0lBRUQsMkJBQTJCO0lBQzNCO1FBQ0MsTUFBTSxFQUFFLDZCQUE2QjtRQUNyQyxZQUFZLEVBQUUsVUFBVTtRQUN4QixlQUFlLEVBQUUsWUFBWTtRQUM3QixxQkFBcUIsRUFBRTtZQUN0QixhQUFhLENBQUMsYUFBYTtTQUMzQjtLQUNEO0lBRUQsVUFBVTtJQUNWO1FBQ0MsTUFBTSxFQUFFLGtCQUFrQjtRQUMxQixZQUFZLEVBQUUsVUFBVTtRQUN4QixxQkFBcUIsRUFBRTtZQUN0QixjQUFjLENBQUMsU0FBUztTQUN4QjtLQUNEO0lBRUQscUJBQXFCO0lBQ3JCO1FBQ0MsTUFBTSxFQUFFLDhCQUE4QjtRQUN0QyxZQUFZLEVBQUUsVUFBVTtRQUN4QixxQkFBcUIsRUFBRTtZQUN0QixhQUFhLENBQUMsYUFBYTtTQUMzQjtLQUNEO0lBRUQsa0JBQWtCO0lBQ2xCO1FBQ0MsTUFBTSxFQUFFLDJCQUEyQjtRQUNuQyxZQUFZLEVBQUU7WUFDYixHQUFHLFVBQVU7WUFFYixnRUFBZ0U7WUFDaEUsT0FBTztZQUNQLFNBQVM7U0FDVDtRQUNELGVBQWUsRUFBRTtZQUNoQixTQUFTLENBQUMsNENBQTRDO1NBQ3REO1FBQ0QscUJBQXFCLEVBQUU7WUFDdEIsY0FBYyxDQUFDLFNBQVM7U0FDeEI7S0FDRDtDQUNELENBQUM7QUFFRixNQUFNLGNBQWMsR0FBRyxJQUFBLFdBQUksRUFBQyxTQUFTLEVBQUUsUUFBUSxFQUFFLEtBQUssRUFBRSxlQUFlLENBQUMsQ0FBQztBQVd6RSxJQUFJLFNBQVMsR0FBRyxLQUFLLENBQUM7QUFFdEIsU0FBUyxTQUFTLENBQUMsT0FBbUIsRUFBRSxVQUF5QixFQUFFLElBQVc7SUFDN0UsU0FBUyxDQUFDLFVBQVUsQ0FBQyxDQUFDO0lBRXRCLFNBQVMsU0FBUyxDQUFDLElBQWE7UUFDL0IsSUFBSSxJQUFJLENBQUMsSUFBSSxLQUFLLEVBQUUsQ0FBQyxVQUFVLENBQUMsVUFBVSxFQUFFLENBQUM7WUFDNUMsT0FBTyxFQUFFLENBQUMsWUFBWSxDQUFDLElBQUksRUFBRSxTQUFTLENBQUMsQ0FBQyxDQUFDLGVBQWU7UUFDekQsQ0FBQztRQUVELE1BQU0sT0FBTyxHQUFHLE9BQU8sQ0FBQyxjQUFjLEVBQUUsQ0FBQztRQUN6QyxNQUFNLE1BQU0sR0FBRyxPQUFPLENBQUMsbUJBQW1CLENBQUMsSUFBSSxDQUFDLENBQUM7UUFFakQsSUFBSSxDQUFDLE1BQU0sRUFBRSxDQUFDO1lBQ2IsT0FBTztRQUNSLENBQUM7UUFFRCxJQUFJLGFBQWEsR0FBUSxNQUFNLENBQUM7UUFFaEMsT0FBTyxhQUFhLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDN0IsYUFBYSxHQUFHLGFBQWEsQ0FBQyxNQUFNLENBQUM7UUFDdEMsQ0FBQztRQUVELE1BQU0sWUFBWSxHQUFHLGFBQTBCLENBQUM7UUFDaEQsTUFBTSxJQUFJLEdBQUcsWUFBWSxDQUFDLE9BQU8sRUFBRSxDQUFDO1FBRXBDLElBQUksSUFBSSxDQUFDLFlBQVksRUFBRSxJQUFJLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQyxPQUFPLEtBQUssSUFBSSxDQUFDLEVBQUUsQ0FBQztZQUMxRCxPQUFPLENBQUMsV0FBVztRQUNwQixDQUFDO1FBRUQsSUFBSSxJQUFJLENBQUMsZUFBZSxFQUFFLElBQUksQ0FBQyxVQUFVLENBQUMsRUFBRSxDQUFDLFVBQVUsS0FBSyxJQUFJLENBQUMsRUFBRSxDQUFDO1lBQ25FLE1BQU0sRUFBRSxJQUFJLEVBQUUsU0FBUyxFQUFFLEdBQUcsVUFBVSxDQUFDLDZCQUE2QixDQUFDLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQyxDQUFDO1lBQ3RGLE9BQU8sQ0FBQyxHQUFHLENBQUMsb0RBQW9ELElBQUkscUJBQXFCLElBQUksQ0FBQyxNQUFNLE1BQU0sVUFBVSxDQUFDLFFBQVEsS0FBSyxJQUFJLEdBQUcsQ0FBQyxJQUFJLFNBQVMsR0FBRyxDQUFDLHdIQUF3SCxDQUFDLENBQUM7WUFFclIsU0FBUyxHQUFHLElBQUksQ0FBQztZQUNqQixPQUFPO1FBQ1IsQ0FBQztRQUVELE1BQU0sWUFBWSxHQUFHLE1BQU0sQ0FBQyxZQUFZLENBQUM7UUFDekMsSUFBSSxLQUFLLENBQUMsT0FBTyxDQUFDLFlBQVksQ0FBQyxFQUFFLENBQUM7WUFDakMsZUFBZSxFQUFFLEtBQUssTUFBTSxXQUFXLElBQUksWUFBWSxFQUFFLENBQUM7Z0JBQ3pELElBQUksV0FBVyxFQUFFLENBQUM7b0JBQ2pCLE1BQU0sTUFBTSxHQUFHLFdBQVcsQ0FBQyxNQUFNLENBQUM7b0JBQ2xDLElBQUksTUFBTSxFQUFFLENBQUM7d0JBQ1osTUFBTSxnQkFBZ0IsR0FBRyxNQUFNLENBQUMsYUFBYSxFQUFFLENBQUM7d0JBQ2hELElBQUksZ0JBQWdCLEVBQUUsQ0FBQzs0QkFDdEIsTUFBTSxrQkFBa0IsR0FBRyxnQkFBZ0IsQ0FBQyxRQUFRLENBQUM7NEJBQ3JELElBQUksSUFBSSxDQUFDLGtCQUFrQixFQUFFLENBQUM7Z0NBQzdCLEtBQUssTUFBTSxpQkFBaUIsSUFBSSxJQUFJLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztvQ0FDekQsSUFBSSxrQkFBa0IsQ0FBQyxPQUFPLENBQUMsaUJBQWlCLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQzt3Q0FDeEQsU0FBUyxlQUFlLENBQUM7b0NBQzFCLENBQUM7Z0NBQ0YsQ0FBQzs0QkFDRixDQUFDOzRCQUNELElBQUksSUFBSSxDQUFDLHFCQUFxQixFQUFFLENBQUM7Z0NBQ2hDLEtBQUssTUFBTSxvQkFBb0IsSUFBSSxJQUFJLENBQUMscUJBQXFCLEVBQUUsQ0FBQztvQ0FDL0QsSUFBSSxrQkFBa0IsQ0FBQyxPQUFPLENBQUMsb0JBQW9CLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQzt3Q0FDM0QsTUFBTSxFQUFFLElBQUksRUFBRSxTQUFTLEVBQUUsR0FBRyxVQUFVLENBQUMsNkJBQTZCLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUM7d0NBRXRGLE9BQU8sQ0FBQyxHQUFHLENBQUMsc0RBQXNELElBQUksV0FBVyxvQkFBb0IscUJBQXFCLElBQUksQ0FBQyxNQUFNLE1BQU0sVUFBVSxDQUFDLFFBQVEsS0FBSyxJQUFJLEdBQUcsQ0FBQyxJQUFJLFNBQVMsR0FBRyxDQUFDLHVIQUF1SCxDQUFDLENBQUM7d0NBRXJULFNBQVMsR0FBRyxJQUFJLENBQUM7d0NBQ2pCLE9BQU87b0NBQ1IsQ0FBQztnQ0FDRixDQUFDOzRCQUNGLENBQUM7d0JBQ0YsQ0FBQztvQkFDRixDQUFDO2dCQUNGLENBQUM7WUFDRixDQUFDO1FBQ0YsQ0FBQztJQUNGLENBQUM7QUFDRixDQUFDO0FBRUQsU0FBUyxhQUFhLENBQUMsWUFBb0I7SUFDMUMsTUFBTSxRQUFRLEdBQUcsRUFBRSxDQUFDLGNBQWMsQ0FBQyxZQUFZLEVBQUUsRUFBRSxDQUFDLEdBQUcsQ0FBQyxRQUFRLENBQUMsQ0FBQztJQUVsRSxNQUFNLGdCQUFnQixHQUF1QixFQUFFLFVBQVUsRUFBRSxlQUFVLEVBQUUsYUFBYSxFQUFFLEVBQUUsQ0FBQyxHQUFHLENBQUMsYUFBYSxFQUFFLFFBQVEsRUFBRSxJQUFJLENBQUMsRUFBRSxDQUFDLElBQUEsaUJBQVksRUFBQyxJQUFJLEVBQUUsTUFBTSxDQUFDLEVBQUUseUJBQXlCLEVBQUUsT0FBTyxDQUFDLFFBQVEsS0FBSyxPQUFPLEVBQUUsQ0FBQztJQUNwTixNQUFNLGNBQWMsR0FBRyxFQUFFLENBQUMsMEJBQTBCLENBQUMsUUFBUSxDQUFDLE1BQU0sRUFBRSxnQkFBZ0IsRUFBRSxJQUFBLGNBQU8sRUFBQyxJQUFBLGNBQU8sRUFBQyxZQUFZLENBQUMsQ0FBQyxFQUFFLEVBQUUsTUFBTSxFQUFFLElBQUksRUFBRSxDQUFDLENBQUM7SUFFMUksTUFBTSxZQUFZLEdBQUcsRUFBRSxDQUFDLGtCQUFrQixDQUFDLGNBQWMsQ0FBQyxPQUFPLEVBQUUsSUFBSSxDQUFDLENBQUM7SUFFekUsT0FBTyxFQUFFLENBQUMsYUFBYSxDQUFDLGNBQWMsQ0FBQyxTQUFTLEVBQUUsY0FBYyxDQUFDLE9BQU8sRUFBRSxZQUFZLENBQUMsQ0FBQztBQUN6RixDQUFDO0FBRUQsRUFBRTtBQUNGLG9DQUFvQztBQUNwQyxFQUFFO0FBQ0YsTUFBTSxPQUFPLEdBQUcsYUFBYSxDQUFDLGNBQWMsQ0FBQyxDQUFDO0FBRTlDLEtBQUssTUFBTSxVQUFVLElBQUksT0FBTyxDQUFDLGNBQWMsRUFBRSxFQUFFLENBQUM7SUFDbkQsS0FBSyxNQUFNLElBQUksSUFBSSxLQUFLLEVBQUUsQ0FBQztRQUMxQixJQUFJLElBQUEsaUJBQUssRUFBQyxDQUFDLFVBQVUsQ0FBQyxRQUFRLENBQUMsRUFBRSxJQUFJLENBQUMsTUFBTSxDQUFDLENBQUMsTUFBTSxHQUFHLENBQUMsRUFBRSxDQUFDO1lBQzFELElBQUksQ0FBQyxJQUFJLENBQUMsSUFBSSxFQUFFLENBQUM7Z0JBQ2hCLFNBQVMsQ0FBQyxPQUFPLEVBQUUsVUFBVSxFQUFFLElBQUksQ0FBQyxDQUFDO1lBQ3RDLENBQUM7WUFFRCxNQUFNO1FBQ1AsQ0FBQztJQUNGLENBQUM7QUFDRixDQUFDO0FBRUQsSUFBSSxTQUFTLEVBQUUsQ0FBQztJQUNmLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLENBQUM7QUFDakIsQ0FBQyJ9 \ No newline at end of file +//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibGF5ZXJzQ2hlY2tlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbImxheWVyc0NoZWNrZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IjtBQUFBOzs7Z0dBR2dHOztBQUVoRyxpQ0FBaUM7QUFDakMsMkJBQThDO0FBQzlDLCtCQUE4QztBQUM5Qyx5Q0FBa0M7QUFFbEMsRUFBRTtBQUNGLGdHQUFnRztBQUNoRyxFQUFFO0FBQ0YsK0ZBQStGO0FBQy9GLG1EQUFtRDtBQUNuRCw0RUFBNEU7QUFDNUUsaUVBQWlFO0FBQ2pFLEVBQUU7QUFDRixnR0FBZ0c7QUFDaEcsRUFBRTtBQUNGLGdHQUFnRztBQUNoRyxFQUFFO0FBRUYsbUZBQW1GO0FBQ25GLHdGQUF3RjtBQUN4RixNQUFNLFVBQVUsR0FBRztJQUNsQixTQUFTLEVBQUUsc0JBQXNCO0lBQ2pDLFlBQVk7SUFDWixjQUFjO0lBQ2QsYUFBYTtJQUNiLGVBQWU7SUFDZixTQUFTO0lBQ1QsU0FBUztJQUNULE9BQU87SUFDUCxrQkFBa0I7SUFDbEIsUUFBUTtJQUNSLGFBQWE7SUFDYixhQUFhO0lBQ2IsTUFBTTtJQUNOLGdCQUFnQjtJQUNoQixPQUFPO0lBQ1AsWUFBWTtJQUNaLGFBQWE7SUFDYixhQUFhO0lBQ2IsV0FBVztJQUNYLFlBQVk7SUFDWixZQUFZO0lBQ1osY0FBYztJQUNkLGNBQWM7SUFDZCxtQkFBbUI7SUFDbkIsZ0JBQWdCO0lBQ2hCLGVBQWU7SUFDZixNQUFNO0lBQ04sTUFBTTtJQUNOLGlCQUFpQjtJQUNqQixhQUFhO0lBQ2IsZ0JBQWdCO0lBQ2hCLGFBQWE7SUFDYixLQUFLO0lBQ0wsaUJBQWlCO0lBQ2pCLGVBQWU7SUFDZixPQUFPO0lBQ1AsYUFBYTtJQUNiLGtCQUFrQjtJQUNsQixhQUFhO0lBQ2IsTUFBTTtDQUNOLENBQUM7QUFFRixvRUFBb0U7QUFDcEUsb0VBQW9FO0FBQ3BFLE1BQU0sWUFBWSxHQUFHO0lBQ3BCLGtCQUFrQjtJQUNsQiwyQkFBMkI7SUFDM0Isa0NBQWtDO0lBQ2xDLDRCQUE0QjtJQUM1QiwwQkFBMEI7SUFDMUIsb0JBQW9CO0lBQ3BCLHFCQUFxQjtDQUNyQixDQUFDO0FBRUYsTUFBTSxLQUFLLEdBQVk7SUFFdEIsY0FBYztJQUNkO1FBQ0MsTUFBTSxFQUFFLGtCQUFrQjtRQUMxQixJQUFJLEVBQUUsSUFBSSxDQUFDLHlCQUF5QjtLQUNwQztJQUVELHFDQUFxQztJQUNyQztRQUNDLE1BQU0sRUFBRSwrQkFBK0I7UUFDdkMsWUFBWSxFQUFFO1lBQ2IsR0FBRyxVQUFVO1lBRWIsMkNBQTJDO1lBQzNDLGNBQWM7U0FDZDtRQUNELGVBQWUsRUFBRSxZQUFZO1FBQzdCLHFCQUFxQixFQUFFO1lBQ3RCLGNBQWMsRUFBRSxTQUFTO1lBQ3pCLGFBQWEsQ0FBQyxhQUFhO1NBQzNCO0tBQ0Q7SUFFRCxrQ0FBa0M7SUFDbEM7UUFDQyxNQUFNLEVBQUUsNEJBQTRCO1FBQ3BDLFlBQVksRUFBRTtZQUNiLEdBQUcsVUFBVTtZQUViLDBEQUEwRDtZQUMxRCxxQkFBcUI7WUFDckIsb0JBQW9CO1NBQ3BCO1FBQ0QsZUFBZSxFQUFFLFlBQVk7UUFDN0IscUJBQXFCLEVBQUU7WUFDdEIsY0FBYyxFQUFFLFNBQVM7WUFDekIsYUFBYSxDQUFDLGFBQWE7U0FDM0I7S0FDRDtJQUVELDJDQUEyQztJQUMzQztRQUNDLE1BQU0sRUFBRSx3Q0FBd0M7UUFDaEQsWUFBWSxFQUFFLFVBQVU7UUFDeEIsZUFBZSxFQUFFLEVBQUMsb0RBQW9ELENBQUM7UUFDdkUscUJBQXFCLEVBQUU7WUFDdEIsY0FBYyxFQUFFLFNBQVM7WUFDekIsYUFBYSxDQUFDLGFBQWE7U0FDM0I7S0FDRDtJQUVELDhDQUE4QztJQUM5QztRQUNDLE1BQU0sRUFBRSx3Q0FBd0M7UUFDaEQsWUFBWSxFQUFFLFVBQVU7UUFDeEIsZUFBZSxFQUFFLEVBQUMsb0RBQW9ELENBQUM7UUFDdkUscUJBQXFCLEVBQUU7WUFDdEIsY0FBYyxFQUFFLFNBQVM7WUFDekIsYUFBYSxDQUFDLGFBQWE7U0FDM0I7S0FDRDtJQUVELDhDQUE4QztJQUM5QztRQUNDLE1BQU0sRUFBRSx3Q0FBd0M7UUFDaEQsWUFBWSxFQUFFLFVBQVU7UUFDeEIsZUFBZSxFQUFFLEVBQUMsb0RBQW9ELENBQUM7UUFDdkUscUJBQXFCLEVBQUU7WUFDdEIsY0FBYyxFQUFFLFNBQVM7WUFDekIsYUFBYSxDQUFDLGFBQWE7U0FDM0I7S0FDRDtJQUVELHlEQUF5RDtJQUN6RDtRQUNDLE1BQU0sRUFBRSxtREFBbUQ7UUFDM0QsWUFBWSxFQUFFLFVBQVU7UUFDeEIsZUFBZSxFQUFFLEVBQUMsb0RBQW9ELENBQUM7UUFDdkUscUJBQXFCLEVBQUU7WUFDdEIsY0FBYyxFQUFFLFNBQVM7WUFDekIsYUFBYSxDQUFDLGFBQWE7U0FDM0I7S0FDRDtJQUVELDZEQUE2RDtJQUM3RDtRQUNDLE1BQU0sRUFBRSx1REFBdUQ7UUFDL0QsWUFBWSxFQUFFO1lBQ2IsR0FBRyxVQUFVO1lBRWIsd0JBQXdCO1lBQ3hCLFFBQVE7U0FDUjtRQUNELGVBQWUsRUFBRSxZQUFZO1FBQzdCLHFCQUFxQixFQUFFO1lBQ3RCLGNBQWMsRUFBRSxTQUFTO1lBQ3pCLGFBQWEsQ0FBQyxhQUFhO1NBQzNCO0tBQ0Q7SUFFRCxTQUFTO0lBQ1Q7UUFDQyxNQUFNLEVBQUUsb0JBQW9CO1FBQzVCLFlBQVksRUFBRSxVQUFVO1FBQ3hCLGVBQWUsRUFBRSxZQUFZO1FBQzdCLHFCQUFxQixFQUFFO1lBQ3RCLGNBQWMsRUFBRSxTQUFTO1lBQ3pCLGFBQWEsQ0FBQyxhQUFhO1NBQzNCO0tBQ0Q7SUFFRCxVQUFVO0lBQ1Y7UUFDQyxNQUFNLEVBQUUscUJBQXFCO1FBQzdCLFlBQVksRUFBRSxVQUFVO1FBQ3hCLGVBQWUsRUFBRSxZQUFZO1FBQzdCLGtCQUFrQixFQUFFO1lBQ25CLG1DQUFtQyxDQUFDLHNGQUFzRjtTQUMxSDtRQUNELHFCQUFxQixFQUFFO1lBQ3RCLGFBQWEsQ0FBQyxhQUFhO1NBQzNCO0tBQ0Q7SUFFRCwyQkFBMkI7SUFDM0I7UUFDQyxNQUFNLEVBQUUsNkJBQTZCO1FBQ3JDLFlBQVksRUFBRSxVQUFVO1FBQ3hCLGVBQWUsRUFBRSxZQUFZO1FBQzdCLHFCQUFxQixFQUFFO1lBQ3RCLGFBQWEsQ0FBQyxhQUFhO1NBQzNCO0tBQ0Q7SUFFRCxVQUFVO0lBQ1Y7UUFDQyxNQUFNLEVBQUUsa0JBQWtCO1FBQzFCLFlBQVksRUFBRSxVQUFVO1FBQ3hCLHFCQUFxQixFQUFFO1lBQ3RCLGNBQWMsQ0FBQyxTQUFTO1NBQ3hCO0tBQ0Q7SUFFRCxxQkFBcUI7SUFDckI7UUFDQyxNQUFNLEVBQUUsOEJBQThCO1FBQ3RDLFlBQVksRUFBRSxVQUFVO1FBQ3hCLHFCQUFxQixFQUFFO1lBQ3RCLGFBQWEsQ0FBQyxhQUFhO1NBQzNCO0tBQ0Q7SUFFRCxrQkFBa0I7SUFDbEI7UUFDQyxNQUFNLEVBQUUsMkJBQTJCO1FBQ25DLFlBQVksRUFBRTtZQUNiLEdBQUcsVUFBVTtZQUViLGdFQUFnRTtZQUNoRSxPQUFPO1lBQ1AsU0FBUztTQUNUO1FBQ0QsZUFBZSxFQUFFO1lBQ2hCLFNBQVMsQ0FBQyw0Q0FBNEM7U0FDdEQ7UUFDRCxxQkFBcUIsRUFBRTtZQUN0QixjQUFjLENBQUMsU0FBUztTQUN4QjtLQUNEO0NBQ0QsQ0FBQztBQUVGLE1BQU0sY0FBYyxHQUFHLElBQUEsV0FBSSxFQUFDLFNBQVMsRUFBRSxRQUFRLEVBQUUsS0FBSyxFQUFFLGVBQWUsQ0FBQyxDQUFDO0FBV3pFLElBQUksU0FBUyxHQUFHLEtBQUssQ0FBQztBQUV0QixTQUFTLFNBQVMsQ0FBQyxPQUFtQixFQUFFLFVBQXlCLEVBQUUsSUFBVztJQUM3RSxTQUFTLENBQUMsVUFBVSxDQUFDLENBQUM7SUFFdEIsU0FBUyxTQUFTLENBQUMsSUFBYTtRQUMvQixJQUFJLElBQUksQ0FBQyxJQUFJLEtBQUssRUFBRSxDQUFDLFVBQVUsQ0FBQyxVQUFVLEVBQUUsQ0FBQztZQUM1QyxPQUFPLEVBQUUsQ0FBQyxZQUFZLENBQUMsSUFBSSxFQUFFLFNBQVMsQ0FBQyxDQUFDLENBQUMsZUFBZTtRQUN6RCxDQUFDO1FBRUQsTUFBTSxPQUFPLEdBQUcsT0FBTyxDQUFDLGNBQWMsRUFBRSxDQUFDO1FBQ3pDLE1BQU0sTUFBTSxHQUFHLE9BQU8sQ0FBQyxtQkFBbUIsQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUVqRCxJQUFJLENBQUMsTUFBTSxFQUFFLENBQUM7WUFDYixPQUFPO1FBQ1IsQ0FBQztRQUVELElBQUksYUFBYSxHQUFRLE1BQU0sQ0FBQztRQUVoQyxPQUFPLGFBQWEsQ0FBQyxNQUFNLEVBQUUsQ0FBQztZQUM3QixhQUFhLEdBQUcsYUFBYSxDQUFDLE1BQU0sQ0FBQztRQUN0QyxDQUFDO1FBRUQsTUFBTSxZQUFZLEdBQUcsYUFBMEIsQ0FBQztRQUNoRCxNQUFNLElBQUksR0FBRyxZQUFZLENBQUMsT0FBTyxFQUFFLENBQUM7UUFFcEMsSUFBSSxJQUFJLENBQUMsWUFBWSxFQUFFLElBQUksQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDLE9BQU8sS0FBSyxJQUFJLENBQUMsRUFBRSxDQUFDO1lBQzFELE9BQU8sQ0FBQyxXQUFXO1FBQ3BCLENBQUM7UUFFRCxJQUFJLElBQUksQ0FBQyxlQUFlLEVBQUUsSUFBSSxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUMsVUFBVSxLQUFLLElBQUksQ0FBQyxFQUFFLENBQUM7WUFDbkUsTUFBTSxFQUFFLElBQUksRUFBRSxTQUFTLEVBQUUsR0FBRyxVQUFVLENBQUMsNkJBQTZCLENBQUMsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDLENBQUM7WUFDdEYsT0FBTyxDQUFDLEdBQUcsQ0FBQyxvREFBb0QsSUFBSSxxQkFBcUIsSUFBSSxDQUFDLE1BQU0sTUFBTSxVQUFVLENBQUMsUUFBUSxLQUFLLElBQUksR0FBRyxDQUFDLElBQUksU0FBUyxHQUFHLENBQUMsd0hBQXdILENBQUMsQ0FBQztZQUVyUixTQUFTLEdBQUcsSUFBSSxDQUFDO1lBQ2pCLE9BQU87UUFDUixDQUFDO1FBRUQsTUFBTSxZQUFZLEdBQUcsTUFBTSxDQUFDLFlBQVksQ0FBQztRQUN6QyxJQUFJLEtBQUssQ0FBQyxPQUFPLENBQUMsWUFBWSxDQUFDLEVBQUUsQ0FBQztZQUNqQyxlQUFlLEVBQUUsS0FBSyxNQUFNLFdBQVcsSUFBSSxZQUFZLEVBQUUsQ0FBQztnQkFDekQsSUFBSSxXQUFXLEVBQUUsQ0FBQztvQkFDakIsTUFBTSxNQUFNLEdBQUcsV0FBVyxDQUFDLE1BQU0sQ0FBQztvQkFDbEMsSUFBSSxNQUFNLEVBQUUsQ0FBQzt3QkFDWixNQUFNLGdCQUFnQixHQUFHLE1BQU0sQ0FBQyxhQUFhLEVBQUUsQ0FBQzt3QkFDaEQsSUFBSSxnQkFBZ0IsRUFBRSxDQUFDOzRCQUN0QixNQUFNLGtCQUFrQixHQUFHLGdCQUFnQixDQUFDLFFBQVEsQ0FBQzs0QkFDckQsSUFBSSxJQUFJLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztnQ0FDN0IsS0FBSyxNQUFNLGlCQUFpQixJQUFJLElBQUksQ0FBQyxrQkFBa0IsRUFBRSxDQUFDO29DQUN6RCxJQUFJLGtCQUFrQixDQUFDLE9BQU8sQ0FBQyxpQkFBaUIsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDO3dDQUN4RCxTQUFTLGVBQWUsQ0FBQztvQ0FDMUIsQ0FBQztnQ0FDRixDQUFDOzRCQUNGLENBQUM7NEJBQ0QsSUFBSSxJQUFJLENBQUMscUJBQXFCLEVBQUUsQ0FBQztnQ0FDaEMsS0FBSyxNQUFNLG9CQUFvQixJQUFJLElBQUksQ0FBQyxxQkFBcUIsRUFBRSxDQUFDO29DQUMvRCxJQUFJLGtCQUFrQixDQUFDLE9BQU8sQ0FBQyxvQkFBb0IsQ0FBQyxJQUFJLENBQUMsRUFBRSxDQUFDO3dDQUMzRCxNQUFNLEVBQUUsSUFBSSxFQUFFLFNBQVMsRUFBRSxHQUFHLFVBQVUsQ0FBQyw2QkFBNkIsQ0FBQyxJQUFJLENBQUMsUUFBUSxFQUFFLENBQUMsQ0FBQzt3Q0FFdEYsT0FBTyxDQUFDLEdBQUcsQ0FBQyxzREFBc0QsSUFBSSxXQUFXLG9CQUFvQixxQkFBcUIsSUFBSSxDQUFDLE1BQU0sTUFBTSxVQUFVLENBQUMsUUFBUSxLQUFLLElBQUksR0FBRyxDQUFDLElBQUksU0FBUyxHQUFHLENBQUMsdUhBQXVILENBQUMsQ0FBQzt3Q0FFclQsU0FBUyxHQUFHLElBQUksQ0FBQzt3Q0FDakIsT0FBTztvQ0FDUixDQUFDO2dDQUNGLENBQUM7NEJBQ0YsQ0FBQzt3QkFDRixDQUFDO29CQUNGLENBQUM7Z0JBQ0YsQ0FBQztZQUNGLENBQUM7UUFDRixDQUFDO0lBQ0YsQ0FBQztBQUNGLENBQUM7QUFFRCxTQUFTLGFBQWEsQ0FBQyxZQUFvQjtJQUMxQyxNQUFNLFFBQVEsR0FBRyxFQUFFLENBQUMsY0FBYyxDQUFDLFlBQVksRUFBRSxFQUFFLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBRWxFLE1BQU0sZ0JBQWdCLEdBQXVCLEVBQUUsVUFBVSxFQUFFLGVBQVUsRUFBRSxhQUFhLEVBQUUsRUFBRSxDQUFDLEdBQUcsQ0FBQyxhQUFhLEVBQUUsUUFBUSxFQUFFLElBQUksQ0FBQyxFQUFFLENBQUMsSUFBQSxpQkFBWSxFQUFDLElBQUksRUFBRSxNQUFNLENBQUMsRUFBRSx5QkFBeUIsRUFBRSxPQUFPLENBQUMsUUFBUSxLQUFLLE9BQU8sRUFBRSxDQUFDO0lBQ3BOLE1BQU0sY0FBYyxHQUFHLEVBQUUsQ0FBQywwQkFBMEIsQ0FBQyxRQUFRLENBQUMsTUFBTSxFQUFFLGdCQUFnQixFQUFFLElBQUEsY0FBTyxFQUFDLElBQUEsY0FBTyxFQUFDLFlBQVksQ0FBQyxDQUFDLEVBQUUsRUFBRSxNQUFNLEVBQUUsSUFBSSxFQUFFLENBQUMsQ0FBQztJQUUxSSxNQUFNLFlBQVksR0FBRyxFQUFFLENBQUMsa0JBQWtCLENBQUMsY0FBYyxDQUFDLE9BQU8sRUFBRSxJQUFJLENBQUMsQ0FBQztJQUV6RSxPQUFPLEVBQUUsQ0FBQyxhQUFhLENBQUMsY0FBYyxDQUFDLFNBQVMsRUFBRSxjQUFjLENBQUMsT0FBTyxFQUFFLFlBQVksQ0FBQyxDQUFDO0FBQ3pGLENBQUM7QUFFRCxFQUFFO0FBQ0Ysb0NBQW9DO0FBQ3BDLEVBQUU7QUFDRixNQUFNLE9BQU8sR0FBRyxhQUFhLENBQUMsY0FBYyxDQUFDLENBQUM7QUFFOUMsS0FBSyxNQUFNLFVBQVUsSUFBSSxPQUFPLENBQUMsY0FBYyxFQUFFLEVBQUUsQ0FBQztJQUNuRCxLQUFLLE1BQU0sSUFBSSxJQUFJLEtBQUssRUFBRSxDQUFDO1FBQzFCLElBQUksSUFBQSxpQkFBSyxFQUFDLENBQUMsVUFBVSxDQUFDLFFBQVEsQ0FBQyxFQUFFLElBQUksQ0FBQyxNQUFNLENBQUMsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxFQUFFLENBQUM7WUFDMUQsSUFBSSxDQUFDLElBQUksQ0FBQyxJQUFJLEVBQUUsQ0FBQztnQkFDaEIsU0FBUyxDQUFDLE9BQU8sRUFBRSxVQUFVLEVBQUUsSUFBSSxDQUFDLENBQUM7WUFDdEMsQ0FBQztZQUVELE1BQU07UUFDUCxDQUFDO0lBQ0YsQ0FBQztBQUNGLENBQUM7QUFFRCxJQUFJLFNBQVMsRUFBRSxDQUFDO0lBQ2YsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsQ0FBQztBQUNqQixDQUFDIn0= \ No newline at end of file diff --git a/build/lib/layersChecker.ts b/build/lib/layersChecker.ts index e7fdfd4214e..864d61f1452 100644 --- a/build/lib/layersChecker.ts +++ b/build/lib/layersChecker.ts @@ -102,6 +102,23 @@ const RULES: IRule[] = [ ] }, + // Common: vs/base/common/async.ts + { + target: '**/vs/base/common/async.ts', + allowedTypes: [ + ...CORE_TYPES, + + // Safe access to requestIdleCallback & cancelIdleCallback + 'requestIdleCallback', + 'cancelIdleCallback' + ], + disallowedTypes: NATIVE_TYPES, + disallowedDefinitions: [ + 'lib.dom.d.ts', // no DOM + '@types/node' // no node.js + ] + }, + // Common: vs/platform/environment/common/* { target: '**/vs/platform/environment/common/*.ts', @@ -135,6 +152,17 @@ const RULES: IRule[] = [ ] }, + // Common: vs/platform/native/common/nativeHostService.ts + { + target: '**/vs/platform/native/common/nativeHostService.ts', + allowedTypes: CORE_TYPES, + disallowedTypes: [/* Ignore native types that are defined from here */], + disallowedDefinitions: [ + 'lib.dom.d.ts', // no DOM + '@types/node' // no node.js + ] + }, + // Common: vs/workbench/api/common/extHostExtensionService.ts { target: '**/vs/workbench/api/common/extHostExtensionService.ts', diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index 0baec502572..f81642f392c 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -109,6 +109,7 @@ "--vscode-diffEditor-unchangedCodeBackground", "--vscode-diffEditor-unchangedRegionBackground", "--vscode-diffEditor-unchangedRegionForeground", + "--vscode-diffEditor-unchangedRegionShadow", "--vscode-diffEditorGutter-insertedLineBackground", "--vscode-diffEditorGutter-removedLineBackground", "--vscode-diffEditorOverview-insertedForeground", @@ -179,6 +180,7 @@ "--vscode-editorCodeLens-foreground", "--vscode-editorCommentsWidget-rangeActiveBackground", "--vscode-editorCommentsWidget-rangeBackground", + "--vscode-editorCommentsWidget-replyButtonBackground", "--vscode-editorCommentsWidget-resolvedBorder", "--vscode-editorCommentsWidget-unresolvedBorder", "--vscode-editorCursor-background", @@ -417,6 +419,7 @@ "--vscode-minimapSlider-activeBackground", "--vscode-minimapSlider-background", "--vscode-minimapSlider-hoverBackground", + "--vscode-multiDiffEditor-headerBackground", "--vscode-notebook-cellBorderColor", "--vscode-notebook-cellEditorBackground", "--vscode-notebook-cellHoverBackground", @@ -665,6 +668,8 @@ "--vscode-terminalCursor-foreground", "--vscode-terminalOverviewRuler-cursorForeground", "--vscode-terminalOverviewRuler-findMatchForeground", + "--vscode-terminalStickyScroll-background", + "--vscode-terminalStickyScrollHover-background", "--vscode-testing-iconErrored", "--vscode-testing-iconFailed", "--vscode-testing-iconPassed", @@ -784,4 +789,4 @@ "--z-index-notebook-sticky-scroll", "--zoom-factor" ] -} \ No newline at end of file +} diff --git a/cli/src/tunnels/control_server.rs b/cli/src/tunnels/control_server.rs index a33b59db32a..5f564494b98 100644 --- a/cli/src/tunnels/control_server.rs +++ b/cli/src/tunnels/control_server.rs @@ -32,6 +32,7 @@ use opentelemetry::KeyValue; use std::collections::HashMap; use std::path::PathBuf; use std::process::Stdio; +use tokio::net::TcpStream; use tokio::pin; use tokio::process::{ChildStderr, ChildStdin}; use tokio_util::codec::Decoder; @@ -55,9 +56,9 @@ use super::protocol::{ ChallengeIssueResponse, ChallengeVerifyParams, ClientRequestMethod, EmptyObject, ForwardParams, ForwardResult, FsReadDirEntry, FsReadDirResponse, FsRenameRequest, FsSinglePathRequest, FsStatResponse, GetEnvResponse, GetHostnameResponse, HttpBodyParams, HttpHeadersParams, - ServeParams, ServerLog, ServerMessageParams, SpawnParams, SpawnResult, SysKillRequest, - SysKillResponse, ToClientRequest, UnforwardParams, UpdateParams, UpdateResult, VersionResponse, - METHOD_CHALLENGE_VERIFY, + NetConnectRequest, ServeParams, ServerLog, ServerMessageParams, SpawnParams, SpawnResult, + SysKillRequest, SysKillResponse, ToClientRequest, UnforwardParams, UpdateParams, UpdateResult, + VersionResponse, METHOD_CHALLENGE_VERIFY, }; use super::server_bridge::ServerBridge; use super::server_multiplexer::ServerMultiplexer; @@ -341,6 +342,14 @@ fn make_socket_rpc( handle_fs_connect(streams.remove(0), p.path).await }, ); + rpc.register_duplex( + "net_connect", + 1, + move |mut streams, n: NetConnectRequest, c| async move { + ensure_auth(&c.auth_state)?; + handle_net_connect(streams.remove(0), n).await + }, + ); rpc.register_async("fs_rm", move |p: FsSinglePathRequest, c| async move { ensure_auth(&c.auth_state)?; handle_fs_remove(p.path).await @@ -896,6 +905,20 @@ async fn handle_fs_write(mut input: DuplexStream, path: String) -> Result Result { + let mut s = TcpStream::connect((req.host, req.port)) + .await + .map_err(|e| wrap(e, "could not connect to address"))?; + + tokio::io::copy_bidirectional(&mut stream, &mut s) + .await + .map_err(|e| wrap(e, "error copying stream data"))?; + + Ok(EmptyObject {}) +} async fn handle_fs_connect( mut stream: DuplexStream, path: String, diff --git a/cli/src/tunnels/protocol.rs b/cli/src/tunnels/protocol.rs index 547ffba82a8..6434faadc39 100644 --- a/cli/src/tunnels/protocol.rs +++ b/cli/src/tunnels/protocol.rs @@ -209,6 +209,13 @@ pub struct FsRenameRequest { pub to_path: String, } +/// Method: `net_connect`. Connects to a port. +#[derive(Deserialize)] +pub struct NetConnectRequest { + pub port: u16, + pub host: String, +} + #[derive(Deserialize, Debug)] pub struct CallServerHttpParams { pub path: String, diff --git a/extensions/configuration-editing/schemas/devContainer.vscode.schema.json b/extensions/configuration-editing/schemas/devContainer.vscode.schema.json index 13b4e53f894..d64641c1951 100644 --- a/extensions/configuration-editing/schemas/devContainer.vscode.schema.json +++ b/extensions/configuration-editing/schemas/devContainer.vscode.schema.json @@ -10,11 +10,11 @@ "properties": { "extensions": { "type": "array", - "description": "An array of extensions that should be installed into the container.", + "description": "An array of extensions that should be installed into the container. A minus '-' in front of the extension id removes it from the list of extensions to be installed.", "items": { "type": "string", - "pattern": "^([a-z0-9A-Z][a-z0-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-Z-]*)((@(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)|@prerelease)?$", - "errorMessage": "Expected format: '${publisher}.${name}' or '${publisher}.${name}@${version}'. Example: 'ms-dotnettools.csharp'." + "pattern": "^-?([a-z0-9A-Z][a-z0-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-Z-]*)((@(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)|@prerelease)?$", + "errorMessage": "Expected format: '${publisher}.${name}', '-${publisher}.${name}' or '${publisher}.${name}@${version}'. Example: 'ms-dotnettools.csharp'." } }, "settings": { @@ -31,11 +31,11 @@ }, "extensions": { "type": "array", - "description": "An array of extensions that should be installed into the container.", + "description": "An array of extensions that should be installed into the container. A minus '-' in front of the extension id removes it from the list of extensions to be installed.", "items": { "type": "string", - "pattern": "^([a-z0-9A-Z][a-z0-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-Z-]*)((@(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)|@prerelease)?$", - "errorMessage": "Expected format: '${publisher}.${name}' or '${publisher}.${name}@${version}'. Example: 'ms-dotnettools.csharp'." + "pattern": "^-?([a-z0-9A-Z][a-z0-9A-Z-]*)\\.([a-z0-9A-Z][a-z0-9A-Z-]*)((@(0|[1-9]\\d*)\\.(0|[1-9]\\d*)\\.(0|[1-9]\\d*)(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?)|@prerelease)?$", + "errorMessage": "Expected format: '${publisher}.${name}', '-${publisher}.${name}' or '${publisher}.${name}@${version}'. Example: 'ms-dotnettools.csharp'." }, "deprecated": true, "deprecationMessage": "Use 'customizations/vscode/extensions' instead" diff --git a/extensions/configuration-editing/src/configurationEditingMain.ts b/extensions/configuration-editing/src/configurationEditingMain.ts index acbd032dee2..02f52f6e721 100644 --- a/extensions/configuration-editing/src/configurationEditingMain.ts +++ b/extensions/configuration-editing/src/configurationEditingMain.ts @@ -54,6 +54,7 @@ function registerVariableCompletions(pattern: string): vscode.Disposable { return [ { label: 'workspaceFolder', detail: vscode.l10n.t("The path of the folder opened in VS Code") }, { label: 'workspaceFolderBasename', detail: vscode.l10n.t("The name of the folder opened in VS Code without any slashes (/)") }, + { label: 'fileWorkspaceFolderBasename', detail: vscode.l10n.t("The current opened file workspace folder name without any slashes (/)") }, { label: 'relativeFile', detail: vscode.l10n.t("The current opened file relative to ${workspaceFolder}") }, { label: 'relativeFileDirname', detail: vscode.l10n.t("The current opened file's dirname relative to ${workspaceFolder}") }, { label: 'file', detail: vscode.l10n.t("The current opened file") }, diff --git a/extensions/git/package.json b/extensions/git/package.json index 8b1df39dd43..7a20e7ffef1 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -694,6 +694,12 @@ "title": "%command.timelineCompareWithSelected%", "category": "Git" }, + { + "command": "git.timeline.openCommit", + "title": "%command.timelineOpenCommit%", + "icon": "$(wand)", + "category": "Git" + }, { "command": "git.rebaseAbort", "title": "%command.rebaseAbort%", @@ -753,6 +759,18 @@ "command": "git.openRepositoriesInParentFolders", "title": "%command.openRepositoriesInParentFolders%", "category": "Git" + }, + { + "command": "git.viewChanges", + "title": "View Changes", + "icon": "$(search)", + "category": "Git" + }, + { + "command": "git.viewStagedChanges", + "title": "View Staged Changes", + "icon": "$(search)", + "category": "Git" } ], "continueEditSession": [ @@ -1197,6 +1215,10 @@ "command": "git.timeline.compareWithSelected", "when": "false" }, + { + "command": "git.timeline.openCommit", + "when": "false" + }, { "command": "git.closeAllDiffEditors", "when": "config.git.enabled && !git.missing && gitOpenRepositoryCount != 0" @@ -1224,6 +1246,14 @@ { "command": "git.openRepositoriesInParentFolders", "when": "config.git.enabled && !git.missing && git.parentRepositoryCount != 0" + }, + { + "command": "git.viewChanges", + "when": "false" + }, + { + "command": "git.viewStagedChanges", + "when": "false" } ], "scm/title": [ @@ -1390,6 +1420,16 @@ "command": "git.stageAllUntracked", "when": "scmProvider == git && scmResourceGroup == untracked", "group": "inline@2" + }, + { + "command": "git.viewStagedChanges", + "when": "scmProvider == git && scmResourceGroup == index", + "group": "inline@1" + }, + { + "command": "git.viewChanges", + "when": "scmProvider == git && scmResourceGroup == workingTree", + "group": "inline@1" } ], "scm/resourceFolder/context": [ @@ -1767,11 +1807,21 @@ } ], "timeline/item/context": [ + { + "command": "git.timeline.openCommit", + "group": "inline", + "when": "config.git.enabled && !git.missing && timelineItem =~ /git:file:commit\\b/ && !listMultiSelection" + }, { "command": "git.timeline.openDiff", - "group": "1_actions", + "group": "1_actions@1", "when": "config.git.enabled && !git.missing && timelineItem =~ /git:file\\b/ && !listMultiSelection" }, + { + "command": "git.timeline.openCommit", + "group": "1_actions@2", + "when": "config.git.enabled && !git.missing && timelineItem =~ /git:file:commit\\b/ && !listMultiSelection" + }, { "command": "git.timeline.compareWithSelected", "group": "3_compare@1", diff --git a/extensions/git/package.nls.json b/extensions/git/package.nls.json index 20a807be2cb..5f454ae4008 100644 --- a/extensions/git/package.nls.json +++ b/extensions/git/package.nls.json @@ -104,6 +104,7 @@ "command.stashDrop": "Drop Stash...", "command.stashDropAll": "Drop All Stashes...", "command.timelineOpenDiff": "Open Changes", + "command.timelineOpenCommit": "Open Commit", "command.timelineCopyCommitId": "Copy Commit ID", "command.timelineCopyCommitMessage": "Copy Commit Message", "command.timelineSelectForCompare": "Select for Compare", diff --git a/extensions/git/src/commands.ts b/extensions/git/src/commands.ts index f6b625a71de..89ca2519c5a 100644 --- a/extensions/git/src/commands.ts +++ b/extensions/git/src/commands.ts @@ -3426,6 +3426,54 @@ export class CommandCenter { }; } + @command('git.timeline.openCommit', { repository: false }) + async timelineOpenCommit(item: TimelineItem, uri: Uri | undefined, _source: string) { + console.log('timelineOpenCommit', item); + if (!GitTimelineItem.is(item)) { + return; + } + + const cmd = await this._resolveTimelineOpenCommitCommand( + item, uri, + { + preserveFocus: true, + preview: true, + viewColumn: ViewColumn.Active + }, + ); + if (cmd === undefined) { + return undefined; + } + + return commands.executeCommand(cmd.command, ...(cmd.arguments ?? [])); + } + + private async _resolveTimelineOpenCommitCommand(item: TimelineItem, uri: Uri | undefined, options?: TextDocumentShowOptions): Promise { + if (uri === undefined || uri === null || !GitTimelineItem.is(item)) { + return undefined; + } + + const repository = await this.model.getRepository(uri.fsPath); + if (!repository) { + return undefined; + } + + const commit = await repository.getCommit(item.ref); + const commitFiles = await repository.getCommitFiles(item.ref); + + const args: [Uri, Uri | undefined, Uri | undefined][] = []; + for (const commitFile of commitFiles) { + const commitFileUri = Uri.file(path.join(repository.root, commitFile)); + args.push([commitFileUri, toGitUri(commitFileUri, item.previousRef), toGitUri(commitFileUri, item.ref)]); + } + + return { + command: 'vscode.changes', + title: l10n.t('Open Commit'), + arguments: [`${item.shortRef} - ${commit.message}`, args, options] + }; + } + @command('git.timeline.copyCommitId', { repository: false }) async timelineCopyCommitId(item: TimelineItem, _uri: Uri | undefined, _source: string) { if (!GitTimelineItem.is(item)) { @@ -3603,6 +3651,26 @@ export class CommandCenter { repository.generateCommitMessageCancel(); } + @command('git.viewChanges', { repository: true }) + viewChanges(repository: Repository): void { + this._viewChanges('Changes', repository.workingTreeGroup.resourceStates); + } + + @command('git.viewStagedChanges', { repository: true }) + viewStagedChanges(repository: Repository): void { + this._viewChanges('Staged Changes', repository.indexGroup.resourceStates); + } + + private _viewChanges(title: string, resources: Resource[]): void { + const args: [Uri, Uri | undefined, Uri | undefined][] = []; + + for (const resource of resources) { + args.push([resource.resourceUri, resource.leftUri, resource.rightUri]); + } + + commands.executeCommand('vscode.changes', title, args); + } + private createCommand(id: string, key: string, method: Function, options: ScmCommandOptions): (...args: any[]) => any { const result = (...args: any[]) => { let result: Promise; diff --git a/extensions/git/src/git.ts b/extensions/git/src/git.ts index 421cc14b752..5dde9b26da9 100644 --- a/extensions/git/src/git.ts +++ b/extensions/git/src/git.ts @@ -1316,15 +1316,23 @@ export class Repository { return result.stdout.trim(); } - async diffBetweenShortStat(ref1: string, ref2: string): Promise { + async diffBetweenShortStat(ref1: string, ref2: string): Promise<{ files: number; insertions: number; deletions: number }> { const args = ['diff', '--shortstat', `${ref1}...${ref2}`]; const result = await this.exec(args); if (result.exitCode) { - return ''; + return { files: 0, insertions: 0, deletions: 0 }; } - return result.stdout.trim(); + const regex = /(\d+) files? changed(?:, (\d+) insertions\(\+\))?(?:, (\d+) deletions\(-\))?/; + const matches = result.stdout.trim().match(regex); + + if (!matches) { + return { files: 0, insertions: 0, deletions: 0 }; + } + + const [, files, insertions = undefined, deletions = undefined] = matches; + return { files: parseInt(files), insertions: parseInt(insertions ?? '0'), deletions: parseInt(deletions ?? '0') }; } private async diffFiles(cached: boolean, ref?: string): Promise { @@ -2552,6 +2560,11 @@ export class Repository { return commits[0]; } + async getCommitFiles(ref: string): Promise { + const result = await this.exec(['diff-tree', '--no-commit-id', '--name-only', '-r', ref]); + return result.stdout.split('\n').filter(l => !!l); + } + async getCommitCount(range: string): Promise<{ ahead: number; behind: number }> { const result = await this.exec(['rev-list', '--count', '--left-right', range]); const [ahead, behind] = result.stdout.trim().split('\t'); diff --git a/extensions/git/src/historyProvider.ts b/extensions/git/src/historyProvider.ts index d3a64bb4bb5..0256ffdcc1c 100644 --- a/extensions/git/src/historyProvider.ts +++ b/extensions/git/src/historyProvider.ts @@ -10,6 +10,7 @@ import { IDisposable } from './util'; import { toGitUri } from './uri'; import { SyncActionButton } from './actionButton'; import { RefType, Status } from './api/git'; +import { emojify, ensureEmojis } from './emoji'; export class GitHistoryProvider implements SourceControlHistoryProvider, FileDecorationProvider, IDisposable { @@ -83,6 +84,8 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec this.getSummaryHistoryItem(optionsRef, historyItemGroupIdRef) ]); + await ensureEmojis(); + const historyItems = commits.length === 0 ? [] : [summary]; historyItems.push(...commits.map(commit => { const newLineIndex = commit.message.indexOf('\n'); @@ -91,9 +94,9 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec return { id: commit.hash, parentIds: commit.parents, - label: subject, + label: emojify(subject), description: commit.authorName, - icon: new ThemeIcon('account'), + icon: new ThemeIcon('git-commit'), timestamp: commit.authorDate?.getTime() }; })); @@ -192,8 +195,8 @@ export class GitHistoryProvider implements SourceControlHistoryProvider, FileDec } private async getSummaryHistoryItem(ref1: string, ref2: string): Promise { - const diffShortStat = await this.repository.diffBetweenShortStat(ref1, ref2); - return { id: `${ref1}..${ref2}`, parentIds: [], icon: new ThemeIcon('files'), label: l10n.t('All Changes'), description: diffShortStat }; + const statistics = await this.repository.diffBetweenShortStat(ref1, ref2); + return { id: `${ref1}..${ref2}`, parentIds: [], icon: new ThemeIcon('files'), label: l10n.t('All Changes'), statistics }; } dispose(): void { diff --git a/extensions/git/src/operation.ts b/extensions/git/src/operation.ts index 71d6578b2f1..44ee21f2325 100644 --- a/extensions/git/src/operation.ts +++ b/extensions/git/src/operation.ts @@ -137,7 +137,7 @@ export const Operation = { CheckoutTracking: (refLabel: string) => ({ kind: OperationKind.CheckoutTracking, blocking: true, readOnly: false, remote: false, retry: false, showProgress: true, refLabel } as CheckoutTrackingOperation), Clean: (showProgress: boolean) => ({ kind: OperationKind.Clean, blocking: false, readOnly: false, remote: false, retry: false, showProgress } as CleanOperation), Commit: { kind: OperationKind.Commit, blocking: true, readOnly: false, remote: false, retry: false, showProgress: true } as CommitOperation, - Config: { kind: OperationKind.Config, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as ConfigOperation, + Config: (readOnly: boolean) => ({ kind: OperationKind.Config, blocking: false, readOnly, remote: false, retry: false, showProgress: true } as ConfigOperation), DeleteBranch: { kind: OperationKind.DeleteBranch, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteBranchOperation, DeleteRef: { kind: OperationKind.DeleteRef, blocking: false, readOnly: false, remote: false, retry: false, showProgress: true } as DeleteRefOperation, DeleteRemoteTag: { kind: OperationKind.DeleteRemoteTag, blocking: false, readOnly: false, remote: true, retry: false, showProgress: true } as DeleteRemoteTagOperation, diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 21859a62434..9e32d37f147 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1113,19 +1113,19 @@ export class Repository implements Disposable { } getConfigs(): Promise<{ key: string; value: string }[]> { - return this.run(Operation.Config, () => this.repository.getConfigs('local')); + return this.run(Operation.Config(true), () => this.repository.getConfigs('local')); } getConfig(key: string): Promise { - return this.run(Operation.Config, () => this.repository.config('local', key)); + return this.run(Operation.Config(true), () => this.repository.config('local', key)); } getGlobalConfig(key: string): Promise { - return this.run(Operation.Config, () => this.repository.config('global', key)); + return this.run(Operation.Config(true), () => this.repository.config('global', key)); } setConfig(key: string, value: string): Promise { - return this.run(Operation.Config, () => this.repository.config('local', key, value)); + return this.run(Operation.Config(false), () => this.repository.config('local', key, value)); } log(options?: LogOptions): Promise { @@ -1185,7 +1185,7 @@ export class Repository implements Disposable { return this.run(Operation.Diff, () => this.repository.diffBetween(ref1, ref2, path)); } - diffBetweenShortStat(ref1: string, ref2: string): Promise { + diffBetweenShortStat(ref1: string, ref2: string): Promise<{ files: number; insertions: number; deletions: number }> { return this.run(Operation.Diff, () => this.repository.diffBetweenShortStat(ref1, ref2)); } @@ -1658,6 +1658,10 @@ export class Repository implements Disposable { return await this.repository.getCommit(ref); } + async getCommitFiles(ref: string): Promise { + return await this.repository.getCommitFiles(ref); + } + async getCommitCount(range: string): Promise<{ ahead: number; behind: number }> { return await this.run(Operation.RevList, () => this.repository.getCommitCount(range)); } diff --git a/extensions/ipynb/src/deserializers.ts b/extensions/ipynb/src/deserializers.ts index bea8025be31..21dd078b89b 100644 --- a/extensions/ipynb/src/deserializers.ts +++ b/extensions/ipynb/src/deserializers.ts @@ -139,6 +139,8 @@ function convertJupyterOutputToBuffer(mime: string, value: unknown): NotebookCel } } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) { return NotebookCellOutputItem.text(JSON.stringify(value), mime); + } else if (mime === 'application/json') { + return NotebookCellOutputItem.json(value, mime); } else { // For everything else, treat the data as strings (or multi-line strings). value = Array.isArray(value) ? concatMultilineString(value) : value; diff --git a/extensions/less/build/update-grammar.js b/extensions/less/build/update-grammar.js new file mode 100644 index 00000000000..d0b2ec9025f --- /dev/null +++ b/extensions/less/build/update-grammar.js @@ -0,0 +1,19 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +var updateGrammar = require('vscode-grammar-updater'); + +function adaptLess(grammar) { + grammar.name = 'Less'; + grammar.scopeName = 'source.css.less'; +} + +async function updateGrammars() { + await updateGrammar.update('radium-v/Better-Less', 'Syntaxes/Better%20Less.tmLanguage', './syntaxes/less.tmLanguage.json', adaptLess, 'master'); +} + +updateGrammars(); + diff --git a/extensions/less/cgmanifest.json b/extensions/less/cgmanifest.json index 1951ef04bee..663e1cc4daf 100644 --- a/extensions/less/cgmanifest.json +++ b/extensions/less/cgmanifest.json @@ -5,12 +5,12 @@ "type": "git", "git": { "name": "language-less", - "repositoryUrl": "https://github.com/atom/language-less", - "commitHash": "87d4d59e8de6796b506b81a16e1dc1fafc99d30f" + "repositoryUrl": "https://github.com/radium-v/Better-Less", + "commitHash": "05de79f600227201e35f07a49f07acce80e49dbf" } }, "license": "MIT", - "version": "0.34.2" + "version": "0.6.1" } ], "version": 1 diff --git a/extensions/less/package.json b/extensions/less/package.json index 5974be71933..96301ba0fd1 100644 --- a/extensions/less/package.json +++ b/extensions/less/package.json @@ -9,7 +9,7 @@ "vscode": "*" }, "scripts": { - "update-grammar": "node ../node_modules/vscode-grammar-updater/bin atom/language-less grammars/less.cson ./syntaxes/less.tmLanguage.json" + "update-grammar": "node ./build/update-grammar.js" }, "contributes": { "languages": [ diff --git a/extensions/less/syntaxes/less.tmLanguage.json b/extensions/less/syntaxes/less.tmLanguage.json index 3f0b88a29d7..3d2c6bdaeb0 100644 --- a/extensions/less/syntaxes/less.tmLanguage.json +++ b/extensions/less/syntaxes/less.tmLanguage.json @@ -1,183 +1,155 @@ { "information_for_contributors": [ - "This file has been converted from https://github.com/atom/language-less/blob/master/grammars/less.cson", + "This file has been converted from https://github.com/radium-v/Better-Less/blob/master/Syntaxes/Better%20Less.tmLanguage", "If you want to provide a fix or improvement, please create a pull request against the original repository.", "Once accepted there, we are happy to receive an update request." ], - "version": "https://github.com/atom/language-less/commit/87d4d59e8de6796b506b81a16e1dc1fafc99d30f", + "version": "https://github.com/radium-v/Better-Less/commit/05de79f600227201e35f07a49f07acce80e49dbf", "name": "Less", "scopeName": "source.css.less", "patterns": [ { - "include": "#strings" + "include": "#comment-block" }, { + "include": "#less-namespace-accessors" + }, + { + "include": "#less-extend" + }, + { + "include": "#at-rules" + }, + { + "include": "#less-variable-assignment" + }, + { + "include": "#property-list" + }, + { + "include": "#selector" + } + ], + "repository": { + "angle-type": { "captures": { "1": { - "name": "entity.other.attribute-name.class.mixin.css" + "name": "keyword.other.unit.less" } }, - "match": "(\\.[_a-zA-Z][a-zA-Z0-9_-]*(?=\\())" + "match": "(?i:[-+]?(?:(?:\\d*\\.\\d+(?:[eE](?:[-+]?\\d+))*)|(?:[-+]?\\d+))(deg|grad|rad|turn))\\b", + "name": "constant.numeric.less" }, - { + "at-charset": { + "begin": "\\s*((@)charset\\b)\\s*", "captures": { "1": { - "name": "entity.other.attribute-name.class.css" + "name": "keyword.control.at-rule.charset.less" }, "2": { - "name": "punctuation.definition.entity.css" - }, - "4": { - "name": "variable.other.interpolation.less" - } - }, - "match": "((\\.)([_a-zA-Z]|(@{[a-zA-Z0-9_-]+}))[a-zA-Z0-9_-]*)" - }, - { - "captures": { - "0": { - "name": "entity.other.attribute-name.parent-selector.css" - }, - "1": { - "name": "punctuation.definition.entity.css" - } - }, - "match": "(&)[a-zA-Z0-9_-]*" - }, - { - "begin": "(format|local|url|attr|counter|counters)\\s*(\\()", - "beginCaptures": { - "1": { - "name": "support.function.misc.css" - }, - "2": { - "name": "punctuation.section.function.css" - } - }, - "end": "\\)", - "endCaptures": { - "0": { - "name": "punctuation.section.function.css" + "name": "punctuation.definition.keyword.less" } }, + "end": "\\s*((?=;|$))", + "name": "meta.at-rule.charset.less", "patterns": [ { - "begin": "'", - "beginCaptures": { - "0": { - "name": "punctuation.definition.string.begin.css" - } - }, - "end": "'", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.css" - } - }, - "name": "string.quoted.single.css", - "patterns": [ - { - "match": "\\\\.", - "name": "constant.character.escape.css" - } - ] - }, - { - "begin": "\"", - "beginCaptures": { - "0": { - "name": "punctuation.definition.string.begin.css" - } - }, - "end": "\"", - "endCaptures": { - "0": { - "name": "punctuation.definition.string.end.css" - } - }, - "name": "string.quoted.double.css", - "patterns": [ - { - "match": "\\\\(\\d{1,6}|.)", - "name": "constant.character.escape.css" - } - ] - }, - { - "match": "[^'\") \\t]+", - "name": "variable.parameter.misc.css" + "include": "#literal-string" } ] }, - { - "match": "(#)([0-9a-fA-F]{3}|[0-9a-fA-F]{6})\\b(?!.*?(?(['\"])(?:[^\\\\]|\\\\.)*?(\\6)))))?\\s*(\\])", - "name": "meta.attribute-selector.css" + "end": "\\s*(\\})", + "endCaptures": { + "1": { + "name": "punctuation.definition.block.begin.less" + } + }, + "name": "meta.at-rule.counter-style.less", + "patterns": [ + { + "include": "#comment-block" + }, + { + "include": "#rule-list" + } + ] }, - { - "begin": "((@)import\\b)", + "at-custom-media": { + "begin": "(?=\\s*@custom-media\\b)", + "end": "\\s*(?=;)", + "name": "meta.at-rule.custom-media.less", + "patterns": [ + { + "captures": { + "0": { + "name": "punctuation.section.property-list.less" + } + }, + "match": "\\s*;" + }, + { + "captures": { + "1": { + "name": "keyword.control.at-rule.custom-media.less" + }, + "2": { + "name": "punctuation.definition.keyword.less" + }, + "3": { + "name": "support.constant.custom-media.less" + } + }, + "match": "\\s*((@)custom-media)(?=.*?)" + }, + { + "include": "#media-query-list" + } + ] + }, + "at-font-face": { + "begin": "\\s*((@)font-face)\\s*(?=\\{|$)", + "captures": { + "1": { + "name": "keyword.control.at-rule.font-face.less" + }, + "2": { + "name": "punctuation.definition.keyword.less" + } + }, + "end": "\\s*(\\})", + "endCaptures": { + "1": { + "name": "punctuation.definition.block.end.less" + } + }, + "name": "meta.at-rule.font-face.less", + "patterns": [ + { + "include": "#comment-block" + }, + { + "include": "#rule-list" + } + ] + }, + "at-import": { + "begin": "\\s*((@)import\\b)\\s*", "beginCaptures": { "1": { "name": "keyword.control.at-rule.import.less" @@ -186,357 +158,4860 @@ "name": "punctuation.definition.keyword.less" } }, - "end": ";", + "end": "\\;", "endCaptures": { "0": { - "name": "punctuation.terminator.rule.css" + "name": "punctuation.terminator.rule.less" } }, - "name": "meta.at-rule.import.css", + "name": "meta.at-rule.import.less", "patterns": [ { - "match": "(?<=\\(|,|\\s)\\b(reference|optional|once|multiple|less|inline)\\b(?=\\)|,)", - "name": "keyword.control.import.option.less" + "include": "#url-function" }, { - "include": "#brace_round" + "include": "#less-variables" }, { - "include": "source.css#commas" - }, - { - "include": "#strings" - } - ] - }, - { - "captures": { - "1": { - "name": "keyword.control.at-rule.fontface.css" - }, - "2": { - "name": "punctuation.definition.keyword.css" - } - }, - "match": "^\\s*((@)font-face\\b)", - "name": "meta.at-rule.fontface.css" - }, - { - "captures": { - "1": { - "name": "keyword.control.at-rule.media.css" - }, - "2": { - "name": "punctuation.definition.keyword.css" - } - }, - "match": "^\\s*((@)media\\b)", - "name": "meta.at-rule.media.css" - }, - { - "include": "source.css#media-features" - }, - { - "match": "\\b(tv|tty|screen|projection|print|handheld|embossed|braille|aural|all)\\b", - "name": "support.constant.media-type.media.css" - }, - { - "match": "\\b(portrait|landscape)\\b", - "name": "support.constant.property-value.media-property.media.css" - }, - { - "captures": { - "1": { - "name": "support.function.less" - } - }, - "match": "(\\.[a-zA-Z0-9_-]+)\\s*(;|\\()" - }, - { - "begin": "(^[ \\t]+)?(?=//)", - "beginCaptures": { - "1": { - "name": "punctuation.whitespace.comment.leading.less" - } - }, - "end": "(?!\\G)", - "patterns": [ - { - "begin": "//", - "beginCaptures": { - "0": { - "name": "punctuation.definition.comment.less" - } - }, - "end": "\\n", - "name": "comment.line.double-slash.less" - } - ] - }, - { - "match": "(@|\\-\\-)[\\w-]+(?=\\s*)", - "name": "variable.other.less", - "captures": { - "1": { - "name": "punctuation.definition.variable.less" - } - } - }, - { - "include": "#variable_interpolation" - }, - { - "begin": "{", - "beginCaptures": { - "0": { - "name": "punctuation.section.property-list.begin.bracket.curly.css" - } - }, - "end": "}", - "endCaptures": { - "0": { - "name": "punctuation.section.property-list.end.bracket.curly.css" - } - }, - "name": "meta.property-list.css", - "patterns": [ - { - "include": "source.css#pseudo-elements" - }, - { - "include": "source.css#pseudo-classes" - }, - { - "include": "source.css#tag-names" - }, - { - "include": "source.css#commas" - }, - { - "include": "#variable_interpolation" - }, - { - "include": "source.css#property-names" - }, - { - "include": "#property_values" - }, - { - "include": "$self" - } - ] - }, - { - "match": "\\!\\s*important", - "name": "keyword.other.important.css" - }, - { - "match": "\\*|\\/|\\-|\\+|~|=|<=|>=|<|>", - "name": "keyword.operator.less" - }, - { - "match": "\\b(not|and|when)\\b", - "name": "keyword.control.logical.operator.less" - }, - { - "include": "source.css#tag-names" - }, - { - "match": "(?)=?))\\s*" + }, + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.group.less", + "patterns": [ + { + "include": "#less-logical-comparisons" + } + ] + }, + { + "match": "\\btrue|false\\b", + "name": "constant.language.less" + }, + { + "match": ",", + "name": "punctuation.separator.less" + }, + { + "include": "#property-values" + }, + { + "include": "#selectors" + }, + { + "include": "#unquoted-string" + } + ] + }, + "less-math": { + "patterns": [ + { + "match": "[-\\+\\*\\/]", + "name": "keyword.operator.arithmetic.less" + }, + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.group.less", + "patterns": [ + { + "include": "#less-math" + } + ] + }, + { + "include": "#numeric-values" + }, + { + "include": "#less-variables" + } + ] + }, + "less-math-functions": { + "patterns": [ + { + "begin": "\\b(ceil|floor|percentage|round|sqrt|abs|a?(sin|cos|tan))(?=\\()", + "beginCaptures": { + "1": { + "name": "support.function.math.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#less-variables" + }, + { + "include": "#numeric-values" + } + ] + } + ] + }, + { + "captures": { + "2": { + "name": "support.function.math.less" + }, + "3": { + "name": "punctuation.definition.group.begin.less" + }, + "4": { + "name": "punctuation.definition.group.end.less" + } + }, + "match": "((pi)(\\()(\\)))", + "name": "meta.function-call.less" + }, + { + "begin": "\\b(pow|m(od|in|ax))(?=\\()", + "beginCaptures": { + "1": { + "name": "support.function.math.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#less-variables" + }, + { + "include": "#numeric-values" + }, + { + "include": "#comma-delimiter" + } + ] + } + ] + } + ] + }, + "less-misc-functions": { + "patterns": [ + { + "begin": "\\b(color)(?=\\()", + "beginCaptures": { + "1": { + "name": "support.function.color.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#literal-string" + } + ] + } + ] + }, + { + "begin": "\\b(image-(size|width|height))(?=\\()", + "beginCaptures": { + "1": { + "name": "support.function.image.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#literal-string" + }, + { + "include": "#unquoted-string" + } + ] + } + ] + }, + { + "begin": "\\b(convert|unit)(?=\\()", + "beginCaptures": { + "1": { + "name": "support.function.convert.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#less-variables" + }, + { + "include": "#dimensions" + }, + { + "include": "#numeric-values" + }, + { + "include": "#literal-string" + }, + { + "include": "#comma-delimiter" + }, + { + "match": "((c|m)?m|in|p(t|c|x)|m?s|g?rad|deg|turn|%|r?em|ex|ch)", + "name": "keyword.other.unit.less" + } + ] + } + ] + }, + { + "begin": "\\b(data-uri)(?=\\()", + "beginCaptures": { + "1": { + "name": "support.function.data-uri.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#less-variables" + }, + { + "include": "#literal-string" + }, + { + "captures": { + "1": { + "name": "punctuation.separator.less" + } + }, + "match": "\\s*(?:(,))" + } + ] + } + ] + }, + { + "captures": { + "2": { + "name": "punctuation.definition.group.begin.less" + }, + "3": { + "name": "punctuation.definition.group.end.less" + } + }, + "match": "\\b(default(\\()(\\)))\\b", + "name": "support.function.default.less" + }, + { + "begin": "\\b(get-unit)(?=\\()", + "beginCaptures": { + "1": { + "name": "support.function.get-unit.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#dimensions" + } + ] + } + ] + }, + { + "begin": "\\b(svg-gradient)(?=\\()", + "beginCaptures": { + "1": { + "name": "support.function.svg-gradient.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#angle-type" + }, + { + "include": "#comma-delimiter" + }, + { + "include": "#color-values" + }, + { + "include": "#percentage-type" + }, + { + "include": "#length-type" + }, + { + "match": "\\bto\\b", + "name": "keyword.other.less" + }, + { + "match": "\\b(top|right|bottom|left|center)\\b", + "name": "support.constant.property-value.less" + }, + { + "match": "\\b(at|circle|ellipse)\\b", + "name": "keyword.other.less" + } + ] + } + ] + } + ] + }, + "less-mixin-guards": { + "patterns": [ + { + "begin": "\\s*(and|not|or)?\\s*(?=\\()", + "beginCaptures": { + "1": { + "name": "keyword.operator.logical.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "name": "meta.group.less", + "patterns": [ + { + "include": "#less-variable-comparison" + }, + { + "captures": { + "1": { + "name": "meta.group.less" + }, + "2": { + "name": "punctuation.definition.group.begin.less" + }, + "3": { + "name": "punctuation.definition.group.end.less" + } + }, + "match": "default((\\()(\\)))", + "name": "support.function.default.less" + }, + { + "include": "#property-values" + }, + { + "include": "#less-logical-comparisons" + }, + { + "include": "$self" + } + ] + } + ] + } + ] + }, + "less-namespace-accessors": { + "patterns": [ + { + "begin": "(?=\\s*when\\b)", + "end": "\\s*(?:(,)|(?=[{;]))", + "endCaptures": { + "1": { + "name": "punctuation.definition.block.end.less" + } + }, + "name": "meta.conditional.guarded-namespace.less", + "patterns": [ + { + "captures": { + "1": { + "name": "keyword.control.conditional.less" + }, + "2": { + "name": "punctuation.definition.keyword.less" + } + }, + "match": "\\s*(when)(?=.*?)" + }, + { + "include": "#less-mixin-guards" + }, + { + "include": "#comma-delimiter" + }, + { + "begin": "\\s*(\\{)", + "beginCaptures": { + "1": { + "name": "punctuation.section.property-list.begin.less" + } + }, + "end": "(?=\\})", + "name": "meta.block.less", + "patterns": [ + { + "include": "#rule-list-body" + } + ] + }, + { + "include": "#selectors" + } + ] + }, + { + "begin": "(\\()", + "beginCaptures": { + "1": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(\\))", + "endCaptures": { + "1": { + "name": "punctuation.definition.group.end.less" + }, + "2": { + "name": "punctuation.terminator.rule.less" + } + }, + "name": "meta.group.less", + "patterns": [ + { + "include": "#less-variable-assignment" + }, + { + "include": "#comma-delimiter" + }, + { + "captures": { + "1": { + "name": "punctuation.terminator.rule.less" + } + }, + "match": "\\s*(;)|(?=[})])" + }, + { + "include": "#property-values" + }, + { + "include": "#rule-list-body" + } + ] + } + ] + }, + "less-number-units": { + "patterns": [ + { + "match": "\\b((c|m)?m|in|p(t|c)|m?s|g?rad|deg|turn)\\b", + "name": "keyword.other.unit.less" + }, + { + "match": "\\b(r?em|ex|ch|vw|vh|vmin|vmax|cm|mm|q|in|pt|pc|px|fr|s|ms|Hz|kHz|dpi|dpcm|dppx|deg|grad|rad|turn)\\b" + } + ] + }, + "less-string-functions": { + "patterns": [ + { + "begin": "\\b(e(scape)?)(?=\\()\\b", + "beginCaptures": { + "1": { + "name": "support.function.escape.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#less-variables" + }, + { + "include": "#comma-delimiter" + }, + { + "include": "#literal-string" + }, + { + "include": "#unquoted-string" + } + ] + } + ] + }, + { + "begin": "\\s*(%)(?=\\()\\s*", + "beginCaptures": { + "1": { + "name": "support.function.format.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#less-variables" + }, + { + "include": "#comma-delimiter" + }, + { + "include": "#literal-string" + }, + { + "include": "#property-values" + } + ] + } + ] + }, + { + "begin": "\\b(replace)(?=\\()\\b", + "beginCaptures": { + "1": { + "name": "support.function.replace.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#less-variables" + }, + { + "include": "#comma-delimiter" + }, + { + "include": "#literal-string" + }, + { + "include": "#property-values" + } + ] + } + ] + } + ] + }, + "less-strings": { + "patterns": [ + { + "begin": "(~)('|\")", + "beginCaptures": { + "1": { + "name": "constant.character.escape.less" + }, + "2": { + "name": "punctuation.definition.string.begin.less" + } + }, + "contentName": "markup.raw.inline.less", + "end": "('|\")|(\\n)", + "endCaptures": { + "1": { + "name": "punctuation.definition.string.end.less" + }, + "2": { + "name": "invalid.illegal.newline.less" + } + }, + "name": "string.quoted.other.less", + "patterns": [ + { + "include": "#string-content" + } + ] + } + ] + }, + "less-type-functions": { + "patterns": [ + { + "begin": "\\b(is(number|string|color|keyword|url|pixel|em|percentage|ruleset))(?=\\()", + "beginCaptures": { + "1": { + "name": "support.function.type.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#property-values" + } + ] + } + ] + }, + { + "begin": "\\b(isunit)(?=\\()", + "beginCaptures": { + "1": { + "name": "support.function.type.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#property-values" + }, + { + "include": "#comma-delimiter" + }, + { + "match": "(?x)\\b((?i:em|ex|ch|rem)|(?i:vw|vh|vmin|vmax)|(?i:cm|mm|q|in|pt|pc|px|fr)|(?i:deg|grad|rad|turn)|(?i:s|ms)|(?i:Hz|kHz)|(?i:dpi|dpcm|dppx))\\b", + "name": "keyword.other.unit.less" + } + ] + } + ] + }, + { + "begin": "\\b(isdefined)(?=\\()", + "beginCaptures": { + "1": { + "name": "support.function.type.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#less-variables" + } + ] + } + ] + } + ] + }, + "less-variable-assignment": { + "patterns": [ + { + "begin": "(@)(-?(?:[[-\\w][^\\x{00}-\\x{7F}]]|(?:\\\\\\h{1,6}[\\s\\t\\n\\f]?|\\\\[^\\n\\f\\h]))(?:[[-\\w][^\\x{00}-\\x{7F}]]|(?:\\\\\\h{1,6}[\\s\\t\\n\\f]?|\\\\[^\\n\\f\\h]))*)", + "beginCaptures": { + "0": { + "name": "variable.other.readwrite.less" + }, + "1": { + "name": "punctuation.definition.variable.less" + }, + "2": { + "name": "support.other.variable.less" + } + }, + "end": "\\s*(;|(\\.{3})|(?=\\)))", + "endCaptures": { + "1": { + "name": "punctuation.terminator.rule.less" + }, + "2": { + "name": "keyword.operator.spread.less" + } + }, + "name": "meta.property-value.less", + "patterns": [ + { + "captures": { + "1": { + "name": "punctuation.separator.key-value.less" + }, + "4": { + "name": "meta.property-value.less" + } + }, + "match": "(((\\+_?)?):)([\\s\\t]*)" + }, + { + "include": "#property-values" + }, + { + "include": "#comma-delimiter" + }, + { + "include": "#property-list" + }, + { + "include": "#unquoted-string" + } + ] + } + ] + }, + "less-variable-comparison": { + "patterns": [ + { + "begin": "(@{1,2})([-]?([_a-z]|[^\\x{00}-\\x{7F}]|(?:\\\\\\h{1,6}[\\s\\t\\n\\f]?|\\\\[^\\n\\f\\h]))(?:[[-\\w][^\\x{00}-\\x{7F}]]|(?:\\\\\\h{1,6}[\\s\\t\\n\\f]?|\\\\[^\\n\\f\\h]))*)", + "beginCaptures": { + "0": { + "name": "variable.other.readwrite.less" + }, + "1": { + "name": "punctuation.definition.variable.less" + }, + "2": { + "name": "support.other.variable.less" + } + }, + "end": "\\s*(?=\\))", + "endCaptures": { + "1": { + "name": "punctuation.terminator.rule.less" + } + }, + "patterns": [ + { + "captures": { + "1": { + "name": "keyword.operator.logical.less" + } + }, + "match": "\\s*(=|((<|>)=?))\\s*" + }, + { + "match": "\\btrue\\b", + "name": "constant.language.less" + }, + { + "include": "#property-values" + }, + { + "include": "#selectors" + }, + { + "include": "#unquoted-string" + }, + { + "match": ",", + "name": "punctuation.separator.less" + } + ] + } + ] + }, + "less-variable-interpolation": { + "captures": { + "1": { + "name": "punctuation.definition.variable.less" + }, + "2": { + "name": "punctuation.definition.expression.less" + }, + "3": { + "name": "support.other.variable.less" + }, + "4": { + "name": "punctuation.definition.expression.less" + } + }, + "match": "(@)(\\{)([-\\w]+)(\\})", + "name": "variable.other.readwrite.less" + }, + "less-variables": { + "captures": { + "1": { + "name": "punctuation.definition.variable.less" + }, + "2": { + "name": "support.other.variable.less" + } + }, + "match": "\\s*(@@?)([-\\w]+)", + "name": "variable.other.readwrite.less" + }, + "literal-string": { + "patterns": [ + { + "begin": "'", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.less" + } + }, + "end": "(')|(\\n)", + "endCaptures": { + "1": { + "name": "punctuation.definition.string.end.less" + }, + "2": { + "name": "invalid.illegal.newline.less" + } + }, + "name": "string.quoted.single.less", + "patterns": [ + { + "include": "#string-content" + } + ] + }, + { + "begin": "\"", + "beginCaptures": { + "0": { + "name": "punctuation.definition.string.begin.less" + } + }, + "end": "(\")|(\\n)", + "endCaptures": { + "1": { + "name": "punctuation.definition.string.end.less" + }, + "2": { + "name": "invalid.illegal.newline.less" + } + }, + "name": "string.quoted.double.less", + "patterns": [ + { + "include": "#string-content" + } + ] + }, + { + "include": "#less-strings" + } + ] + }, + "local-function": { + "begin": "\\b(local)(?=\\()", + "beginCaptures": { + "0": { + "name": "support.function.font-face.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#unquoted-string" + } + ] + } + ] + }, + "media-query": { + "begin": "\\s*(only|not)?\\s*(all|aural|braille|embossed|handheld|print|projection|screen|tty|tv)?", + "beginCaptures": { + "1": { + "name": "keyword.operator.logic.media.less" + }, + "2": { + "name": "support.constant.media.less" + } + }, + "end": "\\s*(?:(,)|(?=[{;]))", + "endCaptures": { + "1": { + "name": "punctuation.definition.arbitrary-repetition.less" + } + }, + "patterns": [ + { + "include": "#less-variables" + }, + { + "include": "#custom-property-name" + }, + { + "begin": "\\s*(and)?\\s*(\\()\\s*", + "beginCaptures": { + "1": { + "name": "keyword.operator.logic.media.less" + }, + "2": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.group.less", + "patterns": [ + { + "begin": "((-webkit-|-o-)?((min|max)-)?(-moz-)?(((device-)?(height|width|aspect-ratio|pixel-ratio))|(color(-index)?)|monochrome|resolution))|grid|scan|orientation\\s*(?=[:)])", + "beginCaptures": { + "0": { + "name": "support.type.property-name.media.less" + }, + "2": { + "name": "support.type.vendor-prefix.less" + }, + "5": { + "name": "support.type.vendor-prefix.less" + } + }, + "end": "(((\\+_?)?):)|(?=\\))", + "endCaptures": { + "1": { + "name": "punctuation.separator.key-value.less" + } + } + }, + { + "match": "\\b(portrait|landscape|progressive|interlace)", + "name": "support.constant.property-value.less" + }, + { + "captures": { + "1": { + "name": "constant.numeric.less" + }, + "2": { + "name": "keyword.operator.arithmetic.less" + }, + "3": { + "name": "constant.numeric.less" + } + }, + "match": "\\s*(\\d+)(/)(\\d+)" + }, + { + "include": "#less-math" + } + ] + } + ] + }, + "media-query-list": { + "begin": "\\s*(?=[^{;])", + "end": "\\s*(?=[{;])", + "patterns": [ + { + "include": "#media-query" + } + ] + }, + "minmax-function": { + "begin": "\\b(minmax)(?=\\()", + "beginCaptures": { + "1": { + "name": "support.function.grid.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#less-variables" + }, + { + "include": "#length-type" + }, + { + "include": "#comma-delimiter" + }, + { + "match": "\\b(max-content|min-content)\\b", + "name": "support.constant.property-value.less" + } + ] + } + ] + }, + "number-type": { + "match": "[-+]?(?:(?:\\d*\\.\\d+(?:[eE](?:[-+]?\\d+))*)|(?:[-+]?\\d+))", + "name": "constant.numeric.less" + }, + "numeric-values": { + "patterns": [ + { + "include": "#dimensions" + }, + { + "include": "#percentage-type" + }, + { + "include": "#number-type" + } + ] + }, + "percentage-type": { + "captures": { + "1": { + "name": "keyword.other.unit.less" + } + }, + "match": "[-+]?(?:(?:\\d*\\.\\d+(?:[eE](?:[-+]?\\d+))*)|(?:[-+]?\\d+))(%)", + "name": "constant.numeric.less" + }, + "property-list": { + "patterns": [ + { + "begin": "(?=(?=[^;]*)\\{)", + "end": "\\}", + "endCaptures": { + "0": { + "name": "punctuation.definition.block.end.less" + } + }, + "patterns": [ + { + "include": "#rule-list" + } + ] + } + ] + }, + "property-value-constants": { + "patterns": [ + { + "match": "(?x)\\b(\n absolute|active|add\n |all(-(petite|small)-caps|-scroll)?\n |alpha(betic)?\n |alternate(-reverse)?\n |always|annotation|antialiased|at\n |auto(hiding-scrollbar)?\n |avoid(-column|-page|-region)?\n |background(-color|-image|-position|-size)?\n |backwards|balance|baseline|below|bevel|bicubic|bidi-override|blink\n |block(-line-height)?\n |blur\n |bold(er)?\n |border(-bottom|-left|-right|-top)?-(color|radius|width|style)\n |border-(bottom|top)-(left|right)-radius\n |border-image(-outset|-repeat|-slice|-source|-width)?\n |border(-bottom|-left|-right|-top|-collapse|-spacing|-box)?\n |both|bottom\n |box(-shadow)?\n |break-(all|word)\n |brightness\n |butt(on)?\n |capitalize\n |cent(er|ral)\n |char(acter-variant)?\n |cjk-ideographic|clip|clone|close-quote\n |closest-(corner|side)\n |col-resize|collapse\n |color(-stop|-burn|-dodge)?\n |column((-count|-gap|-reverse|-rule(-color|-width)?|-width)|s)?\n |common-ligatures|condensed|consider-shifts|contain\n |content(-box|s)?\n |contextual|contrast|cover\n |crisp(-e|E)dges\n |crop\n |cross(hair)?\n |da(rken|shed)\n |default|dense|diagonal-fractions|difference|disabled\n |discretionary-ligatures|disregard-shifts\n |distribute(-all-lines|-letter|-space)?\n |dotted|double|drop-shadow\n |(nwse|nesw|ns|ew|sw|se|nw|ne|w|s|e|n)-resize\n |ease(-in-out|-in|-out)?\n |element|ellipsis|embed|end|EndColorStr|evenodd\n |exclu(de(-ruby)?|sion)\n |expanded\n |(extra|semi|ultra)-(condensed|expanded)\n |farthest-(corner|side)?\n |fill(-box|-opacity)?\n |filter|fixed|flat\n |flex((-basis|-end|-grow|-shrink|-start)|box)?\n |flip|flood-color\n |font(-size(-adjust)?|-stretch|-weight)?\n |forwards\n |from(-image)?\n |full-width|geometricPrecision|glyphs|gradient|grayscale\n |grid(-height)?\n |groove|hand|hanging|hard-light|height|help|hidden|hide\n |historical-(forms|ligatures)\n |horizontal(-tb)?\n |hue\n |ideograph(-alpha|-numeric|-parenthesis|-space|ic)\n |inactive|include-ruby|infinite|inherit|initial\n |inline(-block|-box|-flex(box)?|-line-height|-table)?\n |inset|inside\n |inter(-ideograph|-word|sect)\n |invert|isolat(e|ion)|italic\n |jis(04|78|83|90)\n |justify(-all)?\n |keep-all\n |large[r]?\n |last|layout|left|letter-spacing\n |light(e[nr]|ing-color)\n |line(-edge|-height|-through)?\n |linear(-gradient|RGB)?\n |lining-nums|list-item|local|loose|lowercase|lr-tb|ltr\n |lumin(osity|ance)|manual\n |manipulation\n |margin(-bottom|-box|-left|-right|-top)?\n |marker(-offset|s)?\n |mathematical\n |max-(content|height|lines|size|width)\n |medium|middle\n |min-(content|height|width)\n |miter|mixed|move|multiply|newspaper\n |no-(change|clip|(close|open)-quote|(common|discretionary|historical)-ligatures|contextual|drop|repeat)\n |none|nonzero|normal|not-allowed|nowrap|oblique\n |offset(-after|-before|-end|-start)?\n |oldstyle-nums|opacity|open-quote\n |optimize(Legibility|Precision|Quality|Speed)\n |order|ordinal|ornaments\n |outline(-color|-offset|-width)?\n |outset|outside|over(line|-edge|lay)\n |padding(-bottom|-box|-left|-right|-top|-box)?\n |page|painted|paused\n |pan-(x|left|right|y|up|down)\n |perspective-origin\n |petite-caps|pixelated|pointer\n |pinch-zoom\n |pre(-line|-wrap)?\n |preserve-3d\n |progid:DXImageTransform.Microsoft.(Alpha|Blur|dropshadow|gradient|Shadow)\n |progress\n |proportional-(nums|width)\n |radial-gradient|recto|region|relative\n |repeat(-[xy])?\n |repeating-(linear|radial)-gradient\n |replaced|reset-size|reverse|ridge|right\n |round\n |row(-resize|-reverse)?\n |rtl|ruby|running|saturat(e|ion)|screen\n |scroll(-position|bar)?\n |separate|sepia\n |scale-down\n |shape-(image-threshold|margin|outside)\n |show\n |sideways(-lr|-rl)?\n |simplified\n |size\n |slashed-zero|slice\n |small(-caps|er)?\n |smooth|snap|solid|soft-light\n |space(-around|-between)?\n |span|sRGB\n |stack(ed-fractions)?\n |start(ColorStr)?\n |static\n |step-(end|start)\n |sticky\n |stop-(color|opacity)\n |stretch|strict\n |stroke(-box|-dash(array|offset)|-miterlimit|-opacity|-width)?\n |style(set)?\n |stylistic\n |sub(grid|pixel-antialiased|tract)?\n |super|swash\n |table(-caption|-cell|(-column|-footer|-header|-row)-group|-column|-row)?\n |tabular-nums|tb-rl\n |text((-bottom|-(decoration|emphasis)-color|-indent|-(over|under)-edge|-shadow|-size(-adjust)?|-top)|field)?\n |thi(ck|n)\n |titling-ca(ps|se)\n |to[p]?\n |touch|traditional\n |transform(-origin)?\n |under(-edge|line)?\n |unicase|unset|uppercase|upright\n |use-(glyph-orientation|script)\n |verso\n |vertical(-align|-ideographic|-lr|-rl|-text)?\n |view-box\n |viewport-fill(-opacity)?\n |visibility\n |visible(Fill|Painted|Stroke)?\n |wait|wavy|weight|whitespace|(device-)?width|word-spacing\n |wrap(-reverse)?\n |x{1,2}-(large|small)\n |z-index|zero\n |zoom(-in|-out)?\n |((?xi:arabic-indic|armenian|bengali|cambodian|circle|cjk-decimal|cjk-earthly-branch|cjk-heavenly-stem|decimal-leading-zero|decimal|devanagari|disclosure-closed|disclosure-open|disc|ethiopic-numeric|georgian|gujarati|gurmukhi|hebrew|hiragana-iroha|hiragana|japanese-formal|japanese-informal|kannada|katakana-iroha|katakana|khmer|korean-hangul-formal|korean-hanja-formal|korean-hanja-informal|lao|lower-alpha|lower-armenian|lower-greek|lower-latin|lower-roman|malayalam|mongolian|myanmar|oriya|persian|simp-chinese-formal|simp-chinese-informal|square|tamil|telugu|thai|tibetan|trad-chinese-formal|trad-chinese-informal|upper-alpha|upper-armenian|upper-latin|upper-roman)))\\b", + "name": "support.constant.property-value.less" + }, + { + "match": "\\b(?i:sans-serif|serif|monospace|fantasy|cursive)\\b(?=\\s*[;,\\n}])", + "name": "support.constant.font-name.less" + } + ] + }, + "property-values": { + "patterns": [ + { + "include": "#comment-block" + }, + { + "include": "#vendor-prefix" + }, + { + "include": "#builtin-functions" + }, + { + "include": "#color-functions" + }, + { + "include": "#less-math" + }, + { + "include": "#less-functions" + }, + { + "include": "#less-variables" + }, + { + "include": "#unicode-range" + }, + { + "include": "#numeric-values" + }, + { + "include": "#color-values" + }, + { + "include": "#property-value-constants" + }, + { + "include": "#literal-string" + }, + { + "captures": { + "1": { + "name": "punctuation.separator.less" + } + }, + "match": "(\\!)\\s*important", + "name": "keyword.other.important.less" + } + ] + }, + "pseudo-classes": { + "patterns": [ + { + "begin": "(:)(dir|lang)(?=\\()", + "captures": { + "1": { + "name": "punctuation.definition.entity.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "entity.other.attribute-name.pseudo-class.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#unquoted-string" + } + ] + } + ] + }, + { + "begin": "(:)(not)(?=\\()", + "captures": { + "1": { + "name": "punctuation.definition.entity.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "entity.other.attribute-name.pseudo-class.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#selectors" + } + ] + } + ] + }, + { + "begin": "(:)(nth(-last)?-(child|of-type))(?=\\()", + "captures": { + "1": { + "name": "punctuation.definition.entity.less" + }, + "2": { + "name": "entity.other.attribute-name.pseudo-class.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "name": "meta.group.less", + "patterns": [ + { + "match": "\\b(even|odd)\\b", + "name": "keyword.other.pseudo-class.less" + }, + { + "captures": { + "1": { + "name": "keyword.other.unit.less" + } + }, + "match": "(?:[-+]?(?:\\d+)?(n)(\\s*[-+]\\s*\\d+)?|[-+]?\\s*\\d+)", + "name": "constant.numeric.less" + }, + { + "include": "#less-math" + }, + { + "include": "#less-strings" + }, + { + "include": "#less-variable-interpolation" + } + ] + } + ] + }, + { + "begin": "(:)(host-context)(?=\\()", + "captures": { + "1": { + "name": "punctuation.definition.entity.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "entity.other.attribute-name.pseudo-class.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#selectors" + } + ] + } + ] + }, + { + "captures": { + "1": { + "name": "punctuation.definition.entity.less" + }, + "2": { + "name": "entity.other.attribute-name.pseudo-class.less" + } + }, + "match": "(:)(active|any|checked|default|disabled|empty|enabled|first(-(child|of-type))?|fullscreen|focus|host|hover|indeterminate|in-range|invalid|last-(child|of-type)|left|link|only-(child|of-type)|optional|out-of-range|read-(only|write)|required|right|root|scope|target|valid|visited)", + "name": "meta.function-call.less" + } + ] + }, + "pseudo-elements": { + "patterns": [ + { + "begin": "(::)(slotted)(?=\\()", + "captures": { + "1": { + "name": "punctuation.definition.entity.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "entity.other.attribute-name.pseudo-class.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#selectors" + } + ] + } + ] + }, + { + "captures": { + "1": { + "name": "punctuation.definition.entity.less" + }, + "2": { + "name": "punctuation.definition.entity.less" + }, + "3": { + "name": "support.type.vendor-prefix.less" + } + }, + "match": "(?:(:{1,2})(?:before|after|first-line|first-letter)|(::)(-(?:moz|ms|webkit)-)?(?:(-?(?:[[-\\w][^\\x{00}-\\x{7F}]]|(?:\\\\\\h{1,6}[\\s\\t\\n\\f]?|\\\\[^\\n\\f\\h]))(?:[[-\\w][^\\x{00}-\\x{7F}]]|(?:\\\\\\h{1,6}[\\s\\t\\n\\f]?|\\\\[^\\n\\f\\h]))*)))\\b", + "name": "entity.other.attribute-name.pseudo-element.less" + } + ] + }, + "qualified-name": { + "captures": { + "1": { + "name": "entity.name.constant.less" + }, + "2": { + "name": "entity.name.namespace.wildcard.less" + }, + "3": { + "name": "punctuation.separator.namespace.less" + } + }, + "match": "(?:(-?(?:[[-\\w][^\\x{00}-\\x{7F}]]|(?:\\\\\\h{1,6}[\\s\\t\\n\\f]?|\\\\[^\\n\\f\\h]))(?:[[_a-zA-Z][^\\x{00}-\\x{7F}]]|(?:\\\\\\h{1,6}[\\s\\t\\n\\f]?|\\\\[^\\n\\f\\h]))*)|(\\*))?([|])(?!=)" + }, + "regexp-function": { + "begin": "\\b(regexp)(?=\\()", + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "support.function.regexp.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "name": "meta.function-call.less", + "patterns": [ + { + "include": "#literal-string" + } + ] + } + ] + }, + "resolution-type": { + "captures": { + "1": { + "name": "keyword.other.unit.less" + } + }, + "match": "(?i:[-+]?(?:(?:\\d*\\.\\d+(?:[eE](?:[-+]?\\d+))*)|(?:[-+]?\\d+))(dpi|dpcm|dppx))\\b", + "name": "constant.numeric.less" + }, + "rule-list": { + "patterns": [ + { + "begin": "\\{", + "beginCaptures": { + "0": { + "name": "punctuation.definition.block.begin.less" + } + }, + "end": "(?=\\s*\\})", + "name": "meta.property-list.less", + "patterns": [ + { + "captures": { + "1": { + "name": "punctuation.terminator.rule.less" + } + }, + "match": "\\s*(;)|(?=[})])" + }, + { + "include": "#rule-list-body" + }, + { + "include": "#less-extend" + } + ] + } + ] + }, + "rule-list-body": { + "patterns": [ + { + "include": "#comment-block" + }, + { + "include": "#comment-line" + }, + { + "include": "#at-rules" + }, + { + "include": "#less-variable-assignment" + }, + { + "include": "#less-variable-interpolation" + }, + { + "begin": "(?=[-a-z])", + "end": "$|(?![-a-z])", + "patterns": [ + { + "include": "#vendor-prefix" + }, + { + "include": "#custom-property-name" + }, + { + "include": "#filter-function" + }, + { + "captures": { + "1": { + "name": "keyword.other.custom-property.prefix.less" + }, + "2": { + "name": "support.type.custom-property.name.less" + } + }, + "match": "\\b(var-)(-?(?:[[-\\w][^\\x{00}-\\x{9f}]]|(?:\\\\\\h{1,6}[\\s\\t\\n\\f]?|\\\\[^\\n\\f\\h]))(?:[[_a-zA-Z][^\\x{00}-\\x{9f}]]|(?:\\\\\\h{1,6}[\\s\\t\\n\\f]?|\\\\[^\\n\\f\\h]))*)(?=\\s)", + "name": "invalid.deprecated.custom-property.less" + }, + { + "begin": "\\bfont(-family)?(?!-)\\b", + "beginCaptures": { + "0": { + "name": "support.type.property-name.less" + } + }, + "end": "\\s*(;)|(?=[})])", + "endCaptures": { + "1": { + "name": "punctuation.terminator.rule.less" + } + }, + "name": "meta.property-name.less", + "patterns": [ + { + "captures": { + "1": { + "name": "punctuation.separator.key-value.less" + }, + "4": { + "name": "meta.property-value.less" + } + }, + "match": "(((\\+_?)?):)([\\s\\t]*)" + }, + { + "include": "#property-values" + }, + { + "match": "-?(?:[[_a-zA-Z][^\\x{00}-\\x{9f}]]|(?:\\\\\\h{1,6}[\\s\\t\\n\\f]?|\\\\[^\\n\\f\\h]))(?:[[-\\w][^\\x{00}-\\x{9f}]]|(?:\\\\\\h{1,6}[\\s\\t\\n\\f]?|\\\\[^\\n\\f\\h]))*(\\s+-?(?:[[_a-zA-Z][^\\x{00}-\\x{9f}]]|(?:\\\\\\h{1,6}[\\s\\t\\n\\f]?|\\\\[^\\n\\f\\h]))(?:[[-\\w][^\\x{00}-\\x{9f}]]|(?:\\\\\\h{1,6}[\\s\\t\\n\\f]?|\\\\[^\\n\\f\\h]))*)*", + "name": "string.unquoted.less" + }, + { + "match": ",", + "name": "punctuation.separator.less" + } + ] + }, + { + "begin": "\\banimation(-(delay|direction|duration|fill-mode|iteration-count|name|play-state|timing-function))?\\b", + "beginCaptures": { + "0": { + "name": "support.type.property-name.less" + } + }, + "end": "\\s*(;)|(?=[})])", + "endCaptures": { + "1": { + "name": "punctuation.terminator.rule.less" + } + }, + "patterns": [ + { + "begin": "(((\\+_?)?):)(?=[\\s\\t]*)", + "beginCaptures": { + "1": { + "name": "punctuation.separator.key-value.less" + } + }, + "captures": { + "1": { + "name": "punctuation.definition.arbitrary-repetition.less" + } + }, + "contentName": "meta.property-value.less", + "end": "(?=\\s*(;)|(?=[})]))", + "patterns": [ + { + "match": "\\b(linear|ease(-in)?(-out)?|step-(start|end)|none|forwards|backwards|both|normal|alternate(-reverse)?|reverse|running|paused)\\b", + "name": "support.constant.property-value.less" + }, + { + "include": "#cubic-bezier-function" + }, + { + "include": "#steps-function" + }, + { + "include": "#time-type" + }, + { + "include": "#number-type" + }, + { + "match": "-?(?:[_a-zA-Z]|[^\\x{00}-\\x{7F}]|(?:(:?\\\\[0-9a-f]{1,6}(\\r\\n|[\\s\\t\\r\\n\\f])?)|\\\\[^\\r\\n\\f0-9a-f]))(?:[-_a-zA-Z0-9]|[^\\x{00}-\\x{7F}]|(?:(:?\\\\[0-9a-f]{1,6}(\\r\\n|[\\t\\r\\n\\f])?)|\\\\[^\\r\\n\\f0-9a-f]))*", + "name": "variable.other.constant.animation-name.less" + }, + { + "include": "#literal-string" + }, + { + "include": "#property-values" + }, + { + "match": "\\s*(?:(,))" + } + ] + } + ] + }, + { + "begin": "\\b(transition(-(property|duration|delay|timing-function))?)\\b", + "beginCaptures": { + "0": { + "name": "meta.property-name.less" + }, + "1": { + "name": "support.type.property-name.less" + } + }, + "end": "\\s*(;)|(?=[})])", + "endCaptures": { + "1": { + "name": "punctuation.terminator.rule.less" + } + }, + "patterns": [ + { + "captures": { + "1": { + "name": "punctuation.separator.key-value.less" + }, + "4": { + "name": "meta.property-value.less" + } + }, + "match": "(((\\+_?)?):)([\\s\\t]*)" + }, + { + "include": "#time-type" + }, + { + "include": "#property-values" + }, + { + "include": "#cubic-bezier-function" + }, + { + "include": "#steps-function" + }, + { + "captures": { + "1": { + "name": "punctuation.definition.arbitrary-repetition.less" + } + }, + "match": "\\s*(?:(,))" + } + ] + }, + { + "begin": "\\bfilter\\b", + "beginCaptures": { + "0": { + "name": "support.type.property-name.less" + } + }, + "end": "\\s*(;)|(?=[})])", + "endCaptures": { + "1": { + "name": "punctuation.terminator.rule.less" + } + }, + "name": "meta.property-name.less", + "patterns": [ + { + "captures": { + "1": { + "name": "punctuation.separator.key-value.less" + }, + "4": { + "name": "meta.property-value.less" + } + }, + "match": "(((\\+_?)?):)([\\s\\t]*)" + }, + { + "match": "\\b(inherit|initial|unset|none)\\b", + "name": "meta.property-value.less" + }, + { + "include": "#filter-functions" + } + ] + }, + { + "begin": "\\bwill-change\\b", + "beginCaptures": { + "0": { + "name": "support.type.property-name.less" + } + }, + "end": "\\s*(;)|(?=[})])", + "endCaptures": { + "1": { + "name": "punctuation.terminator.rule.less" + } + }, + "name": "meta.property-name.less", + "patterns": [ + { + "captures": { + "1": { + "name": "punctuation.separator.key-value.less" + }, + "4": { + "name": "meta.property-value.less" + } + }, + "match": "(((\\+_?)?):)([\\s\\t]*)" + }, + { + "match": "unset|initial|inherit|will-change|auto|scroll-position|contents", + "name": "invalid.illegal.property-value.less" + }, + { + "match": "-?(?:[[-\\w][^\\x{00}-\\x{9f}]]|(?:\\\\\\h{1,6}[\\s\\t\\n\\f]?|\\\\[^\\n\\f\\h]))(?:[[_a-zA-Z][^\\x{00}-\\x{9f}]]|(?:\\\\\\h{1,6}[\\s\\t\\n\\f]?|\\\\[^\\n\\f\\h]))*", + "name": "support.constant.property-value.less" + }, + { + "captures": { + "1": { + "name": "punctuation.definition.arbitrary-repetition.less" + } + }, + "match": "\\s*(?:(,))" + } + ] + }, + { + "begin": "\\bcounter-(increment|(re)?set)\\b", + "beginCaptures": { + "0": { + "name": "support.type.property-name.less" + } + }, + "end": "\\s*(;)|(?=[})])", + "endCaptures": { + "1": { + "name": "punctuation.terminator.rule.less" + } + }, + "name": "meta.property-name.less", + "patterns": [ + { + "captures": { + "1": { + "name": "punctuation.separator.key-value.less" + }, + "4": { + "name": "meta.property-value.less" + } + }, + "match": "(((\\+_?)?):)([\\s\\t]*)" + }, + { + "match": "-?(?:[[-\\w][^\\x{00}-\\x{9f}]]|(?:\\\\\\h{1,6}[\\s\\t\\n\\f]?|\\\\[^\\n\\f\\h]))(?:[[_a-zA-Z][^\\x{00}-\\x{9f}]]|(?:\\\\\\h{1,6}[\\s\\t\\n\\f]?|\\\\[^\\n\\f\\h]))*", + "name": "entity.name.constant.counter-name.less" + }, + { + "include": "#integer-type" + }, + { + "match": "unset|initial|inherit|auto", + "name": "invalid.illegal.property-value.less" + } + ] + }, + { + "match": "(?x)\\b( accent-height | align-content | align-items | align-self | alignment-baseline | all | animation-timing-function | animation-play-state | animation-name | animation-iteration-count | animation-fill-mode | animation-duration | animation-direction | animation-delay | animation | appearance | ascent | azimuth | backface-visibility | background-size | background-repeat-y | background-repeat-x | background-repeat | background-position-y | background-position-x | background-position | background-origin | background-image | background-color | background-clip | background-blend-mode | background-attachment | background | baseline-shift | begin | bias | blend-mode | border-((top|right|bottom|left)-)?(width|style|color) | border-(top|bottom)-(right|left)-radius | border-image-(width|source|slice|repeat|outset) | border-(top|right|bottom|left|collapse|image|radius|spacing) | border | bottom | box-(align|decoration-break|direction|flex|ordinal-group|orient|pack|shadow|sizing) | break-(after|before|inside) | caption-side | clear | clip-path | clip-rule | clip | color(-(interpolation(-filters)?|profile|rendering))? | columns | column-(break-before|count|fill|gap|(rule(-(color|style|width))?)|span|width) | contain | content | counter-(increment|reset) | cursor | (c|d|f)(x|y) | direction | display | divisor | dominant-baseline | dur | elevation | empty-cells | enable-background | end | fallback | fill(-(opacity|rule))? | filter | flex(-(align|basis|direction|flow|grow|item-align|line-pack|negative|order|pack|positive|preferred-size|shrink|wrap))? | float | flood-(color|opacity) | font-display | font-family | font-feature-settings | font-kerning | font-language-override | font-size(-adjust)? | font-smoothing | font-stretch | font-style | font-synthesis | font-variant(-(alternates|caps|east-asian|ligatures|numeric|position))? | font-weight | font | fr | glyph-orientation-(horizontal|vertical) | grid-(area|gap) | grid-auto-(columns|flow|rows) | grid-(column|row)(-(end|gap|start))? | grid-template(-(areas|columns|rows))? | height | hyphens | image-(orientation|rendering|resolution) | isolation | justify-content | kerning | left | letter-spacing | lighting-color | line-(box-contain|break|clamp|height) | list-style(-(image|position|type))? | margin(-(bottom|left|right|top))? | marker(-(end|mid|start))? | mask(-(clip||composite|image|origin|position|repeat|size|type))? | (max|min)-(height|width) | mix-blend-mode | nbsp-mode | negative | object-(fit|position) | opacity | operator | order | orphans | outline(-(color|offset|style|width))? | overflow(-(scrolling|wrap|x|y))? | pad(ding(-(bottom|left|right|top))?)? | page(-break-(after|before|inside))? | paint-order | pause(-(after|before))? | perspective(-origin(-(x|y))?)? | pitch(-range)? | pointer-events | position | prefix | quotes | range | resize | right | rotate | scale | scroll-behavior | shape-(image-threshold|margin|outside|rendering) | size | speak(-as)? | src | stop-(color|opacity) | stroke(-(dash(array|offset)|line(cap|join)|miterlimit|opacity|width))? | suffix | symbols | system | tab-size | table-layout | tap-highlight-color | text-align(-last)? | text-decoration(-(color|line|style))? | text-emphasis(-(color|position|style))? | text-(anchor|fill-color|height|indent|justify|orientation|overflow|rendering|shadow|transform|underline-position) | top | touch-action | transform(-origin(-(x|y))?) | transform(-style)? | transition(-(delay|duration|property|timing-function))? | translate | unicode-(bidi|range) | user-(drag|select) | vertical-align | visibility | white-space | widows | width | will-change | word-(break|spacing|wrap) | writing-mode | z-index | zoom )\\b", + "name": "support.type.property-name.less" + }, + { + "include": "$self" + } + ] + }, + { + "begin": "\\b(((\\+_?)?):)([\\s\\t]*)", + "captures": { + "1": { + "name": "punctuation.separator.key-value.less" + }, + "4": { + "name": "meta.property-value.less" + } + }, + "contentName": "meta.property-value.less", + "end": "\\s*(;)|(?=[})])", + "endCaptures": { + "1": { + "name": "punctuation.terminator.rule.less" + } + }, + "patterns": [ + { + "include": "#property-values" + } + ] }, { "include": "$self" } ] }, - "less_builtin_functions": { - "match": "\\b(abs|acos|alpha|argb|asin|atan|average|blue|calc|ceil|color|contrast|convert|convert|cos|darken|data-uri|desaturate|difference|e|escape|exclusion|extract|fade|fadein|fadeout|floor|format|green|greyscale|hardlight|hsl|hsla|hsv|hsva|hsvhue|hsvsaturation|hsvvalue|hue|length|lighten|lightness|luma|max|min|mix|mod|multiply|negation|overlay|percentage|pi|pow|red|replace|round|saturate|saturation|screen|sin|softlight|spin|sqrt|tan|unit)\\b", - "name": "support.function.any-method.builtin.less" + "selector": { + "patterns": [ + { + "begin": "(?=[>~+/\\.*#a-zA-Z\\[&]|(\\:{1,2}[^\\s])|@\\{)", + "contentName": "meta.selector.less", + "end": "(?=@(?!\\{)|[{;])", + "patterns": [ + { + "include": "#comment-line" + }, + { + "include": "#selectors" + }, + { + "include": "#less-namespace-accessors" + }, + { + "include": "#less-variable-interpolation" + }, + { + "captures": { + "1": { + "name": "punctuation.separator.less" + } + }, + "match": "(\\!)\\s*important", + "name": "keyword.other.important.less" + } + ] + } + ] + }, + "selectors": { + "patterns": [ + { + "match": "\\b([a-z](?:(?:[-_a-z0-9\\x{00B7}]|\\\\\\.|[[\\x{00C0}-\\x{00D6}][\\x{00D8}-\\x{00F6}][\\x{00F8}-\\x{02FF}][\\x{0300}-\\x{037D}][\\x{037F}-\\x{1FFF}][\\x{200C}-\\x{200D}][\\x{203F}-\\x{2040}][\\x{2070}-\\x{218F}][\\x{2C00}-\\x{2FEF}][\\x{3001}-\\x{D7FF}][\\x{F900}-\\x{FDCF}][\\x{FDF0}-\\x{FFFD}][\\x{10000}-\\x{EFFFF}]]))*-(?:(?:[-_a-z0-9\\x{00B7}]|\\\\\\.|[[\\x{00C0}-\\x{00D6}][\\x{00D8}-\\x{00F6}][\\x{00F8}-\\x{02FF}][\\x{0300}-\\x{037D}][\\x{037F}-\\x{1FFF}][\\x{200C}-\\x{200D}][\\x{203F}-\\x{2040}][\\x{2070}-\\x{218F}][\\x{2C00}-\\x{2FEF}][\\x{3001}-\\x{D7FF}][\\x{F900}-\\x{FDCF}][\\x{FDF0}-\\x{FFFD}][\\x{10000}-\\x{EFFFF}]]))*)\\b", + "name": "entity.name.tag.custom.less" + }, + { + "match": "(?x)\\b( a | abbr | acronym | address | applet | area | article | aside | audio | b | base | basefont | bdi | bdo | big | blockquote | body | br | button | canvas | caption | circle | cite | clipPath | code | col | colgroup | content | data | dataList | dd | defs | del | details | dfn | dialog | dir | div | dl | dt | element | ellipse | em | embed | eventsource | fieldset | figcaption | figure | filter | footer | foreignObject | form | frame | frameset | g | glyph | glyphRef | h1 | h2 | h3 | h4 | h5 | h6 | head | header | hgroup | hr | html | i | iframe | image | img | input | ins | isindex | kbd | keygen | label | legend | li | line | linearGradient | link | main | map | mark | marker | mask | menu | meta | meter | nav | noframes | noscript | object | ol | optgroup | option | output | p | param | path | pattern | picture | polygon | polyline | pre | progress | q | radialGradient | rect | rp | ruby | rt | rtc | s | samp | script | section | select | shadow | small | source | span | stop | strike | strong | style | sub | summary | sup | svg | switch | symbol | table | tbody | td | template | textarea | textPath | tfoot | th | thead | time | title | tr | track | tref | tspan | tt | u | ul | use | var | video | wbr | xmp )\\b", + "name": "entity.name.tag.less" + }, + { + "begin": "(\\.)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.entity.less" + } + }, + "end": "(?![-\\w]|[^\\x{00}-\\x{9f}]|\\\\([A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9])|(\\@(?=\\{)))", + "name": "entity.other.attribute-name.class.less", + "patterns": [ + { + "include": "#less-variable-interpolation" + } + ] + }, + { + "begin": "(#)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.entity.less" + } + }, + "end": "(?![-\\w]|[^\\x{00}-\\x{9f}]|\\\\([A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9])|(\\@(?=\\{)))", + "name": "entity.other.attribute-name.id.less", + "patterns": [ + { + "include": "#less-variable-interpolation" + } + ] + }, + { + "begin": "(&)", + "beginCaptures": { + "1": { + "name": "punctuation.definition.entity.less" + } + }, + "contentName": "entity.other.attribute-name.parent.less", + "end": "(?![-\\w]|[^\\x{00}-\\x{9f}]|\\\\([A-Fa-f0-9]{1,6} ?|[^A-Fa-f0-9])|(\\@(?=\\{)))", + "name": "entity.other.attribute-name.parent.less", + "patterns": [ + { + "include": "#less-variable-interpolation" + }, + { + "include": "#selectors" + } + ] + }, + { + "include": "#pseudo-elements" + }, + { + "include": "#pseudo-classes" + }, + { + "include": "#less-extend" + }, + { + "match": "(?!\\+_?:)(?:>{1,3}|[~+])(?![>~+;}])", + "name": "punctuation.separator.combinator.less" + }, + { + "match": "((?:>{1,3}|[~+])){2,}", + "name": "invalid.illegal.combinator.less" + }, + { + "match": "\\/deep\\/", + "name": "invalid.illegal.combinator.less" + }, + { + "begin": "\\[", + "captures": { + "0": { + "name": "punctuation.definition.entity.less" + } + }, + "end": "\\]", + "name": "meta.attribute-selector.less", + "patterns": [ + { + "include": "#less-variable-interpolation" + }, + { + "include": "#qualified-name" + }, + { + "match": "(-?(?:[[_a-zA-Z][^\\x{00}-\\x{7F}]]|(?:\\\\\\h{1,6}[\\s\\t\\n\\f]?|\\\\[^\\n\\f\\h]))(?:[[-\\w][^\\x{00}-\\x{7F}]]|(?:\\\\\\h{1,6}[\\s\\t\\n\\f]?|\\\\[^\\n\\f\\h]))*)", + "name": "entity.other.attribute-name.less" + }, + { + "begin": "\\s*([~*|^$]?=)\\s*", + "captures": { + "1": { + "name": "keyword.operator.attribute-selector.less" + } + }, + "end": "(?=(\\s|\\]))", + "patterns": [ + { + "include": "#less-variable-interpolation" + }, + { + "match": "[^\\s\\]\\['\"]", + "name": "string.unquoted.less" + }, + { + "include": "#literal-string" + }, + { + "captures": { + "1": { + "name": "keyword.other.less" + } + }, + "match": "(?:\\s+([iI]))?" + }, + { + "match": "\\]", + "name": "punctuation.definition.entity.less" + } + ] + } + ] + }, + { + "captures": { + "1": { + "name": "punctuation.definition.arbitrary-repetition.less" + } + }, + "match": "\\s*(?:(,))" + }, + { + "match": "\\*", + "name": "entity.name.tag.wildcard.less" + } + ] + }, + "shape-functions": { + "patterns": [ + { + "begin": "\\b(rect)(?=\\()", + "beginCaptures": { + "0": { + "name": "support.function.shape.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "match": "\\bauto\\b", + "name": "support.constant.property-value.less" + }, + { + "include": "#length-type" + }, + { + "include": "#comma-delimiter" + } + ] + } + ] + }, + { + "begin": "\\b(inset)(?=\\()", + "beginCaptures": { + "0": { + "name": "support.function.shape.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "match": "\\bround\\b", + "name": "keyword.other.less" + }, + { + "include": "#length-type" + }, + { + "include": "#percentage-type" + } + ] + } + ] + }, + { + "begin": "\\b(circle|ellipse)(?=\\()", + "beginCaptures": { + "0": { + "name": "support.function.shape.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "match": "\\bat\\b", + "name": "keyword.other.less" + }, + { + "match": "\\b(top|right|bottom|left|center|closest-side|farthest-side)\\b", + "name": "support.constant.property-value.less" + }, + { + "include": "#length-type" + }, + { + "include": "#percentage-type" + } + ] + } + ] + }, + { + "begin": "\\b(polygon)(?=\\()", + "beginCaptures": { + "0": { + "name": "support.function.shape.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "match": "\\b(nonzero|evenodd)\\b", + "name": "support.constant.property-value.less" + }, + { + "include": "#length-type" + }, + { + "include": "#percentage-type" + } + ] + } + ] + } + ] + }, + "steps-function": { + "begin": "\\b(steps)(?=\\()", + "beginCaptures": { + "0": { + "name": "support.function.timing.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#comma-delimiter" + }, + { + "include": "#integer-type" + }, + { + "match": "(end|middle|start)", + "name": "support.keyword.timing-direction.less" + } + ] + } + ] + }, + "string-content": { + "patterns": [ + { + "include": "#less-variable-interpolation" + }, + { + "match": "\\\\\\s*\\n", + "name": "constant.character.escape.newline.less" + }, + { + "match": "\\\\(\\h{1,6}|.)", + "name": "constant.character.escape.less" + } + ] + }, + "symbols-function": { + "begin": "\\b(symbols)(?=\\()", + "beginCaptures": { + "1": { + "name": "support.function.counter.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "match": "\\b(cyclic|numeric|alphabetic|symbolic|fixed)\\b", + "name": "support.constant.symbol-type.less" + }, + { + "include": "#comma-delimiter" + }, + { + "include": "#literal-string" + }, + { + "include": "#image-type" + } + ] + } + ] + }, + "time-type": { + "captures": { + "1": { + "name": "keyword.other.unit.less" + } + }, + "match": "(?i:[-+]?(?:(?:\\d*\\.\\d+(?:[eE](?:[-+]?\\d+))*)|(?:[-+]?\\d+))(s|ms))\\b", + "name": "constant.numeric.less" + }, + "transform-functions": { + "patterns": [ + { + "begin": "\\b(matrix3d|scale3d|matrix|scale)(?=\\()", + "beginCaptures": { + "0": { + "name": "support.function.transform.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#comma-delimiter" + }, + { + "include": "#number-type" + }, + { + "include": "#less-variables" + }, + { + "include": "#var-function" + } + ] + } + ] + }, + { + "begin": "\\b(translate(3d)?)(?=\\()", + "beginCaptures": { + "0": { + "name": "support.function.transform.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#comma-delimiter" + }, + { + "include": "#percentage-type" + }, + { + "include": "#length-type" + }, + { + "include": "#number-type" + }, + { + "include": "#less-variables" + }, + { + "include": "#var-function" + } + ] + } + ] + }, + { + "begin": "\\b(translate[XY])(?=\\()", + "beginCaptures": { + "0": { + "name": "support.function.transform.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#percentage-type" + }, + { + "include": "#length-type" + }, + { + "include": "#number-type" + }, + { + "include": "#less-variables" + }, + { + "include": "#var-function" + } + ] + } + ] + }, + { + "begin": "\\b(rotate[XYZ]?|skew[XY])(?=\\()", + "beginCaptures": { + "0": { + "name": "support.function.transform.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#angle-type" + }, + { + "include": "#less-variables" + }, + { + "include": "#calc-function" + }, + { + "include": "#var-function" + } + ] + } + ] + }, + { + "begin": "\\b(skew)(?=\\()", + "beginCaptures": { + "0": { + "name": "support.function.transform.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#comma-delimiter" + }, + { + "include": "#angle-type" + }, + { + "include": "#less-variables" + }, + { + "include": "#calc-function" + }, + { + "include": "#var-function" + } + ] + } + ] + }, + { + "begin": "\\b(translateZ|perspective)(?=\\()", + "beginCaptures": { + "0": { + "name": "support.function.transform.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#length-type" + }, + { + "include": "#less-variables" + }, + { + "include": "#calc-function" + }, + { + "include": "#var-function" + } + ] + } + ] + }, + { + "begin": "\\b(rotate3d)(?=\\()", + "beginCaptures": { + "0": { + "name": "support.function.transform.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#comma-delimiter" + }, + { + "include": "#angle-type" + }, + { + "include": "#number-type" + }, + { + "include": "#less-variables" + }, + { + "include": "#calc-function" + }, + { + "include": "#var-function" + } + ] + } + ] + }, + { + "begin": "\\b(scale[XYZ])(?=\\()", + "beginCaptures": { + "0": { + "name": "support.function.transform.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#comma-delimiter" + }, + { + "include": "#number-type" + }, + { + "include": "#less-variables" + }, + { + "include": "#calc-function" + }, + { + "include": "#var-function" + } + ] + } + ] + } + ] + }, + "unicode-range": { + "captures": { + "1": { + "name": "support.constant.unicode-range.prefix.less" + }, + "2": { + "name": "constant.codepoint-range.less" + }, + "3": { + "name": "punctuation.section.range.less" + } + }, + "match": "(?i)(u\\+)([0-9a-f?]{1,6}(?:(-)[0-9a-f]{1,6})?)", + "name": "support.unicode-range.less" + }, + "unquoted-string": { + "match": "[^\\s'\"]", + "name": "string.unquoted.less" + }, + "url-function": { + "begin": "\\b(url)(?=\\()", + "beginCaptures": { + "1": { + "name": "support.function.url.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#less-variables" + }, + { + "include": "#literal-string" + }, + { + "include": "#unquoted-string" + }, + { + "include": "#var-function" + } + ] + } + ] + }, + "var-function": { + "patterns": [ + { + "begin": "\\b(var)(?=\\()", + "beginCaptures": { + "1": { + "name": "support.function.var.less" + } + }, + "end": "\\)", + "endCaptures": { + "0": { + "name": "punctuation.definition.group.end.less" + } + }, + "name": "meta.function-call.less", + "patterns": [ + { + "begin": "\\(", + "beginCaptures": { + "0": { + "name": "punctuation.definition.group.begin.less" + } + }, + "end": "(?=\\))", + "patterns": [ + { + "include": "#comma-delimiter" + }, + { + "include": "#custom-property-name" + }, + { + "include": "#less-variables" + } + ] + } + ] + } + ] + }, + "vendor-prefix": { + "match": "-(?:webkit|moz(-osx)?|ms|o)-", + "name": "support.type.vendor-prefix.less" } } } \ No newline at end of file diff --git a/extensions/notebook-renderers/src/stackTraceHelper.ts b/extensions/notebook-renderers/src/stackTraceHelper.ts index e72c3b8a88c..943877ad59d 100644 --- a/extensions/notebook-renderers/src/stackTraceHelper.ts +++ b/extensions/notebook-renderers/src/stackTraceHelper.ts @@ -31,7 +31,7 @@ export function formatStackTrace(stack: string) { const formatSequence = /\u001b\[.+?m/g; const fileRegex = /File\s+(?:\u001b\[.+?m)?(.+):(\d+)/; -const lineNumberRegex = /((?:\u001b\[.+?m)?[ ->]*?)(\d+)(.*)/; +const lineNumberRegex = /^((?:\u001b\[.+?m)?[ \->]+?)(\d+)(?:\u001b\[0m)?( .*)/; const cellRegex = /(?Cell\s+(?:\u001b\[.+?m)?In\s*\[(?\d+)\],\s*)(?line (?\d+)).*/; // older versions of IPython ~8.3.0 const inputRegex = /(?Input\s+?(?:\u001b\[.+?m)(?In\s*\[(?\d+)\]))(?.*)/; @@ -41,7 +41,7 @@ function isIpythonStackTrace(stack: string) { } function stripFormatting(text: string) { - return text.replace(formatSequence, ''); + return text.replace(formatSequence, '').trim(); } type cellLocation = { kind: 'cell'; path: string }; diff --git a/extensions/notebook-renderers/src/test/stackTraceHelper.test.ts b/extensions/notebook-renderers/src/test/stackTraceHelper.test.ts index e65efe3ee29..c4e99179086 100644 --- a/extensions/notebook-renderers/src/test/stackTraceHelper.test.ts +++ b/extensions/notebook-renderers/src/test/stackTraceHelper.test.ts @@ -43,8 +43,6 @@ suite('StackTraceHelper', () => { assert.ok(formatted.indexOf('2') > 0, 'Missing frame link in ' + formatted); }); - - test('IPython stack line numbers are linkified for IPython 8.3', () => { // stack frames within functions do not list the line number, i.e. // 'Input In [1], in myfunc()' vs @@ -88,4 +86,20 @@ suite('StackTraceHelper', () => { assert.ok(!/\d<\/a>/.test(formatted), formatted); }); + test('IPython stack without line numbers are not linkified', () => { + const stack = + '\u001b[1;36m Cell \u001b[1;32mIn[6], line 1\u001b[1;36m\u001b[0m\n' + + '\u001b[1;33m print(\u001b[0m\n' + + '\u001b[1;37m ^\u001b[0m\n' + + '\u001b[1;31mSyntaxError\u001b[0m\u001b[1;31m:\u001b[0m incomplete input\n' + + // contrived examples to check for more false positives + '1 print(\n' + + 'a 1 print(\n' + + ' 1a print(\n'; + + const formattedLines = formatStackTrace(stack).split('\n'); + assert.ok(/ assert.ok(!//.test(line), 'line should not contain a link: ' + line)); + }); + }); diff --git a/extensions/package.json b/extensions/package.json index 2b68f668d5b..680720b8a54 100644 --- a/extensions/package.json +++ b/extensions/package.json @@ -4,7 +4,7 @@ "license": "MIT", "description": "Dependencies shared by all extensions", "dependencies": { - "typescript": "^5.2.2" + "typescript": "^5.3.1-rc" }, "scripts": { "postinstall": "node ./postinstall.mjs" diff --git a/extensions/theme-defaults/themes/dark_vs.json b/extensions/theme-defaults/themes/dark_vs.json index 2b9f0d5a5ab..bbd2b232db2 100644 --- a/extensions/theme-defaults/themes/dark_vs.json +++ b/extensions/theme-defaults/themes/dark_vs.json @@ -6,8 +6,8 @@ "editor.background": "#1E1E1E", "editor.foreground": "#D4D4D4", "editor.inactiveSelectionBackground": "#3A3D41", - "editorIndentGuide.background": "#404040", - "editorIndentGuide.activeBackground": "#707070", + "editorIndentGuide.background1": "#404040", + "editorIndentGuide.activeBackground1": "#707070", "editor.selectionHighlightBackground": "#ADD6FF26", "list.dropBackground": "#383B3D", "activityBarBadge.background": "#007ACC", @@ -94,7 +94,10 @@ } }, { - "scope": "entity.name.tag.css", + "scope": [ + "entity.name.tag.css", + "entity.name.tag.less" + ], "settings": { "foreground": "#d7ba7d" } @@ -108,10 +111,11 @@ { "scope": [ "entity.other.attribute-name.class.css", - "entity.other.attribute-name.class.mixin.css", + "source.css entity.other.attribute-name.class", "entity.other.attribute-name.id.css", "entity.other.attribute-name.parent-selector.css", - "entity.other.attribute-name.pseudo-class.css", + "entity.other.attribute-name.parent.less", + "source.css entity.other.attribute-name.pseudo-class", "entity.other.attribute-name.pseudo-element.css", "source.css.less entity.other.attribute-name.id", "entity.other.attribute-name.scss" @@ -308,7 +312,7 @@ "support.type.property-name", "variable.css", "variable.scss", - "variable.other.less", + "source.css.less variable.other", "source.coffee.embedded" ], "settings": { diff --git a/extensions/theme-defaults/themes/hc_black.json b/extensions/theme-defaults/themes/hc_black.json index 7b671065646..07cf7df710e 100644 --- a/extensions/theme-defaults/themes/hc_black.json +++ b/extensions/theme-defaults/themes/hc_black.json @@ -4,8 +4,8 @@ "colors": { "editor.background": "#000000", "editor.foreground": "#FFFFFF", - "editorIndentGuide.background": "#FFFFFF", - "editorIndentGuide.activeBackground": "#FFFFFF", + "editorIndentGuide.background1": "#FFFFFF", + "editorIndentGuide.activeBackground1": "#FFFFFF", "sideBarTitle.foreground": "#FFFFFF", "selection.background": "#008000", "editor.selectionBackground": "#FFFFFF", @@ -86,7 +86,10 @@ } }, { - "scope": "entity.name.tag.css", + "scope": [ + "entity.name.tag.css", + "entity.name.tag.less" + ], "settings": { "foreground": "#d7ba7d" } @@ -100,10 +103,11 @@ { "scope": [ "entity.other.attribute-name.class.css", - "entity.other.attribute-name.class.mixin.css", + "source.css entity.other.attribute-name.class", "entity.other.attribute-name.id.css", "entity.other.attribute-name.parent-selector.css", - "entity.other.attribute-name.pseudo-class.css", + "entity.other.attribute-name.parent.less", + "source.css entity.other.attribute-name.pseudo-class", "entity.other.attribute-name.pseudo-element.css", "source.css.less entity.other.attribute-name.id", "entity.other.attribute-name.scss" @@ -268,7 +272,7 @@ "support.type.property-name", "variable.css", "variable.scss", - "variable.other.less", + "source.css.less variable.other", "source.coffee.embedded" ], "settings": { diff --git a/extensions/theme-defaults/themes/hc_light.json b/extensions/theme-defaults/themes/hc_light.json index aaf0e2f9cf3..ff22daa9d3d 100644 --- a/extensions/theme-defaults/themes/hc_light.json +++ b/extensions/theme-defaults/themes/hc_light.json @@ -80,10 +80,11 @@ { "scope": [ "entity.other.attribute-name.class.css", - "entity.other.attribute-name.class.mixin.css", + "source.css entity.other.attribute-name.class", "entity.other.attribute-name.id.css", "entity.other.attribute-name.parent-selector.css", - "entity.other.attribute-name.pseudo-class.css", + "entity.other.attribute-name.parent.less", + "source.css entity.other.attribute-name.pseudo-class", "entity.other.attribute-name.pseudo-element.css", "source.css.less entity.other.attribute-name.id", "entity.other.attribute-name.scss" @@ -281,7 +282,7 @@ "support.type.property-name", "variable.css", "variable.scss", - "variable.other.less", + "source.css.less variable.other", "source.coffee.embedded" ], "settings": { diff --git a/extensions/theme-defaults/themes/light_modern.json b/extensions/theme-defaults/themes/light_modern.json index a89defee385..b37c6275c24 100644 --- a/extensions/theme-defaults/themes/light_modern.json +++ b/extensions/theme-defaults/themes/light_modern.json @@ -38,7 +38,7 @@ "editorGutter.addedBackground": "#2EA043", "editorGutter.deletedBackground": "#F85149", "editorGutter.modifiedBackground": "#005FB8", - "editorIndentGuide.background": "#D3D3D3", + "editorIndentGuide.background1": "#D3D3D3", "editorLineNumber.activeForeground": "#171184", "editorLineNumber.foreground": "#6E7681", "editorOverviewRuler.border": "#E5E5E5", diff --git a/extensions/theme-defaults/themes/light_vs.json b/extensions/theme-defaults/themes/light_vs.json index 5e2d5a7889e..80fe7c7aaa7 100644 --- a/extensions/theme-defaults/themes/light_vs.json +++ b/extensions/theme-defaults/themes/light_vs.json @@ -6,8 +6,8 @@ "editor.background": "#FFFFFF", "editor.foreground": "#000000", "editor.inactiveSelectionBackground": "#E5EBF1", - "editorIndentGuide.background": "#D3D3D3", - "editorIndentGuide.activeBackground": "#939393", + "editorIndentGuide.background1": "#D3D3D3", + "editorIndentGuide.activeBackground1": "#939393", "editor.selectionHighlightBackground": "#ADD6FF80", "editorSuggestWidget.background": "#F3F3F3", "activityBarBadge.background": "#007ACC", @@ -114,10 +114,11 @@ { "scope": [ "entity.other.attribute-name.class.css", - "entity.other.attribute-name.class.mixin.css", + "source.css entity.other.attribute-name.class", "entity.other.attribute-name.id.css", "entity.other.attribute-name.parent-selector.css", - "entity.other.attribute-name.pseudo-class.css", + "entity.other.attribute-name.parent.less", + "source.css entity.other.attribute-name.pseudo-class", "entity.other.attribute-name.pseudo-element.css", "source.css.less entity.other.attribute-name.id", "entity.other.attribute-name.scss" @@ -329,7 +330,7 @@ "support.type.property-name", "variable.css", "variable.scss", - "variable.other.less", + "source.css.less variable.other", "source.coffee.embedded" ], "settings": { diff --git a/extensions/theme-quietlight/themes/quietlight-color-theme.json b/extensions/theme-quietlight/themes/quietlight-color-theme.json index 3705ed48608..0b6cd0be740 100644 --- a/extensions/theme-quietlight/themes/quietlight-color-theme.json +++ b/extensions/theme-quietlight/themes/quietlight-color-theme.json @@ -252,7 +252,8 @@ "meta.selector", "meta.selector entity", "meta.selector entity punctuation", - "entity.name.tag.css" + "entity.name.tag.css", + "entity.name.tag.less" ], "settings": { "foreground": "#7A3E9D" diff --git a/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-color-theme.json b/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-color-theme.json index 840a1764bf1..28fb8cd7f28 100644 --- a/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-color-theme.json +++ b/extensions/theme-tomorrow-night-blue/themes/tomorrow-night-blue-color-theme.json @@ -140,7 +140,10 @@ }, { "name": "Keyword, Storage", - "scope": "keyword, storage, storage.type, entity.name.tag.css", + "scope": [ + "keyword, storage, storage.type, entity.name.tag.css", + "entity.name.tag.less" + ], "settings": { "fontStyle": "", "foreground": "#EBBBFF" diff --git a/extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts b/extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts index b0346c8572b..ca71495b345 100644 --- a/extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts +++ b/extensions/typescript-language-features/src/languageFeatures/codeLens/implementationsCodeLens.ts @@ -13,6 +13,7 @@ import * as typeConverters from '../../typeConverters'; import { ClientCapability, ITypeScriptServiceClient } from '../../typescriptService'; import { conditionalRegistration, requireGlobalConfiguration, requireSomeCapability } from '../util/dependentRegistration'; import { ReferencesCodeLens, TypeScriptBaseCodeLensProvider, getSymbolRange } from './baseCodeLensProvider'; +import { ExecutionTarget } from '../../tsServer/server'; export default class TypeScriptImplementationsCodeLensProvider extends TypeScriptBaseCodeLensProvider { @@ -22,7 +23,11 @@ export default class TypeScriptImplementationsCodeLensProvider extends TypeScrip token: vscode.CancellationToken, ): Promise { const args = typeConverters.Position.toFileLocationRequestArgs(codeLens.file, codeLens.range.start); - const response = await this.client.execute('implementation', args, token, { lowPriority: true, cancelOnResourceChange: codeLens.document }); + const response = await this.client.execute('implementation', args, token, { + lowPriority: true, + executionTarget: ExecutionTarget.Semantic, + cancelOnResourceChange: codeLens.document, + }); if (response.type !== 'response' || !response.body) { codeLens.command = response.type === 'cancelled' ? TypeScriptBaseCodeLensProvider.cancelledCommand diff --git a/extensions/typescript-language-features/src/languageFeatures/tsconfig.ts b/extensions/typescript-language-features/src/languageFeatures/tsconfig.ts index 398c8c10ee2..85fe3f6de5f 100644 --- a/extensions/typescript-language-features/src/languageFeatures/tsconfig.ts +++ b/extensions/typescript-language-features/src/languageFeatures/tsconfig.ts @@ -17,9 +17,16 @@ function mapChildren(node: jsonc.Node | undefined, f: (x: jsonc.Node) => R): } const openExtendsLinkCommandId = '_typescript.openExtendsLink'; + +enum TsConfigLinkType { + Extends, + References +} + type OpenExtendsLinkCommandArgs = { readonly resourceUri: vscode.Uri; readonly extendsValue: string; + readonly linkType: TsConfigLinkType; }; @@ -43,7 +50,7 @@ class TsconfigLinkProvider implements vscode.DocumentLinkProvider { private getExtendsLink(document: vscode.TextDocument, root: jsonc.Node): vscode.DocumentLink | undefined { const node = jsonc.findNodeAtLocation(root, ['extends']); - return node && this.tryCreateTsConfigLink(document, node); + return node && this.tryCreateTsConfigLink(document, node, TsConfigLinkType.Extends); } private getReferencesLinks(document: vscode.TextDocument, root: jsonc.Node) { @@ -51,18 +58,19 @@ class TsconfigLinkProvider implements vscode.DocumentLinkProvider { jsonc.findNodeAtLocation(root, ['references']), child => { const pathNode = jsonc.findNodeAtLocation(child, ['path']); - return pathNode && this.tryCreateTsConfigLink(document, pathNode); + return pathNode && this.tryCreateTsConfigLink(document, pathNode, TsConfigLinkType.References); }); } - private tryCreateTsConfigLink(document: vscode.TextDocument, node: jsonc.Node): vscode.DocumentLink | undefined { + private tryCreateTsConfigLink(document: vscode.TextDocument, node: jsonc.Node, linkType: TsConfigLinkType): vscode.DocumentLink | undefined { if (!this.isPathValue(node)) { return undefined; } const args: OpenExtendsLinkCommandArgs = { resourceUri: { ...document.uri.toJSON(), $mid: undefined }, - extendsValue: node.value + extendsValue: node.value, + linkType }; const link = new vscode.DocumentLink( @@ -148,17 +156,18 @@ async function resolveNodeModulesPath(baseDirUri: vscode.Uri, pathCandidates: st } } -// Reference: https://github.com/microsoft/TypeScript/blob/febfd442cdba343771f478cf433b0892f213ad2f/src/compiler/commandLineParser.ts#L3005 +// Reference Extends:https://github.com/microsoft/TypeScript/blob/febfd442cdba343771f478cf433b0892f213ad2f/src/compiler/commandLineParser.ts#L3005 +// Reference Project References: https://github.com/microsoft/TypeScript/blob/7377f5cb9db19d79a6167065b323a45611c812b5/src/compiler/tsbuild.ts#L188C1-L194C2 /** * @returns Returns undefined in case of lack of result while trying to resolve from node_modules */ -async function getTsconfigPath(baseDirUri: vscode.Uri, pathValue: string): Promise { +async function getTsconfigPath(baseDirUri: vscode.Uri, pathValue: string, linkType: TsConfigLinkType): Promise { async function resolve(absolutePath: vscode.Uri): Promise { if (absolutePath.path.endsWith('.json') || await exists(absolutePath)) { return absolutePath; } return absolutePath.with({ - path: `${absolutePath.path}.json` + path: `${absolutePath.path}${linkType === TsConfigLinkType.References ? '/tsconfig.json' : '.json'}` }); } @@ -194,8 +203,8 @@ export function register() { .flat(); return vscode.Disposable.from( - vscode.commands.registerCommand(openExtendsLinkCommandId, async ({ resourceUri, extendsValue, }: OpenExtendsLinkCommandArgs) => { - const tsconfigPath = await getTsconfigPath(Utils.dirname(vscode.Uri.from(resourceUri)), extendsValue); + vscode.commands.registerCommand(openExtendsLinkCommandId, async ({ resourceUri, extendsValue, linkType }: OpenExtendsLinkCommandArgs) => { + const tsconfigPath = await getTsconfigPath(Utils.dirname(vscode.Uri.from(resourceUri)), extendsValue, linkType); if (tsconfigPath === undefined) { vscode.window.showErrorMessage(vscode.l10n.t("Failed to resolve {0} as module", extendsValue)); return; diff --git a/extensions/vscode-api-tests/package.json b/extensions/vscode-api-tests/package.json index beb65ffb2e6..401b9a4da03 100644 --- a/extensions/vscode-api-tests/package.json +++ b/extensions/vscode-api-tests/package.json @@ -52,6 +52,7 @@ "treeViewReveal", "workspaceTrust", "telemetry", + "testingActiveProfile", "windowActivity", "interactiveUserActions" ], diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts index 561f040dedb..1d1da2658c3 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/chat.test.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import 'mocha'; -import { CancellationToken, chat, ChatAgentRequest, ChatVariableLevel, CompletionItemKind, Disposable, interactive, InteractiveProgress, InteractiveRequest, InteractiveResponseForProgress, InteractiveSession, InteractiveSessionState, Progress, ProviderResult } from 'vscode'; +import { CancellationToken, chat, ChatAgentRequest, ChatVariableLevel, Disposable, interactive, InteractiveSession, ProviderResult } from 'vscode'; import { assertNoRpc, closeAllEditors, DeferredPromise, disposeAll } from '../utils'; suite('chat', () => { @@ -22,24 +22,12 @@ suite('chat', () => { function getDeferredForRequest(): DeferredPromise { disposables.push(interactive.registerInteractiveSessionProvider('provider', { - prepareSession: (_initialState: InteractiveSessionState | undefined, _token: CancellationToken): ProviderResult => { + prepareSession: (_token: CancellationToken): ProviderResult => { return { requester: { name: 'test' }, responder: { name: 'test' }, }; }, - - provideResponseWithProgress: (_request: InteractiveRequest, _progress: Progress, _token: CancellationToken): ProviderResult => { - return null; - }, - - provideSlashCommands: (_session, _token) => { - return [{ command: 'hello', title: 'Hello', kind: CompletionItemKind.Text }]; - }, - - removeRequest: (_session: InteractiveSession, _requestId: string): void => { - throw new Error('Function not implemented.'); - } })); const deferred = new DeferredPromise(); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/interactive.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/interactive.test.ts deleted file mode 100644 index b6b5623a7fb..00000000000 --- a/extensions/vscode-api-tests/src/singlefolder-tests/interactive.test.ts +++ /dev/null @@ -1,62 +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 assert from 'assert'; -import 'mocha'; -import { CancellationToken, CompletionItemKind, Disposable, interactive, InteractiveProgress, InteractiveRequest, InteractiveResponseForProgress, InteractiveSession, InteractiveSessionState, Progress, ProviderResult } from 'vscode'; -import { assertNoRpc, closeAllEditors, DeferredPromise, disposeAll } from '../utils'; - -suite('InteractiveSessionProvider', () => { - let disposables: Disposable[] = []; - setup(async () => { - disposables = []; - }); - - teardown(async function () { - assertNoRpc(); - await closeAllEditors(); - disposeAll(disposables); - }); - - function getDeferredForRequest(): DeferredPromise { - const deferred = new DeferredPromise(); - disposables.push(interactive.registerInteractiveSessionProvider('provider', { - prepareSession: (_initialState: InteractiveSessionState | undefined, _token: CancellationToken): ProviderResult => { - return { - requester: { name: 'test' }, - responder: { name: 'test' }, - }; - }, - - provideResponseWithProgress: (request: InteractiveRequest, _progress: Progress, _token: CancellationToken): ProviderResult => { - deferred.complete(request); - return null; - }, - - provideSlashCommands: (_session, _token) => { - return [{ command: 'hello', title: 'Hello', kind: CompletionItemKind.Text }]; - }, - - removeRequest: (_session: InteractiveSession, _requestId: string): void => { - throw new Error('Function not implemented.'); - } - })); - return deferred; - } - - test('plain text query', async () => { - const deferred = getDeferredForRequest(); - interactive.sendInteractiveRequestToProvider('provider', { message: 'hello' }); - const lastResult = await deferred.p; - assert.strictEqual(lastResult.message, 'hello'); - }); - - test('slash command', async () => { - const deferred = getDeferredForRequest(); - interactive.sendInteractiveRequestToProvider('provider', { message: '/hello' }); - const lastResult = await deferred.p; - assert.strictEqual(lastResult.message, '/hello'); - }); -}); diff --git a/extensions/vscode-colorize-tests/test/colorize-results/14119_less.json b/extensions/vscode-colorize-tests/test/colorize-results/14119_less.json index befc3ca8321..38a79e12ff6 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/14119_less.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/14119_less.json @@ -1,7 +1,7 @@ [ { "c": "#", - "t": "source.css.less meta.selector.css entity.other.attribute-name.id punctuation.definition.entity.css", + "t": "source.css.less meta.selector.less entity.other.attribute-name.id.less punctuation.definition.entity.less", "r": { "dark_plus": "source.css.less entity.other.attribute-name.id: #D7BA7D", "light_plus": "source.css.less entity.other.attribute-name.id: #800000", @@ -15,7 +15,7 @@ }, { "c": "f", - "t": "source.css.less meta.selector.css entity.other.attribute-name.id", + "t": "source.css.less meta.selector.less entity.other.attribute-name.id.less", "r": { "dark_plus": "source.css.less entity.other.attribute-name.id: #D7BA7D", "light_plus": "source.css.less entity.other.attribute-name.id: #800000", @@ -29,7 +29,7 @@ }, { "c": "(", - "t": "source.css.less meta.brace.round.css", + "t": "source.css.less meta.selector.less meta.group.less punctuation.definition.group.begin.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -43,35 +43,35 @@ }, { "c": "@", - "t": "source.css.less variable.other.less punctuation.definition.variable.less", + "t": "source.css.less meta.selector.less meta.group.less meta.property-value.less variable.other.readwrite.less punctuation.definition.variable.less", "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" } }, { "c": "hm", - "t": "source.css.less variable.other.less", + "t": "source.css.less meta.selector.less meta.group.less meta.property-value.less variable.other.readwrite.less support.other.variable.less", "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" } }, { "c": ":", - "t": "source.css.less punctuation.separator.key-value.css", + "t": "source.css.less meta.selector.less meta.group.less meta.property-value.less punctuation.separator.key-value.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -85,7 +85,7 @@ }, { "c": " ", - "t": "source.css.less", + "t": "source.css.less meta.selector.less meta.group.less meta.property-value.less meta.property-value.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -99,7 +99,7 @@ }, { "c": "\"", - "t": "source.css.less string.quoted.double.css punctuation.definition.string.begin.css", + "t": "source.css.less meta.selector.less meta.group.less meta.property-value.less string.quoted.double.less punctuation.definition.string.begin.less", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -113,7 +113,7 @@ }, { "c": "broken highlighting in VS Code", - "t": "source.css.less string.quoted.double.css", + "t": "source.css.less meta.selector.less meta.group.less meta.property-value.less string.quoted.double.less", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -127,7 +127,7 @@ }, { "c": "\"", - "t": "source.css.less string.quoted.double.css punctuation.definition.string.end.css", + "t": "source.css.less meta.selector.less meta.group.less meta.property-value.less string.quoted.double.less punctuation.definition.string.end.less", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -141,7 +141,7 @@ }, { "c": ")", - "t": "source.css.less meta.brace.round.css", + "t": "source.css.less meta.selector.less meta.group.less punctuation.definition.group.end.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -155,7 +155,7 @@ }, { "c": " ", - "t": "source.css.less", + "t": "source.css.less meta.selector.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -169,7 +169,7 @@ }, { "c": "{", - "t": "source.css.less meta.property-list.css punctuation.section.property-list.begin.bracket.curly.css", + "t": "source.css.less meta.property-list.less punctuation.definition.block.begin.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -183,7 +183,7 @@ }, { "c": " ", - "t": "source.css.less meta.property-list.css", + "t": "source.css.less meta.property-list.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -197,21 +197,21 @@ }, { "c": "content", - "t": "source.css.less meta.property-list.css entity.name.tag.css", + "t": "source.css.less meta.property-list.less support.type.property-name.less", "r": { - "dark_plus": "entity.name.tag.css: #D7BA7D", - "light_plus": "entity.name.tag: #800000", - "dark_vs": "entity.name.tag.css: #D7BA7D", - "light_vs": "entity.name.tag: #800000", - "hc_black": "entity.name.tag.css: #D7BA7D", - "dark_modern": "entity.name.tag.css: #D7BA7D", - "hc_light": "entity.name.tag: #0F4A85", - "light_modern": "entity.name.tag: #800000" + "dark_plus": "support.type.property-name: #9CDCFE", + "light_plus": "support.type.property-name: #E50000", + "dark_vs": "support.type.property-name: #9CDCFE", + "light_vs": "support.type.property-name: #E50000", + "hc_black": "support.type.property-name: #D4D4D4", + "dark_modern": "support.type.property-name: #9CDCFE", + "hc_light": "support.type.property-name: #264F78", + "light_modern": "support.type.property-name: #E50000" } }, { "c": ":", - "t": "source.css.less meta.property-list.css punctuation.separator.key-value.css", + "t": "source.css.less meta.property-list.less punctuation.separator.key-value.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -225,7 +225,7 @@ }, { "c": " ", - "t": "source.css.less meta.property-list.css", + "t": "source.css.less meta.property-list.less meta.property-value.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -239,7 +239,7 @@ }, { "c": "\"", - "t": "source.css.less meta.property-list.css meta.property-value.css string.quoted.double.css punctuation.definition.string.begin.css", + "t": "source.css.less meta.property-list.less meta.property-value.less string.quoted.double.less punctuation.definition.string.begin.less", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -253,7 +253,7 @@ }, { "c": "\"", - "t": "source.css.less meta.property-list.css meta.property-value.css string.quoted.double.css punctuation.definition.string.end.css", + "t": "source.css.less meta.property-list.less meta.property-value.less string.quoted.double.less punctuation.definition.string.end.less", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -267,7 +267,7 @@ }, { "c": ";", - "t": "source.css.less meta.property-list.css punctuation.terminator.rule.css", + "t": "source.css.less meta.property-list.less punctuation.terminator.rule.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -281,7 +281,7 @@ }, { "c": "}", - "t": "source.css.less meta.property-list.css punctuation.section.property-list.end.bracket.curly.css", + "t": "source.css.less punctuation.definition.block.end.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test-cssvariables_less.json b/extensions/vscode-colorize-tests/test/colorize-results/test-cssvariables_less.json index 859e808c155..856f013541b 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test-cssvariables_less.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test-cssvariables_less.json @@ -1,35 +1,35 @@ [ { "c": ":", - "t": "source.css.less entity.other.attribute-name.pseudo-class.css punctuation.definition.entity.css", + "t": "source.css.less meta.selector.less meta.function-call.less punctuation.definition.entity.less", "r": { - "dark_plus": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.pseudo-class.css: #800000", - "dark_vs": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.pseudo-class.css: #800000", - "hc_black": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.pseudo-class.css: #0F4A85", - "light_modern": "entity.other.attribute-name.pseudo-class.css: #800000" + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" } }, { "c": "root", - "t": "source.css.less entity.other.attribute-name.pseudo-class.css", + "t": "source.css.less meta.selector.less meta.function-call.less entity.other.attribute-name.pseudo-class.less", "r": { - "dark_plus": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.pseudo-class.css: #800000", - "dark_vs": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.pseudo-class.css: #800000", - "hc_black": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.pseudo-class.css: #0F4A85", - "light_modern": "entity.other.attribute-name.pseudo-class.css: #800000" + "dark_plus": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.pseudo-class: #800000", + "dark_vs": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.pseudo-class: #800000", + "hc_black": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.pseudo-class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.pseudo-class: #800000" } }, { "c": " ", - "t": "source.css.less", + "t": "source.css.less meta.selector.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -43,7 +43,7 @@ }, { "c": "{", - "t": "source.css.less meta.property-list.css punctuation.section.property-list.begin.bracket.curly.css", + "t": "source.css.less meta.property-list.less punctuation.definition.block.begin.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -57,7 +57,7 @@ }, { "c": " ", - "t": "source.css.less meta.property-list.css", + "t": "source.css.less meta.property-list.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -71,35 +71,35 @@ }, { "c": "--", - "t": "source.css.less meta.property-list.css variable.other.less punctuation.definition.variable.less", + "t": "source.css.less meta.property-list.less support.type.custom-property.less punctuation.definition.custom-property.less", "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" + "dark_plus": "support.type: #4EC9B0", + "light_plus": "support.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.type: #4EC9B0", + "dark_modern": "support.type: #4EC9B0", + "hc_light": "support.type: #185E73", + "light_modern": "support.type: #267F99" } }, { "c": "spacing-unit", - "t": "source.css.less meta.property-list.css variable.other.less", + "t": "source.css.less meta.property-list.less support.type.custom-property.less support.type.custom-property.name.less", "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" + "dark_plus": "support.type: #4EC9B0", + "light_plus": "support.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.type: #4EC9B0", + "dark_modern": "support.type: #4EC9B0", + "hc_light": "support.type: #185E73", + "light_modern": "support.type: #267F99" } }, { "c": ":", - "t": "source.css.less meta.property-list.css punctuation.separator.key-value.css", + "t": "source.css.less meta.property-list.less punctuation.separator.key-value.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -113,7 +113,7 @@ }, { "c": " ", - "t": "source.css.less meta.property-list.css", + "t": "source.css.less meta.property-list.less meta.property-value.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -127,7 +127,7 @@ }, { "c": "6", - "t": "source.css.less meta.property-list.css meta.property-value.css constant.numeric.css", + "t": "source.css.less meta.property-list.less meta.property-value.less constant.numeric.less", "r": { "dark_plus": "constant.numeric: #B5CEA8", "light_plus": "constant.numeric: #098658", @@ -141,7 +141,7 @@ }, { "c": "px", - "t": "source.css.less meta.property-list.css meta.property-value.css constant.numeric.css keyword.other.unit.px.css", + "t": "source.css.less meta.property-list.less meta.property-value.less constant.numeric.less keyword.other.unit.less", "r": { "dark_plus": "keyword.other.unit: #B5CEA8", "light_plus": "keyword.other.unit: #098658", @@ -155,7 +155,7 @@ }, { "c": ";", - "t": "source.css.less meta.property-list.css punctuation.terminator.rule.css", + "t": "source.css.less meta.property-list.less punctuation.terminator.rule.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -169,7 +169,7 @@ }, { "c": " ", - "t": "source.css.less meta.property-list.css", + "t": "source.css.less meta.property-list.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -183,35 +183,35 @@ }, { "c": "--", - "t": "source.css.less meta.property-list.css variable.other.less punctuation.definition.variable.less", + "t": "source.css.less meta.property-list.less support.type.custom-property.less punctuation.definition.custom-property.less", "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" + "dark_plus": "support.type: #4EC9B0", + "light_plus": "support.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.type: #4EC9B0", + "dark_modern": "support.type: #4EC9B0", + "hc_light": "support.type: #185E73", + "light_modern": "support.type: #267F99" } }, { "c": "cell-padding", - "t": "source.css.less meta.property-list.css variable.other.less", + "t": "source.css.less meta.property-list.less support.type.custom-property.less support.type.custom-property.name.less", "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" + "dark_plus": "support.type: #4EC9B0", + "light_plus": "support.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.type: #4EC9B0", + "dark_modern": "support.type: #4EC9B0", + "hc_light": "support.type: #185E73", + "light_modern": "support.type: #267F99" } }, { "c": ":", - "t": "source.css.less meta.property-list.css punctuation.separator.key-value.css", + "t": "source.css.less meta.property-list.less punctuation.separator.key-value.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -225,7 +225,7 @@ }, { "c": " ", - "t": "source.css.less meta.property-list.css", + "t": "source.css.less meta.property-list.less meta.property-value.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -239,7 +239,7 @@ }, { "c": "(", - "t": "source.css.less meta.property-list.css meta.property-value.css meta.brace.round.css", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less punctuation.definition.group.begin.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -253,7 +253,7 @@ }, { "c": "4", - "t": "source.css.less meta.property-list.css meta.property-value.css constant.numeric.css", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less constant.numeric.less", "r": { "dark_plus": "constant.numeric: #B5CEA8", "light_plus": "constant.numeric: #098658", @@ -267,7 +267,7 @@ }, { "c": " ", - "t": "source.css.less meta.property-list.css meta.property-value.css", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -281,7 +281,7 @@ }, { "c": "*", - "t": "source.css.less meta.property-list.css meta.property-value.css keyword.operator.less", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less keyword.operator.arithmetic.less", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -294,8 +294,8 @@ } }, { - "c": " ", - "t": "source.css.less meta.property-list.css meta.property-value.css", + "c": " var", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -307,23 +307,9 @@ "light_modern": "default: #3B3B3B" } }, - { - "c": "var", - "t": "source.css.less meta.property-list.css meta.property-value.css meta.function.variable.css support.function.misc.css", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "dark_modern": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC", - "light_modern": "support.function: #795E26" - } - }, { "c": "(", - "t": "source.css.less meta.property-list.css meta.property-value.css meta.function.variable.css punctuation.section.function.begin.bracket.round.css", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less meta.group.less punctuation.definition.group.begin.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -336,22 +322,50 @@ } }, { - "c": "--spacing-unit", - "t": "source.css.less meta.property-list.css meta.property-value.css meta.function.variable.css variable.argument.css", + "c": "--", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less meta.group.less keyword.operator.arithmetic.less", "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "variable: #9CDCFE", - "dark_modern": "variable: #9CDCFE", - "hc_light": "variable: #001080", - "light_modern": "variable: #001080" + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4", + "dark_modern": "keyword.operator: #D4D4D4", + "hc_light": "keyword.operator: #000000", + "light_modern": "keyword.operator: #000000" } }, { - "c": ")", - "t": "source.css.less meta.property-list.css meta.property-value.css meta.function.variable.css punctuation.section.function.end.bracket.round.css", + "c": "spacing", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less meta.group.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "-", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less meta.group.less keyword.operator.arithmetic.less", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4", + "dark_modern": "keyword.operator: #D4D4D4", + "hc_light": "keyword.operator: #000000", + "light_modern": "keyword.operator: #000000" + } + }, + { + "c": "unit", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less meta.group.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -365,7 +379,21 @@ }, { "c": ")", - "t": "source.css.less meta.property-list.css meta.property-value.css meta.brace.round.css", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less meta.group.less punctuation.definition.group.end.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": ")", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less punctuation.definition.group.end.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -379,7 +407,7 @@ }, { "c": ";", - "t": "source.css.less meta.property-list.css punctuation.terminator.rule.css", + "t": "source.css.less meta.property-list.less punctuation.terminator.rule.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -393,7 +421,7 @@ }, { "c": "}", - "t": "source.css.less meta.property-list.css punctuation.section.property-list.end.bracket.curly.css", + "t": "source.css.less punctuation.definition.block.end.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -407,21 +435,21 @@ }, { "c": "body", - "t": "source.css.less entity.name.tag.css", + "t": "source.css.less meta.selector.less entity.name.tag.less", "r": { - "dark_plus": "entity.name.tag.css: #D7BA7D", + "dark_plus": "entity.name.tag.less: #D7BA7D", "light_plus": "entity.name.tag: #800000", - "dark_vs": "entity.name.tag.css: #D7BA7D", + "dark_vs": "entity.name.tag.less: #D7BA7D", "light_vs": "entity.name.tag: #800000", - "hc_black": "entity.name.tag.css: #D7BA7D", - "dark_modern": "entity.name.tag.css: #D7BA7D", + "hc_black": "entity.name.tag.less: #D7BA7D", + "dark_modern": "entity.name.tag.less: #D7BA7D", "hc_light": "entity.name.tag: #0F4A85", "light_modern": "entity.name.tag: #800000" } }, { "c": " ", - "t": "source.css.less", + "t": "source.css.less meta.selector.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -435,7 +463,7 @@ }, { "c": "{", - "t": "source.css.less meta.property-list.css punctuation.section.property-list.begin.bracket.curly.css", + "t": "source.css.less meta.property-list.less punctuation.definition.block.begin.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -449,7 +477,7 @@ }, { "c": " ", - "t": "source.css.less meta.property-list.css", + "t": "source.css.less meta.property-list.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -463,7 +491,7 @@ }, { "c": "padding-left", - "t": "source.css.less meta.property-list.css support.type.property-name.css", + "t": "source.css.less meta.property-list.less support.type.property-name.less", "r": { "dark_plus": "support.type.property-name: #9CDCFE", "light_plus": "support.type.property-name: #E50000", @@ -477,7 +505,7 @@ }, { "c": ":", - "t": "source.css.less meta.property-list.css punctuation.separator.key-value.css", + "t": "source.css.less meta.property-list.less punctuation.separator.key-value.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -491,7 +519,7 @@ }, { "c": " ", - "t": "source.css.less meta.property-list.css", + "t": "source.css.less meta.property-list.less meta.property-value.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -505,7 +533,7 @@ }, { "c": "calc", - "t": "source.css.less meta.property-list.css meta.property-value.css support.function.any-method.builtin.less", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less support.function.calc.less", "r": { "dark_plus": "support.function: #DCDCAA", "light_plus": "support.function: #795E26", @@ -519,7 +547,7 @@ }, { "c": "(", - "t": "source.css.less meta.property-list.css meta.property-value.css meta.brace.round.css", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less punctuation.definition.group.begin.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -533,7 +561,7 @@ }, { "c": "4", - "t": "source.css.less meta.property-list.css meta.property-value.css constant.numeric.css", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less constant.numeric.less", "r": { "dark_plus": "constant.numeric: #B5CEA8", "light_plus": "constant.numeric: #098658", @@ -547,7 +575,7 @@ }, { "c": " ", - "t": "source.css.less meta.property-list.css meta.property-value.css", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -561,7 +589,7 @@ }, { "c": "*", - "t": "source.css.less meta.property-list.css meta.property-value.css keyword.operator.less", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less keyword.operator.arithmetic.less", "r": { "dark_plus": "keyword.operator: #D4D4D4", "light_plus": "keyword.operator: #000000", @@ -575,7 +603,7 @@ }, { "c": " ", - "t": "source.css.less meta.property-list.css meta.property-value.css", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -589,7 +617,7 @@ }, { "c": "var", - "t": "source.css.less meta.property-list.css meta.property-value.css meta.function.variable.css support.function.misc.css", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less meta.function-call.less support.function.var.less", "r": { "dark_plus": "support.function: #DCDCAA", "light_plus": "support.function: #795E26", @@ -603,7 +631,7 @@ }, { "c": "(", - "t": "source.css.less meta.property-list.css meta.property-value.css meta.function.variable.css punctuation.section.function.begin.bracket.round.css", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less meta.function-call.less punctuation.definition.group.begin.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -616,22 +644,36 @@ } }, { - "c": "--spacing-unit", - "t": "source.css.less meta.property-list.css meta.property-value.css meta.function.variable.css variable.argument.css", + "c": "--", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less meta.function-call.less support.type.custom-property.less punctuation.definition.custom-property.less", "r": { - "dark_plus": "variable: #9CDCFE", - "light_plus": "variable: #001080", + "dark_plus": "support.type: #4EC9B0", + "light_plus": "support.type: #267F99", "dark_vs": "default: #D4D4D4", "light_vs": "default: #000000", - "hc_black": "variable: #9CDCFE", - "dark_modern": "variable: #9CDCFE", - "hc_light": "variable: #001080", - "light_modern": "variable: #001080" + "hc_black": "support.type: #4EC9B0", + "dark_modern": "support.type: #4EC9B0", + "hc_light": "support.type: #185E73", + "light_modern": "support.type: #267F99" + } + }, + { + "c": "spacing-unit", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less meta.function-call.less support.type.custom-property.less support.type.custom-property.name.less", + "r": { + "dark_plus": "support.type: #4EC9B0", + "light_plus": "support.type: #267F99", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.type: #4EC9B0", + "dark_modern": "support.type: #4EC9B0", + "hc_light": "support.type: #185E73", + "light_modern": "support.type: #267F99" } }, { "c": ",", - "t": "source.css.less meta.property-list.css meta.property-value.css meta.function.variable.css punctuation.separator.list.comma.css", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less meta.function-call.less punctuation.separator.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -644,50 +686,8 @@ } }, { - "c": " ", - "t": "source.css.less meta.property-list.css meta.property-value.css meta.function.variable.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "5", - "t": "source.css.less meta.property-list.css meta.property-value.css meta.function.variable.css constant.numeric.css", - "r": { - "dark_plus": "constant.numeric: #B5CEA8", - "light_plus": "constant.numeric: #098658", - "dark_vs": "constant.numeric: #B5CEA8", - "light_vs": "constant.numeric: #098658", - "hc_black": "constant.numeric: #B5CEA8", - "dark_modern": "constant.numeric: #B5CEA8", - "hc_light": "constant.numeric: #096D48", - "light_modern": "constant.numeric: #098658" - } - }, - { - "c": "px", - "t": "source.css.less meta.property-list.css meta.property-value.css meta.function.variable.css constant.numeric.css keyword.other.unit.px.css", - "r": { - "dark_plus": "keyword.other.unit: #B5CEA8", - "light_plus": "keyword.other.unit: #098658", - "dark_vs": "keyword.other.unit: #B5CEA8", - "light_vs": "keyword.other.unit: #098658", - "hc_black": "keyword.other.unit: #B5CEA8", - "dark_modern": "keyword.other.unit: #B5CEA8", - "hc_light": "keyword.other.unit: #096D48", - "light_modern": "keyword.other.unit: #098658" - } - }, - { - "c": ")", - "t": "source.css.less meta.property-list.css meta.property-value.css meta.function.variable.css punctuation.section.function.end.bracket.round.css", + "c": " 5px", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less meta.function-call.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -701,7 +701,21 @@ }, { "c": ")", - "t": "source.css.less meta.property-list.css meta.property-value.css meta.brace.round.css", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less meta.function-call.less punctuation.definition.group.end.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": ")", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less punctuation.definition.group.end.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -715,7 +729,7 @@ }, { "c": ";", - "t": "source.css.less meta.property-list.css punctuation.terminator.rule.css", + "t": "source.css.less meta.property-list.less punctuation.terminator.rule.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -729,7 +743,7 @@ }, { "c": "}", - "t": "source.css.less meta.property-list.css punctuation.section.property-list.end.bracket.curly.css", + "t": "source.css.less punctuation.definition.block.end.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test-cssvariables_scss.json b/extensions/vscode-colorize-tests/test/colorize-results/test-cssvariables_scss.json index f5cddc27d06..ebeff26f98f 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test-cssvariables_scss.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test-cssvariables_scss.json @@ -3,28 +3,28 @@ "c": ":", "t": "source.css.scss entity.other.attribute-name.pseudo-class.css punctuation.definition.entity.css", "r": { - "dark_plus": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.pseudo-class.css: #800000", - "dark_vs": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.pseudo-class.css: #800000", - "hc_black": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.pseudo-class.css: #0F4A85", - "light_modern": "entity.other.attribute-name.pseudo-class.css: #800000" + "dark_plus": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.pseudo-class: #800000", + "dark_vs": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.pseudo-class: #800000", + "hc_black": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.pseudo-class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.pseudo-class: #800000" } }, { "c": "root", "t": "source.css.scss entity.other.attribute-name.pseudo-class.css", "r": { - "dark_plus": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.pseudo-class.css: #800000", - "dark_vs": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.pseudo-class.css: #800000", - "hc_black": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.pseudo-class.css: #0F4A85", - "light_modern": "entity.other.attribute-name.pseudo-class.css: #800000" + "dark_plus": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.pseudo-class: #800000", + "dark_vs": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.pseudo-class: #800000", + "hc_black": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.pseudo-class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.pseudo-class: #800000" } }, { diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test-variables_css.json b/extensions/vscode-colorize-tests/test/colorize-results/test-variables_css.json index 8819269523a..b87252f2279 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test-variables_css.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test-variables_css.json @@ -3,28 +3,28 @@ "c": ":", "t": "source.css meta.selector.css entity.other.attribute-name.pseudo-class.css punctuation.definition.entity.css", "r": { - "dark_plus": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.pseudo-class.css: #800000", - "dark_vs": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.pseudo-class.css: #800000", - "hc_black": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.pseudo-class.css: #0F4A85", - "light_modern": "entity.other.attribute-name.pseudo-class.css: #800000" + "dark_plus": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.pseudo-class: #800000", + "dark_vs": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.pseudo-class: #800000", + "hc_black": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.pseudo-class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.pseudo-class: #800000" } }, { "c": "root", "t": "source.css meta.selector.css entity.other.attribute-name.pseudo-class.css", "r": { - "dark_plus": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.pseudo-class.css: #800000", - "dark_vs": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.pseudo-class.css: #800000", - "hc_black": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.pseudo-class.css: #0F4A85", - "light_modern": "entity.other.attribute-name.pseudo-class.css: #800000" + "dark_plus": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.pseudo-class: #800000", + "dark_vs": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.pseudo-class: #800000", + "hc_black": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.pseudo-class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.pseudo-class: #800000" } }, { diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_css.json b/extensions/vscode-colorize-tests/test/colorize-results/test_css.json index 78a5ca00d07..0510fc82cd2 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_css.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_css.json @@ -3601,28 +3601,28 @@ "c": ":", "t": "source.css meta.selector.css entity.other.attribute-name.pseudo-class.css punctuation.definition.entity.css", "r": { - "dark_plus": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.pseudo-class.css: #800000", - "dark_vs": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.pseudo-class.css: #800000", - "hc_black": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.pseudo-class.css: #0F4A85", - "light_modern": "entity.other.attribute-name.pseudo-class.css: #800000" + "dark_plus": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.pseudo-class: #800000", + "dark_vs": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.pseudo-class: #800000", + "hc_black": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.pseudo-class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.pseudo-class: #800000" } }, { "c": "link", "t": "source.css meta.selector.css entity.other.attribute-name.pseudo-class.css", "r": { - "dark_plus": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.pseudo-class.css: #800000", - "dark_vs": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.pseudo-class.css: #800000", - "hc_black": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.pseudo-class.css: #0F4A85", - "light_modern": "entity.other.attribute-name.pseudo-class.css: #800000" + "dark_plus": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.pseudo-class: #800000", + "dark_vs": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.pseudo-class: #800000", + "hc_black": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.pseudo-class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.pseudo-class: #800000" } }, { @@ -3951,28 +3951,28 @@ "c": ":", "t": "source.css meta.selector.css entity.other.attribute-name.pseudo-class.css punctuation.definition.entity.css", "r": { - "dark_plus": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.pseudo-class.css: #800000", - "dark_vs": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.pseudo-class.css: #800000", - "hc_black": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.pseudo-class.css: #0F4A85", - "light_modern": "entity.other.attribute-name.pseudo-class.css: #800000" + "dark_plus": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.pseudo-class: #800000", + "dark_vs": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.pseudo-class: #800000", + "hc_black": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.pseudo-class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.pseudo-class: #800000" } }, { "c": "visited", "t": "source.css meta.selector.css entity.other.attribute-name.pseudo-class.css", "r": { - "dark_plus": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.pseudo-class.css: #800000", - "dark_vs": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.pseudo-class.css: #800000", - "hc_black": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.pseudo-class.css: #0F4A85", - "light_modern": "entity.other.attribute-name.pseudo-class.css: #800000" + "dark_plus": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.pseudo-class: #800000", + "dark_vs": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.pseudo-class: #800000", + "hc_black": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.pseudo-class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.pseudo-class: #800000" } }, { @@ -4385,28 +4385,28 @@ "c": ":", "t": "source.css meta.selector.css entity.other.attribute-name.pseudo-class.css punctuation.definition.entity.css", "r": { - "dark_plus": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.pseudo-class.css: #800000", - "dark_vs": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.pseudo-class.css: #800000", - "hc_black": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.pseudo-class.css: #0F4A85", - "light_modern": "entity.other.attribute-name.pseudo-class.css: #800000" + "dark_plus": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.pseudo-class: #800000", + "dark_vs": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.pseudo-class: #800000", + "hc_black": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.pseudo-class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.pseudo-class: #800000" } }, { "c": "hover", "t": "source.css meta.selector.css entity.other.attribute-name.pseudo-class.css", "r": { - "dark_plus": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.pseudo-class.css: #800000", - "dark_vs": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.pseudo-class.css: #800000", - "hc_black": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.pseudo-class.css: #0F4A85", - "light_modern": "entity.other.attribute-name.pseudo-class.css: #800000" + "dark_plus": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.pseudo-class: #800000", + "dark_vs": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.pseudo-class: #800000", + "hc_black": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.pseudo-class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.pseudo-class: #800000" } }, { @@ -4441,28 +4441,28 @@ "c": ":", "t": "source.css meta.selector.css entity.other.attribute-name.pseudo-class.css punctuation.definition.entity.css", "r": { - "dark_plus": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.pseudo-class.css: #800000", - "dark_vs": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.pseudo-class.css: #800000", - "hc_black": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.pseudo-class.css: #0F4A85", - "light_modern": "entity.other.attribute-name.pseudo-class.css: #800000" + "dark_plus": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.pseudo-class: #800000", + "dark_vs": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.pseudo-class: #800000", + "hc_black": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.pseudo-class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.pseudo-class: #800000" } }, { "c": "focus", "t": "source.css meta.selector.css entity.other.attribute-name.pseudo-class.css", "r": { - "dark_plus": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.pseudo-class.css: #800000", - "dark_vs": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.pseudo-class.css: #800000", - "hc_black": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.pseudo-class.css: #0F4A85", - "light_modern": "entity.other.attribute-name.pseudo-class.css: #800000" + "dark_plus": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.pseudo-class: #800000", + "dark_vs": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.pseudo-class: #800000", + "hc_black": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.pseudo-class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.pseudo-class: #800000" } }, { @@ -4497,28 +4497,28 @@ "c": ":", "t": "source.css meta.selector.css entity.other.attribute-name.pseudo-class.css punctuation.definition.entity.css", "r": { - "dark_plus": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.pseudo-class.css: #800000", - "dark_vs": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.pseudo-class.css: #800000", - "hc_black": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.pseudo-class.css: #0F4A85", - "light_modern": "entity.other.attribute-name.pseudo-class.css: #800000" + "dark_plus": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.pseudo-class: #800000", + "dark_vs": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.pseudo-class: #800000", + "hc_black": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.pseudo-class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.pseudo-class: #800000" } }, { "c": "active", "t": "source.css meta.selector.css entity.other.attribute-name.pseudo-class.css", "r": { - "dark_plus": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.pseudo-class.css: #800000", - "dark_vs": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.pseudo-class.css: #800000", - "hc_black": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.pseudo-class.css: #0F4A85", - "light_modern": "entity.other.attribute-name.pseudo-class.css: #800000" + "dark_plus": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.pseudo-class: #800000", + "dark_vs": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.pseudo-class: #800000", + "hc_black": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.pseudo-class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.pseudo-class: #800000" } }, { @@ -8879,28 +8879,28 @@ "c": ":", "t": "source.css meta.selector.css entity.other.attribute-name.pseudo-class.css punctuation.definition.entity.css", "r": { - "dark_plus": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.pseudo-class.css: #800000", - "dark_vs": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.pseudo-class.css: #800000", - "hc_black": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.pseudo-class.css: #0F4A85", - "light_modern": "entity.other.attribute-name.pseudo-class.css: #800000" + "dark_plus": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.pseudo-class: #800000", + "dark_vs": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.pseudo-class: #800000", + "hc_black": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.pseudo-class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.pseudo-class: #800000" } }, { "c": "link", "t": "source.css meta.selector.css entity.other.attribute-name.pseudo-class.css", "r": { - "dark_plus": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.pseudo-class.css: #800000", - "dark_vs": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.pseudo-class.css: #800000", - "hc_black": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.pseudo-class.css: #0F4A85", - "light_modern": "entity.other.attribute-name.pseudo-class.css: #800000" + "dark_plus": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.pseudo-class: #800000", + "dark_vs": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.pseudo-class: #800000", + "hc_black": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.pseudo-class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.pseudo-class: #800000" } }, { @@ -8963,28 +8963,28 @@ "c": ":", "t": "source.css meta.selector.css entity.other.attribute-name.pseudo-class.css punctuation.definition.entity.css", "r": { - "dark_plus": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.pseudo-class.css: #800000", - "dark_vs": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.pseudo-class.css: #800000", - "hc_black": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.pseudo-class.css: #0F4A85", - "light_modern": "entity.other.attribute-name.pseudo-class.css: #800000" + "dark_plus": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.pseudo-class: #800000", + "dark_vs": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.pseudo-class: #800000", + "hc_black": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.pseudo-class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.pseudo-class: #800000" } }, { "c": "visited", "t": "source.css meta.selector.css entity.other.attribute-name.pseudo-class.css", "r": { - "dark_plus": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.pseudo-class.css: #800000", - "dark_vs": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.pseudo-class.css: #800000", - "hc_black": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.pseudo-class.css: #0F4A85", - "light_modern": "entity.other.attribute-name.pseudo-class.css: #800000" + "dark_plus": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.pseudo-class: #800000", + "dark_vs": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.pseudo-class: #800000", + "hc_black": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.pseudo-class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.pseudo-class: #800000" } }, { @@ -10447,28 +10447,28 @@ "c": ":", "t": "source.css meta.selector.css entity.other.attribute-name.pseudo-class.css punctuation.definition.entity.css", "r": { - "dark_plus": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.pseudo-class.css: #800000", - "dark_vs": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.pseudo-class.css: #800000", - "hc_black": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.pseudo-class.css: #0F4A85", - "light_modern": "entity.other.attribute-name.pseudo-class.css: #800000" + "dark_plus": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.pseudo-class: #800000", + "dark_vs": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.pseudo-class: #800000", + "hc_black": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.pseudo-class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.pseudo-class: #800000" } }, { "c": "link", "t": "source.css meta.selector.css entity.other.attribute-name.pseudo-class.css", "r": { - "dark_plus": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.pseudo-class.css: #800000", - "dark_vs": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.pseudo-class.css: #800000", - "hc_black": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.pseudo-class.css: #0F4A85", - "light_modern": "entity.other.attribute-name.pseudo-class.css: #800000" + "dark_plus": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.pseudo-class: #800000", + "dark_vs": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.pseudo-class: #800000", + "hc_black": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.pseudo-class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.pseudo-class: #800000" } }, { @@ -10699,28 +10699,28 @@ "c": ":", "t": "source.css meta.selector.css entity.other.attribute-name.pseudo-class.css punctuation.definition.entity.css", "r": { - "dark_plus": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.pseudo-class.css: #800000", - "dark_vs": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.pseudo-class.css: #800000", - "hc_black": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.pseudo-class.css: #0F4A85", - "light_modern": "entity.other.attribute-name.pseudo-class.css: #800000" + "dark_plus": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.pseudo-class: #800000", + "dark_vs": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.pseudo-class: #800000", + "hc_black": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.pseudo-class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.pseudo-class: #800000" } }, { "c": "visited", "t": "source.css meta.selector.css entity.other.attribute-name.pseudo-class.css", "r": { - "dark_plus": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.pseudo-class.css: #800000", - "dark_vs": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.pseudo-class.css: #800000", - "hc_black": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.pseudo-class.css: #0F4A85", - "light_modern": "entity.other.attribute-name.pseudo-class.css: #800000" + "dark_plus": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.pseudo-class: #800000", + "dark_vs": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.pseudo-class: #800000", + "hc_black": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.pseudo-class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.pseudo-class: #800000" } }, { diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_less.json b/extensions/vscode-colorize-tests/test/colorize-results/test_less.json index 6937f0e26dc..3ab58551bd3 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_less.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_less.json @@ -1,7 +1,7 @@ [ { "c": "@", - "t": "source.css.less meta.at-rule.import.css keyword.control.at-rule.import.less punctuation.definition.keyword.less", + "t": "source.css.less meta.at-rule.import.less keyword.control.at-rule.import.less punctuation.definition.keyword.less", "r": { "dark_plus": "keyword.control: #C586C0", "light_plus": "keyword.control: #AF00DB", @@ -15,7 +15,7 @@ }, { "c": "import", - "t": "source.css.less meta.at-rule.import.css keyword.control.at-rule.import.less", + "t": "source.css.less meta.at-rule.import.less keyword.control.at-rule.import.less", "r": { "dark_plus": "keyword.control: #C586C0", "light_plus": "keyword.control: #AF00DB", @@ -29,7 +29,7 @@ }, { "c": " ", - "t": "source.css.less meta.at-rule.import.css", + "t": "source.css.less meta.at-rule.import.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -43,7 +43,7 @@ }, { "c": "\"", - "t": "source.css.less meta.at-rule.import.css string.quoted.double.css punctuation.definition.string.begin.css", + "t": "source.css.less meta.at-rule.import.less string.quoted.double.less punctuation.definition.string.begin.less", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -57,7 +57,7 @@ }, { "c": "mystyle.css", - "t": "source.css.less meta.at-rule.import.css string.quoted.double.css", + "t": "source.css.less meta.at-rule.import.less string.quoted.double.less", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -71,7 +71,7 @@ }, { "c": "\"", - "t": "source.css.less meta.at-rule.import.css string.quoted.double.css punctuation.definition.string.end.css", + "t": "source.css.less meta.at-rule.import.less string.quoted.double.less punctuation.definition.string.end.less", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -85,7 +85,7 @@ }, { "c": ";", - "t": "source.css.less meta.at-rule.import.css punctuation.terminator.rule.css", + "t": "source.css.less meta.at-rule.import.less punctuation.terminator.rule.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -99,7 +99,7 @@ }, { "c": "@", - "t": "source.css.less meta.at-rule.import.css keyword.control.at-rule.import.less punctuation.definition.keyword.less", + "t": "source.css.less meta.at-rule.import.less keyword.control.at-rule.import.less punctuation.definition.keyword.less", "r": { "dark_plus": "keyword.control: #C586C0", "light_plus": "keyword.control: #AF00DB", @@ -113,7 +113,7 @@ }, { "c": "import", - "t": "source.css.less meta.at-rule.import.css keyword.control.at-rule.import.less", + "t": "source.css.less meta.at-rule.import.less keyword.control.at-rule.import.less", "r": { "dark_plus": "keyword.control: #C586C0", "light_plus": "keyword.control: #AF00DB", @@ -126,8 +126,8 @@ } }, { - "c": " url", - "t": "source.css.less meta.at-rule.import.css", + "c": " ", + "t": "source.css.less meta.at-rule.import.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -139,9 +139,23 @@ "light_modern": "default: #3B3B3B" } }, + { + "c": "url", + "t": "source.css.less meta.at-rule.import.less meta.function-call.less support.function.url.less", + "r": { + "dark_plus": "support.function: #DCDCAA", + "light_plus": "support.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.function: #DCDCAA", + "dark_modern": "support.function: #DCDCAA", + "hc_light": "support.function: #5E2CBC", + "light_modern": "support.function: #795E26" + } + }, { "c": "(", - "t": "source.css.less meta.at-rule.import.css meta.brace.round.css", + "t": "source.css.less meta.at-rule.import.less meta.function-call.less punctuation.definition.group.begin.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -155,7 +169,7 @@ }, { "c": "\"", - "t": "source.css.less meta.at-rule.import.css string.quoted.double.css punctuation.definition.string.begin.css", + "t": "source.css.less meta.at-rule.import.less meta.function-call.less string.quoted.double.less punctuation.definition.string.begin.less", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -169,7 +183,7 @@ }, { "c": "mystyle.css", - "t": "source.css.less meta.at-rule.import.css string.quoted.double.css", + "t": "source.css.less meta.at-rule.import.less meta.function-call.less string.quoted.double.less", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -183,7 +197,7 @@ }, { "c": "\"", - "t": "source.css.less meta.at-rule.import.css string.quoted.double.css punctuation.definition.string.end.css", + "t": "source.css.less meta.at-rule.import.less meta.function-call.less string.quoted.double.less punctuation.definition.string.end.less", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -197,7 +211,7 @@ }, { "c": ")", - "t": "source.css.less meta.at-rule.import.css meta.brace.round.css", + "t": "source.css.less meta.at-rule.import.less meta.function-call.less punctuation.definition.group.end.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -211,7 +225,7 @@ }, { "c": ";", - "t": "source.css.less meta.at-rule.import.css punctuation.terminator.rule.css", + "t": "source.css.less meta.at-rule.import.less punctuation.terminator.rule.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -225,7 +239,7 @@ }, { "c": "@", - "t": "source.css.less meta.at-rule.import.css keyword.control.at-rule.import.less punctuation.definition.keyword.less", + "t": "source.css.less meta.at-rule.import.less keyword.control.at-rule.import.less punctuation.definition.keyword.less", "r": { "dark_plus": "keyword.control: #C586C0", "light_plus": "keyword.control: #AF00DB", @@ -239,7 +253,7 @@ }, { "c": "import", - "t": "source.css.less meta.at-rule.import.css keyword.control.at-rule.import.less", + "t": "source.css.less meta.at-rule.import.less keyword.control.at-rule.import.less", "r": { "dark_plus": "keyword.control: #C586C0", "light_plus": "keyword.control: #AF00DB", @@ -252,8 +266,8 @@ } }, { - "c": " url", - "t": "source.css.less meta.at-rule.import.css", + "c": " ", + "t": "source.css.less meta.at-rule.import.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -265,9 +279,23 @@ "light_modern": "default: #3B3B3B" } }, + { + "c": "url", + "t": "source.css.less meta.at-rule.import.less meta.function-call.less support.function.url.less", + "r": { + "dark_plus": "support.function: #DCDCAA", + "light_plus": "support.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.function: #DCDCAA", + "dark_modern": "support.function: #DCDCAA", + "hc_light": "support.function: #5E2CBC", + "light_modern": "support.function: #795E26" + } + }, { "c": "(", - "t": "source.css.less meta.at-rule.import.css meta.brace.round.css", + "t": "source.css.less meta.at-rule.import.less meta.function-call.less punctuation.definition.group.begin.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -281,7 +309,7 @@ }, { "c": "\"", - "t": "source.css.less meta.at-rule.import.css string.quoted.double.css punctuation.definition.string.begin.css", + "t": "source.css.less meta.at-rule.import.less meta.function-call.less string.quoted.double.less punctuation.definition.string.begin.less", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -295,7 +323,7 @@ }, { "c": "bluish.css", - "t": "source.css.less meta.at-rule.import.css string.quoted.double.css", + "t": "source.css.less meta.at-rule.import.less meta.function-call.less string.quoted.double.less", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -309,7 +337,7 @@ }, { "c": "\"", - "t": "source.css.less meta.at-rule.import.css string.quoted.double.css punctuation.definition.string.end.css", + "t": "source.css.less meta.at-rule.import.less meta.function-call.less string.quoted.double.less punctuation.definition.string.end.less", "r": { "dark_plus": "string: #CE9178", "light_plus": "string: #A31515", @@ -323,7 +351,7 @@ }, { "c": ")", - "t": "source.css.less meta.at-rule.import.css meta.brace.round.css", + "t": "source.css.less meta.at-rule.import.less meta.function-call.less punctuation.definition.group.end.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -336,8 +364,8 @@ } }, { - "c": " projection", - "t": "source.css.less meta.at-rule.import.css", + "c": " ", + "t": "source.css.less meta.at-rule.import.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -349,9 +377,23 @@ "light_modern": "default: #3B3B3B" } }, + { + "c": "projection", + "t": "source.css.less meta.at-rule.import.less support.constant.media.less", + "r": { + "dark_plus": "support.constant.media: #CE9178", + "light_plus": "support.constant.media: #0451A5", + "dark_vs": "default: #D4D4D4", + "light_vs": "support.constant.media: #0451A5", + "hc_black": "support.constant.media: #CE9178", + "dark_modern": "support.constant.media: #CE9178", + "hc_light": "support.constant.media: #0451A5", + "light_modern": "support.constant.media: #0451A5" + } + }, { "c": ",", - "t": "source.css.less meta.at-rule.import.css punctuation.separator.list.comma.css", + "t": "source.css.less meta.at-rule.import.less punctuation.definition.arbitrary-repetition.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -364,8 +406,8 @@ } }, { - "c": " tv", - "t": "source.css.less meta.at-rule.import.css", + "c": " ", + "t": "source.css.less meta.at-rule.import.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -377,9 +419,23 @@ "light_modern": "default: #3B3B3B" } }, + { + "c": "tv", + "t": "source.css.less meta.at-rule.import.less support.constant.media.less", + "r": { + "dark_plus": "support.constant.media: #CE9178", + "light_plus": "support.constant.media: #0451A5", + "dark_vs": "default: #D4D4D4", + "light_vs": "support.constant.media: #0451A5", + "hc_black": "support.constant.media: #CE9178", + "dark_modern": "support.constant.media: #CE9178", + "hc_light": "support.constant.media: #0451A5", + "light_modern": "support.constant.media: #0451A5" + } + }, { "c": ";", - "t": "source.css.less meta.at-rule.import.css punctuation.terminator.rule.css", + "t": "source.css.less meta.at-rule.import.less punctuation.terminator.rule.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -393,35 +449,35 @@ }, { "c": "@", - "t": "source.css.less variable.other.less punctuation.definition.variable.less", + "t": "source.css.less meta.property-value.less variable.other.readwrite.less punctuation.definition.variable.less", "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" } }, { "c": "base", - "t": "source.css.less variable.other.less", + "t": "source.css.less meta.property-value.less variable.other.readwrite.less support.other.variable.less", "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" } }, { "c": ":", - "t": "source.css.less punctuation.separator.key-value.css", + "t": "source.css.less meta.property-value.less punctuation.separator.key-value.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -435,7 +491,7 @@ }, { "c": " ", - "t": "source.css.less", + "t": "source.css.less meta.property-value.less meta.property-value.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -448,1114 +504,36 @@ } }, { - "c": "#f938ab", - "t": "source.css.less constant.other.rgb-value.css", + "c": "#", + "t": "source.css.less meta.property-value.less constant.other.color.rgb-value.less punctuation.definition.constant.less", "r": { - "dark_plus": "constant.other.rgb-value: #CE9178", - "light_plus": "constant.other.rgb-value: #0451A5", + "dark_plus": "constant.other.color.rgb-value: #CE9178", + "light_plus": "constant.other.color.rgb-value: #0451A5", "dark_vs": "default: #D4D4D4", - "light_vs": "constant.other.rgb-value: #0451A5", - "hc_black": "constant.other.rgb-value: #CE9178", - "dark_modern": "constant.other.rgb-value: #CE9178", - "hc_light": "constant.other.rgb-value: #0451A5", - "light_modern": "constant.other.rgb-value: #0451A5" + "light_vs": "constant.other.color.rgb-value: #0451A5", + "hc_black": "constant.other.color.rgb-value: #CE9178", + "dark_modern": "constant.other.color.rgb-value: #CE9178", + "hc_light": "constant.other.color.rgb-value: #0451A5", + "light_modern": "constant.other.color.rgb-value: #0451A5" + } + }, + { + "c": "f938ab", + "t": "source.css.less meta.property-value.less constant.other.color.rgb-value.less", + "r": { + "dark_plus": "constant.other.color.rgb-value: #CE9178", + "light_plus": "constant.other.color.rgb-value: #0451A5", + "dark_vs": "default: #D4D4D4", + "light_vs": "constant.other.color.rgb-value: #0451A5", + "hc_black": "constant.other.color.rgb-value: #CE9178", + "dark_modern": "constant.other.color.rgb-value: #CE9178", + "hc_light": "constant.other.color.rgb-value: #0451A5", + "light_modern": "constant.other.color.rgb-value: #0451A5" } }, { "c": ";", - "t": "source.css.less punctuation.terminator.rule.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": ".box-shadow", - "t": "source.css.less entity.other.attribute-name.class.mixin.css", - "r": { - "dark_plus": "entity.other.attribute-name.class.mixin.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.class.mixin.css: #800000", - "dark_vs": "entity.other.attribute-name.class.mixin.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.class.mixin.css: #800000", - "hc_black": "entity.other.attribute-name.class.mixin.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.class.mixin.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.class.mixin.css: #0F4A85", - "light_modern": "entity.other.attribute-name.class.mixin.css: #800000" - } - }, - { - "c": "(", - "t": "source.css.less meta.brace.round.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "@", - "t": "source.css.less variable.other.less punctuation.definition.variable.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": "style", - "t": "source.css.less variable.other.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": ",", - "t": "source.css.less punctuation.separator.list.comma.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": " ", - "t": "source.css.less", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "@", - "t": "source.css.less variable.other.less punctuation.definition.variable.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": "c", - "t": "source.css.less variable.other.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": ")", - "t": "source.css.less meta.brace.round.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": " ", - "t": "source.css.less", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "when", - "t": "source.css.less keyword.control.logical.operator.less", - "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", - "dark_vs": "keyword.control: #569CD6", - "light_vs": "keyword.control: #0000FF", - "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", - "hc_light": "keyword.control: #B5200D", - "light_modern": "keyword.control: #AF00DB" - } - }, - { - "c": " ", - "t": "source.css.less", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "(", - "t": "source.css.less meta.brace.round.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "iscolor", - "t": "source.css.less support.function.type-checking.less", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "dark_modern": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC", - "light_modern": "support.function: #795E26" - } - }, - { - "c": "(", - "t": "source.css.less meta.brace.round.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "@", - "t": "source.css.less variable.other.less punctuation.definition.variable.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": "c", - "t": "source.css.less variable.other.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": "))", - "t": "source.css.less meta.brace.round.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": " ", - "t": "source.css.less", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "{", - "t": "source.css.less meta.property-list.css punctuation.section.property-list.begin.bracket.curly.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "\t", - "t": "source.css.less meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "border-radius", - "t": "source.css.less meta.property-list.css support.type.property-name.css", - "r": { - "dark_plus": "support.type.property-name: #9CDCFE", - "light_plus": "support.type.property-name: #E50000", - "dark_vs": "support.type.property-name: #9CDCFE", - "light_vs": "support.type.property-name: #E50000", - "hc_black": "support.type.property-name: #D4D4D4", - "dark_modern": "support.type.property-name: #9CDCFE", - "hc_light": "support.type.property-name: #264F78", - "light_modern": "support.type.property-name: #E50000" - } - }, - { - "c": ":", - "t": "source.css.less meta.property-list.css punctuation.separator.key-value.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "@", - "t": "source.css.less meta.property-list.css meta.property-value.css variable.other.less punctuation.definition.variable.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": "style", - "t": "source.css.less meta.property-list.css meta.property-value.css variable.other.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css meta.property-value.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "@", - "t": "source.css.less meta.property-list.css meta.property-value.css variable.other.less punctuation.definition.variable.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": "c", - "t": "source.css.less meta.property-list.css meta.property-value.css variable.other.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": ";", - "t": "source.css.less meta.property-list.css punctuation.terminator.rule.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "}", - "t": "source.css.less meta.property-list.css punctuation.section.property-list.end.bracket.curly.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": ".box-shadow", - "t": "source.css.less entity.other.attribute-name.class.mixin.css", - "r": { - "dark_plus": "entity.other.attribute-name.class.mixin.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.class.mixin.css: #800000", - "dark_vs": "entity.other.attribute-name.class.mixin.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.class.mixin.css: #800000", - "hc_black": "entity.other.attribute-name.class.mixin.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.class.mixin.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.class.mixin.css: #0F4A85", - "light_modern": "entity.other.attribute-name.class.mixin.css: #800000" - } - }, - { - "c": "(", - "t": "source.css.less meta.brace.round.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "@", - "t": "source.css.less variable.other.less punctuation.definition.variable.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": "style", - "t": "source.css.less variable.other.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": ",", - "t": "source.css.less punctuation.separator.list.comma.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": " ", - "t": "source.css.less", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "@", - "t": "source.css.less variable.other.less punctuation.definition.variable.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": "alpha", - "t": "source.css.less variable.other.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": ":", - "t": "source.css.less punctuation.separator.key-value.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": " ", - "t": "source.css.less", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "50", - "t": "source.css.less constant.numeric.css", - "r": { - "dark_plus": "constant.numeric: #B5CEA8", - "light_plus": "constant.numeric: #098658", - "dark_vs": "constant.numeric: #B5CEA8", - "light_vs": "constant.numeric: #098658", - "hc_black": "constant.numeric: #B5CEA8", - "dark_modern": "constant.numeric: #B5CEA8", - "hc_light": "constant.numeric: #096D48", - "light_modern": "constant.numeric: #098658" - } - }, - { - "c": "%", - "t": "source.css.less constant.numeric.css keyword.other.unit.percentage.css", - "r": { - "dark_plus": "keyword.other.unit: #B5CEA8", - "light_plus": "keyword.other.unit: #098658", - "dark_vs": "keyword.other.unit: #B5CEA8", - "light_vs": "keyword.other.unit: #098658", - "hc_black": "keyword.other.unit: #B5CEA8", - "dark_modern": "keyword.other.unit: #B5CEA8", - "hc_light": "keyword.other.unit: #096D48", - "light_modern": "keyword.other.unit: #098658" - } - }, - { - "c": ")", - "t": "source.css.less meta.brace.round.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": " ", - "t": "source.css.less", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "when", - "t": "source.css.less keyword.control.logical.operator.less", - "r": { - "dark_plus": "keyword.control: #C586C0", - "light_plus": "keyword.control: #AF00DB", - "dark_vs": "keyword.control: #569CD6", - "light_vs": "keyword.control: #0000FF", - "hc_black": "keyword.control: #C586C0", - "dark_modern": "keyword.control: #C586C0", - "hc_light": "keyword.control: #B5200D", - "light_modern": "keyword.control: #AF00DB" - } - }, - { - "c": " ", - "t": "source.css.less", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "(", - "t": "source.css.less meta.brace.round.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "isnumber", - "t": "source.css.less support.function.type-checking.less", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "dark_modern": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC", - "light_modern": "support.function: #795E26" - } - }, - { - "c": "(", - "t": "source.css.less meta.brace.round.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "@", - "t": "source.css.less variable.other.less punctuation.definition.variable.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": "alpha", - "t": "source.css.less variable.other.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": "))", - "t": "source.css.less meta.brace.round.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": " ", - "t": "source.css.less", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "{", - "t": "source.css.less meta.property-list.css punctuation.section.property-list.begin.bracket.curly.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "\t", - "t": "source.css.less meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": ".box-shadow", - "t": "source.css.less meta.property-list.css entity.other.attribute-name.class.mixin.css", - "r": { - "dark_plus": "entity.other.attribute-name.class.mixin.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.class.mixin.css: #800000", - "dark_vs": "entity.other.attribute-name.class.mixin.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.class.mixin.css: #800000", - "hc_black": "entity.other.attribute-name.class.mixin.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.class.mixin.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.class.mixin.css: #0F4A85", - "light_modern": "entity.other.attribute-name.class.mixin.css: #800000" - } - }, - { - "c": "(", - "t": "source.css.less meta.property-list.css meta.brace.round.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "@", - "t": "source.css.less meta.property-list.css variable.other.less punctuation.definition.variable.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": "style", - "t": "source.css.less meta.property-list.css variable.other.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": ",", - "t": "source.css.less meta.property-list.css punctuation.separator.list.comma.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "rgba", - "t": "source.css.less meta.property-list.css meta.function.color.css support.function.misc.css", - "r": { - "dark_plus": "support.function: #DCDCAA", - "light_plus": "support.function: #795E26", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "support.function: #DCDCAA", - "dark_modern": "support.function: #DCDCAA", - "hc_light": "support.function: #5E2CBC", - "light_modern": "support.function: #795E26" - } - }, - { - "c": "(", - "t": "source.css.less meta.property-list.css meta.function.color.css punctuation.section.function.begin.bracket.round.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "0", - "t": "source.css.less meta.property-list.css meta.function.color.css constant.numeric.css", - "r": { - "dark_plus": "constant.numeric: #B5CEA8", - "light_plus": "constant.numeric: #098658", - "dark_vs": "constant.numeric: #B5CEA8", - "light_vs": "constant.numeric: #098658", - "hc_black": "constant.numeric: #B5CEA8", - "dark_modern": "constant.numeric: #B5CEA8", - "hc_light": "constant.numeric: #096D48", - "light_modern": "constant.numeric: #098658" - } - }, - { - "c": ",", - "t": "source.css.less meta.property-list.css meta.function.color.css punctuation.separator.list.comma.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css meta.function.color.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "0", - "t": "source.css.less meta.property-list.css meta.function.color.css constant.numeric.css", - "r": { - "dark_plus": "constant.numeric: #B5CEA8", - "light_plus": "constant.numeric: #098658", - "dark_vs": "constant.numeric: #B5CEA8", - "light_vs": "constant.numeric: #098658", - "hc_black": "constant.numeric: #B5CEA8", - "dark_modern": "constant.numeric: #B5CEA8", - "hc_light": "constant.numeric: #096D48", - "light_modern": "constant.numeric: #098658" - } - }, - { - "c": ",", - "t": "source.css.less meta.property-list.css meta.function.color.css punctuation.separator.list.comma.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css meta.function.color.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "0", - "t": "source.css.less meta.property-list.css meta.function.color.css constant.numeric.css", - "r": { - "dark_plus": "constant.numeric: #B5CEA8", - "light_plus": "constant.numeric: #098658", - "dark_vs": "constant.numeric: #B5CEA8", - "light_vs": "constant.numeric: #098658", - "hc_black": "constant.numeric: #B5CEA8", - "dark_modern": "constant.numeric: #B5CEA8", - "hc_light": "constant.numeric: #096D48", - "light_modern": "constant.numeric: #098658" - } - }, - { - "c": ",", - "t": "source.css.less meta.property-list.css meta.function.color.css punctuation.separator.list.comma.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": " @", - "t": "source.css.less meta.property-list.css meta.function.color.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "alpha", - "t": "source.css.less meta.property-list.css meta.function.color.css support.constant.property-value.css", - "r": { - "dark_plus": "support.constant.property-value: #CE9178", - "light_plus": "support.constant.property-value: #0451A5", - "dark_vs": "default: #D4D4D4", - "light_vs": "support.constant.property-value: #0451A5", - "hc_black": "support.constant.property-value: #CE9178", - "dark_modern": "support.constant.property-value: #CE9178", - "hc_light": "support.constant.property-value: #0451A5", - "light_modern": "support.constant.property-value: #0451A5" - } - }, - { - "c": ")", - "t": "source.css.less meta.property-list.css meta.function.color.css punctuation.section.function.end.bracket.round.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": ")", - "t": "source.css.less meta.property-list.css meta.brace.round.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": ";", - "t": "source.css.less meta.property-list.css punctuation.terminator.rule.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "}", - "t": "source.css.less meta.property-list.css punctuation.section.property-list.end.bracket.curly.css", + "t": "source.css.less meta.property-value.less punctuation.terminator.rule.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1569,35 +547,287 @@ }, { "c": ".", - "t": "source.css.less entity.other.attribute-name.class.css punctuation.definition.entity.css", + "t": "source.css.less meta.selector.less entity.other.attribute-name.class.less punctuation.definition.entity.less", "r": { - "dark_plus": "entity.other.attribute-name.class.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.class.css: #800000", - "dark_vs": "entity.other.attribute-name.class.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.class.css: #800000", - "hc_black": "entity.other.attribute-name.class.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.class.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.class.css: #0F4A85", - "light_modern": "entity.other.attribute-name.class.css: #800000" + "dark_plus": "source.css entity.other.attribute-name.class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.class: #800000", + "dark_vs": "source.css entity.other.attribute-name.class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.class: #800000", + "hc_black": "source.css entity.other.attribute-name.class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.class: #800000" } }, { - "c": "box", - "t": "source.css.less entity.other.attribute-name.class.css", + "c": "box-shadow", + "t": "source.css.less meta.selector.less entity.other.attribute-name.class.less", "r": { - "dark_plus": "entity.other.attribute-name.class.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.class.css: #800000", - "dark_vs": "entity.other.attribute-name.class.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.class.css: #800000", - "hc_black": "entity.other.attribute-name.class.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.class.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.class.css: #0F4A85", - "light_modern": "entity.other.attribute-name.class.css: #800000" + "dark_plus": "source.css entity.other.attribute-name.class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.class: #800000", + "dark_vs": "source.css entity.other.attribute-name.class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.class: #800000", + "hc_black": "source.css entity.other.attribute-name.class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.class: #800000" + } + }, + { + "c": "(", + "t": "source.css.less meta.selector.less meta.group.less punctuation.definition.group.begin.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "@", + "t": "source.css.less meta.selector.less meta.group.less meta.property-value.less variable.other.readwrite.less punctuation.definition.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": "style", + "t": "source.css.less meta.selector.less meta.group.less meta.property-value.less variable.other.readwrite.less support.other.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": ",", + "t": "source.css.less meta.selector.less meta.group.less meta.property-value.less punctuation.separator.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" } }, { "c": " ", - "t": "source.css.less", + "t": "source.css.less meta.selector.less meta.group.less meta.property-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "@", + "t": "source.css.less meta.selector.less meta.group.less meta.property-value.less variable.other.readwrite.less punctuation.definition.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": "c", + "t": "source.css.less meta.selector.less meta.group.less meta.property-value.less variable.other.readwrite.less support.other.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": ")", + "t": "source.css.less meta.selector.less meta.group.less punctuation.definition.group.end.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.css.less meta.selector.less meta.conditional.guarded-namespace.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "when", + "t": "source.css.less meta.selector.less meta.conditional.guarded-namespace.less keyword.control.conditional.less", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #C586C0", + "hc_light": "keyword.control: #B5200D", + "light_modern": "keyword.control: #AF00DB" + } + }, + { + "c": " ", + "t": "source.css.less meta.selector.less meta.conditional.guarded-namespace.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "(", + "t": "source.css.less meta.selector.less meta.conditional.guarded-namespace.less meta.group.less punctuation.definition.group.begin.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "iscolor", + "t": "source.css.less meta.selector.less meta.conditional.guarded-namespace.less meta.group.less meta.function-call.less support.function.type.less", + "r": { + "dark_plus": "support.function: #DCDCAA", + "light_plus": "support.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.function: #DCDCAA", + "dark_modern": "support.function: #DCDCAA", + "hc_light": "support.function: #5E2CBC", + "light_modern": "support.function: #795E26" + } + }, + { + "c": "(", + "t": "source.css.less meta.selector.less meta.conditional.guarded-namespace.less meta.group.less meta.function-call.less punctuation.definition.group.begin.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "@", + "t": "source.css.less meta.selector.less meta.conditional.guarded-namespace.less meta.group.less meta.function-call.less variable.other.readwrite.less punctuation.definition.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": "c", + "t": "source.css.less meta.selector.less meta.conditional.guarded-namespace.less meta.group.less meta.function-call.less variable.other.readwrite.less support.other.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": ")", + "t": "source.css.less meta.selector.less meta.conditional.guarded-namespace.less meta.group.less meta.function-call.less punctuation.definition.group.end.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": ")", + "t": "source.css.less meta.selector.less meta.conditional.guarded-namespace.less punctuation.definition.group.end.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.css.less meta.selector.less meta.conditional.guarded-namespace.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1611,7 +841,7 @@ }, { "c": "{", - "t": "source.css.less meta.property-list.css punctuation.section.property-list.begin.bracket.curly.css", + "t": "source.css.less meta.property-list.less punctuation.definition.block.begin.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1625,7 +855,7 @@ }, { "c": "\t", - "t": "source.css.less meta.property-list.css", + "t": "source.css.less meta.property-list.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1638,8 +868,8 @@ } }, { - "c": "color", - "t": "source.css.less meta.property-list.css support.type.property-name.css", + "c": "border-radius", + "t": "source.css.less meta.property-list.less support.type.property-name.less", "r": { "dark_plus": "support.type.property-name: #9CDCFE", "light_plus": "support.type.property-name: #E50000", @@ -1653,7 +883,7 @@ }, { "c": ":", - "t": "source.css.less meta.property-list.css punctuation.separator.key-value.css", + "t": "source.css.less meta.property-list.less punctuation.separator.key-value.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1667,7 +897,931 @@ }, { "c": " ", - "t": "source.css.less meta.property-list.css", + "t": "source.css.less meta.property-list.less meta.property-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "@", + "t": "source.css.less meta.property-list.less meta.property-value.less variable.other.readwrite.less punctuation.definition.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": "style", + "t": "source.css.less meta.property-list.less meta.property-value.less variable.other.readwrite.less support.other.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.property-value.less variable.other.readwrite.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": "@", + "t": "source.css.less meta.property-list.less meta.property-value.less variable.other.readwrite.less punctuation.definition.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": "c", + "t": "source.css.less meta.property-list.less meta.property-value.less variable.other.readwrite.less support.other.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": ";", + "t": "source.css.less meta.property-list.less punctuation.terminator.rule.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "}", + "t": "source.css.less punctuation.definition.block.end.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": ".", + "t": "source.css.less meta.selector.less entity.other.attribute-name.class.less punctuation.definition.entity.less", + "r": { + "dark_plus": "source.css entity.other.attribute-name.class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.class: #800000", + "dark_vs": "source.css entity.other.attribute-name.class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.class: #800000", + "hc_black": "source.css entity.other.attribute-name.class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.class: #800000" + } + }, + { + "c": "box-shadow", + "t": "source.css.less meta.selector.less entity.other.attribute-name.class.less", + "r": { + "dark_plus": "source.css entity.other.attribute-name.class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.class: #800000", + "dark_vs": "source.css entity.other.attribute-name.class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.class: #800000", + "hc_black": "source.css entity.other.attribute-name.class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.class: #800000" + } + }, + { + "c": "(", + "t": "source.css.less meta.selector.less meta.group.less punctuation.definition.group.begin.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "@", + "t": "source.css.less meta.selector.less meta.group.less meta.property-value.less variable.other.readwrite.less punctuation.definition.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": "style", + "t": "source.css.less meta.selector.less meta.group.less meta.property-value.less variable.other.readwrite.less support.other.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": ",", + "t": "source.css.less meta.selector.less meta.group.less meta.property-value.less punctuation.separator.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.css.less meta.selector.less meta.group.less meta.property-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "@", + "t": "source.css.less meta.selector.less meta.group.less meta.property-value.less variable.other.readwrite.less punctuation.definition.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": "alpha", + "t": "source.css.less meta.selector.less meta.group.less meta.property-value.less variable.other.readwrite.less support.other.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": ":", + "t": "source.css.less meta.selector.less meta.group.less meta.property-value.less punctuation.separator.key-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.css.less meta.selector.less meta.group.less meta.property-value.less meta.property-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "50", + "t": "source.css.less meta.selector.less meta.group.less meta.property-value.less constant.numeric.less", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8", + "dark_modern": "constant.numeric: #B5CEA8", + "hc_light": "constant.numeric: #096D48", + "light_modern": "constant.numeric: #098658" + } + }, + { + "c": "%", + "t": "source.css.less meta.selector.less meta.group.less meta.property-value.less constant.numeric.less keyword.other.unit.less", + "r": { + "dark_plus": "keyword.other.unit: #B5CEA8", + "light_plus": "keyword.other.unit: #098658", + "dark_vs": "keyword.other.unit: #B5CEA8", + "light_vs": "keyword.other.unit: #098658", + "hc_black": "keyword.other.unit: #B5CEA8", + "dark_modern": "keyword.other.unit: #B5CEA8", + "hc_light": "keyword.other.unit: #096D48", + "light_modern": "keyword.other.unit: #098658" + } + }, + { + "c": ")", + "t": "source.css.less meta.selector.less meta.group.less punctuation.definition.group.end.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.css.less meta.selector.less meta.conditional.guarded-namespace.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "when", + "t": "source.css.less meta.selector.less meta.conditional.guarded-namespace.less keyword.control.conditional.less", + "r": { + "dark_plus": "keyword.control: #C586C0", + "light_plus": "keyword.control: #AF00DB", + "dark_vs": "keyword.control: #569CD6", + "light_vs": "keyword.control: #0000FF", + "hc_black": "keyword.control: #C586C0", + "dark_modern": "keyword.control: #C586C0", + "hc_light": "keyword.control: #B5200D", + "light_modern": "keyword.control: #AF00DB" + } + }, + { + "c": " ", + "t": "source.css.less meta.selector.less meta.conditional.guarded-namespace.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "(", + "t": "source.css.less meta.selector.less meta.conditional.guarded-namespace.less meta.group.less punctuation.definition.group.begin.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "isnumber", + "t": "source.css.less meta.selector.less meta.conditional.guarded-namespace.less meta.group.less meta.function-call.less support.function.type.less", + "r": { + "dark_plus": "support.function: #DCDCAA", + "light_plus": "support.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.function: #DCDCAA", + "dark_modern": "support.function: #DCDCAA", + "hc_light": "support.function: #5E2CBC", + "light_modern": "support.function: #795E26" + } + }, + { + "c": "(", + "t": "source.css.less meta.selector.less meta.conditional.guarded-namespace.less meta.group.less meta.function-call.less punctuation.definition.group.begin.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "@", + "t": "source.css.less meta.selector.less meta.conditional.guarded-namespace.less meta.group.less meta.function-call.less variable.other.readwrite.less punctuation.definition.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": "alpha", + "t": "source.css.less meta.selector.less meta.conditional.guarded-namespace.less meta.group.less meta.function-call.less variable.other.readwrite.less support.other.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": ")", + "t": "source.css.less meta.selector.less meta.conditional.guarded-namespace.less meta.group.less meta.function-call.less punctuation.definition.group.end.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": ")", + "t": "source.css.less meta.selector.less meta.conditional.guarded-namespace.less punctuation.definition.group.end.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.css.less meta.selector.less meta.conditional.guarded-namespace.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "{", + "t": "source.css.less meta.property-list.less punctuation.definition.block.begin.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "\t", + "t": "source.css.less meta.property-list.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": ".", + "t": "source.css.less meta.property-list.less meta.selector.less entity.other.attribute-name.class.less punctuation.definition.entity.less", + "r": { + "dark_plus": "source.css entity.other.attribute-name.class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.class: #800000", + "dark_vs": "source.css entity.other.attribute-name.class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.class: #800000", + "hc_black": "source.css entity.other.attribute-name.class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.class: #800000" + } + }, + { + "c": "box-shadow", + "t": "source.css.less meta.property-list.less meta.selector.less entity.other.attribute-name.class.less", + "r": { + "dark_plus": "source.css entity.other.attribute-name.class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.class: #800000", + "dark_vs": "source.css entity.other.attribute-name.class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.class: #800000", + "hc_black": "source.css entity.other.attribute-name.class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.class: #800000" + } + }, + { + "c": "(", + "t": "source.css.less meta.property-list.less meta.selector.less meta.group.less punctuation.definition.group.begin.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "@", + "t": "source.css.less meta.property-list.less meta.selector.less meta.group.less meta.property-value.less variable.other.readwrite.less punctuation.definition.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": "style", + "t": "source.css.less meta.property-list.less meta.selector.less meta.group.less meta.property-value.less variable.other.readwrite.less support.other.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": ",", + "t": "source.css.less meta.property-list.less meta.selector.less meta.group.less meta.property-value.less punctuation.separator.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.selector.less meta.group.less meta.property-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "rgba", + "t": "source.css.less meta.property-list.less meta.selector.less meta.group.less meta.property-value.less meta.function-call.less support.function.color.less", + "r": { + "dark_plus": "support.function: #DCDCAA", + "light_plus": "support.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.function: #DCDCAA", + "dark_modern": "support.function: #DCDCAA", + "hc_light": "support.function: #5E2CBC", + "light_modern": "support.function: #795E26" + } + }, + { + "c": "(", + "t": "source.css.less meta.property-list.less meta.selector.less meta.group.less meta.property-value.less meta.function-call.less punctuation.definition.group.begin.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "0", + "t": "source.css.less meta.property-list.less meta.selector.less meta.group.less meta.property-value.less meta.function-call.less constant.numeric.less", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8", + "dark_modern": "constant.numeric: #B5CEA8", + "hc_light": "constant.numeric: #096D48", + "light_modern": "constant.numeric: #098658" + } + }, + { + "c": ",", + "t": "source.css.less meta.property-list.less meta.selector.less meta.group.less meta.property-value.less meta.function-call.less punctuation.separator.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.selector.less meta.group.less meta.property-value.less meta.function-call.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "0", + "t": "source.css.less meta.property-list.less meta.selector.less meta.group.less meta.property-value.less meta.function-call.less constant.numeric.less", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8", + "dark_modern": "constant.numeric: #B5CEA8", + "hc_light": "constant.numeric: #096D48", + "light_modern": "constant.numeric: #098658" + } + }, + { + "c": ",", + "t": "source.css.less meta.property-list.less meta.selector.less meta.group.less meta.property-value.less meta.function-call.less punctuation.separator.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.selector.less meta.group.less meta.property-value.less meta.function-call.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "0", + "t": "source.css.less meta.property-list.less meta.selector.less meta.group.less meta.property-value.less meta.function-call.less constant.numeric.less", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8", + "dark_modern": "constant.numeric: #B5CEA8", + "hc_light": "constant.numeric: #096D48", + "light_modern": "constant.numeric: #098658" + } + }, + { + "c": ",", + "t": "source.css.less meta.property-list.less meta.selector.less meta.group.less meta.property-value.less meta.function-call.less punctuation.separator.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.selector.less meta.group.less meta.property-value.less meta.function-call.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "@", + "t": "source.css.less meta.property-list.less meta.selector.less meta.group.less meta.property-value.less meta.function-call.less variable.other.readwrite.less punctuation.definition.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": "alpha", + "t": "source.css.less meta.property-list.less meta.selector.less meta.group.less meta.property-value.less meta.function-call.less variable.other.readwrite.less support.other.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": ")", + "t": "source.css.less meta.property-list.less meta.selector.less meta.group.less meta.property-value.less meta.function-call.less punctuation.definition.group.end.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": ")", + "t": "source.css.less meta.property-list.less meta.selector.less meta.group.less punctuation.definition.group.end.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": ";", + "t": "source.css.less meta.property-list.less punctuation.terminator.rule.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "}", + "t": "source.css.less punctuation.definition.block.end.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": ".", + "t": "source.css.less meta.selector.less entity.other.attribute-name.class.less punctuation.definition.entity.less", + "r": { + "dark_plus": "source.css entity.other.attribute-name.class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.class: #800000", + "dark_vs": "source.css entity.other.attribute-name.class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.class: #800000", + "hc_black": "source.css entity.other.attribute-name.class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.class: #800000" + } + }, + { + "c": "box", + "t": "source.css.less meta.selector.less entity.other.attribute-name.class.less", + "r": { + "dark_plus": "source.css entity.other.attribute-name.class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.class: #800000", + "dark_vs": "source.css entity.other.attribute-name.class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.class: #800000", + "hc_black": "source.css entity.other.attribute-name.class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.class: #800000" + } + }, + { + "c": " ", + "t": "source.css.less meta.selector.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "{", + "t": "source.css.less meta.property-list.less punctuation.definition.block.begin.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "\t", + "t": "source.css.less meta.property-list.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "color", + "t": "source.css.less meta.property-list.less support.type.property-name.less", + "r": { + "dark_plus": "support.type.property-name: #9CDCFE", + "light_plus": "support.type.property-name: #E50000", + "dark_vs": "support.type.property-name: #9CDCFE", + "light_vs": "support.type.property-name: #E50000", + "hc_black": "support.type.property-name: #D4D4D4", + "dark_modern": "support.type.property-name: #9CDCFE", + "hc_light": "support.type.property-name: #264F78", + "light_modern": "support.type.property-name: #E50000" + } + }, + { + "c": ":", + "t": "source.css.less meta.property-list.less punctuation.separator.key-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.property-value.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1681,7 +1835,7 @@ }, { "c": "saturate", - "t": "source.css.less meta.property-list.css meta.property-value.css support.function.any-method.builtin.less", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less support.function.color-operation.less", "r": { "dark_plus": "support.function: #DCDCAA", "light_plus": "support.function: #795E26", @@ -1695,7 +1849,7 @@ }, { "c": "(", - "t": "source.css.less meta.property-list.css meta.property-value.css meta.brace.round.css", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less punctuation.definition.group.begin.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1709,35 +1863,35 @@ }, { "c": "@", - "t": "source.css.less meta.property-list.css meta.property-value.css variable.other.less punctuation.definition.variable.less", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less variable.other.readwrite.less punctuation.definition.variable.less", "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" } }, { "c": "base", - "t": "source.css.less meta.property-list.css meta.property-value.css variable.other.less", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less variable.other.readwrite.less support.other.variable.less", "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" } }, { "c": ",", - "t": "source.css.less meta.property-list.css meta.property-value.css punctuation.separator.list.comma.css", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less punctuation.separator.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1751,7 +1905,7 @@ }, { "c": " ", - "t": "source.css.less meta.property-list.css meta.property-value.css", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1765,7 +1919,7 @@ }, { "c": "5", - "t": "source.css.less meta.property-list.css meta.property-value.css constant.numeric.css", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less constant.numeric.less", "r": { "dark_plus": "constant.numeric: #B5CEA8", "light_plus": "constant.numeric: #098658", @@ -1779,7 +1933,7 @@ }, { "c": "%", - "t": "source.css.less meta.property-list.css meta.property-value.css constant.numeric.css keyword.other.unit.percentage.css", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less constant.numeric.less keyword.other.unit.less", "r": { "dark_plus": "keyword.other.unit: #B5CEA8", "light_plus": "keyword.other.unit: #098658", @@ -1793,7 +1947,7 @@ }, { "c": ")", - "t": "source.css.less meta.property-list.css meta.property-value.css meta.brace.round.css", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less punctuation.definition.group.end.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1807,7 +1961,7 @@ }, { "c": ";", - "t": "source.css.less meta.property-list.css punctuation.terminator.rule.css", + "t": "source.css.less meta.property-list.less punctuation.terminator.rule.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1821,7 +1975,7 @@ }, { "c": "\t", - "t": "source.css.less meta.property-list.css", + "t": "source.css.less meta.property-list.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1835,7 +1989,7 @@ }, { "c": "border-color", - "t": "source.css.less meta.property-list.css support.type.property-name.css", + "t": "source.css.less meta.property-list.less support.type.property-name.less", "r": { "dark_plus": "support.type.property-name: #9CDCFE", "light_plus": "support.type.property-name: #E50000", @@ -1849,7 +2003,7 @@ }, { "c": ":", - "t": "source.css.less meta.property-list.css punctuation.separator.key-value.css", + "t": "source.css.less meta.property-list.less punctuation.separator.key-value.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1863,7 +2017,7 @@ }, { "c": " ", - "t": "source.css.less meta.property-list.css", + "t": "source.css.less meta.property-list.less meta.property-value.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -1877,2443 +2031,7 @@ }, { "c": "lighten", - "t": "source.css.less meta.property-list.css meta.property-value.css support.constant.property-value.css", - "r": { - "dark_plus": "support.constant.property-value: #CE9178", - "light_plus": "support.constant.property-value: #0451A5", - "dark_vs": "default: #D4D4D4", - "light_vs": "support.constant.property-value: #0451A5", - "hc_black": "support.constant.property-value: #CE9178", - "dark_modern": "support.constant.property-value: #CE9178", - "hc_light": "support.constant.property-value: #0451A5", - "light_modern": "support.constant.property-value: #0451A5" - } - }, - { - "c": "(", - "t": "source.css.less meta.property-list.css meta.property-value.css meta.brace.round.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "@", - "t": "source.css.less meta.property-list.css meta.property-value.css variable.other.less punctuation.definition.variable.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": "base", - "t": "source.css.less meta.property-list.css meta.property-value.css variable.other.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": ",", - "t": "source.css.less meta.property-list.css meta.property-value.css punctuation.separator.list.comma.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css meta.property-value.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "30", - "t": "source.css.less meta.property-list.css meta.property-value.css constant.numeric.css", - "r": { - "dark_plus": "constant.numeric: #B5CEA8", - "light_plus": "constant.numeric: #098658", - "dark_vs": "constant.numeric: #B5CEA8", - "light_vs": "constant.numeric: #098658", - "hc_black": "constant.numeric: #B5CEA8", - "dark_modern": "constant.numeric: #B5CEA8", - "hc_light": "constant.numeric: #096D48", - "light_modern": "constant.numeric: #098658" - } - }, - { - "c": "%", - "t": "source.css.less meta.property-list.css meta.property-value.css constant.numeric.css keyword.other.unit.percentage.css", - "r": { - "dark_plus": "keyword.other.unit: #B5CEA8", - "light_plus": "keyword.other.unit: #098658", - "dark_vs": "keyword.other.unit: #B5CEA8", - "light_vs": "keyword.other.unit: #098658", - "hc_black": "keyword.other.unit: #B5CEA8", - "dark_modern": "keyword.other.unit: #B5CEA8", - "hc_light": "keyword.other.unit: #096D48", - "light_modern": "keyword.other.unit: #098658" - } - }, - { - "c": ")", - "t": "source.css.less meta.property-list.css meta.property-value.css meta.brace.round.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": ";", - "t": "source.css.less meta.property-list.css punctuation.terminator.rule.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "\t", - "t": "source.css.less meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "div", - "t": "source.css.less meta.property-list.css entity.name.tag.css", - "r": { - "dark_plus": "entity.name.tag.css: #D7BA7D", - "light_plus": "entity.name.tag: #800000", - "dark_vs": "entity.name.tag.css: #D7BA7D", - "light_vs": "entity.name.tag: #800000", - "hc_black": "entity.name.tag.css: #D7BA7D", - "dark_modern": "entity.name.tag.css: #D7BA7D", - "hc_light": "entity.name.tag: #0F4A85", - "light_modern": "entity.name.tag: #800000" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "{", - "t": "source.css.less meta.property-list.css meta.property-list.css punctuation.section.property-list.begin.bracket.curly.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "\t\t", - "t": "source.css.less meta.property-list.css meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": ".box-shadow", - "t": "source.css.less meta.property-list.css meta.property-list.css entity.other.attribute-name.class.mixin.css", - "r": { - "dark_plus": "entity.other.attribute-name.class.mixin.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.class.mixin.css: #800000", - "dark_vs": "entity.other.attribute-name.class.mixin.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.class.mixin.css: #800000", - "hc_black": "entity.other.attribute-name.class.mixin.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.class.mixin.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.class.mixin.css: #0F4A85", - "light_modern": "entity.other.attribute-name.class.mixin.css: #800000" - } - }, - { - "c": "((", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.brace.round.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "0", - "t": "source.css.less meta.property-list.css meta.property-list.css constant.numeric.css", - "r": { - "dark_plus": "constant.numeric: #B5CEA8", - "light_plus": "constant.numeric: #098658", - "dark_vs": "constant.numeric: #B5CEA8", - "light_vs": "constant.numeric: #098658", - "hc_black": "constant.numeric: #B5CEA8", - "dark_modern": "constant.numeric: #B5CEA8", - "hc_light": "constant.numeric: #096D48", - "light_modern": "constant.numeric: #098658" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "0", - "t": "source.css.less meta.property-list.css meta.property-list.css constant.numeric.css", - "r": { - "dark_plus": "constant.numeric: #B5CEA8", - "light_plus": "constant.numeric: #098658", - "dark_vs": "constant.numeric: #B5CEA8", - "light_vs": "constant.numeric: #098658", - "hc_black": "constant.numeric: #B5CEA8", - "dark_modern": "constant.numeric: #B5CEA8", - "hc_light": "constant.numeric: #096D48", - "light_modern": "constant.numeric: #098658" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "5", - "t": "source.css.less meta.property-list.css meta.property-list.css constant.numeric.css", - "r": { - "dark_plus": "constant.numeric: #B5CEA8", - "light_plus": "constant.numeric: #098658", - "dark_vs": "constant.numeric: #B5CEA8", - "light_vs": "constant.numeric: #098658", - "hc_black": "constant.numeric: #B5CEA8", - "dark_modern": "constant.numeric: #B5CEA8", - "hc_light": "constant.numeric: #096D48", - "light_modern": "constant.numeric: #098658" - } - }, - { - "c": "px", - "t": "source.css.less meta.property-list.css meta.property-list.css constant.numeric.css keyword.other.unit.px.css", - "r": { - "dark_plus": "keyword.other.unit: #B5CEA8", - "light_plus": "keyword.other.unit: #098658", - "dark_vs": "keyword.other.unit: #B5CEA8", - "light_vs": "keyword.other.unit: #098658", - "hc_black": "keyword.other.unit: #B5CEA8", - "dark_modern": "keyword.other.unit: #B5CEA8", - "hc_light": "keyword.other.unit: #096D48", - "light_modern": "keyword.other.unit: #098658" - } - }, - { - "c": ")", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.brace.round.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": ",", - "t": "source.css.less meta.property-list.css meta.property-list.css punctuation.separator.list.comma.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "30", - "t": "source.css.less meta.property-list.css meta.property-list.css constant.numeric.css", - "r": { - "dark_plus": "constant.numeric: #B5CEA8", - "light_plus": "constant.numeric: #098658", - "dark_vs": "constant.numeric: #B5CEA8", - "light_vs": "constant.numeric: #098658", - "hc_black": "constant.numeric: #B5CEA8", - "dark_modern": "constant.numeric: #B5CEA8", - "hc_light": "constant.numeric: #096D48", - "light_modern": "constant.numeric: #098658" - } - }, - { - "c": "%", - "t": "source.css.less meta.property-list.css meta.property-list.css constant.numeric.css keyword.other.unit.percentage.css", - "r": { - "dark_plus": "keyword.other.unit: #B5CEA8", - "light_plus": "keyword.other.unit: #098658", - "dark_vs": "keyword.other.unit: #B5CEA8", - "light_vs": "keyword.other.unit: #098658", - "hc_black": "keyword.other.unit: #B5CEA8", - "dark_modern": "keyword.other.unit: #B5CEA8", - "hc_light": "keyword.other.unit: #096D48", - "light_modern": "keyword.other.unit: #098658" - } - }, - { - "c": ")", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.brace.round.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": ";", - "t": "source.css.less meta.property-list.css meta.property-list.css punctuation.terminator.rule.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "\t", - "t": "source.css.less meta.property-list.css meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "}", - "t": "source.css.less meta.property-list.css meta.property-list.css punctuation.section.property-list.end.bracket.curly.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "}", - "t": "source.css.less meta.property-list.css punctuation.section.property-list.end.bracket.curly.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "#", - "t": "source.css.less meta.selector.css entity.other.attribute-name.id punctuation.definition.entity.css", - "r": { - "dark_plus": "source.css.less entity.other.attribute-name.id: #D7BA7D", - "light_plus": "source.css.less entity.other.attribute-name.id: #800000", - "dark_vs": "source.css.less entity.other.attribute-name.id: #D7BA7D", - "light_vs": "source.css.less entity.other.attribute-name.id: #800000", - "hc_black": "source.css.less entity.other.attribute-name.id: #D7BA7D", - "dark_modern": "source.css.less entity.other.attribute-name.id: #D7BA7D", - "hc_light": "source.css.less entity.other.attribute-name.id: #0F4A85", - "light_modern": "source.css.less entity.other.attribute-name.id: #800000" - } - }, - { - "c": "header", - "t": "source.css.less meta.selector.css entity.other.attribute-name.id", - "r": { - "dark_plus": "source.css.less entity.other.attribute-name.id: #D7BA7D", - "light_plus": "source.css.less entity.other.attribute-name.id: #800000", - "dark_vs": "source.css.less entity.other.attribute-name.id: #D7BA7D", - "light_vs": "source.css.less entity.other.attribute-name.id: #800000", - "hc_black": "source.css.less entity.other.attribute-name.id: #D7BA7D", - "dark_modern": "source.css.less entity.other.attribute-name.id: #D7BA7D", - "hc_light": "source.css.less entity.other.attribute-name.id: #0F4A85", - "light_modern": "source.css.less entity.other.attribute-name.id: #800000" - } - }, - { - "c": " ", - "t": "source.css.less", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "{", - "t": "source.css.less meta.property-list.css punctuation.section.property-list.begin.bracket.curly.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "\t", - "t": "source.css.less meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "h1", - "t": "source.css.less meta.property-list.css entity.name.tag.css", - "r": { - "dark_plus": "entity.name.tag.css: #D7BA7D", - "light_plus": "entity.name.tag: #800000", - "dark_vs": "entity.name.tag.css: #D7BA7D", - "light_vs": "entity.name.tag: #800000", - "hc_black": "entity.name.tag.css: #D7BA7D", - "dark_modern": "entity.name.tag.css: #D7BA7D", - "hc_light": "entity.name.tag: #0F4A85", - "light_modern": "entity.name.tag: #800000" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "{", - "t": "source.css.less meta.property-list.css meta.property-list.css punctuation.section.property-list.begin.bracket.curly.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "\t\t", - "t": "source.css.less meta.property-list.css meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "font-size", - "t": "source.css.less meta.property-list.css meta.property-list.css support.type.property-name.css", - "r": { - "dark_plus": "support.type.property-name: #9CDCFE", - "light_plus": "support.type.property-name: #E50000", - "dark_vs": "support.type.property-name: #9CDCFE", - "light_vs": "support.type.property-name: #E50000", - "hc_black": "support.type.property-name: #D4D4D4", - "dark_modern": "support.type.property-name: #9CDCFE", - "hc_light": "support.type.property-name: #264F78", - "light_modern": "support.type.property-name: #E50000" - } - }, - { - "c": ":", - "t": "source.css.less meta.property-list.css meta.property-list.css punctuation.separator.key-value.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "26", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.property-value.css constant.numeric.css", - "r": { - "dark_plus": "constant.numeric: #B5CEA8", - "light_plus": "constant.numeric: #098658", - "dark_vs": "constant.numeric: #B5CEA8", - "light_vs": "constant.numeric: #098658", - "hc_black": "constant.numeric: #B5CEA8", - "dark_modern": "constant.numeric: #B5CEA8", - "hc_light": "constant.numeric: #096D48", - "light_modern": "constant.numeric: #098658" - } - }, - { - "c": "px", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.property-value.css constant.numeric.css keyword.other.unit.px.css", - "r": { - "dark_plus": "keyword.other.unit: #B5CEA8", - "light_plus": "keyword.other.unit: #098658", - "dark_vs": "keyword.other.unit: #B5CEA8", - "light_vs": "keyword.other.unit: #098658", - "hc_black": "keyword.other.unit: #B5CEA8", - "dark_modern": "keyword.other.unit: #B5CEA8", - "hc_light": "keyword.other.unit: #096D48", - "light_modern": "keyword.other.unit: #098658" - } - }, - { - "c": ";", - "t": "source.css.less meta.property-list.css meta.property-list.css punctuation.terminator.rule.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "\t\t", - "t": "source.css.less meta.property-list.css meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "font-weight", - "t": "source.css.less meta.property-list.css meta.property-list.css support.type.property-name.css", - "r": { - "dark_plus": "support.type.property-name: #9CDCFE", - "light_plus": "support.type.property-name: #E50000", - "dark_vs": "support.type.property-name: #9CDCFE", - "light_vs": "support.type.property-name: #E50000", - "hc_black": "support.type.property-name: #D4D4D4", - "dark_modern": "support.type.property-name: #9CDCFE", - "hc_light": "support.type.property-name: #264F78", - "light_modern": "support.type.property-name: #E50000" - } - }, - { - "c": ":", - "t": "source.css.less meta.property-list.css meta.property-list.css punctuation.separator.key-value.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "bold", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.property-value.css support.constant.property-value.css", - "r": { - "dark_plus": "support.constant.property-value: #CE9178", - "light_plus": "support.constant.property-value: #0451A5", - "dark_vs": "default: #D4D4D4", - "light_vs": "support.constant.property-value: #0451A5", - "hc_black": "support.constant.property-value: #CE9178", - "dark_modern": "support.constant.property-value: #CE9178", - "hc_light": "support.constant.property-value: #0451A5", - "light_modern": "support.constant.property-value: #0451A5" - } - }, - { - "c": ";", - "t": "source.css.less meta.property-list.css meta.property-list.css punctuation.terminator.rule.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "\t", - "t": "source.css.less meta.property-list.css meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "}", - "t": "source.css.less meta.property-list.css meta.property-list.css punctuation.section.property-list.end.bracket.curly.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "\t", - "t": "source.css.less meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "p", - "t": "source.css.less meta.property-list.css entity.name.tag.css", - "r": { - "dark_plus": "entity.name.tag.css: #D7BA7D", - "light_plus": "entity.name.tag: #800000", - "dark_vs": "entity.name.tag.css: #D7BA7D", - "light_vs": "entity.name.tag: #800000", - "hc_black": "entity.name.tag.css: #D7BA7D", - "dark_modern": "entity.name.tag.css: #D7BA7D", - "hc_light": "entity.name.tag: #0F4A85", - "light_modern": "entity.name.tag: #800000" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "{", - "t": "source.css.less meta.property-list.css meta.property-list.css punctuation.section.property-list.begin.bracket.curly.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "font-size", - "t": "source.css.less meta.property-list.css meta.property-list.css support.type.property-name.css", - "r": { - "dark_plus": "support.type.property-name: #9CDCFE", - "light_plus": "support.type.property-name: #E50000", - "dark_vs": "support.type.property-name: #9CDCFE", - "light_vs": "support.type.property-name: #E50000", - "hc_black": "support.type.property-name: #D4D4D4", - "dark_modern": "support.type.property-name: #9CDCFE", - "hc_light": "support.type.property-name: #264F78", - "light_modern": "support.type.property-name: #E50000" - } - }, - { - "c": ":", - "t": "source.css.less meta.property-list.css meta.property-list.css punctuation.separator.key-value.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "12", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.property-value.css constant.numeric.css", - "r": { - "dark_plus": "constant.numeric: #B5CEA8", - "light_plus": "constant.numeric: #098658", - "dark_vs": "constant.numeric: #B5CEA8", - "light_vs": "constant.numeric: #098658", - "hc_black": "constant.numeric: #B5CEA8", - "dark_modern": "constant.numeric: #B5CEA8", - "hc_light": "constant.numeric: #096D48", - "light_modern": "constant.numeric: #098658" - } - }, - { - "c": "px", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.property-value.css constant.numeric.css keyword.other.unit.px.css", - "r": { - "dark_plus": "keyword.other.unit: #B5CEA8", - "light_plus": "keyword.other.unit: #098658", - "dark_vs": "keyword.other.unit: #B5CEA8", - "light_vs": "keyword.other.unit: #098658", - "hc_black": "keyword.other.unit: #B5CEA8", - "dark_modern": "keyword.other.unit: #B5CEA8", - "hc_light": "keyword.other.unit: #096D48", - "light_modern": "keyword.other.unit: #098658" - } - }, - { - "c": ";", - "t": "source.css.less meta.property-list.css meta.property-list.css punctuation.terminator.rule.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "\t\t", - "t": "source.css.less meta.property-list.css meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "a", - "t": "source.css.less meta.property-list.css meta.property-list.css entity.name.tag.css", - "r": { - "dark_plus": "entity.name.tag.css: #D7BA7D", - "light_plus": "entity.name.tag: #800000", - "dark_vs": "entity.name.tag.css: #D7BA7D", - "light_vs": "entity.name.tag: #800000", - "hc_black": "entity.name.tag.css: #D7BA7D", - "dark_modern": "entity.name.tag.css: #D7BA7D", - "hc_light": "entity.name.tag: #0F4A85", - "light_modern": "entity.name.tag: #800000" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "{", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.property-list.css punctuation.section.property-list.begin.bracket.curly.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "text-decoration", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.property-list.css support.type.property-name.css", - "r": { - "dark_plus": "support.type.property-name: #9CDCFE", - "light_plus": "support.type.property-name: #E50000", - "dark_vs": "support.type.property-name: #9CDCFE", - "light_vs": "support.type.property-name: #E50000", - "hc_black": "support.type.property-name: #D4D4D4", - "dark_modern": "support.type.property-name: #9CDCFE", - "hc_light": "support.type.property-name: #264F78", - "light_modern": "support.type.property-name: #E50000" - } - }, - { - "c": ":", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.property-list.css punctuation.separator.key-value.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "none", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.property-list.css meta.property-value.css support.constant.property-value.css", - "r": { - "dark_plus": "support.constant.property-value: #CE9178", - "light_plus": "support.constant.property-value: #0451A5", - "dark_vs": "default: #D4D4D4", - "light_vs": "support.constant.property-value: #0451A5", - "hc_black": "support.constant.property-value: #CE9178", - "dark_modern": "support.constant.property-value: #CE9178", - "hc_light": "support.constant.property-value: #0451A5", - "light_modern": "support.constant.property-value: #0451A5" - } - }, - { - "c": ";", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.property-list.css punctuation.terminator.rule.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "\t\t\t", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "&", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.property-list.css entity.other.attribute-name.parent-selector.css punctuation.definition.entity.css", - "r": { - "dark_plus": "entity.other.attribute-name.parent-selector.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.parent-selector.css: #800000", - "dark_vs": "entity.other.attribute-name.parent-selector.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.parent-selector.css: #800000", - "hc_black": "entity.other.attribute-name.parent-selector.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.parent-selector.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.parent-selector.css: #0F4A85", - "light_modern": "entity.other.attribute-name.parent-selector.css: #800000" - } - }, - { - "c": ":", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.property-list.css entity.other.attribute-name.pseudo-class.css punctuation.definition.entity.css", - "r": { - "dark_plus": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.pseudo-class.css: #800000", - "dark_vs": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.pseudo-class.css: #800000", - "hc_black": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.pseudo-class.css: #0F4A85", - "light_modern": "entity.other.attribute-name.pseudo-class.css: #800000" - } - }, - { - "c": "hover", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.property-list.css entity.other.attribute-name.pseudo-class.css", - "r": { - "dark_plus": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.pseudo-class.css: #800000", - "dark_vs": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.pseudo-class.css: #800000", - "hc_black": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.pseudo-class.css: #0F4A85", - "light_modern": "entity.other.attribute-name.pseudo-class.css: #800000" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "{", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.property-list.css meta.property-list.css punctuation.section.property-list.begin.bracket.curly.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.property-list.css meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "border-width", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.property-list.css meta.property-list.css support.type.property-name.css", - "r": { - "dark_plus": "support.type.property-name: #9CDCFE", - "light_plus": "support.type.property-name: #E50000", - "dark_vs": "support.type.property-name: #9CDCFE", - "light_vs": "support.type.property-name: #E50000", - "hc_black": "support.type.property-name: #D4D4D4", - "dark_modern": "support.type.property-name: #9CDCFE", - "hc_light": "support.type.property-name: #264F78", - "light_modern": "support.type.property-name: #E50000" - } - }, - { - "c": ":", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.property-list.css meta.property-list.css punctuation.separator.key-value.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.property-list.css meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "1", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.property-list.css meta.property-list.css meta.property-value.css constant.numeric.css", - "r": { - "dark_plus": "constant.numeric: #B5CEA8", - "light_plus": "constant.numeric: #098658", - "dark_vs": "constant.numeric: #B5CEA8", - "light_vs": "constant.numeric: #098658", - "hc_black": "constant.numeric: #B5CEA8", - "dark_modern": "constant.numeric: #B5CEA8", - "hc_light": "constant.numeric: #096D48", - "light_modern": "constant.numeric: #098658" - } - }, - { - "c": "px", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.property-list.css meta.property-list.css meta.property-value.css constant.numeric.css keyword.other.unit.px.css", - "r": { - "dark_plus": "keyword.other.unit: #B5CEA8", - "light_plus": "keyword.other.unit: #098658", - "dark_vs": "keyword.other.unit: #B5CEA8", - "light_vs": "keyword.other.unit: #098658", - "hc_black": "keyword.other.unit: #B5CEA8", - "dark_modern": "keyword.other.unit: #B5CEA8", - "hc_light": "keyword.other.unit: #096D48", - "light_modern": "keyword.other.unit: #098658" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.property-list.css meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "}", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.property-list.css meta.property-list.css punctuation.section.property-list.end.bracket.curly.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "\t\t", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "}", - "t": "source.css.less meta.property-list.css meta.property-list.css meta.property-list.css punctuation.section.property-list.end.bracket.curly.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "\t", - "t": "source.css.less meta.property-list.css meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "}", - "t": "source.css.less meta.property-list.css meta.property-list.css punctuation.section.property-list.end.bracket.curly.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "}", - "t": "source.css.less meta.property-list.css punctuation.section.property-list.end.bracket.curly.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "@", - "t": "source.css.less variable.other.less punctuation.definition.variable.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": "the-border", - "t": "source.css.less variable.other.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": ":", - "t": "source.css.less punctuation.separator.key-value.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": " ", - "t": "source.css.less", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "1", - "t": "source.css.less constant.numeric.css", - "r": { - "dark_plus": "constant.numeric: #B5CEA8", - "light_plus": "constant.numeric: #098658", - "dark_vs": "constant.numeric: #B5CEA8", - "light_vs": "constant.numeric: #098658", - "hc_black": "constant.numeric: #B5CEA8", - "dark_modern": "constant.numeric: #B5CEA8", - "hc_light": "constant.numeric: #096D48", - "light_modern": "constant.numeric: #098658" - } - }, - { - "c": "px", - "t": "source.css.less constant.numeric.css keyword.other.unit.px.css", - "r": { - "dark_plus": "keyword.other.unit: #B5CEA8", - "light_plus": "keyword.other.unit: #098658", - "dark_vs": "keyword.other.unit: #B5CEA8", - "light_vs": "keyword.other.unit: #098658", - "hc_black": "keyword.other.unit: #B5CEA8", - "dark_modern": "keyword.other.unit: #B5CEA8", - "hc_light": "keyword.other.unit: #096D48", - "light_modern": "keyword.other.unit: #098658" - } - }, - { - "c": ";", - "t": "source.css.less punctuation.terminator.rule.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "@", - "t": "source.css.less variable.other.less punctuation.definition.variable.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": "base-color", - "t": "source.css.less variable.other.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": ":", - "t": "source.css.less punctuation.separator.key-value.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": " ", - "t": "source.css.less", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "#111", - "t": "source.css.less constant.other.rgb-value.css", - "r": { - "dark_plus": "constant.other.rgb-value: #CE9178", - "light_plus": "constant.other.rgb-value: #0451A5", - "dark_vs": "default: #D4D4D4", - "light_vs": "constant.other.rgb-value: #0451A5", - "hc_black": "constant.other.rgb-value: #CE9178", - "dark_modern": "constant.other.rgb-value: #CE9178", - "hc_light": "constant.other.rgb-value: #0451A5", - "light_modern": "constant.other.rgb-value: #0451A5" - } - }, - { - "c": ";", - "t": "source.css.less punctuation.terminator.rule.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "@", - "t": "source.css.less variable.other.less punctuation.definition.variable.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": "red", - "t": "source.css.less variable.other.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": ":", - "t": "source.css.less punctuation.separator.key-value.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": " ", - "t": "source.css.less", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "#842210", - "t": "source.css.less constant.other.rgb-value.css", - "r": { - "dark_plus": "constant.other.rgb-value: #CE9178", - "light_plus": "constant.other.rgb-value: #0451A5", - "dark_vs": "default: #D4D4D4", - "light_vs": "constant.other.rgb-value: #0451A5", - "hc_black": "constant.other.rgb-value: #CE9178", - "dark_modern": "constant.other.rgb-value: #CE9178", - "hc_light": "constant.other.rgb-value: #0451A5", - "light_modern": "constant.other.rgb-value: #0451A5" - } - }, - { - "c": ";", - "t": "source.css.less punctuation.terminator.rule.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "#", - "t": "source.css.less meta.selector.css entity.other.attribute-name.id punctuation.definition.entity.css", - "r": { - "dark_plus": "source.css.less entity.other.attribute-name.id: #D7BA7D", - "light_plus": "source.css.less entity.other.attribute-name.id: #800000", - "dark_vs": "source.css.less entity.other.attribute-name.id: #D7BA7D", - "light_vs": "source.css.less entity.other.attribute-name.id: #800000", - "hc_black": "source.css.less entity.other.attribute-name.id: #D7BA7D", - "dark_modern": "source.css.less entity.other.attribute-name.id: #D7BA7D", - "hc_light": "source.css.less entity.other.attribute-name.id: #0F4A85", - "light_modern": "source.css.less entity.other.attribute-name.id: #800000" - } - }, - { - "c": "header", - "t": "source.css.less meta.selector.css entity.other.attribute-name.id", - "r": { - "dark_plus": "source.css.less entity.other.attribute-name.id: #D7BA7D", - "light_plus": "source.css.less entity.other.attribute-name.id: #800000", - "dark_vs": "source.css.less entity.other.attribute-name.id: #D7BA7D", - "light_vs": "source.css.less entity.other.attribute-name.id: #800000", - "hc_black": "source.css.less entity.other.attribute-name.id: #D7BA7D", - "dark_modern": "source.css.less entity.other.attribute-name.id: #D7BA7D", - "hc_light": "source.css.less entity.other.attribute-name.id: #0F4A85", - "light_modern": "source.css.less entity.other.attribute-name.id: #800000" - } - }, - { - "c": " ", - "t": "source.css.less", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "{", - "t": "source.css.less meta.property-list.css punctuation.section.property-list.begin.bracket.curly.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "\t", - "t": "source.css.less meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "color", - "t": "source.css.less meta.property-list.css support.type.property-name.css", - "r": { - "dark_plus": "support.type.property-name: #9CDCFE", - "light_plus": "support.type.property-name: #E50000", - "dark_vs": "support.type.property-name: #9CDCFE", - "light_vs": "support.type.property-name: #E50000", - "hc_black": "support.type.property-name: #D4D4D4", - "dark_modern": "support.type.property-name: #9CDCFE", - "hc_light": "support.type.property-name: #264F78", - "light_modern": "support.type.property-name: #E50000" - } - }, - { - "c": ":", - "t": "source.css.less meta.property-list.css punctuation.separator.key-value.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "(", - "t": "source.css.less meta.property-list.css meta.property-value.css meta.brace.round.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "@", - "t": "source.css.less meta.property-list.css meta.property-value.css variable.other.less punctuation.definition.variable.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": "base-color", - "t": "source.css.less meta.property-list.css meta.property-value.css variable.other.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css meta.property-value.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "*", - "t": "source.css.less meta.property-list.css meta.property-value.css keyword.operator.less", - "r": { - "dark_plus": "keyword.operator: #D4D4D4", - "light_plus": "keyword.operator: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4", - "dark_modern": "keyword.operator: #D4D4D4", - "hc_light": "keyword.operator: #000000", - "light_modern": "keyword.operator: #000000" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css meta.property-value.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "3", - "t": "source.css.less meta.property-list.css meta.property-value.css constant.numeric.css", - "r": { - "dark_plus": "constant.numeric: #B5CEA8", - "light_plus": "constant.numeric: #098658", - "dark_vs": "constant.numeric: #B5CEA8", - "light_vs": "constant.numeric: #098658", - "hc_black": "constant.numeric: #B5CEA8", - "dark_modern": "constant.numeric: #B5CEA8", - "hc_light": "constant.numeric: #096D48", - "light_modern": "constant.numeric: #098658" - } - }, - { - "c": ")", - "t": "source.css.less meta.property-list.css meta.property-value.css meta.brace.round.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": ";", - "t": "source.css.less meta.property-list.css punctuation.terminator.rule.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "\t", - "t": "source.css.less meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "border-left", - "t": "source.css.less meta.property-list.css support.type.property-name.css", - "r": { - "dark_plus": "support.type.property-name: #9CDCFE", - "light_plus": "support.type.property-name: #E50000", - "dark_vs": "support.type.property-name: #9CDCFE", - "light_vs": "support.type.property-name: #E50000", - "hc_black": "support.type.property-name: #D4D4D4", - "dark_modern": "support.type.property-name: #9CDCFE", - "hc_light": "support.type.property-name: #264F78", - "light_modern": "support.type.property-name: #E50000" - } - }, - { - "c": ":", - "t": "source.css.less meta.property-list.css punctuation.separator.key-value.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "@", - "t": "source.css.less meta.property-list.css meta.property-value.css variable.other.less punctuation.definition.variable.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": "the-border", - "t": "source.css.less meta.property-list.css meta.property-value.css variable.other.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": ";", - "t": "source.css.less meta.property-list.css punctuation.terminator.rule.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "\t", - "t": "source.css.less meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "border-right", - "t": "source.css.less meta.property-list.css support.type.property-name.css", - "r": { - "dark_plus": "support.type.property-name: #9CDCFE", - "light_plus": "support.type.property-name: #E50000", - "dark_vs": "support.type.property-name: #9CDCFE", - "light_vs": "support.type.property-name: #E50000", - "hc_black": "support.type.property-name: #D4D4D4", - "dark_modern": "support.type.property-name: #9CDCFE", - "hc_light": "support.type.property-name: #264F78", - "light_modern": "support.type.property-name: #E50000" - } - }, - { - "c": ":", - "t": "source.css.less meta.property-list.css punctuation.separator.key-value.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "(", - "t": "source.css.less meta.property-list.css meta.property-value.css meta.brace.round.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "@", - "t": "source.css.less meta.property-list.css meta.property-value.css variable.other.less punctuation.definition.variable.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": "the-border", - "t": "source.css.less meta.property-list.css meta.property-value.css variable.other.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css meta.property-value.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "*", - "t": "source.css.less meta.property-list.css meta.property-value.css keyword.operator.less", - "r": { - "dark_plus": "keyword.operator: #D4D4D4", - "light_plus": "keyword.operator: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4", - "dark_modern": "keyword.operator: #D4D4D4", - "hc_light": "keyword.operator: #000000", - "light_modern": "keyword.operator: #000000" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css meta.property-value.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "2", - "t": "source.css.less meta.property-list.css meta.property-value.css constant.numeric.css", - "r": { - "dark_plus": "constant.numeric: #B5CEA8", - "light_plus": "constant.numeric: #098658", - "dark_vs": "constant.numeric: #B5CEA8", - "light_vs": "constant.numeric: #098658", - "hc_black": "constant.numeric: #B5CEA8", - "dark_modern": "constant.numeric: #B5CEA8", - "hc_light": "constant.numeric: #096D48", - "light_modern": "constant.numeric: #098658" - } - }, - { - "c": ")", - "t": "source.css.less meta.property-list.css meta.property-value.css meta.brace.round.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": ";", - "t": "source.css.less meta.property-list.css punctuation.terminator.rule.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "}", - "t": "source.css.less meta.property-list.css punctuation.section.property-list.end.bracket.curly.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "#", - "t": "source.css.less meta.selector.css entity.other.attribute-name.id punctuation.definition.entity.css", - "r": { - "dark_plus": "source.css.less entity.other.attribute-name.id: #D7BA7D", - "light_plus": "source.css.less entity.other.attribute-name.id: #800000", - "dark_vs": "source.css.less entity.other.attribute-name.id: #D7BA7D", - "light_vs": "source.css.less entity.other.attribute-name.id: #800000", - "hc_black": "source.css.less entity.other.attribute-name.id: #D7BA7D", - "dark_modern": "source.css.less entity.other.attribute-name.id: #D7BA7D", - "hc_light": "source.css.less entity.other.attribute-name.id: #0F4A85", - "light_modern": "source.css.less entity.other.attribute-name.id: #800000" - } - }, - { - "c": "footer", - "t": "source.css.less meta.selector.css entity.other.attribute-name.id", - "r": { - "dark_plus": "source.css.less entity.other.attribute-name.id: #D7BA7D", - "light_plus": "source.css.less entity.other.attribute-name.id: #800000", - "dark_vs": "source.css.less entity.other.attribute-name.id: #D7BA7D", - "light_vs": "source.css.less entity.other.attribute-name.id: #800000", - "hc_black": "source.css.less entity.other.attribute-name.id: #D7BA7D", - "dark_modern": "source.css.less entity.other.attribute-name.id: #D7BA7D", - "hc_light": "source.css.less entity.other.attribute-name.id: #0F4A85", - "light_modern": "source.css.less entity.other.attribute-name.id: #800000" - } - }, - { - "c": " ", - "t": "source.css.less", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "{", - "t": "source.css.less meta.property-list.css punctuation.section.property-list.begin.bracket.curly.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "\t", - "t": "source.css.less meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "color", - "t": "source.css.less meta.property-list.css support.type.property-name.css", - "r": { - "dark_plus": "support.type.property-name: #9CDCFE", - "light_plus": "support.type.property-name: #E50000", - "dark_vs": "support.type.property-name: #9CDCFE", - "light_vs": "support.type.property-name: #E50000", - "hc_black": "support.type.property-name: #D4D4D4", - "dark_modern": "support.type.property-name: #9CDCFE", - "hc_light": "support.type.property-name: #264F78", - "light_modern": "support.type.property-name: #E50000" - } - }, - { - "c": ":", - "t": "source.css.less meta.property-list.css punctuation.separator.key-value.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "(", - "t": "source.css.less meta.property-list.css meta.property-value.css meta.brace.round.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "@", - "t": "source.css.less meta.property-list.css meta.property-value.css variable.other.less punctuation.definition.variable.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": "base-color", - "t": "source.css.less meta.property-list.css meta.property-value.css variable.other.less", - "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css meta.property-value.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "+", - "t": "source.css.less meta.property-list.css meta.property-value.css keyword.operator.less", - "r": { - "dark_plus": "keyword.operator: #D4D4D4", - "light_plus": "keyword.operator: #000000", - "dark_vs": "keyword.operator: #D4D4D4", - "light_vs": "keyword.operator: #000000", - "hc_black": "keyword.operator: #D4D4D4", - "dark_modern": "keyword.operator: #D4D4D4", - "hc_light": "keyword.operator: #000000", - "light_modern": "keyword.operator: #000000" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css meta.property-value.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "#003300", - "t": "source.css.less meta.property-list.css meta.property-value.css constant.other.rgb-value.css", - "r": { - "dark_plus": "constant.other.rgb-value: #CE9178", - "light_plus": "constant.other.rgb-value: #0451A5", - "dark_vs": "default: #D4D4D4", - "light_vs": "constant.other.rgb-value: #0451A5", - "hc_black": "constant.other.rgb-value: #CE9178", - "dark_modern": "constant.other.rgb-value: #CE9178", - "hc_light": "constant.other.rgb-value: #0451A5", - "light_modern": "constant.other.rgb-value: #0451A5" - } - }, - { - "c": ")", - "t": "source.css.less meta.property-list.css meta.property-value.css meta.brace.round.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": ";", - "t": "source.css.less meta.property-list.css punctuation.terminator.rule.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "\t", - "t": "source.css.less meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "border-color", - "t": "source.css.less meta.property-list.css support.type.property-name.css", - "r": { - "dark_plus": "support.type.property-name: #9CDCFE", - "light_plus": "support.type.property-name: #E50000", - "dark_vs": "support.type.property-name: #9CDCFE", - "light_vs": "support.type.property-name: #E50000", - "hc_black": "support.type.property-name: #D4D4D4", - "dark_modern": "support.type.property-name: #9CDCFE", - "hc_light": "support.type.property-name: #264F78", - "light_modern": "support.type.property-name: #E50000" - } - }, - { - "c": ":", - "t": "source.css.less meta.property-list.css punctuation.separator.key-value.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": " ", - "t": "source.css.less meta.property-list.css", - "r": { - "dark_plus": "default: #D4D4D4", - "light_plus": "default: #000000", - "dark_vs": "default: #D4D4D4", - "light_vs": "default: #000000", - "hc_black": "default: #FFFFFF", - "dark_modern": "default: #CCCCCC", - "hc_light": "default: #292929", - "light_modern": "default: #3B3B3B" - } - }, - { - "c": "desaturate", - "t": "source.css.less meta.property-list.css meta.property-value.css support.function.any-method.builtin.less", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less support.function.color-operation.less", "r": { "dark_plus": "support.function: #DCDCAA", "light_plus": "support.function: #795E26", @@ -4327,7 +2045,7 @@ }, { "c": "(", - "t": "source.css.less meta.property-list.css meta.property-value.css meta.brace.round.css", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less punctuation.definition.group.begin.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -4341,35 +2059,35 @@ }, { "c": "@", - "t": "source.css.less meta.property-list.css meta.property-value.css variable.other.less punctuation.definition.variable.less", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less variable.other.readwrite.less punctuation.definition.variable.less", "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" } }, { - "c": "red", - "t": "source.css.less meta.property-list.css meta.property-value.css variable.other.less", + "c": "base", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less variable.other.readwrite.less support.other.variable.less", "r": { - "dark_plus": "variable.other.less: #9CDCFE", - "light_plus": "variable.other.less: #E50000", - "dark_vs": "variable.other.less: #9CDCFE", - "light_vs": "variable.other.less: #E50000", - "hc_black": "variable.other.less: #D4D4D4", - "dark_modern": "variable.other.less: #9CDCFE", - "hc_light": "variable.other.less: #264F78", - "light_modern": "variable.other.less: #E50000" + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" } }, { "c": ",", - "t": "source.css.less meta.property-list.css meta.property-value.css punctuation.separator.list.comma.css", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less punctuation.separator.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -4383,7 +2101,7 @@ }, { "c": " ", - "t": "source.css.less meta.property-list.css meta.property-value.css", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -4396,8 +2114,8 @@ } }, { - "c": "10", - "t": "source.css.less meta.property-list.css meta.property-value.css constant.numeric.css", + "c": "30", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less constant.numeric.less", "r": { "dark_plus": "constant.numeric: #B5CEA8", "light_plus": "constant.numeric: #098658", @@ -4411,7 +2129,7 @@ }, { "c": "%", - "t": "source.css.less meta.property-list.css meta.property-value.css constant.numeric.css keyword.other.unit.percentage.css", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less constant.numeric.less keyword.other.unit.less", "r": { "dark_plus": "keyword.other.unit: #B5CEA8", "light_plus": "keyword.other.unit: #098658", @@ -4425,7 +2143,7 @@ }, { "c": ")", - "t": "source.css.less meta.property-list.css meta.property-value.css meta.brace.round.css", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less punctuation.definition.group.end.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -4439,7 +2157,329 @@ }, { "c": ";", - "t": "source.css.less meta.property-list.css punctuation.terminator.rule.css", + "t": "source.css.less meta.property-list.less punctuation.terminator.rule.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "\t", + "t": "source.css.less meta.property-list.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "div", + "t": "source.css.less meta.property-list.less meta.selector.less entity.name.tag.less", + "r": { + "dark_plus": "entity.name.tag.less: #D7BA7D", + "light_plus": "entity.name.tag: #800000", + "dark_vs": "entity.name.tag.less: #D7BA7D", + "light_vs": "entity.name.tag: #800000", + "hc_black": "entity.name.tag.less: #D7BA7D", + "dark_modern": "entity.name.tag.less: #D7BA7D", + "hc_light": "entity.name.tag: #0F4A85", + "light_modern": "entity.name.tag: #800000" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.selector.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "{", + "t": "source.css.less meta.property-list.less meta.property-list.less punctuation.definition.block.begin.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "\t\t", + "t": "source.css.less meta.property-list.less meta.property-list.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": ".", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.selector.less entity.other.attribute-name.class.less punctuation.definition.entity.less", + "r": { + "dark_plus": "source.css entity.other.attribute-name.class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.class: #800000", + "dark_vs": "source.css entity.other.attribute-name.class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.class: #800000", + "hc_black": "source.css entity.other.attribute-name.class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.class: #800000" + } + }, + { + "c": "box-shadow", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.selector.less entity.other.attribute-name.class.less", + "r": { + "dark_plus": "source.css entity.other.attribute-name.class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.class: #800000", + "dark_vs": "source.css entity.other.attribute-name.class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.class: #800000", + "hc_black": "source.css entity.other.attribute-name.class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.class: #800000" + } + }, + { + "c": "(", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.selector.less meta.group.less punctuation.definition.group.begin.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "(", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.selector.less meta.group.less meta.group.less punctuation.definition.group.begin.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "0", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.selector.less meta.group.less meta.group.less constant.numeric.less", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8", + "dark_modern": "constant.numeric: #B5CEA8", + "hc_light": "constant.numeric: #096D48", + "light_modern": "constant.numeric: #098658" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.selector.less meta.group.less meta.group.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "0", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.selector.less meta.group.less meta.group.less constant.numeric.less", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8", + "dark_modern": "constant.numeric: #B5CEA8", + "hc_light": "constant.numeric: #096D48", + "light_modern": "constant.numeric: #098658" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.selector.less meta.group.less meta.group.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "5", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.selector.less meta.group.less meta.group.less constant.numeric.less", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8", + "dark_modern": "constant.numeric: #B5CEA8", + "hc_light": "constant.numeric: #096D48", + "light_modern": "constant.numeric: #098658" + } + }, + { + "c": "px", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.selector.less meta.group.less meta.group.less constant.numeric.less keyword.other.unit.less", + "r": { + "dark_plus": "keyword.other.unit: #B5CEA8", + "light_plus": "keyword.other.unit: #098658", + "dark_vs": "keyword.other.unit: #B5CEA8", + "light_vs": "keyword.other.unit: #098658", + "hc_black": "keyword.other.unit: #B5CEA8", + "dark_modern": "keyword.other.unit: #B5CEA8", + "hc_light": "keyword.other.unit: #096D48", + "light_modern": "keyword.other.unit: #098658" + } + }, + { + "c": ")", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.selector.less meta.group.less meta.group.less punctuation.definition.group.end.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": ",", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.selector.less meta.group.less punctuation.separator.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.selector.less meta.group.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "30", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.selector.less meta.group.less constant.numeric.less", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8", + "dark_modern": "constant.numeric: #B5CEA8", + "hc_light": "constant.numeric: #096D48", + "light_modern": "constant.numeric: #098658" + } + }, + { + "c": "%", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.selector.less meta.group.less constant.numeric.less keyword.other.unit.less", + "r": { + "dark_plus": "keyword.other.unit: #B5CEA8", + "light_plus": "keyword.other.unit: #098658", + "dark_vs": "keyword.other.unit: #B5CEA8", + "light_vs": "keyword.other.unit: #098658", + "hc_black": "keyword.other.unit: #B5CEA8", + "dark_modern": "keyword.other.unit: #B5CEA8", + "hc_light": "keyword.other.unit: #096D48", + "light_modern": "keyword.other.unit: #098658" + } + }, + { + "c": ")", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.selector.less meta.group.less punctuation.definition.group.end.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": ";", + "t": "source.css.less meta.property-list.less meta.property-list.less punctuation.terminator.rule.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "\t", + "t": "source.css.less meta.property-list.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", @@ -4453,7 +2493,2177 @@ }, { "c": "}", - "t": "source.css.less meta.property-list.css punctuation.section.property-list.end.bracket.curly.css", + "t": "source.css.less meta.property-list.less punctuation.definition.block.end.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "}", + "t": "source.css.less punctuation.definition.block.end.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "#", + "t": "source.css.less meta.selector.less entity.other.attribute-name.id.less punctuation.definition.entity.less", + "r": { + "dark_plus": "source.css.less entity.other.attribute-name.id: #D7BA7D", + "light_plus": "source.css.less entity.other.attribute-name.id: #800000", + "dark_vs": "source.css.less entity.other.attribute-name.id: #D7BA7D", + "light_vs": "source.css.less entity.other.attribute-name.id: #800000", + "hc_black": "source.css.less entity.other.attribute-name.id: #D7BA7D", + "dark_modern": "source.css.less entity.other.attribute-name.id: #D7BA7D", + "hc_light": "source.css.less entity.other.attribute-name.id: #0F4A85", + "light_modern": "source.css.less entity.other.attribute-name.id: #800000" + } + }, + { + "c": "header", + "t": "source.css.less meta.selector.less entity.other.attribute-name.id.less", + "r": { + "dark_plus": "source.css.less entity.other.attribute-name.id: #D7BA7D", + "light_plus": "source.css.less entity.other.attribute-name.id: #800000", + "dark_vs": "source.css.less entity.other.attribute-name.id: #D7BA7D", + "light_vs": "source.css.less entity.other.attribute-name.id: #800000", + "hc_black": "source.css.less entity.other.attribute-name.id: #D7BA7D", + "dark_modern": "source.css.less entity.other.attribute-name.id: #D7BA7D", + "hc_light": "source.css.less entity.other.attribute-name.id: #0F4A85", + "light_modern": "source.css.less entity.other.attribute-name.id: #800000" + } + }, + { + "c": " ", + "t": "source.css.less meta.selector.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "{", + "t": "source.css.less meta.property-list.less punctuation.definition.block.begin.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "\t", + "t": "source.css.less meta.property-list.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "h1", + "t": "source.css.less meta.property-list.less meta.selector.less entity.name.tag.less", + "r": { + "dark_plus": "entity.name.tag.less: #D7BA7D", + "light_plus": "entity.name.tag: #800000", + "dark_vs": "entity.name.tag.less: #D7BA7D", + "light_vs": "entity.name.tag: #800000", + "hc_black": "entity.name.tag.less: #D7BA7D", + "dark_modern": "entity.name.tag.less: #D7BA7D", + "hc_light": "entity.name.tag: #0F4A85", + "light_modern": "entity.name.tag: #800000" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.selector.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "{", + "t": "source.css.less meta.property-list.less meta.property-list.less punctuation.definition.block.begin.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "\t\t", + "t": "source.css.less meta.property-list.less meta.property-list.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "font-size", + "t": "source.css.less meta.property-list.less meta.property-list.less support.type.property-name.less", + "r": { + "dark_plus": "support.type.property-name: #9CDCFE", + "light_plus": "support.type.property-name: #E50000", + "dark_vs": "support.type.property-name: #9CDCFE", + "light_vs": "support.type.property-name: #E50000", + "hc_black": "support.type.property-name: #D4D4D4", + "dark_modern": "support.type.property-name: #9CDCFE", + "hc_light": "support.type.property-name: #264F78", + "light_modern": "support.type.property-name: #E50000" + } + }, + { + "c": ":", + "t": "source.css.less meta.property-list.less meta.property-list.less punctuation.separator.key-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.property-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "26", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.property-value.less constant.numeric.less", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8", + "dark_modern": "constant.numeric: #B5CEA8", + "hc_light": "constant.numeric: #096D48", + "light_modern": "constant.numeric: #098658" + } + }, + { + "c": "px", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.property-value.less constant.numeric.less keyword.other.unit.less", + "r": { + "dark_plus": "keyword.other.unit: #B5CEA8", + "light_plus": "keyword.other.unit: #098658", + "dark_vs": "keyword.other.unit: #B5CEA8", + "light_vs": "keyword.other.unit: #098658", + "hc_black": "keyword.other.unit: #B5CEA8", + "dark_modern": "keyword.other.unit: #B5CEA8", + "hc_light": "keyword.other.unit: #096D48", + "light_modern": "keyword.other.unit: #098658" + } + }, + { + "c": ";", + "t": "source.css.less meta.property-list.less meta.property-list.less punctuation.terminator.rule.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "\t\t", + "t": "source.css.less meta.property-list.less meta.property-list.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "font-weight", + "t": "source.css.less meta.property-list.less meta.property-list.less support.type.property-name.less", + "r": { + "dark_plus": "support.type.property-name: #9CDCFE", + "light_plus": "support.type.property-name: #E50000", + "dark_vs": "support.type.property-name: #9CDCFE", + "light_vs": "support.type.property-name: #E50000", + "hc_black": "support.type.property-name: #D4D4D4", + "dark_modern": "support.type.property-name: #9CDCFE", + "hc_light": "support.type.property-name: #264F78", + "light_modern": "support.type.property-name: #E50000" + } + }, + { + "c": ":", + "t": "source.css.less meta.property-list.less meta.property-list.less punctuation.separator.key-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.property-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "bold", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.property-value.less support.constant.property-value.less", + "r": { + "dark_plus": "support.constant.property-value: #CE9178", + "light_plus": "support.constant.property-value: #0451A5", + "dark_vs": "default: #D4D4D4", + "light_vs": "support.constant.property-value: #0451A5", + "hc_black": "support.constant.property-value: #CE9178", + "dark_modern": "support.constant.property-value: #CE9178", + "hc_light": "support.constant.property-value: #0451A5", + "light_modern": "support.constant.property-value: #0451A5" + } + }, + { + "c": ";", + "t": "source.css.less meta.property-list.less meta.property-list.less punctuation.terminator.rule.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "\t", + "t": "source.css.less meta.property-list.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "}", + "t": "source.css.less meta.property-list.less punctuation.definition.block.end.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "\t", + "t": "source.css.less meta.property-list.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "p", + "t": "source.css.less meta.property-list.less meta.selector.less entity.name.tag.less", + "r": { + "dark_plus": "entity.name.tag.less: #D7BA7D", + "light_plus": "entity.name.tag: #800000", + "dark_vs": "entity.name.tag.less: #D7BA7D", + "light_vs": "entity.name.tag: #800000", + "hc_black": "entity.name.tag.less: #D7BA7D", + "dark_modern": "entity.name.tag.less: #D7BA7D", + "hc_light": "entity.name.tag: #0F4A85", + "light_modern": "entity.name.tag: #800000" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.selector.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "{", + "t": "source.css.less meta.property-list.less meta.property-list.less punctuation.definition.block.begin.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.property-list.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "font-size", + "t": "source.css.less meta.property-list.less meta.property-list.less support.type.property-name.less", + "r": { + "dark_plus": "support.type.property-name: #9CDCFE", + "light_plus": "support.type.property-name: #E50000", + "dark_vs": "support.type.property-name: #9CDCFE", + "light_vs": "support.type.property-name: #E50000", + "hc_black": "support.type.property-name: #D4D4D4", + "dark_modern": "support.type.property-name: #9CDCFE", + "hc_light": "support.type.property-name: #264F78", + "light_modern": "support.type.property-name: #E50000" + } + }, + { + "c": ":", + "t": "source.css.less meta.property-list.less meta.property-list.less punctuation.separator.key-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.property-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "12", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.property-value.less constant.numeric.less", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8", + "dark_modern": "constant.numeric: #B5CEA8", + "hc_light": "constant.numeric: #096D48", + "light_modern": "constant.numeric: #098658" + } + }, + { + "c": "px", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.property-value.less constant.numeric.less keyword.other.unit.less", + "r": { + "dark_plus": "keyword.other.unit: #B5CEA8", + "light_plus": "keyword.other.unit: #098658", + "dark_vs": "keyword.other.unit: #B5CEA8", + "light_vs": "keyword.other.unit: #098658", + "hc_black": "keyword.other.unit: #B5CEA8", + "dark_modern": "keyword.other.unit: #B5CEA8", + "hc_light": "keyword.other.unit: #096D48", + "light_modern": "keyword.other.unit: #098658" + } + }, + { + "c": ";", + "t": "source.css.less meta.property-list.less meta.property-list.less punctuation.terminator.rule.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "\t\t", + "t": "source.css.less meta.property-list.less meta.property-list.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "a", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.selector.less entity.name.tag.less", + "r": { + "dark_plus": "entity.name.tag.less: #D7BA7D", + "light_plus": "entity.name.tag: #800000", + "dark_vs": "entity.name.tag.less: #D7BA7D", + "light_vs": "entity.name.tag: #800000", + "hc_black": "entity.name.tag.less: #D7BA7D", + "dark_modern": "entity.name.tag.less: #D7BA7D", + "hc_light": "entity.name.tag: #0F4A85", + "light_modern": "entity.name.tag: #800000" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.selector.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "{", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.property-list.less punctuation.definition.block.begin.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.property-list.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "text-decoration", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.property-list.less support.type.property-name.less", + "r": { + "dark_plus": "support.type.property-name: #9CDCFE", + "light_plus": "support.type.property-name: #E50000", + "dark_vs": "support.type.property-name: #9CDCFE", + "light_vs": "support.type.property-name: #E50000", + "hc_black": "support.type.property-name: #D4D4D4", + "dark_modern": "support.type.property-name: #9CDCFE", + "hc_light": "support.type.property-name: #264F78", + "light_modern": "support.type.property-name: #E50000" + } + }, + { + "c": ":", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.property-list.less punctuation.separator.key-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.property-list.less meta.property-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "none", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.property-list.less meta.property-value.less support.constant.property-value.less", + "r": { + "dark_plus": "support.constant.property-value: #CE9178", + "light_plus": "support.constant.property-value: #0451A5", + "dark_vs": "default: #D4D4D4", + "light_vs": "support.constant.property-value: #0451A5", + "hc_black": "support.constant.property-value: #CE9178", + "dark_modern": "support.constant.property-value: #CE9178", + "hc_light": "support.constant.property-value: #0451A5", + "light_modern": "support.constant.property-value: #0451A5" + } + }, + { + "c": ";", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.property-list.less punctuation.terminator.rule.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "\t\t\t", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.property-list.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "&", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.property-list.less meta.selector.less entity.other.attribute-name.parent.less punctuation.definition.entity.less", + "r": { + "dark_plus": "entity.other.attribute-name.parent.less: #D7BA7D", + "light_plus": "entity.other.attribute-name.parent.less: #800000", + "dark_vs": "entity.other.attribute-name.parent.less: #D7BA7D", + "light_vs": "entity.other.attribute-name.parent.less: #800000", + "hc_black": "entity.other.attribute-name.parent.less: #D7BA7D", + "dark_modern": "entity.other.attribute-name.parent.less: #D7BA7D", + "hc_light": "entity.other.attribute-name.parent.less: #0F4A85", + "light_modern": "entity.other.attribute-name.parent.less: #800000" + } + }, + { + "c": ":", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.property-list.less meta.selector.less meta.function-call.less punctuation.definition.entity.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "hover", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.property-list.less meta.selector.less meta.function-call.less entity.other.attribute-name.pseudo-class.less", + "r": { + "dark_plus": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.pseudo-class: #800000", + "dark_vs": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.pseudo-class: #800000", + "hc_black": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.pseudo-class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.pseudo-class: #800000" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.property-list.less meta.selector.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "{", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.property-list.less meta.property-list.less punctuation.definition.block.begin.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.property-list.less meta.property-list.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "border-width", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.property-list.less meta.property-list.less support.type.property-name.less", + "r": { + "dark_plus": "support.type.property-name: #9CDCFE", + "light_plus": "support.type.property-name: #E50000", + "dark_vs": "support.type.property-name: #9CDCFE", + "light_vs": "support.type.property-name: #E50000", + "hc_black": "support.type.property-name: #D4D4D4", + "dark_modern": "support.type.property-name: #9CDCFE", + "hc_light": "support.type.property-name: #264F78", + "light_modern": "support.type.property-name: #E50000" + } + }, + { + "c": ":", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.property-list.less meta.property-list.less punctuation.separator.key-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.property-list.less meta.property-list.less meta.property-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "1", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.property-list.less meta.property-list.less meta.property-value.less constant.numeric.less", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8", + "dark_modern": "constant.numeric: #B5CEA8", + "hc_light": "constant.numeric: #096D48", + "light_modern": "constant.numeric: #098658" + } + }, + { + "c": "px", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.property-list.less meta.property-list.less meta.property-value.less constant.numeric.less keyword.other.unit.less", + "r": { + "dark_plus": "keyword.other.unit: #B5CEA8", + "light_plus": "keyword.other.unit: #098658", + "dark_vs": "keyword.other.unit: #B5CEA8", + "light_vs": "keyword.other.unit: #098658", + "hc_black": "keyword.other.unit: #B5CEA8", + "dark_modern": "keyword.other.unit: #B5CEA8", + "hc_light": "keyword.other.unit: #096D48", + "light_modern": "keyword.other.unit: #098658" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.property-list.less meta.property-list.less meta.property-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "}", + "t": "source.css.less meta.property-list.less meta.property-list.less meta.property-list.less punctuation.definition.block.end.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "\t\t", + "t": "source.css.less meta.property-list.less meta.property-list.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "}", + "t": "source.css.less meta.property-list.less meta.property-list.less punctuation.definition.block.end.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "\t", + "t": "source.css.less meta.property-list.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "}", + "t": "source.css.less meta.property-list.less punctuation.definition.block.end.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "}", + "t": "source.css.less punctuation.definition.block.end.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "@", + "t": "source.css.less meta.property-value.less variable.other.readwrite.less punctuation.definition.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": "the-border", + "t": "source.css.less meta.property-value.less variable.other.readwrite.less support.other.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": ":", + "t": "source.css.less meta.property-value.less punctuation.separator.key-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-value.less meta.property-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "1", + "t": "source.css.less meta.property-value.less constant.numeric.less", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8", + "dark_modern": "constant.numeric: #B5CEA8", + "hc_light": "constant.numeric: #096D48", + "light_modern": "constant.numeric: #098658" + } + }, + { + "c": "px", + "t": "source.css.less meta.property-value.less constant.numeric.less keyword.other.unit.less", + "r": { + "dark_plus": "keyword.other.unit: #B5CEA8", + "light_plus": "keyword.other.unit: #098658", + "dark_vs": "keyword.other.unit: #B5CEA8", + "light_vs": "keyword.other.unit: #098658", + "hc_black": "keyword.other.unit: #B5CEA8", + "dark_modern": "keyword.other.unit: #B5CEA8", + "hc_light": "keyword.other.unit: #096D48", + "light_modern": "keyword.other.unit: #098658" + } + }, + { + "c": ";", + "t": "source.css.less meta.property-value.less punctuation.terminator.rule.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "@", + "t": "source.css.less meta.property-value.less variable.other.readwrite.less punctuation.definition.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": "base-color", + "t": "source.css.less meta.property-value.less variable.other.readwrite.less support.other.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": ":", + "t": "source.css.less meta.property-value.less punctuation.separator.key-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-value.less meta.property-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "#", + "t": "source.css.less meta.property-value.less constant.other.color.rgb-value.less punctuation.definition.constant.less", + "r": { + "dark_plus": "constant.other.color.rgb-value: #CE9178", + "light_plus": "constant.other.color.rgb-value: #0451A5", + "dark_vs": "default: #D4D4D4", + "light_vs": "constant.other.color.rgb-value: #0451A5", + "hc_black": "constant.other.color.rgb-value: #CE9178", + "dark_modern": "constant.other.color.rgb-value: #CE9178", + "hc_light": "constant.other.color.rgb-value: #0451A5", + "light_modern": "constant.other.color.rgb-value: #0451A5" + } + }, + { + "c": "111", + "t": "source.css.less meta.property-value.less constant.other.color.rgb-value.less", + "r": { + "dark_plus": "constant.other.color.rgb-value: #CE9178", + "light_plus": "constant.other.color.rgb-value: #0451A5", + "dark_vs": "default: #D4D4D4", + "light_vs": "constant.other.color.rgb-value: #0451A5", + "hc_black": "constant.other.color.rgb-value: #CE9178", + "dark_modern": "constant.other.color.rgb-value: #CE9178", + "hc_light": "constant.other.color.rgb-value: #0451A5", + "light_modern": "constant.other.color.rgb-value: #0451A5" + } + }, + { + "c": ";", + "t": "source.css.less meta.property-value.less punctuation.terminator.rule.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "@", + "t": "source.css.less meta.property-value.less variable.other.readwrite.less punctuation.definition.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": "red", + "t": "source.css.less meta.property-value.less variable.other.readwrite.less support.other.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": ":", + "t": "source.css.less meta.property-value.less punctuation.separator.key-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-value.less meta.property-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "#", + "t": "source.css.less meta.property-value.less constant.other.color.rgb-value.less punctuation.definition.constant.less", + "r": { + "dark_plus": "constant.other.color.rgb-value: #CE9178", + "light_plus": "constant.other.color.rgb-value: #0451A5", + "dark_vs": "default: #D4D4D4", + "light_vs": "constant.other.color.rgb-value: #0451A5", + "hc_black": "constant.other.color.rgb-value: #CE9178", + "dark_modern": "constant.other.color.rgb-value: #CE9178", + "hc_light": "constant.other.color.rgb-value: #0451A5", + "light_modern": "constant.other.color.rgb-value: #0451A5" + } + }, + { + "c": "842210", + "t": "source.css.less meta.property-value.less constant.other.color.rgb-value.less", + "r": { + "dark_plus": "constant.other.color.rgb-value: #CE9178", + "light_plus": "constant.other.color.rgb-value: #0451A5", + "dark_vs": "default: #D4D4D4", + "light_vs": "constant.other.color.rgb-value: #0451A5", + "hc_black": "constant.other.color.rgb-value: #CE9178", + "dark_modern": "constant.other.color.rgb-value: #CE9178", + "hc_light": "constant.other.color.rgb-value: #0451A5", + "light_modern": "constant.other.color.rgb-value: #0451A5" + } + }, + { + "c": ";", + "t": "source.css.less meta.property-value.less punctuation.terminator.rule.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "#", + "t": "source.css.less meta.selector.less entity.other.attribute-name.id.less punctuation.definition.entity.less", + "r": { + "dark_plus": "source.css.less entity.other.attribute-name.id: #D7BA7D", + "light_plus": "source.css.less entity.other.attribute-name.id: #800000", + "dark_vs": "source.css.less entity.other.attribute-name.id: #D7BA7D", + "light_vs": "source.css.less entity.other.attribute-name.id: #800000", + "hc_black": "source.css.less entity.other.attribute-name.id: #D7BA7D", + "dark_modern": "source.css.less entity.other.attribute-name.id: #D7BA7D", + "hc_light": "source.css.less entity.other.attribute-name.id: #0F4A85", + "light_modern": "source.css.less entity.other.attribute-name.id: #800000" + } + }, + { + "c": "header", + "t": "source.css.less meta.selector.less entity.other.attribute-name.id.less", + "r": { + "dark_plus": "source.css.less entity.other.attribute-name.id: #D7BA7D", + "light_plus": "source.css.less entity.other.attribute-name.id: #800000", + "dark_vs": "source.css.less entity.other.attribute-name.id: #D7BA7D", + "light_vs": "source.css.less entity.other.attribute-name.id: #800000", + "hc_black": "source.css.less entity.other.attribute-name.id: #D7BA7D", + "dark_modern": "source.css.less entity.other.attribute-name.id: #D7BA7D", + "hc_light": "source.css.less entity.other.attribute-name.id: #0F4A85", + "light_modern": "source.css.less entity.other.attribute-name.id: #800000" + } + }, + { + "c": " ", + "t": "source.css.less meta.selector.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "{", + "t": "source.css.less meta.property-list.less punctuation.definition.block.begin.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "\t", + "t": "source.css.less meta.property-list.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "color", + "t": "source.css.less meta.property-list.less support.type.property-name.less", + "r": { + "dark_plus": "support.type.property-name: #9CDCFE", + "light_plus": "support.type.property-name: #E50000", + "dark_vs": "support.type.property-name: #9CDCFE", + "light_vs": "support.type.property-name: #E50000", + "hc_black": "support.type.property-name: #D4D4D4", + "dark_modern": "support.type.property-name: #9CDCFE", + "hc_light": "support.type.property-name: #264F78", + "light_modern": "support.type.property-name: #E50000" + } + }, + { + "c": ":", + "t": "source.css.less meta.property-list.less punctuation.separator.key-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.property-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "(", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less punctuation.definition.group.begin.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "@", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less variable.other.readwrite.less punctuation.definition.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": "base-color", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less variable.other.readwrite.less support.other.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "*", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less keyword.operator.arithmetic.less", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4", + "dark_modern": "keyword.operator: #D4D4D4", + "hc_light": "keyword.operator: #000000", + "light_modern": "keyword.operator: #000000" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "3", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less constant.numeric.less", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8", + "dark_modern": "constant.numeric: #B5CEA8", + "hc_light": "constant.numeric: #096D48", + "light_modern": "constant.numeric: #098658" + } + }, + { + "c": ")", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less punctuation.definition.group.end.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": ";", + "t": "source.css.less meta.property-list.less punctuation.terminator.rule.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "\t", + "t": "source.css.less meta.property-list.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "border-left", + "t": "source.css.less meta.property-list.less support.type.property-name.less", + "r": { + "dark_plus": "support.type.property-name: #9CDCFE", + "light_plus": "support.type.property-name: #E50000", + "dark_vs": "support.type.property-name: #9CDCFE", + "light_vs": "support.type.property-name: #E50000", + "hc_black": "support.type.property-name: #D4D4D4", + "dark_modern": "support.type.property-name: #9CDCFE", + "hc_light": "support.type.property-name: #264F78", + "light_modern": "support.type.property-name: #E50000" + } + }, + { + "c": ":", + "t": "source.css.less meta.property-list.less punctuation.separator.key-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.property-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "@", + "t": "source.css.less meta.property-list.less meta.property-value.less variable.other.readwrite.less punctuation.definition.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": "the-border", + "t": "source.css.less meta.property-list.less meta.property-value.less variable.other.readwrite.less support.other.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": ";", + "t": "source.css.less meta.property-list.less punctuation.terminator.rule.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "\t", + "t": "source.css.less meta.property-list.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "border-right", + "t": "source.css.less meta.property-list.less support.type.property-name.less", + "r": { + "dark_plus": "support.type.property-name: #9CDCFE", + "light_plus": "support.type.property-name: #E50000", + "dark_vs": "support.type.property-name: #9CDCFE", + "light_vs": "support.type.property-name: #E50000", + "hc_black": "support.type.property-name: #D4D4D4", + "dark_modern": "support.type.property-name: #9CDCFE", + "hc_light": "support.type.property-name: #264F78", + "light_modern": "support.type.property-name: #E50000" + } + }, + { + "c": ":", + "t": "source.css.less meta.property-list.less punctuation.separator.key-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.property-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "(", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less punctuation.definition.group.begin.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "@", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less variable.other.readwrite.less punctuation.definition.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": "the-border", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less variable.other.readwrite.less support.other.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "*", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less keyword.operator.arithmetic.less", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4", + "dark_modern": "keyword.operator: #D4D4D4", + "hc_light": "keyword.operator: #000000", + "light_modern": "keyword.operator: #000000" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "2", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less constant.numeric.less", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8", + "dark_modern": "constant.numeric: #B5CEA8", + "hc_light": "constant.numeric: #096D48", + "light_modern": "constant.numeric: #098658" + } + }, + { + "c": ")", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less punctuation.definition.group.end.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": ";", + "t": "source.css.less meta.property-list.less punctuation.terminator.rule.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "}", + "t": "source.css.less punctuation.definition.block.end.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "#", + "t": "source.css.less meta.selector.less entity.other.attribute-name.id.less punctuation.definition.entity.less", + "r": { + "dark_plus": "source.css.less entity.other.attribute-name.id: #D7BA7D", + "light_plus": "source.css.less entity.other.attribute-name.id: #800000", + "dark_vs": "source.css.less entity.other.attribute-name.id: #D7BA7D", + "light_vs": "source.css.less entity.other.attribute-name.id: #800000", + "hc_black": "source.css.less entity.other.attribute-name.id: #D7BA7D", + "dark_modern": "source.css.less entity.other.attribute-name.id: #D7BA7D", + "hc_light": "source.css.less entity.other.attribute-name.id: #0F4A85", + "light_modern": "source.css.less entity.other.attribute-name.id: #800000" + } + }, + { + "c": "footer", + "t": "source.css.less meta.selector.less entity.other.attribute-name.id.less", + "r": { + "dark_plus": "source.css.less entity.other.attribute-name.id: #D7BA7D", + "light_plus": "source.css.less entity.other.attribute-name.id: #800000", + "dark_vs": "source.css.less entity.other.attribute-name.id: #D7BA7D", + "light_vs": "source.css.less entity.other.attribute-name.id: #800000", + "hc_black": "source.css.less entity.other.attribute-name.id: #D7BA7D", + "dark_modern": "source.css.less entity.other.attribute-name.id: #D7BA7D", + "hc_light": "source.css.less entity.other.attribute-name.id: #0F4A85", + "light_modern": "source.css.less entity.other.attribute-name.id: #800000" + } + }, + { + "c": " ", + "t": "source.css.less meta.selector.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "{", + "t": "source.css.less meta.property-list.less punctuation.definition.block.begin.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "\t", + "t": "source.css.less meta.property-list.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "color", + "t": "source.css.less meta.property-list.less support.type.property-name.less", + "r": { + "dark_plus": "support.type.property-name: #9CDCFE", + "light_plus": "support.type.property-name: #E50000", + "dark_vs": "support.type.property-name: #9CDCFE", + "light_vs": "support.type.property-name: #E50000", + "hc_black": "support.type.property-name: #D4D4D4", + "dark_modern": "support.type.property-name: #9CDCFE", + "hc_light": "support.type.property-name: #264F78", + "light_modern": "support.type.property-name: #E50000" + } + }, + { + "c": ":", + "t": "source.css.less meta.property-list.less punctuation.separator.key-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.property-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "(", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less punctuation.definition.group.begin.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "@", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less variable.other.readwrite.less punctuation.definition.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": "base-color", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less variable.other.readwrite.less support.other.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "+", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less keyword.operator.arithmetic.less", + "r": { + "dark_plus": "keyword.operator: #D4D4D4", + "light_plus": "keyword.operator: #000000", + "dark_vs": "keyword.operator: #D4D4D4", + "light_vs": "keyword.operator: #000000", + "hc_black": "keyword.operator: #D4D4D4", + "dark_modern": "keyword.operator: #D4D4D4", + "hc_light": "keyword.operator: #000000", + "light_modern": "keyword.operator: #000000" + } + }, + { + "c": " #", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "003300", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less constant.numeric.less", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8", + "dark_modern": "constant.numeric: #B5CEA8", + "hc_light": "constant.numeric: #096D48", + "light_modern": "constant.numeric: #098658" + } + }, + { + "c": ")", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.group.less punctuation.definition.group.end.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": ";", + "t": "source.css.less meta.property-list.less punctuation.terminator.rule.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "\t", + "t": "source.css.less meta.property-list.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "border-color", + "t": "source.css.less meta.property-list.less support.type.property-name.less", + "r": { + "dark_plus": "support.type.property-name: #9CDCFE", + "light_plus": "support.type.property-name: #E50000", + "dark_vs": "support.type.property-name: #9CDCFE", + "light_vs": "support.type.property-name: #E50000", + "hc_black": "support.type.property-name: #D4D4D4", + "dark_modern": "support.type.property-name: #9CDCFE", + "hc_light": "support.type.property-name: #264F78", + "light_modern": "support.type.property-name: #E50000" + } + }, + { + "c": ":", + "t": "source.css.less meta.property-list.less punctuation.separator.key-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.property-value.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "desaturate", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less support.function.color-operation.less", + "r": { + "dark_plus": "support.function: #DCDCAA", + "light_plus": "support.function: #795E26", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "support.function: #DCDCAA", + "dark_modern": "support.function: #DCDCAA", + "hc_light": "support.function: #5E2CBC", + "light_modern": "support.function: #795E26" + } + }, + { + "c": "(", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less punctuation.definition.group.begin.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "@", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less variable.other.readwrite.less punctuation.definition.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": "red", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less variable.other.readwrite.less support.other.variable.less", + "r": { + "dark_plus": "source.css.less variable.other: #9CDCFE", + "light_plus": "source.css.less variable.other: #E50000", + "dark_vs": "source.css.less variable.other: #9CDCFE", + "light_vs": "source.css.less variable.other: #E50000", + "hc_black": "source.css.less variable.other: #D4D4D4", + "dark_modern": "source.css.less variable.other: #9CDCFE", + "hc_light": "source.css.less variable.other: #264F78", + "light_modern": "source.css.less variable.other: #E50000" + } + }, + { + "c": ",", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less punctuation.separator.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": " ", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "10", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less constant.numeric.less", + "r": { + "dark_plus": "constant.numeric: #B5CEA8", + "light_plus": "constant.numeric: #098658", + "dark_vs": "constant.numeric: #B5CEA8", + "light_vs": "constant.numeric: #098658", + "hc_black": "constant.numeric: #B5CEA8", + "dark_modern": "constant.numeric: #B5CEA8", + "hc_light": "constant.numeric: #096D48", + "light_modern": "constant.numeric: #098658" + } + }, + { + "c": "%", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less constant.numeric.less keyword.other.unit.less", + "r": { + "dark_plus": "keyword.other.unit: #B5CEA8", + "light_plus": "keyword.other.unit: #098658", + "dark_vs": "keyword.other.unit: #B5CEA8", + "light_vs": "keyword.other.unit: #098658", + "hc_black": "keyword.other.unit: #B5CEA8", + "dark_modern": "keyword.other.unit: #B5CEA8", + "hc_light": "keyword.other.unit: #096D48", + "light_modern": "keyword.other.unit: #098658" + } + }, + { + "c": ")", + "t": "source.css.less meta.property-list.less meta.property-value.less meta.function-call.less punctuation.definition.group.end.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": ";", + "t": "source.css.less meta.property-list.less punctuation.terminator.rule.less", + "r": { + "dark_plus": "default: #D4D4D4", + "light_plus": "default: #000000", + "dark_vs": "default: #D4D4D4", + "light_vs": "default: #000000", + "hc_black": "default: #FFFFFF", + "dark_modern": "default: #CCCCCC", + "hc_light": "default: #292929", + "light_modern": "default: #3B3B3B" + } + }, + { + "c": "}", + "t": "source.css.less punctuation.definition.block.end.less", "r": { "dark_plus": "default: #D4D4D4", "light_plus": "default: #000000", diff --git a/extensions/vscode-colorize-tests/test/colorize-results/test_scss.json b/extensions/vscode-colorize-tests/test/colorize-results/test_scss.json index 843cf288784..b316b8a0531 100644 --- a/extensions/vscode-colorize-tests/test/colorize-results/test_scss.json +++ b/extensions/vscode-colorize-tests/test/colorize-results/test_scss.json @@ -1333,28 +1333,28 @@ "c": ":", "t": "source.css.scss meta.property-list.scss meta.property-list.scss entity.other.attribute-name.pseudo-class.css punctuation.definition.entity.css", "r": { - "dark_plus": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.pseudo-class.css: #800000", - "dark_vs": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.pseudo-class.css: #800000", - "hc_black": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.pseudo-class.css: #0F4A85", - "light_modern": "entity.other.attribute-name.pseudo-class.css: #800000" + "dark_plus": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.pseudo-class: #800000", + "dark_vs": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.pseudo-class: #800000", + "hc_black": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.pseudo-class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.pseudo-class: #800000" } }, { "c": "hover", "t": "source.css.scss meta.property-list.scss meta.property-list.scss entity.other.attribute-name.pseudo-class.css", "r": { - "dark_plus": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_plus": "entity.other.attribute-name.pseudo-class.css: #800000", - "dark_vs": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "light_vs": "entity.other.attribute-name.pseudo-class.css: #800000", - "hc_black": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "dark_modern": "entity.other.attribute-name.pseudo-class.css: #D7BA7D", - "hc_light": "entity.other.attribute-name.pseudo-class.css: #0F4A85", - "light_modern": "entity.other.attribute-name.pseudo-class.css: #800000" + "dark_plus": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_plus": "source.css entity.other.attribute-name.pseudo-class: #800000", + "dark_vs": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "light_vs": "source.css entity.other.attribute-name.pseudo-class: #800000", + "hc_black": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "dark_modern": "source.css entity.other.attribute-name.pseudo-class: #D7BA7D", + "hc_light": "source.css entity.other.attribute-name.pseudo-class: #0F4A85", + "light_modern": "source.css entity.other.attribute-name.pseudo-class: #800000" } }, { diff --git a/extensions/yarn.lock b/extensions/yarn.lock index b704d5f8a28..504c8370b1d 100644 --- a/extensions/yarn.lock +++ b/extensions/yarn.lock @@ -228,10 +228,10 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -typescript@^5.2.2: - version "5.2.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.2.2.tgz#5ebb5e5a5b75f085f22bc3f8460fba308310fa78" - integrity sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w== +typescript@^5.3.1-rc: + version "5.3.1-rc" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.1-rc.tgz#c307d4b69ea0c1c2cd17d4dd7700d30f7197f829" + integrity sha512-NVq/AufFc6KVjmVPcuVwdCkhTQlTcMEyRYJPvaGhPvj+X80MYUF+90qf0//uvINPb2ULg9m91/gbdIOhN2cZqA== vscode-grammar-updater@^1.1.0: version "1.1.0" diff --git a/package.json b/package.json index 287397ab20e..35a85347013 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.85.0", - "distro": "781fcac8037888d06551bccd6dd63728021d0e65", + "distro": "cbc81496e34eafbc771b8f008e6a742d755968c9", "author": { "name": "Microsoft Corporation" }, @@ -80,14 +80,14 @@ "@vscode/windows-mutex": "^0.4.4", "@vscode/windows-process-tree": "^0.5.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-canvas": "0.6.0-beta.9", - "@xterm/addon-image": "0.7.0-beta.7", - "@xterm/addon-search": "0.14.0-beta.9", - "@xterm/addon-serialize": "0.12.0-beta.9", - "@xterm/addon-unicode11": "0.7.0-beta.9", - "@xterm/addon-webgl": "0.17.0-beta.9", - "@xterm/headless": "5.4.0-beta.9", - "@xterm/xterm": "5.4.0-beta.9", + "@xterm/addon-canvas": "0.6.0-beta.14", + "@xterm/addon-image": "0.7.0-beta.12", + "@xterm/addon-search": "0.14.0-beta.14", + "@xterm/addon-serialize": "0.12.0-beta.14", + "@xterm/addon-unicode11": "0.7.0-beta.14", + "@xterm/addon-webgl": "0.17.0-beta.14", + "@xterm/headless": "5.4.0-beta.14", + "@xterm/xterm": "5.4.0-beta.14", "graceful-fs": "4.2.11", "http-proxy-agent": "^7.0.0", "https-proxy-agent": "^7.0.2", diff --git a/remote/package.json b/remote/package.json index 4df9e4efd4c..2a4dcf858e1 100644 --- a/remote/package.json +++ b/remote/package.json @@ -13,14 +13,14 @@ "@vscode/vscode-languagedetection": "1.0.21", "@vscode/windows-process-tree": "^0.5.0", "@vscode/windows-registry": "^1.1.0", - "@xterm/addon-canvas": "0.6.0-beta.9", - "@xterm/addon-image": "0.7.0-beta.7", - "@xterm/addon-search": "0.14.0-beta.9", - "@xterm/addon-serialize": "0.12.0-beta.9", - "@xterm/addon-unicode11": "0.7.0-beta.9", - "@xterm/addon-webgl": "0.17.0-beta.9", - "@xterm/headless": "5.4.0-beta.9", - "@xterm/xterm": "5.4.0-beta.9", + "@xterm/addon-canvas": "0.6.0-beta.14", + "@xterm/addon-image": "0.7.0-beta.12", + "@xterm/addon-search": "0.14.0-beta.14", + "@xterm/addon-serialize": "0.12.0-beta.14", + "@xterm/addon-unicode11": "0.7.0-beta.14", + "@xterm/addon-webgl": "0.17.0-beta.14", + "@xterm/headless": "5.4.0-beta.14", + "@xterm/xterm": "5.4.0-beta.14", "cookie": "^0.4.0", "graceful-fs": "4.2.11", "http-proxy-agent": "^7.0.0", diff --git a/remote/web/package.json b/remote/web/package.json index ad987ea5aba..a056b0f158e 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -7,12 +7,13 @@ "@microsoft/1ds-post-js": "^3.2.13", "@vscode/iconv-lite-umd": "0.7.0", "@vscode/vscode-languagedetection": "1.0.21", - "@xterm/addon-canvas": "0.6.0-beta.9", - "@xterm/addon-image": "0.7.0-beta.7", - "@xterm/addon-search": "0.14.0-beta.9", - "@xterm/addon-unicode11": "0.7.0-beta.9", - "@xterm/addon-webgl": "0.17.0-beta.9", - "@xterm/xterm": "5.4.0-beta.9", + "@xterm/addon-canvas": "0.6.0-beta.14", + "@xterm/addon-image": "0.7.0-beta.12", + "@xterm/addon-search": "0.14.0-beta.14", + "@xterm/addon-serialize": "0.12.0-beta.14", + "@xterm/addon-unicode11": "0.7.0-beta.14", + "@xterm/addon-webgl": "0.17.0-beta.14", + "@xterm/xterm": "5.4.0-beta.14", "jschardet": "3.0.0", "tas-client-umd": "0.1.8", "vscode-oniguruma": "1.7.0", diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index ddda0b266a2..b93751d612a 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -48,35 +48,40 @@ resolved "https://registry.yarnpkg.com/@vscode/vscode-languagedetection/-/vscode-languagedetection-1.0.21.tgz#89b48f293f6aa3341bb888c1118d16ff13b032d3" integrity sha512-zSUH9HYCw5qsCtd7b31yqkpaCU6jhtkKLkvOOA8yTrIRfBSOFb8PPhgmMicD7B/m+t4PwOJXzU1XDtrM9Fd3/g== -"@xterm/addon-canvas@0.6.0-beta.9": - version "0.6.0-beta.9" - resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.9.tgz#36d9368c2a428bd2a1577ba336a6d12dee4ed374" - integrity sha512-WYoDnAjzuZbo66s2pN+rq6tOcezOkCwWJGsDI3G3Yddra9iT928fmXb9YJ54B9vbcjn3EVxINwHIy6CANryMuQ== +"@xterm/addon-canvas@0.6.0-beta.14": + version "0.6.0-beta.14" + resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.14.tgz#5a4022edf80e4b62348c11e56a0c7247b38ac946" + integrity sha512-J2+XfgTV+1ee48awyajh4maJDfzSbtF0FkOoxIUUDaw2fAPOBqeUqTDHxOyvzoYwAHlaJ98AA5HaYjrTkXqXlg== -"@xterm/addon-image@0.7.0-beta.7": - version "0.7.0-beta.7" - resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.7.tgz#8c8bba429d15cbb85d59dd8ec7447e998734bc2b" - integrity sha512-ulfa6BcIZTK+FUWRVtrVLNEcyIqg/y1Tu3o9eihNHxhlwqfPmtCYIGiMbu+/2WbHTedeJBLH4yaikYBCWCnw0w== +"@xterm/addon-image@0.7.0-beta.12": + version "0.7.0-beta.12" + resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.12.tgz#b1ed300492429a1394c94385fb0c61ed36ad2041" + integrity sha512-MseIv99qlQ0u113a4PFAfSv4vQi+16HC6S9oAcS/CSAHeSySYqPPLdZXAMqLZo0n/vIESmSFXlX4bm2k3YIhoQ== -"@xterm/addon-search@0.14.0-beta.9": - version "0.14.0-beta.9" - resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.9.tgz#ba2627eef452bff4aa70b403a711d5dd2e103d33" - integrity sha512-lRIji5o4hb/1u9Iiy0T2g19tjsKZMuuqwfKNfa2/o/LmV3qadcjUKiR+2RKbbCRYszPnclrzNpdBBv+Kz8BhgA== +"@xterm/addon-search@0.14.0-beta.14": + version "0.14.0-beta.14" + resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.14.tgz#09da64dfea88315e1e07f95e5d8ec371fe986edb" + integrity sha512-SOIUqIakxLCT2jyn0GEidmd1KJRqGbuOITWXx8loBeiBb/Omo01nqhtHFU0HTdCRwFbOhMRMDTKKr2nQxaoPEQ== -"@xterm/addon-unicode11@0.7.0-beta.9": - version "0.7.0-beta.9" - resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.9.tgz#7cbe6dc0838b480e57a93f22b8217b3ab123a61a" - integrity sha512-77OrmilFxNTgXair6d9NDZgN5uVUat6u7q0OrnTaUEacuyyw39RAacqg+Zaf/hv8SzeRD+dHICDKP1K4d2km8w== +"@xterm/addon-serialize@0.12.0-beta.14": + version "0.12.0-beta.14" + resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.12.0-beta.14.tgz#a02553b318f2f9863cadb072e3e671ba34985e3b" + integrity sha512-zHNc6gKxHG8D+oZRWopHRCZXGt/eTBGUbVKc3Jk0h5zuWrNATZCztljQ7Zy/VOwhxQRpFrV6MXE4vb2cBLdmvw== -"@xterm/addon-webgl@0.17.0-beta.9": - version "0.17.0-beta.9" - resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.9.tgz#a8f11d73aecebc408ca0d772afa7c477d20581ed" - integrity sha512-/IlZOYdbUhL188GTRRq+b4bDipePYSlw2d8HhUusaRHJVyFAN/xZHcoIJ3M6cRI5puyxK2Rkq12XDDJ1Qrdyrg== +"@xterm/addon-unicode11@0.7.0-beta.14": + version "0.7.0-beta.14" + resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.14.tgz#37f672945b59c62676a76f9afa292973bff13763" + integrity sha512-L1Maeye7mPYgllIOaD8b3J70B73SoC0deNrlhiMkry/8ue2Q48JzQnOGVWKIOZC+owB1aQRWZbQZbQaAiLKz4A== -"@xterm/xterm@5.4.0-beta.9": - version "5.4.0-beta.9" - resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.9.tgz#2c557f7d3175526389bd180831dbf4a263f888f7" - integrity sha512-hTtiqDCQ2C52E5bCTq4q03aSQUGxDmBQo8CqS/StUPzSq21Um0V6yf4xYweW3xnGHUtACjUacFjLJ6Fzn9H75A== +"@xterm/addon-webgl@0.17.0-beta.14": + version "0.17.0-beta.14" + resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.14.tgz#767057842d72258656be990b85718f01cf53ab55" + integrity sha512-FZoC0gk/H0hpQFrPEp2LM4qllj4a1MR/6sIg0TY6L83KS++8gxQLonTxDQoAmb9Ld5816s7ynLZJSTJUyDWRpg== + +"@xterm/xterm@5.4.0-beta.14": + version "5.4.0-beta.14" + resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.14.tgz#fa0902f457fe799c65dec6ff784538a087f611d9" + integrity sha512-69E++VxeLPxBRhR3EGpkDe3R+EhpEZILo0m6TcN+atC4Zm5+WgcqXBCrBhkvpnBA2AhZp51w+hLC/OCwaHE0rg== jschardet@3.0.0: version "3.0.0" diff --git a/remote/yarn.lock b/remote/yarn.lock index bdc92eecb1e..c83c555aa64 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -113,45 +113,45 @@ resolved "https://registry.yarnpkg.com/@vscode/windows-registry/-/windows-registry-1.1.0.tgz#03dace7c29c46f658588b9885b9580e453ad21f9" integrity sha512-5AZzuWJpGscyiMOed0IuyEwt6iKmV5Us7zuwCDCFYMIq7tsvooO9BUiciywsvuthGz6UG4LSpeDeCxvgMVhnIw== -"@xterm/addon-canvas@0.6.0-beta.9": - version "0.6.0-beta.9" - resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.9.tgz#36d9368c2a428bd2a1577ba336a6d12dee4ed374" - integrity sha512-WYoDnAjzuZbo66s2pN+rq6tOcezOkCwWJGsDI3G3Yddra9iT928fmXb9YJ54B9vbcjn3EVxINwHIy6CANryMuQ== +"@xterm/addon-canvas@0.6.0-beta.14": + version "0.6.0-beta.14" + resolved "https://registry.yarnpkg.com/@xterm/addon-canvas/-/addon-canvas-0.6.0-beta.14.tgz#5a4022edf80e4b62348c11e56a0c7247b38ac946" + integrity sha512-J2+XfgTV+1ee48awyajh4maJDfzSbtF0FkOoxIUUDaw2fAPOBqeUqTDHxOyvzoYwAHlaJ98AA5HaYjrTkXqXlg== -"@xterm/addon-image@0.7.0-beta.7": - version "0.7.0-beta.7" - resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.7.tgz#8c8bba429d15cbb85d59dd8ec7447e998734bc2b" - integrity sha512-ulfa6BcIZTK+FUWRVtrVLNEcyIqg/y1Tu3o9eihNHxhlwqfPmtCYIGiMbu+/2WbHTedeJBLH4yaikYBCWCnw0w== +"@xterm/addon-image@0.7.0-beta.12": + version "0.7.0-beta.12" + resolved "https://registry.yarnpkg.com/@xterm/addon-image/-/addon-image-0.7.0-beta.12.tgz#b1ed300492429a1394c94385fb0c61ed36ad2041" + integrity sha512-MseIv99qlQ0u113a4PFAfSv4vQi+16HC6S9oAcS/CSAHeSySYqPPLdZXAMqLZo0n/vIESmSFXlX4bm2k3YIhoQ== -"@xterm/addon-search@0.14.0-beta.9": - version "0.14.0-beta.9" - resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.9.tgz#ba2627eef452bff4aa70b403a711d5dd2e103d33" - integrity sha512-lRIji5o4hb/1u9Iiy0T2g19tjsKZMuuqwfKNfa2/o/LmV3qadcjUKiR+2RKbbCRYszPnclrzNpdBBv+Kz8BhgA== +"@xterm/addon-search@0.14.0-beta.14": + version "0.14.0-beta.14" + resolved "https://registry.yarnpkg.com/@xterm/addon-search/-/addon-search-0.14.0-beta.14.tgz#09da64dfea88315e1e07f95e5d8ec371fe986edb" + integrity sha512-SOIUqIakxLCT2jyn0GEidmd1KJRqGbuOITWXx8loBeiBb/Omo01nqhtHFU0HTdCRwFbOhMRMDTKKr2nQxaoPEQ== -"@xterm/addon-serialize@0.12.0-beta.9": - version "0.12.0-beta.9" - resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.12.0-beta.9.tgz#4453db50c405555dd94791306eeb8358353f6bb2" - integrity sha512-N5BmlAT6fxeHjN+ylldNBbmczEcIF4fyJcE810prxCCYasRdnNy/KjGFWH7r1v4qoOxqY4LEaCUBbdh+SGiaUQ== +"@xterm/addon-serialize@0.12.0-beta.14": + version "0.12.0-beta.14" + resolved "https://registry.yarnpkg.com/@xterm/addon-serialize/-/addon-serialize-0.12.0-beta.14.tgz#a02553b318f2f9863cadb072e3e671ba34985e3b" + integrity sha512-zHNc6gKxHG8D+oZRWopHRCZXGt/eTBGUbVKc3Jk0h5zuWrNATZCztljQ7Zy/VOwhxQRpFrV6MXE4vb2cBLdmvw== -"@xterm/addon-unicode11@0.7.0-beta.9": - version "0.7.0-beta.9" - resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.9.tgz#7cbe6dc0838b480e57a93f22b8217b3ab123a61a" - integrity sha512-77OrmilFxNTgXair6d9NDZgN5uVUat6u7q0OrnTaUEacuyyw39RAacqg+Zaf/hv8SzeRD+dHICDKP1K4d2km8w== +"@xterm/addon-unicode11@0.7.0-beta.14": + version "0.7.0-beta.14" + resolved "https://registry.yarnpkg.com/@xterm/addon-unicode11/-/addon-unicode11-0.7.0-beta.14.tgz#37f672945b59c62676a76f9afa292973bff13763" + integrity sha512-L1Maeye7mPYgllIOaD8b3J70B73SoC0deNrlhiMkry/8ue2Q48JzQnOGVWKIOZC+owB1aQRWZbQZbQaAiLKz4A== -"@xterm/addon-webgl@0.17.0-beta.9": - version "0.17.0-beta.9" - resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.9.tgz#a8f11d73aecebc408ca0d772afa7c477d20581ed" - integrity sha512-/IlZOYdbUhL188GTRRq+b4bDipePYSlw2d8HhUusaRHJVyFAN/xZHcoIJ3M6cRI5puyxK2Rkq12XDDJ1Qrdyrg== +"@xterm/addon-webgl@0.17.0-beta.14": + version "0.17.0-beta.14" + resolved "https://registry.yarnpkg.com/@xterm/addon-webgl/-/addon-webgl-0.17.0-beta.14.tgz#767057842d72258656be990b85718f01cf53ab55" + integrity sha512-FZoC0gk/H0hpQFrPEp2LM4qllj4a1MR/6sIg0TY6L83KS++8gxQLonTxDQoAmb9Ld5816s7ynLZJSTJUyDWRpg== -"@xterm/headless@5.4.0-beta.9": - version "5.4.0-beta.9" - resolved "https://registry.yarnpkg.com/@xterm/headless/-/headless-5.4.0-beta.9.tgz#1452b433cf22e6d416f6638dfafcaa78809b2fca" - integrity sha512-MiBbdetKrZ7uh9kobKO6ZD3o5z7Ud/MIIavNEJpUEphvYbkIsO/YUZAnd0Oxr908ESDpjoDevFuzfYagO+KRvg== +"@xterm/headless@5.4.0-beta.14": + version "5.4.0-beta.14" + resolved "https://registry.yarnpkg.com/@xterm/headless/-/headless-5.4.0-beta.14.tgz#22f56a23bff9434b586a09771ac0c1dc06d72330" + integrity sha512-W1OPpSuKJqaJUXIi6GjOYRYzmmeB1SX6o2YIuYLJTzZx3cQzg6/gecYbGL1VKp7WzouAb8yJSlSkSQRIUMDRYg== -"@xterm/xterm@5.4.0-beta.9": - version "5.4.0-beta.9" - resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.9.tgz#2c557f7d3175526389bd180831dbf4a263f888f7" - integrity sha512-hTtiqDCQ2C52E5bCTq4q03aSQUGxDmBQo8CqS/StUPzSq21Um0V6yf4xYweW3xnGHUtACjUacFjLJ6Fzn9H75A== +"@xterm/xterm@5.4.0-beta.14": + version "5.4.0-beta.14" + resolved "https://registry.yarnpkg.com/@xterm/xterm/-/xterm-5.4.0-beta.14.tgz#fa0902f457fe799c65dec6ff784538a087f611d9" + integrity sha512-69E++VxeLPxBRhR3EGpkDe3R+EhpEZILo0m6TcN+atC4Zm5+WgcqXBCrBhkvpnBA2AhZp51w+hLC/OCwaHE0rg== agent-base@^7.0.1, agent-base@^7.0.2, agent-base@^7.1.0: version "7.1.0" diff --git a/scripts/debugger-scripts-api.d.ts b/scripts/debugger-scripts-api.d.ts index 149912bc04f..0a06ad925e0 100644 --- a/scripts/debugger-scripts-api.d.ts +++ b/scripts/debugger-scripts-api.d.ts @@ -15,8 +15,8 @@ interface IDisposable { dispose(): void; } -interface GlobalThisAddition extends globalThis { - $hotReload_applyNewExports?(oldExports: Record): AcceptNewExportsFn | undefined; +interface GlobalThisAddition { + $hotReload_applyNewExports?(args: { oldExports: Record; newSrc: string }): AcceptNewExportsFn | undefined; } type AcceptNewExportsFn = (newExports: Record) => boolean; diff --git a/scripts/hot-reload-injected-script.js b/scripts/hot-reload-injected-script.js index c6311f3b9c9..8d082f1a139 100644 --- a/scripts/hot-reload-injected-script.js +++ b/scripts/hot-reload-injected-script.js @@ -85,7 +85,7 @@ module.exports.run = async function (debugSession) { // A frozen copy of the previous exports const oldExports = Object.freeze({ ...oldModule.exports }); - const reloadFn = g.$hotReload_applyNewExports?.(oldExports); + const reloadFn = g.$hotReload_applyNewExports?.({ oldExports, newSrc }); if (!reloadFn) { console.log(debugSessionName, 'ignoring js change, as module does not support hot-reload', relativePath); @@ -94,7 +94,11 @@ module.exports.run = async function (debugSession) { return; } - const newScript = new Function('define', newSrc); // CodeQL [SM01632] This code is only executed during development. It is required for the hot-reload functionality. + // Eval maintains source maps + function newScript(/* this parameter is used by newSrc */ define) { + // eslint-disable-next-line no-eval + eval(newSrc); // CodeQL [SM01632] This code is only executed during development. It is required for the hot-reload functionality. + } newScript(/* define */ function (deps, callback) { // Evaluating the new code was successful. diff --git a/scripts/update-xterm.js b/scripts/update-xterm.js index 5f465efb43c..747d7af3ea8 100644 --- a/scripts/update-xterm.js +++ b/scripts/update-xterm.js @@ -11,13 +11,13 @@ const moduleNames = [ '@xterm/addon-canvas', '@xterm/addon-image', '@xterm/addon-search', + '@xterm/addon-serialize', '@xterm/addon-unicode11', '@xterm/addon-webgl', ]; const backendOnlyModuleNames = [ - '@xterm/headless', - '@xterm/addon-serialize' + '@xterm/headless' ]; const vscodeDir = process.argv.length >= 3 ? process.argv[2] : process.cwd(); diff --git a/src/bootstrap-window.js b/src/bootstrap-window.js index 04aa152d75f..6334d62afc5 100644 --- a/src/bootstrap-window.js +++ b/src/bootstrap-window.js @@ -8,6 +8,8 @@ //@ts-check 'use strict'; +/* eslint-disable no-restricted-globals */ + // Simple module style to support node.js and browser environments (function (globalThis, factory) { diff --git a/src/typings/base-common.d.ts b/src/typings/base-common.d.ts new file mode 100644 index 00000000000..4fc7b598566 --- /dev/null +++ b/src/typings/base-common.d.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. + *--------------------------------------------------------------------------------------------*/ + +// Declare types that we probe for to implement util and/or polyfill functions + +declare global { + + interface IdleDeadline { + readonly didTimeout: boolean; + timeRemaining(): number; + } + + function requestIdleCallback(callback: (args: IdleDeadline) => void, options?: { timeout: number }): number; + function cancelIdleCallback(handle: number): void; + +} + +export { } diff --git a/src/vs/amdX.ts b/src/vs/amdX.ts index 75b21c60828..ba164787d2e 100644 --- a/src/vs/amdX.ts +++ b/src/vs/amdX.ts @@ -57,8 +57,10 @@ class AMDModuleImporter { (globalThis).define.amd = true; if (this._isRenderer) { + // eslint-disable-next-line no-restricted-globals this._amdPolicy = window.trustedTypes?.createPolicy('amdLoader', { createScriptURL(value) { + // eslint-disable-next-line no-restricted-globals if (value.startsWith(window.location.origin)) { return value; } @@ -121,7 +123,8 @@ class AMDModuleImporter { scriptSrc = this._amdPolicy.createScriptURL(scriptSrc) as any as string; } scriptElement.setAttribute('src', scriptSrc); - document.getElementsByTagName('head')[0].appendChild(scriptElement); + // eslint-disable-next-line no-restricted-globals + window.document.getElementsByTagName('head')[0].appendChild(scriptElement); }); } diff --git a/src/vs/base/browser/broadcast.ts b/src/vs/base/browser/broadcast.ts index d7785f42ff8..53c921fdb2e 100644 --- a/src/vs/base/browser/broadcast.ts +++ b/src/vs/base/browser/broadcast.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { mainWindow } from 'vs/base/browser/window'; import { getErrorMessage } from 'vs/base/common/errors'; import { Emitter } from 'vs/base/common/event'; import { Disposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -18,7 +19,7 @@ export class BroadcastDataChannel extends Disposable { super(); // Use BroadcastChannel - if ('BroadcastChannel' in window) { + if ('BroadcastChannel' in mainWindow) { try { this.broadcastChannel = new BroadcastChannel(channelName); const listener = (event: MessageEvent) => { @@ -49,8 +50,8 @@ export class BroadcastDataChannel extends Disposable { this._onDidReceiveData.fire(JSON.parse(event.newValue)); } }; - window.addEventListener('storage', listener); - this._register(toDisposable(() => window.removeEventListener('storage', listener))); + mainWindow.addEventListener('storage', listener); + this._register(toDisposable(() => mainWindow.removeEventListener('storage', listener))); } /** @@ -62,8 +63,8 @@ export class BroadcastDataChannel extends Disposable { this.broadcastChannel.postMessage(data); } else { // remove previous changes so that event is triggered even if new changes are same as old changes - window.localStorage.removeItem(this.channelName); - window.localStorage.setItem(this.channelName, JSON.stringify(data)); + localStorage.removeItem(this.channelName); + localStorage.setItem(this.channelName, JSON.stringify(data)); } } } diff --git a/src/vs/base/browser/browser.ts b/src/vs/base/browser/browser.ts index 2ac4671e497..de37d415e03 100644 --- a/src/vs/base/browser/browser.ts +++ b/src/vs/base/browser/browser.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { $window } from 'vs/base/browser/window'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, markAsSingleton } from 'vs/base/common/lifecycle'; @@ -73,7 +74,7 @@ class DevicePixelRatioMonitor extends Disposable { private _handleChange(fireEvent: boolean): void { this._mediaQueryList?.removeEventListener('change', this._listener); - this._mediaQueryList = window.matchMedia(`(resolution: ${window.devicePixelRatio}dppx)`); + this._mediaQueryList = $window.matchMedia(`(resolution: ${$window.devicePixelRatio}dppx)`); this._mediaQueryList.addEventListener('change', this._listener); if (fireEvent) { @@ -107,7 +108,7 @@ class PixelRatioImpl extends Disposable { private _getPixelRatio(): number { const ctx: any = document.createElement('canvas').getContext('2d'); - const dpr = window.devicePixelRatio || 1; + const dpr = $window.devicePixelRatio || 1; const bsr = ctx.webkitBackingStorePixelRatio || ctx.mozBackingStorePixelRatio || ctx.msBackingStorePixelRatio || @@ -144,7 +145,7 @@ class PixelRatioFacade { export function addMatchMediaChangeListener(query: string | MediaQueryList, callback: (this: MediaQueryList, ev: MediaQueryListEvent) => any): void { if (typeof query === 'string') { - query = window.matchMedia(query); + query = $window.matchMedia(query); } query.addEventListener('change', callback); } @@ -193,9 +194,9 @@ export const isElectron = (userAgent.indexOf('Electron/') >= 0); export const isAndroid = (userAgent.indexOf('Android') >= 0); let standalone = false; -if (window.matchMedia) { - const standaloneMatchMedia = window.matchMedia('(display-mode: standalone) or (display-mode: window-controls-overlay)'); - const fullScreenMatchMedia = window.matchMedia('(display-mode: fullscreen)'); +if ($window.matchMedia) { + const standaloneMatchMedia = $window.matchMedia('(display-mode: standalone) or (display-mode: window-controls-overlay)'); + const fullScreenMatchMedia = $window.matchMedia('(display-mode: fullscreen)'); standalone = standaloneMatchMedia.matches; addMatchMediaChangeListener(standaloneMatchMedia, ({ matches }) => { // entering fullscreen would change standaloneMatchMedia.matches to false diff --git a/src/vs/base/browser/canIUse.ts b/src/vs/base/browser/canIUse.ts index a759f7085db..c126a33b45b 100644 --- a/src/vs/base/browser/canIUse.ts +++ b/src/vs/base/browser/canIUse.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as browser from 'vs/base/browser/browser'; +import { mainWindow } from 'vs/base/browser/window'; import * as platform from 'vs/base/common/platform'; export const enum KeyboardSupport { @@ -41,6 +42,6 @@ export const BrowserFeatures = { // 'ontouchstart' in window always evaluates to true with typescript's modern typings. This causes `window` to be // `never` later in `window.navigator`. That's why we need the explicit `window as Window` cast - touch: 'ontouchstart' in window || navigator.maxTouchPoints > 0, - pointerEvents: window.PointerEvent && ('ontouchstart' in window || (window as Window).navigator.maxTouchPoints > 0 || navigator.maxTouchPoints > 0) + touch: 'ontouchstart' in mainWindow || navigator.maxTouchPoints > 0, + pointerEvents: mainWindow.PointerEvent && ('ontouchstart' in mainWindow || navigator.maxTouchPoints > 0 || navigator.maxTouchPoints > 0) }; diff --git a/src/vs/base/browser/dom.ts b/src/vs/base/browser/dom.ts index e39ade1ff36..823de33bc6e 100644 --- a/src/vs/base/browser/dom.ts +++ b/src/vs/base/browser/dom.ts @@ -7,7 +7,7 @@ import * as browser from 'vs/base/browser/browser'; import { BrowserFeatures } from 'vs/base/browser/canIUse'; import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { IMouseEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent'; -import { TimeoutTimer } from 'vs/base/common/async'; +import { AbstractIdleValue, IntervalTimer, TimeoutTimer, _runWhenIdle, IdleDeadline } from 'vs/base/common/async'; import { onUnexpectedError } from 'vs/base/common/errors'; import * as event from 'vs/base/common/event'; import * as dompurify from 'vs/base/browser/dompurify/dompurify'; @@ -17,10 +17,7 @@ import { FileAccess, RemoteAuthorities, Schemas } from 'vs/base/common/network'; import * as platform from 'vs/base/common/platform'; import { URI } from 'vs/base/common/uri'; import { hash } from 'vs/base/common/hash'; - -export type CodeWindow = Window & typeof globalThis & { - readonly vscodeWindowId: number; -}; +import { CodeWindow, mainWindow } from 'vs/base/browser/window'; interface IRegisteredCodeWindow { readonly window: CodeWindow; @@ -30,10 +27,9 @@ interface IRegisteredCodeWindow { export const { registerWindow, getWindows, getWindowsCount, getWindowId, onDidRegisterWindow, onWillUnregisterWindow, onDidUnregisterWindow } = (function () { const windows = new Map(); - const mainWindow = window as CodeWindow; if (typeof mainWindow.vscodeWindowId !== 'number') { - Object.defineProperty(window, 'vscodeWindowId', { - get: () => -1 + Object.defineProperty(mainWindow, 'vscodeWindowId', { + get: () => 1 }); } windows.set(mainWindow.vscodeWindowId, { window: mainWindow, disposables: new DisposableStore() }); @@ -137,9 +133,9 @@ export interface IAddStandardDisposableListenerSignature { (node: HTMLElement, type: 'pointerup', handler: (event: PointerEvent) => void, useCapture?: boolean): IDisposable; (node: HTMLElement, type: string, handler: (event: any) => void, useCapture?: boolean): IDisposable; } -function _wrapAsStandardMouseEvent(handler: (e: IMouseEvent) => void): (e: MouseEvent) => void { +function _wrapAsStandardMouseEvent(targetWindow: Window, handler: (e: IMouseEvent) => void): (e: MouseEvent) => void { return function (e: MouseEvent) { - return handler(new StandardMouseEvent(e)); + return handler(new StandardMouseEvent(targetWindow, e)); }; } function _wrapAsStandardKeyboardEvent(handler: (e: IKeyboardEvent) => void): (e: KeyboardEvent) => void { @@ -151,7 +147,7 @@ export const addStandardDisposableListener: IAddStandardDisposableListenerSignat let wrapHandler = handler; if (type === 'click' || type === 'mousedown') { - wrapHandler = _wrapAsStandardMouseEvent(handler); + wrapHandler = _wrapAsStandardMouseEvent(getWindow(node), handler); } else if (type === 'keydown' || type === 'keypress' || type === 'keyup') { wrapHandler = _wrapAsStandardKeyboardEvent(handler); } @@ -160,13 +156,13 @@ export const addStandardDisposableListener: IAddStandardDisposableListenerSignat }; export const addStandardDisposableGenericMouseDownListener = function addStandardDisposableListener(node: HTMLElement, handler: (event: any) => void, useCapture?: boolean): IDisposable { - const wrapHandler = _wrapAsStandardMouseEvent(handler); + const wrapHandler = _wrapAsStandardMouseEvent(getWindow(node), handler); return addDisposableGenericMouseDownListener(node, wrapHandler, useCapture); }; export const addStandardDisposableGenericMouseUpListener = function addStandardDisposableListener(node: HTMLElement, handler: (event: any) => void, useCapture?: boolean): IDisposable { - const wrapHandler = _wrapAsStandardMouseEvent(handler); + const wrapHandler = _wrapAsStandardMouseEvent(getWindow(node), handler); return addDisposableGenericMouseUpListener(node, wrapHandler, useCapture); }; @@ -182,20 +178,76 @@ export function addDisposableGenericMouseUpListener(node: EventTarget, handler: return addDisposableListener(node, platform.isIOS && BrowserFeatures.pointerEvents ? EventType.POINTER_UP : EventType.MOUSE_UP, handler, useCapture); } + +/** + * Execute the callback the next time the browser is idle, returning an + * {@link IDisposable} that will cancel the callback when disposed. This wraps + * [requestIdleCallback] so it will fallback to [setTimeout] if the environment + * doesn't support it. + * + * @param targetWindow The window for which to run the idle callback + * @param callback The callback to run when idle, this includes an + * [IdleDeadline] that provides the time alloted for the idle callback by the + * browser. Not respecting this deadline will result in a degraded user + * experience. + * @param timeout A timeout at which point to queue no longer wait for an idle + * callback but queue it on the regular event loop (like setTimeout). Typically + * this should not be used. + * + * [IdleDeadline]: https://developer.mozilla.org/en-US/docs/Web/API/IdleDeadline + * [requestIdleCallback]: https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback + * [setTimeout]: https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout + */ +export function runWhenWindowIdle(targetWindow: Window | typeof globalThis, callback: (idle: IdleDeadline) => void, timeout?: number): IDisposable { + return _runWhenIdle(targetWindow, callback, timeout); +} + +/** + * An implementation of the "idle-until-urgent"-strategy as introduced + * here: https://philipwalton.com/articles/idle-until-urgent/ + */ +export class WindowIdleValue extends AbstractIdleValue { + constructor(targetWindow: Window | typeof globalThis, executor: () => T) { + super(targetWindow, executor); + } +} + + /** * Schedule a callback to be run at the next animation frame. * This allows multiple parties to register callbacks that should run at the next animation frame. * If currently in an animation frame, `runner` will be executed immediately. * @return token that can be used to cancel the scheduled runner (only if `runner` was not executed immediately). */ -export let runAtThisOrScheduleAtNextAnimationFrame: (runner: () => void, targetWindow: Window, priority?: number) => IDisposable; +export let runAtThisOrScheduleAtNextAnimationFrame: (targetWindow: Window, runner: () => void, priority?: number) => IDisposable; /** * Schedule a callback to be run at the next animation frame. * This allows multiple parties to register callbacks that should run at the next animation frame. * If currently in an animation frame, `runner` will be executed at the next animation frame. * @return token that can be used to cancel the scheduled runner. */ -export let scheduleAtNextAnimationFrame: (runner: () => void, targetWindow: Window, priority?: number) => IDisposable; +export let scheduleAtNextAnimationFrame: (targetWindow: Window, runner: () => void, priority?: number) => IDisposable; + +export function disposableWindowInterval(targetWindow: Window & typeof globalThis, handler: () => void | boolean /* stop interval */ | Promise, interval: number, iterations?: number): IDisposable { + let iteration = 0; + const timer = targetWindow.setInterval(() => { + iteration++; + if ((typeof iterations === 'number' && iteration >= iterations) || handler() === true) { + disposable.dispose(); + } + }, interval); + const disposable = toDisposable(() => { + targetWindow.clearInterval(timer); + }); + return disposable; +} + +export class WindowIntervalTimer extends IntervalTimer { + + override cancelAndSet(runner: () => void, interval: number, targetWindow: Window & typeof globalThis): void { + return super.cancelAndSet(runner, interval, targetWindow); + } +} class AnimationFrameQueueItem implements IDisposable { @@ -265,7 +317,7 @@ class AnimationFrameQueueItem implements IDisposable { inAnimationFrameRunner.set(targetWindowId, false); }; - scheduleAtNextAnimationFrame = (runner: () => void, targetWindow: Window, priority: number = 0) => { + scheduleAtNextAnimationFrame = (targetWindow: Window, runner: () => void, priority: number = 0) => { const targetWindowId = getWindowId(targetWindow); const item = new AnimationFrameQueueItem(runner, priority); @@ -284,7 +336,7 @@ class AnimationFrameQueueItem implements IDisposable { return item; }; - runAtThisOrScheduleAtNextAnimationFrame = (runner: () => void, targetWindow: Window, priority?: number) => { + runAtThisOrScheduleAtNextAnimationFrame = (targetWindow: Window, runner: () => void, priority?: number) => { const targetWindowId = getWindowId(targetWindow); if (inAnimationFrameRunner.get(targetWindowId)) { const item = new AnimationFrameQueueItem(runner, priority); @@ -296,17 +348,17 @@ class AnimationFrameQueueItem implements IDisposable { currentQueue.push(item); return item; } else { - return scheduleAtNextAnimationFrame(runner, targetWindow, priority); + return scheduleAtNextAnimationFrame(targetWindow, runner, priority); } }; })(); -export function measure(callback: () => void, targetWindow: Window): IDisposable { - return scheduleAtNextAnimationFrame(callback, targetWindow, 10000 /* must be early */); +export function measure(targetWindow: Window, callback: () => void): IDisposable { + return scheduleAtNextAnimationFrame(targetWindow, callback, 10000 /* must be early */); } -export function modify(callback: () => void, targetWindow: Window): IDisposable { - return scheduleAtNextAnimationFrame(callback, targetWindow, -10000 /* must be late */); +export function modify(targetWindow: Window, callback: () => void): IDisposable { + return scheduleAtNextAnimationFrame(targetWindow, callback, -10000 /* must be late */); } /** @@ -798,12 +850,12 @@ export function getActiveDocument(): Document { } const documents = Array.from(getWindows()).map(({ window }) => window.document); - return documents.find(document => document.hasFocus()) ?? document; + return documents.find(doc => doc.hasFocus()) ?? document; } export function getActiveWindow(): CodeWindow { const document = getActiveDocument(); - return (document.defaultView?.window ?? window) as CodeWindow; + return (document.defaultView?.window ?? mainWindow) as CodeWindow; } export function getWindow(element: Node | undefined | null): CodeWindow; @@ -819,7 +871,7 @@ export function getWindow(e: unknown): CodeWindow { return candidateEvent.view.window as CodeWindow; } - return window as CodeWindow; + return mainWindow; } export function focusWindow(element: Node): void { @@ -829,7 +881,13 @@ export function focusWindow(element: Node): void { } } -export function createStyleSheet(container: HTMLElement = document.head, beforeAppend?: (style: HTMLStyleElement) => void, disposableStore?: DisposableStore): HTMLStyleElement { +const globalStylesheets = new Map>(); + +export function isGlobalStylesheet(node: Node): boolean { + return globalStylesheets.has(node as HTMLStyleElement); +} + +export function createStyleSheet(container: HTMLElement = mainWindow.document.head, beforeAppend?: (style: HTMLStyleElement) => void, disposableStore?: DisposableStore): HTMLStyleElement { const style = document.createElement('style'); style.type = 'text/css'; style.media = 'screen'; @@ -842,38 +900,34 @@ export function createStyleSheet(container: HTMLElement = document.head, beforeA // With as container, the stylesheet becomes global and is tracked // to support auxiliary windows to clone the stylesheet. - if (container === document.head) { + if (container === mainWindow.document.head) { + const globalStylesheetClones = new Set(); + globalStylesheets.set(style, globalStylesheetClones); + for (const { window: targetWindow, disposables } of getWindows()) { - if (targetWindow === window) { + if (targetWindow === mainWindow) { continue; // main window is already tracked } - const cloneDisposable = disposables.add(cloneGlobalStyleSheet(style, targetWindow)); + const cloneDisposable = disposables.add(cloneGlobalStyleSheet(style, globalStylesheetClones, targetWindow)); disposableStore?.add(cloneDisposable); } - } return style; } -const globalStylesheets = new Map>(); - -export function isGlobalStylesheet(node: Node): boolean { - return globalStylesheets.has(node as HTMLStyleElement); -} - export function cloneGlobalStylesheets(targetWindow: Window): IDisposable { const disposables = new DisposableStore(); - for (const [globalStylesheet] of globalStylesheets) { - disposables.add(cloneGlobalStyleSheet(globalStylesheet, targetWindow)); + for (const [globalStylesheet, clonedGlobalStylesheets] of globalStylesheets) { + disposables.add(cloneGlobalStyleSheet(globalStylesheet, clonedGlobalStylesheets, targetWindow)); } return disposables; } -function cloneGlobalStyleSheet(globalStylesheet: HTMLStyleElement, targetWindow: Window): IDisposable { +function cloneGlobalStyleSheet(globalStylesheet: HTMLStyleElement, globalStylesheetClones: Set, targetWindow: Window): IDisposable { const disposables = new DisposableStore(); const clone = globalStylesheet.cloneNode(true) as HTMLStyleElement; @@ -888,13 +942,8 @@ function cloneGlobalStyleSheet(globalStylesheet: HTMLStyleElement, targetWindow: clone.textContent = globalStylesheet.textContent; })); - let clonedGlobalStylesheets = globalStylesheets.get(globalStylesheet); - if (!clonedGlobalStylesheets) { - clonedGlobalStylesheets = new Set(); - globalStylesheets.set(globalStylesheet, clonedGlobalStylesheets); - } - clonedGlobalStylesheets.add(clone); - disposables.add(toDisposable(() => clonedGlobalStylesheets?.delete(clone))); + globalStylesheetClones.add(clone); + disposables.add(toDisposable(() => globalStylesheetClones.delete(clone))); return disposables; } @@ -952,7 +1001,7 @@ export const sharedMutationObserver = new class { } }; -export function createMetaElement(container: HTMLElement = document.head): HTMLMetaElement { +export function createMetaElement(container: HTMLElement = mainWindow.document.head): HTMLMetaElement { const meta = document.createElement('meta'); container.appendChild(meta); return meta; @@ -1183,7 +1232,7 @@ class FocusTracker extends Disposable implements IFocusTracker { const onBlur = () => { if (hasFocus) { loosingFocus = true; - window.setTimeout(() => { + (element instanceof HTMLElement ? getWindow(element) : element).setTimeout(() => { if (loosingFocus) { loosingFocus = false; hasFocus = false; @@ -1441,7 +1490,7 @@ export function windowOpenNoOpener(url: string): void { // See https://developer.mozilla.org/en-US/docs/Web/API/Window/open#noopener // However, this also doesn't allow us to realize if the browser blocked // the creation of the window. - window.open(url, '_blank', 'noopener'); + mainWindow.open(url, '_blank', 'noopener'); } /** @@ -1457,9 +1506,9 @@ export function windowOpenNoOpener(url: string): void { */ const popupWidth = 780, popupHeight = 640; export function windowOpenPopup(url: string): void { - const left = Math.floor(window.screenLeft + window.innerWidth / 2 - popupWidth / 2); - const top = Math.floor(window.screenTop + window.innerHeight / 2 - popupHeight / 2); - window.open( + const left = Math.floor(mainWindow.screenLeft + mainWindow.innerWidth / 2 - popupWidth / 2); + const top = Math.floor(mainWindow.screenTop + mainWindow.innerHeight / 2 - popupHeight / 2); + mainWindow.open( url, '_blank', `width=${popupWidth},height=${popupHeight},top=${top},left=${left}` @@ -1482,7 +1531,7 @@ export function windowOpenPopup(url: string): void { * @returns boolean indicating if the {@link window.open} call succeeded */ export function windowOpenWithSuccess(url: string, noOpener = true): boolean { - const newTab = window.open(); + const newTab = mainWindow.open(); if (newTab) { if (noOpener) { // see `windowOpenNoOpener` for details on why this is important @@ -1494,17 +1543,17 @@ export function windowOpenWithSuccess(url: string, noOpener = true): boolean { return false; } -export function animate(fn: () => void, targetWindow: Window): IDisposable { +export function animate(targetWindow: Window, fn: () => void): IDisposable { const step = () => { fn(); - stepDisposable = scheduleAtNextAnimationFrame(step, targetWindow); + stepDisposable = scheduleAtNextAnimationFrame(targetWindow, step); }; - let stepDisposable = scheduleAtNextAnimationFrame(step, targetWindow); + let stepDisposable = scheduleAtNextAnimationFrame(targetWindow, step); return toDisposable(() => stepDisposable.dispose()); } -RemoteAuthorities.setPreferredWebSchema(/^https:/.test(window.location.href) ? 'https' : 'http'); +RemoteAuthorities.setPreferredWebSchema(/^https:/.test(mainWindow.location.href) ? 'https' : 'http'); /** * returns url('...') @@ -1554,14 +1603,15 @@ export function triggerDownload(dataOrUri: Uint8Array | URI, name: string): void // to be creating a element with download attribute that // points to the file to download. // See also https://developers.google.com/web/updates/2011/08/Downloading-resources-in-HTML5-a-download + const activeWindow = getActiveWindow(); const anchor = document.createElement('a'); - document.body.appendChild(anchor); + activeWindow.document.body.appendChild(anchor); anchor.download = name; anchor.href = url; anchor.click(); // Ensure to remove the element from DOM eventually - setTimeout(() => document.body.removeChild(anchor)); + setTimeout(() => activeWindow.document.body.removeChild(anchor)); } export function triggerUpload(): Promise { @@ -1570,8 +1620,9 @@ export function triggerUpload(): Promise { // In order to upload to the browser, create a // input element of type `file` and click it // to gather the selected files + const activeWindow = getActiveWindow(); const input = document.createElement('input'); - document.body.appendChild(input); + activeWindow.document.body.appendChild(input); input.type = 'file'; input.multiple = true; @@ -1583,7 +1634,7 @@ export function triggerUpload(): Promise { input.click(); // Ensure to remove the element from DOM eventually - setTimeout(() => document.body.removeChild(input)); + setTimeout(() => activeWindow.document.body.removeChild(input)); }); } @@ -1616,10 +1667,10 @@ export interface IDetectedFullscreen { guess: boolean; } -export function detectFullscreen(): IDetectedFullscreen | null { +export function detectFullscreen(targetWindow: Window): IDetectedFullscreen | null { // Browser fullscreen: use DOM APIs to detect - if (document.fullscreenElement || (document).webkitFullscreenElement || (document).webkitIsFullScreen) { + if (targetWindow.document.fullscreenElement || (targetWindow.document).webkitFullscreenElement || (targetWindow.document).webkitIsFullScreen) { return { mode: DetectedFullscreenMode.DOCUMENT, guess: false }; } @@ -1628,7 +1679,7 @@ export function detectFullscreen(): IDetectedFullscreen | null { // height and comparing that to window height, we can guess // it though. - if (window.innerHeight === screen.height) { + if (targetWindow.innerHeight === targetWindow.screen.height) { // if the height of the window matches the screen height, we can // safely assume that the browser is fullscreen because no browser // chrome is taking height away (e.g. like toolbars). @@ -1637,7 +1688,7 @@ export function detectFullscreen(): IDetectedFullscreen | null { if (platform.isMacintosh || platform.isLinux) { // macOS and Linux do not properly report `innerHeight`, only Windows does - if (window.outerHeight === screen.height && window.outerWidth === screen.width) { + if (targetWindow.outerHeight === targetWindow.screen.height && targetWindow.outerWidth === targetWindow.screen.width) { // if the height of the browser matches the screen height, we can // only guess that we are in fullscreen. It is also possible that // the user has turned off taskbars in the OS and the browser is @@ -1842,7 +1893,7 @@ export class ModifierKeyEmitter extends event.Emitter { metaKey: false }; - this._subscriptions.add(event.Event.runAndSubscribe(onDidRegisterWindow, ({ window, disposables }) => this.registerListeners(window, disposables), { window, disposables: this._subscriptions })); + this._subscriptions.add(event.Event.runAndSubscribe(onDidRegisterWindow, ({ window, disposables }) => this.registerListeners(window, disposables), { window: mainWindow, disposables: this._subscriptions })); } private registerListeners(window: Window, disposables: DisposableStore): void { @@ -1980,10 +2031,12 @@ export function getCookieValue(name: string): string | undefined { } export interface IDragAndDropObserverCallbacks { - readonly onDragEnter: (e: DragEvent) => void; - readonly onDragLeave: (e: DragEvent) => void; - readonly onDrop: (e: DragEvent) => void; - readonly onDragEnd: (e: DragEvent) => void; + readonly onDragEnter?: (e: DragEvent) => void; + readonly onDragLeave?: (e: DragEvent) => void; + readonly onDrop?: (e: DragEvent) => void; + readonly onDragEnd?: (e: DragEvent) => void; + readonly onDragStart?: (e: DragEvent) => void; + readonly onDrag?: (e: DragEvent) => void; readonly onDragOver?: (e: DragEvent, dragDuration: number) => void; } @@ -2005,11 +2058,23 @@ export class DragAndDropObserver extends Disposable { } private registerListeners(): void { + if (this.callbacks.onDragStart) { + this._register(addDisposableListener(this.element, EventType.DRAG_START, (e: DragEvent) => { + this.callbacks.onDragStart?.(e); + })); + } + + if (this.callbacks.onDrag) { + this._register(addDisposableListener(this.element, EventType.DRAG, (e: DragEvent) => { + this.callbacks.onDrag?.(e); + })); + } + this._register(addDisposableListener(this.element, EventType.DRAG_ENTER, (e: DragEvent) => { this.counter++; this.dragStartTime = e.timeStamp; - this.callbacks.onDragEnter(e); + this.callbacks.onDragEnter?.(e); })); this._register(addDisposableListener(this.element, EventType.DRAG_OVER, (e: DragEvent) => { @@ -2024,7 +2089,7 @@ export class DragAndDropObserver extends Disposable { if (this.counter === 0) { this.dragStartTime = 0; - this.callbacks.onDragLeave(e); + this.callbacks.onDragLeave?.(e); } })); @@ -2032,14 +2097,14 @@ export class DragAndDropObserver extends Disposable { this.counter = 0; this.dragStartTime = 0; - this.callbacks.onDragEnd(e); + this.callbacks.onDragEnd?.(e); })); this._register(addDisposableListener(this.element, EventType.DROP, (e: DragEvent) => { this.counter = 0; this.dragStartTime = 0; - this.callbacks.onDrop(e); + this.callbacks.onDrop?.(e); })); } } diff --git a/src/vs/base/browser/iframe.ts b/src/vs/base/browser/iframe.ts index 2a32decc492..e8522e03115 100644 --- a/src/vs/base/browser/iframe.ts +++ b/src/vs/base/browser/iframe.ts @@ -6,19 +6,18 @@ /** * Represents a window in a possible chain of iframes */ -export interface IWindowChainElement { +interface IWindowChainElement { /** * The window object for it */ - window: Window; + readonly window: WeakRef; /** * The iframe element inside the window.parent corresponding to window */ - iframeElement: Element | null; + readonly iframeElement: Element | null; } -let hasDifferentOriginAncestorFlag: boolean = false; -let sameOriginWindowChainCache: IWindowChainElement[] | null = null; +const sameOriginWindowChainCache = new WeakMap(); function getParentWindowIfSameOrigin(w: Window): Window | null { if (!w.parent || w.parent === w) { @@ -30,11 +29,9 @@ function getParentWindowIfSameOrigin(w: Window): Window | null { const location = w.location; const parentLocation = w.parent.location; if (location.origin !== 'null' && parentLocation.origin !== 'null' && location.origin !== parentLocation.origin) { - hasDifferentOriginAncestorFlag = true; return null; } } catch (e) { - hasDifferentOriginAncestorFlag = true; return null; } @@ -46,41 +43,31 @@ export class IframeUtils { /** * Returns a chain of embedded windows with the same origin (which can be accessed programmatically). * Having a chain of length 1 might mean that the current execution environment is running outside of an iframe or inside an iframe embedded in a window with a different origin. - * To distinguish if at one point the current execution environment is running inside a window with a different origin, see hasDifferentOriginAncestor() */ - public static getSameOriginWindowChain(): IWindowChainElement[] { - if (!sameOriginWindowChainCache) { - sameOriginWindowChainCache = []; - let w: Window | null = window; + private static getSameOriginWindowChain(targetWindow: Window): IWindowChainElement[] { + let windowChainCache = sameOriginWindowChainCache.get(targetWindow); + if (!windowChainCache) { + windowChainCache = []; + sameOriginWindowChainCache.set(targetWindow, windowChainCache); + let w: Window | null = targetWindow; let parent: Window | null; do { parent = getParentWindowIfSameOrigin(w); if (parent) { - sameOriginWindowChainCache.push({ - window: w, + windowChainCache.push({ + window: new WeakRef(w), iframeElement: w.frameElement || null }); } else { - sameOriginWindowChainCache.push({ - window: w, + windowChainCache.push({ + window: new WeakRef(w), iframeElement: null }); } w = parent; } while (w); } - return sameOriginWindowChainCache.slice(0); - } - - /** - * Returns true if the current execution environment is chained in a list of iframes which at one point ends in a window with a different origin. - * Returns false if the current execution environment is not running inside an iframe or if the entire chain of iframes have the same origin. - */ - public static hasDifferentOriginAncestor(): boolean { - if (!sameOriginWindowChainCache) { - this.getSameOriginWindowChain(); - } - return hasDifferentOriginAncestorFlag; + return windowChainCache.slice(0); } /** @@ -97,14 +84,14 @@ export class IframeUtils { let top = 0, left = 0; - const windowChain = this.getSameOriginWindowChain(); + const windowChain = this.getSameOriginWindowChain(childWindow); for (const windowChainEl of windowChain) { + const windowInChain = windowChainEl.window.deref(); + top += windowInChain?.scrollY ?? 0; + left += windowInChain?.scrollX ?? 0; - top += windowChainEl.window.scrollY; - left += windowChainEl.window.scrollX; - - if (windowChainEl.window === ancestorWindow) { + if (windowInChain === ancestorWindow) { break; } diff --git a/src/vs/base/browser/indexedDB.ts b/src/vs/base/browser/indexedDB.ts index e04e5c8a62a..38be907918e 100644 --- a/src/vs/base/browser/indexedDB.ts +++ b/src/vs/base/browser/indexedDB.ts @@ -54,7 +54,7 @@ export class IndexedDB { private static doOpenDatabase(name: string, version: number | undefined, stores: string[]): Promise { return new Promise((c, e) => { - const request = window.indexedDB.open(name, version); + const request = indexedDB.open(name, version); request.onerror = () => e(request.error); request.onsuccess = () => { const db = request.result; @@ -78,13 +78,13 @@ export class IndexedDB { }); } - private static deleteDatabase(indexedDB: IDBDatabase): Promise { + private static deleteDatabase(database: IDBDatabase): Promise { return new Promise((c, e) => { // Close any opened connections - indexedDB.close(); + database.close(); // Delete the db - const deleteRequest = window.indexedDB.deleteDatabase(indexedDB.name); + const deleteRequest = indexedDB.deleteDatabase(database.name); deleteRequest.onerror = (err) => e(deleteRequest.error); deleteRequest.onsuccess = () => c(); }); diff --git a/src/vs/base/browser/markdownRenderer.ts b/src/vs/base/browser/markdownRenderer.ts index d7f6d0502cf..3ae619a9f14 100644 --- a/src/vs/base/browser/markdownRenderer.ts +++ b/src/vs/base/browser/markdownRenderer.ts @@ -195,7 +195,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende const onClick = options.actionHandler.disposables.add(new DomEmitter(element, 'click')); const onAuxClick = options.actionHandler.disposables.add(new DomEmitter(element, 'auxclick')); options.actionHandler.disposables.add(Event.any(onClick.event, onAuxClick.event)(e => { - const mouseEvent = new StandardMouseEvent(e); + const mouseEvent = new StandardMouseEvent(DOM.getWindow(element), e); if (!mouseEvent.leftButton && !mouseEvent.middleButton) { return; } diff --git a/src/vs/base/browser/mouseEvent.ts b/src/vs/base/browser/mouseEvent.ts index bdfbf2ed661..b4ae34ad5ec 100644 --- a/src/vs/base/browser/mouseEvent.ts +++ b/src/vs/base/browser/mouseEvent.ts @@ -45,7 +45,7 @@ export class StandardMouseEvent implements IMouseEvent { public readonly metaKey: boolean; public readonly timestamp: number; - constructor(e: MouseEvent) { + constructor(targetWindow: Window, e: MouseEvent) { this.timestamp = Date.now(); this.browserEvent = e; this.leftButton = e.button === 0; @@ -74,7 +74,7 @@ export class StandardMouseEvent implements IMouseEvent { } // Find the position of the iframe this code is executing in relative to the iframe where the event was captured. - const iframeOffsets = IframeUtils.getPositionOfChildWindowRelativeToAncestorWindow(window, e.view); + const iframeOffsets = IframeUtils.getPositionOfChildWindowRelativeToAncestorWindow(targetWindow, e.view); this.posx -= iframeOffsets.left; this.posy -= iframeOffsets.top; } @@ -92,8 +92,8 @@ export class DragMouseEvent extends StandardMouseEvent { public readonly dataTransfer: DataTransfer; - constructor(e: MouseEvent) { - super(e); + constructor(targetWindow: Window, e: MouseEvent) { + super(targetWindow, e); this.dataTransfer = (e).dataTransfer; } } diff --git a/src/vs/base/browser/touch.ts b/src/vs/base/browser/touch.ts index 23607237cd2..79e3211d85f 100644 --- a/src/vs/base/browser/touch.ts +++ b/src/vs/base/browser/touch.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as DomUtils from 'vs/base/browser/dom'; +import { mainWindow } from 'vs/base/browser/window'; import * as arrays from 'vs/base/common/arrays'; import { memoize } from 'vs/base/common/decorators'; import { Event as EventUtils } from 'vs/base/common/event'; @@ -95,7 +96,7 @@ export class Gesture extends Disposable { disposables.add(DomUtils.addDisposableListener(window.document, 'touchstart', (e: TouchEvent) => this.onTouchStart(e), { passive: false })); disposables.add(DomUtils.addDisposableListener(window.document, 'touchend', (e: TouchEvent) => this.onTouchEnd(window, e))); disposables.add(DomUtils.addDisposableListener(window.document, 'touchmove', (e: TouchEvent) => this.onTouchMove(e), { passive: false })); - }, { window, disposables: this._store })); + }, { window: mainWindow, disposables: this._store })); } public static addTarget(element: HTMLElement): IDisposable { @@ -126,7 +127,7 @@ export class Gesture extends Disposable { static isTouchDevice(): boolean { // `'ontouchstart' in window` always evaluates to true with typescript's modern typings. This causes `window` to be // `never` later in `window.navigator`. That's why we need the explicit `window as Window` cast - return 'ontouchstart' in window || navigator.maxTouchPoints > 0; + return 'ontouchstart' in mainWindow || navigator.maxTouchPoints > 0; } public override dispose(): void { @@ -283,7 +284,7 @@ export class Gesture extends Disposable { } private inertia(targetWindow: Window, dispatchTo: readonly EventTarget[], t1: number, vX: number, dirX: number, x: number, vY: number, dirY: number, y: number): void { - this.handle = DomUtils.scheduleAtNextAnimationFrame(() => { + this.handle = DomUtils.scheduleAtNextAnimationFrame(targetWindow, () => { const now = Date.now(); // velocity: old speed + accel_over_time @@ -313,7 +314,7 @@ export class Gesture extends Disposable { if (!stopped) { this.inertia(targetWindow, dispatchTo, now, vX, dirX, x + delta_pos_x, vY, dirY, y + delta_pos_y); } - }, targetWindow); + }); } private onTouchMove(e: TouchEvent): void { diff --git a/src/vs/base/browser/trustedTypes.ts b/src/vs/base/browser/trustedTypes.ts index 4503f210481..48c02ca8c97 100644 --- a/src/vs/base/browser/trustedTypes.ts +++ b/src/vs/base/browser/trustedTypes.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { mainWindow } from 'vs/base/browser/window'; import { onUnexpectedError } from 'vs/base/common/errors'; export function createTrustedTypesPolicy( @@ -27,7 +28,7 @@ export function createTrustedTypesPolicy { + disposables.add(dom.modify(dom.getWindow(this._domNode), () => { this._dimension = dim; this._domNode.style.width = `${dim.width}px`; this._domNode.style.height = `${dim.height}px`; disposables.add(this._updateScrollbar()); - }, dom.getWindow(this._domNode))); + })); return disposables; } private _updateScrollbar(): IDisposable { - return dom.measure(() => { - dom.measure(() => { // double RAF + return dom.measure(dom.getWindow(this._domNode), () => { + dom.measure(dom.getWindow(this._domNode), () => { // double RAF this._scrollable.setRevealOnScroll(false); this._scrollable.scanDomNode(); this._scrollable.setRevealOnScroll(true); - }, dom.getWindow(this._domNode)); - }, dom.getWindow(this._domNode)); + }); + }); } private _style(styleElement: HTMLStyleElement, style: IBreadcrumbsWidgetStyles): void { diff --git a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts index f61963e182b..a5fee4835b3 100644 --- a/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts +++ b/src/vs/base/browser/ui/dropdown/dropdownActionViewItem.ts @@ -203,7 +203,7 @@ export class ActionWithDropdownActionViewItem extends ActionViewItem { separator.classList.toggle('prominent', menuActionClassNames.includes('prominent')); append(this.element, separator); - this.dropdownMenuActionViewItem = new DropdownMenuActionViewItem(this._register(new Action('dropdownAction', nls.localize('moreActions', "More Actions..."))), menuActionsProvider, this.contextMenuProvider, { classNames: ['dropdown', ...ThemeIcon.asClassNameArray(Codicon.dropDownButton), ...menuActionClassNames] }); + this.dropdownMenuActionViewItem = this._register(new DropdownMenuActionViewItem(this._register(new Action('dropdownAction', nls.localize('moreActions', "More Actions..."))), menuActionsProvider, this.contextMenuProvider, { classNames: ['dropdown', ...ThemeIcon.asClassNameArray(Codicon.dropDownButton), ...menuActionClassNames] })); this.dropdownMenuActionViewItem.render(this.element); this._register(addDisposableListener(this.element, EventType.KEY_DOWN, e => { diff --git a/src/vs/base/browser/ui/list/listView.ts b/src/vs/base/browser/ui/list/listView.ts index 24af99d7e50..af760c78a6d 100644 --- a/src/vs/base/browser/ui/list/listView.ts +++ b/src/vs/base/browser/ui/list/listView.ts @@ -407,7 +407,7 @@ export class ListView implements IListView { this.scrollable = this.disposables.add(new Scrollable({ forceIntegerValues: true, smoothScrollDuration: (options.smoothScrolling ?? false) ? 125 : 0, - scheduleAtNextAnimationFrame: cb => scheduleAtNextAnimationFrame(cb, getWindow(this.domNode)) + scheduleAtNextAnimationFrame: cb => scheduleAtNextAnimationFrame(getWindow(this.domNode), cb) })); this.scrollableElement = this.disposables.add(new SmoothScrollableElement(this.rowsContainer, { alwaysConsumeMouseWheel: options.alwaysConsumeMouseWheel ?? DefaultOptions.alwaysConsumeMouseWheel, @@ -680,11 +680,11 @@ export class ListView implements IListView { this.rowsContainer.style.height = `${this._scrollHeight}px`; if (!this.scrollableElementUpdateDisposable) { - this.scrollableElementUpdateDisposable = scheduleAtNextAnimationFrame(() => { + this.scrollableElementUpdateDisposable = scheduleAtNextAnimationFrame(getWindow(this.domNode), () => { this.scrollableElement.setScrollDimensions({ scrollHeight: this.scrollHeight }); this.updateScrollWidth(); this.scrollableElementUpdateDisposable = null; - }, getWindow(this.domNode)); + }); } } @@ -940,7 +940,7 @@ export class ListView implements IListView { item.row.domNode.style.width = 'fit-content'; item.width = getContentWidth(item.row.domNode); - const style = window.getComputedStyle(item.row.domNode); + const style = getWindow(item.row.domNode).getComputedStyle(item.row.domNode); if (style.paddingLeft) { item.width += parseFloat(style.paddingLeft); @@ -1291,7 +1291,7 @@ export class ListView implements IListView { private setupDragAndDropScrollTopAnimation(event: DragEvent): void { if (!this.dragOverAnimationDisposable) { const viewTop = getTopLeftOffset(this.domNode).top; - this.dragOverAnimationDisposable = animate(this.animateDragAndDropScrollTop.bind(this, viewTop), getWindow(this.domNode)); + this.dragOverAnimationDisposable = animate(getWindow(this.domNode), this.animateDragAndDropScrollTop.bind(this, viewTop)); } this.dragOverAnimationStopDisposable.dispose(); diff --git a/src/vs/base/browser/ui/list/listWidget.ts b/src/vs/base/browser/ui/list/listWidget.ts index 60f2ee87f1e..e05bf2c9dab 100644 --- a/src/vs/base/browser/ui/list/listWidget.ts +++ b/src/vs/base/browser/ui/list/listWidget.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { IDragAndDropData } from 'vs/base/browser/dnd'; -import { asCssValueWithDefault, createStyleSheet, Dimension, EventHelper, getActiveElement, isActiveElement, isMouseEvent } from 'vs/base/browser/dom'; +import { asCssValueWithDefault, createStyleSheet, Dimension, EventHelper, getActiveElement, getWindow, isActiveElement, isMouseEvent } from 'vs/base/browser/dom'; import { DomEmitter } from 'vs/base/browser/event'; import { IKeyboardEvent, StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Gesture } from 'vs/base/browser/touch'; @@ -618,7 +618,7 @@ class DOMFocusController implements IDisposable { return; } - const style = window.getComputedStyle(tabIndexElement); + const style = getWindow(tabIndexElement).getComputedStyle(tabIndexElement); if (style.visibility === 'hidden' || style.display === 'none') { return; } @@ -1390,7 +1390,7 @@ export class List implements ISpliceable, IDisposable { const fromMouse = Event.chain(this.view.onContextMenu, $ => $.filter(_ => !didJustPressContextMenuKey) - .map(({ element, index, browserEvent }) => ({ element, index, anchor: new StandardMouseEvent(browserEvent), browserEvent })) + .map(({ element, index, browserEvent }) => ({ element, index, anchor: new StandardMouseEvent(getWindow(this.view.domNode), browserEvent), browserEvent })) ); return Event.any>(fromKeyDown, fromKeyUp, fromMouse); diff --git a/src/vs/base/browser/ui/menu/menu.ts b/src/vs/base/browser/ui/menu/menu.ts index 64c1daf2838..0a4415803db 100644 --- a/src/vs/base/browser/ui/menu/menu.ts +++ b/src/vs/base/browser/ui/menu/menu.ts @@ -478,7 +478,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem { // => to get the Copy and Paste context menu actions working on Firefox, // there should be no timeout here if (isFirefox) { - const mouseEvent = new StandardMouseEvent(e); + const mouseEvent = new StandardMouseEvent(getWindow(this.element), e); // Allowing right click to trigger the event causes the issue described below, // but since the solution below does not work in FF, we must disable right click @@ -876,7 +876,7 @@ class SubmenuMenuActionViewItem extends BaseMenuActionViewItem { // Set the top value of the menu container before construction // This allows the menu constructor to calculate the proper max height - const computedStyles = getComputedStyle(this.parentData.parent.domNode); + const computedStyles = getWindow(this.parentData.parent.domNode).getComputedStyle(this.parentData.parent.domNode); const paddingTop = parseFloat(computedStyles.paddingTop || '0') || 0; // this.submenuContainer.style.top = `${this.element.offsetTop - this.parentData.parent.scrollOffset - paddingTop}px`; this.submenuContainer.style.zIndex = '1'; diff --git a/src/vs/base/browser/ui/menu/menubar.ts b/src/vs/base/browser/ui/menu/menubar.ts index 58b3f29af0f..5c6ff94f205 100644 --- a/src/vs/base/browser/ui/menu/menubar.ts +++ b/src/vs/base/browser/ui/menu/menubar.ts @@ -258,7 +258,7 @@ export class MenuBar extends Disposable { this._register(DOM.addDisposableListener(buttonElement, DOM.EventType.MOUSE_DOWN, (e: MouseEvent) => { // Ignore non-left-click - const mouseEvent = new StandardMouseEvent(e); + const mouseEvent = new StandardMouseEvent(DOM.getWindow(buttonElement), e); if (!mouseEvent.leftButton) { e.preventDefault(); return; @@ -367,7 +367,7 @@ export class MenuBar extends Disposable { this._register(DOM.addDisposableListener(buttonElement, DOM.EventType.MOUSE_DOWN, (e) => { // Ignore non-left-click - const mouseEvent = new StandardMouseEvent(e); + const mouseEvent = new StandardMouseEvent(DOM.getWindow(buttonElement), e); if (!mouseEvent.leftButton) { e.preventDefault(); return; @@ -627,10 +627,10 @@ export class MenuBar extends Disposable { }); if (!this.overflowLayoutScheduled) { - this.overflowLayoutScheduled = DOM.scheduleAtNextAnimationFrame(() => { + this.overflowLayoutScheduled = DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this.container), () => { this.updateOverflowAction(); this.overflowLayoutScheduled = undefined; - }, DOM.getWindow(this.container)); + }); } this.setUnfocusedState(); diff --git a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts index ac02cd0c567..ea8a90ea6c0 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollableElement.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollableElement.ts @@ -576,7 +576,7 @@ export class ScrollableElement extends AbstractScrollableElement { const scrollable = new Scrollable({ forceIntegerValues: true, smoothScrollDuration: 0, - scheduleAtNextAnimationFrame: (callback) => dom.scheduleAtNextAnimationFrame(callback, dom.getWindow(element)) + scheduleAtNextAnimationFrame: (callback) => dom.scheduleAtNextAnimationFrame(dom.getWindow(element), callback) }); super(element, options, scrollable); this._register(scrollable); @@ -621,7 +621,7 @@ export class DomScrollableElement extends AbstractScrollableElement { const scrollable = new Scrollable({ forceIntegerValues: false, // See https://github.com/microsoft/vscode/issues/139877 smoothScrollDuration: 0, - scheduleAtNextAnimationFrame: (callback) => dom.scheduleAtNextAnimationFrame(callback, dom.getWindow(element)) + scheduleAtNextAnimationFrame: (callback) => dom.scheduleAtNextAnimationFrame(dom.getWindow(element), callback) }); super(element, options, scrollable); this._register(scrollable); diff --git a/src/vs/base/browser/ui/scrollbar/scrollbarArrow.ts b/src/vs/base/browser/ui/scrollbar/scrollbarArrow.ts index 2f6e33eeeb6..c786a576ce6 100644 --- a/src/vs/base/browser/ui/scrollbar/scrollbarArrow.ts +++ b/src/vs/base/browser/ui/scrollbar/scrollbarArrow.ts @@ -5,7 +5,7 @@ import { GlobalPointerMoveMonitor } from 'vs/base/browser/globalPointerMoveMonitor'; import { Widget } from 'vs/base/browser/ui/widget'; -import { IntervalTimer, TimeoutTimer } from 'vs/base/common/async'; +import { TimeoutTimer } from 'vs/base/common/async'; import { ThemeIcon } from 'vs/base/common/themables'; import * as dom from 'vs/base/browser/dom'; @@ -33,7 +33,7 @@ export class ScrollbarArrow extends Widget { private _onActivate: () => void; public bgDomNode: HTMLElement; public domNode: HTMLElement; - private _pointerdownRepeatTimer: IntervalTimer; + private _pointerdownRepeatTimer: dom.WindowIntervalTimer; private _pointerdownScheduleRepeatTimer: TimeoutTimer; private _pointerMoveMonitor: GlobalPointerMoveMonitor; @@ -83,7 +83,7 @@ export class ScrollbarArrow extends Widget { this._register(dom.addStandardDisposableListener(this.bgDomNode, dom.EventType.POINTER_DOWN, (e) => this._arrowPointerDown(e))); this._register(dom.addStandardDisposableListener(this.domNode, dom.EventType.POINTER_DOWN, (e) => this._arrowPointerDown(e))); - this._pointerdownRepeatTimer = this._register(new IntervalTimer()); + this._pointerdownRepeatTimer = this._register(new dom.WindowIntervalTimer()); this._pointerdownScheduleRepeatTimer = this._register(new TimeoutTimer()); } @@ -92,7 +92,7 @@ export class ScrollbarArrow extends Widget { return; } const scheduleRepeater = () => { - this._pointerdownRepeatTimer.cancelAndSet(() => this._onActivate(), 1000 / 24); + this._pointerdownRepeatTimer.cancelAndSet(() => this._onActivate(), 1000 / 24, dom.getWindow(e)); }; this._onActivate(); diff --git a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts index 4ec3abac78e..83073ee2666 100644 --- a/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts +++ b/src/vs/base/browser/ui/selectBox/selectBoxCustom.ts @@ -547,7 +547,7 @@ export class SelectBoxList extends Disposable implements ISelectBoxDelegate, ILi const window = dom.getWindow(this.selectElement); const selectPosition = dom.getDomNodePagePosition(this.selectElement); - const styles = getComputedStyle(this.selectElement); + const styles = dom.getWindow(this.selectElement).getComputedStyle(this.selectElement); const verticalPadding = parseFloat(styles.getPropertyValue('--dropdown-padding-top')) + parseFloat(styles.getPropertyValue('--dropdown-padding-bottom')); const maxSelectDropDownHeightBelow = (window.innerHeight - selectPosition.top - selectPosition.height - (this.selectBoxOptions.minBottomMargin || 0)); const maxSelectDropDownHeightAbove = (selectPosition.top - SelectBoxList.DEFAULT_DROPDOWN_MINIMUM_TOP_MARGIN); diff --git a/src/vs/base/browser/ui/splitview/paneview.ts b/src/vs/base/browser/ui/splitview/paneview.ts index 2747c01eff2..0b519e43411 100644 --- a/src/vs/base/browser/ui/splitview/paneview.ts +++ b/src/vs/base/browser/ui/splitview/paneview.ts @@ -5,7 +5,7 @@ import { isFirefox } from 'vs/base/browser/browser'; import { DataTransfers } from 'vs/base/browser/dnd'; -import { $, addDisposableListener, append, clearNode, EventHelper, EventType, trackFocus } from 'vs/base/browser/dom'; +import { $, addDisposableListener, append, clearNode, EventHelper, EventType, getWindow, trackFocus } from 'vs/base/browser/dom'; import { DomEmitter } from 'vs/base/browser/event'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { Gesture, EventType as TouchEventType } from 'vs/base/browser/touch'; @@ -175,11 +175,11 @@ export abstract class Pane extends Disposable implements IView { } if (typeof this.animationTimer === 'number') { - clearTimeout(this.animationTimer); + getWindow(this.element).clearTimeout(this.animationTimer); } append(this.element, this.body); } else { - this.animationTimer = window.setTimeout(() => { + this.animationTimer = getWindow(this.element).setTimeout(() => { this.body.remove(); }, 200); } @@ -636,12 +636,12 @@ export class PaneView extends Disposable { private setupAnimation(): void { if (typeof this.animationTimer === 'number') { - window.clearTimeout(this.animationTimer); + getWindow(this.element).clearTimeout(this.animationTimer); } this.element.classList.add('animated'); - this.animationTimer = window.setTimeout(() => { + this.animationTimer = getWindow(this.element).setTimeout(() => { this.animationTimer = undefined; this.element.classList.remove('animated'); }, 200); diff --git a/src/vs/base/browser/ui/splitview/splitview.ts b/src/vs/base/browser/ui/splitview/splitview.ts index e88947b044e..fd0251c432f 100644 --- a/src/vs/base/browser/ui/splitview/splitview.ts +++ b/src/vs/base/browser/ui/splitview/splitview.ts @@ -584,7 +584,7 @@ export class SplitView scheduleAtNextAnimationFrame(callback, getWindow(this.el)), + scheduleAtNextAnimationFrame: callback => scheduleAtNextAnimationFrame(getWindow(this.el), callback), })); this.scrollableElement = this._register(new SmoothScrollableElement(this.viewContainer, { vertical: this.orientation === Orientation.VERTICAL ? (options.scrollbarVisibility ?? ScrollbarVisibility.Auto) : ScrollbarVisibility.Hidden, diff --git a/src/vs/base/browser/ui/widget.ts b/src/vs/base/browser/ui/widget.ts index 2586cce024e..440a9ace259 100644 --- a/src/vs/base/browser/ui/widget.ts +++ b/src/vs/base/browser/ui/widget.ts @@ -12,19 +12,19 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; export abstract class Widget extends Disposable { protected onclick(domNode: HTMLElement, listener: (e: IMouseEvent) => void): void { - this._register(dom.addDisposableListener(domNode, dom.EventType.CLICK, (e: MouseEvent) => listener(new StandardMouseEvent(e)))); + this._register(dom.addDisposableListener(domNode, dom.EventType.CLICK, (e: MouseEvent) => listener(new StandardMouseEvent(dom.getWindow(domNode), e)))); } protected onmousedown(domNode: HTMLElement, listener: (e: IMouseEvent) => void): void { - this._register(dom.addDisposableListener(domNode, dom.EventType.MOUSE_DOWN, (e: MouseEvent) => listener(new StandardMouseEvent(e)))); + this._register(dom.addDisposableListener(domNode, dom.EventType.MOUSE_DOWN, (e: MouseEvent) => listener(new StandardMouseEvent(dom.getWindow(domNode), e)))); } protected onmouseover(domNode: HTMLElement, listener: (e: IMouseEvent) => void): void { - this._register(dom.addDisposableListener(domNode, dom.EventType.MOUSE_OVER, (e: MouseEvent) => listener(new StandardMouseEvent(e)))); + this._register(dom.addDisposableListener(domNode, dom.EventType.MOUSE_OVER, (e: MouseEvent) => listener(new StandardMouseEvent(dom.getWindow(domNode), e)))); } protected onmouseleave(domNode: HTMLElement, listener: (e: IMouseEvent) => void): void { - this._register(dom.addDisposableListener(domNode, dom.EventType.MOUSE_LEAVE, (e: MouseEvent) => listener(new StandardMouseEvent(e)))); + this._register(dom.addDisposableListener(domNode, dom.EventType.MOUSE_LEAVE, (e: MouseEvent) => listener(new StandardMouseEvent(dom.getWindow(domNode), e)))); } protected onkeydown(domNode: HTMLElement, listener: (e: IKeyboardEvent) => void): void { diff --git a/src/vs/base/browser/window.ts b/src/vs/base/browser/window.ts new file mode 100644 index 00000000000..dde39958ff4 --- /dev/null +++ b/src/vs/base/browser/window.ts @@ -0,0 +1,18 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +export type CodeWindow = Window & typeof globalThis & { + readonly vscodeWindowId: number; +}; + +// eslint-disable-next-line no-restricted-globals +export const mainWindow = window as CodeWindow; + +/** + * @deprecated to support multi-window scenarios, use `DOM.mainWindow` + * if you target the main global window or use helpers such as `DOM.getWindow()` + * or `DOM.getActiveWindow()` to obtain the correct window for the context you are in. + */ +export const $window = mainWindow; diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index fa02c451db4..14cad1ec215 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -522,20 +522,6 @@ export function disposableTimeout(handler: () => void, timeout = 0, store?: Disp return disposable; } -export function disposableInterval(handler: () => boolean /* stop interval */, interval: number, iterations: number): IDisposable { - let iteration = 0; - const timer = setInterval(() => { - iteration++; - if (iteration >= iterations || handler()) { - disposable.dispose(); - } - }, interval); - const disposable = toDisposable(() => { - clearInterval(timer); - }); - return disposable; -} - /** * Runs the provided list of promise factories in sequential order. The returned * promise will complete to an array of results from each promise. @@ -894,29 +880,28 @@ export class TimeoutTimer implements IDisposable { export class IntervalTimer implements IDisposable { - private _token: any; + private disposable: IDisposable | undefined = undefined; - constructor() { - this._token = -1; + cancel(): void { + this.disposable?.dispose(); + this.disposable = undefined; + } + + cancelAndSet(runner: () => void, interval: number, context = globalThis): void { + this.cancel(); + const handle = context.setInterval(() => { + runner(); + }, interval); + + this.disposable = toDisposable(() => { + context.clearInterval(handle); + this.disposable = undefined; + }); } dispose(): void { this.cancel(); } - - cancel(): void { - if (this._token !== -1) { - clearInterval(this._token); - this._token = -1; - } - } - - cancelAndSet(runner: () => void, interval: number): void { - this.cancel(); - this._token = setInterval(() => { - runner(); - }, interval); - } } export class RunOnceScheduler implements IDisposable { @@ -1220,6 +1205,9 @@ export interface IdleDeadline { timeRemaining(): number; } +type IdleApi = Pick; + + /** * Execute the callback the next time the browser is idle, returning an * {@link IDisposable} that will cancel the callback when disposed. This wraps @@ -1237,26 +1225,29 @@ export interface IdleDeadline { * [IdleDeadline]: https://developer.mozilla.org/en-US/docs/Web/API/IdleDeadline * [requestIdleCallback]: https://developer.mozilla.org/en-US/docs/Web/API/Window/requestIdleCallback * [setTimeout]: https://developer.mozilla.org/en-US/docs/Web/API/Window/setTimeout + * + * **Note** that there is `dom.ts#runWhenWindowIdle` which is better suited when running inside a browser + * context */ -export let runWhenIdle: (callback: (idle: IdleDeadline) => void, timeout?: number) => IDisposable; +export let runWhenGlobalIdle: (callback: (idle: IdleDeadline) => void, timeout?: number) => IDisposable; -declare function requestIdleCallback(callback: (args: IdleDeadline) => void, options?: { timeout: number }): number; -declare function cancelIdleCallback(handle: number): void; +export let _runWhenIdle: (targetWindow: IdleApi, callback: (idle: IdleDeadline) => void, timeout?: number) => IDisposable; (function () { - if (typeof requestIdleCallback !== 'function' || typeof cancelIdleCallback !== 'function') { - runWhenIdle = (runner) => { + if (typeof globalThis.requestIdleCallback !== 'function' || typeof globalThis.cancelIdleCallback !== 'function') { + _runWhenIdle = (_targetWindow, runner) => { setTimeout0(() => { if (disposed) { return; } const end = Date.now() + 15; // one frame at 64fps - runner(Object.freeze({ + const deadline: IdleDeadline = { didTimeout: true, timeRemaining() { return Math.max(0, end - Date.now()); } - })); + }; + runner(Object.freeze(deadline)); }); let disposed = false; return { @@ -1269,8 +1260,8 @@ declare function cancelIdleCallback(handle: number): void; }; }; } else { - runWhenIdle = (runner, timeout?) => { - const handle: number = requestIdleCallback(runner, typeof timeout === 'number' ? { timeout } : undefined); + _runWhenIdle = (targetWindow: IdleApi, runner, timeout?) => { + const handle: number = targetWindow.requestIdleCallback(runner, typeof timeout === 'number' ? { timeout } : undefined); let disposed = false; return { dispose() { @@ -1278,18 +1269,15 @@ declare function cancelIdleCallback(handle: number): void; return; } disposed = true; - cancelIdleCallback(handle); + targetWindow.cancelIdleCallback(handle); } }; }; } + runWhenGlobalIdle = (runner) => _runWhenIdle(globalThis, runner); })(); -/** - * An implementation of the "idle-until-urgent"-strategy as introduced - * here: https://philipwalton.com/articles/idle-until-urgent/ - */ -export class IdleValue { +export abstract class AbstractIdleValue { private readonly _executor: () => void; private readonly _handle: IDisposable; @@ -1298,7 +1286,7 @@ export class IdleValue { private _value?: T; private _error: unknown; - constructor(executor: () => T) { + constructor(targetWindow: IdleApi, executor: () => T) { this._executor = () => { try { this._value = executor(); @@ -1308,7 +1296,7 @@ export class IdleValue { this._didRun = true; } }; - this._handle = runWhenIdle(() => this._executor()); + this._handle = _runWhenIdle(targetWindow, () => this._executor()); } dispose(): void { @@ -1331,6 +1319,19 @@ export class IdleValue { } } +/** + * An `IdleValue` that always uses the current window (which might be throttled or inactive) + * + * **Note** that there is `dom.ts#WindowIdleValue` which is better suited when running inside a browser + * context + */ +export class GlobalIdleValue extends AbstractIdleValue { + + constructor(executor: () => T) { + super(globalThis, executor); + } +} + //#endregion export async function retry(task: ITask>, delay: number, retries: number): Promise { diff --git a/src/vs/base/common/event.ts b/src/vs/base/common/event.ts index 9f76502f48a..07f7846157a 100644 --- a/src/vs/base/common/event.ts +++ b/src/vs/base/common/event.ts @@ -540,8 +540,8 @@ export namespace Event { export interface IChainableSythensis { map(fn: (i: T) => O): IChainableSythensis; forEach(fn: (i: T) => void): IChainableSythensis; + filter(fn: (e: T) => e is R): IChainableSythensis; filter(fn: (e: T) => boolean): IChainableSythensis; - filter(fn: (e: T | R) => e is R): IChainableSythensis; reduce(merge: (last: R, event: T) => R, initial: R): IChainableSythensis; reduce(merge: (last: R | undefined, event: T) => R): IChainableSythensis; latch(equals?: (a: T, b: T) => boolean): IChainableSythensis; diff --git a/src/vs/base/common/hotReload.ts b/src/vs/base/common/hotReload.ts index 17724907937..3544ff2c514 100644 --- a/src/vs/base/common/hotReload.ts +++ b/src/vs/base/common/hotReload.ts @@ -14,7 +14,6 @@ export function registerHotReloadHandler(handler: HotReloadHandler): IDisposable return { dispose() { } }; } else { const handlers = registerGlobalHotReloadHandler(); - handlers.add(handler); return { dispose() { handlers.delete(handler); } @@ -28,7 +27,7 @@ export function registerHotReloadHandler(handler: HotReloadHandler): IDisposable * * If no handler can apply the new exports, the module will not be reloaded. */ -export type HotReloadHandler = (oldExports: Record) => AcceptNewExportsHandler | undefined; +export type HotReloadHandler = (args: { oldExports: Record; newSrc: string }) => AcceptNewExportsHandler | undefined; export type AcceptNewExportsHandler = (newExports: Record) => boolean; function registerGlobalHotReloadHandler() { @@ -50,10 +49,44 @@ function registerGlobalHotReloadHandler() { return hotReloadHandlers; } -let hotReloadHandlers: Set<(oldExports: Record) => AcceptNewExportsFn | undefined> | undefined = undefined; +let hotReloadHandlers: Set<(args: { oldExports: Record; newSrc: string }) => AcceptNewExportsFn | undefined> | undefined = undefined; interface GlobalThisAddition { - $hotReload_applyNewExports?(oldExports: Record): AcceptNewExportsFn | undefined; + $hotReload_applyNewExports?(args: { oldExports: Record; newSrc: string }): AcceptNewExportsFn | undefined; } + type AcceptNewExportsFn = (newExports: Record) => boolean; + +if (isHotReloadEnabled()) { + // This code does not run in production. + registerHotReloadHandler(({ oldExports, newSrc }) => { + // Don't match its own source code + if (newSrc.indexOf('/* ' + 'hot-reload:patch-prototype-methods */') === -1) { + return undefined; + } + return newExports => { + for (const key in newExports) { + const exportedItem = newExports[key]; + console.log(`[hot-reload] Patching prototype methods of '${key}'`, { exportedItem }); + if (typeof exportedItem === 'function' && exportedItem.prototype) { + const oldExportedItem = oldExports[key]; + if (oldExportedItem) { + for (const prop of Object.getOwnPropertyNames(exportedItem.prototype)) { + const descriptor = Object.getOwnPropertyDescriptor(exportedItem.prototype, prop)!; + const oldDescriptor = Object.getOwnPropertyDescriptor((oldExportedItem as any).prototype, prop); + + if (descriptor?.value?.toString() !== oldDescriptor?.value?.toString()) { + console.log(`[hot-reload] Patching prototype method '${key}.${prop}'`); + } + + Object.defineProperty((oldExportedItem as any).prototype, prop, descriptor); + } + newExports[key] = oldExportedItem; + } + } + } + return true; + }; + }); +} diff --git a/src/vs/base/common/labels.ts b/src/vs/base/common/labels.ts index 707ed621b22..1229b7f59e8 100644 --- a/src/vs/base/common/labels.ts +++ b/src/vs/base/common/labels.ts @@ -80,10 +80,10 @@ export function getPathLabel(resource: URI, formatting: IPathLabelFormatting): s // to a user home resource. We cannot assume that the resource is // already a user home resource. let userHomeCandidate: string; - if (resource.scheme !== tildifier.userHome.scheme && resource.path.startsWith(posix.sep)) { + if (resource.scheme !== tildifier.userHome.scheme && resource.path[0] === posix.sep && resource.path[1] !== posix.sep) { userHomeCandidate = tildifier.userHome.with({ path: resource.path }).fsPath; } else { - userHomeCandidate = resource.fsPath; + userHomeCandidate = absolutePath; } absolutePath = tildify(userHomeCandidate, userHome, os); @@ -108,7 +108,7 @@ function getRelativePathLabel(resource: URI, relativePathProvider: IRelativePath // the resource belongs to, we need to make sure to convert it // to a workspace resource. We cannot assume that the resource is // already matching the workspace. - if (resource.scheme !== firstFolder.uri.scheme && resource.path.startsWith(posix.sep)) { + if (resource.scheme !== firstFolder.uri.scheme && resource.path[0] === posix.sep && resource.path[1] !== posix.sep) { resource = firstFolder.uri.with({ path: resource.path }); } diff --git a/src/vs/base/common/lifecycle.ts b/src/vs/base/common/lifecycle.ts index fedbb85a6cf..4e5aed3d277 100644 --- a/src/vs/base/common/lifecycle.ts +++ b/src/vs/base/common/lifecycle.ts @@ -556,6 +556,35 @@ export class MutableDisposable implements IDisposable { } } +/** + * Manages the lifecycle of a disposable value that may be changed like {@link MutableDisposable}, but the value must + * exist and cannot be undefined. + */ +export class MandatoryMutableDisposable implements IDisposable { + private _disposable = new MutableDisposable(); + private _isDisposed = false; + + constructor(initialValue: T) { + this._disposable.value = initialValue; + } + + get value(): T { + return this._disposable.value!; + } + + set value(value: T) { + if (this._isDisposed || value === this._disposable.value) { + return; + } + this._disposable.value = value; + } + + dispose() { + this._isDisposed = true; + this._disposable.dispose(); + } +} + export class RefCountedDisposable { private _counter: number = 1; @@ -752,6 +781,10 @@ export class DisposableMap implements ID return this._store.keys(); } + values(): IterableIterator { + return this._store.values(); + } + [Symbol.iterator](): IterableIterator<[K, V]> { return this._store[Symbol.iterator](); } diff --git a/src/vs/base/common/network.ts b/src/vs/base/common/network.ts index d1e52876883..ad4808472c6 100644 --- a/src/vs/base/common/network.ts +++ b/src/vs/base/common/network.ts @@ -229,7 +229,7 @@ class FileAccessImpl { // ...and we run in native environments platform.isNative || // ...or web worker extensions on desktop - (platform.isWebWorker && platform.globals.origin === `${Schemas.vscodeFileResource}://${FileAccessImpl.FALLBACK_AUTHORITY}`) + (platform.webWorkerOrigin === `${Schemas.vscodeFileResource}://${FileAccessImpl.FALLBACK_AUTHORITY}`) ) ) { return uri.with({ diff --git a/src/vs/base/common/observable.ts b/src/vs/base/common/observable.ts index 2e0023bbc55..033cecf010a 100644 --- a/src/vs/base/common/observable.ts +++ b/src/vs/base/common/observable.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +// This is a facade for the observable implementation. Only import from here! + export { IObservable, IObserver, diff --git a/src/vs/base/common/observableInternal/autorun.ts b/src/vs/base/common/observableInternal/autorun.ts index 050e9bae1c3..6e7fdcf6d52 100644 --- a/src/vs/base/common/observableInternal/autorun.ts +++ b/src/vs/base/common/observableInternal/autorun.ts @@ -8,14 +8,33 @@ import { DisposableStore, IDisposable, markAsDisposed, toDisposable, trackDispos import { IReader, IObservable, IObserver, IChangeContext, getFunctionName } from 'vs/base/common/observableInternal/base'; import { getLogger } from 'vs/base/common/observableInternal/logging'; -export function autorunOpts(options: { debugName: string | (() => string | undefined) }, fn: (reader: IReader) => void): IDisposable { - return new AutorunObserver(options.debugName, fn, undefined, undefined); -} - +/** + * Runs immediately and whenever a transaction ends and an observed observable changed. + * {@link fn} should start with a JS Doc using `@description` to name the autorun. + */ export function autorun(fn: (reader: IReader) => void): IDisposable { return new AutorunObserver(undefined, fn, undefined, undefined); } +/** + * Runs immediately and whenever a transaction ends and an observed observable changed. + * {@link fn} should start with a JS Doc using `@description` to name the autorun. + */ +export function autorunOpts(options: { debugName?: string | (() => string | undefined) }, fn: (reader: IReader) => void): IDisposable { + return new AutorunObserver(options.debugName, fn, undefined, undefined); +} + +/** + * Runs immediately and whenever a transaction ends and an observed observable changed. + * {@link fn} should start with a JS Doc using `@description` to name the autorun. + * + * Use `createEmptyChangeSummary` to create a "change summary" that can collect the changes. + * Use `handleChange` to add a reported change to the change summary. + * The run function is given the last change summary. + * The change summary is discarded after the run function was called. + * + * @see autorun + */ export function autorunHandleChanges( options: { debugName?: string | (() => string | undefined); @@ -27,6 +46,9 @@ export function autorunHandleChanges( return new AutorunObserver(options.debugName, fn, options.createEmptyChangeSummary, options.handleChange); } +/** + * @see autorunHandleChanges (but with a disposable store that is cleared before the next run or on dispose) + */ export function autorunWithStoreHandleChanges( options: { debugName?: string | (() => string | undefined); @@ -53,6 +75,9 @@ export function autorunWithStoreHandleChanges( }); } +/** + * @see autorun (but with a disposable store that is cleared before the next run or on dispose) + */ export function autorunWithStore(fn: (reader: IReader, store: DisposableStore) => void): IDisposable { const store = new DisposableStore(); const disposable = autorunOpts( @@ -140,15 +165,18 @@ export class AutorunObserver implements IObserver, IReader this.state = AutorunState.upToDate; + const isDisposed = this.disposed; try { - if (!this.disposed) { + if (!isDisposed) { getLogger()?.handleAutorunTriggered(this); const changeSummary = this.changeSummary!; this.changeSummary = this.createChangeSummary?.(); this._runFn(this, changeSummary); } } finally { - getLogger()?.handleAutorunFinished(this); + if (!isDisposed) { + getLogger()?.handleAutorunFinished(this); + } // We don't want our observed observables to think that they are (not even temporarily) not being observed. // Thus, we only unsubscribe from observables that are definitely not read anymore. for (const o of this.dependenciesToBeRemoved) { diff --git a/src/vs/base/common/observableInternal/base.ts b/src/vs/base/common/observableInternal/base.ts index bc7f900caed..d925197b3f8 100644 --- a/src/vs/base/common/observableInternal/base.ts +++ b/src/vs/base/common/observableInternal/base.ts @@ -3,15 +3,19 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IDisposable } from 'vs/base/common/lifecycle'; +import { DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; +import { recomputeInitiallyAndOnChange } from 'vs/base/common/observable'; import type { derivedOpts } from 'vs/base/common/observableInternal/derived'; import { getLogger } from 'vs/base/common/observableInternal/logging'; /** * Represents an observable value. * - * @template T The type of the value. - * @template TChange The type of delta information (usually `void` and only used in advanced scenarios). + * @template T The type of the values the observable can hold. + * @template TChange The type used to describe value changes + * (usually `void` and only used in advanced scenarios). + * While observers can miss temporary values of an observable, + * they will receive all change values (as long as they are subscribed)! */ export interface IObservable { /** @@ -23,7 +27,7 @@ export interface IObservable { get(): T; /** - * Forces the observable to check for and report changes. + * Forces the observable to check for changes and report them. * * Has the same effect as calling {@link IObservable.get}, but does not force the observable * to actually construct the value, e.g. if change deltas are used. @@ -45,21 +49,26 @@ export interface IObservable { removeObserver(observer: IObserver): void; /** - * Reads the current value and subscribes to this observable. + * Reads the current value and subscribes the reader to this observable. * - * Just calls {@link IReader.readObservable} if a reader is given, otherwise {@link IObservable.get} - * (see {@link ConvenientObservable.read}). + * Calls {@link IReader.readObservable} if a reader is given, otherwise {@link IObservable.get} + * (see {@link ConvenientObservable.read} for the implementation). */ read(reader: IReader | undefined): T; /** * Creates a derived observable that depends on this observable. * Use the reader to read other observables - * (see {@link ConvenientObservable.map}). + * (see {@link ConvenientObservable.map} for the implementation). */ map(fn: (value: T, reader: IReader) => TNew): IObservable; map(owner: object, fn: (value: T, reader: IReader) => TNew): IObservable; + /** + * Makes sure this value is computed eagerly. + */ + recomputeInitiallyAndOnChange(store: DisposableStore, handleValue?: (value: T) => void): IObservable; + /** * A human-readable name for debugging purposes. */ @@ -92,12 +101,14 @@ export interface IObserver { * Signals that the given observable might have changed and a transaction potentially modifying that observable started. * Before the given observable can call this method again, is must call {@link IObserver.endUpdate}. * + * Implementations must not get/read the value of other observables, as they might not have received this event yet! * The method {@link IObservable.reportChanges} can be used to force the observable to report the changes. */ beginUpdate(observable: IObservable): void; /** * Signals that the transaction that potentially modified the given observable ended. + * This is a good place to react to (potential) changes. */ endUpdate(observable: IObservable): void; @@ -105,16 +116,18 @@ export interface IObserver { * Signals that the given observable might have changed. * The method {@link IObservable.reportChanges} can be used to force the observable to report the changes. * - * Implementations must not call into other observables, as they might not have received this event yet! + * Implementations must not get/read the value of other observables, as they might not have received this event yet! * The change should be processed lazily or in {@link IObserver.endUpdate}. */ handlePossibleChange(observable: IObservable): void; /** - * Signals that the given observable changed. + * Signals that the given {@link observable} changed. * - * Implementations must not call into other observables, as they might not have received this event yet! + * Implementations must not get/read the value of other observables, as they might not have received this event yet! * The change should be processed lazily or in {@link IObserver.endUpdate}. + * + * @param change Indicates how or why the value changed. */ handleChange(observable: IObservable, change: TChange): void; } @@ -123,6 +136,9 @@ export interface ISettable { /** * Sets the value of the observable. * Use a transaction to batch multiple changes (with a transaction, observers only react at the end of the transaction). + * + * @param transaction When given, value changes are handled on demand or when the transaction ends. + * @param change Describes how or why the value changed. */ set(value: T, transaction: ITransaction | undefined, change: TChange): void; } @@ -135,6 +151,12 @@ export interface ITransaction { updateObserver(observer: IObserver, observable: IObservable): void; } +let _recomputeInitiallyAndOnChange: typeof recomputeInitiallyAndOnChange; + +export function _setRecomputeInitiallyAndOnChange(recomputeInitiallyAndOnChange: typeof _recomputeInitiallyAndOnChange) { + _recomputeInitiallyAndOnChange = recomputeInitiallyAndOnChange; +} + let _derived: typeof derivedOpts; /** * @internal @@ -197,6 +219,11 @@ export abstract class ConvenientObservable implements IObservable void): IObservable { + store.add(_recomputeInitiallyAndOnChange!(this, handleValue)); + return this; + } + public abstract get debugName(): string; } @@ -222,6 +249,12 @@ export abstract class BaseObservable extends ConvenientObserv protected onLastObserverRemoved(): void { } } +/** + * Starts a transaction in which many observables can be changed at once. + * {@link fn} should start with a JS Doc using `@description` to give the transaction a debug name. + * Reaction run on demand or when the transaction ends. + */ + export function transaction(fn: (tx: ITransaction) => void, getDebugName?: () => string): void { const tx = new TransactionImpl(fn, getDebugName); try { @@ -231,6 +264,24 @@ export function transaction(fn: (tx: ITransaction) => void, getDebugName?: () => } } +let _globalTransaction: ITransaction | undefined = undefined; + +export function globalTransaction(fn: (tx: ITransaction) => void) { + if (_globalTransaction) { + fn(_globalTransaction); + } else { + const tx = new TransactionImpl(fn, undefined); + _globalTransaction = tx; + try { + fn(tx); + } finally { + tx.finish(); // During finish, more actions might be added to the transaction. + // Which is why we only clear the global transaction after finish. + _globalTransaction = undefined; + } + } +} + export async function asyncTransaction(fn: (tx: ITransaction) => Promise, getDebugName?: () => string): Promise { const tx = new TransactionImpl(fn, getDebugName); try { @@ -240,6 +291,9 @@ export async function asyncTransaction(fn: (tx: ITransaction) => Promise, } } +/** + * Allows to chain transactions. + */ export function subtransaction(tx: ITransaction | undefined, fn: (tx: ITransaction) => void, getDebugName?: () => string): void { if (!tx) { transaction(fn, getDebugName); @@ -263,17 +317,19 @@ export class TransactionImpl implements ITransaction { } public updateObserver(observer: IObserver, observable: IObservable): void { + // When this gets called while finish is active, they will still get considered this.updatingObservers!.push({ observer, observable }); observer.beginUpdate(observable); } public finish(): void { const updatingObservers = this.updatingObservers!; - // Prevent anyone from updating observers from now on. - this.updatingObservers = null; - for (const { observer, observable } of updatingObservers) { + for (let i = 0; i < updatingObservers.length; i++) { + const { observer, observable } = updatingObservers[i]; observer.endUpdate(observable); } + // Prevent anyone from updating observers from now on. + this.updatingObservers = null; getLogger()?.handleEndTransaction(); } } @@ -372,12 +428,17 @@ export function getFunctionName(fn: Function): string | undefined { return result?.trim(); } +/** + * A settable observable. + */ export interface ISettableObservable extends IObservable, ISettable { } /** * Creates an observable value. * Observers get informed when the value changes. + * @template TChange An arbitrary type to describe how or why the value changed. Defaults to `void`. + * Observers will receive every single change value. */ export function observableValue(name: string, initialValue: T): ISettableObservable; export function observableValue(owner: object, initialValue: T): ISettableObservable; @@ -445,6 +506,10 @@ export class ObservableValue } } +/** + * A disposable observable. When disposed, its value is also disposed. + * When a new value is set, the previous value is disposed. + */ export function disposableObservableValue(nameOrOwner: string | object, initialValue: T): ISettableObservable & IDisposable { if (typeof nameOrOwner === 'string') { return new DisposableObservableValue(undefined, nameOrOwner, initialValue); @@ -469,17 +534,20 @@ export class DisposableObservableValue; readonly change: unknown; + /** + * Returns if the given observable caused the change. + */ didChange(observable: IObservable): this is { change: TChange }; } - -export interface IChangeTracker { - /** - * Returns if this change should cause an invalidation. - * Can record the changes to just process deltas. - */ - handleChange(context: IChangeContext): boolean; -} diff --git a/src/vs/base/common/observableInternal/derived.ts b/src/vs/base/common/observableInternal/derived.ts index e2004b75e48..bcfe729ec37 100644 --- a/src/vs/base/common/observableInternal/derived.ts +++ b/src/vs/base/common/observableInternal/derived.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { BugIndicatingError } from 'vs/base/common/errors'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle'; import { IReader, IObservable, BaseObservable, IObserver, _setDerivedOpts, IChangeContext, getFunctionName, DebugNameFn, getDebugName } from 'vs/base/common/observableInternal/base'; import { getLogger } from 'vs/base/common/observableInternal/logging'; @@ -13,6 +13,9 @@ const defaultEqualityComparer: EqualityComparer = (a, b) => a === b; /** * Creates an observable that is derived from other observables. + * The value is only recomputed when absolutely needed. + * + * {@link computeFn} should start with a JS Doc using `@description` to name the derived. */ export function derived(computeFn: (reader: IReader) => T): IObservable; export function derived(owner: object, computeFn: (reader: IReader) => T): IObservable; @@ -34,6 +37,19 @@ export function derivedOpts( return new Derived(options.owner, options.debugName, computeFn, undefined, undefined, undefined, options.equalityComparer ?? defaultEqualityComparer); } +/** + * Represents an observable that is derived from other observables. + * The value is only recomputed when absolutely needed. + * + * {@link computeFn} should start with a JS Doc using `@description` to name the derived. + * + * Use `createEmptyChangeSummary` to create a "change summary" that can collect the changes. + * Use `handleChange` to add a reported change to the change summary. + * The compute function is given the last change summary. + * The change summary is discarded after the compute function was called. + * + * @see derived + */ export function derivedHandleChanges( options: { owner?: object; @@ -74,6 +90,37 @@ export function derivedWithStore(computeFnOrOwner: ((reader: IReader, store: ); } +export function derivedDisposable(computeFn: (reader: IReader) => T): IObservable; +export function derivedDisposable(owner: object, computeFn: (reader: IReader) => T): IObservable; +export function derivedDisposable(computeFnOrOwner: ((reader: IReader) => T) | object, computeFnOrUndefined?: ((reader: IReader) => T)): IObservable { + let computeFn: (reader: IReader) => T; + let owner: object | undefined; + if (computeFnOrUndefined === undefined) { + computeFn = computeFnOrOwner as any; + owner = undefined; + } else { + owner = computeFnOrOwner; + computeFn = computeFnOrUndefined as any; + } + + const store = new DisposableStore(); + return new Derived( + owner, + (() => getFunctionName(computeFn) ?? '(anonymous)'), + r => { + store.clear(); + const result = computeFn(r); + if (result) { + store.add(result); + } + return result; + }, undefined, + undefined, + () => store.dispose(), + defaultEqualityComparer + ); +} + _setDerivedOpts(derivedOpts); const enum DerivedState { diff --git a/src/vs/base/common/observableInternal/utils.ts b/src/vs/base/common/observableInternal/utils.ts index 77cf5fb7422..35b7c695f5a 100644 --- a/src/vs/base/common/observableInternal/utils.ts +++ b/src/vs/base/common/observableInternal/utils.ts @@ -6,7 +6,7 @@ import { Event } from 'vs/base/common/event'; import { DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { autorun } from 'vs/base/common/observableInternal/autorun'; -import { BaseObservable, ConvenientObservable, IObservable, IObserver, IReader, ITransaction, getDebugName, getFunctionName, observableValue, transaction } from 'vs/base/common/observableInternal/base'; +import { BaseObservable, ConvenientObservable, IObservable, IObserver, IReader, ITransaction, _setRecomputeInitiallyAndOnChange, getDebugName, getFunctionName, observableValue, subtransaction, transaction } from 'vs/base/common/observableInternal/base'; import { derived } from 'vs/base/common/observableInternal/derived'; import { getLogger } from 'vs/base/common/observableInternal/logging'; @@ -56,16 +56,17 @@ export function waitForState(observable: IObservable, predicate: (state: T return new Promise(resolve => { let didRun = false; let shouldDispose = false; + const stateObs = observable.map(state => ({ isFinished: predicate(state), state })); const d = autorun(reader => { /** @description waitForState */ - const currentState = observable.read(reader); - if (predicate(currentState)) { + const { isFinished, state } = stateObs.read(reader); + if (isFinished) { if (!didRun) { shouldDispose = true; } else { d.dispose(); } - resolve(currentState); + resolve(state); } }); didRun = true; @@ -83,6 +84,8 @@ export function observableFromEvent( } export class FromEventObservable extends BaseObservable { + public static globalTransaction: ITransaction | undefined; + private value: T | undefined; private hasValue = false; private subscription: IDisposable | undefined; @@ -109,17 +112,21 @@ export class FromEventObservable extends BaseObservable { private readonly handleEvent = (args: TArgs | undefined) => { const newValue = this._getValue(args); + const oldValue = this.value; - const didChange = !this.hasValue || this.value !== newValue; - - getLogger()?.handleFromEventObservableTriggered(this, { oldValue: this.value, newValue, change: undefined, didChange, hadValue: this.hasValue }); + const didChange = !this.hasValue || oldValue !== newValue; + let didRunTransaction = false; if (didChange) { this.value = newValue; if (this.hasValue) { - transaction( + didRunTransaction = true; + subtransaction( + FromEventObservable.globalTransaction, (tx) => { + getLogger()?.handleFromEventObservableTriggered(this, { oldValue, newValue, change: undefined, didChange, hadValue: this.hasValue }); + for (const o of this.observers) { tx.updateObserver(o, this); o.handleChange(this, undefined); @@ -133,6 +140,10 @@ export class FromEventObservable extends BaseObservable { } this.hasValue = true; } + + if (!didRunTransaction) { + getLogger()?.handleFromEventObservableTriggered(this, { oldValue, newValue, change: undefined, didChange, hadValue: this.hasValue }); + } }; protected override onLastObserverRemoved(): void { @@ -157,6 +168,21 @@ export class FromEventObservable extends BaseObservable { export namespace observableFromEvent { export const Observer = FromEventObservable; + + export function batchEventsGlobally(tx: ITransaction, fn: () => void): void { + let didSet = false; + if (FromEventObservable.globalTransaction === undefined) { + FromEventObservable.globalTransaction = tx; + didSet = true; + } + try { + fn(); + } finally { + if (didSet) { + FromEventObservable.globalTransaction = undefined; + } + } + } } export function observableSignalFromEvent( @@ -298,7 +324,7 @@ export function wasEventTriggeredRecently(event: Event, timeoutMs: number, * This makes sure the observable is being observed and keeps its cache alive. */ export function keepObserved(observable: IObservable): IDisposable { - const o = new KeepAliveObserver(false); + const o = new KeepAliveObserver(false, undefined); observable.addObserver(o); return toDisposable(() => { observable.removeObserver(o); @@ -308,29 +334,42 @@ export function keepObserved(observable: IObservable): IDisposable { /** * This converts the given observable into an autorun. */ -export function recomputeInitiallyAndOnChange(observable: IObservable): IDisposable { - const o = new KeepAliveObserver(true); +export function recomputeInitiallyAndOnChange(observable: IObservable, handleValue?: (value: T) => void): IDisposable { + const o = new KeepAliveObserver(true, handleValue); observable.addObserver(o); - observable.reportChanges(); + if (handleValue) { + handleValue(observable.get()); + } else { + observable.reportChanges(); + } return toDisposable(() => { observable.removeObserver(o); }); } -class KeepAliveObserver implements IObserver { - private counter = 0; +_setRecomputeInitiallyAndOnChange(recomputeInitiallyAndOnChange); - constructor(private readonly forceRecompute: boolean) { } +class KeepAliveObserver implements IObserver { + private _counter = 0; + + constructor( + private readonly _forceRecompute: boolean, + private readonly _handleValue: ((value: any) => void) | undefined, + ) { } beginUpdate(observable: IObservable): void { - this.counter++; + this._counter++; } endUpdate(observable: IObservable): void { - this.counter--; - if (this.counter === 0 && this.forceRecompute) { - observable.reportChanges(); + this._counter--; + if (this._counter === 0 && this._forceRecompute) { + if (this._handleValue) { + this._handleValue(observable.get()); + } else { + observable.reportChanges(); + } } } diff --git a/src/vs/base/common/platform.ts b/src/vs/base/common/platform.ts index c752f32550f..5c52adbfcd5 100644 --- a/src/vs/base/common/platform.ts +++ b/src/vs/base/common/platform.ts @@ -53,18 +53,13 @@ export interface INodeProcess { } declare const process: INodeProcess; -declare const global: unknown; -declare const self: unknown; -/** - * @deprecated use `globalThis` instead - */ -export const globals: any = (typeof self === 'object' ? self : typeof global === 'object' ? global : {}); +const $globalThis: any = globalThis; let nodeProcess: INodeProcess | undefined = undefined; -if (typeof globals.vscode !== 'undefined' && typeof globals.vscode.process !== 'undefined') { +if (typeof $globalThis.vscode !== 'undefined' && typeof $globalThis.vscode.process !== 'undefined') { // Native environment (sandboxed) - nodeProcess = globals.vscode.process; + nodeProcess = $globalThis.vscode.process; } else if (typeof process !== 'undefined') { // Native environment (non-sandboxed) nodeProcess = process; @@ -167,7 +162,8 @@ export const isLinuxSnap = _isLinuxSnap; export const isNative = _isNative; export const isElectron = _isElectron; export const isWeb = _isWeb; -export const isWebWorker = (_isWeb && typeof globals.importScripts === 'function'); +export const isWebWorker = (_isWeb && typeof $globalThis.importScripts === 'function'); +export const webWorkerOrigin = isWebWorker ? $globalThis.origin : undefined; export const isIOS = _isIOS; export const isMobile = _isMobile; /** @@ -226,7 +222,7 @@ export const platformLocale = _platformLocale; */ export const translationsConfigFile = _translationsConfigFile; -export const setTimeout0IsFaster = (typeof globals.postMessage === 'function' && !globals.importScripts); +export const setTimeout0IsFaster = (typeof $globalThis.postMessage === 'function' && !$globalThis.importScripts); /** * See https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#:~:text=than%204%2C%20then-,set%20timeout%20to%204,-. @@ -241,7 +237,8 @@ export const setTimeout0 = (() => { callback: () => void; } const pending: IQueueElement[] = []; - globals.addEventListener('message', (e: MessageEvent) => { + + $globalThis.addEventListener('message', (e: any) => { if (e.data && e.data.vscodeScheduleAsyncWork) { for (let i = 0, len = pending.length; i < len; i++) { const candidate = pending[i]; @@ -260,7 +257,7 @@ export const setTimeout0 = (() => { id: myId, callback: callback }); - globals.postMessage({ vscodeScheduleAsyncWork: myId }, '*'); + $globalThis.postMessage({ vscodeScheduleAsyncWork: myId }, '*'); }; } return (callback: () => void) => setTimeout(callback); diff --git a/src/vs/base/common/process.ts b/src/vs/base/common/process.ts index a50f849b5af..48fcd8acb43 100644 --- a/src/vs/base/common/process.ts +++ b/src/vs/base/common/process.ts @@ -3,14 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { globals, INodeProcess, isMacintosh, isWindows } from 'vs/base/common/platform'; +import { INodeProcess, isMacintosh, isWindows } from 'vs/base/common/platform'; let safeProcess: Omit & { arch: string | undefined }; declare const process: INodeProcess; // Native sandbox environment -if (typeof globals.vscode !== 'undefined' && typeof globals.vscode.process !== 'undefined') { - const sandboxProcess: INodeProcess = globals.vscode.process; +const vscodeGlobal = (globalThis as any).vscode; +if (typeof vscodeGlobal !== 'undefined' && typeof vscodeGlobal.process !== 'undefined') { + const sandboxProcess: INodeProcess = vscodeGlobal.process; safeProcess = { get platform() { return sandboxProcess.platform; }, get arch() { return sandboxProcess.arch; }, diff --git a/src/vs/base/parts/ipc/electron-sandbox/ipc.mp.ts b/src/vs/base/parts/ipc/electron-sandbox/ipc.mp.ts index cd6ea8fa3b2..bc2bfd17892 100644 --- a/src/vs/base/parts/ipc/electron-sandbox/ipc.mp.ts +++ b/src/vs/base/parts/ipc/electron-sandbox/ipc.mp.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { mainWindow } from 'vs/base/browser/window'; import { Event } from 'vs/base/common/event'; import { generateUuid } from 'vs/base/common/uuid'; import { ipcMessagePort, ipcRenderer } from 'vs/base/parts/sandbox/electron-sandbox/globals'; @@ -28,8 +29,8 @@ export async function acquirePort(requestChannel: string | undefined, responseCh // Wait until the main side has returned the `MessagePort` // We need to filter by the `nonce` to ensure we listen // to the right response. - const onMessageChannelResult = Event.fromDOMEventEmitter(window, 'message', (e: MessageEvent) => ({ nonce: e.data, port: e.ports[0], source: e.source })); - const { port } = await Event.toPromise(Event.once(Event.filter(onMessageChannelResult, e => e.nonce === nonce && e.source === window))); + const onMessageChannelResult = Event.fromDOMEventEmitter(mainWindow, 'message', (e: MessageEvent) => ({ nonce: e.data, port: e.ports[0], source: e.source })); + const { port } = await Event.toPromise(Event.once(Event.filter(onMessageChannelResult, e => e.nonce === nonce && e.source === mainWindow))); return port; } diff --git a/src/vs/base/parts/sandbox/electron-sandbox/globals.ts b/src/vs/base/parts/sandbox/electron-sandbox/globals.ts index 2fd7a08808a..44a54904e26 100644 --- a/src/vs/base/parts/sandbox/electron-sandbox/globals.ts +++ b/src/vs/base/parts/sandbox/electron-sandbox/globals.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { globals, INodeProcess, IProcessEnvironment } from 'vs/base/common/platform'; +import { INodeProcess, IProcessEnvironment } from 'vs/base/common/platform'; import { ISandboxConfiguration } from 'vs/base/parts/sandbox/common/sandboxTypes'; import { IpcRenderer, ProcessMemoryInfo, WebFrame } from 'vs/base/parts/sandbox/electron-sandbox/electronTypes'; @@ -115,11 +115,12 @@ export interface ISandboxContext { resolveConfiguration(): Promise; } -export const ipcRenderer: IpcRenderer = globals.vscode.ipcRenderer; -export const ipcMessagePort: IpcMessagePort = globals.vscode.ipcMessagePort; -export const webFrame: WebFrame = globals.vscode.webFrame; -export const process: ISandboxNodeProcess = globals.vscode.process; -export const context: ISandboxContext = globals.vscode.context; +const vscodeGlobal = (globalThis as any).vscode; +export const ipcRenderer: IpcRenderer = vscodeGlobal.ipcRenderer; +export const ipcMessagePort: IpcMessagePort = vscodeGlobal.ipcMessagePort; +export const webFrame: WebFrame = vscodeGlobal.webFrame; +export const process: ISandboxNodeProcess = vscodeGlobal.process; +export const context: ISandboxContext = vscodeGlobal.context; /** * A set of globals that are available in all windows that either diff --git a/src/vs/base/test/browser/dom.test.ts b/src/vs/base/test/browser/dom.test.ts index 41529f4868c..4af44d2f442 100644 --- a/src/vs/base/test/browser/dom.test.ts +++ b/src/vs/base/test/browser/dom.test.ts @@ -4,8 +4,9 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { $, asCssValueWithDefault, h, multibyteAwareBtoa, trackAttributes, copyAttributes } from 'vs/base/browser/dom'; -import { timeout } from 'vs/base/common/async'; +import { $, asCssValueWithDefault, h, multibyteAwareBtoa, trackAttributes, copyAttributes, disposableWindowInterval } from 'vs/base/browser/dom'; +import { mainWindow } from 'vs/base/browser/window'; +import { DeferredPromise, timeout } from 'vs/base/common/async'; import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler'; suite('dom', () => { @@ -336,4 +337,50 @@ suite('dom', () => { disposable.dispose(); }); }); + + suite('disposableWindowInterval', () => { + test('basics', async () => { + let count = 0; + const promise = new DeferredPromise(); + const interval = disposableWindowInterval(mainWindow, () => { + count++; + if (count === 3) { + promise.complete(undefined); + return true; + } else { + return false; + } + }, 0, 10); + + await promise.p; + assert.strictEqual(count, 3); + interval.dispose(); + }); + + test('iterations', async () => { + let count = 0; + const interval = disposableWindowInterval(mainWindow, () => { + count++; + + return false; + }, 0, 0); + + await timeout(5); + assert.strictEqual(count, 0); + interval.dispose(); + }); + + test('dispose', async () => { + let count = 0; + const interval = disposableWindowInterval(mainWindow, () => { + count++; + + return false; + }, 0, 10); + + interval.dispose(); + await timeout(5); + assert.strictEqual(count, 0); + }); + }); }); diff --git a/src/vs/base/test/browser/formattedTextRenderer.test.ts b/src/vs/base/test/browser/formattedTextRenderer.test.ts index f12ca3222ee..f5bcc1b2d5d 100644 --- a/src/vs/base/test/browser/formattedTextRenderer.test.ts +++ b/src/vs/base/test/browser/formattedTextRenderer.test.ts @@ -77,7 +77,7 @@ suite('FormattedTextRenderer', () => { }); assert.strictEqual(result.innerHTML, 'action'); - const event: MouseEvent = document.createEvent('MouseEvent'); + const event: MouseEvent = document.createEvent('MouseEvent'); event.initEvent('click', true, true); result.firstChild!.dispatchEvent(event); assert.strictEqual(callbackCalled, true); @@ -96,7 +96,7 @@ suite('FormattedTextRenderer', () => { }); assert.strictEqual(result.innerHTML, 'action'); - const event: MouseEvent = document.createEvent('MouseEvent'); + const event: MouseEvent = document.createEvent('MouseEvent'); event.initEvent('click', true, true); result.firstChild!.firstChild!.firstChild!.dispatchEvent(event); assert.strictEqual(callbackCalled, true); @@ -116,7 +116,7 @@ suite('FormattedTextRenderer', () => { }); assert.strictEqual(result.innerHTML, 'action'); - const event: MouseEvent = document.createEvent('MouseEvent'); + const event: MouseEvent = document.createEvent('MouseEvent'); event.initEvent('click', true, true); result.firstChild!.firstChild!.firstChild!.firstChild!.dispatchEvent(event); assert.strictEqual(callbackCalled, true); diff --git a/src/vs/base/test/browser/progressBar.test.ts b/src/vs/base/test/browser/progressBar.test.ts index eb9790cd6ce..9620ae31948 100644 --- a/src/vs/base/test/browser/progressBar.test.ts +++ b/src/vs/base/test/browser/progressBar.test.ts @@ -5,6 +5,7 @@ import * as assert from 'assert'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; +import { mainWindow } from 'vs/base/browser/window'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; suite('ProgressBar', () => { @@ -12,11 +13,11 @@ suite('ProgressBar', () => { setup(() => { fixture = document.createElement('div'); - document.body.appendChild(fixture); + mainWindow.document.body.appendChild(fixture); }); teardown(() => { - document.body.removeChild(fixture); + mainWindow.document.body.removeChild(fixture); }); test('Progress Bar', function () { diff --git a/src/vs/base/test/common/async.test.ts b/src/vs/base/test/common/async.test.ts index 832275813e5..bd6ec6a4bcc 100644 --- a/src/vs/base/test/common/async.test.ts +++ b/src/vs/base/test/common/async.test.ts @@ -867,52 +867,6 @@ suite('Async', () => { }); }); - suite('disposableInterval', () => { - test('basics', async () => { - let count = 0; - const promise = new async.DeferredPromise(); - const interval = async.disposableInterval(() => { - count++; - if (count === 3) { - promise.complete(undefined); - return true; - } else { - return false; - } - }, 0, 10); - - await promise.p; - assert.strictEqual(count, 3); - interval.dispose(); - }); - - test('iterations', async () => { - let count = 0; - const interval = async.disposableInterval(() => { - count++; - - return false; - }, 0, 0); - - await async.timeout(5); - assert.strictEqual(count, 0); - interval.dispose(); - }); - - test('dispose', async () => { - let count = 0; - const interval = async.disposableInterval(() => { - count++; - - return false; - }, 0, 10); - - interval.dispose(); - await async.timeout(5); - assert.strictEqual(count, 0); - }); - }); - test('raceCancellation', async () => { const cts = store.add(new CancellationTokenSource()); const ctsTimeout = store.add(new CancellationTokenSource()); diff --git a/src/vs/base/test/common/labels.test.ts b/src/vs/base/test/common/labels.test.ts index 8928b09c72d..6c9d3eb0f11 100644 --- a/src/vs/base/test/common/labels.test.ts +++ b/src/vs/base/test/common/labels.test.ts @@ -162,6 +162,7 @@ suite('Labels', () => { test('getPathLabel', () => { const winFileUri = URI.file('c:/some/folder/file.txt'); const nixFileUri = URI.file('/some/folder/file.txt'); + const nixBadFileUri = URI.revive({ scheme: 'vscode', authority: 'file', path: '//some/folder/file.txt' }); const uncFileUri = URI.file('c:/some/folder/file.txt').with({ authority: 'auth' }); const remoteFileUri = URI.file('/some/folder/file.txt').with({ scheme: 'vscode-test', authority: 'auth' }); @@ -190,6 +191,7 @@ suite('Labels', () => { assert.strictEqual(labels.getPathLabel(nixFileUri, { os: OperatingSystem.Windows, tildify: { userHome: nixUserHome } }), '\\some\\folder\\file.txt'); assert.strictEqual(labels.getPathLabel(nixFileUri, { os: OperatingSystem.Macintosh, tildify: { userHome: nixUserHome } }), '~/folder/file.txt'); + assert.strictEqual(labels.getPathLabel(nixBadFileUri, { os: OperatingSystem.Macintosh, tildify: { userHome: nixUserHome } }), '/some/folder/file.txt'); assert.strictEqual(labels.getPathLabel(nixFileUri, { os: OperatingSystem.Linux, tildify: { userHome: nixUserHome } }), '~/folder/file.txt'); assert.strictEqual(labels.getPathLabel(nixFileUri, { os: OperatingSystem.Windows, tildify: { userHome: remoteUserHome } }), '\\some\\folder\\file.txt'); diff --git a/src/vs/code/browser/workbench/callback.html b/src/vs/code/browser/workbench/callback.html index cd12754b170..4f54d4d1c97 100644 --- a/src/vs/code/browser/workbench/callback.html +++ b/src/vs/code/browser/workbench/callback.html @@ -61,7 +61,7 @@ uri.fragment = fragment; } - window.localStorage.setItem(`vscode-web.url-callbacks[${id}]`, JSON.stringify(uri)); + localStorage.setItem(`vscode-web.url-callbacks[${id}]`, JSON.stringify(uri)); diff --git a/src/vs/code/browser/workbench/workbench.html b/src/vs/code/browser/workbench/workbench.html index addc28f0c4d..700231f0357 100644 --- a/src/vs/code/browser/workbench/workbench.html +++ b/src/vs/code/browser/workbench/workbench.html @@ -45,7 +45,7 @@ const nlsConfig = {}; // Normalize locale to lowercase because translationServiceUrl is case-sensitive. // ref: https://github.com/microsoft/vscode/issues/187795 - const locale = window.localStorage.getItem('vscode.nls.locale') || navigator.language.toLowerCase(); + const locale = localStorage.getItem('vscode.nls.locale') || navigator.language.toLowerCase(); if (!locale.startsWith('en')) { nlsConfig['vs/nls'] = { availableLanguages: { diff --git a/src/vs/code/browser/workbench/workbench.ts b/src/vs/code/browser/workbench/workbench.ts index 962e2f4c314..dee15a5f132 100644 --- a/src/vs/code/browser/workbench/workbench.ts +++ b/src/vs/code/browser/workbench/workbench.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { isStandalone } from 'vs/base/browser/browser'; +import { mainWindow } from 'vs/base/browser/window'; import { VSBuffer, decodeBase64, encodeBase64 } from 'vs/base/common/buffer'; import { Emitter } from 'vs/base/common/event'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; @@ -56,18 +57,18 @@ class ServerKeyedAESCrypto implements ISecretStorageCrypto { async seal(data: string): Promise { // Get a new key and IV on every change, to avoid the risk of reusing the same key and IV pair with AES-GCM // (see also: https://developer.mozilla.org/en-US/docs/Web/API/AesGcmParams#properties) - const iv = window.crypto.getRandomValues(new Uint8Array(AESConstants.IV_LENGTH)); + const iv = mainWindow.crypto.getRandomValues(new Uint8Array(AESConstants.IV_LENGTH)); // crypto.getRandomValues isn't a good-enough PRNG to generate crypto keys, so we need to use crypto.subtle.generateKey and export the key instead - const clientKeyObj = await window.crypto.subtle.generateKey( + const clientKeyObj = await mainWindow.crypto.subtle.generateKey( { name: AESConstants.ALGORITHM as const, length: AESConstants.KEY_LENGTH as const }, true, ['encrypt', 'decrypt'] ); - const clientKey = new Uint8Array(await window.crypto.subtle.exportKey('raw', clientKeyObj)); + const clientKey = new Uint8Array(await mainWindow.crypto.subtle.exportKey('raw', clientKeyObj)); const key = await this.getKey(clientKey); const dataUint8Array = new TextEncoder().encode(data); - const cipherText: ArrayBuffer = await window.crypto.subtle.encrypt( + const cipherText: ArrayBuffer = await mainWindow.crypto.subtle.encrypt( { name: AESConstants.ALGORITHM as const, iv }, key, dataUint8Array @@ -95,7 +96,7 @@ class ServerKeyedAESCrypto implements ISecretStorageCrypto { // Do the decryption and parse the result as JSON const key = await this.getKey(clientKey.buffer); - const decrypted = await window.crypto.subtle.decrypt( + const decrypted = await mainWindow.crypto.subtle.decrypt( { name: AESConstants.ALGORITHM as const, iv: iv.buffer }, key, cipherText.buffer @@ -120,7 +121,7 @@ class ServerKeyedAESCrypto implements ISecretStorageCrypto { keyData[i] = clientKey[i]! ^ serverKey[i]!; } - return window.crypto.subtle.importKey( + return mainWindow.crypto.subtle.importKey( 'raw', keyData, { @@ -179,7 +180,7 @@ export class LocalStorageSecretStorageProvider implements ISecretStorageProvider private async load(): Promise> { const record = this.loadAuthSessionFromElement(); // Get the secrets from localStorage - const encrypted = window.localStorage.getItem(this._storageKey); + const encrypted = localStorage.getItem(this._storageKey); if (encrypted) { try { const decrypted = JSON.parse(await this.crypto.unseal(encrypted)); @@ -187,7 +188,7 @@ export class LocalStorageSecretStorageProvider implements ISecretStorageProvider } catch (err) { // TODO: send telemetry console.error('Failed to decrypt secrets from localStorage', err); - window.localStorage.removeItem(this._storageKey); + localStorage.removeItem(this._storageKey); } } @@ -196,7 +197,7 @@ export class LocalStorageSecretStorageProvider implements ISecretStorageProvider private loadAuthSessionFromElement(): Record { let authSessionInfo: (AuthenticationSessionInfo & { scopes: string[][] }) | undefined; - const authSessionElement = document.getElementById('vscode-workbench-auth-session'); + const authSessionElement = mainWindow.document.getElementById('vscode-workbench-auth-session'); const authSessionElementAttribute = authSessionElement ? authSessionElement.getAttribute('data-settings') : undefined; if (authSessionElementAttribute) { try { @@ -249,7 +250,7 @@ export class LocalStorageSecretStorageProvider implements ISecretStorageProvider private async save(): Promise { try { const encrypted = await this.crypto.seal(JSON.stringify(await this._secretsPromise)); - window.localStorage.setItem(this._storageKey, encrypted); + localStorage.setItem(this._storageKey, encrypted); } catch (err) { console.error(err); } @@ -298,13 +299,13 @@ class LocalStorageURLCallbackProvider extends Disposable implements IURLCallback // https://github.com/microsoft/vscode/blob/159479eb5ae451a66b5dac3c12d564f32f454796/extensions/github-authentication/src/githubServer.ts#L50-L50 if (!(options.authority === 'vscode.github-authentication' && options.path === '/dummy')) { const key = `vscode-web.url-callbacks[${id}]`; - window.localStorage.removeItem(key); + localStorage.removeItem(key); this.pendingCallbacks.add(id); this.startListening(); } - return URI.parse(window.location.href).with({ path: this._callbackRoute, query: queryParams.join('&') }); + return URI.parse(mainWindow.location.href).with({ path: this._callbackRoute, query: queryParams.join('&') }); } private startListening(): void { @@ -313,8 +314,8 @@ class LocalStorageURLCallbackProvider extends Disposable implements IURLCallback } const fn = () => this.onDidChangeLocalStorage(); - window.addEventListener('storage', fn); - this.onDidChangeLocalStorageDisposable = { dispose: () => window.removeEventListener('storage', fn) }; + mainWindow.addEventListener('storage', fn); + this.onDidChangeLocalStorageDisposable = { dispose: () => mainWindow.removeEventListener('storage', fn) }; } private stopListening(): void { @@ -342,7 +343,7 @@ class LocalStorageURLCallbackProvider extends Disposable implements IURLCallback for (const id of this.pendingCallbacks) { const key = `vscode-web.url-callbacks[${id}]`; - const result = window.localStorage.getItem(key); + const result = localStorage.getItem(key); if (result !== null) { try { @@ -353,7 +354,7 @@ class LocalStorageURLCallbackProvider extends Disposable implements IURLCallback pendingCallbacks = pendingCallbacks ?? new Set(this.pendingCallbacks); pendingCallbacks.delete(id); - window.localStorage.removeItem(key); + localStorage.removeItem(key); } } @@ -459,14 +460,14 @@ class WorkspaceProvider implements IWorkspaceProvider { const targetHref = this.createTargetUrl(workspace, options); if (targetHref) { if (options?.reuse) { - window.location.href = targetHref; + mainWindow.location.href = targetHref; return true; } else { let result; if (isStandalone()) { - result = window.open(targetHref, '_blank', 'toolbar=no'); // ensures to open another 'standalone' window! + result = mainWindow.open(targetHref, '_blank', 'toolbar=no'); // ensures to open another 'standalone' window! } else { - result = window.open(targetHref); + result = mainWindow.open(targetHref); } return !!result; @@ -564,7 +565,7 @@ function readCookie(name: string): string | undefined { (function () { // Find config by checking for DOM - const configElement = document.getElementById('vscode-workbench-web-configuration'); + const configElement = mainWindow.document.getElementById('vscode-workbench-web-configuration'); const configElementAttribute = configElement ? configElement.getAttribute('data-settings') : undefined; if (!configElement || !configElementAttribute) { throw new Error('Missing web configuration element'); @@ -575,7 +576,7 @@ function readCookie(name: string): string | undefined { ? new ServerKeyedAESCrypto(secretStorageKeyPath) : new TransparentCrypto(); // Create workbench - create(document.body, { + create(mainWindow.document.body, { ...config, windowIndicator: config.windowIndicator ?? { label: '$(remote)', tooltip: `${product.nameShort} Web` }, settingsSyncOptions: config.settingsSyncOptions ? { enabled: config.settingsSyncOptions.enabled, } : undefined, diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index 192447bb600..42e165b8517 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -84,7 +84,7 @@ import { NativeURLService } from 'vs/platform/url/common/urlService'; import { ElectronURLListener } from 'vs/platform/url/electron-main/electronUrlListener'; import { IWebviewManagerService } from 'vs/platform/webview/common/webviewManagerService'; import { WebviewMainService } from 'vs/platform/webview/electron-main/webviewMainService'; -import { IWindowOpenable } from 'vs/platform/window/common/window'; +import { isFolderToOpen, isWorkspaceToOpen, IWindowOpenable } from 'vs/platform/window/common/window'; import { IWindowsMainService, OpenContext } from 'vs/platform/windows/electron-main/windows'; import { ICodeWindow } from 'vs/platform/window/electron-main/window'; import { WindowsMainService } from 'vs/platform/windows/electron-main/windowsMainService'; @@ -104,7 +104,7 @@ import { IExtensionsScannerService } from 'vs/platform/extensionManagement/commo import { ExtensionsScannerService } from 'vs/platform/extensionManagement/node/extensionsScannerService'; import { UserDataProfilesHandler } from 'vs/platform/userDataProfile/electron-main/userDataProfilesHandler'; import { ProfileStorageChangesListenerChannel } from 'vs/platform/userDataProfile/electron-main/userDataProfileStorageIpc'; -import { Promises, RunOnceScheduler, runWhenIdle } from 'vs/base/common/async'; +import { Promises, RunOnceScheduler, runWhenGlobalIdle } from 'vs/base/common/async'; import { resolveMachineId, resolveSqmId } from 'vs/platform/telemetry/electron-main/telemetryUtils'; import { ExtensionsProfileScannerService } from 'vs/platform/extensionManagement/node/extensionsProfileScannerService'; import { LoggerChannel } from 'vs/platform/log/electron-main/logIpc'; @@ -624,7 +624,7 @@ export class CodeApplication extends Disposable { // Set lifecycle phase to `Eventually` after a short delay and when idle (min 2.5sec, max 5sec) const eventuallyPhaseScheduler = this._register(new RunOnceScheduler(() => { - this._register(runWhenIdle(() => this.lifecycleMainService.phase = LifecycleMainPhase.Eventually, 2500)); + this._register(runWhenGlobalIdle(() => this.lifecycleMainService.phase = LifecycleMainPhase.Eventually, 2500)); }, 2500)); eventuallyPhaseScheduler.schedule(); } @@ -727,17 +727,17 @@ export class CodeApplication extends Disposable { continue; // invalid } - if (await this.shouldBlockURI(protocolUrl.uri, windowsMainService, dialogMainService)) { - this.logService.trace('app#resolveInitialProtocolUrls() protocol url was blocked:', protocolUrl.uri.toString(true)); - - continue; // blocked - } - const windowOpenable = this.getWindowOpenableFromProtocolUrl(protocolUrl.uri); if (windowOpenable) { - this.logService.trace('app#resolveInitialProtocolUrls() protocol url will be handled as window to open:', protocolUrl.uri.toString(true), windowOpenable); + if (await this.shouldBlockOpenable(windowOpenable, windowsMainService, dialogMainService)) { + this.logService.trace('app#resolveInitialProtocolUrls() protocol url was blocked:', protocolUrl.uri.toString(true)); - openables.push(windowOpenable); // handled as window to open + continue; // blocked + } else { + this.logService.trace('app#resolveInitialProtocolUrls() protocol url will be handled as window to open:', protocolUrl.uri.toString(true), windowOpenable); + + openables.push(windowOpenable); // handled as window to open + } } else { this.logService.trace('app#resolveInitialProtocolUrls() protocol url will be passed to active window for handling:', protocolUrl.uri.toString(true)); @@ -748,8 +748,21 @@ export class CodeApplication extends Disposable { return { urls, openables }; } - private async shouldBlockURI(uri: URI, windowsMainService: IWindowsMainService, dialogMainService: IDialogMainService): Promise { - if (uri.authority !== Schemas.file && uri.authority !== Schemas.vscodeRemote) { + private async shouldBlockOpenable(openable: IWindowOpenable, windowsMainService: IWindowsMainService, dialogMainService: IDialogMainService): Promise { + let openableUri: URI; + let message: string; + if (isWorkspaceToOpen(openable)) { + openableUri = openable.workspaceUri; + message = localize('confirmOpenMessageWorkspace', "An external application wants to open '{0}' in {1}. Do you want to open this workspace file?", openableUri.scheme === Schemas.file ? getPathLabel(openableUri, { os: OS, tildify: this.environmentMainService }) : openableUri.toString(true), this.productService.nameShort); + } else if (isFolderToOpen(openable)) { + openableUri = openable.folderUri; + message = localize('confirmOpenMessageFolder', "An external application wants to open '{0}' in {1}. Do you want to open this folder?", openableUri.scheme === Schemas.file ? getPathLabel(openableUri, { os: OS, tildify: this.environmentMainService }) : openableUri.toString(true), this.productService.nameShort); + } else { + openableUri = openable.fileUri; + message = localize('confirmOpenMessageFileOrFolder', "An external application wants to open '{0}' in {1}. Do you want to open this file or folder?", openableUri.scheme === Schemas.file ? getPathLabel(openableUri, { os: OS, tildify: this.environmentMainService }) : openableUri.toString(true), this.productService.nameShort); + } + + if (openableUri.scheme !== Schemas.file && openableUri.scheme !== Schemas.vscodeRemote) { // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // @@ -765,7 +778,7 @@ export class CodeApplication extends Disposable { return false; } - const askForConfirmation = this.configurationService.getValue(CodeApplication.SECURITY_PROTOCOL_HANDLING_CONFIRMATION_SETTING_KEY[uri.authority]); + const askForConfirmation = this.configurationService.getValue(CodeApplication.SECURITY_PROTOCOL_HANDLING_CONFIRMATION_SETTING_KEY[openableUri.scheme]); if (askForConfirmation === false) { return false; // not blocked via settings } @@ -776,9 +789,9 @@ export class CodeApplication extends Disposable { localize({ key: 'open', comment: ['&& denotes a mnemonic'] }, "&&Yes"), localize({ key: 'cancel', comment: ['&& denotes a mnemonic'] }, "&&No") ], - message: localize('confirmOpenMessage', "An external application wants to open '{0}' in {1}. Do you want to open this file or folder?", uri.authority === Schemas.file ? getPathLabel(uri, { os: OS, tildify: this.environmentMainService }) : uri.toString(true), this.productService.nameShort), + message, detail: localize('confirmOpenDetail', "If you did not initiate this request, it may represent an attempted attack on your system. Unless you took an explicit action to initiate this request, you should press 'No'"), - checkboxLabel: uri.authority === Schemas.file ? localize('doNotAskAgainLocal', "Allow opening local paths without asking") : localize('doNotAskAgainRemote', "Allow opening remote paths without asking"), + checkboxLabel: openableUri.scheme === Schemas.file ? localize('doNotAskAgainLocal', "Allow opening local paths without asking") : localize('doNotAskAgainRemote', "Allow opening remote paths without asking"), cancelId: 1 }); @@ -791,7 +804,7 @@ export class CodeApplication extends Disposable { // update settings from within a window. But we do not know if a window // is about to open or can already handle the request, so we have to send // to any current window and any newly opening window. - const request = { channel: 'vscode:disablePromptForProtocolHandling', args: uri.authority === Schemas.file ? 'local' : 'remote' }; + const request = { channel: 'vscode:disablePromptForProtocolHandling', args: openableUri.scheme === Schemas.file ? 'local' : 'remote' }; windowsMainService.sendToFocused(request.channel, request.args); windowsMainService.sendToOpeningWindow(request.channel, request.args); } @@ -866,13 +879,6 @@ export class CodeApplication extends Disposable { }); } - // If URI should be blocked, behave as if it's handled - if (await this.shouldBlockURI(uri, windowsMainService, dialogMainService)) { - this.logService.trace('app#handleProtocolUrl() protocol url was blocked:', uri.toString(true)); - - return true; - } - let shouldOpenInNewWindow = false; // We should handle the URI in a new window if the URL contains `windowId=_blank` @@ -907,20 +913,26 @@ export class CodeApplication extends Disposable { // Check if the protocol URL is a window openable to open... const windowOpenableFromProtocolUrl = this.getWindowOpenableFromProtocolUrl(uri); if (windowOpenableFromProtocolUrl) { - this.logService.trace('app#handleProtocolUrl() opening protocol url as window:', windowOpenableFromProtocolUrl, uri.toString(true)); + if (await this.shouldBlockOpenable(windowOpenableFromProtocolUrl, windowsMainService, dialogMainService)) { + this.logService.trace('app#handleProtocolUrl() protocol url was blocked:', uri.toString(true)); - const window = firstOrDefault(await windowsMainService.open({ - context: OpenContext.API, - cli: { ...this.environmentMainService.args }, - urisToOpen: [windowOpenableFromProtocolUrl], - forceNewWindow: shouldOpenInNewWindow, - gotoLineMode: true - // remoteAuthority: will be determined based on windowOpenableFromProtocolUrl - })); + return true; // If openable should be blocked, behave as if it's handled + } else { + this.logService.trace('app#handleProtocolUrl() opening protocol url as window:', windowOpenableFromProtocolUrl, uri.toString(true)); - window?.focus(); // this should help ensuring that the right window gets focus when multiple are opened + const window = firstOrDefault(await windowsMainService.open({ + context: OpenContext.API, + cli: { ...this.environmentMainService.args }, + urisToOpen: [windowOpenableFromProtocolUrl], + forceNewWindow: shouldOpenInNewWindow, + gotoLineMode: true + // remoteAuthority: will be determined based on windowOpenableFromProtocolUrl + })); - return true; + window?.focus(); // this should help ensuring that the right window gets focus when multiple are opened + + return true; + } } // ...or if we should open in a new window and then handle it within that window diff --git a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts index 3d7ef0fa47c..2cbe4b0f8d2 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterMain.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterMain.ts @@ -17,20 +17,21 @@ import { ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/ import { registerMainProcessRemoteService } from 'vs/platform/ipc/electron-sandbox/services'; import { IIssueMainService, IssueReporterWindowConfiguration } from 'vs/platform/issue/common/issue'; import { INativeHostService } from 'vs/platform/native/common/native'; -import { NativeHostService } from 'vs/platform/native/electron-sandbox/nativeHostService'; +import { NativeHostService } from 'vs/platform/native/common/nativeHostService'; import { IssueReporter } from './issueReporterService'; +import { mainWindow } from 'vs/base/browser/window'; export function startup(configuration: IssueReporterWindowConfiguration) { const platformClass = isWindows ? 'windows' : isLinux ? 'linux' : 'mac'; - document.body.classList.add(platformClass); // used by our fonts + mainWindow.document.body.classList.add(platformClass); // used by our fonts - safeInnerHtml(document.body, BaseHtml()); + safeInnerHtml(mainWindow.document.body, BaseHtml()); const instantiationService = initServices(configuration.windowId); const issueReporter = instantiationService.createInstance(IssueReporter, configuration); issueReporter.render(); - document.body.style.display = 'block'; + mainWindow.document.body.style.display = 'block'; issueReporter.setInitialFocus(); } diff --git a/src/vs/code/electron-sandbox/issue/issueReporterModel.ts b/src/vs/code/electron-sandbox/issue/issueReporterModel.ts index 794d371126a..74f993903e1 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterModel.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterModel.ts @@ -123,37 +123,38 @@ ${this.getInfos()} private getInfos(): string { let info = ''; - if (this._data.issueType === IssueType.Bug || this._data.issueType === IssueType.PerformanceIssue) { - if (!this._data.fileOnMarketplace && this._data.includeExtensionData && this._data.extensionData) { - info += this.getExtensionData(); - } + if (this._data.fileOnMarketplace) { + return info; } - if (this._data.issueType === IssueType.Bug || this._data.issueType === IssueType.PerformanceIssue) { - if (!this._data.fileOnMarketplace && this._data.includeSystemInfo && this._data.systemInfo) { + const isBugOrPerformanceIssue = this._data.issueType === IssueType.Bug || this._data.issueType === IssueType.PerformanceIssue; + + if (isBugOrPerformanceIssue) { + if (this._data.includeExtensionData && this._data.extensionData) { + info += this.getExtensionData(); + } + + if (this._data.includeSystemInfo && this._data.systemInfo) { info += this.generateSystemInfoMd(); } } if (this._data.issueType === IssueType.PerformanceIssue) { - - if (!this._data.fileOnMarketplace && this._data.includeProcessInfo) { + if (this._data.includeProcessInfo) { info += this.generateProcessInfoMd(); } - if (!this._data.fileOnMarketplace && this._data.includeWorkspaceInfo) { + if (this._data.includeWorkspaceInfo) { info += this.generateWorkspaceInfoMd(); } } - if (this._data.issueType === IssueType.Bug || this._data.issueType === IssueType.PerformanceIssue) { - if (!this._data.fileOnMarketplace && !this._data.fileOnExtension && this._data.includeExtensions) { + if (isBugOrPerformanceIssue) { + if (!this._data.fileOnExtension && this._data.includeExtensions) { info += this.generateExtensionsMd(); } - } - if (this._data.issueType === IssueType.Bug || this._data.issueType === IssueType.PerformanceIssue) { - if (!this._data.fileOnMarketplace && this._data.includeExperiments && this._data.experimentInfo) { + if (this._data.includeExperiments && this._data.experimentInfo) { info += this.generateExperimentsInfoMd(); } } diff --git a/src/vs/code/electron-sandbox/issue/issueReporterService.ts b/src/vs/code/electron-sandbox/issue/issueReporterService.ts index afb7a654c09..d7f98f666b9 100644 --- a/src/vs/code/electron-sandbox/issue/issueReporterService.ts +++ b/src/vs/code/electron-sandbox/issue/issueReporterService.ts @@ -5,6 +5,7 @@ import { $, createStyleSheet, reset, windowOpenNoOpener } from 'vs/base/browser/dom'; import { Button, unthemedButtonStyles } from 'vs/base/browser/ui/button/button'; import { renderIcon } from 'vs/base/browser/ui/iconLabel/iconLabels'; +import { mainWindow } from 'vs/base/browser/window'; import { Delayer, RunOnceScheduler } from 'vs/base/common/async'; import { Codicon } from 'vs/base/common/codicons'; import { groupBy } from 'vs/base/common/collections'; @@ -107,7 +108,7 @@ export class IssueReporter extends Disposable { }); } - if (window.document.documentElement.lang !== 'en') { + if (mainWindow.document.documentElement.lang !== 'en') { show(this.getElementById('english')); } @@ -141,10 +142,10 @@ export class IssueReporter extends Disposable { setInitialFocus() { const { fileOnExtension } = this.issueReporterModel.getData(); if (fileOnExtension) { - const issueTitle = document.getElementById('issue-title'); + const issueTitle = mainWindow.document.getElementById('issue-title'); issueTitle?.focus(); } else { - const issueType = document.getElementById('issue-type'); + const issueType = mainWindow.document.getElementById('issue-type'); issueType?.focus(); } } @@ -221,8 +222,8 @@ export class IssueReporter extends Disposable { } styleTag.textContent = content.join('\n'); - document.head.appendChild(styleTag); - document.body.style.color = styles.color || ''; + mainWindow.document.head.appendChild(styleTag); + mainWindow.document.body.style.color = styles.color || ''; } private handleExtensionData(extensions: IssueReporterExtensionData[]) { @@ -277,6 +278,16 @@ export class IssueReporter extends Disposable { } } + private async getReporterStatus(extension: IssueReporterExtensionData): Promise { + try { + const data = await this.issueMainService.$getReporterStatus(extension.id, extension.name); + return data; + } catch (e) { + console.error(e); + return [false, false]; + } + } + private setEventHandlers(): void { this.addEventListener('issue-type', 'change', (event: Event) => { const issueType = parseInt((event.target).value); @@ -298,7 +309,7 @@ export class IssueReporter extends Disposable { }); }); - const showInfoElements = document.getElementsByClassName('showInfo'); + const showInfoElements = mainWindow.document.getElementsByClassName('showInfo'); for (let i = 0; i < showInfoElements.length; i++) { const showInfo = showInfoElements.item(i)!; (showInfo as HTMLAnchorElement).addEventListener('click', (e: MouseEvent) => { @@ -396,7 +407,7 @@ export class IssueReporter extends Disposable { } }); - document.onkeydown = async (e: KeyboardEvent) => { + mainWindow.document.onkeydown = async (e: KeyboardEvent) => { const cmdOrCtrlKey = isMacintosh ? e.metaKey : e.ctrlKey; // Cmd/Ctrl+Enter previews issue and closes window if (cmdOrCtrlKey && e.keyCode === 13) { @@ -569,7 +580,7 @@ export class IssueReporter extends Disposable { const query = `is:issue+repo:${repo}+${title}`; const similarIssues = this.getElementById('similar-issues')!; - window.fetch(`https://api.github.com/search/issues?q=${query}`).then((response) => { + fetch(`https://api.github.com/search/issues?q=${query}`).then((response) => { response.json().then(result => { similarIssues.innerText = ''; if (result && result.items) { @@ -612,7 +623,7 @@ export class IssueReporter extends Disposable { }) }; - window.fetch(url, init).then((response) => { + fetch(url, init).then((response) => { response.json().then(result => { this.clearSearchResults(); @@ -738,12 +749,12 @@ export class IssueReporter extends Disposable { // Depending on Issue Type, we render different blocks and text const { issueType, fileOnExtension, fileOnMarketplace, selectedExtension } = this.issueReporterModel.getData(); const blockContainer = this.getElementById('block-container'); - const systemBlock = document.querySelector('.block-system'); - const processBlock = document.querySelector('.block-process'); - const workspaceBlock = document.querySelector('.block-workspace'); - const extensionsBlock = document.querySelector('.block-extensions'); - const experimentsBlock = document.querySelector('.block-experiments'); - const extensionDataBlock = document.querySelector('.block-extension-data'); + const systemBlock = mainWindow.document.querySelector('.block-system'); + const processBlock = mainWindow.document.querySelector('.block-process'); + const workspaceBlock = mainWindow.document.querySelector('.block-workspace'); + const extensionsBlock = mainWindow.document.querySelector('.block-extensions'); + const experimentsBlock = mainWindow.document.querySelector('.block-experiments'); + const extensionDataBlock = mainWindow.document.querySelector('.block-extension-data'); const problemSource = this.getElementById('problem-source')!; const descriptionTitle = this.getElementById('issue-description-label')!; @@ -868,7 +879,7 @@ export class IssueReporter extends Disposable { }) }; - const response = await window.fetch(url, init); + const response = await fetch(url, init); if (!response.ok) { return false; } @@ -892,7 +903,7 @@ export class IssueReporter extends Disposable { if (!this.validateInputs()) { // If inputs are invalid, set focus to the first one and add listeners on them // to detect further changes - const invalidInput = document.getElementsByClassName('invalid-input'); + const invalidInput = mainWindow.document.getElementsByClassName('invalid-input'); if (invalidInput.length) { (invalidInput[0]).focus(); } @@ -1001,7 +1012,7 @@ export class IssueReporter extends Disposable { } private updateSystemInfo(state: IssueReporterModelData) { - const target = document.querySelector('.block-system .block-info'); + const target = mainWindow.document.querySelector('.block-system .block-info'); if (target) { const systemInfo = state.systemInfo!; @@ -1131,6 +1142,15 @@ export class IssueReporter extends Disposable { const matches = extensions.filter(extension => extension.id === selectedExtensionId); if (matches.length) { this.issueReporterModel.update({ selectedExtension: matches[0] }); + + // if extension does not have provider/handles, will check for either. If extension is already active, IPC will return [false, false] and will proceed as normal. + if (!matches[0].hasIssueDataProviders && !matches[0].hasIssueUriRequestHandler) { + const toActivate = await this.getReporterStatus(matches[0]); + matches[0].hasIssueDataProviders = toActivate[0]; + matches[0].hasIssueUriRequestHandler = toActivate[1]; + this.renderBlocks(); + } + if (matches[0].hasIssueUriRequestHandler) { this.updateIssueReporterUri(matches[0]); } else if (matches[0].hasIssueDataProviders) { @@ -1142,7 +1162,7 @@ export class IssueReporter extends Disposable { (descriptionTextArea as HTMLTextAreaElement).value = fullTextArea; this.issueReporterModel.update({ issueDescription: fullTextArea }); } - const extensionDataBlock = document.querySelector('.block-extension-data')!; + const extensionDataBlock = mainWindow.document.querySelector('.block-extension-data')!; show(extensionDataBlock); // Start loading for extension data. @@ -1201,12 +1221,14 @@ export class IssueReporter extends Disposable { const extensionDataCaption = this.getElementById('extension-id')!; hide(extensionDataCaption); - const extensionDataCaption2 = Array.from(document.querySelectorAll('.ext-parens')); + const extensionDataCaption2 = Array.from(mainWindow.document.querySelectorAll('.ext-parens')); extensionDataCaption2.forEach(extensionDataCaption2 => hide(extensionDataCaption2)); const showLoading = this.getElementById('ext-loading')!; show(showLoading); showLoading.append(element); + + this.renderBlocks(); } private removeLoading(element: HTMLElement) { @@ -1215,7 +1237,7 @@ export class IssueReporter extends Disposable { const extensionDataCaption = this.getElementById('extension-id')!; show(extensionDataCaption); - const extensionDataCaption2 = Array.from(document.querySelectorAll('.ext-parens')); + const extensionDataCaption2 = Array.from(mainWindow.document.querySelectorAll('.ext-parens')); extensionDataCaption2.forEach(extensionDataCaption2 => show(extensionDataCaption2)); const hideLoading = this.getElementById('ext-loading')!; @@ -1246,18 +1268,18 @@ export class IssueReporter extends Disposable { } private updateProcessInfo(state: IssueReporterModelData) { - const target = document.querySelector('.block-process .block-info') as HTMLElement; + const target = mainWindow.document.querySelector('.block-process .block-info') as HTMLElement; if (target) { reset(target, $('code', undefined, state.processInfo ?? '')); } } private updateWorkspaceInfo(state: IssueReporterModelData) { - document.querySelector('.block-workspace .block-info code')!.textContent = '\n' + state.workspaceInfo; + mainWindow.document.querySelector('.block-workspace .block-info code')!.textContent = '\n' + state.workspaceInfo; } private updateExtensionTable(extensions: IssueReporterExtensionData[], numThemeExtensions: number): void { - const target = document.querySelector('.block-extensions .block-info'); + const target = mainWindow.document.querySelector('.block-extensions .block-info'); if (target) { if (this.configuration.disableExtensions) { reset(target, localize('disabledExtensions', "Extensions are disabled")); @@ -1286,7 +1308,7 @@ export class IssueReporter extends Disposable { private updateExperimentsInfo(experimentInfo: string | undefined) { this.issueReporterModel.update({ experimentInfo }); - const target = document.querySelector('.block-experiments .block-info'); + const target = mainWindow.document.querySelector('.block-experiments .block-info'); if (target) { target.textContent = experimentInfo ? experimentInfo : localize('noCurrentExperiments', "No current experiments."); } @@ -1317,7 +1339,7 @@ export class IssueReporter extends Disposable { } private getElementById(elementId: string): T | undefined { - const element = document.getElementById(elementId) as T | undefined; + const element = mainWindow.document.getElementById(elementId) as T | undefined; if (element) { return element; } else { diff --git a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts index 19763e979f1..522c418ffaa 100644 --- a/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts +++ b/src/vs/code/electron-sandbox/processExplorer/processExplorerMain.ts @@ -20,11 +20,12 @@ import { ByteSize } from 'vs/platform/files/common/files'; import { ElectronIPCMainProcessService } from 'vs/platform/ipc/electron-sandbox/mainProcessService'; import { ProcessExplorerData, ProcessExplorerStyles, ProcessExplorerWindowConfiguration } from 'vs/platform/issue/common/issue'; import { INativeHostService } from 'vs/platform/native/common/native'; -import { NativeHostService } from 'vs/platform/native/electron-sandbox/nativeHostService'; +import { NativeHostService } from 'vs/platform/native/common/nativeHostService'; import { getIconsStyleSheet } from 'vs/platform/theme/browser/iconsStyleSheet'; import { applyZoom, zoomIn, zoomOut } from 'vs/platform/window/electron-sandbox/window'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { KeyCode } from 'vs/base/common/keyCodes'; +import { mainWindow } from 'vs/base/browser/window'; const DEBUG_FLAGS_PATTERN = /\s--inspect(?:-brk|port)?=(?\d+)?/; const DEBUG_PORT_PATTERN = /\s--inspect-port=(?\d+)/; @@ -262,7 +263,7 @@ class ProcessExplorer { await this.createProcessTree(processRoots); } else { this.tree.setInput({ processes: { processRoots } }); - this.tree.layout(window.innerHeight, window.innerWidth); + this.tree.layout(mainWindow.innerHeight, mainWindow.innerWidth); } this.requestProcessList(0); @@ -274,7 +275,7 @@ class ProcessExplorer { } private setEventHandlers(data: ProcessExplorerData): void { - document.onkeydown = (e: KeyboardEvent) => { + mainWindow.document.onkeydown = (e: KeyboardEvent) => { const cmdOrCtrlKey = data.platform === 'darwin' ? e.metaKey : e.ctrlKey; // Cmd/Ctrl + w closes issue window @@ -298,7 +299,7 @@ class ProcessExplorer { } private async createProcessTree(processRoots: MachineProcessInformation[]): Promise { - const container = document.getElementById('process-list'); + const container = mainWindow.document.getElementById('process-list'); if (!container) { return; } @@ -342,7 +343,7 @@ class ProcessExplorer { }); this.tree.setInput({ processes: { processRoots } }); - this.tree.layout(window.innerHeight, window.innerWidth); + this.tree.layout(mainWindow.innerHeight, mainWindow.innerWidth); this.tree.onKeyDown(e => { const event = new StandardKeyboardEvent(e); if (event.keyCode === KeyCode.KeyE && event.altKey) { @@ -356,11 +357,11 @@ class ProcessExplorer { } }); - container.style.height = `${window.innerHeight}px`; + container.style.height = `${mainWindow.innerHeight}px`; - window.addEventListener('resize', () => { - container.style.height = `${window.innerHeight}px`; - this.tree?.layout(window.innerHeight, window.innerWidth); + mainWindow.addEventListener('resize', () => { + container.style.height = `${mainWindow.innerHeight}px`; + this.tree?.layout(mainWindow.innerHeight, mainWindow.innerWidth); }); } @@ -476,7 +477,7 @@ class ProcessExplorer { styleElement.textContent = content.join('\n'); if (styles.color) { - document.body.style.color = styles.color; + mainWindow.document.body.style.color = styles.color; } } @@ -516,7 +517,7 @@ class ProcessExplorer { selectionPids.length = 0; selectionPids.push(pid); } - const rows = selectionPids?.map(e => document.getElementById(`pid-${e}`)).filter(e => !!e) as HTMLElement[]; + const rows = selectionPids?.map(e => mainWindow.document.getElementById(`pid-${e}`)).filter(e => !!e) as HTMLElement[]; if (rows) { const text = rows.map(e => e.innerText).filter(e => !!e) as string[]; this.nativeHostService.writeClipboardText(text.join('\n')); @@ -527,7 +528,7 @@ class ProcessExplorer { items.push({ label: localize('copyAll', "Copy All"), click: () => { - const processList = document.getElementById('process-list'); + const processList = mainWindow.document.getElementById('process-list'); if (processList) { this.nativeHostService.writeClipboardText(processList.innerText); } @@ -592,7 +593,7 @@ function createCodiconStyleSheet() { export function startup(configuration: ProcessExplorerWindowConfiguration): void { const platformClass = configuration.data.platform === 'win32' ? 'windows' : configuration.data.platform === 'linux' ? 'linux' : 'mac'; - document.body.classList.add(platformClass); // used by our fonts + mainWindow.document.body.classList.add(platformClass); // used by our fonts createCodiconStyleSheet(); applyZoom(configuration.data.zoomLevel); diff --git a/src/vs/code/electron-sandbox/workbench/workbench.js b/src/vs/code/electron-sandbox/workbench/workbench.js index b709c8c5008..35e8368d3c9 100644 --- a/src/vs/code/electron-sandbox/workbench/workbench.js +++ b/src/vs/code/electron-sandbox/workbench/workbench.js @@ -61,7 +61,6 @@ // locations, we try to help the browser to // initialize canvas when it is idle, right // before we wait for the scripts to be loaded. - // @ts-ignore window.requestIdleCallback(() => { const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); diff --git a/src/vs/code/node/sharedProcess/sharedProcessMain.ts b/src/vs/code/node/sharedProcess/sharedProcessMain.ts index 84d080486a3..3c5650cb69d 100644 --- a/src/vs/code/node/sharedProcess/sharedProcessMain.ts +++ b/src/vs/code/node/sharedProcess/sharedProcessMain.ts @@ -106,6 +106,7 @@ import { ExtensionsProfileScannerService } from 'vs/platform/extensionManagement import { RequestChannelClient } from 'vs/platform/request/common/requestIpc'; import { ExtensionRecommendationNotificationServiceChannelClient } from 'vs/platform/extensionRecommendations/common/extensionRecommendationsIpc'; import { INativeHostService } from 'vs/platform/native/common/native'; +import { NativeHostService } from 'vs/platform/native/common/nativeHostService'; import { UserDataAutoSyncService } from 'vs/platform/userDataSync/node/userDataAutoSyncService'; import { ExtensionTipsService } from 'vs/platform/extensionManagement/node/extensionTipsService'; import { IMainProcessService, MainProcessService } from 'vs/platform/ipc/common/mainProcessService'; @@ -274,7 +275,7 @@ class SharedProcessMain extends Disposable implements IClientConnectionFilter { services.set(IV8InspectProfilingService, new SyncDescriptor(V8InspectProfilingService, undefined, false /* proxied to other processes */)); // Native Host - const nativeHostService = ProxyChannel.toService(mainProcessService.getChannel('nativeHost')); + const nativeHostService = new NativeHostService(-1 /* we are not running in a browser window context */, mainProcessService) as INativeHostService; services.set(INativeHostService, nativeHostService); // Download diff --git a/src/vs/css.ts b/src/vs/css.ts index 5337b4f2136..e142f8dc8b5 100644 --- a/src/vs/css.ts +++ b/src/vs/css.ts @@ -41,7 +41,8 @@ function loadCSS(name: string, cssUrl: string, callback: () => void, errorback: } function linkTagExists(name: string, cssUrl: string): boolean { - const links = document.getElementsByTagName('link'); + // eslint-disable-next-line no-restricted-globals + const links = window.document.getElementsByTagName('link'); for (let i = 0, len = links.length; i < len; i++) { const nameAttr = links[i].getAttribute('data-name'); const hrefAttr = links[i].getAttribute('href'); @@ -61,7 +62,8 @@ function createLinkTag(name: string, cssUrl: string, callback: () => void, error attachListeners(name, linkNode, callback, errorback); linkNode.setAttribute('href', cssUrl); - const head = document.head || document.getElementsByTagName('head')[0]; + // eslint-disable-next-line no-restricted-globals + const head = window.document.head || window.document.getElementsByTagName('head')[0]; head.appendChild(linkNode); } diff --git a/src/vs/editor/browser/config/charWidthReader.ts b/src/vs/editor/browser/config/charWidthReader.ts index 16b6d7d1c48..c2097a6074a 100644 --- a/src/vs/editor/browser/config/charWidthReader.ts +++ b/src/vs/editor/browser/config/charWidthReader.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { $window } from 'vs/base/browser/window'; import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo'; import { BareFontInfo } from 'vs/editor/common/config/fontInfo'; @@ -50,13 +51,13 @@ class DomCharWidthReader { this._createDomElements(); // Add the container to the DOM - document.body.appendChild(this._container!); + $window.document.body.appendChild(this._container!); // Read character widths this._readFromDomElements(); // Remove the container from the DOM - document.body.removeChild(this._container!); + $window.document.body.removeChild(this._container!); this._container = null; this._testElements = null; diff --git a/src/vs/editor/browser/config/elementSizeObserver.ts b/src/vs/editor/browser/config/elementSizeObserver.ts index a7da5f289ed..9dd5af8e6d3 100644 --- a/src/vs/editor/browser/config/elementSizeObserver.ts +++ b/src/vs/editor/browser/config/elementSizeObserver.ts @@ -66,10 +66,10 @@ export class ElementSizeObserver extends Disposable { alreadyObservedThisAnimationFrame = true; observeNow(); } finally { - scheduleAtNextAnimationFrame(() => { + scheduleAtNextAnimationFrame(getWindow(this._referenceDomElement), () => { alreadyObservedThisAnimationFrame = false; update(); - }, getWindow(this._referenceDomElement)); + }); } } }; diff --git a/src/vs/editor/browser/config/fontMeasurements.ts b/src/vs/editor/browser/config/fontMeasurements.ts index 952c207f209..0e984df7df0 100644 --- a/src/vs/editor/browser/config/fontMeasurements.ts +++ b/src/vs/editor/browser/config/fontMeasurements.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as browser from 'vs/base/browser/browser'; +import { mainWindow } from 'vs/base/browser/window'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { CharWidthRequest, CharWidthRequestType, readCharWidths } from 'vs/editor/browser/config/charWidthReader'; @@ -50,7 +51,7 @@ export class FontMeasurementsImpl extends Disposable { public override dispose(): void { if (this._evictUntrustedReadingsTimeout !== -1) { - window.clearTimeout(this._evictUntrustedReadingsTimeout); + clearTimeout(this._evictUntrustedReadingsTimeout); this._evictUntrustedReadingsTimeout = -1; } super.dispose(); @@ -69,7 +70,7 @@ export class FontMeasurementsImpl extends Disposable { if (!value.isTrusted && this._evictUntrustedReadingsTimeout === -1) { // Try reading again after some time - this._evictUntrustedReadingsTimeout = window.setTimeout(() => { + this._evictUntrustedReadingsTimeout = mainWindow.setTimeout(() => { this._evictUntrustedReadingsTimeout = -1; this._evictUntrustedReadings(); }, 5000); diff --git a/src/vs/editor/browser/config/migrateOptions.ts b/src/vs/editor/browser/config/migrateOptions.ts index 6c3d926ff36..b465fb5d107 100644 --- a/src/vs/editor/browser/config/migrateOptions.ts +++ b/src/vs/editor/browser/config/migrateOptions.ts @@ -91,6 +91,8 @@ registerSimpleEditorSettingMigration('autoIndent', [[false, 'advanced'], [true, registerSimpleEditorSettingMigration('matchBrackets', [[true, 'always'], [false, 'never']]); registerSimpleEditorSettingMigration('renderFinalNewline', [[true, 'on'], [false, 'off']]); registerSimpleEditorSettingMigration('cursorSmoothCaretAnimation', [[true, 'on'], [false, 'off']]); +registerSimpleEditorSettingMigration('occurrencesHighlight', [[true, 'singleFile'], [false, 'off']]); +registerSimpleEditorSettingMigration('wordBasedSuggestions', [[true, 'matchingDocuments'], [false, 'off']]); registerEditorSettingMigration('autoClosingBrackets', (value, read, write) => { if (value === false) { diff --git a/src/vs/editor/browser/controller/mouseHandler.ts b/src/vs/editor/browser/controller/mouseHandler.ts index 6796378114d..e4551a39cbe 100644 --- a/src/vs/editor/browser/controller/mouseHandler.ts +++ b/src/vs/editor/browser/controller/mouseHandler.ts @@ -226,7 +226,7 @@ export class MouseHandler extends ViewEventHandler { public getTargetAtClientPoint(clientX: number, clientY: number): IMouseTarget | null { const clientPos = new ClientCoordinates(clientX, clientY); - const pos = clientPos.toPageCoordinates(); + const pos = clientPos.toPageCoordinates(dom.getWindow(this.viewHelper.viewDomNode)); const editorPos = createEditorPagePosition(this.viewHelper.viewDomNode); if (pos.y < editorPos.y || pos.y > editorPos.y + editorPos.height || pos.x < editorPos.x || pos.x > editorPos.x + editorPos.width) { @@ -682,7 +682,7 @@ class TopBottomDragScrollingOperation extends Disposable { this._position = position; this._mouseEvent = mouseEvent; this._lastTime = Date.now(); - this._animationFrameDisposable = dom.scheduleAtNextAnimationFrame(() => this._execute(), dom.getWindow(mouseEvent.browserEvent)); + this._animationFrameDisposable = dom.scheduleAtNextAnimationFrame(dom.getWindow(mouseEvent.browserEvent), () => this._execute()); } public override dispose(): void { @@ -752,7 +752,7 @@ class TopBottomDragScrollingOperation extends Disposable { } this._dispatchMouse(mouseTarget, true, NavigationCommandRevealType.None); - this._animationFrameDisposable = dom.scheduleAtNextAnimationFrame(() => this._execute(), dom.getWindow(mouseTarget.element)); + this._animationFrameDisposable = dom.scheduleAtNextAnimationFrame(dom.getWindow(mouseTarget.element), () => this._execute()); } } diff --git a/src/vs/editor/browser/controller/mouseTarget.ts b/src/vs/editor/browser/controller/mouseTarget.ts index 4c57874ba42..4b7929cd8ed 100644 --- a/src/vs/editor/browser/controller/mouseTarget.ts +++ b/src/vs/editor/browser/controller/mouseTarget.ts @@ -836,7 +836,7 @@ export class MouseTargetFactory { points.sort((a, b) => a.offset - b.offset); - const mouseCoordinates = request.pos.toClientCoordinates(); + const mouseCoordinates = request.pos.toClientCoordinates(dom.getWindow(ctx.viewDomNode)); const spanNodeClientRect = spanNode.getBoundingClientRect(); const mouseIsOverSpanNode = (spanNodeClientRect.left <= mouseCoordinates.clientX && mouseCoordinates.clientX <= spanNodeClientRect.right); @@ -897,14 +897,14 @@ export class MouseTargetFactory { const adjustedPage = new PageCoordinates(request.pos.x, adjustedPageY); - const r = this._actualDoHitTestWithCaretRangeFromPoint(ctx, adjustedPage.toClientCoordinates()); + const r = this._actualDoHitTestWithCaretRangeFromPoint(ctx, adjustedPage.toClientCoordinates(dom.getWindow(ctx.viewDomNode))); if (r.type === HitTestResultType.Content) { return r; } } // Also try to hit test without the adjustment (for the edge cases that we are near the top or bottom) - return this._actualDoHitTestWithCaretRangeFromPoint(ctx, request.pos.toClientCoordinates()); + return this._actualDoHitTestWithCaretRangeFromPoint(ctx, request.pos.toClientCoordinates(dom.getWindow(ctx.viewDomNode))); } private static _actualDoHitTestWithCaretRangeFromPoint(ctx: HitTestContext, coords: ClientCoordinates): HitTestResult { @@ -1014,7 +1014,7 @@ export class MouseTargetFactory { if (typeof (ctx.viewDomNode.ownerDocument).caretRangeFromPoint === 'function') { result = this._doHitTestWithCaretRangeFromPoint(ctx, request); } else if ((ctx.viewDomNode.ownerDocument).caretPositionFromPoint) { - result = this._doHitTestWithCaretPositionFromPoint(ctx, request.pos.toClientCoordinates()); + result = this._doHitTestWithCaretPositionFromPoint(ctx, request.pos.toClientCoordinates(dom.getWindow(ctx.viewDomNode))); } if (result.type === HitTestResultType.Content) { const injectedText = ctx.viewModel.getInjectedTextAt(result.position); @@ -1046,12 +1046,13 @@ function shadowCaretRangeFromPoint(shadowRoot: ShadowRoot, x: number, y: number) const rect = el.getBoundingClientRect(); // And its font (the computed shorthand font property might be empty, see #3217) - const fontStyle = window.getComputedStyle(el, null).getPropertyValue('font-style'); - const fontVariant = window.getComputedStyle(el, null).getPropertyValue('font-variant'); - const fontWeight = window.getComputedStyle(el, null).getPropertyValue('font-weight'); - const fontSize = window.getComputedStyle(el, null).getPropertyValue('font-size'); - const lineHeight = window.getComputedStyle(el, null).getPropertyValue('line-height'); - const fontFamily = window.getComputedStyle(el, null).getPropertyValue('font-family'); + const elWindow = dom.getWindow(el); + const fontStyle = elWindow.getComputedStyle(el, null).getPropertyValue('font-style'); + const fontVariant = elWindow.getComputedStyle(el, null).getPropertyValue('font-variant'); + const fontWeight = elWindow.getComputedStyle(el, null).getPropertyValue('font-weight'); + const fontSize = elWindow.getComputedStyle(el, null).getPropertyValue('font-size'); + const lineHeight = elWindow.getComputedStyle(el, null).getPropertyValue('line-height'); + const fontFamily = elWindow.getComputedStyle(el, null).getPropertyValue('font-family'); const font = `${fontStyle} ${fontVariant} ${fontWeight} ${fontSize}/${lineHeight} ${fontFamily}`; // And also its txt content diff --git a/src/vs/editor/browser/controller/pointerHandler.ts b/src/vs/editor/browser/controller/pointerHandler.ts index fc7103855a3..b21e9fb821b 100644 --- a/src/vs/editor/browser/controller/pointerHandler.ts +++ b/src/vs/editor/browser/controller/pointerHandler.ts @@ -15,6 +15,7 @@ import { ViewContext } from 'vs/editor/common/viewModel/viewContext'; import { BrowserFeatures } from 'vs/base/browser/canIUse'; import { TextAreaSyntethicEvents } from 'vs/editor/browser/controller/textAreaInput'; import { NavigationCommandRevealType } from 'vs/editor/browser/coreCommands'; +import { mainWindow } from 'vs/base/browser/window'; /** * Currently only tested on iOS 13/ iPadOS. @@ -138,7 +139,7 @@ export class PointerHandler extends Disposable { super(); if ((platform.isIOS && BrowserFeatures.pointerEvents)) { this.handler = this._register(new PointerEventHandler(context, viewController, viewHelper)); - } else if (window.TouchEvent) { + } else if (mainWindow.TouchEvent) { this.handler = this._register(new TouchHandler(context, viewController, viewHelper)); } else { this.handler = this._register(new MouseHandler(context, viewController, viewHelper)); diff --git a/src/vs/editor/browser/controller/textAreaHandler.ts b/src/vs/editor/browser/controller/textAreaHandler.ts index 76a007a4cad..e7f16198870 100644 --- a/src/vs/editor/browser/controller/textAreaHandler.ts +++ b/src/vs/editor/browser/controller/textAreaHandler.ts @@ -927,28 +927,28 @@ interface IRenderData { strikethrough?: boolean; } -function measureText(document: Document, text: string, fontInfo: FontInfo, tabSize: number): number { +function measureText(targetDocument: Document, text: string, fontInfo: FontInfo, tabSize: number): number { if (text.length === 0) { return 0; } - const container = document.createElement('div'); + const container = targetDocument.createElement('div'); container.style.position = 'absolute'; container.style.top = '-50000px'; container.style.width = '50000px'; - const regularDomNode = document.createElement('span'); + const regularDomNode = targetDocument.createElement('span'); applyFontInfo(regularDomNode, fontInfo); regularDomNode.style.whiteSpace = 'pre'; // just like the textarea regularDomNode.style.tabSize = `${tabSize * fontInfo.spaceWidth}px`; // just like the textarea regularDomNode.append(text); container.appendChild(regularDomNode); - document.body.appendChild(container); + targetDocument.body.appendChild(container); const res = regularDomNode.offsetWidth; - document.body.removeChild(container); + targetDocument.body.removeChild(container); return res; } diff --git a/src/vs/editor/browser/controller/textAreaInput.ts b/src/vs/editor/browser/controller/textAreaInput.ts index f548b74c04f..d0a06a18c51 100644 --- a/src/vs/editor/browser/controller/textAreaInput.ts +++ b/src/vs/editor/browser/controller/textAreaInput.ts @@ -783,6 +783,7 @@ export class TextAreaWrapper extends Disposable implements ICompleteTextAreaWrap } else { activeElement = textArea.ownerDocument.activeElement; } + const activeWindow = dom.getWindow(activeElement); const currentIsFocused = (activeElement === textArea); const currentSelectionStart = textArea.selectionStart; @@ -791,7 +792,7 @@ export class TextAreaWrapper extends Disposable implements ICompleteTextAreaWrap if (currentIsFocused && currentSelectionStart === selectionStart && currentSelectionEnd === selectionEnd) { // No change // Firefox iframe bug https://github.com/microsoft/monaco-editor/issues/643#issuecomment-367871377 - if (browser.isFirefox && window.parent !== window) { + if (browser.isFirefox && activeWindow.parent !== activeWindow) { textArea.focus(); } return; @@ -803,7 +804,7 @@ export class TextAreaWrapper extends Disposable implements ICompleteTextAreaWrap // No need to focus, only need to change the selection range this.setIgnoreSelectionChangeTime('setSelectionRange'); textArea.setSelectionRange(selectionStart, selectionEnd); - if (browser.isFirefox && window.parent !== window) { + if (browser.isFirefox && activeWindow.parent !== activeWindow) { textArea.focus(); } return; diff --git a/src/vs/editor/browser/editorDom.ts b/src/vs/editor/browser/editorDom.ts index e533d4d13ca..f295dd8b43d 100644 --- a/src/vs/editor/browser/editorDom.ts +++ b/src/vs/editor/browser/editorDom.ts @@ -23,8 +23,8 @@ export class PageCoordinates { public readonly y: number ) { } - public toClientCoordinates(): ClientCoordinates { - return new ClientCoordinates(this.x - window.scrollX, this.y - window.scrollY); + public toClientCoordinates(targetWindow: Window): ClientCoordinates { + return new ClientCoordinates(this.x - targetWindow.scrollX, this.y - targetWindow.scrollY); } } @@ -43,8 +43,8 @@ export class ClientCoordinates { public readonly clientY: number ) { } - public toPageCoordinates(): PageCoordinates { - return new PageCoordinates(this.clientX + window.scrollX, this.clientY + window.scrollY); + public toPageCoordinates(targetWindow: Window): PageCoordinates { + return new PageCoordinates(this.clientX + targetWindow.scrollX, this.clientY + targetWindow.scrollY); } } @@ -128,7 +128,7 @@ export class EditorMouseEvent extends StandardMouseEvent { public readonly relativePos: CoordinatesRelativeToEditor; constructor(e: MouseEvent, isFromPointerCapture: boolean, editorViewDomNode: HTMLElement) { - super(e); + super(dom.getWindow(editorViewDomNode), e); this.isFromPointerCapture = isFromPointerCapture; this.pos = new PageCoordinates(this.posx, this.posy); this.editorPos = createEditorPagePosition(editorViewDomNode); diff --git a/src/vs/editor/browser/services/editorWorkerService.ts b/src/vs/editor/browser/services/editorWorkerService.ts index 5b411eeec3c..f707c6fa25e 100644 --- a/src/vs/editor/browser/services/editorWorkerService.ts +++ b/src/vs/editor/browser/services/editorWorkerService.ts @@ -29,6 +29,8 @@ import { IDocumentDiff, IDocumentDiffProviderOptions } from 'vs/editor/common/di import { ILinesDiffComputerOptions, MovedText } from 'vs/editor/common/diff/linesDiffComputer'; import { DetailedLineRangeMapping, RangeMapping, LineRangeMapping } from 'vs/editor/common/diff/rangeMapping'; import { LineRange } from 'vs/editor/common/core/lineRange'; +import { $window } from 'vs/base/browser/window'; +import { WindowIntervalTimer } from 'vs/base/browser/dom'; /** * Stop syncing a model to the worker if it was not needed for 1 min. @@ -211,16 +213,15 @@ class WordBasedCompletionItemProvider implements languages.CompletionItemProvide async provideCompletionItems(model: ITextModel, position: Position): Promise { type WordBasedSuggestionsConfig = { - wordBasedSuggestions?: boolean; - wordBasedSuggestionsMode?: 'currentDocument' | 'matchingDocuments' | 'allDocuments'; + wordBasedSuggestions?: 'off' | 'currentDocument' | 'matchingDocuments' | 'allDocuments'; }; const config = this._configurationService.getValue(model.uri, position, 'editor'); - if (!config.wordBasedSuggestions) { + if (config.wordBasedSuggestions === 'off') { return undefined; } const models: URI[] = []; - if (config.wordBasedSuggestionsMode === 'currentDocument') { + if (config.wordBasedSuggestions === 'currentDocument') { // only current file and only if not too large if (canSyncModel(this._modelService, model.uri)) { models.push(model.uri); @@ -234,7 +235,7 @@ class WordBasedCompletionItemProvider implements languages.CompletionItemProvide if (candidate === model) { models.unshift(candidate.uri); - } else if (config.wordBasedSuggestionsMode === 'allDocuments' || candidate.getLanguageId() === model.getLanguageId()) { + } else if (config.wordBasedSuggestions === 'allDocuments' || candidate.getLanguageId() === model.getLanguageId()) { models.push(candidate.uri); } } @@ -281,8 +282,8 @@ class WorkerManager extends Disposable { this._editorWorkerClient = null; this._lastWorkerUsedTime = (new Date()).getTime(); - const stopWorkerInterval = this._register(new IntervalTimer()); - stopWorkerInterval.cancelAndSet(() => this._checkStopIdleWorker(), Math.round(STOP_WORKER_DELTA_TIME_MS / 2)); + const stopWorkerInterval = this._register(new WindowIntervalTimer()); + stopWorkerInterval.cancelAndSet(() => this._checkStopIdleWorker(), Math.round(STOP_WORKER_DELTA_TIME_MS / 2), $window); this._register(this._modelService.onModelRemoved(_ => this._checkStopEmptyWorker())); } diff --git a/src/vs/editor/browser/services/openerService.ts b/src/vs/editor/browser/services/openerService.ts index e329d0eea4d..5e63ede1f3f 100644 --- a/src/vs/editor/browser/services/openerService.ts +++ b/src/vs/editor/browser/services/openerService.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as dom from 'vs/base/browser/dom'; +import { mainWindow } from 'vs/base/browser/window'; import { CancellationToken } from 'vs/base/common/cancellation'; import { IDisposable } from 'vs/base/common/lifecycle'; import { LinkedList } from 'vs/base/common/linkedList'; @@ -122,7 +123,7 @@ export class OpenerService implements IOpenerService { if (matchesSomeScheme(href, Schemas.http, Schemas.https)) { dom.windowOpenNoOpener(href); } else { - window.location.href = href; + mainWindow.location.href = href; } return true; } diff --git a/src/vs/editor/browser/view.ts b/src/vs/editor/browser/view.ts index 700b1d55b1d..15889c003ff 100644 --- a/src/vs/editor/browser/view.ts +++ b/src/vs/editor/browser/view.ts @@ -7,7 +7,7 @@ import * as dom from 'vs/base/browser/dom'; import { Selection } from 'vs/editor/common/core/selection'; import { Range } from 'vs/editor/common/core/range'; import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; -import { onUnexpectedError } from 'vs/base/common/errors'; +import { BugIndicatingError, onUnexpectedError } from 'vs/base/common/errors'; import { IDisposable } from 'vs/base/common/lifecycle'; import { IPointerHandlerHelper } from 'vs/editor/browser/controller/mouseHandler'; import { PointerHandler } from 'vs/editor/browser/controller/pointerHandler'; @@ -431,12 +431,18 @@ export class View extends ViewEventHandler { } private _scheduleRender(): void { + if (this._store.isDisposed) { + throw new BugIndicatingError(); + } if (this._renderAnimationFrame === null) { - this._renderAnimationFrame = dom.runAtThisOrScheduleAtNextAnimationFrame(this._onRenderScheduled.bind(this), dom.getWindow(this.domNode.domNode), 100); + this._renderAnimationFrame = dom.runAtThisOrScheduleAtNextAnimationFrame(dom.getWindow(this.domNode.domNode), this._onRenderScheduled.bind(this), 100); } } private _onRenderScheduled(): void { + if (this._store.isDisposed) { + throw new BugIndicatingError(); + } this._renderAnimationFrame = null; this._flushAccumulatedAndRenderNow(); } @@ -457,6 +463,10 @@ export class View extends ViewEventHandler { } private _actualRender(): void { + if (this._store.isDisposed) { + throw new BugIndicatingError(); + } + if (!this.domNode.domNode.isConnected) { return; } diff --git a/src/vs/editor/browser/view/domLineBreaksComputer.ts b/src/vs/editor/browser/view/domLineBreaksComputer.ts index 9aba078cf67..64fb2185eed 100644 --- a/src/vs/editor/browser/view/domLineBreaksComputer.ts +++ b/src/vs/editor/browser/view/domLineBreaksComputer.ts @@ -6,6 +6,7 @@ import { createTrustedTypesPolicy } from 'vs/base/browser/trustedTypes'; import { CharCode } from 'vs/base/common/charCode'; import * as strings from 'vs/base/common/strings'; +import { assertIsDefined } from 'vs/base/common/types'; import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo'; import { WrappingIndent } from 'vs/editor/common/config/editorOptions'; import { FontInfo } from 'vs/editor/common/config/fontInfo'; @@ -18,11 +19,11 @@ const ttPolicy = createTrustedTypesPolicy('domLineBreaksComputer', { createHTML: export class DOMLineBreaksComputerFactory implements ILineBreaksComputerFactory { - public static create(): DOMLineBreaksComputerFactory { - return new DOMLineBreaksComputerFactory(); + public static create(targetWindow: Window): DOMLineBreaksComputerFactory { + return new DOMLineBreaksComputerFactory(new WeakRef(targetWindow)); } - constructor() { + constructor(private targetWindow: WeakRef) { } public createLineBreaksComputer(fontInfo: FontInfo, tabSize: number, wrappingColumn: number, wrappingIndent: WrappingIndent, wordBreak: 'normal' | 'keepAll'): ILineBreaksComputer { @@ -34,13 +35,13 @@ export class DOMLineBreaksComputerFactory implements ILineBreaksComputerFactory injectedTexts.push(injectedText); }, finalize: () => { - return createLineBreaks(requests, fontInfo, tabSize, wrappingColumn, wrappingIndent, wordBreak, injectedTexts); + return createLineBreaks(assertIsDefined(this.targetWindow.deref()), requests, fontInfo, tabSize, wrappingColumn, wrappingIndent, wordBreak, injectedTexts); } }; } } -function createLineBreaks(requests: string[], fontInfo: FontInfo, tabSize: number, firstLineBreakColumn: number, wrappingIndent: WrappingIndent, wordBreak: 'normal' | 'keepAll', injectedTextsPerLine: (LineInjectedText[] | null)[]): (ModelLineProjectionData | null)[] { +function createLineBreaks(targetWindow: Window, requests: string[], fontInfo: FontInfo, tabSize: number, firstLineBreakColumn: number, wrappingIndent: WrappingIndent, wordBreak: 'normal' | 'keepAll', injectedTextsPerLine: (LineInjectedText[] | null)[]): (ModelLineProjectionData | null)[] { function createEmptyLineBreakWithPossiblyInjectedText(requestIdx: number): ModelLineProjectionData | null { const injectedTexts = injectedTextsPerLine[requestIdx]; if (injectedTexts) { @@ -139,7 +140,7 @@ function createLineBreaks(requests: string[], fontInfo: FontInfo, tabSize: numbe containerDomNode.style.wordBreak = 'inherit'; containerDomNode.style.overflowWrap = 'break-word'; } - document.body.appendChild(containerDomNode); + targetWindow.document.body.appendChild(containerDomNode); const range = document.createRange(); const lineDomNodes = Array.prototype.slice.call(containerDomNode.children, 0); @@ -183,7 +184,7 @@ function createLineBreaks(requests: string[], fontInfo: FontInfo, tabSize: numbe result[i] = new ModelLineProjectionData(injectionOffsets, injectionOptions, breakOffsets, breakOffsetsVisibleColumn, wrappedTextIndentLength); } - document.body.removeChild(containerDomNode); + targetWindow.document.body.removeChild(containerDomNode); return result; } diff --git a/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts b/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts index 3f2b4139479..1be969b7497 100644 --- a/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts +++ b/src/vs/editor/browser/viewParts/viewCursors/viewCursors.ts @@ -17,6 +17,7 @@ import * as viewEvents from 'vs/editor/common/viewEvents'; import { registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { isHighContrast } from 'vs/platform/theme/common/theme'; import { CursorChangeReason } from 'vs/editor/common/cursorEvents'; +import { WindowIntervalTimer, getWindow } from 'vs/base/browser/dom'; export class ViewCursors extends ViewPart { @@ -68,7 +69,7 @@ export class ViewCursors extends ViewPart { this._domNode.appendChild(this._primaryCursor.getDomNode()); this._startCursorBlinkAnimation = new TimeoutTimer(); - this._cursorFlatBlinkInterval = new IntervalTimer(); + this._cursorFlatBlinkInterval = new WindowIntervalTimer(); this._blinkingEnabled = false; @@ -253,7 +254,7 @@ export class ViewCursors extends ViewPart { } else { this._show(); } - }, ViewCursors.BLINK_INTERVAL); + }, ViewCursors.BLINK_INTERVAL, getWindow(this._domNode.domNode)); } else { this._startCursorBlinkAnimation.setIfNotSet(() => { this._blinkingEnabled = true; diff --git a/src/vs/editor/browser/widget/codeEditorContributions.ts b/src/vs/editor/browser/widget/codeEditorContributions.ts index 35bf94643b2..f556e1440f1 100644 --- a/src/vs/editor/browser/widget/codeEditorContributions.ts +++ b/src/vs/editor/browser/widget/codeEditorContributions.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { runWhenIdle } from 'vs/base/common/async'; +import { getWindow, runWhenWindowIdle } from 'vs/base/browser/dom'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Disposable, DisposableMap } from 'vs/base/common/lifecycle'; import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; @@ -57,21 +57,21 @@ export class CodeEditorContributions extends Disposable { // AfterFirstRender // - these extensions will be instantiated at the latest 50ms after the first render. // - but if there is idle time, we will instantiate them sooner. - this._register(runWhenIdle(() => { + this._register(runWhenWindowIdle(getWindow(this._editor.getDomNode()), () => { this._instantiateSome(EditorContributionInstantiation.AfterFirstRender); })); // BeforeFirstInteraction // - these extensions will be instantiated at the latest before a mouse or a keyboard event. // - but if there is idle time, we will instantiate them sooner. - this._register(runWhenIdle(() => { + this._register(runWhenWindowIdle(getWindow(this._editor.getDomNode()), () => { this._instantiateSome(EditorContributionInstantiation.BeforeFirstInteraction); })); // Eventually // - these extensions will only be instantiated when there is idle time. // - since there is no guarantee that there will ever be idle time, we set a timeout of 5s here. - this._register(runWhenIdle(() => { + this._register(runWhenWindowIdle(getWindow(this._editor.getDomNode()), () => { this._instantiateSome(EditorContributionInstantiation.Eventually); }, 5000)); } @@ -112,7 +112,7 @@ export class CodeEditorContributions extends Disposable { } public onAfterModelAttached(): void { - this._register(runWhenIdle(() => { + this._register(runWhenWindowIdle(getWindow(this._editor?.getDomNode()), () => { this._instantiateSome(EditorContributionInstantiation.AfterFirstRender); }, 50)); } diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 414b6136d92..675691568ac 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -363,7 +363,6 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE }; this._register(new dom.DragAndDropObserver(this._domElement, { - onDragEnter: () => undefined, onDragOver: e => { if (!isDropIntoEnabled()) { return; @@ -1491,7 +1490,6 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE } this._overlayWidgets[widget.getId()] = widgetData; - if (this._modelData && this._modelData.hasRealView) { this._modelData.view.addOverlayWidget(widgetData); } @@ -1647,9 +1645,9 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE this._id, this._configuration, model, - DOMLineBreaksComputerFactory.create(), + DOMLineBreaksComputerFactory.create(dom.getWindow(this._domElement)), MonospaceLineBreaksComputerFactory.create(this._configuration.options), - (callback) => dom.scheduleAtNextAnimationFrame(callback, dom.getWindow(this._domElement)), + (callback) => dom.scheduleAtNextAnimationFrame(dom.getWindow(this._domElement), callback), this.languageConfigurationService, this._themeService, attachedView, diff --git a/src/vs/editor/browser/widget/diffEditor/accessibleDiffViewer.ts b/src/vs/editor/browser/widget/diffEditor/accessibleDiffViewer.ts index e7a8d7c5181..8ad31fc0537 100644 --- a/src/vs/editor/browser/widget/diffEditor/accessibleDiffViewer.ts +++ b/src/vs/editor/browser/widget/diffEditor/accessibleDiffViewer.ts @@ -12,7 +12,7 @@ import { forEachAdjacent, groupAdjacentBy } from 'vs/base/common/arrays'; import { Codicon } from 'vs/base/common/codicons'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; -import { IObservable, ITransaction, autorun, autorunWithStore, derived, derivedWithStore, recomputeInitiallyAndOnChange, observableValue, subtransaction, transaction } from 'vs/base/common/observable'; +import { IObservable, ITransaction, autorun, autorunWithStore, derived, derivedWithStore, observableValue, subtransaction, transaction } from 'vs/base/common/observable'; import { ThemeIcon } from 'vs/base/common/themables'; import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo'; import { DiffEditorEditors } from 'vs/editor/browser/widget/diffEditor/diffEditorEditors'; @@ -54,10 +54,9 @@ export class AccessibleDiffViewer extends Disposable { @IInstantiationService private readonly _instantiationService: IInstantiationService, ) { super(); - this._register(recomputeInitiallyAndOnChange(this.model)); } - private readonly model = derivedWithStore(this, (reader, store) => { + private readonly _state = derivedWithStore(this, (reader, store) => { const visible = this._visible.read(reader); this._parentNode.style.visibility = visible ? 'visible' : 'hidden'; if (!visible) { @@ -65,18 +64,15 @@ export class AccessibleDiffViewer extends Disposable { } const model = store.add(this._instantiationService.createInstance(ViewModel, this._diffs, this._editors, this._setVisible, this._canClose)); const view = store.add(this._instantiationService.createInstance(View, this._parentNode, model, this._width, this._height, this._editors)); - return { - model, - view - }; - }); + return { model, view, }; + }).recomputeInitiallyAndOnChange(this._store); next(): void { transaction(tx => { const isVisible = this._visible.get(); this._setVisible(true, tx); if (isVisible) { - this.model.get()!.model.nextGroup(tx); + this._state.get()!.model.nextGroup(tx); } }); } @@ -84,7 +80,7 @@ export class AccessibleDiffViewer extends Disposable { prev(): void { transaction(tx => { this._setVisible(true, tx); - this.model.get()!.model.previousGroup(tx); + this._state.get()!.model.previousGroup(tx); }); } diff --git a/src/vs/editor/browser/widget/diffEditor/colors.ts b/src/vs/editor/browser/widget/diffEditor/colors.ts index 449e67f06f8..503eb148e5e 100644 --- a/src/vs/editor/browser/widget/diffEditor/colors.ts +++ b/src/vs/editor/browser/widget/diffEditor/colors.ts @@ -17,3 +17,9 @@ export const diffMoveBorderActive = registerColor( { dark: '#FFA500', light: '#FFA500', hcDark: '#FFA500', hcLight: '#FFA500', }, localize('diffEditor.moveActive.border', 'The active border color for text that got moved in the diff editor.') ); + +export const diffEditorUnchangedRegionShadow = registerColor( + 'diffEditor.unchangedRegionShadow', + { dark: '#000000', light: '#737373BF', hcDark: '#000000', hcLight: '#737373BF', }, + localize('diffEditor.unchangedRegionShadow', 'The color of the shadow around unchanged region widgets.') +); diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorEditors.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorEditors.ts index bb6cbeef5c5..56f4f7b2686 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorEditors.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorEditors.ts @@ -25,6 +25,9 @@ export class DiffEditorEditors extends Disposable { private readonly _onDidContentSizeChange = this._register(new Emitter()); public get onDidContentSizeChange() { return this._onDidContentSizeChange.event; } + public readonly modifiedScrollTop: IObservable; + public readonly modifiedScrollHeight: IObservable; + public readonly modifiedModel: IObservable; constructor( @@ -41,7 +44,10 @@ export class DiffEditorEditors extends Disposable { this.original = this._register(this._createLeftHandSideEditor(_options.editorOptions.get(), codeEditorWidgetOptions.originalEditor || {})); this.modified = this._register(this._createRightHandSideEditor(_options.editorOptions.get(), codeEditorWidgetOptions.modifiedEditor || {})); - this.modifiedModel = observableFromEvent(this.modified.onDidChangeModel, () => this.modified.getModel()); + this.modifiedModel = observableFromEvent(this.modified.onDidChangeModel, () => /** @description modified.model */ this.modified.getModel()); + + this.modifiedScrollTop = observableFromEvent(this.modified.onDidScrollChange, () => /** @description modified.getScrollTop */ this.modified.getScrollTop()); + this.modifiedScrollHeight = observableFromEvent(this.modified.onDidScrollChange, () => /** @description modified.getScrollHeight */ this.modified.getScrollHeight()); this._register(autorunHandleChanges({ createEmptyChangeSummary: () => ({} as IDiffEditorConstructionOptions), @@ -143,7 +149,6 @@ export class DiffEditorEditors extends Disposable { // Clone scrollbar options before changing them clonedOptions.scrollbar = { ...(clonedOptions.scrollbar || {}) }; - clonedOptions.scrollbar.vertical = 'visible'; clonedOptions.folding = false; clonedOptions.codeLens = this._options.diffCodeLens.get(); clonedOptions.fixedOverflowWidgets = true; diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts index 7126405bf34..14085fcd753 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorOptions.ts @@ -10,12 +10,14 @@ import { diffEditorDefaultOptions } from 'vs/editor/common/config/diffEditor'; import { IDiffEditorBaseOptions, IDiffEditorOptions, IEditorOptions, ValidDiffEditorBaseOptions, clampedFloat, clampedInt, boolean as validateBooleanOption, stringSet as validateStringSetOption } from 'vs/editor/common/config/editorOptions'; export class DiffEditorOptions { - private readonly _options: ISettableObservable, { changedOptions: IDiffEditorOptions }>; public get editorOptions(): IObservable { return this._options; } - constructor(options: Readonly, private readonly diffEditorWidth: IObservable) { + constructor( + options: Readonly, + private readonly diffEditorWidth: IObservable, + ) { const optionsCopy = { ...options, ...validateDiffEditorOptions(options, diffEditorDefaultOptions) }; this._options = observableValue(this, optionsCopy); } diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorSash.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorSash.ts index b5aeba205ef..e6f4675c7b5 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorSash.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorSash.ts @@ -28,6 +28,7 @@ export class DiffEditorSash extends Disposable { private readonly _options: DiffEditorOptions, private readonly _domNode: HTMLElement, private readonly _dimensions: { height: IObservable; width: IObservable }, + private readonly _sashes: IObservable, ) { super(); @@ -43,7 +44,14 @@ export class DiffEditorSash extends Disposable { this._register(this._sash.onDidReset(() => this._sashRatio.set(undefined, undefined))); this._register(autorun(reader => { - /** @description update sash layout */ + const sashes = this._sashes.read(reader); + if (sashes) { + this._sash.orthogonalEndSash = sashes.bottom; + } + })); + + this._register(autorun(reader => { + /** @description DiffEditorSash.layoutSash */ const enabled = this._options.enableSplitViewResizing.read(reader); this._sash.state = enabled ? SashState.Enabled : SashState.Disabled; this.sashLeft.read(reader); @@ -52,10 +60,6 @@ export class DiffEditorSash extends Disposable { })); } - setBoundarySashes(sashes: IBoundarySashes): void { - this._sash.orthogonalEndSash = sashes.bottom; - } - /** @pure */ private _computeSashLeft(desiredRatio: number, reader: IReader | undefined): number { const contentWidth = this._dimensions.width.read(reader); diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts index 6b81646b0f9..3b54ff415f4 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorViewModel.ts @@ -221,6 +221,7 @@ export class DiffEditorViewModel extends Disposable implements IDiffEditorViewMo result = applyModifiedEdits(result, modifiedTextEditInfos, model.original, model.modified) ?? result; transaction(tx => { + /** @description write diff result */ updateUnchangedRegions(result, tx); this._lastDiff = result; diff --git a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts index 1a4a07758c5..b3c6822a2a7 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffEditorWidget.ts @@ -8,12 +8,14 @@ import { findLast } from 'vs/base/common/arraysFind'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Event } from 'vs/base/common/event'; import { toDisposable } from 'vs/base/common/lifecycle'; -import { IObservable, autorun, autorunWithStore, derived, derivedWithStore, disposableObservableValue, recomputeInitiallyAndOnChange, observableValue, transaction } from 'vs/base/common/observable'; +import { IObservable, ITransaction, autorunWithStore, derived, observableFromEvent, observableValue, recomputeInitiallyAndOnChange, subtransaction, transaction } from 'vs/base/common/observable'; +import { derivedDisposable } from 'vs/base/common/observableInternal/derived'; import 'vs/css!./style'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import { ICodeEditor, IDiffEditor, IDiffEditorConstructionOptions, IMouseTargetViewZone } from 'vs/editor/browser/editorBrowser'; import { EditorExtensionsRegistry, IDiffEditorContributionDescription } from 'vs/editor/browser/editorExtensions'; import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; +import { StableEditorScrollState } from 'vs/editor/browser/stableEditorScroll'; import { CodeEditorWidget, ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; import { AccessibleDiffViewer } from 'vs/editor/browser/widget/diffEditor/accessibleDiffViewer'; import { DiffEditorDecorations } from 'vs/editor/browser/widget/diffEditor/diffEditorDecorations'; @@ -22,7 +24,7 @@ import { HideUnchangedRegionsFeature } from 'vs/editor/browser/widget/diffEditor import { ViewZoneManager } from 'vs/editor/browser/widget/diffEditor/lineAlignment'; import { MovedBlocksLinesPart } from 'vs/editor/browser/widget/diffEditor/movedBlocksLines'; import { OverviewRulerPart } from 'vs/editor/browser/widget/diffEditor/overviewRulerPart'; -import { CSSStyle, ObservableElementSizeObserver, applyStyle, readHotReloadableExport } from 'vs/editor/browser/widget/diffEditor/utils'; +import { CSSStyle, ObservableElementSizeObserver, applyStyle, applyViewZones, bindContextKey, readHotReloadableExport, translatePosition } from 'vs/editor/browser/widget/diffEditor/utils'; import { IDiffEditorOptions } from 'vs/editor/common/config/editorOptions'; import { IDimension } from 'vs/editor/common/core/dimension'; import { Position } from 'vs/editor/common/core/position'; @@ -33,7 +35,6 @@ import { DetailedLineRangeMapping } from 'vs/editor/common/diff/rangeMapping'; import { EditorType, IDiffEditorModel, IDiffEditorViewModel, IDiffEditorViewState } from 'vs/editor/common/editorCommon'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; import { IIdentifiedSingleEditOperation } from 'vs/editor/common/model'; -import { LengthObj } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length'; import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -59,7 +60,8 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { h('div.editor.modified@modified', { style: { position: 'absolute', height: '100%' } }), h('div.accessibleDiffViewer@accessibleDiffViewer', { style: { position: 'absolute', height: '100%' } }), ]); - private readonly _diffModel = this._register(disposableObservableValue('diffModel', undefined)); + private readonly _diffModel = observableValue(this, undefined); + private _shouldDisposeDiffModel = false; public readonly onDidChangeModel = Event.fromObservableLight(this._diffModel); public get onDidContentSizeChange() { return this._editors.onDidContentSizeChange; } @@ -73,18 +75,17 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { private readonly _sash: IObservable; private readonly _boundarySashes = observableValue(this, undefined); - private unchangedRangesFeature!: HideUnchangedRegionsFeature; - private _accessibleDiffViewerShouldBeVisible = observableValue(this, false); private _accessibleDiffViewerVisible = derived(this, reader => this._options.onlyShowAccessibleDiffViewer.read(reader) ? true : this._accessibleDiffViewerShouldBeVisible.read(reader) ); - private _accessibleDiffViewer!: AccessibleDiffViewer; + private readonly _accessibleDiffViewer: IObservable; private readonly _options: DiffEditorOptions; private readonly _editors: DiffEditorEditors; + private readonly _overviewRulerPart: IObservable; private readonly movedBlocksLinesPart = observableValue(this, undefined); public get collapseUnchangedRegions() { return this._options.hideUnchangedRegions.get(); } @@ -113,23 +114,15 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { this._options = new DiffEditorOptions(options, this._rootSizeObserver.width); this._contextKeyService.createKey(EditorContextKeys.isEmbeddedDiffEditor.key, false); - const isEmbeddedDiffEditorKey = EditorContextKeys.isEmbeddedDiffEditor.bindTo(this._contextKeyService); - this._register(autorun(reader => { - /** @description update isEmbeddedDiffEditorKey */ - isEmbeddedDiffEditorKey.set(this._options.isInEmbeddedEditor.read(reader)); - })); - - const comparingMovedCodeKey = EditorContextKeys.comparingMovedCode.bindTo(this._contextKeyService); - this._register(autorun(reader => { - /** @description update comparingMovedCodeKey */ - comparingMovedCodeKey.set(!!this._diffModel.read(reader)?.movedTextToCompare.read(reader)); - })); - - const diffEditorRenderSideBySideInlineBreakpointReachedContextKeyValue = EditorContextKeys.diffEditorRenderSideBySideInlineBreakpointReached.bindTo(this._contextKeyService); - this._register(autorun(reader => { - /** @description update accessibleDiffViewerVisible context key */ - diffEditorRenderSideBySideInlineBreakpointReachedContextKeyValue.set(this._options.couldShowInlineViewBecauseOfSize.read(reader)); - })); + this._register(bindContextKey(EditorContextKeys.isEmbeddedDiffEditor, this._contextKeyService, + reader => this._options.isInEmbeddedEditor.read(reader) + )); + this._register(bindContextKey(EditorContextKeys.comparingMovedCode, this._contextKeyService, + reader => !!this._diffModel.read(reader)?.movedTextToCompare.read(reader) + )); + this._register(bindContextKey(EditorContextKeys.diffEditorRenderSideBySideInlineBreakpointReached, this._contextKeyService, + reader => this._options.couldShowInlineViewBecauseOfSize.read(reader) + )); this._editors = this._register(this._instantiationService.createInstance( DiffEditorEditors, @@ -137,73 +130,94 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { this.elements.modified, this._options, codeEditorWidgetOptions, - (i, c, o, o2) => this._createInnerEditor(i, c, o, o2) + (i, c, o, o2) => this._createInnerEditor(i, c, o, o2), )); - this._sash = derivedWithStore(this, (reader, store) => { + this._overviewRulerPart = derivedDisposable(this, reader => + !this._options.renderOverviewRuler.read(reader) + ? undefined + : this._instantiationService.createInstance( + readHotReloadableExport(OverviewRulerPart, reader), + this._editors, + this.elements.root, + this._diffModel, + this._rootSizeObserver.width, + this._rootSizeObserver.height, + this._layoutInfo.map(i => i.modifiedEditor), + ) + ).recomputeInitiallyAndOnChange(this._store); + + this._sash = derivedDisposable(this, reader => { const showSash = this._options.renderSideBySide.read(reader); this.elements.root.classList.toggle('side-by-side', showSash); - if (!showSash) { return undefined; } - const result = store.add(new DiffEditorSash( + return !showSash ? undefined : new DiffEditorSash( this._options, this.elements.root, { height: this._rootSizeObserver.height, - width: this._rootSizeObserver.width.map((w, reader) => w - (this._options.renderOverviewRuler.read(reader) ? OverviewRulerPart.ENTIRE_DIFF_OVERVIEW_WIDTH : 0)), - } - )); - store.add(autorun(reader => { - /** @description setBoundarySashes */ - const boundarySashes = this._boundarySashes.read(reader); - if (boundarySashes) { - result.setBoundarySashes(boundarySashes); - } - })); - return result; - }); - this._register(recomputeInitiallyAndOnChange(this._sash)); - - this._register(autorunWithStore((reader, store) => { - /** @description UnchangedRangesFeature */ - this.unchangedRangesFeature = store.add( - this._instantiationService.createInstance(readHotReloadableExport(HideUnchangedRegionsFeature, reader), this._editors, this._diffModel, this._options) + width: this._rootSizeObserver.width.map((w, reader) => w - (this._overviewRulerPart.read(reader)?.width ?? 0)), + }, + this._boundarySashes, ); - })); + }).recomputeInitiallyAndOnChange(this._store); - this._register(autorunWithStore((reader, store) => { - /** @description DiffEditorDecorations */ - store.add(new (readHotReloadableExport(DiffEditorDecorations, reader))(this._editors, this._diffModel, this._options)); - })); - this._register(autorunWithStore((reader, store) => { - /** @description ViewZoneManager */ - store.add(this._instantiationService.createInstance( + const unchangedRangesFeature = derivedDisposable(this, reader => /** @description UnchangedRangesFeature */ + this._instantiationService.createInstance( + readHotReloadableExport(HideUnchangedRegionsFeature, reader), + this._editors, this._diffModel, this._options + ) + ).recomputeInitiallyAndOnChange(this._store); + + derivedDisposable(this, reader => /** @description DiffEditorDecorations */ + this._instantiationService.createInstance( + readHotReloadableExport(DiffEditorDecorations, reader), + this._editors, this._diffModel, this._options, + ) + ).recomputeInitiallyAndOnChange(this._store); + + const origViewZoneIdsToIgnore = new Set(); + const modViewZoneIdsToIgnore = new Set(); + let isUpdatingViewZones = false; + const viewZoneManager = derivedDisposable(this, reader => /** @description ViewZoneManager */ + this._instantiationService.createInstance( readHotReloadableExport(ViewZoneManager, reader), getWindow(this._domElement), this._editors, this._diffModel, this._options, this, - () => this.unchangedRangesFeature.isUpdatingViewZones, - )); - })); + () => isUpdatingViewZones || unchangedRangesFeature.get().isUpdatingHiddenAreas, + origViewZoneIdsToIgnore, + modViewZoneIdsToIgnore + ) + ).recomputeInitiallyAndOnChange(this._store); - this._register(autorunWithStore((reader, store) => { - /** @description OverviewRulerPart */ - store.add(this._instantiationService.createInstance( - readHotReloadableExport(OverviewRulerPart, reader), - this._editors, - this.elements.root, - this._diffModel, - this._rootSizeObserver.width, - this._rootSizeObserver.height, - this._layoutInfo.map(i => i.modifiedEditor), - this._options, - )); - })); + const originalViewZones = derived(this, (reader) => { /** @description originalViewZones */ + const orig = viewZoneManager.read(reader).viewZones.read(reader).orig; + const orig2 = unchangedRangesFeature.read(reader).viewZones.read(reader).origViewZones; + return orig.concat(orig2); + }); + const modifiedViewZones = derived(this, (reader) => { /** @description modifiedViewZones */ + const mod = viewZoneManager.read(reader).viewZones.read(reader).mod; + const mod2 = unchangedRangesFeature.read(reader).viewZones.read(reader).modViewZones; + return mod.concat(mod2); + }); + this._register(applyViewZones(this._editors.original, originalViewZones, isUpdatingOrigViewZones => { + isUpdatingViewZones = isUpdatingOrigViewZones; + }, origViewZoneIdsToIgnore)); + let scrollState: StableEditorScrollState | undefined; + this._register(applyViewZones(this._editors.modified, modifiedViewZones, isUpdatingModViewZones => { + isUpdatingViewZones = isUpdatingModViewZones; + if (isUpdatingViewZones) { + scrollState = StableEditorScrollState.capture(this._editors.modified); + } else { + scrollState?.restore(this._editors.modified); + scrollState = undefined; + } + }, modViewZoneIdsToIgnore)); - this._register(autorunWithStore((reader, store) => { - /** @description _accessibleDiffViewer */ - this._accessibleDiffViewer = store.add(this._register(this._instantiationService.createInstance( + this._accessibleDiffViewer = derivedDisposable(this, reader => + this._instantiationService.createInstance( readHotReloadableExport(AccessibleDiffViewer, reader), this.elements.accessibleDiffViewer, this._accessibleDiffViewerVisible, @@ -213,8 +227,9 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { this._rootSizeObserver.height, this._diffModel.map((m, r) => m?.diff.read(r)?.mappings.map(m => m.lineRangeMapping)), this._editors, - ))); - })); + ) + ).recomputeInitiallyAndOnChange(this._store); + const visibility = this._accessibleDiffViewerVisible.map(v => v ? 'hidden' : 'visible'); this._register(applyStyle(this.elements.modified, { visibility })); this._register(applyStyle(this.elements.original, { visibility })); @@ -225,15 +240,18 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { this._register(recomputeInitiallyAndOnChange(this._layoutInfo)); - this._register(autorunWithStore((reader, store) => { - this.movedBlocksLinesPart.set(store.add(new (readHotReloadableExport(MovedBlocksLinesPart, reader))( + derivedDisposable(this, reader => /** @description MovedBlocksLinesPart */ + new (readHotReloadableExport(MovedBlocksLinesPart, reader))( this.elements.root, this._diffModel, this._layoutInfo.map(i => i.originalEditor), this._layoutInfo.map(i => i.modifiedEditor), this._editors, - )), undefined); - })); + ) + ).recomputeInitiallyAndOnChange(this._store, value => { + // This is to break the layout info <-> moved blocks lines part dependency cycle. + this.movedBlocksLinesPart.set(value, undefined); + }); this._register(applyStyle(this.elements.overlay, { width: this._layoutInfo.map((i, r) => i.originalEditor.width + (this._options.renderSideBySide.read(r) ? 0 : i.modifiedEditor.width)), @@ -276,13 +294,20 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { } })); - const isDiffUpToDate = this._diffModel.map((m, reader) => m?.isDiffUpToDate.read(reader)); + const isDiffUpToDate = this._diffModel.map(this, (m, reader) => /** @description isDiffUpToDate */ m?.isDiffUpToDate.read(reader)); this._register(autorunWithStore((reader, store) => { + /** @description DiffEditorWidgetHelper.ShowProgress */ if (isDiffUpToDate.read(reader) === false) { const r = this._editorProgressService.show(true, 1000); store.add(toDisposable(() => r.done())); } })); + + this._register(toDisposable(() => { + if (this._shouldDisposeDiffModel) { + this._diffModel.get()?.dispose(); + } + })); } public getViewWidth(): number { @@ -304,7 +329,7 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { const sashLeft = this._sash.read(reader)?.sashLeft.read(reader); const originalWidth = sashLeft ?? Math.max(5, this._editors.original.getLayoutInfo().decorationsLeft); - const modifiedWidth = width - originalWidth - (this._options.renderOverviewRuler.read(reader) ? OverviewRulerPart.ENTIRE_DIFF_OVERVIEW_WIDTH : 0); + const modifiedWidth = width - originalWidth - (this._overviewRulerPart.read(reader)?.width ?? 0); const movedBlocksLinesWidth = this.movedBlocksLinesPart.read(reader)?.width.read(reader) ?? 0; const originalWidthWithoutMovedBlockLines = originalWidth - movedBlocksLinesWidth; @@ -385,18 +410,32 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { override getModel(): IDiffEditorModel | null { return this._diffModel.get()?.model ?? null; } - override setModel(model: IDiffEditorModel | null | IDiffEditorViewModel): void { + override setModel(model: IDiffEditorModel | null | IDiffEditorViewModel, tx?: ITransaction): void { if (!model && this._diffModel.get()) { // Transitioning from a model to no-model - this._accessibleDiffViewer.close(); + this._accessibleDiffViewer.get().close(); } - const vm = model ? ('model' in model) ? model : this.createViewModel(model) : undefined; - this._editors.original.setModel(vm ? vm.model.original : null); - this._editors.modified.setModel(vm ? vm.model.modified : null); - transaction(tx => { - this._diffModel.set(vm as (DiffEditorViewModel | undefined), tx); - }); + const vm = model ? ('model' in model) ? { model, shouldDispose: false } : { model: this.createViewModel(model), shouldDispose: true } : undefined; + + if (this._diffModel.get() !== vm?.model) { + subtransaction(tx, tx => { + /** @description DiffEditorWidget.setModel */ + observableFromEvent.batchEventsGlobally(tx, () => { + this._editors.original.setModel(vm ? vm.model.model.original : null); + this._editors.modified.setModel(vm ? vm.model.model.modified : null); + }); + const prevValue = this._diffModel.get(); + const shouldDispose = this._shouldDisposeDiffModel; + + this._shouldDisposeDiffModel = vm?.shouldDispose ?? false; + this._diffModel.set(vm?.model as (DiffEditorViewModel | undefined), tx); + + if (shouldDispose) { + prevValue?.dispose(); + } + }); + } } /** @@ -508,9 +547,9 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { }); } - accessibleDiffViewerNext(): void { this._accessibleDiffViewer.next(); } + accessibleDiffViewerNext(): void { this._accessibleDiffViewer.get().next(); } - accessibleDiffViewerPrev(): void { this._accessibleDiffViewer.prev(); } + accessibleDiffViewerPrev(): void { this._accessibleDiffViewer.get().prev(); } async waitForDiff(): Promise { const diffModel = this._diffModel.get(); @@ -572,53 +611,6 @@ export class DiffEditorWidget extends DelegatingEditor implements IDiffEditor { } } -function translatePosition(posInOriginal: Position, mappings: DetailedLineRangeMapping[]): Range { - const mapping = findLast(mappings, m => m.original.startLineNumber <= posInOriginal.lineNumber); - if (!mapping) { - // No changes before the position - return Range.fromPositions(posInOriginal); - } - - if (mapping.original.endLineNumberExclusive <= posInOriginal.lineNumber) { - const newLineNumber = posInOriginal.lineNumber - mapping.original.endLineNumberExclusive + mapping.modified.endLineNumberExclusive; - return Range.fromPositions(new Position(newLineNumber, posInOriginal.column)); - } - - if (!mapping.innerChanges) { - // Only for legacy algorithm - return Range.fromPositions(new Position(mapping.modified.startLineNumber, 1)); - } - - const innerMapping = findLast(mapping.innerChanges, m => m.originalRange.getStartPosition().isBeforeOrEqual(posInOriginal)); - if (!innerMapping) { - const newLineNumber = posInOriginal.lineNumber - mapping.original.startLineNumber + mapping.modified.startLineNumber; - return Range.fromPositions(new Position(newLineNumber, posInOriginal.column)); - } - - if (innerMapping.originalRange.containsPosition(posInOriginal)) { - return innerMapping.modifiedRange; - } else { - const l = lengthBetweenPositions(innerMapping.originalRange.getEndPosition(), posInOriginal); - return Range.fromPositions(addLength(innerMapping.modifiedRange.getEndPosition(), l)); - } -} - -function lengthBetweenPositions(position1: Position, position2: Position): LengthObj { - if (position1.lineNumber === position2.lineNumber) { - return new LengthObj(0, position2.column - position1.column); - } else { - return new LengthObj(position2.lineNumber - position1.lineNumber, position2.column - 1); - } -} - -function addLength(position: Position, length: LengthObj): Position { - if (length.lineCount === 0) { - return new Position(position.lineNumber, position.column + length.columnCount); - } else { - return new Position(position.lineNumber + length.lineCount, length.columnCount + 1); - } -} - function toLineChanges(state: DiffState): ILineChange[] { return state.mappings.map(x => { const m = x.lineRangeMapping; diff --git a/src/vs/editor/browser/widget/diffEditor/diffProviderFactoryService.ts b/src/vs/editor/browser/widget/diffEditor/diffProviderFactoryService.ts index 3fe7ebb61d4..6f548bbfa5e 100644 --- a/src/vs/editor/browser/widget/diffEditor/diffProviderFactoryService.ts +++ b/src/vs/editor/browser/widget/diffEditor/diffProviderFactoryService.ts @@ -17,6 +17,7 @@ export interface IDocumentDiffProviderOptions { export interface IDiffProviderFactoryService { readonly _serviceBrand: undefined; + // TODO, don't include IDiffEditor createDiffProvider(editor: IDiffEditor, options: IDocumentDiffProviderOptions): IDocumentDiffProvider; } diff --git a/src/vs/editor/browser/widget/diffEditor/hideUnchangedRegionsFeature.ts b/src/vs/editor/browser/widget/diffEditor/hideUnchangedRegionsFeature.ts index 395ecbcd785..138c1eb3cae 100644 --- a/src/vs/editor/browser/widget/diffEditor/hideUnchangedRegionsFeature.ts +++ b/src/vs/editor/browser/widget/diffEditor/hideUnchangedRegionsFeature.ts @@ -11,14 +11,15 @@ import { Event } from 'vs/base/common/event'; import { MarkdownString } from 'vs/base/common/htmlContent'; import { Disposable } from 'vs/base/common/lifecycle'; import { IObservable, IReader, autorun, autorunWithStore, derived, derivedWithStore, observableFromEvent, observableSignalFromEvent, observableValue, transaction } from 'vs/base/common/observable'; +import { derivedDisposable } from 'vs/base/common/observableInternal/derived'; import { ThemeIcon } from 'vs/base/common/themables'; import { isDefined } from 'vs/base/common/types'; -import { ICodeEditor, IViewZone } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { DiffEditorEditors } from 'vs/editor/browser/widget/diffEditor/diffEditorEditors'; import { DiffEditorOptions } from 'vs/editor/browser/widget/diffEditor/diffEditorOptions'; import { DiffEditorViewModel, UnchangedRegion } from 'vs/editor/browser/widget/diffEditor/diffEditorViewModel'; import { OutlineModel } from 'vs/editor/browser/widget/diffEditor/outlineModel'; -import { DisposableCancellationTokenSource, PlaceholderViewZone, ViewZoneOverlayWidget, applyObservableDecorations, applyStyle, applyViewZones } from 'vs/editor/browser/widget/diffEditor/utils'; +import { DisposableCancellationTokenSource, IObservableViewZone, PlaceholderViewZone, ViewZoneOverlayWidget, applyObservableDecorations, applyStyle } from 'vs/editor/browser/widget/diffEditor/utils'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { LineRange } from 'vs/editor/common/core/lineRange'; import { Position } from 'vs/editor/common/core/position'; @@ -29,16 +30,23 @@ import { IModelDecorationOptions, IModelDeltaDecoration, ITextModel } from 'vs/e import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { localize } from 'vs/nls'; +/** + * Make sure to add the view zones to the editor! + */ export class HideUnchangedRegionsFeature extends Disposable { - private _isUpdatingViewZones = false; - public get isUpdatingViewZones(): boolean { return this._isUpdatingViewZones; } - - private readonly _modifiedOutlineSource = derivedWithStore(this, (reader, store) => { + private readonly _modifiedOutlineSource = derivedDisposable(this, reader => { const m = this._editors.modifiedModel.read(reader); - if (!m) { return undefined; } - return store.add(new OutlineSource(this._languageFeaturesService, m)); + return !m ? undefined : new OutlineSource(this._languageFeaturesService, m); }); + public readonly viewZones: IObservable<{ + origViewZones: IObservableViewZone[]; + modViewZones: IObservableViewZone[]; + }>; + + private _isUpdatingHiddenAreas = false; + public get isUpdatingHiddenAreas() { return this._isUpdatingHiddenAreas; } + constructor( private readonly _editors: DiffEditorEditors, private readonly _diffModel: IObservable, @@ -73,13 +81,13 @@ export class HideUnchangedRegionsFeature extends Disposable { const unchangedRegions = this._diffModel.map((m, reader) => m?.diff.read(reader)?.mappings.length === 0 ? [] : m?.unchangedRegions.read(reader) ?? []); - const viewZones = derivedWithStore(this, (reader, store) => { + this.viewZones = derivedWithStore(this, (reader, store) => { /** @description view Zones */ const modifiedOutlineSource = this._modifiedOutlineSource.read(reader); if (!modifiedOutlineSource) { return { origViewZones: [], modViewZones: [] }; } - const origViewZones: IViewZone[] = []; - const modViewZones: IViewZone[] = []; + const origViewZones: IObservableViewZone[] = []; + const modViewZones: IObservableViewZone[] = []; const sideBySide = this._options.renderSideBySide.read(reader); const curUnchangedRegions = unchangedRegions.read(reader); @@ -89,7 +97,7 @@ export class HideUnchangedRegionsFeature extends Disposable { } { - const d = derived(reader => /** @description hiddenOriginalRangeStart */ r.getHiddenOriginalRange(reader).startLineNumber - 1); + const d = derived(this, reader => /** @description hiddenOriginalRangeStart */ r.getHiddenOriginalRange(reader).startLineNumber - 1); const origVz = new PlaceholderViewZone(d, 24); origViewZones.push(origVz); store.add(new CollapsedCodeOverlayWidget( @@ -100,11 +108,11 @@ export class HideUnchangedRegionsFeature extends Disposable { !sideBySide, modifiedOutlineSource, l => this._diffModel.get()!.ensureModifiedLineIsVisible(l, undefined), - this._options + this._options, )); } { - const d = derived(reader => /** @description hiddenModifiedRangeStart */ r.getHiddenModifiedRange(reader).startLineNumber - 1); + const d = derived(this, reader => /** @description hiddenModifiedRangeStart */ r.getHiddenModifiedRange(reader).startLineNumber - 1); const modViewZone = new PlaceholderViewZone(d, 24); modViewZones.push(modViewZone); store.add(new CollapsedCodeOverlayWidget( @@ -114,7 +122,8 @@ export class HideUnchangedRegionsFeature extends Disposable { r.modifiedUnchangedRange, false, modifiedOutlineSource, - l => this._diffModel.get()!.ensureModifiedLineIsVisible(l, undefined), this._options + l => this._diffModel.get()!.ensureModifiedLineIsVisible(l, undefined), + this._options, )); } } @@ -136,7 +145,7 @@ export class HideUnchangedRegionsFeature extends Disposable { zIndex: 10001, }; - this._register(applyObservableDecorations(this._editors.original, derived(reader => { + this._register(applyObservableDecorations(this._editors.original, derived(this, reader => { /** @description decorations */ const curUnchangedRegions = unchangedRegions.read(reader); const result = curUnchangedRegions.map(r => ({ @@ -154,7 +163,7 @@ export class HideUnchangedRegionsFeature extends Disposable { return result; }))); - this._register(applyObservableDecorations(this._editors.modified, derived(reader => { + this._register(applyObservableDecorations(this._editors.modified, derived(this, reader => { /** @description decorations */ const curUnchangedRegions = unchangedRegions.read(reader); const result = curUnchangedRegions.map(r => ({ @@ -172,14 +181,16 @@ export class HideUnchangedRegionsFeature extends Disposable { return result; }))); - this._register(applyViewZones(this._editors.original, viewZones.map(v => v.origViewZones), v => this._isUpdatingViewZones = v)); - this._register(applyViewZones(this._editors.modified, viewZones.map(v => v.modViewZones), v => this._isUpdatingViewZones = v)); - this._register(autorun((reader) => { /** @description update folded unchanged regions */ const curUnchangedRegions = unchangedRegions.read(reader); - this._editors.original.setHiddenAreas(curUnchangedRegions.map(r => r.getHiddenOriginalRange(reader).toInclusiveRange()).filter(isDefined)); - this._editors.modified.setHiddenAreas(curUnchangedRegions.map(r => r.getHiddenModifiedRange(reader).toInclusiveRange()).filter(isDefined)); + this._isUpdatingHiddenAreas = true; + try { + this._editors.original.setHiddenAreas(curUnchangedRegions.map(r => r.getHiddenOriginalRange(reader).toInclusiveRange()).filter(isDefined)); + this._editors.modified.setHiddenAreas(curUnchangedRegions.map(r => r.getHiddenModifiedRange(reader).toInclusiveRange()).filter(isDefined)); + } finally { + this._isUpdatingHiddenAreas = false; + } })); this._register(this._editors.modified.onMouseUp(event => { @@ -210,51 +221,6 @@ export class HideUnchangedRegionsFeature extends Disposable { } } -class OutlineSource extends Disposable { - private readonly _currentModel = observableValue(this, undefined); - - constructor( - @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, - private readonly _textModel: ITextModel, - ) { - super(); - - const documentSymbolProviderChanged = observableSignalFromEvent( - 'documentSymbolProvider.onDidChange', - this._languageFeaturesService.documentSymbolProvider.onDidChange - ); - - const textModelChanged = observableSignalFromEvent( - '_textModel.onDidChangeContent', - Event.debounce(e => this._textModel.onDidChangeContent(e), () => undefined, 100) - ); - - this._register(autorunWithStore(async (reader, store) => { - documentSymbolProviderChanged.read(reader); - textModelChanged.read(reader); - - const src = store.add(new DisposableCancellationTokenSource()); - const model = await OutlineModel.create( - this._languageFeaturesService.documentSymbolProvider, - this._textModel, - src.token, - ); - if (store.isDisposed) { return; } - - this._currentModel.set(model, undefined); - })); - } - - public getBreadcrumbItems(startRange: LineRange, reader: IReader): { name: string; kind: SymbolKind; startLineNumber: number }[] { - const m = this._currentModel.read(reader); - if (!m) { return []; } - const symbols = m.asListOfDocumentSymbols() - .filter(s => startRange.contains(s.range.startLineNumber) && !startRange.contains(s.range.endLineNumber)); - symbols.sort(reverseOrder(compareBy(s => s.range.endLineNumber - s.range.startLineNumber, numberComparator))); - return symbols.map(s => ({ name: s.name, kind: s.kind, startLineNumber: s.range.startLineNumber })); - } -} - class CollapsedCodeOverlayWidget extends ViewZoneOverlayWidget { private readonly _nodes = h('div.diff-hidden-lines', [ h('div.top@top', { title: localize('diff.hiddenLines.top', 'Click or drag to show more above') }), @@ -273,7 +239,7 @@ class CollapsedCodeOverlayWidget extends ViewZoneOverlayWidget { _viewZone: PlaceholderViewZone, private readonly _unchangedRegion: UnchangedRegion, private readonly _unchangedRegionRange: LineRange, - private readonly hide: boolean, + private readonly _hide: boolean, private readonly _modifiedOutlineSource: OutlineSource, private readonly _revealModifiedHiddenLine: (lineNumber: number) => void, private readonly _options: DiffEditorOptions, @@ -286,7 +252,7 @@ class CollapsedCodeOverlayWidget extends ViewZoneOverlayWidget { this._editor.getLayoutInfo() ); - if (!this.hide) { + if (!this._hide) { this._register(applyStyle(this._nodes.first, { width: layoutInfo.map((l) => l.contentLeft) })); } else { reset(this._nodes.first); @@ -376,7 +342,7 @@ class CollapsedCodeOverlayWidget extends ViewZoneOverlayWidget { /** @description update labels */ const children: HTMLElement[] = []; - if (!this.hide) { + if (!this._hide) { const lineCount = _unchangedRegion.getHiddenModifiedRange(reader).length; const linesHiddenText = localize('hiddenLines', '{0} hidden lines', lineCount); const span = $('span', { title: localize('diff.hiddenLines.expandAll', 'Double click to unfold') }, linesHiddenText); @@ -419,3 +385,48 @@ class CollapsedCodeOverlayWidget extends ViewZoneOverlayWidget { })); } } + +class OutlineSource extends Disposable { + private readonly _currentModel = observableValue(this, undefined); + + constructor( + @ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService, + private readonly _textModel: ITextModel, + ) { + super(); + + const documentSymbolProviderChanged = observableSignalFromEvent( + 'documentSymbolProvider.onDidChange', + this._languageFeaturesService.documentSymbolProvider.onDidChange + ); + + const textModelChanged = observableSignalFromEvent( + '_textModel.onDidChangeContent', + Event.debounce(e => this._textModel.onDidChangeContent(e), () => undefined, 100) + ); + + this._register(autorunWithStore(async (reader, store) => { + documentSymbolProviderChanged.read(reader); + textModelChanged.read(reader); + + const src = store.add(new DisposableCancellationTokenSource()); + const model = await OutlineModel.create( + this._languageFeaturesService.documentSymbolProvider, + this._textModel, + src.token, + ); + if (store.isDisposed) { return; } + + this._currentModel.set(model, undefined); + })); + } + + public getBreadcrumbItems(startRange: LineRange, reader: IReader): { name: string; kind: SymbolKind; startLineNumber: number }[] { + const m = this._currentModel.read(reader); + if (!m) { return []; } + const symbols = m.asListOfDocumentSymbols() + .filter(s => startRange.contains(s.range.startLineNumber) && !startRange.contains(s.range.endLineNumber)); + symbols.sort(reverseOrder(compareBy(s => s.range.endLineNumber - s.range.startLineNumber, numberComparator))); + return symbols.map(s => ({ name: s.name, kind: s.kind, startLineNumber: s.range.startLineNumber })); + } +} diff --git a/src/vs/editor/browser/widget/diffEditor/lineAlignment.ts b/src/vs/editor/browser/widget/diffEditor/lineAlignment.ts index 698734aeba0..d86a9720f4f 100644 --- a/src/vs/editor/browser/widget/diffEditor/lineAlignment.ts +++ b/src/vs/editor/browser/widget/diffEditor/lineAlignment.ts @@ -7,13 +7,11 @@ import { $ } from 'vs/base/browser/dom'; import { ArrayQueue } from 'vs/base/common/arrays'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Codicon } from 'vs/base/common/codicons'; -import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; -import { IObservable, autorun, autorunWithStore, derived, observableFromEvent, observableValue } from 'vs/base/common/observable'; +import { Disposable, DisposableStore } from 'vs/base/common/lifecycle'; +import { IObservable, autorun, derived, observableFromEvent, observableValue } from 'vs/base/common/observable'; import { ThemeIcon } from 'vs/base/common/themables'; import { assertIsDefined } from 'vs/base/common/types'; import { applyFontInfo } from 'vs/editor/browser/config/domFontInfo'; -import { IViewZone } from 'vs/editor/browser/editorBrowser'; -import { StableEditorScrollState } from 'vs/editor/browser/stableEditorScroll'; import { CodeEditorWidget } from 'vs/editor/browser/widget/codeEditorWidget'; import { diffDeleteDecoration, diffRemoveIcon } from 'vs/editor/browser/widget/diffEditor/decorations'; import { DiffEditorEditors } from 'vs/editor/browser/widget/diffEditor/diffEditorEditors'; @@ -21,7 +19,7 @@ import { DiffEditorViewModel, DiffMapping } from 'vs/editor/browser/widget/diffE import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; import { InlineDiffDeletedCodeMargin } from 'vs/editor/browser/widget/diffEditor/inlineDiffDeletedCodeMargin'; import { LineSource, RenderOptions, renderLines } from 'vs/editor/browser/widget/diffEditor/renderLines'; -import { animatedObservable, joinCombine } from 'vs/editor/browser/widget/diffEditor/utils'; +import { IObservableViewZone, animatedObservable, joinCombine } from 'vs/editor/browser/widget/diffEditor/utils'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { LineRange } from 'vs/editor/common/core/lineRange'; import { Position } from 'vs/editor/common/core/position'; @@ -37,6 +35,8 @@ import { DiffEditorOptions } from './diffEditorOptions'; * Ensures both editors have the same height by aligning unchanged lines. * In inline view mode, inserts viewzones to show deleted code from the original text model in the modified code editor. * Synchronizes scrolling. + * + * Make sure to add the view zones! */ export class ViewZoneManager extends Disposable { private readonly _originalTopPadding = observableValue(this, 0); @@ -49,6 +49,8 @@ export class ViewZoneManager extends Disposable { private readonly _modifiedScrollOffset = observableValue(this, 0); private readonly _modifiedScrollOffsetAnimated = animatedObservable(this._targetWindow, this._modifiedScrollOffset, this._store); + public readonly viewZones: IObservable<{ orig: IObservableViewZone[]; mod: IObservableViewZone[] }>; + constructor( private readonly _targetWindow: Window, private readonly _editors: DiffEditorEditors, @@ -56,20 +58,21 @@ export class ViewZoneManager extends Disposable { private readonly _options: DiffEditorOptions, private readonly _diffEditorWidget: DiffEditorWidget, private readonly _canIgnoreViewZoneUpdateEvent: () => boolean, + private readonly _origViewZonesToIgnore: Set, + private readonly _modViewZonesToIgnore: Set, @IClipboardService private readonly _clipboardService: IClipboardService, @IContextMenuService private readonly _contextMenuService: IContextMenuService, ) { super(); - let isChangingViewZones = false; - const state = observableValue('state', 0); + const state = observableValue('invalidateAlignmentsState', 0); const updateImmediately = this._register(new RunOnceScheduler(() => { state.set(state.get() + 1, undefined); }, 0)); - this._register(this._editors.original.onDidChangeViewZones((_args) => { if (!isChangingViewZones && !this._canIgnoreViewZoneUpdateEvent()) { updateImmediately.schedule(); } })); - this._register(this._editors.modified.onDidChangeViewZones((_args) => { if (!isChangingViewZones && !this._canIgnoreViewZoneUpdateEvent()) { updateImmediately.schedule(); } })); + this._register(this._editors.original.onDidChangeViewZones((_args) => { if (!this._canIgnoreViewZoneUpdateEvent()) { updateImmediately.schedule(); } })); + this._register(this._editors.modified.onDidChangeViewZones((_args) => { if (!this._canIgnoreViewZoneUpdateEvent()) { updateImmediately.schedule(); } })); this._register(this._editors.original.onDidChangeConfiguration((args) => { if (args.hasChanged(EditorOption.wrappingInfo) || args.hasChanged(EditorOption.lineHeight)) { updateImmediately.schedule(); } })); @@ -81,9 +84,6 @@ export class ViewZoneManager extends Disposable { m ? observableFromEvent(m.model.original.onDidChangeTokens, () => m.model.original.tokenization.backgroundTokenizationState === BackgroundTokenizationState.Completed) : undefined ).map((m, reader) => m?.read(reader)); - const alignmentViewZoneIdsOrig = new Set(); - const alignmentViewZoneIdsMod = new Set(); - const alignments = derived((reader) => { /** @description alignments */ const diffModel = this._diffModel.read(reader); @@ -96,14 +96,14 @@ export class ViewZoneManager extends Disposable { this._editors.original, this._editors.modified, diff.mappings, - alignmentViewZoneIdsOrig, - alignmentViewZoneIdsMod, + this._origViewZonesToIgnore, + this._modViewZonesToIgnore, innerHunkAlignment ); }); const alignmentsSyncedMovedText = derived((reader) => { - /** @description alignments */ + /** @description alignmentsSyncedMovedText */ const syncedMovedText = this._diffModel.read(reader)?.movedTextToCompare.read(reader); if (!syncedMovedText) { return null; } state.read(reader); @@ -113,8 +113,8 @@ export class ViewZoneManager extends Disposable { this._editors.original, this._editors.modified, mappings, - alignmentViewZoneIdsOrig, - alignmentViewZoneIdsMod, + this._origViewZonesToIgnore, + this._modViewZonesToIgnore, true ); }); @@ -126,14 +126,13 @@ export class ViewZoneManager extends Disposable { } const alignmentViewZonesDisposables = this._register(new DisposableStore()); - const alignmentViewZones = derived<{ orig: IViewZoneWithZoneId[]; mod: IViewZoneWithZoneId[] }>((reader) => { - /** @description alignment viewzones */ + this.viewZones = derived<{ orig: IObservableViewZone[]; mod: IObservableViewZone[] }>(this, (reader) => { alignmentViewZonesDisposables.clear(); const alignmentsVal = alignments.read(reader) || []; - const origViewZones: IViewZoneWithZoneId[] = []; - const modViewZones: IViewZoneWithZoneId[] = []; + const origViewZones: IObservableViewZone[] = []; + const modViewZones: IObservableViewZone[] = []; const modifiedTopPaddingVal = this._modifiedTopPadding.read(reader); if (modifiedTopPaddingVal > 0) { @@ -340,50 +339,6 @@ export class ViewZoneManager extends Disposable { return { orig: origViewZones, mod: modViewZones }; }); - this._register(autorunWithStore((reader) => { - /** @description alignment viewzones */ - const scrollState = StableEditorScrollState.capture(this._editors.modified); - - const alignmentViewZones_ = alignmentViewZones.read(reader); - isChangingViewZones = true; - this._editors.original.changeViewZones((aOrig) => { - for (const id of alignmentViewZoneIdsOrig) { aOrig.removeZone(id); } - alignmentViewZoneIdsOrig.clear(); - for (const z of alignmentViewZones_.orig) { - const id = aOrig.addZone(z); - if (z.setZoneId) { - z.setZoneId(id); - } - alignmentViewZoneIdsOrig.add(id); - } - }); - this._editors.modified.changeViewZones(aMod => { - for (const id of alignmentViewZoneIdsMod) { aMod.removeZone(id); } - alignmentViewZoneIdsMod.clear(); - for (const z of alignmentViewZones_.mod) { - const id = aMod.addZone(z); - if (z.setZoneId) { - z.setZoneId(id); - } - alignmentViewZoneIdsMod.add(id); - } - }); - isChangingViewZones = false; - - scrollState.restore(this._editors.modified); - })); - - this._register(toDisposable(() => { - this._editors.original.changeViewZones((a) => { - for (const id of alignmentViewZoneIdsOrig) { a.removeZone(id); } - alignmentViewZoneIdsOrig.clear(); - }); - this._editors.modified.changeViewZones((a) => { - for (const id of alignmentViewZoneIdsMod) { a.removeZone(id); } - alignmentViewZoneIdsMod.clear(); - }); - })); - let ignoreChange = false; this._register(this._editors.original.onDidScrollChange(e => { if (e.scrollLeftChanged && !ignoreChange) { @@ -400,8 +355,8 @@ export class ViewZoneManager extends Disposable { } })); - this._originalScrollTop = observableFromEvent(this._editors.original.onDidScrollChange, () => this._editors.original.getScrollTop()); - this._modifiedScrollTop = observableFromEvent(this._editors.modified.onDidScrollChange, () => this._editors.modified.getScrollTop()); + this._originalScrollTop = observableFromEvent(this._editors.original.onDidScrollChange, () => /** @description original.getScrollTop */ this._editors.original.getScrollTop()); + this._modifiedScrollTop = observableFromEvent(this._editors.modified.onDidScrollChange, () => /** @description modified.getScrollTop */ this._editors.modified.getScrollTop()); // origExtraHeight + origOffset - origScrollTop = modExtraHeight + modOffset - modScrollTop @@ -465,11 +420,6 @@ export class ViewZoneManager extends Disposable { } } -interface IViewZoneWithZoneId extends IViewZone { - // Tells a view zone its id. - setZoneId?(zoneId: string): void; -} - interface ILineRangeAlignment { originalRange: LineRange; modifiedRange: LineRange; diff --git a/src/vs/editor/browser/widget/diffEditor/movedBlocksLines.ts b/src/vs/editor/browser/widget/diffEditor/movedBlocksLines.ts index edaf0c6e099..618fe3d40df 100644 --- a/src/vs/editor/browser/widget/diffEditor/movedBlocksLines.ts +++ b/src/vs/editor/browser/widget/diffEditor/movedBlocksLines.ts @@ -72,8 +72,8 @@ export class MovedBlocksLinesPart extends Disposable { })); }); - this._register(applyViewZones(this._editors.original, movedBlockViewZones.map(zones => zones.map(z => z.original)))); - this._register(applyViewZones(this._editors.modified, movedBlockViewZones.map(zones => zones.map(z => z.modified)))); + this._register(applyViewZones(this._editors.original, movedBlockViewZones.map(zones => /** @description movedBlockViewZones.original */ zones.map(z => z.original)))); + this._register(applyViewZones(this._editors.modified, movedBlockViewZones.map(zones => /** @description movedBlockViewZones.modified */ zones.map(z => z.modified)))); this._register(autorunWithStore((reader, store) => { const blocks = movedBlockViewZones.read(reader); @@ -104,6 +104,7 @@ export class MovedBlocksLinesPart extends Disposable { return true; } }, reader => { + /** @description MovedBlocksLines.setActiveMovedTextFromCursor */ originalHasFocus.read(reader); modifiedHasFocus.read(reader); @@ -137,7 +138,7 @@ export class MovedBlocksLinesPart extends Disposable { private readonly _modifiedViewZonesChangedSignal = observableSignalFromEvent('modified.onDidChangeViewZones', this._editors.modified.onDidChangeViewZones); private readonly _originalViewZonesChangedSignal = observableSignalFromEvent('original.onDidChangeViewZones', this._editors.original.onDidChangeViewZones); - private readonly _state = derivedWithStore((reader, store) => { + private readonly _state = derivedWithStore(this, (reader, store) => { /** @description state */ this._element.replaceChildren(); diff --git a/src/vs/editor/browser/widget/diffEditor/overviewRulerPart.ts b/src/vs/editor/browser/widget/diffEditor/overviewRulerPart.ts index 5b9ad1569b9..61411aec1f5 100644 --- a/src/vs/editor/browser/widget/diffEditor/overviewRulerPart.ts +++ b/src/vs/editor/browser/widget/diffEditor/overviewRulerPart.ts @@ -20,11 +20,11 @@ import { Position } from 'vs/editor/common/core/position'; import { OverviewRulerZone } from 'vs/editor/common/viewModel/overviewZoneManager'; import { defaultInsertColor, defaultRemoveColor, diffInserted, diffOverviewRulerInserted, diffOverviewRulerRemoved, diffRemoved } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService } from 'vs/platform/theme/common/themeService'; -import { DiffEditorOptions } from './diffEditorOptions'; export class OverviewRulerPart extends Disposable { - public static readonly ONE_OVERVIEW_WIDTH = 15; + private static readonly ONE_OVERVIEW_WIDTH = 15; public static readonly ENTIRE_DIFF_OVERVIEW_WIDTH = OverviewRulerPart.ONE_OVERVIEW_WIDTH * 2; + public readonly width = OverviewRulerPart.ENTIRE_DIFF_OVERVIEW_WIDTH; constructor( private readonly _editors: DiffEditorEditors, @@ -33,7 +33,6 @@ export class OverviewRulerPart extends Disposable { private readonly _rootWidth: IObservable, private readonly _rootHeight: IObservable, private readonly _modifiedEditorLayoutInfo: IObservable, - public readonly _options: DiffEditorOptions, @IThemeService private readonly _themeService: IThemeService, ) { super(); @@ -48,134 +47,124 @@ export class OverviewRulerPart extends Disposable { return { insertColor, removeColor }; }); - const scrollTopObservable = observableFromEvent(this._editors.modified.onDidScrollChange, () => this._editors.modified.getScrollTop()); - const scrollHeightObservable = observableFromEvent(this._editors.modified.onDidScrollChange, () => this._editors.modified.getScrollHeight()); + const viewportDomElement = createFastDomNode(document.createElement('div')); + viewportDomElement.setClassName('diffViewport'); + viewportDomElement.setPosition('absolute'); + + const diffOverviewRoot = h('div.diffOverview', { + style: { position: 'absolute', top: '0px', width: OverviewRulerPart.ENTIRE_DIFF_OVERVIEW_WIDTH + 'px' } + }).root; + this._register(appendRemoveOnDispose(diffOverviewRoot, viewportDomElement.domNode)); + this._register(addStandardDisposableListener(diffOverviewRoot, EventType.POINTER_DOWN, (e) => { + this._editors.modified.delegateVerticalScrollbarPointerDown(e); + })); + this._register(addDisposableListener(diffOverviewRoot, EventType.MOUSE_WHEEL, (e: IMouseWheelEvent) => { + this._editors.modified.delegateScrollFromMouseWheelEvent(e); + }, { passive: false })); + this._register(appendRemoveOnDispose(this._rootElement, diffOverviewRoot)); this._register(autorunWithStore((reader, store) => { - /** @description create diff editor overview ruler if enabled */ - if (!this._options.renderOverviewRuler.read(reader)) { + /** @description recreate overview rules when model changes */ + const m = this._diffModel.read(reader); + + const originalOverviewRuler = this._editors.original.createOverviewRuler('original diffOverviewRuler'); + if (originalOverviewRuler) { + store.add(originalOverviewRuler); + store.add(appendRemoveOnDispose(diffOverviewRoot, originalOverviewRuler.getDomNode())); + } + + const modifiedOverviewRuler = this._editors.modified.createOverviewRuler('modified diffOverviewRuler'); + if (modifiedOverviewRuler) { + store.add(modifiedOverviewRuler); + store.add(appendRemoveOnDispose(diffOverviewRoot, modifiedOverviewRuler.getDomNode())); + } + + if (!originalOverviewRuler || !modifiedOverviewRuler) { + // probably no model return; } - const viewportDomElement = createFastDomNode(document.createElement('div')); - viewportDomElement.setClassName('diffViewport'); - viewportDomElement.setPosition('absolute'); + const origViewZonesChanged = observableSignalFromEvent('viewZoneChanged', this._editors.original.onDidChangeViewZones); + const modViewZonesChanged = observableSignalFromEvent('viewZoneChanged', this._editors.modified.onDidChangeViewZones); + const origHiddenRangesChanged = observableSignalFromEvent('hiddenRangesChanged', this._editors.original.onDidChangeHiddenAreas); + const modHiddenRangesChanged = observableSignalFromEvent('hiddenRangesChanged', this._editors.modified.onDidChangeHiddenAreas); - const diffOverviewRoot = h('div.diffOverview', { - style: { position: 'absolute', top: '0px', width: OverviewRulerPart.ENTIRE_DIFF_OVERVIEW_WIDTH + 'px' } - }).root; - store.add(appendRemoveOnDispose(diffOverviewRoot, viewportDomElement.domNode)); - store.add(addStandardDisposableListener(diffOverviewRoot, EventType.POINTER_DOWN, (e) => { - this._editors.modified.delegateVerticalScrollbarPointerDown(e); + store.add(autorun(reader => { + /** @description set overview ruler zones */ + origViewZonesChanged.read(reader); + modViewZonesChanged.read(reader); + origHiddenRangesChanged.read(reader); + modHiddenRangesChanged.read(reader); + + const colors = currentColors.read(reader); + const diff = m?.diff.read(reader)?.mappings; + + function createZones(ranges: LineRange[], color: Color, editor: CodeEditorWidget) { + const vm = editor._getViewModel(); + if (!vm) { + return []; + } + return ranges + .filter(d => d.length > 0) + .map(r => { + const start = vm.coordinatesConverter.convertModelPositionToViewPosition(new Position(r.startLineNumber, 1)); + const end = vm.coordinatesConverter.convertModelPositionToViewPosition(new Position(r.endLineNumberExclusive, 1)); + // By computing the lineCount, we won't ask the view model later for the bottom vertical position. + // (The view model will take into account the alignment viewzones, which will give + // modifications and deletetions always the same height.) + const lineCount = end.lineNumber - start.lineNumber; + return new OverviewRulerZone(start.lineNumber, end.lineNumber, lineCount, color.toString()); + }); + } + + const originalZones = createZones((diff || []).map(d => d.lineRangeMapping.original), colors.removeColor, this._editors.original); + const modifiedZones = createZones((diff || []).map(d => d.lineRangeMapping.modified), colors.insertColor, this._editors.modified); + originalOverviewRuler?.setZones(originalZones); + modifiedOverviewRuler?.setZones(modifiedZones); })); - store.add(addDisposableListener(diffOverviewRoot, EventType.MOUSE_WHEEL, (e: IMouseWheelEvent) => { - this._editors.modified.delegateScrollFromMouseWheelEvent(e); - }, { passive: false })); - store.add(appendRemoveOnDispose(this._rootElement, diffOverviewRoot)); - store.add(autorunWithStore((reader, store) => { - /** @description recreate overview rules when model changes */ - const m = this._diffModel.read(reader); + store.add(autorun(reader => { + /** @description layout overview ruler */ + const height = this._rootHeight.read(reader); + const width = this._rootWidth.read(reader); + const layoutInfo = this._modifiedEditorLayoutInfo.read(reader); + if (layoutInfo) { + const freeSpace = OverviewRulerPart.ENTIRE_DIFF_OVERVIEW_WIDTH - 2 * OverviewRulerPart.ONE_OVERVIEW_WIDTH; + originalOverviewRuler.setLayout({ + top: 0, + height: height, + right: freeSpace + OverviewRulerPart.ONE_OVERVIEW_WIDTH, + width: OverviewRulerPart.ONE_OVERVIEW_WIDTH, + }); + modifiedOverviewRuler.setLayout({ + top: 0, + height: height, + right: 0, + width: OverviewRulerPart.ONE_OVERVIEW_WIDTH, + }); + const scrollTop = this._editors.modifiedScrollTop.read(reader); + const scrollHeight = this._editors.modifiedScrollHeight.read(reader); - const originalOverviewRuler = this._editors.original.createOverviewRuler('original diffOverviewRuler'); - if (originalOverviewRuler) { - store.add(originalOverviewRuler); - store.add(appendRemoveOnDispose(diffOverviewRoot, originalOverviewRuler.getDomNode())); + const scrollBarOptions = this._editors.modified.getOption(EditorOption.scrollbar); + const state = new ScrollbarState( + scrollBarOptions.verticalHasArrows ? scrollBarOptions.arrowSize : 0, + scrollBarOptions.verticalScrollbarSize, + 0, + layoutInfo.height, + scrollHeight, + scrollTop + ); + + viewportDomElement.setTop(state.getSliderPosition()); + viewportDomElement.setHeight(state.getSliderSize()); + } else { + viewportDomElement.setTop(0); + viewportDomElement.setHeight(0); } - const modifiedOverviewRuler = this._editors.modified.createOverviewRuler('modified diffOverviewRuler'); - if (modifiedOverviewRuler) { - store.add(modifiedOverviewRuler); - store.add(appendRemoveOnDispose(diffOverviewRoot, modifiedOverviewRuler.getDomNode())); - } - - if (!originalOverviewRuler || !modifiedOverviewRuler) { - // probably no model - return; - } - - const origViewZonesChanged = observableSignalFromEvent('viewZoneChanged', this._editors.original.onDidChangeViewZones); - const modViewZonesChanged = observableSignalFromEvent('viewZoneChanged', this._editors.modified.onDidChangeViewZones); - const origHiddenRangesChanged = observableSignalFromEvent('hiddenRangesChanged', this._editors.original.onDidChangeHiddenAreas); - const modHiddenRangesChanged = observableSignalFromEvent('hiddenRangesChanged', this._editors.modified.onDidChangeHiddenAreas); - - store.add(autorun(reader => { - /** @description set overview ruler zones */ - origViewZonesChanged.read(reader); - modViewZonesChanged.read(reader); - origHiddenRangesChanged.read(reader); - modHiddenRangesChanged.read(reader); - - const colors = currentColors.read(reader); - const diff = m?.diff.read(reader)?.mappings; - - function createZones(ranges: LineRange[], color: Color, editor: CodeEditorWidget) { - const vm = editor._getViewModel(); - if (!vm) { - return []; - } - return ranges - .filter(d => d.length > 0) - .map(r => { - const start = vm.coordinatesConverter.convertModelPositionToViewPosition(new Position(r.startLineNumber, 1)); - const end = vm.coordinatesConverter.convertModelPositionToViewPosition(new Position(r.endLineNumberExclusive, 1)); - // By computing the lineCount, we won't ask the view model later for the bottom vertical position. - // (The view model will take into account the alignment viewzones, which will give - // modifications and deletetions always the same height.) - const lineCount = end.lineNumber - start.lineNumber; - return new OverviewRulerZone(start.lineNumber, end.lineNumber, lineCount, color.toString()); - }); - } - - const originalZones = createZones((diff || []).map(d => d.lineRangeMapping.original), colors.removeColor, this._editors.original); - const modifiedZones = createZones((diff || []).map(d => d.lineRangeMapping.modified), colors.insertColor, this._editors.modified); - originalOverviewRuler?.setZones(originalZones); - modifiedOverviewRuler?.setZones(modifiedZones); - })); - - store.add(autorun(reader => { - /** @description layout overview ruler */ - const height = this._rootHeight.read(reader); - const width = this._rootWidth.read(reader); - const layoutInfo = this._modifiedEditorLayoutInfo.read(reader); - if (layoutInfo) { - const freeSpace = OverviewRulerPart.ENTIRE_DIFF_OVERVIEW_WIDTH - 2 * OverviewRulerPart.ONE_OVERVIEW_WIDTH; - originalOverviewRuler.setLayout({ - top: 0, - height: height, - right: freeSpace + OverviewRulerPart.ONE_OVERVIEW_WIDTH, - width: OverviewRulerPart.ONE_OVERVIEW_WIDTH, - }); - modifiedOverviewRuler.setLayout({ - top: 0, - height: height, - right: 0, - width: OverviewRulerPart.ONE_OVERVIEW_WIDTH, - }); - const scrollTop = scrollTopObservable.read(reader); - const scrollHeight = scrollHeightObservable.read(reader); - - const scrollBarOptions = this._editors.modified.getOption(EditorOption.scrollbar); - const state = new ScrollbarState( - scrollBarOptions.verticalHasArrows ? scrollBarOptions.arrowSize : 0, - scrollBarOptions.verticalScrollbarSize, - 0, - layoutInfo.height, - scrollHeight, - scrollTop - ); - - viewportDomElement.setTop(state.getSliderPosition()); - viewportDomElement.setHeight(state.getSliderSize()); - } else { - viewportDomElement.setTop(0); - viewportDomElement.setHeight(0); - } - - diffOverviewRoot.style.height = height + 'px'; - diffOverviewRoot.style.left = (width - OverviewRulerPart.ENTIRE_DIFF_OVERVIEW_WIDTH) + 'px'; - viewportDomElement.setWidth(OverviewRulerPart.ENTIRE_DIFF_OVERVIEW_WIDTH); - })); + diffOverviewRoot.style.height = height + 'px'; + diffOverviewRoot.style.left = (width - OverviewRulerPart.ENTIRE_DIFF_OVERVIEW_WIDTH) + 'px'; + viewportDomElement.setWidth(OverviewRulerPart.ENTIRE_DIFF_OVERVIEW_WIDTH); })); })); } diff --git a/src/vs/editor/browser/widget/diffEditor/style.css b/src/vs/editor/browser/widget/diffEditor/style.css index d81f5cf2ebe..295679451c2 100644 --- a/src/vs/editor/browser/widget/diffEditor/style.css +++ b/src/vs/editor/browser/widget/diffEditor/style.css @@ -63,6 +63,7 @@ white-space: nowrap; height: 24px; + box-shadow: inset 0 -6px 5px -7px var(--vscode-diffEditor-unchangedRegionShadow), inset 0 6px 5px -7px var(--vscode-diffEditor-unchangedRegionShadow); } .monaco-editor .diff-hidden-lines .center span.codicon { diff --git a/src/vs/editor/browser/widget/diffEditor/utils.ts b/src/vs/editor/browser/widget/diffEditor/utils.ts index b847df16668..7502c903225 100644 --- a/src/vs/editor/browser/widget/diffEditor/utils.ts +++ b/src/vs/editor/browser/widget/diffEditor/utils.ts @@ -4,14 +4,20 @@ *--------------------------------------------------------------------------------------------*/ import { IDimension } from 'vs/base/browser/dom'; +import { findLast } from 'vs/base/common/arraysFind'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { isHotReloadEnabled, registerHotReloadHandler } from 'vs/base/common/hotReload'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { IObservable, IReader, ISettableObservable, autorun, autorunHandleChanges, autorunOpts, observableFromEvent, observableSignalFromEvent, observableValue, transaction } from 'vs/base/common/observable'; +import { IObservable, IReader, ISettableObservable, autorun, autorunHandleChanges, autorunOpts, autorunWithStore, observableFromEvent, observableSignalFromEvent, observableValue, transaction } from 'vs/base/common/observable'; import { ElementSizeObserver } from 'vs/editor/browser/config/elementSizeObserver'; import { ICodeEditor, IOverlayWidget, IViewZone } from 'vs/editor/browser/editorBrowser'; +import { Position } from 'vs/editor/common/core/position'; +import { Range } from 'vs/editor/common/core/range'; +import { DetailedLineRangeMapping } from 'vs/editor/common/diff/rangeMapping'; import { IModelDeltaDecoration } from 'vs/editor/common/model'; +import { LengthObj } from 'vs/editor/common/model/bracketPairsTextModelPart/bracketPairsTree/length'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ContextKeyValue, RawContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; export function joinCombine(arr1: readonly T[], arr2: readonly T[], keySelector: (val: T) => number, combine: (v1: T, v2: T) => T): readonly T[] { if (arr1.length === 0) { @@ -91,10 +97,10 @@ export class ObservableElementSizeObserver extends Disposable { private readonly elementSizeObserver: ElementSizeObserver; private readonly _width: ISettableObservable; - public get width(): ISettableObservable { return this._width; } + public get width(): IObservable { return this._width; } private readonly _height: ISettableObservable; - public get height(): ISettableObservable { return this._height; } + public get height(): IObservable { return this._height; } constructor(element: HTMLElement | null, dimension: IDimension | undefined) { super(); @@ -208,7 +214,11 @@ export abstract class ViewZoneOverlayWidget extends Disposable { } export interface IObservableViewZone extends IViewZone { + // Causes the view zone to relayout. onChange?: IObservable; + + // Tells a view zone its id. + setZoneId?(zoneId: string): void; } export class PlaceholderViewZone implements IObservableViewZone { @@ -313,37 +323,43 @@ export function observeHotReloadableExports(values: any[], reader: IReader | und } } -export function applyViewZones(editor: ICodeEditor, viewZones: IObservable, setIsUpdating?: (isUpdatingViewZones: boolean) => void): IDisposable { +export function applyViewZones(editor: ICodeEditor, viewZones: IObservable, setIsUpdating?: (isUpdatingViewZones: boolean) => void, zoneIds?: Set): IDisposable { const store = new DisposableStore(); const lastViewZoneIds: string[] = []; - store.add(autorun(reader => { + store.add(autorunWithStore((reader, store) => { /** @description applyViewZones */ const curViewZones = viewZones.read(reader); const viewZonIdsPerViewZone = new Map(); const viewZoneIdPerOnChangeObservable = new Map, string>(); + // Add/remove view zones if (setIsUpdating) { setIsUpdating(true); } editor.changeViewZones(a => { - for (const id of lastViewZoneIds) { a.removeZone(id); } + for (const id of lastViewZoneIds) { a.removeZone(id); zoneIds?.delete(id); } lastViewZoneIds.length = 0; for (const z of curViewZones) { const id = a.addZone(z); + if (z.setZoneId) { + z.setZoneId(id); + } lastViewZoneIds.push(id); + zoneIds?.add(id); viewZonIdsPerViewZone.set(z, id); } }); if (setIsUpdating) { setIsUpdating(false); } + // Layout zone on change store.add(autorunHandleChanges({ createEmptyChangeSummary() { - return [] as string[]; + return { zoneIds: [] as string[] }; }, handleChange(context, changeSummary) { const id = viewZoneIdPerOnChangeObservable.get(context.changedObservable); - if (id !== undefined) { changeSummary.push(id); } + if (id !== undefined) { changeSummary.zoneIds.push(id); } return true; }, }, (reader, changeSummary) => { @@ -355,7 +371,7 @@ export function applyViewZones(editor: ICodeEditor, viewZones: IObservable { for (const id of changeSummary) { a.layoutZone(id); } }); + editor.changeViewZones(a => { for (const id of changeSummary.zoneIds) { a.layoutZone(id); } }); if (setIsUpdating) { setIsUpdating(false); } })); })); @@ -364,6 +380,7 @@ export function applyViewZones(editor: ICodeEditor, viewZones: IObservable { for (const id of lastViewZoneIds) { a.removeZone(id); } }); + zoneIds?.clear(); if (setIsUpdating) { setIsUpdating(false); } } }); @@ -376,3 +393,57 @@ export class DisposableCancellationTokenSource extends CancellationTokenSource { super.dispose(true); } } + +export function translatePosition(posInOriginal: Position, mappings: DetailedLineRangeMapping[]): Range { + const mapping = findLast(mappings, m => m.original.startLineNumber <= posInOriginal.lineNumber); + if (!mapping) { + // No changes before the position + return Range.fromPositions(posInOriginal); + } + + if (mapping.original.endLineNumberExclusive <= posInOriginal.lineNumber) { + const newLineNumber = posInOriginal.lineNumber - mapping.original.endLineNumberExclusive + mapping.modified.endLineNumberExclusive; + return Range.fromPositions(new Position(newLineNumber, posInOriginal.column)); + } + + if (!mapping.innerChanges) { + // Only for legacy algorithm + return Range.fromPositions(new Position(mapping.modified.startLineNumber, 1)); + } + + const innerMapping = findLast(mapping.innerChanges, m => m.originalRange.getStartPosition().isBeforeOrEqual(posInOriginal)); + if (!innerMapping) { + const newLineNumber = posInOriginal.lineNumber - mapping.original.startLineNumber + mapping.modified.startLineNumber; + return Range.fromPositions(new Position(newLineNumber, posInOriginal.column)); + } + + if (innerMapping.originalRange.containsPosition(posInOriginal)) { + return innerMapping.modifiedRange; + } else { + const l = lengthBetweenPositions(innerMapping.originalRange.getEndPosition(), posInOriginal); + return Range.fromPositions(addLength(innerMapping.modifiedRange.getEndPosition(), l)); + } +} + +function lengthBetweenPositions(position1: Position, position2: Position): LengthObj { + if (position1.lineNumber === position2.lineNumber) { + return new LengthObj(0, position2.column - position1.column); + } else { + return new LengthObj(position2.lineNumber - position1.lineNumber, position2.column - 1); + } +} + +function addLength(position: Position, length: LengthObj): Position { + if (length.lineCount === 0) { + return new Position(position.lineNumber, position.column + length.columnCount); + } else { + return new Position(position.lineNumber + length.lineCount, length.columnCount + 1); + } +} + +export function bindContextKey(key: RawContextKey, service: IContextKeyService, computeValue: (reader: IReader) => T): IDisposable { + const boundKey = key.bindTo(service); + return autorunOpts({ debugName: () => `Update ${key.key}` }, reader => { + boundKey.set(computeValue(reader)); + }); +} diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/colors.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/colors.ts new file mode 100644 index 00000000000..5b69dd6c1df --- /dev/null +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/colors.ts @@ -0,0 +1,13 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { registerColor } from 'vs/platform/theme/common/colorRegistry'; + +export const multiDiffEditorHeaderBackground = registerColor( + 'multiDiffEditor.headerBackground', + { dark: '#808080', light: '#b4b4b4', hcDark: '#808080', hcLight: '#b4b4b4', }, + localize('multiDiffEditor.headerBackground', 'The background color of the diff editor\'s header') +); diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts new file mode 100644 index 00000000000..59f792efe20 --- /dev/null +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/diffEditorItemTemplate.ts @@ -0,0 +1,131 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { h } from 'vs/base/browser/dom'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { ISettableObservable } from 'vs/base/common/observable'; +import { globalTransaction } from 'vs/base/common/observableInternal/base'; +import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; +import { IDiffEntry } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; +import { OffsetRange } from 'vs/editor/common/core/offsetRange'; +import { IDiffEditorViewModel } from 'vs/editor/common/editorCommon'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { ILabelService } from 'vs/platform/label/common/label'; +import { IObjectData, IPooledObject } from './objectPool'; + + +export class TemplateData implements IObjectData { + constructor( + public readonly height: ISettableObservable, + public readonly viewModel: IDiffEditorViewModel, + public readonly entry: IDiffEntry + ) { } + + + getId(): unknown { + return this.entry; + } +} + +export class DiffEditorItemTemplate extends Disposable implements IPooledObject { + private _height: number = 500; + private _heightObs: ISettableObservable | undefined = undefined; + + private readonly _elements = h('div', { + style: { + display: 'flex', + flexDirection: 'column', + } + }, [ + h('div', { + style: { + display: 'flex', + flexDirection: 'column', + + flex: '1', + border: '1px solid #4d4d4d', + borderRadius: '5px', + overflow: 'hidden', + margin: '10px 10px 10px 10px', + } + }, [ + h('div', { style: { display: 'flex', alignItems: 'center', padding: '8px 5px', background: 'var(--vscode-multiDiffEditor-headerBackground)', color: 'black' } }, [ + //h('div.expand-button@collapseButton', { style: { margin: '0 5px' } }), + h('div@title', { style: { fontSize: '14px' } }, ['Title'] as any), + ]), + + h('div', { + style: { + flex: '1', + display: 'flex', + flexDirection: 'column', + } + }, [ + h('div@editor', { style: { flex: '1' } }), + ]) + ]) + ]); + + private readonly _editor = this._register(this._instantiationService.createInstance(DiffEditorWidget, this._elements.editor, { + scrollBeyondLastLine: false, + hideUnchangedRegions: { + enabled: true, + }, + scrollbar: { + vertical: 'hidden', + horizontal: 'visible', + handleMouseWheel: false, + }, + renderOverviewRuler: false, + fixedOverflowWidgets: true, + overflowWidgetsDomNode: this._overflowWidgetsDomNode, // TODO + }, {})); + + constructor( + private readonly _container: HTMLElement, + private readonly _overflowWidgetsDomNode: HTMLElement, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @ILabelService private readonly _labelService: ILabelService + ) { + super(); + + // TODO@hediet + /* + const btn = new Button(this._elements.collapseButton, {}); + btn.icon = Codicon.chevronDown; + */ + this._register(this._editor.onDidContentSizeChange(e => { + this._height = e.contentHeight + this._elements.root.clientHeight - this._elements.editor.clientHeight; + globalTransaction(tx => { + this._heightObs?.set(this._height, tx); + }); + })); + + this._container.appendChild(this._elements.root); + } + + public setData(data: TemplateData) { + this._heightObs = data.height; + this._elements.title.innerText = this._labelService.getUriLabel(data.viewModel.model.modified.uri, { relative: true }); // data.entry.title; + globalTransaction(tx => { + this._editor.setModel(data.viewModel, tx); + this._heightObs!.set(this._height, tx); + }); + } + + public hide(): void { + this._elements.root.style.top = `-100000px`; + this._elements.root.style.visibility = 'hidden'; // Some editor parts are still visible + } + + public render(verticalRange: OffsetRange, width: number, editorScroll: number): void { + this._elements.root.style.visibility = 'visible'; + this._elements.root.style.top = `${verticalRange.start}px`; + this._elements.root.style.height = `${verticalRange.length}px`; + this._elements.root.style.width = `${width}px`; + this._elements.root.style.position = 'absolute'; + this._editor.layout({ width, height: verticalRange.length }); + this._editor.getOriginalEditor().setScrollTop(editorScroll); + } +} diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/model.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/model.ts new file mode 100644 index 00000000000..4de8c1a0280 --- /dev/null +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/model.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { ITextModel } from 'vs/editor/common/model'; + +export interface IMultiDocumentDiffEditorModel { + readonly diffs: LazyPromise[]; + readonly onDidChange: Event; +} + +export interface LazyPromise { + request(): Promise; + readonly value: T | undefined; + readonly onHasValueDidChange: Event; +} + +export class ConstLazyPromise implements LazyPromise { + public readonly onHasValueDidChange = Event.None; + + constructor( + private readonly _value: T + ) { } + + public request(): Promise { + return Promise.resolve(this._value); + } + + public get value(): T { + return this._value; + } +} + +export interface IDiffEntry { + readonly title: string; + readonly original: ITextModel | undefined; // undefined if the file was created. + readonly modified: ITextModel | undefined; // undefined if the file was deleted. +} diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts new file mode 100644 index 00000000000..e59063af53b --- /dev/null +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget.ts @@ -0,0 +1,44 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Dimension } from 'vs/base/browser/dom'; +import { Disposable } from 'vs/base/common/lifecycle'; +import { derivedWithStore, observableValue, recomputeInitiallyAndOnChange } from 'vs/base/common/observable'; +import { readHotReloadableExport } from 'vs/editor/browser/widget/diffEditor/utils'; +import { IMultiDocumentDiffEditorModel } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; +import { MultiDiffEditorWidgetImpl } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import './colors'; + +export class MultiDiffEditorWidget extends Disposable { + private readonly _dimension = observableValue(this, undefined); + private readonly _model = observableValue(this, undefined); + + private readonly widgetImpl = derivedWithStore(this, (reader, store) => { + return store.add(this._instantiationService.createInstance(( + readHotReloadableExport(MultiDiffEditorWidgetImpl, reader)), + this._element, + this._dimension, + this._model, + )); + }); + + constructor( + private readonly _element: HTMLElement, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + ) { + super(); + + this._register(recomputeInitiallyAndOnChange(this.widgetImpl)); + } + + public setModel(model: IMultiDocumentDiffEditorModel): void { + this._model.set(model, undefined); + } + + public layout(dimension: Dimension): void { + this._dimension.set(dimension, undefined); + } +} diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts new file mode 100644 index 00000000000..e05b84f4775 --- /dev/null +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidgetImpl.ts @@ -0,0 +1,194 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { Dimension, getWindow, h, scheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; +import { SmoothScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; +import { Disposable, IReference, toDisposable } from 'vs/base/common/lifecycle'; +import { IObservable, IReader, autorun, constObservable, derivedWithStore, observableFromEvent, observableValue } from 'vs/base/common/observable'; +import { Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable'; +import 'vs/css!./style'; +import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; +import { ObservableElementSizeObserver } from 'vs/editor/browser/widget/diffEditor/utils'; +import { IDiffEntry, IMultiDocumentDiffEditorModel, LazyPromise } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; +import { OffsetRange } from 'vs/editor/common/core/offsetRange'; +import { IDiffEditorViewModel } from 'vs/editor/common/editorCommon'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { DiffEditorItemTemplate, TemplateData } from './diffEditorItemTemplate'; +import { ObjectPool } from './objectPool'; + +export class MultiDiffEditorWidgetImpl extends Disposable { + private readonly _elements = h('div', { + style: { + overflowY: 'hidden', + } + }, [ + h('div@content', { + style: { + overflow: 'hidden', + } + }), + h('div.monaco-editor@overflowWidgetsDomNode', { + }), + ]); + + private readonly _sizeObserver = new ObservableElementSizeObserver(this._element, undefined); + private readonly _documentsObs = this._model.map(this, m => !m ? constObservable([]) : observableFromEvent(m.onDidChange, /** @description Documents changed */() => m.diffs)); + private readonly _documents = this._documentsObs.map(this, (m, reader) => m.read(reader)); + + private readonly _objectPool = new ObjectPool((data) => { + const template = this._instantiationService.createInstance(DiffEditorItemTemplate, this._elements.content, this._elements.overflowWidgetsDomNode); + template.setData(data); + return template; + }); + + private readonly _hiddenContainer = document.createElement('div'); + + private readonly _editor = this._register(this._instantiationService.createInstance(DiffEditorWidget, this._hiddenContainer, { + hideUnchangedRegions: { + enabled: true, + }, + }, {})); + + private readonly _viewItems = derivedWithStore(this, + (reader, store) => this._documents.read(reader).map(d => store.add(new DiffEditorItem(this._objectPool, d, this._editor))) + ); + + private readonly _totalHeight = this._viewItems.map(this, (items, reader) => items.reduce((r, i) => r + i.contentHeight.read(reader), 0)); + + private readonly scrollTop: IObservable; + + constructor( + private readonly _element: HTMLElement, + private readonly _dimension: IObservable, + private readonly _model: IObservable, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + ) { + super(); + + this._sizeObserver.setAutomaticLayout(true); + + this._register(autorun((reader) => { + /** @description Update widget dimension */ + const dimension = this._dimension.read(reader); + this._sizeObserver.observe(dimension); + })); + + const scrollable = this._register(new Scrollable({ + forceIntegerValues: false, + scheduleAtNextAnimationFrame: (cb) => scheduleAtNextAnimationFrame(getWindow(_element), cb), + smoothScrollDuration: 100, + })); + + this._elements.content.style.position = 'relative'; + + const scrollableElement = this._register(new SmoothScrollableElement(this._elements.root, { + vertical: ScrollbarVisibility.Auto, + className: 'monaco-component', + }, scrollable)); + + this.scrollTop = observableFromEvent(scrollableElement.onScroll, () => /** @description onScroll */ scrollableElement.getScrollPosition().scrollTop); + + this._register(autorun((reader) => { + /** @description Update scroll dimensions */ + const height = this._sizeObserver.height.read(reader); + this._elements.root.style.height = `${height}px`; + const totalHeight = this._totalHeight.read(reader); + this._elements.content.style.height = `${totalHeight}px`; + scrollableElement.setScrollDimensions({ + width: _element.clientWidth, + height: height, + scrollHeight: totalHeight, + scrollWidth: _element.clientWidth, + }); + })); + + _element.replaceChildren(scrollableElement.getDomNode()); + this._register(toDisposable(() => { + _element.replaceChildren(); + })); + + this._register(this._register(autorun(reader => { + /** @description Render all */ + this.render(reader); + }))); + } + + private render(reader: IReader | undefined) { + const scrollTop = this.scrollTop.read(reader); + let contentScrollOffsetToScrollOffset = 0; + let itemHeightSumBefore = 0; + let itemContentHeightSumBefore = 0; + const viewPortHeight = this._elements.root.clientHeight; + const contentViewPort = OffsetRange.ofStartAndLength(scrollTop, viewPortHeight); + + const width = this._elements.content.clientWidth; + + for (const v of this._viewItems.read(reader)) { + const itemContentHeight = v.contentHeight.read(reader); + const itemHeight = Math.min(itemContentHeight, viewPortHeight); + const itemRange = OffsetRange.ofStartAndLength(itemHeightSumBefore, itemHeight); + const itemContentRange = OffsetRange.ofStartAndLength(itemContentHeightSumBefore, itemContentHeight); + + if (itemContentRange.isBefore(contentViewPort)) { + contentScrollOffsetToScrollOffset -= itemContentHeight - itemHeight; + v.hide(); + } else if (itemContentRange.isAfter(contentViewPort)) { + v.hide(); + } else { + const scroll = Math.max(0, Math.min(contentViewPort.start - itemContentRange.start, itemContentHeight - itemHeight)); + v.render(itemRange, scroll, width); + contentScrollOffsetToScrollOffset -= scroll; + } + + itemHeightSumBefore += itemHeight; + itemContentHeightSumBefore += itemContentHeight; + } + + this._elements.content.style.transform = `translateY(${-(scrollTop + contentScrollOffsetToScrollOffset)}px)`; + } +} + +class DiffEditorItem extends Disposable { + private readonly _height = observableValue(this, 500); + public readonly contentHeight: IObservable = this._height; + private _templateRef: IReference | undefined; + + private _vm: IDiffEditorViewModel | undefined; + + constructor( + private readonly _objectPool: ObjectPool, + private readonly _entry: LazyPromise, + baseDiffEditorWidget: DiffEditorWidget, + ) { + super(); + + this._vm = this._register(baseDiffEditorWidget.createViewModel({ + original: _entry.value!.original!, + modified: _entry.value!.modified!, + })); + } + + override dispose(): void { + this.hide(); + super.dispose(); + } + + public override toString(): string { + return `ViewItem(${this._entry.value!.title})`; + } + + public hide(): void { + this._templateRef?.object.hide(); + this._templateRef?.dispose(); + this._templateRef = undefined; + } + + public render(verticalSpace: OffsetRange, offset: number, width: number): void { + if (!this._templateRef) { + this._templateRef = this._objectPool.getUnusedObj(new TemplateData(this._height, this._vm!, this._entry.value!)); + } + this._templateRef.object.render(verticalSpace, width, offset); + } +} diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/objectPool.ts b/src/vs/editor/browser/widget/multiDiffEditorWidget/objectPool.ts new file mode 100644 index 00000000000..9c66f25208b --- /dev/null +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/objectPool.ts @@ -0,0 +1,50 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import { IDisposable, IReference } from 'vs/base/common/lifecycle'; + +export class ObjectPool> { + private readonly _unused = new Set(); + private readonly _used = new Set(); + private readonly _itemData = new Map(); + + constructor( + private readonly _create: (data: TData) => T, + ) { } + + public getUnusedObj(data: TData): IReference { + let obj: T; + + if (this._unused.size === 0) { + obj = this._create(data); + this._itemData.set(obj, data); + } else { + const values = [...this._unused.values()]; + obj = values.find(obj => this._itemData.get(obj)!.getId() === data.getId()) ?? values[0]; + this._unused.delete(obj); + obj.setData(data); + this._itemData.set(obj, data); + } + this._used.add(obj); + return { + object: obj, + dispose: () => { + this._used.delete(obj); + if (this._unused.size > 5) { + obj.dispose(); + } else { + this._unused.add(obj); + } + } + }; + } +} + +export interface IObjectData { + getId(): unknown; +} + +export interface IPooledObject extends IDisposable { + setData(data: TData): void; +} diff --git a/src/vs/editor/browser/widget/multiDiffEditorWidget/style.css b/src/vs/editor/browser/widget/multiDiffEditorWidget/style.css new file mode 100644 index 00000000000..7206dda8cf6 --- /dev/null +++ b/src/vs/editor/browser/widget/multiDiffEditorWidget/style.css @@ -0,0 +1,8 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-component .expand-button a { + display: block; +} diff --git a/src/vs/editor/common/config/editorConfigurationSchema.ts b/src/vs/editor/common/config/editorConfigurationSchema.ts index 5f18e0ce03f..3b22985f00d 100644 --- a/src/vs/editor/common/config/editorConfigurationSchema.ts +++ b/src/vs/editor/common/config/editorConfigurationSchema.ts @@ -62,19 +62,15 @@ const editorConfiguration: IConfigurationNode = { description: nls.localize('largeFileOptimizations', "Special handling for large files to disable certain memory intensive features.") }, 'editor.wordBasedSuggestions': { - type: 'boolean', - default: true, - description: nls.localize('wordBasedSuggestions', "Controls whether completions should be computed based on words in the document.") - }, - 'editor.wordBasedSuggestionsMode': { - enum: ['currentDocument', 'matchingDocuments', 'allDocuments'], + enum: ['off', 'currentDocument', 'matchingDocuments', 'allDocuments'], default: 'matchingDocuments', enumDescriptions: [ - nls.localize('wordBasedSuggestionsMode.currentDocument', 'Only suggest words from the active document.'), - nls.localize('wordBasedSuggestionsMode.matchingDocuments', 'Suggest words from all open documents of the same language.'), - nls.localize('wordBasedSuggestionsMode.allDocuments', 'Suggest words from all open documents.') + nls.localize('wordBasedSuggestions.off', 'Turn off Word Based Suggestions.'), + nls.localize('wordBasedSuggestions.currentDocument', 'Only suggest words from the active document.'), + nls.localize('wordBasedSuggestions.matchingDocuments', 'Suggest words from all open documents of the same language.'), + nls.localize('wordBasedSuggestions.allDocuments', 'Suggest words from all open documents.') ], - description: nls.localize('wordBasedSuggestionsMode', "Controls from which documents word based completions are computed.") + description: nls.localize('wordBasedSuggestions', "Controls whether completions should be computed based on words in the document and from which documents they are computed.") }, 'editor.semanticHighlighting.enabled': { enum: [true, false, 'configuredByTheme'], diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 628845544ad..0f8718e744c 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -559,14 +559,12 @@ export interface IEditorOptions { selectionHighlight?: boolean; /** * Enable semantic occurrences highlight. - * Defaults to true. + * Defaults to 'singleFile'. + * 'off' disables occurrence highlighting + * 'singleFile' triggers occurrence highlighting in the current document + * 'multiFile' triggers occurrence highlighting across valid open documents */ - occurrencesHighlight?: boolean; - /** - * Enable semantic occurrences highlight. - * Defaults to true. - */ - multiDocumentOccurrencesHighlight?: boolean; + occurrencesHighlight?: 'off' | 'singleFile' | 'multiFile'; /** * Show code lens * Defaults to true. @@ -4911,7 +4909,7 @@ class EditorDropIntoEditor extends BaseEditorOption endExclusive) { throw new BugIndicatingError(`Invalid range: ${this.toString()}`); @@ -114,6 +118,14 @@ export class OffsetRange implements IOffsetRange { return start <= end; } + public isBefore(other: OffsetRange): boolean { + return this.endExclusive <= other.start; + } + + public isAfter(other: OffsetRange): boolean { + return this.start >= other.endExclusive; + } + public slice(arr: T[]): T[] { return arr.slice(this.start, this.endExclusive); } diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index f4485ae9a6d..5136be7b267 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -105,7 +105,7 @@ export interface IDiffEditorModel { modified: ITextModel; } -export interface IDiffEditorViewModel { +export interface IDiffEditorViewModel extends IDisposable { readonly model: IDiffEditorModel; waitForDiff(): Promise; diff --git a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsImpl.ts b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsImpl.ts index 470a16f009c..4df6b83148d 100644 --- a/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsImpl.ts +++ b/src/vs/editor/common/model/bracketPairsTextModelPart/bracketPairsImpl.ts @@ -18,6 +18,8 @@ import { BracketInfo, BracketPairInfo, BracketPairWithMinIndentationInfo, IBrack import { IModelContentChangedEvent, IModelLanguageChangedEvent, IModelOptionsChangedEvent, IModelTokensChangedEvent } from 'vs/editor/common/textModelEvents'; import { LineTokens } from 'vs/editor/common/tokens/lineTokens'; +/* hot-reload:patch-prototype-methods */ + export class BracketPairsTextModelPart extends Disposable implements IBracketPairsTextModelPart { private readonly bracketPairsTree = this._register(new MutableDisposable>()); diff --git a/src/vs/editor/common/model/textModelTokens.ts b/src/vs/editor/common/model/textModelTokens.ts index 0139d8b2055..fdfb6dbe98f 100644 --- a/src/vs/editor/common/model/textModelTokens.ts +++ b/src/vs/editor/common/model/textModelTokens.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IdleDeadline, runWhenIdle } from 'vs/base/common/async'; +import { IdleDeadline, runWhenGlobalIdle } from 'vs/base/common/async'; import { BugIndicatingError, onUnexpectedError } from 'vs/base/common/errors'; import { setTimeout0 } from 'vs/base/common/platform'; import { StopWatch } from 'vs/base/common/stopwatch'; @@ -462,7 +462,7 @@ export class DefaultBackgroundTokenizer implements IBackgroundTokenizer { } this._isScheduled = true; - runWhenIdle((deadline) => { + runWhenGlobalIdle((deadline) => { this._isScheduled = false; this._backgroundTokenizeWithDeadline(deadline); diff --git a/src/vs/editor/common/model/tokenizationTextModelPart.ts b/src/vs/editor/common/model/tokenizationTextModelPart.ts index 54476c26f20..27658b9db64 100644 --- a/src/vs/editor/common/model/tokenizationTextModelPart.ts +++ b/src/vs/editor/common/model/tokenizationTextModelPart.ts @@ -32,6 +32,8 @@ import { LineTokens } from 'vs/editor/common/tokens/lineTokens'; import { SparseMultilineTokens } from 'vs/editor/common/tokens/sparseMultilineTokens'; import { SparseTokensStore } from 'vs/editor/common/tokens/sparseTokensStore'; +/* hot-reload:patch-prototype-methods */ + export class TokenizationTextModelPart extends TextModelPart implements ITokenizationTextModelPart { private readonly _semanticTokens: SparseTokensStore = new SparseTokensStore(this._languageService.languageIdCodec); diff --git a/src/vs/editor/common/standalone/standaloneEnums.ts b/src/vs/editor/common/standalone/standaloneEnums.ts index 42376eef823..44f9fb9b434 100644 --- a/src/vs/editor/common/standalone/standaloneEnums.ts +++ b/src/vs/editor/common/standalone/standaloneEnums.ts @@ -253,75 +253,74 @@ export enum EditorOption { multiCursorModifier = 77, multiCursorPaste = 78, multiCursorLimit = 79, - multiDocumentOccurrencesHighlight = 80, - occurrencesHighlight = 81, - overviewRulerBorder = 82, - overviewRulerLanes = 83, - padding = 84, - pasteAs = 85, - parameterHints = 86, - peekWidgetDefaultFocus = 87, - definitionLinkOpensInPeek = 88, - quickSuggestions = 89, - quickSuggestionsDelay = 90, - readOnly = 91, - readOnlyMessage = 92, - renameOnType = 93, - renderControlCharacters = 94, - renderFinalNewline = 95, - renderLineHighlight = 96, - renderLineHighlightOnlyWhenFocus = 97, - renderValidationDecorations = 98, - renderWhitespace = 99, - revealHorizontalRightPadding = 100, - roundedSelection = 101, - rulers = 102, - scrollbar = 103, - scrollBeyondLastColumn = 104, - scrollBeyondLastLine = 105, - scrollPredominantAxis = 106, - selectionClipboard = 107, - selectionHighlight = 108, - selectOnLineNumbers = 109, - showFoldingControls = 110, - showUnused = 111, - snippetSuggestions = 112, - smartSelect = 113, - smoothScrolling = 114, - stickyScroll = 115, - stickyTabStops = 116, - stopRenderingLineAfter = 117, - suggest = 118, - suggestFontSize = 119, - suggestLineHeight = 120, - suggestOnTriggerCharacters = 121, - suggestSelection = 122, - tabCompletion = 123, - tabIndex = 124, - unicodeHighlighting = 125, - unusualLineTerminators = 126, - useShadowDOM = 127, - useTabStops = 128, - wordBreak = 129, - wordSeparators = 130, - wordWrap = 131, - wordWrapBreakAfterCharacters = 132, - wordWrapBreakBeforeCharacters = 133, - wordWrapColumn = 134, - wordWrapOverride1 = 135, - wordWrapOverride2 = 136, - wrappingIndent = 137, - wrappingStrategy = 138, - showDeprecated = 139, - inlayHints = 140, - editorClassName = 141, - pixelRatio = 142, - tabFocusMode = 143, - layoutInfo = 144, - wrappingInfo = 145, - defaultColorDecorators = 146, - colorDecoratorsActivatedOn = 147, - inlineCompletionsAccessibilityVerbose = 148 + occurrencesHighlight = 80, + overviewRulerBorder = 81, + overviewRulerLanes = 82, + padding = 83, + pasteAs = 84, + parameterHints = 85, + peekWidgetDefaultFocus = 86, + definitionLinkOpensInPeek = 87, + quickSuggestions = 88, + quickSuggestionsDelay = 89, + readOnly = 90, + readOnlyMessage = 91, + renameOnType = 92, + renderControlCharacters = 93, + renderFinalNewline = 94, + renderLineHighlight = 95, + renderLineHighlightOnlyWhenFocus = 96, + renderValidationDecorations = 97, + renderWhitespace = 98, + revealHorizontalRightPadding = 99, + roundedSelection = 100, + rulers = 101, + scrollbar = 102, + scrollBeyondLastColumn = 103, + scrollBeyondLastLine = 104, + scrollPredominantAxis = 105, + selectionClipboard = 106, + selectionHighlight = 107, + selectOnLineNumbers = 108, + showFoldingControls = 109, + showUnused = 110, + snippetSuggestions = 111, + smartSelect = 112, + smoothScrolling = 113, + stickyScroll = 114, + stickyTabStops = 115, + stopRenderingLineAfter = 116, + suggest = 117, + suggestFontSize = 118, + suggestLineHeight = 119, + suggestOnTriggerCharacters = 120, + suggestSelection = 121, + tabCompletion = 122, + tabIndex = 123, + unicodeHighlighting = 124, + unusualLineTerminators = 125, + useShadowDOM = 126, + useTabStops = 127, + wordBreak = 128, + wordSeparators = 129, + wordWrap = 130, + wordWrapBreakAfterCharacters = 131, + wordWrapBreakBeforeCharacters = 132, + wordWrapColumn = 133, + wordWrapOverride1 = 134, + wordWrapOverride2 = 135, + wrappingIndent = 136, + wrappingStrategy = 137, + showDeprecated = 138, + inlayHints = 139, + editorClassName = 140, + pixelRatio = 141, + tabFocusMode = 142, + layoutInfo = 143, + wrappingInfo = 144, + defaultColorDecorators = 145, + colorDecoratorsActivatedOn = 146, + inlineCompletionsAccessibilityVerbose = 147 } /** diff --git a/src/vs/editor/contrib/codeAction/browser/codeActionModel.ts b/src/vs/editor/contrib/codeAction/browser/codeActionModel.ts index c9c02e6f298..f785fd21e02 100644 --- a/src/vs/editor/contrib/codeAction/browser/codeActionModel.ts +++ b/src/vs/editor/contrib/codeAction/browser/codeActionModel.ts @@ -210,7 +210,7 @@ export class CodeActionModel extends Disposable { const actions = createCancelablePromise(async token => { if (this._settingEnabledNearbyQuickfixes() && trigger.trigger.type === CodeActionTriggerType.Invoke && (trigger.trigger.triggerAction === CodeActionTriggerSource.QuickFix || trigger.trigger.filter?.include?.contains(CodeActionKind.QuickFix))) { const codeActionSet = await getCodeActions(this._registry, model, trigger.selection, trigger.trigger, Progress.None, token); - + const allCodeActions = [...codeActionSet.allActions]; if (token.isCancellationRequested) { return emptyCodeActionSet; } @@ -251,6 +251,11 @@ export class CodeActionModel extends Disposable { for (const action of actionsAtMarker.validActions) { action.highlightRange = action.action.isPreferred; } + + if (codeActionSet.allActions.length === 0) { + allCodeActions.push(...actionsAtMarker.allActions); + } + // Already filtered through to only get quickfixes, so no need to filter again. if (Math.abs(currPosition.column - col) < distance) { currentActions.unshift(...actionsAtMarker.validActions); @@ -269,13 +274,17 @@ export class CodeActionModel extends Disposable { return -1; } else if (!a.action.isPreferred && b.action.isPreferred) { return 1; + } else if (a.action.isAI && !b.action.isAI) { + return 1; + } else if (!a.action.isAI && b.action.isAI) { + return -1; } else { return 0; } }); // Only retriggers if actually found quickfix on the same line as cursor - return { validActions: filteredActions, allActions: codeActionSet.allActions, documentation: codeActionSet.documentation, hasAutoFix: codeActionSet.hasAutoFix, dispose: () => { codeActionSet.dispose(); } }; + return { validActions: filteredActions, allActions: allCodeActions, documentation: codeActionSet.documentation, hasAutoFix: codeActionSet.hasAutoFix, dispose: () => { codeActionSet.dispose(); } }; } } } diff --git a/src/vs/editor/contrib/codelens/browser/codeLensCache.ts b/src/vs/editor/contrib/codelens/browser/codeLensCache.ts index 00249b87bea..67fa3b8009c 100644 --- a/src/vs/editor/contrib/codelens/browser/codeLensCache.ts +++ b/src/vs/editor/contrib/codelens/browser/codeLensCache.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { runWhenIdle } from 'vs/base/common/async'; import { Event } from 'vs/base/common/event'; import { LRUCache } from 'vs/base/common/map'; import { Range } from 'vs/editor/common/core/range'; @@ -13,6 +12,8 @@ import { CodeLensModel } from 'vs/editor/contrib/codelens/browser/codelens'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IStorageService, StorageScope, StorageTarget, WillSaveStateReason } from 'vs/platform/storage/common/storage'; +import { mainWindow } from 'vs/base/browser/window'; +import { runWhenWindowIdle } from 'vs/base/browser/dom'; export const ICodeLensCache = createDecorator('ICodeLensCache'); @@ -52,7 +53,7 @@ export class CodeLensCache implements ICodeLensCache { // remove old data const oldkey = 'codelens/cache'; - runWhenIdle(() => storageService.remove(oldkey, StorageScope.WORKSPACE)); + runWhenWindowIdle(mainWindow, () => storageService.remove(oldkey, StorageScope.WORKSPACE)); // restore lens data on start const key = 'codelens/cache2'; diff --git a/src/vs/editor/contrib/inlineCompletions/browser/ghostTextWidget.ts b/src/vs/editor/contrib/inlineCompletions/browser/ghostTextWidget.ts index f690a7142d9..0cfd4f3cb44 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/ghostTextWidget.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/ghostTextWidget.ts @@ -34,7 +34,7 @@ export interface IGhostTextWidgetModel { export class GhostTextWidget extends Disposable { private readonly isDisposed = observableValue(this, false); - private readonly currentTextModel = observableFromEvent(this.editor.onDidChangeModel, () => this.editor.getModel()); + private readonly currentTextModel = observableFromEvent(this.editor.onDidChangeModel, () => /** @description editor.model */ this.editor.getModel()); constructor( private readonly editor: ICodeEditor, diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts index 4f977c5df2e..b86160ee020 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsController.ts @@ -46,7 +46,7 @@ export class InlineCompletionsController extends Disposable { (tx) => this.updateObservables(tx, VersionIdChangeReason.Other), (item) => { transaction(tx => { - /** @description handleSuggestAccepted */ + /** @description InlineCompletionsController.handleSuggestAccepted */ this.updateObservables(tx, VersionIdChangeReason.Other); this.model.get()?.handleSuggestAccepted(item); }); @@ -83,7 +83,7 @@ export class InlineCompletionsController extends Disposable { this._register(new InlineCompletionContextKeys(this._contextKeyService, this.model)); this._register(Event.runAndSubscribe(editor.onDidChangeModel, () => transaction(tx => { - /** @description onDidChangeModel */ + /** @description InlineCompletionsController.onDidChangeModel */ this.model.set(undefined, tx); this.updateObservables(tx, VersionIdChangeReason.Other); @@ -112,12 +112,12 @@ export class InlineCompletionsController extends Disposable { return VersionIdChangeReason.Other; }; this._register(editor.onDidChangeModelContent((e) => transaction(tx => - /** @description onDidChangeModelContent */ + /** @description InlineCompletionsController.onDidChangeModelContent */ this.updateObservables(tx, getReason(e)) ))); this._register(editor.onDidChangeCursorPosition(e => transaction(tx => { - /** @description onDidChangeCursorPosition */ + /** @description InlineCompletionsController.onDidChangeCursorPosition */ this.updateObservables(tx, VersionIdChangeReason.Other); if (e.reason === CursorChangeReason.Explicit || e.source === 'api') { this.model.get()?.stop(tx); @@ -125,7 +125,7 @@ export class InlineCompletionsController extends Disposable { }))); this._register(editor.onDidType(() => transaction(tx => { - /** @description onDidType */ + /** @description InlineCompletionsController.onDidType */ this.updateObservables(tx, VersionIdChangeReason.Other); if (this._enabled.get()) { this.model.get()?.trigger(tx); @@ -159,13 +159,13 @@ export class InlineCompletionsController extends Disposable { return; } transaction(tx => { - /** @description onDidBlurEditorWidget */ + /** @description InlineCompletionsController.onDidBlurEditorWidget */ this.model.get()?.stop(tx); }); })); this._register(autorun(reader => { - /** @description forceRenderingAbove */ + /** @description InlineCompletionsController.forceRenderingAbove */ const state = this.model.read(reader)?.state.read(reader); if (state?.suggestItem) { if (state.ghostText.lineCount >= 2) { @@ -188,7 +188,7 @@ export class InlineCompletionsController extends Disposable { return true; }, }, async reader => { - /** @description play audio cue & read suggestion */ + /** @description InlineCompletionsController.playAudioCueAndReadSuggestion */ this._playAudioCueSignal.read(reader); const model = this.model.read(reader); diff --git a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts index 1a7663d40ee..66edb2ea369 100644 --- a/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts +++ b/src/vs/editor/contrib/inlineCompletions/browser/inlineCompletionsModel.ts @@ -403,27 +403,33 @@ export class InlineCompletionsModel extends Disposable { const partialText = line.substring(0, acceptUntilIndexExclusive); - this._isAcceptingPartially = true; + // Executing the edit might free the completion, so we have to hold a reference on it. + completion.source.addRef(); try { - editor.pushUndoStop(); - editor.executeEdits('inlineSuggestion.accept', [ - EditOperation.replace(Range.fromPositions(position), partialText), - ]); - const length = lengthOfText(partialText); - editor.setPosition(addPositions(position, length)); - } finally { - this._isAcceptingPartially = false; - } + this._isAcceptingPartially = true; + try { + editor.pushUndoStop(); + editor.executeEdits('inlineSuggestion.accept', [ + EditOperation.replace(Range.fromPositions(position), partialText), + ]); + const length = lengthOfText(partialText); + editor.setPosition(addPositions(position, length)); + } finally { + this._isAcceptingPartially = false; + } - if (completion.source.provider.handlePartialAccept) { - const acceptedRange = Range.fromPositions(completion.range.getStartPosition(), addPositions(position, lengthOfText(partialText))); - // This assumes that the inline completion and the model use the same EOL style. - const text = editor.getModel()!.getValueInRange(acceptedRange, EndOfLinePreference.LF); - completion.source.provider.handlePartialAccept( - completion.source.inlineCompletions, - completion.sourceInlineCompletion, - text.length, - ); + if (completion.source.provider.handlePartialAccept) { + const acceptedRange = Range.fromPositions(completion.range.getStartPosition(), addPositions(position, lengthOfText(partialText))); + // This assumes that the inline completion and the model use the same EOL style. + const text = editor.getModel()!.getValueInRange(acceptedRange, EndOfLinePreference.LF); + completion.source.provider.handlePartialAccept( + completion.source.inlineCompletions, + completion.sourceInlineCompletion, + text.length, + ); + } + } finally { + completion.source.removeRef(); } } diff --git a/src/vs/editor/contrib/multicursor/browser/multicursor.ts b/src/vs/editor/contrib/multicursor/browser/multicursor.ts index d99511cbe34..02e7a44e7f5 100644 --- a/src/vs/editor/contrib/multicursor/browser/multicursor.ts +++ b/src/vs/editor/contrib/multicursor/browser/multicursor.ts @@ -1037,7 +1037,8 @@ export class SelectionHighlighter extends Disposable implements IEditorContribut } } - const hasSemanticHighlights = this._languageFeaturesService.documentHighlightProvider.has(model) && this.editor.getOption(EditorOption.occurrencesHighlight); + const occurrenceHighlighting: boolean = this.editor.getOption(EditorOption.occurrencesHighlight) !== 'off'; + const hasSemanticHighlights = this._languageFeaturesService.documentHighlightProvider.has(model) && occurrenceHighlighting; const decorations = matches.map(r => { return { range: r, diff --git a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts index 1b2a3a7b305..c22f1bcaed1 100644 --- a/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts +++ b/src/vs/editor/contrib/stickyScroll/browser/stickyScrollController.ts @@ -98,7 +98,7 @@ export class StickyScrollController extends Disposable implements IEditorContrib } })); this._register(dom.addDisposableListener(stickyScrollDomNode, dom.EventType.CONTEXT_MENU, async (event: MouseEvent) => { - this._onContextMenu(event); + this._onContextMenu(dom.getWindow(stickyScrollDomNode), event); })); this._stickyScrollFocusedContextKey = EditorContextKeys.stickyScrollFocused.bindTo(this._contextKeyService); this._stickyScrollVisibleContextKey = EditorContextKeys.stickyScrollVisible.bindTo(this._contextKeyService); @@ -378,8 +378,8 @@ export class StickyScrollController extends Disposable implements IEditorContrib })); } - private _onContextMenu(e: MouseEvent) { - const event = new StandardMouseEvent(e); + private _onContextMenu(targetWindow: Window, e: MouseEvent) { + const event = new StandardMouseEvent(targetWindow, e); this._contextMenuService.showContextMenu({ menuId: MenuId.StickyScrollContext, diff --git a/src/vs/editor/contrib/suggest/browser/suggestController.ts b/src/vs/editor/contrib/suggest/browser/suggestController.ts index 6450e989bce..d33ce936fe5 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestController.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestController.ts @@ -5,7 +5,6 @@ import { alert } from 'vs/base/browser/ui/aria/aria'; import { isNonEmptyArray } from 'vs/base/common/arrays'; -import { IdleValue } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; @@ -45,6 +44,7 @@ import { ISelectedSuggestion, SuggestWidget } from './suggestWidget'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { basename, extname } from 'vs/base/common/resources'; import { hash } from 'vs/base/common/hash'; +import { WindowIdleValue, getWindow } from 'vs/base/browser/dom'; // sticky suggest widget which doesn't disappear on focus out and such const _sticky = false @@ -111,12 +111,12 @@ export class SuggestController implements IEditorContribution { readonly editor: ICodeEditor; readonly model: SuggestModel; - readonly widget: IdleValue; + readonly widget: WindowIdleValue; - private readonly _alternatives: IdleValue; + private readonly _alternatives: WindowIdleValue; private readonly _lineSuffix = new MutableDisposable(); private readonly _toDispose = new DisposableStore(); - private readonly _overtypingCapturer: IdleValue; + private readonly _overtypingCapturer: WindowIdleValue; private readonly _selectors = new PriorityRegistry(s => s.priority); private readonly _onWillInsertSuggestItem = new Emitter<{ item: CompletionItem }>(); @@ -145,7 +145,7 @@ export class SuggestController implements IEditorContribution { ctxInsertMode.set(editor.getOption(EditorOption.suggest).insertMode); this._toDispose.add(this.model.onDidTrigger(() => ctxInsertMode.set(editor.getOption(EditorOption.suggest).insertMode))); - this.widget = this._toDispose.add(new IdleValue(() => { + this.widget = this._toDispose.add(new WindowIdleValue(getWindow(editor.getDomNode()), () => { const widget = this._instantiationService.createInstance(SuggestWidget, this.editor); @@ -218,11 +218,11 @@ export class SuggestController implements IEditorContribution { })); // Wire up text overtyping capture - this._overtypingCapturer = this._toDispose.add(new IdleValue(() => { + this._overtypingCapturer = this._toDispose.add(new WindowIdleValue(getWindow(editor.getDomNode()), () => { return this._toDispose.add(new OvertypingCapturer(this.editor, this.model)); })); - this._alternatives = this._toDispose.add(new IdleValue(() => { + this._alternatives = this._toDispose.add(new WindowIdleValue(getWindow(editor.getDomNode()), () => { return this._toDispose.add(new SuggestAlternatives(this.editor, this._contextKeyService)); })); diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts index aa78d74936a..37cca65b961 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts @@ -566,12 +566,12 @@ export class SuggestWidget implements IDisposable { this._onDidSelect.resume(); } - this._pendingLayout.value = dom.runAtThisOrScheduleAtNextAnimationFrame(() => { + this._pendingLayout.value = dom.runAtThisOrScheduleAtNextAnimationFrame(dom.getWindow(this.element.domNode), () => { this._pendingLayout.clear(); this._layout(this.element.size); // Reset focus border this._details.widget.domNode.classList.remove('focused'); - }, dom.getWindow(this.element.domNode)); + }); } focusSelected(): void { @@ -710,7 +710,7 @@ export class SuggestWidget implements IDisposable { } showDetails(loading: boolean): void { - this._pendingShowDetails.value = dom.runAtThisOrScheduleAtNextAnimationFrame(() => { + this._pendingShowDetails.value = dom.runAtThisOrScheduleAtNextAnimationFrame(dom.getWindow(this.element.domNode), () => { this._pendingShowDetails.clear(); this._details.show(); if (loading) { @@ -721,7 +721,7 @@ export class SuggestWidget implements IDisposable { this._positionDetails(); this.editor.focus(); this.element.domNode.classList.add('shows-details'); - }, dom.getWindow(this.element.domNode)); + }); } toggleExplainMode(): void { diff --git a/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts b/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts index 75cf64a446b..6bfb4c0e186 100644 --- a/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts +++ b/src/vs/editor/contrib/wordHighlighter/browser/wordHighlighter.ts @@ -256,8 +256,7 @@ class WordHighlighter { private readonly editor: IActiveCodeEditor; private readonly providers: LanguageFeatureRegistry; private readonly multiDocumentProviders: LanguageFeatureRegistry; - private occurrencesHighlight: boolean; - private multiDocumentOccurrencesHighlight: boolean; + private occurrencesHighlight: string; private readonly model: ITextModel; private readonly decorations: IEditorDecorationsCollection; private readonly toUnhook = new DisposableStore(); @@ -285,7 +284,6 @@ class WordHighlighter { this._hasWordHighlights = ctxHasWordHighlights.bindTo(contextKeyService); this._ignorePositionChangeEvent = false; this.occurrencesHighlight = this.editor.getOption(EditorOption.occurrencesHighlight); - this.multiDocumentOccurrencesHighlight = this.editor.getOption(EditorOption.multiDocumentOccurrencesHighlight); this.model = this.editor.getModel(); this.toUnhook.add(editor.onDidChangeCursorPosition((e: ICursorPositionChangedEvent) => { if (this._ignorePositionChangeEvent) { @@ -293,7 +291,7 @@ class WordHighlighter { return; } - if (!this.occurrencesHighlight) { + if (this.occurrencesHighlight === 'off') { // Early exit if nothing needs to be done! // Leave some form of early exit check here if you wish to continue being a cursor position change listener ;) return; @@ -319,12 +317,6 @@ class WordHighlighter { this.occurrencesHighlight = newValue; this._stopAll(); } - - const newMultiDocumentValue = this.editor.getOption(EditorOption.multiDocumentOccurrencesHighlight); - if (this.multiDocumentOccurrencesHighlight !== newMultiDocumentValue) { - this.multiDocumentOccurrencesHighlight = newMultiDocumentValue; - this._stopAll(); - } })); this.decorations = this.editor.createDecorationsCollection(); @@ -346,14 +338,14 @@ class WordHighlighter { } public restore(): void { - if (!this.occurrencesHighlight) { + if (this.occurrencesHighlight === 'off') { return; } this._run(); } public stop(): void { - if (!this.occurrencesHighlight) { + if (this.occurrencesHighlight === 'off') { return; } @@ -513,7 +505,7 @@ class WordHighlighter { private _onPositionChanged(e: ICursorPositionChangedEvent): void { // disabled - if (!this.occurrencesHighlight) { + if (this.occurrencesHighlight === 'off') { this._stopAll(); return; } @@ -586,7 +578,7 @@ class WordHighlighter { } // multi-doc OFF - if (!this.multiDocumentOccurrencesHighlight) { + if (this.occurrencesHighlight === 'singleFile') { return []; } diff --git a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts index 4ed5acd6a1d..60c16599bf3 100644 --- a/src/vs/editor/standalone/browser/standaloneCodeEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneCodeEditor.ts @@ -38,6 +38,7 @@ import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfi import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; import { IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { mainWindow } from 'vs/base/browser/window'; /** * Description of an action contribution @@ -119,7 +120,7 @@ export interface IGlobalEditorOptions { * Controls whether completions should be computed based on words in the document. * Defaults to true. */ - wordBasedSuggestions?: boolean; + wordBasedSuggestions?: 'off' | 'currentDocument' | 'matchingDocuments' | 'allDocuments'; /** * Controls whether word based completions should be included from opened documents of the same language or any language. */ @@ -252,7 +253,7 @@ function createAriaDomNode(parent: HTMLElement | undefined) { } ariaDomNodeCreated = true; } - aria.setARIAContainer(parent || document.body); + aria.setARIAContainer(parent || mainWindow.document.body); } /** diff --git a/src/vs/editor/standalone/browser/standaloneEditor.ts b/src/vs/editor/standalone/browser/standaloneEditor.ts index 92cf32cf2d5..aa584e5e6af 100644 --- a/src/vs/editor/standalone/browser/standaloneEditor.ts +++ b/src/vs/editor/standalone/browser/standaloneEditor.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { mainWindow } from 'vs/base/browser/window'; import { Disposable, DisposableStore, IDisposable } from 'vs/base/common/lifecycle'; import { splitLines } from 'vs/base/common/strings'; import { URI } from 'vs/base/common/uri'; @@ -38,6 +39,7 @@ import { ITextResourceEditorInput } from 'vs/platform/editor/common/editor'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { IMarker, IMarkerData, IMarkerService } from 'vs/platform/markers/common/markers'; import { IOpenerService } from 'vs/platform/opener/common/opener'; +import { MultiDiffEditorWidget } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget'; /** * Create a new editor under `domElement`. @@ -98,6 +100,11 @@ export function createDiffEditor(domElement: HTMLElement, options?: IStandaloneD return instantiationService.createInstance(StandaloneDiffEditor2, domElement, options); } +export function createMultiFileDiffEditor(domElement: HTMLElement, override?: IEditorOverrideServices) { + const instantiationService = StandaloneServices.initialize(override || {}); + return new MultiDiffEditorWidget(domElement, instantiationService); +} + /** * Description of a command contribution */ @@ -346,7 +353,7 @@ export function colorizeElement(domNode: HTMLElement, options: IColorizerElement export function colorize(text: string, languageId: string, options: IColorizerOptions): Promise { const languageService = StandaloneServices.get(ILanguageService); const themeService = StandaloneServices.get(IStandaloneThemeService); - themeService.registerEditorContainer(document.body); + themeService.registerEditorContainer(mainWindow.document.body); return Colorizer.colorize(languageService, text, languageId, options); } @@ -355,7 +362,7 @@ export function colorize(text: string, languageId: string, options: IColorizerOp */ export function colorizeModelLine(model: ITextModel, lineNumber: number, tabSize: number = 4): string { const themeService = StandaloneServices.get(IStandaloneThemeService); - themeService.registerEditorContainer(document.body); + themeService.registerEditorContainer(mainWindow.document.body); return Colorizer.colorizeModelLine(model, lineNumber, tabSize); } @@ -567,6 +574,8 @@ export function createMonacoEditorAPI(): typeof monaco.editor { ApplyUpdateResult: ApplyUpdateResult, EditorZoom: EditorZoom, + createMultiFileDiffEditor: createMultiFileDiffEditor, + // vars EditorType: EditorType, EditorOptions: EditorOptions diff --git a/src/vs/editor/standalone/browser/standaloneLayoutService.ts b/src/vs/editor/standalone/browser/standaloneLayoutService.ts index ae901c68b41..77cb82585a8 100644 --- a/src/vs/editor/standalone/browser/standaloneLayoutService.ts +++ b/src/vs/editor/standalone/browser/standaloneLayoutService.ts @@ -9,6 +9,7 @@ import { ILayoutService, ILayoutOffsetInfo } from 'vs/platform/layout/browser/la import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { InstantiationType, registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { coalesce } from 'vs/base/common/arrays'; +import { mainWindow } from 'vs/base/browser/window'; class StandaloneLayoutService implements ILayoutService { declare readonly _serviceBrand: undefined; @@ -20,7 +21,7 @@ class StandaloneLayoutService implements ILayoutService { private _dimension?: dom.IDimension; get mainContainerDimension(): dom.IDimension { if (!this._dimension) { - this._dimension = dom.getClientArea(window.document.body); + this._dimension = dom.getClientArea(mainWindow.document.body); } return this._dimension; diff --git a/src/vs/editor/standalone/browser/standaloneServices.ts b/src/vs/editor/standalone/browser/standaloneServices.ts index 01f2c6987ac..353447e0f34 100644 --- a/src/vs/editor/standalone/browser/standaloneServices.ts +++ b/src/vs/editor/standalone/browser/standaloneServices.ts @@ -92,6 +92,7 @@ import { LogService } from 'vs/platform/log/common/logService'; import { getEditorFeatures } from 'vs/editor/common/editorFeatures'; import { onUnexpectedError } from 'vs/base/common/errors'; import { ExtensionKind, IEnvironmentService, IExtensionHostDebugParams } from 'vs/platform/environment/common/environment'; +import { mainWindow } from 'vs/base/browser/window'; class SimpleModel implements IResolvedTextEditorModel { @@ -257,7 +258,7 @@ class StandaloneDialogService implements IDialogService { messageText = messageText + '\n\n' + detail; } - return window.confirm(messageText); + return mainWindow.confirm(messageText); } prompt(prompt: IPromptWithCustomCancel): Promise>; @@ -527,7 +528,7 @@ export class StandaloneKeybindingService extends AbstractKeybindingService { } protected _documentHasFocus(): boolean { - return document.hasFocus(); + return mainWindow.document.hasFocus(); } private _toNormalizedKeybindingItems(items: IKeybindingItem[], isDefault: boolean): ResolvedKeybindingItem[] { diff --git a/src/vs/editor/standalone/browser/standaloneThemeService.ts b/src/vs/editor/standalone/browser/standaloneThemeService.ts index cac2e96a01f..5fc765ed2d1 100644 --- a/src/vs/editor/standalone/browser/standaloneThemeService.ts +++ b/src/vs/editor/standalone/browser/standaloneThemeService.ts @@ -19,6 +19,7 @@ import { Extensions as ThemingExtensions, ICssStyleCollector, IFileIconTheme, IP import { IDisposable, Disposable } from 'vs/base/common/lifecycle'; import { ColorScheme, isDark, isHighContrast } from 'vs/platform/theme/common/theme'; import { getIconsStyleSheet, UnthemedProductIconTheme } from 'vs/platform/theme/browser/iconsStyleSheet'; +import { mainWindow } from 'vs/base/browser/window'; export const VS_LIGHT_THEME_NAME = 'vs'; export const VS_DARK_THEME_NAME = 'vs-dark'; @@ -353,7 +354,7 @@ export class StandaloneThemeService extends Disposable implements IStandaloneThe private _onOSSchemeChanged() { if (this._autoDetectHighContrast) { - const wantsHighContrast = window.matchMedia(`(forced-colors: active)`).matches; + const wantsHighContrast = mainWindow.matchMedia(`(forced-colors: active)`).matches; if (wantsHighContrast !== isHighContrast(this._theme.type)) { // switch to high contrast or non-high contrast but stick to dark or light let newThemeName; @@ -392,7 +393,7 @@ export class StandaloneThemeService extends Disposable implements IStandaloneThe colorVariables.push(`${asCssVariableName(item.id)}: ${color.toString()};`); } } - ruleCollector.addRule(`.monaco-editor, .monaco-diff-editor { ${colorVariables.join('\n')} }`); + ruleCollector.addRule(`.monaco-editor, .monaco-diff-editor, .monaco-component { ${colorVariables.join('\n')} }`); const colorMap = this._colorMapOverride || this._theme.tokenTheme.getColorMap(); ruleCollector.addRule(generateTokensCSSForColorMap(colorMap)); diff --git a/src/vs/editor/test/browser/controller/imeRecorder.ts b/src/vs/editor/test/browser/controller/imeRecorder.ts index e8c6ee7b40e..36290eb2e98 100644 --- a/src/vs/editor/test/browser/controller/imeRecorder.ts +++ b/src/vs/editor/test/browser/controller/imeRecorder.ts @@ -8,11 +8,12 @@ import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle'; import { IRecorded, IRecordedCompositionEvent, IRecordedEvent, IRecordedInputEvent, IRecordedKeyboardEvent, IRecordedTextareaState } from 'vs/editor/test/browser/controller/imeRecordedTypes'; import * as browser from 'vs/base/browser/browser'; import * as platform from 'vs/base/common/platform'; +import { mainWindow } from 'vs/base/browser/window'; (() => { - const startButton = document.getElementById('startRecording')!; - const endButton = document.getElementById('endRecording')!; + const startButton = mainWindow.document.getElementById('startRecording')!; + const endButton = mainWindow.document.getElementById('endRecording')!; let inputarea: HTMLTextAreaElement; const disposables = new DisposableStore(); @@ -90,7 +91,7 @@ import * as platform from 'vs/base/common/platform'; function startTest() { inputarea = document.createElement('textarea'); - document.body.appendChild(inputarea); + mainWindow.document.body.appendChild(inputarea); inputarea.focus(); disposables.add(toDisposable(() => { inputarea.remove(); diff --git a/src/vs/editor/test/browser/controller/imeTester.ts b/src/vs/editor/test/browser/controller/imeTester.ts index b44da204b52..25cdbd57c75 100644 --- a/src/vs/editor/test/browser/controller/imeTester.ts +++ b/src/vs/editor/test/browser/controller/imeTester.ts @@ -11,6 +11,7 @@ import { EndOfLinePreference } from 'vs/editor/common/model'; import * as dom from 'vs/base/browser/dom'; import * as browser from 'vs/base/browser/browser'; import * as platform from 'vs/base/common/platform'; +import { mainWindow } from 'vs/base/browser/window'; // To run this test, open imeTester.html @@ -198,5 +199,5 @@ const TESTS = [ ]; TESTS.forEach((t) => { - document.body.appendChild(doCreateTest(t.description, t.in, t.out)); + mainWindow.document.body.appendChild(doCreateTest(t.description, t.in, t.out)); }); diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index e45bb5af784..c90387e62b4 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -974,6 +974,8 @@ declare namespace monaco.editor { */ export function createDiffEditor(domElement: HTMLElement, options?: IStandaloneDiffEditorConstructionOptions, override?: IEditorOverrideServices): IStandaloneDiffEditor; + export function createMultiFileDiffEditor(domElement: HTMLElement, override?: IEditorOverrideServices): any; + /** * Description of a command contribution */ @@ -1315,7 +1317,7 @@ declare namespace monaco.editor { * Controls whether completions should be computed based on words in the document. * Defaults to true. */ - wordBasedSuggestions?: boolean; + wordBasedSuggestions?: 'off' | 'currentDocument' | 'matchingDocuments' | 'allDocuments'; /** * Controls whether word based completions should be included from opened documents of the same language or any language. */ @@ -2441,7 +2443,7 @@ declare namespace monaco.editor { modified: ITextModel; } - export interface IDiffEditorViewModel { + export interface IDiffEditorViewModel extends IDisposable { readonly model: IDiffEditorModel; waitForDiff(): Promise; } @@ -3536,14 +3538,12 @@ declare namespace monaco.editor { selectionHighlight?: boolean; /** * Enable semantic occurrences highlight. - * Defaults to true. + * Defaults to 'singleFile'. + * 'off' disables occurrence highlighting + * 'singleFile' triggers occurrence highlighting in the current document + * 'multiFile' triggers occurrence highlighting across valid open documents */ - occurrencesHighlight?: boolean; - /** - * Enable semantic occurrences highlight. - * Defaults to true. - */ - multiDocumentOccurrencesHighlight?: boolean; + occurrencesHighlight?: 'off' | 'singleFile' | 'multiFile'; /** * Show code lens * Defaults to true. @@ -4799,75 +4799,74 @@ declare namespace monaco.editor { multiCursorModifier = 77, multiCursorPaste = 78, multiCursorLimit = 79, - multiDocumentOccurrencesHighlight = 80, - occurrencesHighlight = 81, - overviewRulerBorder = 82, - overviewRulerLanes = 83, - padding = 84, - pasteAs = 85, - parameterHints = 86, - peekWidgetDefaultFocus = 87, - definitionLinkOpensInPeek = 88, - quickSuggestions = 89, - quickSuggestionsDelay = 90, - readOnly = 91, - readOnlyMessage = 92, - renameOnType = 93, - renderControlCharacters = 94, - renderFinalNewline = 95, - renderLineHighlight = 96, - renderLineHighlightOnlyWhenFocus = 97, - renderValidationDecorations = 98, - renderWhitespace = 99, - revealHorizontalRightPadding = 100, - roundedSelection = 101, - rulers = 102, - scrollbar = 103, - scrollBeyondLastColumn = 104, - scrollBeyondLastLine = 105, - scrollPredominantAxis = 106, - selectionClipboard = 107, - selectionHighlight = 108, - selectOnLineNumbers = 109, - showFoldingControls = 110, - showUnused = 111, - snippetSuggestions = 112, - smartSelect = 113, - smoothScrolling = 114, - stickyScroll = 115, - stickyTabStops = 116, - stopRenderingLineAfter = 117, - suggest = 118, - suggestFontSize = 119, - suggestLineHeight = 120, - suggestOnTriggerCharacters = 121, - suggestSelection = 122, - tabCompletion = 123, - tabIndex = 124, - unicodeHighlighting = 125, - unusualLineTerminators = 126, - useShadowDOM = 127, - useTabStops = 128, - wordBreak = 129, - wordSeparators = 130, - wordWrap = 131, - wordWrapBreakAfterCharacters = 132, - wordWrapBreakBeforeCharacters = 133, - wordWrapColumn = 134, - wordWrapOverride1 = 135, - wordWrapOverride2 = 136, - wrappingIndent = 137, - wrappingStrategy = 138, - showDeprecated = 139, - inlayHints = 140, - editorClassName = 141, - pixelRatio = 142, - tabFocusMode = 143, - layoutInfo = 144, - wrappingInfo = 145, - defaultColorDecorators = 146, - colorDecoratorsActivatedOn = 147, - inlineCompletionsAccessibilityVerbose = 148 + occurrencesHighlight = 80, + overviewRulerBorder = 81, + overviewRulerLanes = 82, + padding = 83, + pasteAs = 84, + parameterHints = 85, + peekWidgetDefaultFocus = 86, + definitionLinkOpensInPeek = 87, + quickSuggestions = 88, + quickSuggestionsDelay = 89, + readOnly = 90, + readOnlyMessage = 91, + renameOnType = 92, + renderControlCharacters = 93, + renderFinalNewline = 94, + renderLineHighlight = 95, + renderLineHighlightOnlyWhenFocus = 96, + renderValidationDecorations = 97, + renderWhitespace = 98, + revealHorizontalRightPadding = 99, + roundedSelection = 100, + rulers = 101, + scrollbar = 102, + scrollBeyondLastColumn = 103, + scrollBeyondLastLine = 104, + scrollPredominantAxis = 105, + selectionClipboard = 106, + selectionHighlight = 107, + selectOnLineNumbers = 108, + showFoldingControls = 109, + showUnused = 110, + snippetSuggestions = 111, + smartSelect = 112, + smoothScrolling = 113, + stickyScroll = 114, + stickyTabStops = 115, + stopRenderingLineAfter = 116, + suggest = 117, + suggestFontSize = 118, + suggestLineHeight = 119, + suggestOnTriggerCharacters = 120, + suggestSelection = 121, + tabCompletion = 122, + tabIndex = 123, + unicodeHighlighting = 124, + unusualLineTerminators = 125, + useShadowDOM = 126, + useTabStops = 127, + wordBreak = 128, + wordSeparators = 129, + wordWrap = 130, + wordWrapBreakAfterCharacters = 131, + wordWrapBreakBeforeCharacters = 132, + wordWrapColumn = 133, + wordWrapOverride1 = 134, + wordWrapOverride2 = 135, + wrappingIndent = 136, + wrappingStrategy = 137, + showDeprecated = 138, + inlayHints = 139, + editorClassName = 140, + pixelRatio = 141, + tabFocusMode = 142, + layoutInfo = 143, + wrappingInfo = 144, + defaultColorDecorators = 145, + colorDecoratorsActivatedOn = 146, + inlineCompletionsAccessibilityVerbose = 147 } export const EditorOptions: { @@ -4953,8 +4952,7 @@ declare namespace monaco.editor { multiCursorModifier: IEditorOption; multiCursorPaste: IEditorOption; multiCursorLimit: IEditorOption; - occurrencesHighlight: IEditorOption; - multiDocumentOccurrencesHighlight: IEditorOption; + occurrencesHighlight: IEditorOption; overviewRulerBorder: IEditorOption; overviewRulerLanes: IEditorOption; padding: IEditorOption>>; diff --git a/src/vs/platform/accessibility/browser/accessibilityService.ts b/src/vs/platform/accessibility/browser/accessibilityService.ts index 409619555ec..97cd8b57de9 100644 --- a/src/vs/platform/accessibility/browser/accessibilityService.ts +++ b/src/vs/platform/accessibility/browser/accessibilityService.ts @@ -5,6 +5,7 @@ import { addDisposableListener } from 'vs/base/browser/dom'; import { alert } from 'vs/base/browser/ui/aria/aria'; +import { mainWindow } from 'vs/base/browser/window'; import { Emitter, Event } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { AccessibilitySupport, CONTEXT_ACCESSIBILITY_MODE_ENABLED, IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; @@ -45,7 +46,7 @@ export class AccessibilityService extends Disposable implements IAccessibilitySe updateContextKey(); this._register(this.onDidChangeScreenReaderOptimized(() => updateContextKey())); - const reduceMotionMatcher = window.matchMedia(`(prefers-reduced-motion: reduce)`); + const reduceMotionMatcher = mainWindow.matchMedia(`(prefers-reduced-motion: reduce)`); this._systemMotionReduced = reduceMotionMatcher.matches; this._configMotionReduced = this._configurationService.getValue<'auto' | 'on' | 'off'>('workbench.reduceMotion'); diff --git a/src/vs/platform/actions/browser/toolbar.ts b/src/vs/platform/actions/browser/toolbar.ts index 69c6905635b..a9061e12c94 100644 --- a/src/vs/platform/actions/browser/toolbar.ts +++ b/src/vs/platform/actions/browser/toolbar.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { addDisposableListener } from 'vs/base/browser/dom'; +import { addDisposableListener, getWindow } from 'vs/base/browser/dom'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IToolBarOptions, ToolBar } from 'vs/base/browser/ui/toolbar/toolbar'; import { IAction, Separator, SubmenuAction, toAction, WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions'; @@ -184,7 +184,7 @@ export class WorkbenchToolBar extends ToolBar { // add context menu for toggle actions if (toggleActions.length > 0) { this._sessionDisposables.add(addDisposableListener(this.getElement(), 'contextmenu', e => { - const event = new StandardMouseEvent(e); + const event = new StandardMouseEvent(getWindow(this.getElement()), e); const action = this.getItemAction(event.target); if (!(action)) { diff --git a/src/vs/platform/actions/common/actions.ts b/src/vs/platform/actions/common/actions.ts index d917d2f9a38..c3ac021bedc 100644 --- a/src/vs/platform/actions/common/actions.ts +++ b/src/vs/platform/actions/common/actions.ts @@ -70,6 +70,7 @@ export class MenuId { static readonly EmptyEditorGroupContext = new MenuId('EmptyEditorGroupContext'); static readonly EditorTabsBarContext = new MenuId('EditorTabsBarContext'); static readonly EditorTabsBarShowTabsSubmenu = new MenuId('EditorTabsBarShowTabsSubmenu'); + static readonly EditorActionsPositionSubmenu = new MenuId('EditorActionsPositionSubmenu'); static readonly ExplorerContext = new MenuId('ExplorerContext'); static readonly ExplorerContextShare = new MenuId('ExplorerContextShare'); static readonly ExtensionContext = new MenuId('ExtensionContext'); @@ -103,6 +104,7 @@ export class MenuId { static readonly OpenEditorsContext = new MenuId('OpenEditorsContext'); static readonly OpenEditorsContextShare = new MenuId('OpenEditorsContextShare'); static readonly ProblemsPanelContext = new MenuId('ProblemsPanelContext'); + static readonly SCMHistoryItem = new MenuId('SCMHistoryItem'); static readonly SCMChangeContext = new MenuId('SCMChangeContext'); static readonly SCMResourceContext = new MenuId('SCMResourceContext'); static readonly SCMResourceContextShare = new MenuId('SCMResourceContextShare'); diff --git a/src/vs/platform/audioCues/browser/audioCueService.ts b/src/vs/platform/audioCues/browser/audioCueService.ts index 2583c4b92a3..a97297094c1 100644 --- a/src/vs/platform/audioCues/browser/audioCueService.ts +++ b/src/vs/platform/audioCues/browser/audioCueService.ts @@ -122,7 +122,10 @@ export class AudioCueService extends Disposable implements IAudioCueService { this.sounds.set(url, playedSound); } } catch (e) { - console.error('Error while playing sound', e); + if (!e.message.includes('play() can only be initiated by a user gesture')) { + // tracking this issue in #178642, no need to spam the console + console.error('Error while playing sound', e); + } } finally { this.playingSounds.delete(sound); } diff --git a/src/vs/platform/contextview/browser/contextMenuHandler.ts b/src/vs/platform/contextview/browser/contextMenuHandler.ts index fb591a63828..55e0d50422d 100644 --- a/src/vs/platform/contextview/browser/contextMenuHandler.ts +++ b/src/vs/platform/contextview/browser/contextMenuHandler.ts @@ -95,14 +95,14 @@ export class ContextMenuHandler { menu.onDidCancel(() => this.contextViewService.hideContextView(true), null, menuDisposables); menu.onDidBlur(() => this.contextViewService.hideContextView(true), null, menuDisposables); - const window = getWindow(container); - menuDisposables.add(addDisposableListener(window, EventType.BLUR, () => this.contextViewService.hideContextView(true))); - menuDisposables.add(addDisposableListener(window, EventType.MOUSE_DOWN, (e: MouseEvent) => { + const targetWindow = getWindow(container); + menuDisposables.add(addDisposableListener(targetWindow, EventType.BLUR, () => this.contextViewService.hideContextView(true))); + menuDisposables.add(addDisposableListener(targetWindow, EventType.MOUSE_DOWN, (e: MouseEvent) => { if (e.defaultPrevented) { return; } - const event = new StandardMouseEvent(e); + const event = new StandardMouseEvent(targetWindow, e); let element: HTMLElement | null = event.target; // Don't do anything as we are likely creating a context menu diff --git a/src/vs/platform/dnd/browser/dnd.ts b/src/vs/platform/dnd/browser/dnd.ts index 2046ddb1143..1bd55afefaf 100644 --- a/src/vs/platform/dnd/browser/dnd.ts +++ b/src/vs/platform/dnd/browser/dnd.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { DataTransfers } from 'vs/base/browser/dnd'; +import { mainWindow } from 'vs/base/browser/window'; import { DragMouseEvent } from 'vs/base/browser/mouseEvent'; import { coalesce } from 'vs/base/common/arrays'; import { DeferredPromise } from 'vs/base/common/async'; @@ -182,7 +183,7 @@ interface IFileTransferData { async function extractFilesDropData(accessor: ServicesAccessor, event: DragEvent): Promise { // Try to extract via `FileSystemHandle` - if (WebFileSystemAccess.supported(window)) { + if (WebFileSystemAccess.supported(mainWindow)) { const items = event.dataTransfer?.items; if (items) { return extractFileTransferData(accessor, items); diff --git a/src/vs/platform/instantiation/common/instantiationService.ts b/src/vs/platform/instantiation/common/instantiationService.ts index f0db479e3c1..dee82758c0a 100644 --- a/src/vs/platform/instantiation/common/instantiationService.ts +++ b/src/vs/platform/instantiation/common/instantiationService.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IdleValue } from 'vs/base/common/async'; +import { GlobalIdleValue } from 'vs/base/common/async'; import { Event } from 'vs/base/common/event'; import { illegalState } from 'vs/base/common/errors'; import { toDisposable } from 'vs/base/common/lifecycle'; @@ -255,7 +255,7 @@ export class InstantiationService implements IInstantiationService { // return "empty events" when the service isn't instantiated yet const earlyListeners = new Map>>>(); - const idle = new IdleValue(() => { + const idle = new GlobalIdleValue(() => { const result = child._createInstance(ctor, args, _trace); // early listeners that we kept are now being subscribed to diff --git a/src/vs/platform/issue/common/issue.ts b/src/vs/platform/issue/common/issue.ts index 8c4edb4f4c3..6af67b552df 100644 --- a/src/vs/platform/issue/common/issue.ts +++ b/src/vs/platform/issue/common/issue.ts @@ -133,5 +133,6 @@ export interface IIssueMainService { $getIssueReporterUri(extensionId: string): Promise; $getIssueReporterData(extensionId: string): Promise; $getIssueReporterTemplate(extensionId: string): Promise; + $getReporterStatus(extensionId: string, extensionName: string): Promise; $closeReporter(): Promise; } diff --git a/src/vs/platform/issue/electron-main/issueMainService.ts b/src/vs/platform/issue/electron-main/issueMainService.ts index b69bd4d2ee2..c34ab307285 100644 --- a/src/vs/platform/issue/electron-main/issueMainService.ts +++ b/src/vs/platform/issue/electron-main/issueMainService.ts @@ -5,7 +5,7 @@ import { BrowserWindow, BrowserWindowConstructorOptions, contentTracing, Display, IpcMainEvent, screen } from 'electron'; import { arch, release, type } from 'os'; -import { Promises, timeout } from 'vs/base/common/async'; +import { Promises, raceTimeout, timeout } from 'vs/base/common/async'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; import { randomPath } from 'vs/base/common/extpath'; import { DisposableStore } from 'vs/base/common/lifecycle'; @@ -442,6 +442,19 @@ export class IssueMainService implements IIssueMainService { }); } + async $getReporterStatus(extensionId: string, extensionName: string): Promise { + const defaultResult = [false, false]; + const window = this.issueReporterWindowCheck(); + const replyChannel = `vscode:triggerReporterStatus`; + const cts = new CancellationTokenSource(); + window.sendWhenReady(replyChannel, cts.token, { replyChannel, extensionId, extensionName }); + const result = await raceTimeout(new Promise(resolve => validatedIpcMain.once('vscode:triggerReporterStatusResponse', (_: unknown, data: boolean[]) => resolve(data))), 2000, () => { + this.logService.error('Error: Extension timed out waiting for reporter status'); + cts.cancel(); + }); + return (result ?? defaultResult) as boolean[]; + } + async $closeReporter(): Promise { this.issueReporterWindow?.close(); } diff --git a/src/vs/platform/log/browser/log.ts b/src/vs/platform/log/browser/log.ts index 246805d1e54..0abfdaa677b 100644 --- a/src/vs/platform/log/browser/log.ts +++ b/src/vs/platform/log/browser/log.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { mainWindow } from 'vs/base/browser/window'; import { relativePath } from 'vs/base/common/resources'; import { URI } from 'vs/base/common/uri'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -75,7 +76,7 @@ export class ConsoleLogInAutomationLogger extends AdapterLogger implements ILogg } private consoleLog(type: string, args: any[]): void { - const automatedWindow = window as unknown as IAutomatedWindow; + const automatedWindow = mainWindow as unknown as IAutomatedWindow; if (typeof automatedWindow.codeAutomationLog === 'function') { try { automatedWindow.codeAutomationLog(type, args); diff --git a/src/vs/platform/native/electron-sandbox/nativeHostService.ts b/src/vs/platform/native/common/nativeHostService.ts similarity index 100% rename from src/vs/platform/native/electron-sandbox/nativeHostService.ts rename to src/vs/platform/native/common/nativeHostService.ts diff --git a/src/vs/platform/native/electron-main/nativeHostMainService.ts b/src/vs/platform/native/electron-main/nativeHostMainService.ts index 1cd821a33af..29132727162 100644 --- a/src/vs/platform/native/electron-main/nativeHostMainService.ts +++ b/src/vs/platform/native/electron-main/nativeHostMainService.ts @@ -150,7 +150,7 @@ export class NativeHostMainService extends Disposable implements INativeHostMain } async getActiveWindowId(windowId: number | undefined): Promise { - const activeWindow = BrowserWindow.getFocusedWindow() || this.windowsMainService.getLastActiveWindow(); + const activeWindow = this.windowsMainService.getFocusedWindow() || this.windowsMainService.getLastActiveWindow(); if (activeWindow) { return activeWindow.id; } diff --git a/src/vs/platform/product/common/product.ts b/src/vs/platform/product/common/product.ts index 8cc6f60aa63..9482a2c954e 100644 --- a/src/vs/platform/product/common/product.ts +++ b/src/vs/platform/product/common/product.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { globals } from 'vs/base/common/platform'; import { env } from 'vs/base/common/process'; import { IProductConfiguration } from 'vs/base/common/product'; import { ISandboxConfiguration } from 'vs/base/parts/sandbox/common/sandboxTypes'; @@ -14,8 +13,9 @@ import { ISandboxConfiguration } from 'vs/base/parts/sandbox/common/sandboxTypes let product: IProductConfiguration; // Native sandbox environment -if (typeof globals.vscode !== 'undefined' && typeof globals.vscode.context !== 'undefined') { - const configuration: ISandboxConfiguration | undefined = globals.vscode.context.configuration(); +const vscodeGlobal = (globalThis as any).vscode; +if (typeof vscodeGlobal !== 'undefined' && typeof vscodeGlobal.context !== 'undefined') { + const configuration: ISandboxConfiguration | undefined = vscodeGlobal.context.configuration(); if (configuration) { product = configuration.product; } else { diff --git a/src/vs/platform/quickinput/browser/quickInputBox.ts b/src/vs/platform/quickinput/browser/quickInputBox.ts index cebb5ce460d..b3c6694387c 100644 --- a/src/vs/platform/quickinput/browser/quickInputBox.ts +++ b/src/vs/platform/quickinput/browser/quickInputBox.ts @@ -36,15 +36,11 @@ export class QuickInputBox extends Disposable { } onKeyDown = (handler: (event: StandardKeyboardEvent) => void): IDisposable => { - return dom.addDisposableListener(this.findInput.inputBox.inputElement, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => { - handler(new StandardKeyboardEvent(e)); - }); + return dom.addStandardDisposableListener(this.findInput.inputBox.inputElement, dom.EventType.KEY_DOWN, handler); }; onMouseDown = (handler: (event: StandardMouseEvent) => void): IDisposable => { - return dom.addDisposableListener(this.findInput.inputBox.inputElement, dom.EventType.MOUSE_DOWN, (e: MouseEvent) => { - handler(new StandardMouseEvent(e)); - }); + return dom.addStandardDisposableListener(this.findInput.inputBox.inputElement, dom.EventType.MOUSE_DOWN, handler); }; onDidChange = (handler: (event: string) => void): IDisposable => { diff --git a/src/vs/platform/quickinput/browser/quickInputController.ts b/src/vs/platform/quickinput/browser/quickInputController.ts index a2f2e4cb1b9..135ece927b5 100644 --- a/src/vs/platform/quickinput/browser/quickInputController.ts +++ b/src/vs/platform/quickinput/browser/quickInputController.ts @@ -22,6 +22,7 @@ import { QuickInputList, QuickInputListFocus } from 'vs/platform/quickinput/brow import { QuickInputUI, Writeable, IQuickInputStyles, IQuickInputOptions, QuickPick, backButton, InputBox, Visibilities, QuickWidget } from 'vs/platform/quickinput/browser/quickInput'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { mainWindow } from 'vs/base/browser/window'; const $ = dom.$; @@ -58,7 +59,7 @@ export class QuickInputController extends Disposable { this.idPrefix = options.idPrefix; this.parentElement = options.container; this.styles = options.styles; - this._register(Event.runAndSubscribe(dom.onDidRegisterWindow, ({ window, disposables }) => this.registerKeyModsListeners(window, disposables), { window, disposables: this._store })); + this._register(Event.runAndSubscribe(dom.onDidRegisterWindow, ({ window, disposables }) => this.registerKeyModsListeners(window, disposables), { window: mainWindow, disposables: this._store })); this._register(dom.onWillUnregisterWindow(window => { if (this.ui && dom.getWindow(this.ui.container) === window) { // The window this quick input is contained in is about to diff --git a/src/vs/platform/quickinput/test/browser/quickinput.test.ts b/src/vs/platform/quickinput/test/browser/quickinput.test.ts index 8d54f3cd5af..fdfdc1ffcf4 100644 --- a/src/vs/platform/quickinput/test/browser/quickinput.test.ts +++ b/src/vs/platform/quickinput/test/browser/quickinput.test.ts @@ -17,6 +17,7 @@ import { QuickInputController } from 'vs/platform/quickinput/browser/quickInputC import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { toDisposable } from 'vs/base/common/lifecycle'; +import { mainWindow } from 'vs/base/browser/window'; // Sets up an `onShow` listener to allow us to wait until the quick pick is shown (useful when triggering an `accept()` right after launching a quick pick) // kick this off before you launch the picker and then await the promise returned after you launch the picker. @@ -39,8 +40,8 @@ suite('QuickInput', () => { // https://github.com/microsoft/vscode/issues/147543 setup(() => { const fixture = document.createElement('div'); - document.body.appendChild(fixture); - store.add(toDisposable(() => document.body.removeChild(fixture))); + mainWindow.document.body.appendChild(fixture); + store.add(toDisposable(() => mainWindow.document.body.removeChild(fixture))); controller = store.add(new QuickInputController({ container: fixture, diff --git a/src/vs/platform/remote/browser/browserSocketFactory.ts b/src/vs/platform/remote/browser/browserSocketFactory.ts index 765b2f3fa6f..e8d40cb8ecc 100644 --- a/src/vs/platform/remote/browser/browserSocketFactory.ts +++ b/src/vs/platform/remote/browser/browserSocketFactory.ts @@ -11,6 +11,7 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { ISocket, SocketCloseEvent, SocketCloseEventType, SocketDiagnostics, SocketDiagnosticsEventType } from 'vs/base/parts/ipc/common/ipc.net'; import { ISocketFactory } from 'vs/platform/remote/common/remoteSocketFactoryService'; import { RemoteAuthorityResolverError, RemoteAuthorityResolverErrorCode, RemoteConnectionType, WebSocketRemoteConnection } from 'vs/platform/remote/common/remoteAuthorityResolver'; +import { mainWindow } from 'vs/base/browser/window'; export interface IWebSocketFactory { create(url: string, debugLabel: string): IWebSocket; @@ -154,7 +155,7 @@ class BrowserWebSocket extends Disposable implements IWebSocket { this._isClosed = true; if (pendingErrorEvent) { - if (!window.navigator.onLine) { + if (!navigator.onLine) { // The browser is offline => this is a temporary error which might resolve itself sendErrorNow(new RemoteAuthorityResolverError('Browser is offline', RemoteAuthorityResolverErrorCode.TemporarilyNotAvailable, e)); } else { @@ -279,7 +280,7 @@ export class BrowserSocketFactory implements ISocketFactory { return new Promise((resolve, reject) => { - const webSocketSchema = (/^https:/.test(window.location.href) ? 'wss' : 'ws'); + const webSocketSchema = (/^https:/.test(mainWindow.location.href) ? 'wss' : 'ws'); const socket = this._webSocketFactory.create(`${webSocketSchema}://${(/:/.test(host) && !/\[/.test(host)) ? `[${host}]` : host}:${port}${path}?${query}&skipWebSocketFrames=false`, debugLabel); const errorListener = socket.onError(reject); socket.onOpen(() => { diff --git a/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts b/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts index 2db24441fb6..529d9d74999 100644 --- a/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts +++ b/src/vs/platform/remote/browser/remoteAuthorityResolverService.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { mainWindow } from 'vs/base/browser/window'; import { DeferredPromise } from 'vs/base/common/async'; import * as errors from 'vs/base/common/errors'; import { Emitter } from 'vs/base/common/event'; @@ -84,7 +85,7 @@ export class RemoteAuthorityResolverService extends Disposable implements IRemot const connectionToken = await Promise.resolve(this._connectionTokens.get(authority) || this._connectionToken); performance.mark(`code/didResolveConnectionToken/${authorityPrefix}`); this._logService.info(`Resolved connection token (${authorityPrefix}) after ${sw.elapsed()} ms`); - const defaultPort = (/^https:/.test(window.location.href) ? 443 : 80); + const defaultPort = (/^https:/.test(mainWindow.location.href) ? 443 : 80); const { host, port } = parseAuthorityWithOptionalPort(authority, defaultPort); const result: ResolverResult = { authority: { authority, connectTo: new WebSocketRemoteConnection(host, port), connectionToken } }; RemoteAuthorities.set(authority, host, port); diff --git a/src/vs/platform/sign/browser/signService.ts b/src/vs/platform/sign/browser/signService.ts index 8625763cd8e..b2b9ad6cfab 100644 --- a/src/vs/platform/sign/browser/signService.ts +++ b/src/vs/platform/sign/browser/signService.ts @@ -3,7 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { IntervalTimer } from 'vs/base/common/async'; +import { WindowIntervalTimer } from 'vs/base/browser/dom'; +import { $window } from 'vs/base/browser/window'; import { memoize } from 'vs/base/common/decorators'; import { FileAccess } from 'vs/base/common/network'; import { IProductService } from 'vs/platform/product/common/productService'; @@ -57,7 +58,7 @@ export class SignService extends AbstractSignService implements ISignService { @memoize private async vsda(): Promise { - const checkInterval = new IntervalTimer(); + const checkInterval = new WindowIntervalTimer(); let [wasm] = await Promise.all([ this.getWasmBytes(), new Promise((resolve, reject) => { @@ -69,7 +70,7 @@ export class SignService extends AbstractSignService implements ISignService { if (typeof vsda_web !== 'undefined') { resolve(); } - }, 50); + }, 50, $window); }).finally(() => checkInterval!.dispose()), ]); diff --git a/src/vs/platform/state/node/stateService.ts b/src/vs/platform/state/node/stateService.ts index debf184e7d4..46dd2baa43e 100644 --- a/src/vs/platform/state/node/stateService.ts +++ b/src/vs/platform/state/node/stateService.ts @@ -25,20 +25,18 @@ export class FileStorage extends Disposable { private storage: StorageDatabase = Object.create(null); private lastSavedStorageContents = ''; - private readonly flushDelayer: ThrottledDelayer | undefined; + private readonly flushDelayer = this._register(new ThrottledDelayer(this.saveStrategy === SaveStrategy.IMMEDIATE ? 0 : 100 /* buffer saves over a short time */)); private initializing: Promise | undefined = undefined; private closing: Promise | undefined = undefined; constructor( private readonly storagePath: URI, - saveStrategy: SaveStrategy, + private readonly saveStrategy: SaveStrategy, private readonly logService: ILogService, private readonly fileService: IFileService, ) { super(); - - this.flushDelayer = saveStrategy === SaveStrategy.IMMEDIATE ? undefined : this._register(new ThrottledDelayer(100 /* buffer saves over a short time */)); } init(): Promise { @@ -119,11 +117,7 @@ export class FileStorage extends Disposable { return; // already about to close } - if (this.flushDelayer) { - return this.flushDelayer.trigger(() => this.doSave()); - } - - return this.doSave(); + return this.flushDelayer.trigger(() => this.doSave()); } private async doSave(): Promise { @@ -151,9 +145,7 @@ export class FileStorage extends Disposable { async close(): Promise { if (!this.closing) { - this.closing = this.flushDelayer - ? this.flushDelayer.trigger(() => this.doSave(), 0 /* as soon as possible */) - : this.doSave(); + this.closing = this.flushDelayer.trigger(() => this.doSave(), 0 /* as soon as possible */); } return this.closing; diff --git a/src/vs/platform/storage/common/storage.ts b/src/vs/platform/storage/common/storage.ts index 174a3038aaa..fe730ff5cda 100644 --- a/src/vs/platform/storage/common/storage.ts +++ b/src/vs/platform/storage/common/storage.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Promises, RunOnceScheduler, runWhenIdle } from 'vs/base/common/async'; +import { Promises, RunOnceScheduler, runWhenGlobalIdle } from 'vs/base/common/async'; import { Emitter, Event, PauseableEmitter } from 'vs/base/common/event'; import { Disposable, DisposableStore, dispose, MutableDisposable } from 'vs/base/common/lifecycle'; import { mark } from 'vs/base/common/performance'; @@ -343,7 +343,7 @@ export abstract class AbstractStorageService extends Disposable implements IStor } private doFlushWhenIdle(): void { - this.runFlushWhenIdle.value = runWhenIdle(() => { + this.runFlushWhenIdle.value = runWhenGlobalIdle(() => { if (this.shouldFlushWhenIdle()) { this.flush(); } diff --git a/src/vs/platform/telemetry/browser/errorTelemetry.ts b/src/vs/platform/telemetry/browser/errorTelemetry.ts index ae2cea2fe2e..61e5836f539 100644 --- a/src/vs/platform/telemetry/browser/errorTelemetry.ts +++ b/src/vs/platform/telemetry/browser/errorTelemetry.ts @@ -3,25 +3,25 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { mainWindow } from 'vs/base/browser/window'; import { ErrorNoTelemetry } from 'vs/base/common/errors'; import { toDisposable } from 'vs/base/common/lifecycle'; -import { globals } from 'vs/base/common/platform'; import BaseErrorTelemetry, { ErrorEvent } from 'vs/platform/telemetry/common/errorTelemetry'; export default class ErrorTelemetry extends BaseErrorTelemetry { protected override installErrorListeners(): void { - let oldOnError: Function; + let oldOnError: OnErrorEventHandler; const that = this; - if (typeof globals.onerror === 'function') { - oldOnError = globals.onerror; + if (typeof mainWindow.onerror === 'function') { + oldOnError = mainWindow.onerror; } - globals.onerror = function (message: string, filename: string, line: number, column?: number, e?: any) { - that._onUncaughtError(message, filename, line, column, e); - oldOnError?.apply(this, arguments); + mainWindow.onerror = function (message: Event | string, filename?: string, line?: number, column?: number, error?: Error) { + that._onUncaughtError(message as string, filename as string, line as number, column, error); + oldOnError?.apply(this, [message, filename, line, column, error]); }; this._disposables.add(toDisposable(() => { if (oldOnError) { - globals.onerror = oldOnError; + mainWindow.onerror = oldOnError; } })); } diff --git a/src/vs/platform/telemetry/test/browser/telemetryService.test.ts b/src/vs/platform/telemetry/test/browser/telemetryService.test.ts index cbfd8554437..f20eb00d58a 100644 --- a/src/vs/platform/telemetry/test/browser/telemetryService.test.ts +++ b/src/vs/platform/telemetry/test/browser/telemetryService.test.ts @@ -5,6 +5,7 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; import * as sinonTest from 'sinon-test'; +import { mainWindow } from 'vs/base/browser/window'; import * as Errors from 'vs/base/common/errors'; import { Emitter } from 'vs/base/common/event'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; @@ -269,14 +270,14 @@ suite('TelemetryService', () => { test('Handle global errors', sinonTestFn(function (this: any) { const errorStub = sinon.stub(); - window.onerror = errorStub; + mainWindow.onerror = errorStub; const testAppender = new TestTelemetryAppender(); const service = new TestErrorTelemetryService({ appenders: [testAppender] }); const errorTelemetry = new ErrorTelemetry(service); const testError = new Error('test'); - (window.onerror)('Error Message', 'file.js', 2, 42, testError); + (mainWindow.onerror)('Error Message', 'file.js', 2, 42, testError); this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); assert.strictEqual(errorStub.alwaysCalledWithExactly('Error Message', 'file.js', 2, 42, testError), true); @@ -297,7 +298,7 @@ suite('TelemetryService', () => { test('Error Telemetry removes PII from filename with spaces', sinonTestFn(function (this: any) { const errorStub = sinon.stub(); - window.onerror = errorStub; + mainWindow.onerror = errorStub; const settings = new ErrorTestingSettings(); const testAppender = new TestTelemetryAppender(); const service = new TestErrorTelemetryService({ appenders: [testAppender] }); @@ -306,7 +307,7 @@ suite('TelemetryService', () => { const personInfoWithSpaces = settings.personalInfo.slice(0, 2) + ' ' + settings.personalInfo.slice(2); const dangerousFilenameError: any = new Error('dangerousFilename'); dangerousFilenameError.stack = settings.stack; - (window.onerror)('dangerousFilename', settings.dangerousPathWithImportantInfo.replace(settings.personalInfo, personInfoWithSpaces) + '/test.js', 2, 42, dangerousFilenameError); + (mainWindow.onerror)('dangerousFilename', settings.dangerousPathWithImportantInfo.replace(settings.personalInfo, personInfoWithSpaces) + '/test.js', 2, 42, dangerousFilenameError); this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); assert.strictEqual(errorStub.callCount, 1); @@ -321,7 +322,7 @@ suite('TelemetryService', () => { test('Uncaught Error Telemetry removes PII from filename', sinonTestFn(function (this: any) { const clock = this.clock; const errorStub = sinon.stub(); - window.onerror = errorStub; + mainWindow.onerror = errorStub; const settings = new ErrorTestingSettings(); const testAppender = new TestTelemetryAppender(); const service = new TestErrorTelemetryService({ appenders: [testAppender] }); @@ -329,14 +330,14 @@ suite('TelemetryService', () => { let dangerousFilenameError: any = new Error('dangerousFilename'); dangerousFilenameError.stack = settings.stack; - (window.onerror)('dangerousFilename', settings.dangerousPathWithImportantInfo + '/test.js', 2, 42, dangerousFilenameError); + (mainWindow.onerror)('dangerousFilename', settings.dangerousPathWithImportantInfo + '/test.js', 2, 42, dangerousFilenameError); clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); assert.strictEqual(errorStub.callCount, 1); assert.strictEqual(testAppender.events[0].data.file.indexOf(settings.dangerousPathWithImportantInfo), -1); dangerousFilenameError = new Error('dangerousFilename'); dangerousFilenameError.stack = settings.stack; - (window.onerror)('dangerousFilename', settings.dangerousPathWithImportantInfo + '/test.js', 2, 42, dangerousFilenameError); + (mainWindow.onerror)('dangerousFilename', settings.dangerousPathWithImportantInfo + '/test.js', 2, 42, dangerousFilenameError); clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); assert.strictEqual(errorStub.callCount, 2); assert.strictEqual(testAppender.events[0].data.file.indexOf(settings.dangerousPathWithImportantInfo), -1); @@ -379,7 +380,7 @@ suite('TelemetryService', () => { test('Uncaught Error Telemetry removes PII', sinonTestFn(function (this: any) { const errorStub = sinon.stub(); - window.onerror = errorStub; + mainWindow.onerror = errorStub; const settings = new ErrorTestingSettings(); const testAppender = new TestTelemetryAppender(); const service = new TestErrorTelemetryService({ appenders: [testAppender] }); @@ -387,7 +388,7 @@ suite('TelemetryService', () => { const dangerousPathWithoutImportantInfoError: any = new Error('dangerousPathWithoutImportantInfo'); dangerousPathWithoutImportantInfoError.stack = settings.stack; - (window.onerror)(settings.dangerousPathWithoutImportantInfo, 'test.js', 2, 42, dangerousPathWithoutImportantInfoError); + (mainWindow.onerror)(settings.dangerousPathWithoutImportantInfo, 'test.js', 2, 42, dangerousPathWithoutImportantInfoError); this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); assert.strictEqual(errorStub.callCount, 1); @@ -441,7 +442,7 @@ suite('TelemetryService', () => { test('Uncaught Error Telemetry removes PII but preserves Code file path', sinonTestFn(function (this: any) { const errorStub = sinon.stub(); - window.onerror = errorStub; + mainWindow.onerror = errorStub; const settings = new ErrorTestingSettings(); const testAppender = new TestTelemetryAppender(); const service = new TestErrorTelemetryService({ appenders: [testAppender] }); @@ -449,7 +450,7 @@ suite('TelemetryService', () => { const dangerousPathWithImportantInfoError: any = new Error('dangerousPathWithImportantInfo'); dangerousPathWithImportantInfoError.stack = settings.stack; - (window.onerror)(settings.dangerousPathWithImportantInfo, 'test.js', 2, 42, dangerousPathWithImportantInfoError); + (mainWindow.onerror)(settings.dangerousPathWithImportantInfo, 'test.js', 2, 42, dangerousPathWithImportantInfoError); this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); assert.strictEqual(errorStub.callCount, 1); @@ -540,7 +541,7 @@ suite('TelemetryService', () => { test('Uncaught Error Telemetry removes PII but preserves Code file path when PIIPath is configured', sinonTestFn(function (this: any) { const errorStub = sinon.stub(); - window.onerror = errorStub; + mainWindow.onerror = errorStub; const settings = new ErrorTestingSettings(); const testAppender = new TestTelemetryAppender(); const service = new TestErrorTelemetryService({ appenders: [testAppender], piiPaths: [settings.personalInfo + '/resources/app/'] }); @@ -548,7 +549,7 @@ suite('TelemetryService', () => { const dangerousPathWithImportantInfoError: any = new Error('dangerousPathWithImportantInfo'); dangerousPathWithImportantInfoError.stack = settings.stack; - (window.onerror)(settings.dangerousPathWithImportantInfo, 'test.js', 2, 42, dangerousPathWithImportantInfoError); + (mainWindow.onerror)(settings.dangerousPathWithImportantInfo, 'test.js', 2, 42, dangerousPathWithImportantInfoError); this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); assert.strictEqual(errorStub.callCount, 1); @@ -604,7 +605,7 @@ suite('TelemetryService', () => { test('Uncaught Error Telemetry removes PII but preserves Missing Model error message', sinonTestFn(function (this: any) { const errorStub = sinon.stub(); - window.onerror = errorStub; + mainWindow.onerror = errorStub; const settings = new ErrorTestingSettings(); const testAppender = new TestTelemetryAppender(); const service = new TestErrorTelemetryService({ appenders: [testAppender] }); @@ -612,7 +613,7 @@ suite('TelemetryService', () => { const missingModelError: any = new Error('missingModelMessage'); missingModelError.stack = settings.stack; - (window.onerror)(settings.missingModelMessage, 'test.js', 2, 42, missingModelError); + (mainWindow.onerror)(settings.missingModelMessage, 'test.js', 2, 42, missingModelError); this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); assert.strictEqual(errorStub.callCount, 1); @@ -673,7 +674,7 @@ suite('TelemetryService', () => { try { const errorStub = sinon.stub(); - window.onerror = errorStub; + mainWindow.onerror = errorStub; const settings = new ErrorTestingSettings(); const testAppender = new TestTelemetryAppender(); const service = new TestErrorTelemetryService({ appenders: [testAppender] }); @@ -681,7 +682,7 @@ suite('TelemetryService', () => { const noSuchFileError: any = new Error('noSuchFileMessage'); noSuchFileError.stack = settings.stack; - (window.onerror)(settings.noSuchFileMessage, 'test.js', 2, 42, noSuchFileError); + (mainWindow.onerror)(settings.noSuchFileMessage, 'test.js', 2, 42, noSuchFileError); this.clock.tick(ErrorTelemetry.ERROR_FLUSH_TIMEOUT); assert.strictEqual(errorStub.callCount, 1); diff --git a/src/vs/platform/terminal/common/capabilities/capabilities.ts b/src/vs/platform/terminal/common/capabilities/capabilities.ts index 3c3bec8dd6d..ea42306d013 100644 --- a/src/vs/platform/terminal/common/capabilities/capabilities.ts +++ b/src/vs/platform/terminal/common/capabilities/capabilities.ts @@ -186,6 +186,7 @@ export interface ICommandDetectionCapability { * case the terminal's initial cwd should be used. */ getCwdForLine(line: number): string | undefined; + getCommandForLine(line: number): ITerminalCommand | ICurrentPartialCommand | undefined; handlePromptStart(options?: IHandleCommandOptions): void; handleContinuationStart(): void; handleContinuationEnd(): void; @@ -247,10 +248,13 @@ interface IBaseTerminalCommand { exitCode: number | undefined; commandStartLineContent: string | undefined; markProperties: IMarkProperties | undefined; + executedX: number | undefined; + startX: number | undefined; } export interface ITerminalCommand extends IBaseTerminalCommand { // Optional non-serializable + promptStartMarker?: IMarker; marker?: IXtermMarker; endMarker?: IXtermMarker; executedMarker?: IXtermMarker; @@ -265,7 +269,7 @@ export interface ITerminalCommand extends IBaseTerminalCommand { export interface ISerializedTerminalCommand extends IBaseTerminalCommand { // Optional non-serializable converted for serialization startLine: number | undefined; - startX: number | undefined; + promptStartLine: number | undefined; endLine: number | undefined; executedLine: number | undefined; } diff --git a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts index 720fdfa5bf4..553a4011fe3 100644 --- a/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts +++ b/src/vs/platform/terminal/common/capabilities/commandDetectionCapability.ts @@ -6,7 +6,7 @@ import { Barrier, timeout } from 'vs/base/common/async'; import { debounce } from 'vs/base/common/decorators'; import { Emitter } from 'vs/base/common/event'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, MandatoryMutableDisposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { ILogService } from 'vs/platform/log/common/log'; import { ICommandDetectionCapability, TerminalCapability, ITerminalCommand, IHandleCommandOptions, ICommandInvalidationRequest, CommandInvalidationReason, ISerializedTerminalCommand, ISerializedCommandDetectionCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; import { ITerminalOutputMatch, ITerminalOutputMatcher } from 'vs/platform/terminal/common/terminal'; @@ -63,14 +63,13 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe private _exitCode: number | undefined; private _cwd: string | undefined; private _currentCommand: ICurrentPartialCommand = {}; - private _isWindowsPty: boolean = false; - private _onCursorMoveListener?: IDisposable; private _commandMarkers: IMarker[] = []; private _dimensions: ITerminalDimensions; private __isCommandStorageDisabled: boolean = false; private _handleCommandStartOptions?: IHandleCommandOptions; - private _commandStartedWindowsBarrier?: Barrier; - private _windowsPromptPollingInProcess: boolean = false; + + private _ptyHeuristicsHooks: ICommandDetectionHeuristicsHooks; + private _ptyHeuristics: MandatoryMutableDisposable; get commands(): readonly ITerminalCommand[] { return this._commands; } get executingCommand(): string | undefined { return this._currentCommand.command; } @@ -81,7 +80,7 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe } return undefined; } - get currentCommand(): ICurrentPartialCommand | undefined { + get currentCommand(): ICurrentPartialCommand { return this._currentCommand; } get cwd(): string | undefined { return this._cwd; } @@ -121,19 +120,31 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe private readonly _logService: ILogService ) { super(); + + // Set up platform-specific behaviors + const that = this; + this._ptyHeuristicsHooks = new class implements ICommandDetectionHeuristicsHooks { + get onCurrentCommandInvalidatedEmitter() { return that._onCurrentCommandInvalidated; } + get onCommandStartedEmitter() { return that._onCommandStarted; } + get onCommandExecutedEmitter() { return that._onCommandExecuted; } + get dimensions() { return that._dimensions; } + get isCommandStorageDisabled() { return that.__isCommandStorageDisabled; } + get commandMarkers() { return that._commandMarkers; } + set commandMarkers(value) { that._commandMarkers = value; } + get clearCommandsInViewport() { return that._clearCommandsInViewport.bind(that); } + }; + this._ptyHeuristics = this._register(new MandatoryMutableDisposable(new UnixPtyHeuristics(this._terminal, this, this._ptyHeuristicsHooks, this._logService))); + this._dimensions = { cols: this._terminal.cols, rows: this._terminal.rows }; this._register(this._terminal.onResize(e => this._handleResize(e))); this._register(this._terminal.onCursorMove(() => this._handleCursorMove())); - this._setupClearListeners(); } private _handleResize(e: { cols: number; rows: number }) { - if (this._isWindowsPty) { - this._preHandleResizeWindows(e); - } + this._ptyHeuristics.value.preHandleResize?.(e); this._dimensions.cols = e.cols; this._dimensions.rows = e.rows; } @@ -159,85 +170,6 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe } } - private _setupClearListeners() { - // Setup listeners for when clear is run in the shell. Since we don't know immediately if - // this is a Windows pty, listen to both routes and do the Windows check inside them - - // For a Windows backend we cannot listen to CSI J, instead we assume running clear or - // cls will clear all commands in the viewport. This is not perfect but it's right most - // of the time. - this._register(this.onBeforeCommandFinished(command => { - if (this._isWindowsPty) { - if (command.command.trim().toLowerCase() === 'clear' || command.command.trim().toLowerCase() === 'cls') { - this._clearCommandsInViewport(); - this._currentCommand.isInvalid = true; - this._onCurrentCommandInvalidated.fire({ reason: CommandInvalidationReason.Windows }); - } - } - })); - - // For non-Windows backends we can just listen to CSI J which is what the clear command - // typically emits. - this._terminal.parser.registerCsiHandler({ final: 'J' }, params => { - if (!this._isWindowsPty) { - if (params.length >= 1 && (params[0] === 2 || params[0] === 3)) { - this._clearCommandsInViewport(); - } - } - // We don't want to override xterm.js' default behavior, just augment it - return false; - }); - } - - private _preHandleResizeWindows(e: { cols: number; rows: number }) { - // Resize behavior is different under conpty; instead of bringing parts of the scrollback - // back into the viewport, new lines are inserted at the bottom (ie. the same behavior as if - // there was no scrollback). - // - // On resize this workaround will wait for a conpty reprint to occur by waiting for the - // cursor to move, it will then calculate the number of lines that the commands within the - // viewport _may have_ shifted. After verifying the content of the current line is - // incorrect, the line after shifting is checked and if that matches delete events are fired - // on the xterm.js buffer to move the markers. - // - // While a bit hacky, this approach is quite safe and seems to work great at least for pwsh. - const baseY = this._terminal.buffer.active.baseY; - const rowsDifference = e.rows - this._dimensions.rows; - // Only do when rows increase, do in the next frame as this needs to happen after - // conpty reprints the screen - if (rowsDifference > 0) { - this._waitForCursorMove().then(() => { - // Calculate the number of lines the content may have shifted, this will max out at - // scrollback count since the standard behavior will be used then - const potentialShiftedLineCount = Math.min(rowsDifference, baseY); - // For each command within the viewport, assume commands are in the correct order - for (let i = this.commands.length - 1; i >= 0; i--) { - const command = this.commands[i]; - if (!command.marker || command.marker.line < baseY || command.commandStartLineContent === undefined) { - break; - } - const line = this._terminal.buffer.active.getLine(command.marker.line); - if (!line || line.translateToString(true) === command.commandStartLineContent) { - continue; - } - const shiftedY = command.marker.line - potentialShiftedLineCount; - const shiftedLine = this._terminal.buffer.active.getLine(shiftedY); - if (shiftedLine?.translateToString(true) !== command.commandStartLineContent) { - continue; - } - // HACK: xterm.js doesn't expose this by design as it's an internal core - // function an embedder could easily do damage with. Additionally, this - // can't really be upstreamed since the event relies on shell integration to - // verify the shifting is necessary. - (this._terminal as any)._core._bufferService.buffer.lines.onDeleteEmitter.fire({ - index: this._terminal.buffer.active.baseY, - amount: potentialShiftedLineCount - }); - } - }); - } - } - private _clearCommandsInViewport(): void { // Find the number of commands on the tail end of the array that are within the viewport let count = 0; @@ -254,48 +186,77 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe } } - private _waitForCursorMove(): Promise { - const cursorX = this._terminal.buffer.active.cursorX; - const cursorY = this._terminal.buffer.active.cursorY; - let totalDelay = 0; - return new Promise((resolve, reject) => { - const interval = setInterval(() => { - if (cursorX !== this._terminal.buffer.active.cursorX || cursorY !== this._terminal.buffer.active.cursorY) { - resolve(); - clearInterval(interval); - return; - } - totalDelay += 10; - if (totalDelay > 1000) { - clearInterval(interval); - resolve(); - } - }, 10); - }); - } - setCwd(value: string) { this._cwd = value; } setIsWindowsPty(value: boolean) { - this._isWindowsPty = value; + if (value && !(this._ptyHeuristics.value instanceof WindowsPtyHeuristics)) { + const that = this; + this._ptyHeuristics.value = new WindowsPtyHeuristics( + this._terminal, + this, + new class { + get onCurrentCommandInvalidatedEmitter() { return that._onCurrentCommandInvalidated; } + get onCommandStartedEmitter() { return that._onCommandStarted; } + get onCommandExecutedEmitter() { return that._onCommandExecuted; } + get dimensions() { return that._dimensions; } + get isCommandStorageDisabled() { return that.__isCommandStorageDisabled; } + get commandMarkers() { return that._commandMarkers; } + set commandMarkers(value) { that._commandMarkers = value; } + get clearCommandsInViewport() { return that._clearCommandsInViewport; } + }, + this._logService + ); + } else if (!value && !(this._ptyHeuristics.value instanceof UnixPtyHeuristics)) { + this._ptyHeuristics.value = new UnixPtyHeuristics(this._terminal, this, this._ptyHeuristicsHooks, this._logService); + } } setIsCommandStorageDisabled(): void { this.__isCommandStorageDisabled = true; } + getCommandForLine(line: number): ITerminalCommand | ICurrentPartialCommand | undefined { + // Handle the current partial command first, anything below it's prompt is considered part + // of the current command + if (this._currentCommand.promptStartMarker && line >= this._currentCommand.promptStartMarker?.line) { + return this._currentCommand; + } + + // No commands + if (this._commands.length === 0) { + return undefined; + } + + // Line is before any registered commands + if (this._commands[0].marker!.line > line) { + return undefined; + } + + // Iterate backwards through commands to find the right one + for (let i = this.commands.length - 1; i >= 0; i--) { + if (this.commands[i].marker!.line <= line - 1) { + return this.commands[i]; + } + } + + return undefined; + } + getCwdForLine(line: number): string | undefined { // Handle the current partial command first, anything below it's prompt is considered part // of the current command if (this._currentCommand.promptStartMarker && line >= this._currentCommand.promptStartMarker?.line) { return this._cwd; } - // TODO: It would be more reliable to take the closest cwd above the line if it isn't found for the line - // TODO: Use a reverse for loop to find the line to avoid creating another array - const reversed = [...this._commands].reverse(); - return reversed.find(c => c.marker!.line <= line - 1)?.cwd; + + const command = this.getCommandForLine(line); + if (command && 'cwd' in command) { + return command.cwd; + } + + return undefined; } handlePromptStart(options?: IHandleCommandOptions): void { @@ -343,35 +304,375 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe this._logService.debug('CommandDetectionCapability#handleCommandStart', this._currentCommand.commandStartX, this._currentCommand.commandStartMarker?.line); return; } - if (this._isWindowsPty) { - this._handleCommandStartWindows(); - return; - } - this._currentCommand.commandStartX = this._terminal.buffer.active.cursorX; - this._currentCommand.commandStartMarker = options?.marker || this._terminal.registerMarker(0); - - // Clear executed as it must happen after command start - this._currentCommand.commandExecutedMarker?.dispose(); - this._currentCommand.commandExecutedMarker = undefined; - this._currentCommand.commandExecutedX = undefined; - for (const m of this._commandMarkers) { - m.dispose(); - } - this._commandMarkers.length = 0; - - this._onCommandStarted.fire({ marker: options?.marker || this._currentCommand.commandStartMarker, markProperties: options?.markProperties } as ITerminalCommand); - this._logService.debug('CommandDetectionCapability#handleCommandStart', this._currentCommand.commandStartX, this._currentCommand.commandStartMarker?.line); + this._ptyHeuristics.value.handleCommandStart(options); } - private async _handleCommandStartWindows(): Promise { + handleGenericCommand(options?: IHandleCommandOptions): void { + if (options?.markProperties?.disableCommandStorage) { + this.setIsCommandStorageDisabled(); + } + this.handlePromptStart(options); + this.handleCommandStart(options); + this.handleCommandExecuted(options); + this.handleCommandFinished(undefined, options); + } + + handleCommandExecuted(options?: IHandleCommandOptions): void { + this._ptyHeuristics.value.handleCommandExecuted(options); + } + + handleCommandFinished(exitCode: number | undefined, options?: IHandleCommandOptions): void { + this._ptyHeuristics.value?.preHandleCommandFinished?.(); + + this._currentCommand.commandFinishedMarker = options?.marker || this._terminal.registerMarker(0); + let command = this._currentCommand.command; + this._logService.debug('CommandDetectionCapability#handleCommandFinished', this._terminal.buffer.active.cursorX, this._currentCommand.commandFinishedMarker?.line, this._currentCommand.command, this._currentCommand); + this._exitCode = exitCode; + + // HACK: Handle a special case on some versions of bash where identical commands get merged + // in the output of `history`, this detects that case and sets the exit code to the the last + // command's exit code. This covered the majority of cases but will fail if the same command + // runs with a different exit code, that will need a more robust fix where we send the + // command ID and exit code over to the capability to adjust there. + if (this._exitCode === undefined) { + const lastCommand = this.commands.length > 0 ? this.commands[this.commands.length - 1] : undefined; + if (command && command.length > 0 && lastCommand?.command === command) { + this._exitCode = lastCommand.exitCode; + } + } + + if (this._currentCommand.commandStartMarker === undefined || !this._terminal.buffer.active) { + return; + } + + // When the command finishes and executed never fires the placeholder selector should be used. + if (this._exitCode === undefined && command === undefined) { + command = ''; + } + + this._ptyHeuristics.value?.postHandleCommandFinished?.(); + + if ((command !== undefined && !command.startsWith('\\')) || this._handleCommandStartOptions?.ignoreCommandLine) { + const buffer = this._terminal.buffer.active; + const timestamp = Date.now(); + const executedMarker = this._currentCommand.commandExecutedMarker; + const endMarker = this._currentCommand.commandFinishedMarker; + const newCommand: ITerminalCommand = { + command: this._handleCommandStartOptions?.ignoreCommandLine ? '' : (command || ''), + isTrusted: !!this._currentCommand.isTrusted, + promptStartMarker: this._currentCommand.promptStartMarker, + marker: this._currentCommand.commandStartMarker, + startX: this._currentCommand.commandStartX, + endMarker, + executedMarker, + executedX: this._currentCommand.commandExecutedX, + timestamp, + cwd: this._cwd, + exitCode: this._exitCode, + commandStartLineContent: this._currentCommand.commandStartLineContent, + hasOutput: () => !executedMarker?.isDisposed && !endMarker?.isDisposed && !!(executedMarker && endMarker && executedMarker?.line < endMarker!.line), + getOutput: () => getOutputForCommand(executedMarker, endMarker, buffer), + getOutputMatch: (outputMatcher: ITerminalOutputMatcher) => getOutputMatchForCommand(this._ptyHeuristics.value instanceof WindowsPtyHeuristics && (executedMarker?.line === endMarker?.line) ? this._currentCommand.commandStartMarker : executedMarker, endMarker, buffer, this._terminal.cols, outputMatcher), + markProperties: options?.markProperties + }; + this._commands.push(newCommand); + this._logService.debug('CommandDetectionCapability#onCommandFinished', newCommand); + + this._onBeforeCommandFinished.fire(newCommand); + if (!this._currentCommand.isInvalid) { + this._onCommandFinished.fire(newCommand); + } + } + this._currentCommand.previousCommandMarker = this._currentCommand.commandStartMarker; + this._currentCommand = {}; + this._handleCommandStartOptions = undefined; + } + + setCommandLine(commandLine: string, isTrusted: boolean) { + this._logService.debug('CommandDetectionCapability#setCommandLine', commandLine, isTrusted); + this._currentCommand.command = commandLine; + this._currentCommand.isTrusted = isTrusted; + } + + serialize(): ISerializedCommandDetectionCapability { + const commands: ISerializedTerminalCommand[] = this.commands.map(e => { + return { + promptStartLine: e.promptStartMarker?.line, + startLine: e.marker?.line, + startX: undefined, + endLine: e.endMarker?.line, + executedLine: e.executedMarker?.line, + executedX: e.executedX, + command: this.__isCommandStorageDisabled ? '' : e.command, + isTrusted: e.isTrusted, + cwd: e.cwd, + exitCode: e.exitCode, + commandStartLineContent: e.commandStartLineContent, + timestamp: e.timestamp, + markProperties: e.markProperties, + aliases: e.aliases + }; + }); + if (this._currentCommand.commandStartMarker) { + commands.push({ + promptStartLine: this._currentCommand.promptStartMarker?.line, + startLine: this._currentCommand.commandStartMarker.line, + startX: this._currentCommand.commandStartX, + endLine: undefined, + executedLine: undefined, + executedX: undefined, + command: '', + isTrusted: true, + cwd: this._cwd, + exitCode: undefined, + commandStartLineContent: undefined, + timestamp: 0, + markProperties: undefined + }); + } + return { + isWindowsPty: this._ptyHeuristics.value instanceof WindowsPtyHeuristics, + commands + }; + } + + deserialize(serialized: ISerializedCommandDetectionCapability): void { + if (serialized.isWindowsPty) { + this.setIsWindowsPty(serialized.isWindowsPty); + } + const buffer = this._terminal.buffer.normal; + for (const e of serialized.commands) { + const marker = e.startLine !== undefined ? this._terminal.registerMarker(e.startLine - (buffer.baseY + buffer.cursorY)) : undefined; + // Check for invalid command + if (!marker) { + continue; + } + const promptStartMarker = e.promptStartLine !== undefined ? this._terminal.registerMarker(e.promptStartLine - (buffer.baseY + buffer.cursorY)) : undefined; + // Partial command + if (!e.endLine) { + this._currentCommand.commandStartMarker = marker; + this._currentCommand.commandStartX = e.startX; + if (promptStartMarker) { + this._currentCommand.promptStartMarker = promptStartMarker; + } + this._cwd = e.cwd; + this._onCommandStarted.fire({ marker } as ITerminalCommand); + continue; + } + // Full command + const endMarker = e.endLine !== undefined ? this._terminal.registerMarker(e.endLine - (buffer.baseY + buffer.cursorY)) : undefined; + const executedMarker = e.executedLine !== undefined ? this._terminal.registerMarker(e.executedLine - (buffer.baseY + buffer.cursorY)) : undefined; + const newCommand: ITerminalCommand = { + command: this.__isCommandStorageDisabled ? '' : e.command, + isTrusted: e.isTrusted, + promptStartMarker, + marker, + startX: e.startX, + endMarker, + executedMarker, + executedX: e.executedX, + timestamp: e.timestamp, + cwd: e.cwd, + commandStartLineContent: e.commandStartLineContent, + exitCode: e.exitCode, + hasOutput: () => !executedMarker?.isDisposed && !endMarker?.isDisposed && !!(executedMarker && endMarker && executedMarker.line < endMarker.line), + getOutput: () => getOutputForCommand(executedMarker, endMarker, buffer), + getOutputMatch: (outputMatcher: ITerminalOutputMatcher) => getOutputMatchForCommand(this._ptyHeuristics.value instanceof WindowsPtyHeuristics && (executedMarker?.line === endMarker?.line) ? marker : executedMarker, endMarker, buffer, this._terminal.cols, outputMatcher), + markProperties: e.markProperties, + wasReplayed: true + }; + this._commands.push(newCommand); + this._logService.debug('CommandDetectionCapability#onCommandFinished', newCommand); + this._onCommandFinished.fire(newCommand); + } + } +} + +/** + * Additional hooks to private methods on {@link CommandDetectionCapability} that are needed by the + * heuristics objects. + */ +interface ICommandDetectionHeuristicsHooks { + readonly onCurrentCommandInvalidatedEmitter: Emitter; + readonly onCommandExecutedEmitter: Emitter; + readonly onCommandStartedEmitter: Emitter; + readonly dimensions: ITerminalDimensions; + readonly isCommandStorageDisabled: boolean; + + commandMarkers: IMarker[]; + + clearCommandsInViewport(): void; +} + +type IPtyHeuristics = ( + // All optional methods + Partial & Partial & + // All common methods + (UnixPtyHeuristics | WindowsPtyHeuristics) & + IDisposable +); + +/** + * Non-Windows-specific behavior. + */ +class UnixPtyHeuristics extends Disposable { + constructor( + private readonly _terminal: Terminal, + private readonly _capability: CommandDetectionCapability, + private readonly _hooks: ICommandDetectionHeuristicsHooks, + private readonly _logService: ILogService + ) { + super(); + this._register(_terminal.parser.registerCsiHandler({ final: 'J' }, params => { + if (params.length >= 1 && (params[0] === 2 || params[0] === 3)) { + _hooks.clearCommandsInViewport(); + } + // We don't want to override xterm.js' default behavior, just augment it + return false; + })); + } + + async handleCommandStart(options?: IHandleCommandOptions) { + const currentCommand = this._capability.currentCommand; + currentCommand.commandStartX = this._terminal.buffer.active.cursorX; + currentCommand.commandStartMarker = options?.marker || this._terminal.registerMarker(0); + + // Clear executed as it must happen after command start + currentCommand.commandExecutedMarker?.dispose(); + currentCommand.commandExecutedMarker = undefined; + currentCommand.commandExecutedX = undefined; + for (const m of this._hooks.commandMarkers) { + m.dispose(); + } + this._hooks.commandMarkers.length = 0; + + this._hooks.onCommandStartedEmitter.fire({ marker: options?.marker || currentCommand.commandStartMarker, markProperties: options?.markProperties } as ITerminalCommand); + this._logService.debug('CommandDetectionCapability#handleCommandStart', currentCommand.commandStartX, currentCommand.commandStartMarker?.line); + } + + handleCommandExecuted(options?: IHandleCommandOptions) { + const currentCommand = this._capability.currentCommand; + currentCommand.commandExecutedMarker = options?.marker || this._terminal.registerMarker(0); + currentCommand.commandExecutedX = this._terminal.buffer.active.cursorX; + this._logService.debug('CommandDetectionCapability#handleCommandExecuted', currentCommand.commandExecutedX, currentCommand.commandExecutedMarker?.line); + + // Sanity check optional props + if (!currentCommand.commandStartMarker || !currentCommand.commandExecutedMarker || currentCommand.commandStartX === undefined) { + return; + } + + // Calculate the command + currentCommand.command = this._hooks.isCommandStorageDisabled ? '' : this._terminal.buffer.active.getLine(currentCommand.commandStartMarker.line)?.translateToString(true, currentCommand.commandStartX, currentCommand.commandRightPromptStartX).trim(); + let y = currentCommand.commandStartMarker.line + 1; + const commandExecutedLine = currentCommand.commandExecutedMarker.line; + for (; y < commandExecutedLine; y++) { + const line = this._terminal.buffer.active.getLine(y); + if (line) { + const continuation = currentCommand.continuations?.find(e => e.marker.line === y); + if (continuation) { + currentCommand.command += '\n'; + } + const startColumn = continuation?.end ?? 0; + currentCommand.command += line.translateToString(true, startColumn); + } + } + if (y === commandExecutedLine) { + currentCommand.command += this._terminal.buffer.active.getLine(commandExecutedLine)?.translateToString(true, undefined, currentCommand.commandExecutedX) || ''; + } + this._hooks.onCommandExecutedEmitter.fire(); + } +} + +/** + * An object that integrated with and decorates the command detection capability to add heuristics + * that adjust various markers to work better with Windows and ConPTY. This isn't depended upon the + * frontend OS, or even the backend OS, but the `IsWindows` property which technically a non-Windows + * client can emit (for example in tests). + */ +class WindowsPtyHeuristics extends Disposable { + + private _onCursorMoveListener = this._register(new MutableDisposable()); + private _commandStartedWindowsBarrier?: Barrier; + private _windowsPromptPollingInProcess: boolean = false; + + constructor( + private readonly _terminal: Terminal, + private readonly _capability: CommandDetectionCapability, + private readonly _hooks: ICommandDetectionHeuristicsHooks, + @ILogService private readonly _logService: ILogService, + ) { + super(); + + this._register(this._capability.onBeforeCommandFinished(command => { + // For a Windows backend we cannot listen to CSI J, instead we assume running clear or + // cls will clear all commands in the viewport. This is not perfect but it's right most + // of the time. + if (command.command.trim().toLowerCase() === 'clear' || command.command.trim().toLowerCase() === 'cls') { + this._hooks.clearCommandsInViewport(); + this._capability.currentCommand.isInvalid = true; + this._hooks.onCurrentCommandInvalidatedEmitter.fire({ reason: CommandInvalidationReason.Windows }); + } + })); + } + + preHandleResize(e: { cols: number; rows: number }) { + // Resize behavior is different under conpty; instead of bringing parts of the scrollback + // back into the viewport, new lines are inserted at the bottom (ie. the same behavior as if + // there was no scrollback). + // + // On resize this workaround will wait for a conpty reprint to occur by waiting for the + // cursor to move, it will then calculate the number of lines that the commands within the + // viewport _may have_ shifted. After verifying the content of the current line is + // incorrect, the line after shifting is checked and if that matches delete events are fired + // on the xterm.js buffer to move the markers. + // + // While a bit hacky, this approach is quite safe and seems to work great at least for pwsh. + const baseY = this._terminal.buffer.active.baseY; + const rowsDifference = e.rows - this._hooks.dimensions.rows; + // Only do when rows increase, do in the next frame as this needs to happen after + // conpty reprints the screen + if (rowsDifference > 0) { + this._waitForCursorMove().then(() => { + // Calculate the number of lines the content may have shifted, this will max out at + // scrollback count since the standard behavior will be used then + const potentialShiftedLineCount = Math.min(rowsDifference, baseY); + // For each command within the viewport, assume commands are in the correct order + for (let i = this._capability.commands.length - 1; i >= 0; i--) { + const command = this._capability.commands[i]; + if (!command.marker || command.marker.line < baseY || command.commandStartLineContent === undefined) { + break; + } + const line = this._terminal.buffer.active.getLine(command.marker.line); + if (!line || line.translateToString(true) === command.commandStartLineContent) { + continue; + } + const shiftedY = command.marker.line - potentialShiftedLineCount; + const shiftedLine = this._terminal.buffer.active.getLine(shiftedY); + if (shiftedLine?.translateToString(true) !== command.commandStartLineContent) { + continue; + } + // HACK: xterm.js doesn't expose this by design as it's an internal core + // function an embedder could easily do damage with. Additionally, this + // can't really be upstreamed since the event relies on shell integration to + // verify the shifting is necessary. + (this._terminal as any)._core._bufferService.buffer.lines.onDeleteEmitter.fire({ + index: this._terminal.buffer.active.baseY, + amount: potentialShiftedLineCount + }); + } + }); + } + } + + async handleCommandStart() { if (this._windowsPromptPollingInProcess) { this._windowsPromptPollingInProcess = false; } this._commandStartedWindowsBarrier = new Barrier(); - this._currentCommand.commandStartX = this._terminal.buffer.active.cursorX; + this._capability.currentCommand.commandStartX = this._terminal.buffer.active.cursorX; // On Windows track all cursor movements after the command start sequence - this._commandMarkers.length = 0; + this._hooks.commandMarkers.length = 0; let prompt: string | undefined = this._getWindowsPrompt(); // Conpty could have the wrong cursor position at this point. @@ -394,7 +695,7 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe this._logService.debug('CommandDetectionCapability#_handleCommandStartWindows reached max attempts, ', this._cursorOnNextLine(), this._getWindowsPrompt()); } else if (prompt) { // use the regex to set the position as it's possible input has occurred - this._currentCommand.commandStartX = prompt.length; + this._capability.currentCommand.commandStartX = prompt.length; } } else { // HACK: Fire command started on the following frame on Windows to allow the cursor @@ -403,30 +704,135 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe await timeout(0); } - if (!this._currentCommand.commandExecutedMarker) { - this._onCursorMoveListener = this._terminal.onCursorMove(() => { - if (this._commandMarkers.length === 0 || this._commandMarkers[this._commandMarkers.length - 1].line !== this._terminal.buffer.active.cursorY) { + if (!this._capability.currentCommand.commandExecutedMarker) { + this._onCursorMoveListener.value = this._terminal.onCursorMove(() => { + if (this._hooks.commandMarkers.length === 0 || this._hooks.commandMarkers[this._hooks.commandMarkers.length - 1].line !== this._terminal.buffer.active.cursorY) { const marker = this._terminal.registerMarker(0); if (marker) { - this._commandMarkers.push(marker); + this._hooks.commandMarkers.push(marker); } } }); } - this._currentCommand.commandStartMarker = this._terminal.registerMarker(0); - if (this._currentCommand.commandStartMarker) { - const line = this._terminal.buffer.active.getLine(this._currentCommand.commandStartMarker.line); + this._capability.currentCommand.commandStartMarker = this._terminal.registerMarker(0); + if (this._capability.currentCommand.commandStartMarker) { + const line = this._terminal.buffer.active.getLine(this._capability.currentCommand.commandStartMarker.line); if (line) { - this._currentCommand.commandStartLineContent = line.translateToString(true); + this._capability.currentCommand.commandStartLineContent = line.translateToString(true); } } - this._onCommandStarted.fire({ marker: this._currentCommand.commandStartMarker } as ITerminalCommand); - this._logService.debug('CommandDetectionCapability#_handleCommandStartWindows', this._currentCommand.commandStartX, this._currentCommand.commandStartMarker?.line); + this._hooks.onCommandStartedEmitter.fire({ marker: this._capability.currentCommand.commandStartMarker } as ITerminalCommand); + this._logService.debug('CommandDetectionCapability#_handleCommandStartWindows', this._capability.currentCommand.commandStartX, this._capability.currentCommand.commandStartMarker?.line); this._commandStartedWindowsBarrier.open(); } + handleCommandExecuted(options: IHandleCommandOptions | undefined) { + // TODO: Flush here? + // await this._commandStartedWindowsBarrier?.wait(); + // On Windows, use the gathered cursor move markers to correct the command start and + // executed markers + this._onCursorMoveListener.clear(); + this._evaluateCommandMarkers(); + this._capability.currentCommand.commandExecutedX = this._terminal.buffer.active.cursorX; + this._hooks.onCommandExecutedEmitter.fire(); + this._logService.debug('CommandDetectionCapability#handleCommandExecuted', this._capability.currentCommand.commandExecutedX, this._capability.currentCommand.commandExecutedMarker?.line); + } + + preHandleCommandFinished() { + if (this._capability.currentCommand.commandExecutedMarker) { + return; + } + // This is done on command finished just in case command executed never happens (for example + // PSReadLine tab completion) + if (this._hooks.commandMarkers.length === 0) { + // If the command start timeout doesn't happen before command finished, just use the + // current marker. + if (!this._capability.currentCommand.commandStartMarker) { + this._capability.currentCommand.commandStartMarker = this._terminal.registerMarker(0); + } + if (this._capability.currentCommand.commandStartMarker) { + this._hooks.commandMarkers.push(this._capability.currentCommand.commandStartMarker); + } + } + this._evaluateCommandMarkers(); + } + + postHandleCommandFinished(): void { + const currentCommand = this._capability.currentCommand; + const commandText = currentCommand.command; + const commandLine = currentCommand.commandStartMarker?.line; + const executedLine = currentCommand.commandExecutedMarker?.line; + if ( + !commandText || commandText.length === 0 || + commandLine === undefined || commandLine === -1 || + executedLine === undefined || executedLine === -1 + ) { + return; + } + + // Scan downwards from the command start line and search for every character in the actual + // command line. This may end up matching the wrong characters, but it shouldn't matter at + // least in the typical case as the entire command will still get matched. + let current = 0; + let found = false; + for (let i = commandLine; i <= executedLine; i++) { + const line = this._terminal.buffer.active.getLine(i); + if (!line) { + break; + } + const text = line.translateToString(true); + for (let j = 0; j < text.length; j++) { + // Skip whitespace in case it was not actually rendered or could be trimmed from the + // end of the line + while (commandText.length < current && commandText[current] === ' ') { + current++; + } + + // Character match + if (text[j] === commandText[current]) { + current++; + } + + // Full command match + if (current === commandText.length) { + // It's ambiguous whether the command executed marker should ideally appear at + // the end of the line or at the beginning of the next line. Since it's more + // useful for extracting the command at the end of the current line we go with + // that. + const wrapsToNextLine = j >= this._terminal.cols - 1; + currentCommand.commandExecutedMarker = this._terminal.registerMarker(i - (this._terminal.buffer.active.baseY + this._terminal.buffer.active.cursorY) + (wrapsToNextLine ? 1 : 0)); + currentCommand.commandExecutedX = wrapsToNextLine ? 0 : j + 1; + found = true; + break; + } + } + if (found) { + break; + } + } + } + + private _evaluateCommandMarkers(): void { + // On Windows, use the gathered cursor move markers to correct the command start and + // executed markers. + if (this._hooks.commandMarkers.length === 0) { + return; + } + this._hooks.commandMarkers = this._hooks.commandMarkers.sort((a, b) => a.line - b.line); + this._capability.currentCommand.commandStartMarker = this._hooks.commandMarkers[0]; + if (this._capability.currentCommand.commandStartMarker) { + const line = this._terminal.buffer.active.getLine(this._capability.currentCommand.commandStartMarker.line); + if (line) { + this._capability.currentCommand.commandStartLineContent = line.translateToString(true); + } + } + this._capability.currentCommand.commandExecutedMarker = this._hooks.commandMarkers[this._hooks.commandMarkers.length - 1]; + // Fire this now to prevent issues like #197409 + this._hooks.onCommandExecutedEmitter.fire(); + } + private _cursorOnNextLine(): boolean { - const lastCommand = this.commands.at(-1); + const lastCommand = this._capability.commands.at(-1); // There is only a single command, so this check is unnecessary if (!lastCommand) { @@ -439,6 +845,26 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe return cursorYAbsolute > lastCommandYAbsolute; } + private _waitForCursorMove(): Promise { + const cursorX = this._terminal.buffer.active.cursorX; + const cursorY = this._terminal.buffer.active.cursorY; + let totalDelay = 0; + return new Promise((resolve, reject) => { + const interval = setInterval(() => { + if (cursorX !== this._terminal.buffer.active.cursorX || cursorY !== this._terminal.buffer.active.cursorY) { + resolve(); + clearInterval(interval); + return; + } + totalDelay += 10; + if (totalDelay > 1000) { + clearInterval(interval); + resolve(); + } + }, 10); + }); + } + private _getWindowsPrompt(): string | undefined { const line = this._terminal.buffer.active.getLine(this._terminal.buffer.active.baseY + this._terminal.buffer.active.cursorY); if (!line) { @@ -483,252 +909,6 @@ export class CommandDetectionCapability extends Disposable implements ICommandDe } return prompt; } - - handleGenericCommand(options?: IHandleCommandOptions): void { - if (options?.markProperties?.disableCommandStorage) { - this.setIsCommandStorageDisabled(); - } - this.handlePromptStart(options); - this.handleCommandStart(options); - this.handleCommandExecuted(options); - this.handleCommandFinished(undefined, options); - } - - handleCommandExecuted(options?: IHandleCommandOptions): void { - if (this._isWindowsPty) { - this._handleCommandExecutedWindows(); - return; - } - - this._currentCommand.commandExecutedMarker = options?.marker || this._terminal.registerMarker(0); - this._currentCommand.commandExecutedX = this._terminal.buffer.active.cursorX; - this._logService.debug('CommandDetectionCapability#handleCommandExecuted', this._currentCommand.commandExecutedX, this._currentCommand.commandExecutedMarker?.line); - - // Sanity check optional props - if (!this._currentCommand.commandStartMarker || !this._currentCommand.commandExecutedMarker || this._currentCommand.commandStartX === undefined) { - return; - } - - // Calculate the command - this._currentCommand.command = this.__isCommandStorageDisabled ? '' : this._terminal.buffer.active.getLine(this._currentCommand.commandStartMarker.line)?.translateToString(true, this._currentCommand.commandStartX, this._currentCommand.commandRightPromptStartX).trim(); - let y = this._currentCommand.commandStartMarker.line + 1; - const commandExecutedLine = this._currentCommand.commandExecutedMarker.line; - for (; y < commandExecutedLine; y++) { - const line = this._terminal.buffer.active.getLine(y); - if (line) { - const continuation = this._currentCommand.continuations?.find(e => e.marker.line === y); - if (continuation) { - this._currentCommand.command += '\n'; - } - const startColumn = continuation?.end ?? 0; - this._currentCommand.command += line.translateToString(true, startColumn); - } - } - if (y === commandExecutedLine) { - this._currentCommand.command += this._terminal.buffer.active.getLine(commandExecutedLine)?.translateToString(true, undefined, this._currentCommand.commandExecutedX) || ''; - } - this._onCommandExecuted.fire(); - } - - private async _handleCommandExecutedWindows(): Promise { - await this._commandStartedWindowsBarrier?.wait(); - // On Windows, use the gathered cursor move markers to correct the command start and - // executed markers - this._onCursorMoveListener?.dispose(); - this._onCursorMoveListener = undefined; - this._evaluateCommandMarkersWindows(); - this._currentCommand.commandExecutedX = this._terminal.buffer.active.cursorX; - this._onCommandExecuted.fire(); - this._logService.debug('CommandDetectionCapability#handleCommandExecuted', this._currentCommand.commandExecutedX, this._currentCommand.commandExecutedMarker?.line); - } - - handleCommandFinished(exitCode: number | undefined, options?: IHandleCommandOptions): void { - if (this._isWindowsPty) { - this._preHandleCommandFinishedWindows(); - } - - this._currentCommand.commandFinishedMarker = options?.marker || this._terminal.registerMarker(0); - let command = this._currentCommand.command; - this._logService.debug('CommandDetectionCapability#handleCommandFinished', this._terminal.buffer.active.cursorX, this._currentCommand.commandFinishedMarker?.line, this._currentCommand.command, this._currentCommand); - this._exitCode = exitCode; - - // HACK: Handle a special case on some versions of bash where identical commands get merged - // in the output of `history`, this detects that case and sets the exit code to the the last - // command's exit code. This covered the majority of cases but will fail if the same command - // runs with a different exit code, that will need a more robust fix where we send the - // command ID and exit code over to the capability to adjust there. - if (this._exitCode === undefined) { - const lastCommand = this.commands.length > 0 ? this.commands[this.commands.length - 1] : undefined; - if (command && command.length > 0 && lastCommand?.command === command) { - this._exitCode = lastCommand.exitCode; - } - } - - if (this._currentCommand.commandStartMarker === undefined || !this._terminal.buffer.active) { - return; - } - - // When the command finishes and executed never fires the placeholder selector should be used. - if (this._exitCode === undefined && command === undefined) { - command = ''; - } - - if ((command !== undefined && !command.startsWith('\\')) || this._handleCommandStartOptions?.ignoreCommandLine) { - const buffer = this._terminal.buffer.active; - const timestamp = Date.now(); - const executedMarker = this._currentCommand.commandExecutedMarker; - const endMarker = this._currentCommand.commandFinishedMarker; - const newCommand: ITerminalCommand = { - command: this._handleCommandStartOptions?.ignoreCommandLine ? '' : (command || ''), - isTrusted: !!this._currentCommand.isTrusted, - marker: this._currentCommand.commandStartMarker, - endMarker, - executedMarker, - timestamp, - cwd: this._cwd, - exitCode: this._exitCode, - commandStartLineContent: this._currentCommand.commandStartLineContent, - hasOutput: () => !executedMarker?.isDisposed && !endMarker?.isDisposed && !!(executedMarker && endMarker && executedMarker?.line < endMarker!.line), - getOutput: () => getOutputForCommand(executedMarker, endMarker, buffer), - getOutputMatch: (outputMatcher: ITerminalOutputMatcher) => getOutputMatchForCommand(this._isWindowsPty && (executedMarker?.line === endMarker?.line) ? this._currentCommand.commandStartMarker : executedMarker, endMarker, buffer, this._terminal.cols, outputMatcher), - markProperties: options?.markProperties - }; - this._commands.push(newCommand); - this._logService.debug('CommandDetectionCapability#onCommandFinished', newCommand); - - this._onBeforeCommandFinished.fire(newCommand); - if (!this._currentCommand.isInvalid) { - this._onCommandFinished.fire(newCommand); - } - } - this._currentCommand.previousCommandMarker = this._currentCommand.commandStartMarker; - this._currentCommand = {}; - this._handleCommandStartOptions = undefined; - } - - private async _preHandleCommandFinishedWindows(): Promise { - if (this._currentCommand.commandExecutedMarker) { - return; - } - // This is done on command finished just in case command executed never happens (for example - // PSReadLine tab completion) - if (this._commandMarkers.length === 0) { - // If the command start timeout doesn't happen before command finished, just use the - // current marker. - if (!this._currentCommand.commandStartMarker) { - this._currentCommand.commandStartMarker = this._terminal.registerMarker(0); - } - if (this._currentCommand.commandStartMarker) { - this._commandMarkers.push(this._currentCommand.commandStartMarker); - } - } - this._evaluateCommandMarkersWindows(); - } - - private _evaluateCommandMarkersWindows(): void { - // On Windows, use the gathered cursor move markers to correct the command start and - // executed markers. - if (this._commandMarkers.length === 0) { - return; - } - this._commandMarkers = this._commandMarkers.sort((a, b) => a.line - b.line); - this._currentCommand.commandStartMarker = this._commandMarkers[0]; - if (this._currentCommand.commandStartMarker) { - const line = this._terminal.buffer.active.getLine(this._currentCommand.commandStartMarker.line); - if (line) { - this._currentCommand.commandStartLineContent = line.translateToString(true); - } - } - this._currentCommand.commandExecutedMarker = this._commandMarkers[this._commandMarkers.length - 1]; - } - - setCommandLine(commandLine: string, isTrusted: boolean) { - this._logService.debug('CommandDetectionCapability#setCommandLine', commandLine, isTrusted); - this._currentCommand.command = commandLine; - this._currentCommand.isTrusted = isTrusted; - } - - serialize(): ISerializedCommandDetectionCapability { - const commands: ISerializedTerminalCommand[] = this.commands.map(e => { - return { - startLine: e.marker?.line, - startX: undefined, - endLine: e.endMarker?.line, - executedLine: e.executedMarker?.line, - command: this.__isCommandStorageDisabled ? '' : e.command, - isTrusted: e.isTrusted, - cwd: e.cwd, - exitCode: e.exitCode, - commandStartLineContent: e.commandStartLineContent, - timestamp: e.timestamp, - markProperties: e.markProperties, - aliases: e.aliases - }; - }); - if (this._currentCommand.commandStartMarker) { - commands.push({ - startLine: this._currentCommand.commandStartMarker.line, - startX: this._currentCommand.commandStartX, - endLine: undefined, - executedLine: undefined, - command: '', - isTrusted: true, - cwd: this._cwd, - exitCode: undefined, - commandStartLineContent: undefined, - timestamp: 0, - markProperties: undefined - }); - } - return { - isWindowsPty: this._isWindowsPty, - commands - }; - } - - deserialize(serialized: ISerializedCommandDetectionCapability): void { - if (serialized.isWindowsPty) { - this.setIsWindowsPty(serialized.isWindowsPty); - } - const buffer = this._terminal.buffer.normal; - for (const e of serialized.commands) { - const marker = e.startLine !== undefined ? this._terminal.registerMarker(e.startLine - (buffer.baseY + buffer.cursorY)) : undefined; - // Check for invalid command - if (!marker) { - continue; - } - // Partial command - if (!e.endLine) { - this._currentCommand.commandStartMarker = marker; - this._currentCommand.commandStartX = e.startX; - this._cwd = e.cwd; - this._onCommandStarted.fire({ marker } as ITerminalCommand); - continue; - } - // Full command - const endMarker = e.endLine !== undefined ? this._terminal.registerMarker(e.endLine - (buffer.baseY + buffer.cursorY)) : undefined; - const executedMarker = e.executedLine !== undefined ? this._terminal.registerMarker(e.executedLine - (buffer.baseY + buffer.cursorY)) : undefined; - const newCommand: ITerminalCommand = { - command: this.__isCommandStorageDisabled ? '' : e.command, - isTrusted: e.isTrusted, - marker, - endMarker, - executedMarker, - timestamp: e.timestamp, - cwd: e.cwd, - commandStartLineContent: e.commandStartLineContent, - exitCode: e.exitCode, - hasOutput: () => !executedMarker?.isDisposed && !endMarker?.isDisposed && !!(executedMarker && endMarker && executedMarker.line < endMarker.line), - getOutput: () => getOutputForCommand(executedMarker, endMarker, buffer), - getOutputMatch: (outputMatcher: ITerminalOutputMatcher) => getOutputMatchForCommand(this._isWindowsPty && (executedMarker?.line === endMarker?.line) ? marker : executedMarker, endMarker, buffer, this._terminal.cols, outputMatcher), - markProperties: e.markProperties, - wasReplayed: true - }; - this._commands.push(newCommand); - this._logService.debug('CommandDetectionCapability#onCommandFinished', newCommand); - this._onCommandFinished.fire(newCommand); - } - } } function getOutputForCommand(executedMarker: IMarker | undefined, endMarker: IMarker | undefined, buffer: IBuffer): string | undefined { @@ -846,6 +1026,22 @@ export function getLinesForCommand(buffer: IBuffer, command: ITerminalCommand, c return lines; } +export function getPromptRowCount(command: ITerminalCommand, buffer: IBuffer): number { + if (!command.marker) { + return 1; + } + let promptRowCount = 1; + let promptStartLine = command.marker.line; + if (command.promptStartMarker) { + promptStartLine = Math.min(command.promptStartMarker?.line ?? command.marker.line, command.marker.line); + // Trim any leading whitespace-only lines to retain vertical space + while (promptStartLine < command.marker.line && (buffer.getLine(promptStartLine)?.translateToString(true) ?? '').length === 0) { + promptStartLine++; + } + promptRowCount = command.marker.line - promptStartLine + 1; + } + return promptRowCount; +} function getXtermLineContent(buffer: IBuffer, lineStart: number, lineEnd: number, cols: number): string { // Cap the maximum number of lines generated to prevent potential performance problems. This is diff --git a/src/vs/platform/terminal/common/terminal.ts b/src/vs/platform/terminal/common/terminal.ts index 52cb584712a..430097703dc 100644 --- a/src/vs/platform/terminal/common/terminal.ts +++ b/src/vs/platform/terminal/common/terminal.ts @@ -118,6 +118,7 @@ export const enum TerminalSettingId { FocusAfterRun = 'terminal.integrated.focusAfterRun', AccessibleViewPreserveCursorPosition = 'terminal.integrated.accessibleViewPreserveCursorPosition', AccessibleViewFocusOnCommandExecution = 'terminal.integrated.accessibleViewFocusOnCommandExecution', + EnableStickyScroll = 'terminal.integrated.enableStickyScroll', // Debug settings that are hidden from user diff --git a/src/vs/platform/userDataProfile/browser/userDataProfile.ts b/src/vs/platform/userDataProfile/browser/userDataProfile.ts index 044e7ee5888..7af957185d2 100644 --- a/src/vs/platform/userDataProfile/browser/userDataProfile.ts +++ b/src/vs/platform/userDataProfile/browser/userDataProfile.ts @@ -67,7 +67,7 @@ export class BrowserUserDataProfilesService extends UserDataProfilesService impl protected override getStoredProfiles(): StoredUserDataProfile[] { try { - const value = window.localStorage.getItem(UserDataProfilesService.PROFILES_KEY); + const value = localStorage.getItem(UserDataProfilesService.PROFILES_KEY); if (value) { return revive(JSON.parse(value)); } @@ -84,19 +84,19 @@ export class BrowserUserDataProfilesService extends UserDataProfilesService impl } protected override saveStoredProfiles(storedProfiles: StoredUserDataProfile[]): void { - window.localStorage.setItem(UserDataProfilesService.PROFILES_KEY, JSON.stringify(storedProfiles)); + localStorage.setItem(UserDataProfilesService.PROFILES_KEY, JSON.stringify(storedProfiles)); } protected override getStoredProfileAssociations(): StoredProfileAssociations { const migrateKey = 'profileAssociationsMigration'; try { - const value = window.localStorage.getItem(UserDataProfilesService.PROFILE_ASSOCIATIONS_KEY); + const value = localStorage.getItem(UserDataProfilesService.PROFILE_ASSOCIATIONS_KEY); if (value) { let associations: StoredProfileAssociations = JSON.parse(value); - if (!window.localStorage.getItem(migrateKey)) { + if (!localStorage.getItem(migrateKey)) { associations = this.migrateStoredProfileAssociations(associations); this.saveStoredProfileAssociations(associations); - window.localStorage.setItem(migrateKey, 'true'); + localStorage.setItem(migrateKey, 'true'); } return associations; } @@ -108,7 +108,7 @@ export class BrowserUserDataProfilesService extends UserDataProfilesService impl } protected override saveStoredProfileAssociations(storedProfileAssociations: StoredProfileAssociations): void { - window.localStorage.setItem(UserDataProfilesService.PROFILE_ASSOCIATIONS_KEY, JSON.stringify(storedProfileAssociations)); + localStorage.setItem(UserDataProfilesService.PROFILE_ASSOCIATIONS_KEY, JSON.stringify(storedProfileAssociations)); } } diff --git a/src/vs/platform/window/electron-sandbox/window.ts b/src/vs/platform/window/electron-sandbox/window.ts index 692dbb9e44c..55c34942677 100644 --- a/src/vs/platform/window/electron-sandbox/window.ts +++ b/src/vs/platform/window/electron-sandbox/window.ts @@ -5,6 +5,7 @@ import { getZoomLevel, setZoomFactor, setZoomLevel } from 'vs/base/browser/browser'; import { getWindows } from 'vs/base/browser/dom'; +import { mainWindow } from 'vs/base/browser/window'; import { ISandboxGlobals, ipcRenderer, webFrame } from 'vs/base/parts/sandbox/electron-sandbox/globals'; import { zoomLevelToZoomFactor } from 'vs/platform/window/common/window'; @@ -21,7 +22,7 @@ export function applyZoom(zoomLevel: number): void { } function getGlobals(win: Window): ISandboxGlobals | undefined { - if (win === window) { + if (win === mainWindow) { // main window return { ipcRenderer, webFrame }; } else { diff --git a/src/vs/workbench/api/browser/extensionHost.contribution.ts b/src/vs/workbench/api/browser/extensionHost.contribution.ts index b34ef1570cc..a232035c72b 100644 --- a/src/vs/workbench/api/browser/extensionHost.contribution.ts +++ b/src/vs/workbench/api/browser/extensionHost.contribution.ts @@ -20,7 +20,6 @@ import { StatusBarItemsExtensionPoint } from 'vs/workbench/api/browser/statusBar import './mainThreadLocalization'; import './mainThreadBulkEdits'; import './mainThreadChatProvider'; -import './mainThreadChatAgents'; import './mainThreadChatAgents2'; import './mainThreadChatVariables'; import './mainThreadCodeInsets'; diff --git a/src/vs/workbench/api/browser/mainThreadChat.ts b/src/vs/workbench/api/browser/mainThreadChat.ts index 23f0d00ef32..476bf6da65c 100644 --- a/src/vs/workbench/api/browser/mainThreadChat.ts +++ b/src/vs/workbench/api/browser/mainThreadChat.ts @@ -3,31 +3,23 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { DeferredPromise } from 'vs/base/common/async'; import { Emitter } from 'vs/base/common/event'; -import { IMarkdownString } from 'vs/base/common/htmlContent'; import { Disposable, DisposableMap } from 'vs/base/common/lifecycle'; -import { revive } from 'vs/base/common/marshalling'; import { URI, UriComponents } from 'vs/base/common/uri'; -import { ExtHostChatShape, ExtHostContext, IChatRequestDto, IChatResponseProgressDto, ILocationDto, MainContext, MainThreadChatShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostChatShape, ExtHostContext, MainContext, MainThreadChatShape } from 'vs/workbench/api/common/extHost.protocol'; import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; -import { isCompleteInteractiveProgressTreeData } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChat, IChatDynamicRequest, IChatProgress, IChatResponse, IChatResponseProgressFileTreeData, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatDynamicRequest, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; @extHostNamedCustomer(MainContext.MainThreadChat) export class MainThreadChat extends Disposable implements MainThreadChatShape { private readonly _providerRegistrations = this._register(new DisposableMap()); - private readonly _activeRequestProgressCallbacks = new Map (DeferredPromise | void)>(); private readonly _stateEmitters = new Map>(); private readonly _proxy: ExtHostChatShape; - private _responsePartHandlePool = 0; - private readonly _activeResponsePartPromises = new Map>(); - constructor( extHostContext: IExtHostContext, @IChatService private readonly _chatService: IChatService, @@ -64,8 +56,8 @@ export class MainThreadChat extends Disposable implements MainThreadChatShape { const unreg = this._chatService.registerProvider({ id, displayName: registration.label, - prepareSession: async (initialState, token) => { - const session = await this._proxy.$prepareChat(handle, initialState, token); + prepareSession: async (token) => { + const session = await this._proxy.$prepareChat(handle, token); if (!session) { return undefined; } @@ -75,14 +67,13 @@ export class MainThreadChat extends Disposable implements MainThreadChatShape { const emitter = new Emitter(); this._stateEmitters.set(session.id, emitter); - return { + return { id: session.id, requesterUsername: session.requesterUsername, requesterAvatarIconUri: URI.revive(session.requesterAvatarIconUri), responderUsername: session.responderUsername, responderAvatarIconUri, inputPlaceholder: session.inputPlaceholder, - onDidChangeState: emitter.event, dispose: () => { emitter.dispose(); this._stateEmitters.delete(session.id); @@ -90,87 +81,17 @@ export class MainThreadChat extends Disposable implements MainThreadChatShape { } }; }, - provideReply: async (request, progress, token) => { - const id = `${handle}_${request.session.id}`; - this._activeRequestProgressCallbacks.set(id, progress); - try { - const requestDto: IChatRequestDto = { - message: request.message, - variables: request.variables - }; - const dto = await this._proxy.$provideReply(handle, request.session.id, requestDto, token); - return { - session: request.session, - ...dto - }; - } finally { - this._activeRequestProgressCallbacks.delete(id); - } - }, provideWelcomeMessage: (token) => { return this._proxy.$provideWelcomeMessage(handle, token); }, provideSampleQuestions: (token) => { return this._proxy.$provideSampleQuestions(handle, token); }, - provideSlashCommands: (session, token) => { - return this._proxy.$provideSlashCommands(handle, session.id, token); - }, - provideFollowups: (session, token) => { - return this._proxy.$provideFollowups(handle, session.id, token); - }, - removeRequest: (session, requestId) => { - return this._proxy.$removeRequest(handle, session.id, requestId); - } }); this._providerRegistrations.set(handle, unreg); } - async $acceptResponseProgress(handle: number, sessionId: number, progress: IChatResponseProgressDto, responsePartHandle?: number): Promise { - const id = `${handle}_${sessionId}`; - - if ('placeholder' in progress) { - const responsePartId = `${id}_${++this._responsePartHandlePool}`; - const deferredContentPromise = new DeferredPromise(); - this._activeResponsePartPromises.set(responsePartId, deferredContentPromise); - this._activeRequestProgressCallbacks.get(id)?.({ ...progress, resolvedContent: deferredContentPromise.p }); - return this._responsePartHandlePool; - } else if (responsePartHandle) { - // Complete an existing deferred promise with resolved content - const responsePartId = `${id}_${responsePartHandle}`; - const deferredContentPromise = this._activeResponsePartPromises.get(responsePartId); - if (deferredContentPromise && isCompleteInteractiveProgressTreeData(progress)) { - const withRevivedUris = revive<{ treeData: IChatResponseProgressFileTreeData }>(progress); - deferredContentPromise.complete(withRevivedUris); - this._activeResponsePartPromises.delete(responsePartId); - } else if (deferredContentPromise && 'content' in progress) { - deferredContentPromise.complete(progress.content); - this._activeResponsePartPromises.delete(responsePartId); - } - return; - } - - // No need to support standalone tree data that's not attached to a placeholder in API - if (isCompleteInteractiveProgressTreeData(progress)) { - return; - } - - // TS won't let us change the type of `progress` - let revivedProgress: IChatProgress; - if ('documents' in progress) { - revivedProgress = { documents: revive(progress.documents) }; - } else if ('reference' in progress) { - revivedProgress = revive<{ reference: UriComponents | ILocationDto }>(progress); - } else if ('inlineReference' in progress) { - revivedProgress = revive<{ inlineReference: UriComponents | ILocationDto; name?: string }>(progress); - } else { - revivedProgress = progress; - } - - this._activeRequestProgressCallbacks.get(id)?.(revivedProgress); - } - async $acceptChatState(sessionId: number, state: any): Promise { this._stateEmitters.get(sessionId)?.fire(state); } diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents.ts b/src/vs/workbench/api/browser/mainThreadChatAgents.ts deleted file mode 100644 index 150da6ae90c..00000000000 --- a/src/vs/workbench/api/browser/mainThreadChatAgents.ts +++ /dev/null @@ -1,75 +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 { DisposableMap } from 'vs/base/common/lifecycle'; -import { revive } from 'vs/base/common/marshalling'; -import { IProgress } from 'vs/platform/progress/common/progress'; -import { ExtHostChatAgentsShape, ExtHostContext, MainContext, MainThreadChatAgentsShape } from 'vs/workbench/api/common/extHost.protocol'; -import { IChatAgentCommand, IChatAgentMetadata, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { IChatProgress } from 'vs/workbench/contrib/chat/common/chatService'; -import { IChatSlashFragment } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; -import { IExtHostContext, extHostNamedCustomer } from 'vs/workbench/services/extensions/common/extHostCustomers'; - - -@extHostNamedCustomer(MainContext.MainThreadChatAgents) -export class MainThreadChatAgents implements MainThreadChatAgentsShape { - - private readonly _agents = new DisposableMap; - private readonly _pendingProgress = new Map>(); - private readonly _proxy: ExtHostChatAgentsShape; - - constructor( - extHostContext: IExtHostContext, - @IChatAgentService private readonly _chatAgentService: IChatAgentService - ) { - this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostChatAgents); - } - - $unregisterAgent(handle: number): void { - this._agents.deleteAndDispose(handle); - } - - dispose(): void { - this._agents.clearAndDisposeAll(); - } - - $registerAgent(handle: number, name: string, metadata: IChatAgentMetadata & { subCommands: IChatAgentCommand[] }): void { - const d = this._chatAgentService.registerAgent({ - id: name, - metadata: revive(metadata), - invoke: async (request, progress, history, token) => { - const requestId = Math.random(); - this._pendingProgress.set(requestId, { report: progress }); - try { - const message = request.command ? `/${request.command} ${request.message}` : request.message; - const result = await this._proxy.$invokeAgent(handle, requestId, message, { history }, token); - return { - followUp: result?.followUp ?? [], - }; - } finally { - this._pendingProgress.delete(requestId); - } - }, - async provideSlashCommands() { - return metadata.subCommands; - }, - }); - this._agents.set(handle, d); - } - - async $handleProgressChunk(requestId: number, chunk: IChatSlashFragment): Promise { - // An extra step because TS really struggles with type inference in the Revived generic parameter? - const revived = revive(chunk); - if (typeof revived.content === 'string') { - this._pendingProgress.get(requestId)?.report({ content: revived.content }); - } else { - this._pendingProgress.get(requestId)?.report(revived.content); - } - } - - $unregisterCommand(handle: number): void { - this._agents.deleteAndDispose(handle); - } -} diff --git a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts index 07a10a92e9c..ddbd8dd2b07 100644 --- a/src/vs/workbench/api/browser/mainThreadChatAgents2.ts +++ b/src/vs/workbench/api/browser/mainThreadChatAgents2.ts @@ -75,7 +75,7 @@ export class MainThreadChatAgents2 extends Disposable implements MainThreadChatA } }, provideFollowups: async (sessionId, token): Promise => { - if (!this._agents.get(handle)?.hasSlashCommands) { + if (!this._agents.get(handle)?.hasFollowups) { return []; } diff --git a/src/vs/workbench/api/browser/mainThreadDocuments.ts b/src/vs/workbench/api/browser/mainThreadDocuments.ts index 4d28ce7f8e9..02d36af3856 100644 --- a/src/vs/workbench/api/browser/mainThreadDocuments.ts +++ b/src/vs/workbench/api/browser/mainThreadDocuments.ts @@ -17,7 +17,7 @@ import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/ import { toLocalResource, extUri, IExtUri } from 'vs/base/common/resources'; import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; -import { Emitter } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { ResourceMap } from 'vs/base/common/map'; import { IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers'; @@ -267,15 +267,19 @@ export class MainThreadDocuments extends Disposable implements MainThreadDocumen } private async _doCreateUntitled(associatedResource?: URI, languageId?: string, initialValue?: string): Promise { - const model = await this._textFileService.untitled.resolve({ + const model = this._textFileService.untitled.create({ associatedResource, languageId, initialValue }); const resource = model.resource; + const ref = await this._textModelResolverService.createModelReference(resource); if (!this._modelTrackers.has(resource)) { + ref.dispose(); throw new Error(`expected URI ${resource.toString()} to have come to LIFE`); } + this._modelReferenceCollection.add(resource, ref, ref.object.textEditorModel.getValueLength()); + Event.once(model.onDidRevert)(() => this._modelReferenceCollection.remove(resource)); this._proxy.$acceptDirtyStateChanged(resource, true); // mark as dirty return resource; } diff --git a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts index 330094b6da0..fd9e89a6536 100644 --- a/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts +++ b/src/vs/workbench/api/browser/mainThreadLanguageFeatures.ts @@ -302,10 +302,6 @@ export class MainThreadLanguageFeatures extends Disposable implements MainThread }), this._languageFeaturesService.multiDocumentHighlightProvider.register(selector, { provideMultiDocumentHighlights: (model: ITextModel, position: EditorPosition, otherModels: ITextModel[], token: CancellationToken): Promise> => { - if (model.isDisposed()) { - return Promise.resolve(new Map()); - } - const word = model.getWordAtPosition({ lineNumber: position.lineNumber, column: position.column diff --git a/src/vs/workbench/api/browser/mainThreadQuickDiff.ts b/src/vs/workbench/api/browser/mainThreadQuickDiff.ts index 0aa31f94c96..48f53faea39 100644 --- a/src/vs/workbench/api/browser/mainThreadQuickDiff.ts +++ b/src/vs/workbench/api/browser/mainThreadQuickDiff.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { CancellationTokenSource } from 'vs/base/common/cancellation'; -import { dispose, IDisposable } from 'vs/base/common/lifecycle'; +import { DisposableMap, IDisposable } from 'vs/base/common/lifecycle'; import { URI, UriComponents } from 'vs/base/common/uri'; import { ExtHostContext, ExtHostQuickDiffShape, IDocumentFilterDto, MainContext, MainThreadQuickDiffShape } from 'vs/workbench/api/common/extHost.protocol'; import { IQuickDiffService, QuickDiffProvider } from 'vs/workbench/contrib/scm/common/quickDiff'; @@ -14,8 +14,7 @@ import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/ext export class MainThreadQuickDiff implements MainThreadQuickDiffShape { private readonly proxy: ExtHostQuickDiffShape; - private providers = new Map(); - private providerDisposables = new Map(); + private providerDisposables = new DisposableMap(); constructor( extHostContext: IExtHostContext, @@ -34,23 +33,17 @@ export class MainThreadQuickDiff implements MainThreadQuickDiffShape { return URI.revive(await this.proxy.$provideOriginalResource(handle, uri, new CancellationTokenSource().token)); } }; - this.providers.set(handle, provider); const disposable = this.quickDiffService.addQuickDiffProvider(provider); this.providerDisposables.set(handle, disposable); } async $unregisterQuickDiffProvider(handle: number): Promise { - if (this.providers.has(handle)) { - this.providers.delete(handle); - } if (this.providerDisposables.has(handle)) { - this.providerDisposables.delete(handle); + this.providerDisposables.deleteAndDispose(handle); } } dispose(): void { - this.providers.clear(); - dispose(this.providerDisposables.values()); - this.providerDisposables.clear(); + this.providerDisposables.dispose(); } } diff --git a/src/vs/workbench/api/browser/mainThreadTesting.ts b/src/vs/workbench/api/browser/mainThreadTesting.ts index d4858ccb417..356310400ee 100644 --- a/src/vs/workbench/api/browser/mainThreadTesting.ts +++ b/src/vs/workbench/api/browser/mainThreadTesting.ts @@ -5,12 +5,13 @@ import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationToken } from 'vs/base/common/cancellation'; +import { Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { revive } from 'vs/base/common/marshalling'; import { URI } from 'vs/base/common/uri'; import { Range } from 'vs/editor/common/core/range'; import { MutableObservableValue } from 'vs/workbench/contrib/testing/common/observableValue'; -import { ExtensionRunTestsRequest, IFileCoverage, ITestItem, ITestMessage, ITestRunProfile, ITestRunTask, ResolvedTestRunRequest, TestResultState, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testTypes'; +import { ExtensionRunTestsRequest, IFileCoverage, ITestItem, ITestMessage, ITestRunProfile, ITestRunTask, ResolvedTestRunRequest, TestResultState, TestRunProfileBitset, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testTypes'; import { TestCoverage } from 'vs/workbench/contrib/testing/common/testCoverage'; import { ITestProfileService } from 'vs/workbench/contrib/testing/common/testProfileService'; import { LiveTestResult } from 'vs/workbench/contrib/testing/common/testResult'; @@ -45,6 +46,21 @@ export class MainThreadTesting extends Disposable implements MainThreadTestingSh this.proxy.$cancelExtensionTestRun(runId); })); + this._register(Event.debounce(testProfiles.onDidChange, (_last, e) => e)(() => { + const defaults = new Set([ + ...testProfiles.getGroupDefaultProfiles(TestRunProfileBitset.Run), + ...testProfiles.getGroupDefaultProfiles(TestRunProfileBitset.Debug), + ...testProfiles.getGroupDefaultProfiles(TestRunProfileBitset.Coverage), + ]); + + const obj: Record = {}; + for (const { controller, profiles } of this.testProfiles.all()) { + obj[controller.id] = profiles.filter(p => defaults.has(p)).map(p => p.profileId); + } + + this.proxy.$setActiveRunProfiles(obj); + })); + this._register(resultService.onResultsChanged(evt => { const results = 'completed' in evt ? evt.completed : ('inserted' in evt ? evt.inserted : undefined); const serialized = results?.toJSONWithMessages(); diff --git a/src/vs/workbench/api/browser/mainThreadTunnelService.ts b/src/vs/workbench/api/browser/mainThreadTunnelService.ts index 678285a11c5..4fb13ab6231 100644 --- a/src/vs/workbench/api/browser/mainThreadTunnelService.ts +++ b/src/vs/workbench/api/browser/mainThreadTunnelService.ts @@ -53,7 +53,7 @@ export class MainThreadTunnelService extends Disposable implements MainThreadTun if (this.remoteExplorerService.portsFeaturesEnabled) { this._proxy.$registerCandidateFinder(this.processFindingEnabled()); } else { - this._register(this.remoteExplorerService.onEnabledPortsFeatures(() => this._proxy.$registerCandidateFinder(this.configurationService.getValue(PORT_AUTO_FORWARD_SETTING)))); + this._register(this.remoteExplorerService.onEnabledPortsFeatures(() => this._proxy.$registerCandidateFinder(this.processFindingEnabled()))); } this._register(this.configurationService.onDidChangeConfiguration(async (e) => { if (e.affectsConfiguration(PORT_AUTO_FORWARD_SETTING) || e.affectsConfiguration(PORT_AUTO_SOURCE_SETTING)) { diff --git a/src/vs/workbench/api/common/extHost.api.impl.ts b/src/vs/workbench/api/common/extHost.api.impl.ts index 9a38446fbf1..e1fd00232ec 100644 --- a/src/vs/workbench/api/common/extHost.api.impl.ts +++ b/src/vs/workbench/api/common/extHost.api.impl.ts @@ -6,110 +6,109 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation'; import * as errors from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; +import { combinedDisposable } from 'vs/base/common/lifecycle'; +import { Schemas } from 'vs/base/common/network'; import Severity from 'vs/base/common/severity'; import { URI } from 'vs/base/common/uri'; import { TextEditorCursorStyle } from 'vs/editor/common/config/editorOptions'; -import { OverviewRulerLane } from 'vs/editor/common/model'; -import * as languageConfiguration from 'vs/editor/common/languages/languageConfiguration'; import { score } from 'vs/editor/common/languageSelector'; +import * as languageConfiguration from 'vs/editor/common/languages/languageConfiguration'; +import { OverviewRulerLane } from 'vs/editor/common/model'; +import { ExtensionIdentifierSet, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; import * as files from 'vs/platform/files/common/files'; -import { ExtHostContext, MainContext, CandidatePortSource, ExtHostLogLevelServiceShape } from 'vs/workbench/api/common/extHost.protocol'; -import { UIKind } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; +import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { ILogService, ILoggerService, LogLevel } from 'vs/platform/log/common/log'; +import { matchesScheme } from 'vs/platform/opener/common/opener'; +import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; +import { TelemetryTrustedValue } from 'vs/platform/telemetry/common/telemetryUtils'; +import { EditSessionIdentityMatch } from 'vs/platform/workspace/common/editSessions'; +import { CandidatePortSource, ExtHostContext, ExtHostLogLevelServiceShape, MainContext } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostRelatedInformation } from 'vs/workbench/api/common/extHostAiRelatedInformation'; import { ExtHostApiCommands } from 'vs/workbench/api/common/extHostApiCommands'; +import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; +import { ExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication'; +import { ExtHostBulkEdits } from 'vs/workbench/api/common/extHostBulkEdits'; +import { ExtHostChat } from 'vs/workbench/api/common/extHostChat'; +import { ExtHostChatAgents2 } from 'vs/workbench/api/common/extHostChatAgents2'; +import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider'; +import { ExtHostChatVariables } from 'vs/workbench/api/common/extHostChatVariables'; import { ExtHostClipboard } from 'vs/workbench/api/common/extHostClipboard'; +import { ExtHostEditorInsets } from 'vs/workbench/api/common/extHostCodeInsets'; import { IExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { createExtHostComments } from 'vs/workbench/api/common/extHostComments'; import { ExtHostConfigProvider, IExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration'; +import { ExtHostCustomEditors } from 'vs/workbench/api/common/extHostCustomEditors'; +import { IExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService'; +import { IExtHostDecorations } from 'vs/workbench/api/common/extHostDecorations'; import { ExtHostDiagnostics } from 'vs/workbench/api/common/extHostDiagnostics'; import { ExtHostDialogs } from 'vs/workbench/api/common/extHostDialogs'; import { ExtHostDocumentContentProvider } from 'vs/workbench/api/common/extHostDocumentContentProviders'; import { ExtHostDocumentSaveParticipant } from 'vs/workbench/api/common/extHostDocumentSaveParticipant'; import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments'; import { IExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; +import { IExtHostEditorTabs } from 'vs/workbench/api/common/extHostEditorTabs'; +import { ExtHostAiEmbeddingVector } from 'vs/workbench/api/common/extHostEmbeddingVector'; import { Extension, IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService'; import { ExtHostFileSystem } from 'vs/workbench/api/common/extHostFileSystem'; +import { IExtHostConsumerFileSystem } from 'vs/workbench/api/common/extHostFileSystemConsumer'; import { ExtHostFileSystemEventService, FileSystemWatcherCreateOptions } from 'vs/workbench/api/common/extHostFileSystemEventService'; +import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; +import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; +import { ExtHostInteractiveEditor } from 'vs/workbench/api/common/extHostInlineChat'; +import { ExtHostInteractive } from 'vs/workbench/api/common/extHostInteractive'; +import { ExtHostIssueReporter } from 'vs/workbench/api/common/extHostIssueReporter'; +import { ExtHostLabelService } from 'vs/workbench/api/common/extHostLabelService'; import { ExtHostLanguageFeatures } from 'vs/workbench/api/common/extHostLanguageFeatures'; import { ExtHostLanguages } from 'vs/workbench/api/common/extHostLanguages'; +import { IExtHostLocalizationService } from 'vs/workbench/api/common/extHostLocalizationService'; +import { IExtHostManagedSockets } from 'vs/workbench/api/common/extHostManagedSockets'; import { ExtHostMessageService } from 'vs/workbench/api/common/extHostMessageService'; +import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; +import { ExtHostNotebookDocumentSaveParticipant } from 'vs/workbench/api/common/extHostNotebookDocumentSaveParticipant'; +import { ExtHostNotebookDocuments } from 'vs/workbench/api/common/extHostNotebookDocuments'; +import { ExtHostNotebookEditors } from 'vs/workbench/api/common/extHostNotebookEditors'; +import { ExtHostNotebookKernels } from 'vs/workbench/api/common/extHostNotebookKernels'; +import { ExtHostNotebookRenderers } from 'vs/workbench/api/common/extHostNotebookRenderers'; import { IExtHostOutputService } from 'vs/workbench/api/common/extHostOutput'; +import { ExtHostProfileContentHandlers } from 'vs/workbench/api/common/extHostProfileContentHandler'; import { ExtHostProgress } from 'vs/workbench/api/common/extHostProgress'; +import { ExtHostQuickDiff } from 'vs/workbench/api/common/extHostQuickDiff'; import { createExtHostQuickOpen } from 'vs/workbench/api/common/extHostQuickOpen'; +import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; import { ExtHostSCM } from 'vs/workbench/api/common/extHostSCM'; +import { IExtHostSearch } from 'vs/workbench/api/common/extHostSearch'; +import { IExtHostSecretState } from 'vs/workbench/api/common/extHostSecretState'; +import { ExtHostShare } from 'vs/workbench/api/common/extHostShare'; +import { ExtHostSpeech } from 'vs/workbench/api/common/extHostSpeech'; import { ExtHostStatusBar } from 'vs/workbench/api/common/extHostStatusBar'; import { IExtHostStorage } from 'vs/workbench/api/common/extHostStorage'; +import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; +import { IExtHostTask } from 'vs/workbench/api/common/extHostTask'; +import { ExtHostTelemetryLogger, IExtHostTelemetry, isNewAppInstall } from 'vs/workbench/api/common/extHostTelemetry'; import { IExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService'; +import { ExtHostTesting } from 'vs/workbench/api/common/extHostTesting'; import { ExtHostEditors } from 'vs/workbench/api/common/extHostTextEditors'; +import { ExtHostTheming } from 'vs/workbench/api/common/extHostTheming'; +import { ExtHostTimeline } from 'vs/workbench/api/common/extHostTimeline'; import { ExtHostTreeViews } from 'vs/workbench/api/common/extHostTreeViews'; +import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService'; import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters'; import * as extHostTypes from 'vs/workbench/api/common/extHostTypes'; -import { TelemetryTrustedValue } from 'vs/platform/telemetry/common/telemetryUtils'; +import { ExtHostUriOpeners } from 'vs/workbench/api/common/extHostUriOpener'; +import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; import { ExtHostUrls } from 'vs/workbench/api/common/extHostUrls'; import { ExtHostWebviews } from 'vs/workbench/api/common/extHostWebview'; +import { ExtHostWebviewPanels } from 'vs/workbench/api/common/extHostWebviewPanels'; +import { ExtHostWebviewViews } from 'vs/workbench/api/common/extHostWebviewView'; import { IExtHostWindow } from 'vs/workbench/api/common/extHostWindow'; import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace'; -import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier'; -import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; -import type * as vscode from 'vscode'; -import { ExtensionIdentifierSet, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { ExtHostEditorInsets } from 'vs/workbench/api/common/extHostCodeInsets'; -import { ExtHostLabelService } from 'vs/workbench/api/common/extHostLabelService'; -import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; -import { IExtHostDecorations } from 'vs/workbench/api/common/extHostDecorations'; -import { IExtHostTask } from 'vs/workbench/api/common/extHostTask'; -import { IExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService'; -import { IExtHostSearch } from 'vs/workbench/api/common/extHostSearch'; -import { ILoggerService, ILogService, LogLevel } from 'vs/platform/log/common/log'; -import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransformerService'; -import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService'; -import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; -import { ExtHostNotebookController } from 'vs/workbench/api/common/extHostNotebook'; -import { ExtHostTheming } from 'vs/workbench/api/common/extHostTheming'; -import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService'; -import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService'; -import { ExtHostAuthentication } from 'vs/workbench/api/common/extHostAuthentication'; -import { ExtHostTimeline } from 'vs/workbench/api/common/extHostTimeline'; -import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; -import { IExtHostConsumerFileSystem } from 'vs/workbench/api/common/extHostFileSystemConsumer'; -import { ExtHostWebviewViews } from 'vs/workbench/api/common/extHostWebviewView'; -import { ExtHostCustomEditors } from 'vs/workbench/api/common/extHostCustomEditors'; -import { ExtHostWebviewPanels } from 'vs/workbench/api/common/extHostWebviewPanels'; -import { ExtHostBulkEdits } from 'vs/workbench/api/common/extHostBulkEdits'; -import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSystemInfo'; -import { ExtHostTesting } from 'vs/workbench/api/common/extHostTesting'; -import { ExtHostUriOpeners } from 'vs/workbench/api/common/extHostUriOpener'; -import { IExtHostSecretState } from 'vs/workbench/api/common/extHostSecretState'; -import { IExtHostEditorTabs } from 'vs/workbench/api/common/extHostEditorTabs'; -import { ExtHostTelemetryLogger, IExtHostTelemetry, isNewAppInstall } from 'vs/workbench/api/common/extHostTelemetry'; -import { ExtHostNotebookKernels } from 'vs/workbench/api/common/extHostNotebookKernels'; -import { TextSearchCompleteMessageType } from 'vs/workbench/services/search/common/searchExtTypes'; -import { ExtHostNotebookRenderers } from 'vs/workbench/api/common/extHostNotebookRenderers'; -import { Schemas } from 'vs/base/common/network'; -import { matchesScheme } from 'vs/platform/opener/common/opener'; -import { ExtHostNotebookEditors } from 'vs/workbench/api/common/extHostNotebookEditors'; -import { ExtHostNotebookDocuments } from 'vs/workbench/api/common/extHostNotebookDocuments'; -import { ExtHostInteractive } from 'vs/workbench/api/common/extHostInteractive'; -import { combinedDisposable } from 'vs/base/common/lifecycle'; -import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import { DebugConfigurationProviderTriggerKind } from 'vs/workbench/contrib/debug/common/debug'; -import { IExtHostLocalizationService } from 'vs/workbench/api/common/extHostLocalizationService'; -import { EditSessionIdentityMatch } from 'vs/platform/workspace/common/editSessions'; -import { ExtHostProfileContentHandlers } from 'vs/workbench/api/common/extHostProfileContentHandler'; -import { ExtHostQuickDiff } from 'vs/workbench/api/common/extHostQuickDiff'; -import { ExtHostChat } from 'vs/workbench/api/common/extHostChat'; -import { ExtHostInteractiveEditor } from 'vs/workbench/api/common/extHostInlineChat'; -import { ExtHostNotebookDocumentSaveParticipant } from 'vs/workbench/api/common/extHostNotebookDocumentSaveParticipant'; -import { ExtHostIssueReporter } from 'vs/workbench/api/common/extHostIssueReporter'; -import { IExtHostManagedSockets } from 'vs/workbench/api/common/extHostManagedSockets'; -import { ExtHostShare } from 'vs/workbench/api/common/extHostShare'; -import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider'; -import { ExtHostSpeech } from 'vs/workbench/api/common/extHostSpeech'; -import { ExtHostChatVariables } from 'vs/workbench/api/common/extHostChatVariables'; -import { ExtHostRelatedInformation } from 'vs/workbench/api/common/extHostAiRelatedInformation'; -import { ExtHostAiEmbeddingVector } from 'vs/workbench/api/common/extHostEmbeddingVector'; -import { ExtHostChatAgents } from 'vs/workbench/api/common/extHostChatAgents'; -import { ExtHostChatAgents2 } from 'vs/workbench/api/common/extHostChatAgents2'; +import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry'; +import { UIKind } from 'vs/workbench/services/extensions/common/extensionHostProtocol'; +import { checkProposedApiEnabled, isProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; +import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier'; +import { TextSearchCompleteMessageType } from 'vs/workbench/services/search/common/searchExtTypes'; +import type * as vscode from 'vscode'; export interface IExtensionRegistries { mine: ExtensionDescriptionRegistry; @@ -210,10 +209,9 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I rpcProtocol.set(ExtHostContext.ExtHostInteractive, new ExtHostInteractive(rpcProtocol, extHostNotebook, extHostDocumentsAndEditors, extHostCommands, extHostLogService)); const extHostInteractiveEditor = rpcProtocol.set(ExtHostContext.ExtHostInlineChat, new ExtHostInteractiveEditor(rpcProtocol, extHostCommands, extHostDocuments, extHostLogService)); const extHostChatProvider = rpcProtocol.set(ExtHostContext.ExtHostChatProvider, new ExtHostChatProvider(rpcProtocol, extHostLogService)); - const extHostChatAgents = rpcProtocol.set(ExtHostContext.ExtHostChatAgents, new ExtHostChatAgents(rpcProtocol, extHostChatProvider, extHostLogService)); const extHostChatAgents2 = rpcProtocol.set(ExtHostContext.ExtHostChatAgents2, new ExtHostChatAgents2(rpcProtocol, extHostChatProvider, extHostLogService)); const extHostChatVariables = rpcProtocol.set(ExtHostContext.ExtHostChatVariables, new ExtHostChatVariables(rpcProtocol)); - const extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol, extHostLogService)); + const extHostChat = rpcProtocol.set(ExtHostContext.ExtHostChat, new ExtHostChat(rpcProtocol)); const extHostAiRelatedInformation = rpcProtocol.set(ExtHostContext.ExtHostAiRelatedInformation, new ExtHostRelatedInformation(rpcProtocol)); const extHostAiEmbeddingVector = rpcProtocol.set(ExtHostContext.ExtHostAiEmbeddingVector, new ExtHostAiEmbeddingVector(rpcProtocol)); const extHostIssueReporter = rpcProtocol.set(ExtHostContext.ExtHostIssueReporter, new ExtHostIssueReporter(rpcProtocol)); @@ -1382,10 +1380,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I checkProposedApiEnabled(extension, 'chatAgents2'); return extHostChatAgents2.createChatAgent(extension, name, handler); }, - registerAgent(name: string, agent: vscode.ChatAgent, metadata: vscode.ChatAgentMetadata) { - checkProposedApiEnabled(extension, 'chatAgents'); - return extHostChatAgents.registerAgent(extension.identifier, name, agent, metadata); - } }; // namespace: speech diff --git a/src/vs/workbench/api/common/extHost.protocol.ts b/src/vs/workbench/api/common/extHost.protocol.ts index 18035b6cb54..ba221bd753f 100644 --- a/src/vs/workbench/api/common/extHost.protocol.ts +++ b/src/vs/workbench/api/common/extHost.protocol.ts @@ -52,8 +52,7 @@ import { IRevealOptions, ITreeItem, IViewBadge } from 'vs/workbench/common/views import { CallHierarchyItem } from 'vs/workbench/contrib/callHierarchy/common/callHierarchy'; import { IChatAgentCommand, IChatAgentMetadata, IChatAgentRequest, IChatAgentResult } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatMessage, IChatResponseFragment, IChatResponseProviderMetadata } from 'vs/workbench/contrib/chat/common/chatProvider'; -import { IChatAgentDetection, IChatDynamicRequest, IChatFollowup, IChatReplyFollowup, IChatResponseErrorDetails, IChatUserActionEvent, ISlashCommand, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; -import { IChatSlashFragment } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; +import { IChatAgentDetection, IChatDynamicRequest, IChatFollowup, IChatReplyFollowup, IChatResponseErrorDetails, IChatUserActionEvent, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue, IChatVariableData } from 'vs/workbench/contrib/chat/common/chatVariables'; import { DebugConfigurationProviderTriggerKind, IAdapterDescriptor, IConfig, IDebugSessionReplMode } from 'vs/workbench/contrib/debug/common/debug'; import { IInlineChatBulkEditResponse, IInlineChatEditResponse, IInlineChatMessageResponse, IInlineChatProgressItem, IInlineChatRequest, IInlineChatSession, InlineChatResponseFeedbackKind } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; @@ -63,6 +62,7 @@ import { ICellExecutionComplete, ICellExecutionStateUpdate } from 'vs/workbench/ import { ICellRange } from 'vs/workbench/contrib/notebook/common/notebookRange'; import { InputValidationType } from 'vs/workbench/contrib/scm/common/scm'; import { IWorkspaceSymbol, NotebookPriorityInfo } from 'vs/workbench/contrib/search/common/search'; +import { IRawClosedNotebookFileMatch } from 'vs/workbench/contrib/search/common/searchNotebookHelpers'; import { ISpeechProviderMetadata, ISpeechToTextEvent } from 'vs/workbench/contrib/speech/common/speechService'; import { CoverageDetails, ExtensionRunTestsRequest, ICallProfileRunHandler, IFileCoverage, ISerializedTestResults, IStartControllerTests, ITestItem, ITestMessage, ITestRunProfile, ITestRunTask, ResolvedTestRunRequest, TestResultState, TestsDiffOp } from 'vs/workbench/contrib/testing/common/testTypes'; import { Timeline, TimelineChangeEvent, TimelineOptions, TimelineProviderDescriptor } from 'vs/workbench/contrib/timeline/common/timeline'; @@ -80,7 +80,6 @@ import { CandidatePort } from 'vs/workbench/services/remote/common/tunnelModel'; import { ITextQueryBuilderOptions } from 'vs/workbench/services/search/common/queryBuilder'; import * as search from 'vs/workbench/services/search/common/search'; import { ISaveProfileResult } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; -import { IRawClosedNotebookFileMatch } from 'vs/workbench/contrib/search/common/searchNotebookHelpers'; export interface IWorkspaceData extends IStaticWorkspaceData { folders: { uri: UriComponents; name: string; index: number }[]; @@ -1166,12 +1165,6 @@ export interface ExtHostChatProviderShape { $handleResponseFragment(requestId: number, chunk: IChatResponseFragment): Promise; } -export interface MainThreadChatAgentsShape extends IDisposable { - $registerAgent(handle: number, name: string, metadata: IChatAgentMetadata & { subCommands: IChatAgentCommand[] }): void; - $unregisterAgent(handle: number): void; - $handleProgressChunk(requestId: number, chunk: IChatSlashFragment): Promise; -} - export interface IExtensionChatAgentMetadata extends Dto { hasSlashCommands?: boolean; hasFollowup?: boolean; @@ -1184,10 +1177,6 @@ export interface MainThreadChatAgentsShape2 extends IDisposable { $handleProgressChunk(requestId: string, chunk: IChatResponseProgressDto, responsePartHandle?: number): Promise; } -export interface ExtHostChatAgentsShape { - $invokeAgent(handle: number, requestId: number, prompt: string, context: { history: IChatMessage[] }, token: CancellationToken): Promise; -} - export interface ExtHostChatAgentsShape2 { $invokeAgent(handle: number, sessionId: string, requestId: string, request: IChatAgentRequest, context: { history: IChatMessage[] }, token: CancellationToken): Promise; $provideSlashCommands(handle: number, token: CancellationToken): Promise; @@ -1263,7 +1252,6 @@ export type IDocumentContextDto = { export type IChatResponseProgressDto = | { content: string | IMarkdownString } - | { requestId: string } | { placeholder: string } | { treeData: IChatResponseProgressFileTreeData } | { documents: IDocumentContextDto[] } @@ -1276,18 +1264,13 @@ export interface MainThreadChatShape extends IDisposable { $acceptChatState(sessionId: number, state: any): Promise; $sendRequestToProvider(providerId: string, message: IChatDynamicRequest): void; $unregisterChatProvider(handle: number): Promise; - $acceptResponseProgress(handle: number, sessionId: number, progress: IChatResponseProgressDto, responsePartHandle?: number): Promise; $transferChatSession(sessionId: number, toWorkspace: UriComponents): void; } export interface ExtHostChatShape { - $prepareChat(handle: number, initialState: any, token: CancellationToken): Promise; + $prepareChat(handle: number, token: CancellationToken): Promise; $provideWelcomeMessage(handle: number, token: CancellationToken): Promise<(string | IMarkdownString | IChatReplyFollowup[])[] | undefined>; $provideSampleQuestions(handle: number, token: CancellationToken): Promise; - $provideFollowups(handle: number, sessionId: number, token: CancellationToken): Promise; - $provideReply(handle: number, sessionId: number, request: IChatRequestDto, token: CancellationToken): Promise; - $removeRequest(handle: number, sessionId: number, requestId: string): void; - $provideSlashCommands(handle: number, sessionId: number, token: CancellationToken): Promise; $releaseSession(sessionId: number): void; $onDidPerformUserAction(event: IChatUserActionEvent): Promise; } @@ -2638,6 +2621,8 @@ export interface ExtHostTestingShape { $refreshTests(controllerId: string, token: CancellationToken): Promise; /** Ensures any pending test diffs are flushed */ $syncTests(): Promise; + /** Sets the active test run profiles */ + $setActiveRunProfiles(profiles: Record): void; } export interface ExtHostLocalizationShape { @@ -2719,7 +2704,6 @@ export const MainContext = { MainThreadAuthentication: createProxyIdentifier('MainThreadAuthentication'), MainThreadBulkEdits: createProxyIdentifier('MainThreadBulkEdits'), MainThreadChatProvider: createProxyIdentifier('MainThreadChatProvider'), - MainThreadChatAgents: createProxyIdentifier('MainThreadChatAgents'), MainThreadChatAgents2: createProxyIdentifier('MainThreadChatAgents2'), MainThreadChatVariables: createProxyIdentifier('MainThreadChatVariables'), MainThreadClipboard: createProxyIdentifier('MainThreadClipboard'), @@ -2842,7 +2826,6 @@ export const ExtHostContext = { ExtHostInteractive: createProxyIdentifier('ExtHostInteractive'), ExtHostInlineChat: createProxyIdentifier('ExtHostInlineChatShape'), ExtHostChat: createProxyIdentifier('ExtHostChat'), - ExtHostChatAgents: createProxyIdentifier('ExtHostChatAgents'), ExtHostChatAgents2: createProxyIdentifier('ExtHostChatAgents'), ExtHostChatVariables: createProxyIdentifier('ExtHostChatVariables'), ExtHostChatProvider: createProxyIdentifier('ExtHostChatProvider'), diff --git a/src/vs/workbench/api/common/extHostApiCommands.ts b/src/vs/workbench/api/common/extHostApiCommands.ts index e79c39ceee6..7412cb9168a 100644 --- a/src/vs/workbench/api/common/extHostApiCommands.ts +++ b/src/vs/workbench/api/common/extHostApiCommands.ts @@ -432,6 +432,31 @@ const newCommands: ApiCommand[] = [ ], ApiCommandResult.Void ), + new ApiCommand( + 'vscode.changes', '_workbench.changes', 'Opens a list of resources in the changes editor to compare their contents.', + [ + ApiCommandArgument.String.with('title', 'Human readable title for the changes editor'), + new ApiCommandArgument<[URI, URI?, URI?][]>('resourceList', 'List of resources to compare', + resources => { + for (const resource of resources) { + if (resource.length !== 3) { + return false; + } + + const [label, left, right] = resource; + if (!URI.isUri(label) || + (!URI.isUri(left) && left !== undefined && left !== null) || + (!URI.isUri(right) && right !== undefined && right !== null)) { + return false; + } + } + + return true; + }, + v => v) + ], + ApiCommandResult.Void + ), // --- type hierarchy new ApiCommand( 'vscode.prepareTypeHierarchy', '_executePrepareTypeHierarchy', 'Prepare type hierarchy at a position inside a document', diff --git a/src/vs/workbench/api/common/extHostChat.ts b/src/vs/workbench/api/common/extHostChat.ts index d2ace71d47c..870c8a78848 100644 --- a/src/vs/workbench/api/common/extHostChat.ts +++ b/src/vs/workbench/api/common/extHostChat.ts @@ -3,19 +3,15 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { raceCancellation } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter } from 'vs/base/common/event'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { Iterable } from 'vs/base/common/iterator'; import { toDisposable } from 'vs/base/common/lifecycle'; -import { StopWatch } from 'vs/base/common/stopwatch'; -import { localize } from 'vs/nls'; import { IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { ILogService } from 'vs/platform/log/common/log'; -import { ExtHostChatShape, IChatRequestDto, IChatResponseDto, IChatDto, IMainContext, MainContext, MainThreadChatShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostChatShape, IChatDto, IMainContext, MainContext, MainThreadChatShape } from 'vs/workbench/api/common/extHost.protocol'; import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; -import { IChatFollowup, IChatReplyFollowup, IChatUserActionEvent, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatReplyFollowup, IChatUserActionEvent } from 'vs/workbench/contrib/chat/common/chatService'; import type * as vscode from 'vscode'; class ChatProviderWrapper { @@ -36,7 +32,6 @@ export class ExtHostChat implements ExtHostChatShape { private readonly _chatProvider = new Map>(); private readonly _chatSessions = new Map(); - // private readonly _providerResponsesByRequestId = new Map; sessionId: number }>(); private readonly _onDidPerformUserAction = new Emitter(); public readonly onDidPerformUserAction = this._onDidPerformUserAction.event; @@ -45,7 +40,6 @@ export class ExtHostChat implements ExtHostChatShape { constructor( mainContext: IMainContext, - private readonly logService: ILogService ) { this._proxy = mainContext.getProxy(MainContext.MainThreadChat); } @@ -75,13 +69,13 @@ export class ExtHostChat implements ExtHostChatShape { this._proxy.$sendRequestToProvider(providerId, message); } - async $prepareChat(handle: number, initialState: any, token: CancellationToken): Promise { + async $prepareChat(handle: number, token: CancellationToken): Promise { const entry = this._chatProvider.get(handle); if (!entry) { return undefined; } - const session = await entry.provider.prepareSession(initialState, token); + const session = await entry.provider.prepareSession(token); if (!session) { return undefined; } @@ -124,25 +118,6 @@ export class ExtHostChat implements ExtHostChatShape { }); } - async $provideFollowups(handle: number, sessionId: number, token: CancellationToken): Promise { - const entry = this._chatProvider.get(handle); - if (!entry) { - return undefined; - } - - const realSession = this._chatSessions.get(sessionId); - if (!realSession) { - return; - } - - if (!entry.provider.provideFollowups) { - return undefined; - } - - const rawFollowups = await entry.provider.provideFollowups(realSession, token); - return rawFollowups?.map(f => typeConvert.ChatFollowup.from(f)); - } - async $provideSampleQuestions(handle: number, token: CancellationToken): Promise { const entry = this._chatProvider.get(handle); if (!entry) { @@ -161,121 +136,6 @@ export class ExtHostChat implements ExtHostChatShape { return rawFollowups?.map(f => typeConvert.ChatReplyFollowup.from(f)); } - $removeRequest(handle: number, sessionId: number, requestId: string): void { - const entry = this._chatProvider.get(handle); - if (!entry) { - return; - } - - const realSession = this._chatSessions.get(sessionId); - if (!realSession) { - return; - } - - if (!entry.provider.removeRequest) { - return; - } - - entry.provider.removeRequest(realSession, requestId); - } - - async $provideReply(handle: number, sessionId: number, request: IChatRequestDto, token: CancellationToken): Promise { - const entry = this._chatProvider.get(handle); - if (!entry) { - return undefined; - } - - const realSession = this._chatSessions.get(sessionId); - if (!realSession) { - return; - } - - const requestObj: vscode.InteractiveRequest = { - session: realSession, - message: request.message, - variables: {} - }; - - if (request.variables) { - for (const key of Object.keys(request.variables)) { - requestObj.variables[key] = request.variables[key].map(typeConvert.ChatVariable.to); - } - } - - const stopWatch = StopWatch.create(false); - let firstProgress: number | undefined; - const progressObj: vscode.Progress = { - report: (progress: vscode.InteractiveProgress) => { - if (token.isCancellationRequested) { - return; - } - - if (typeof firstProgress === 'undefined') { - firstProgress = stopWatch.elapsed(); - } - - const convertedProgress = typeConvert.ChatResponseProgress.from(entry.extension, progress); - if ('placeholder' in progress && 'resolvedContent' in progress) { - const resolvedContent = Promise.all([this._proxy.$acceptResponseProgress(handle, sessionId, convertedProgress), progress.resolvedContent]); - raceCancellation(resolvedContent, token).then((res) => { - if (!res) { - return; /* Cancelled */ - } - const [progressHandle, progressContent] = res; - this._proxy.$acceptResponseProgress(handle, sessionId, progressContent, progressHandle ?? undefined); - }); - } else { - this._proxy.$acceptResponseProgress(handle, sessionId, convertedProgress); - } - } - }; - let result: vscode.InteractiveResponseForProgress | undefined | null; - try { - result = await entry.provider.provideResponseWithProgress(requestObj, progressObj, token); - if (!result) { - result = { errorDetails: { message: localize('emptyResponse', "Provider returned null response") } }; - } - } catch (err) { - result = { errorDetails: { message: localize('errorResponse', "Error from provider: {0}", err.message), responseIsIncomplete: true } }; - this.logService.error(err); - } - - try { - // Check that the session has not been released since the request started - if (realSession.saveState && this._chatSessions.has(sessionId)) { - const newState = realSession.saveState(); - this._proxy.$acceptChatState(sessionId, newState); - } - } catch (err) { - this.logService.warn(err); - } - - const timings = { firstProgress: firstProgress ?? 0, totalElapsed: stopWatch.elapsed() }; - return { errorDetails: result.errorDetails, timings }; - } - - async $provideSlashCommands(handle: number, sessionId: number, token: CancellationToken): Promise { - const entry = this._chatProvider.get(handle); - if (!entry) { - return undefined; - } - - const realSession = this._chatSessions.get(sessionId); - if (!realSession) { - return undefined; - } - - if (!entry.provider.provideSlashCommands) { - return undefined; - } - - const slashCommands = await entry.provider.provideSlashCommands(realSession, token); - return slashCommands?.map(c => ({ - ...c, - kind: typeConvert.CompletionItemKind.from(c.kind) - })); - } - $releaseSession(sessionId: number) { this._chatSessions.delete(sessionId); } diff --git a/src/vs/workbench/api/common/extHostChatAgents.ts b/src/vs/workbench/api/common/extHostChatAgents.ts deleted file mode 100644 index eca6e389255..00000000000 --- a/src/vs/workbench/api/common/extHostChatAgents.ts +++ /dev/null @@ -1,91 +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 { DeferredPromise, raceCancellation } from 'vs/base/common/async'; -import { CancellationToken } from 'vs/base/common/cancellation'; -import { IDisposable, toDisposable } from 'vs/base/common/lifecycle'; -import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions'; -import { ILogService } from 'vs/platform/log/common/log'; -import { Progress } from 'vs/platform/progress/common/progress'; -import { ExtHostChatAgentsShape, IMainContext, MainContext, MainThreadChatAgentsShape } from 'vs/workbench/api/common/extHost.protocol'; -import { ExtHostChatProvider } from 'vs/workbench/api/common/extHostChatProvider'; -import * as typeConvert from 'vs/workbench/api/common/extHostTypeConverters'; -import { ChatMessageRole } from 'vs/workbench/api/common/extHostTypes'; -import { IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; -import type * as vscode from 'vscode'; - -export class ExtHostChatAgents implements ExtHostChatAgentsShape { - - private static _idPool = 0; - - private readonly _agents = new Map(); - private readonly _proxy: MainThreadChatAgentsShape; - - constructor( - mainContext: IMainContext, - private readonly _extHostChatProvider: ExtHostChatProvider, - private readonly _logService: ILogService, - ) { - this._proxy = mainContext.getProxy(MainContext.MainThreadChatAgents); - } - - registerAgent(extension: ExtensionIdentifier, name: string, agent: vscode.ChatAgent, metadata: vscode.ChatAgentMetadata): IDisposable { - const handle = ExtHostChatAgents._idPool++; - this._agents.set(handle, { extension, agent }); - this._proxy.$registerAgent(handle, name, metadata); - - return toDisposable(() => { - this._proxy.$unregisterAgent(handle); - this._agents.delete(handle); - }); - } - - async $invokeAgent(handle: number, requestId: number, prompt: string, context: { history: IChatMessage[] }, token: CancellationToken): Promise { - const data = this._agents.get(handle); - if (!data) { - this._logService.warn(`[CHAT](${handle}) CANNOT invoke agent because the agent is not registered`); - return; - } - - let done = false; - function throwIfDone() { - if (done) { - throw new Error('Only valid while executing the command'); - } - } - - const commandExecution = new DeferredPromise(); - token.onCancellationRequested(() => commandExecution.complete()); - setTimeout(() => commandExecution.complete(), 10 * 1000); - this._extHostChatProvider.allowListExtensionWhile(data.extension, commandExecution.p); - - const task = data.agent( - { role: ChatMessageRole.User, content: prompt }, - { history: context.history.map(typeConvert.ChatMessage.to) }, - new Progress(p => { - throwIfDone(); - this._proxy.$handleProgressChunk(requestId, { content: isInteractiveProgressFileTree(p.message) ? p.message : p.message.value }); - }), - token - ); - - try { - return await raceCancellation(Promise.resolve(task).then((v) => { - if (v && 'followUp' in v) { - const convertedFollowup = v?.followUp?.map(f => typeConvert.ChatFollowup.from(f)); - return { followUp: convertedFollowup }; - } - return undefined; - }), token); - } finally { - done = true; - commandExecution.complete(); - } - } -} - -function isInteractiveProgressFileTree(thing: unknown): thing is vscode.InteractiveProgressFileTree { - return !!thing && typeof thing === 'object' && 'treeData' in thing; -} diff --git a/src/vs/workbench/api/common/extHostChatAgents2.ts b/src/vs/workbench/api/common/extHostChatAgents2.ts index 2bdb2498e51..3d188ad39b2 100644 --- a/src/vs/workbench/api/common/extHostChatAgents2.ts +++ b/src/vs/workbench/api/common/extHostChatAgents2.ts @@ -217,6 +217,9 @@ class ExtHostChatAgent { private _fullName: string | undefined; private _iconPath: vscode.Uri | { light: vscode.Uri; dark: vscode.Uri } | vscode.ThemeIcon | undefined; private _isDefault: boolean | undefined; + private _helpTextPrefix: string | vscode.MarkdownString | undefined; + private _helpTextPostfix: string | vscode.MarkdownString | undefined; + private _sampleRequest?: string; private _isSecondary: boolean | undefined; private _onDidReceiveFeedback = new Emitter(); private _onDidPerformAction = new Emitter(); @@ -259,7 +262,14 @@ class ExtHostChatAgent { return []; } this._lastSlashCommands = result; - return result.map(c => ({ name: c.name, description: c.description, followupPlaceholder: c.followupPlaceholder, shouldRepopulate: c.shouldRepopulate })); + return result + .map(c => ({ + name: c.name, + description: c.description, + followupPlaceholder: c.followupPlaceholder, + shouldRepopulate: c.shouldRepopulate, + sampleRequest: c.sampleRequest + })); } async provideFollowups(result: vscode.ChatAgentResult2, token: CancellationToken): Promise { @@ -300,6 +310,9 @@ class ExtHostChatAgent { hasFollowup: this._followupProvider !== undefined, isDefault: this._isDefault, isSecondary: this._isSecondary, + helpTextPrefix: (!this._helpTextPrefix || typeof this._helpTextPrefix === 'string') ? this._helpTextPrefix : typeConvert.MarkdownString.from(this._helpTextPrefix), + helpTextPostfix: (!this._helpTextPostfix || typeof this._helpTextPostfix === 'string') ? this._helpTextPostfix : typeConvert.MarkdownString.from(this._helpTextPostfix), + sampleRequest: this._sampleRequest, }); updateScheduled = false; }); @@ -354,6 +367,32 @@ class ExtHostChatAgent { that._isDefault = v; updateMetadataSoon(); }, + get helpTextPrefix() { + checkProposedApiEnabled(that.extension, 'defaultChatAgent'); + return that._helpTextPrefix; + }, + set helpTextPrefix(v) { + checkProposedApiEnabled(that.extension, 'defaultChatAgent'); + if (!that._isDefault) { + throw new Error('helpTextPrefix is only available on the default chat agent'); + } + + that._helpTextPrefix = v; + updateMetadataSoon(); + }, + get helpTextPostfix() { + checkProposedApiEnabled(that.extension, 'defaultChatAgent'); + return that._helpTextPostfix; + }, + set helpTextPostfix(v) { + checkProposedApiEnabled(that.extension, 'defaultChatAgent'); + if (!that._isDefault) { + throw new Error('helpTextPostfix is only available on the default chat agent'); + } + + that._helpTextPostfix = v; + updateMetadataSoon(); + }, get isSecondary() { checkProposedApiEnabled(that.extension, 'defaultChatAgent'); return that._isSecondary; @@ -363,6 +402,13 @@ class ExtHostChatAgent { that._isSecondary = v; updateMetadataSoon(); }, + get sampleRequest() { + return that._sampleRequest; + }, + set sampleRequest(v) { + that._sampleRequest = v; + updateMetadataSoon(); + }, get onDidReceiveFeedback() { return that._onDidReceiveFeedback.event; }, diff --git a/src/vs/workbench/api/common/extHostInlineChat.ts b/src/vs/workbench/api/common/extHostInlineChat.ts index 2cb0f52bb61..0b2c0ee02ce 100644 --- a/src/vs/workbench/api/common/extHostInlineChat.ts +++ b/src/vs/workbench/api/common/extHostInlineChat.ts @@ -193,32 +193,34 @@ export class ExtHostInteractiveEditor implements ExtHostInlineChatShape { placeholder: res.placeholder, }; - if (ExtHostInteractiveEditor._isMessageResponse(res)) { - return { - ...stub, - id, - type: InlineChatResponseType.Message, - message: typeConvert.MarkdownString.from(res.contents), - }; - } + if (ExtHostInteractiveEditor._isEditResponse(res)) { + const { edits, contents } = res; + const message = contents !== undefined ? typeConvert.MarkdownString.from(contents) : undefined; + if (edits instanceof extHostTypes.WorkspaceEdit) { + return { + ...stub, + id, + type: InlineChatResponseType.BulkEdit, + edits: typeConvert.WorkspaceEdit.from(edits), + message + }; - const { edits } = res; - if (edits instanceof extHostTypes.WorkspaceEdit) { - return { - ...stub, - id, - type: InlineChatResponseType.BulkEdit, - edits: typeConvert.WorkspaceEdit.from(edits), - }; - - } else { - return { - ...stub, - id, - type: InlineChatResponseType.EditorEdit, - edits: (edits).map(typeConvert.TextEdit.from), - }; + } else { + return { + ...stub, + id, + type: InlineChatResponseType.EditorEdit, + edits: (edits).map(typeConvert.TextEdit.from), + message + }; + } } + return { + ...stub, + id, + type: InlineChatResponseType.Message, + message: typeConvert.MarkdownString.from(res.contents), + }; } $handleFeedback(handle: number, sessionId: number, responseId: number, kind: InlineChatResponseFeedbackKind): void { @@ -235,7 +237,7 @@ export class ExtHostInteractiveEditor implements ExtHostInlineChatShape { // TODO@jrieken remove this } - private static _isMessageResponse(thing: any): thing is vscode.InteractiveEditorMessageResponse { - return typeof thing === 'object' && typeof (thing).contents === 'object'; + private static _isEditResponse(thing: any): thing is vscode.InteractiveEditorResponse { + return typeof thing === 'object' && typeof (thing).edits === 'object'; } } diff --git a/src/vs/workbench/api/common/extHostSCM.ts b/src/vs/workbench/api/common/extHostSCM.ts index fe6da7f00c0..7f372aea4c6 100644 --- a/src/vs/workbench/api/common/extHostSCM.ts +++ b/src/vs/workbench/api/common/extHostSCM.ts @@ -461,7 +461,7 @@ class ExtHostSourceControlResourceGroup implements vscode.SourceControlResourceG let command: ICommandDto | undefined; if (r.command) { - if (r.command.command === 'vscode.open' || r.command.command === 'vscode.diff') { + if (r.command.command === 'vscode.open' || r.command.command === 'vscode.diff' || r.command.command === 'vscode.changes') { const disposables = new DisposableStore(); command = this._commands.converter.toInternal(r.command, disposables); this._resourceStatesDisposablesMap.set(handle, disposables); @@ -1002,14 +1002,7 @@ export class ExtHostSCM implements ExtHostSCMShape { const historyProvider = this._sourceControls.get(sourceControlHandle)?.historyProvider; const historyItems = await historyProvider?.provideHistoryItems(historyItemGroupId, options, token); - return historyItems?.map(item => ({ - id: item.id, - parentIds: item.parentIds, - label: item.label, - description: item.description, - icon: getHistoryItemIconDto(item), - timestamp: item.timestamp, - })) ?? undefined; + return historyItems?.map(item => ({ ...item, icon: getHistoryItemIconDto(item) })) ?? undefined; } async $provideHistoryItemChanges(sourceControlHandle: number, historyItemId: string, token: CancellationToken): Promise { diff --git a/src/vs/workbench/api/common/extHostTask.ts b/src/vs/workbench/api/common/extHostTask.ts index 49d668916a7..9489576cd03 100644 --- a/src/vs/workbench/api/common/extHostTask.ts +++ b/src/vs/workbench/api/common/extHostTask.ts @@ -746,7 +746,8 @@ export class WorkerExtHostTask extends ExtHostTaskBase { for (const task of value) { this.checkDeprecation(task, handler); if (!task.definition || !validTypes[task.definition.type]) { - this._logService.warn(`The task [${task.source}, ${task.name}] uses an undefined task type. The task will be ignored in the future.`); + const source = task.source ? task.source : 'No task source'; + this._logService.warn(`The task [${source}, ${task.name}] uses an undefined task type. The task will be ignored in the future.`); } const taskDTO: tasks.ITaskDTO | undefined = TaskDTO.from(task, handler.extension); diff --git a/src/vs/workbench/api/common/extHostTesting.ts b/src/vs/workbench/api/common/extHostTesting.ts index 65dd87b6e3f..7756fffa851 100644 --- a/src/vs/workbench/api/common/extHostTesting.ts +++ b/src/vs/workbench/api/common/extHostTesting.ts @@ -29,6 +29,7 @@ import { TestCommandId } from 'vs/workbench/contrib/testing/common/constants'; import { TestId, TestIdPathParts, TestPosition } from 'vs/workbench/contrib/testing/common/testId'; import { InvalidTestItemError } from 'vs/workbench/contrib/testing/common/testItemCollection'; import { AbstractIncrementalTestCollection, CoverageDetails, ICallProfileRunHandler, IFileCoverage, ISerializedTestResults, IStartControllerTests, IStartControllerTestsResult, ITestErrorMessage, ITestItem, ITestItemContext, ITestMessageMenuArgs, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, TestResultState, TestRunProfileBitset, TestsDiff, TestsDiffOp, isStartControllerTests } from 'vs/workbench/contrib/testing/common/testTypes'; +import { checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions'; import type * as vscode from 'vscode'; interface ControllerInfo { @@ -36,14 +37,18 @@ interface ControllerInfo { profiles: Map; collection: ExtHostTestItemCollection; extension: Readonly; + activeProfiles: Set; } -export class ExtHostTesting implements ExtHostTestingShape { - private readonly resultsChangedEmitter = new Emitter(); - private readonly controllers = new Map(); +type ActiveProfileChangeEvent = Map>; + +export class ExtHostTesting extends Disposable implements ExtHostTestingShape { + private readonly resultsChangedEmitter = this._register(new Emitter()); + protected readonly controllers = new Map(); private readonly proxy: MainThreadTestingShape; private readonly runTracker: TestRunCoordinator; private readonly observer: TestObservers; + private readonly activeProfilesChangedEmitter = this._register(new Emitter()); public onResultsChanged = this.resultsChangedEmitter.event; public results: ReadonlyArray = []; @@ -53,6 +58,7 @@ export class ExtHostTesting implements ExtHostTestingShape { commands: ExtHostCommands, private readonly editors: ExtHostDocumentsAndEditors, ) { + super(); this.proxy = rpc.getProxy(MainContext.MainThreadTesting); this.observer = new TestObservers(this.proxy); this.runTracker = new TestRunCoordinator(this.proxy); @@ -110,6 +116,7 @@ export class ExtHostTesting implements ExtHostTestingShape { collection.root.label = label; const profiles = new Map(); + const activeProfiles = new Set(); const proxy = this.proxy; const controller: vscode.TestController = { @@ -140,7 +147,7 @@ export class ExtHostTesting implements ExtHostTestingShape { profileId++; } - return new TestRunProfileImpl(this.proxy, profiles, controllerId, profileId, label, group, runHandler, isDefault, tag, supportsContinuousRun); + return new TestRunProfileImpl(this.proxy, profiles, extension, activeProfiles, this.activeProfilesChangedEmitter.event, controllerId, profileId, label, group, runHandler, isDefault, tag, supportsContinuousRun); }, createTestItem(id, label, uri) { return new TestItemImpl(controllerId, id, label, uri); @@ -170,7 +177,7 @@ export class ExtHostTesting implements ExtHostTestingShape { proxy.$registerTestController(controllerId, label, !!refreshHandler); disposable.add(toDisposable(() => proxy.$unregisterTestController(controllerId))); - const info: ControllerInfo = { controller, collection, profiles: profiles, extension }; + const info: ControllerInfo = { controller, collection, profiles, extension, activeProfiles }; this.controllers.set(controllerId, info); disposable.add(toDisposable(() => this.controllers.delete(controllerId))); @@ -245,6 +252,33 @@ export class ExtHostTesting implements ExtHostTestingShape { this.controllers.get(controllerId)?.profiles.get(profileId)?.configureHandler?.(); } + /** @inheritdoc */ + $setActiveRunProfiles(profiles: Record): void { + const evt: ActiveProfileChangeEvent = new Map(); + for (const [controllerId, profileIds] of Object.entries(profiles)) { + const ctrl = this.controllers.get(controllerId); + if (!ctrl) { + continue; + } + const changes = new Map(); + const added = profileIds.filter(id => !ctrl.activeProfiles.has(id)); + const removed = [...ctrl.activeProfiles].filter(id => !profileIds.includes(id)); + for (const id of added) { + changes.set(id, true); + ctrl.activeProfiles.add(id); + } + for (const id of removed) { + changes.set(id, false); + ctrl.activeProfiles.delete(id); + } + if (changes.size) { + evt.set(controllerId, changes); + } + } + + this.activeProfilesChangedEmitter.fire(evt); + } + /** @inheritdoc */ async $refreshTests(controllerId: string, token: CancellationToken) { await this.controllers.get(controllerId)?.controller.refreshHandler?.(token); @@ -1005,6 +1039,9 @@ class TestObservers { export class TestRunProfileImpl implements vscode.TestRunProfile { readonly #proxy: MainThreadTestingShape; + readonly #extension: IRelaxedExtensionDescription; + readonly #activeProfiles: ReadonlySet; + readonly #onDidChangeActiveProfiles: Event; #profiles?: Map; private _configureHandler?: (() => void); @@ -1065,9 +1102,25 @@ export class TestRunProfileImpl implements vscode.TestRunProfile { } } + public get isSelected() { + checkProposedApiEnabled(this.#extension, 'testingActiveProfile'); + return this.#activeProfiles.has(this.profileId); + } + + public get onDidChangeSelected() { + checkProposedApiEnabled(this.#extension, 'testingActiveProfile'); + return Event.chain(this.#onDidChangeActiveProfiles, $ => $ + .map(ev => ev.get(this.controllerId)?.get(this.profileId)) + .filter(isDefined) + ); + } + constructor( proxy: MainThreadTestingShape, profiles: Map, + extension: IRelaxedExtensionDescription, + activeProfiles: ReadonlySet, + onDidChangeActiveProfiles: Event, public readonly controllerId: string, public readonly profileId: number, private _label: string, @@ -1079,6 +1132,9 @@ export class TestRunProfileImpl implements vscode.TestRunProfile { ) { this.#proxy = proxy; this.#profiles = profiles; + this.#extension = extension; + this.#activeProfiles = activeProfiles; + this.#onDidChangeActiveProfiles = onDidChangeActiveProfiles; profiles.set(profileId, this); const groupBitset = profileGroupToBitset[kind]; diff --git a/src/vs/workbench/api/common/extHostTypeConverters.ts b/src/vs/workbench/api/common/extHostTypeConverters.ts index a0c7cd50b8d..8ae2479fd2f 100644 --- a/src/vs/workbench/api/common/extHostTypeConverters.ts +++ b/src/vs/workbench/api/common/extHostTypeConverters.ts @@ -2157,20 +2157,10 @@ export namespace DataTransfer { } export namespace ChatReplyFollowup { - export function to(followup: IChatReplyFollowup): vscode.InteractiveSessionReplyFollowup { - return { - message: followup.message, - metadata: followup.metadata, - title: followup.title, - tooltip: followup.tooltip, - }; - } - export function from(followup: vscode.InteractiveSessionReplyFollowup): IChatReplyFollowup { return { kind: 'reply', message: followup.message, - metadata: followup.metadata, title: followup.title, tooltip: followup.tooltip, }; @@ -2178,7 +2168,7 @@ export namespace ChatReplyFollowup { } export namespace ChatFollowup { - export function from(followup: string | vscode.InteractiveSessionFollowup): IChatFollowup { + export function from(followup: string | vscode.ChatAgentFollowup): IChatFollowup { if (typeof followup === 'string') { return { title: followup, message: followup, kind: 'reply' }; } else if ('commandId' in followup) { @@ -2303,11 +2293,9 @@ export namespace InteractiveEditorResponseFeedbackKind { } export namespace ChatResponseProgress { - export function from(extension: IExtensionDescription, progress: vscode.InteractiveProgress | vscode.ChatAgentExtendedProgress): extHostProtocol.IChatResponseProgressDto { + export function from(extension: IExtensionDescription, progress: vscode.ChatAgentExtendedProgress): extHostProtocol.IChatResponseProgressDto { if ('placeholder' in progress && 'resolvedContent' in progress) { return { placeholder: progress.placeholder }; - } else if ('responseId' in progress) { - return { requestId: progress.responseId }; } else if ('markdownContent' in progress) { checkProposedApiEnabled(extension, 'chatAgents2Additions'); return { content: MarkdownString.from(progress.markdownContent) }; diff --git a/src/vs/workbench/api/test/browser/extHostTesting.test.ts b/src/vs/workbench/api/test/browser/extHostTesting.test.ts index 0a7b6004539..11dfe6d72a1 100644 --- a/src/vs/workbench/api/test/browser/extHostTesting.test.ts +++ b/src/vs/workbench/api/test/browser/extHostTesting.test.ts @@ -7,21 +7,27 @@ import * as assert from 'assert'; import * as sinon from 'sinon'; import { VSBuffer } from 'vs/base/common/buffer'; import { CancellationTokenSource } from 'vs/base/common/cancellation'; +import { Event } from 'vs/base/common/event'; import { Iterable } from 'vs/base/common/iterator'; import { URI } from 'vs/base/common/uri'; -import { mockObject, MockObject } from 'vs/base/test/common/mock'; +import { mock, mockObject, MockObject } from 'vs/base/test/common/mock'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import * as editorRange from 'vs/editor/common/core/range'; -import { IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { ExtensionIdentifier, IRelaxedExtensionDescription } from 'vs/platform/extensions/common/extensions'; +import { NullLogService } from 'vs/platform/log/common/log'; import { MainThreadTestingShape } from 'vs/workbench/api/common/extHost.protocol'; +import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors'; -import { TestRunCoordinator, TestRunDto, TestRunProfileImpl } from 'vs/workbench/api/common/extHostTesting'; +import { IExtHostTelemetry } from 'vs/workbench/api/common/extHostTelemetry'; +import { ExtHostTesting, TestRunCoordinator, TestRunDto, TestRunProfileImpl } from 'vs/workbench/api/common/extHostTesting'; import { ExtHostTestItemCollection, TestItemImpl } from 'vs/workbench/api/common/extHostTestItem'; import * as convert from 'vs/workbench/api/common/extHostTypeConverters'; import { Location, Position, Range, TestMessage, TestResultState, TestRunProfileKind, TestRunRequest as TestRunRequestImpl, TestTag } from 'vs/workbench/api/common/extHostTypes'; +import { AnyCallRPCProtocol } from 'vs/workbench/api/test/common/testRPCProtocol'; import { TestId } from 'vs/workbench/contrib/testing/common/testId'; import { TestDiffOpType, TestItemExpandState, TestMessageType, TestsDiff } from 'vs/workbench/contrib/testing/common/testTypes'; -import type { TestItem, TestRunRequest } from 'vscode'; +import { nullExtensionDescription } from 'vs/workbench/services/extensions/common/extensions'; +import type { TestController, TestItem, TestRunProfile, TestRunRequest } from 'vscode'; const simplify = (item: TestItem) => ({ id: item.id, @@ -604,7 +610,7 @@ suite('ExtHost Testing', () => { cts = new CancellationTokenSource(); c = new TestRunCoordinator(proxy); - configuration = new TestRunProfileImpl(mockObject()(), new Map(), 'ctrlId', 42, 'Do Run', TestRunProfileKind.Run, () => { }, false); + configuration = new TestRunProfileImpl(mockObject()(), new Map(), nullExtensionDescription, new Set(), Event.None, 'ctrlId', 42, 'Do Run', TestRunProfileKind.Run, () => { }, false); await single.expand(single.root.id, Infinity); single.collectDiff(); @@ -867,4 +873,82 @@ suite('ExtHost Testing', () => { task1.end(); }); }); + + suite('service', () => { + let ctrl: TestExtHostTesting; + + class TestExtHostTesting extends ExtHostTesting { + public getProfileInternalId(ctrl: TestController, profile: TestRunProfile) { + for (const [id, p] of this.controllers.get(ctrl.id)!.profiles) { + if (profile === p) { + return id; + } + } + + throw new Error('profile not found'); + } + } + + setup(() => { + const rpcProtocol = AnyCallRPCProtocol(); + ctrl = ds.add(new TestExtHostTesting( + rpcProtocol, + new ExtHostCommands(rpcProtocol, new NullLogService(), new class extends mock() { + override onExtensionError(): boolean { + return true; + } + }), + new ExtHostDocumentsAndEditors(rpcProtocol, new NullLogService()), + )); + }); + + test('exposes active profiles correctly', async () => { + const extA = { ...nullExtensionDescription, identifier: new ExtensionIdentifier('ext.a'), enabledApiProposals: ['testingActiveProfile'] }; + const extB = { ...nullExtensionDescription, identifier: new ExtensionIdentifier('ext.b'), enabledApiProposals: ['testingActiveProfile'] }; + + const ctrlA = ds.add(ctrl.createTestController(extA, 'a', 'ctrla')); + const profAA = ds.add(ctrlA.createRunProfile('aa', TestRunProfileKind.Run, () => { })); + const profAB = ds.add(ctrlA.createRunProfile('ab', TestRunProfileKind.Run, () => { })); + + const ctrlB = ds.add(ctrl.createTestController(extB, 'b', 'ctrlb')); + const profBA = ds.add(ctrlB.createRunProfile('ba', TestRunProfileKind.Run, () => { })); + const profBB = ds.add(ctrlB.createRunProfile('bb', TestRunProfileKind.Run, () => { })); + const neverCalled = sinon.stub(); + + // empty default state: + assert.deepStrictEqual(profAA.isSelected, false); + assert.deepStrictEqual(profBA.isSelected, false); + assert.deepStrictEqual(profBB.isSelected, false); + + // fires a change event: + const changeA = Event.toPromise(profAA.onDidChangeSelected as Event); + const changeBA = Event.toPromise(profBA.onDidChangeSelected as Event); + const changeBB = Event.toPromise(profBB.onDidChangeSelected as Event); + + ds.add(profAB.onDidChangeSelected(neverCalled)); + assert.strictEqual(neverCalled.called, false); + + ctrl.$setActiveRunProfiles({ + a: [ctrl.getProfileInternalId(ctrlA, profAA)], + b: [ctrl.getProfileInternalId(ctrlB, profBA), ctrl.getProfileInternalId(ctrlB, profBB)] + }); + + assert.deepStrictEqual(await changeA, true); + assert.deepStrictEqual(await changeBA, true); + assert.deepStrictEqual(await changeBB, true); + + // updates internal state: + assert.deepStrictEqual(profAA.isSelected, true); + assert.deepStrictEqual(profBA.isSelected, true); + assert.deepStrictEqual(profBB.isSelected, true); + assert.deepStrictEqual(profAB.isSelected, false); + + // no-ops if equal + ds.add(profAA.onDidChangeSelected(neverCalled)); + ctrl.$setActiveRunProfiles({ + a: [ctrl.getProfileInternalId(ctrlA, profAA)], + }); + assert.strictEqual(neverCalled.called, false); + }); + }); }); diff --git a/src/vs/workbench/api/test/common/testRPCProtocol.ts b/src/vs/workbench/api/test/common/testRPCProtocol.ts index 7907665f711..abf854afa06 100644 --- a/src/vs/workbench/api/test/common/testRPCProtocol.ts +++ b/src/vs/workbench/api/test/common/testRPCProtocol.ts @@ -28,6 +28,18 @@ export function SingleProxyRPCProtocol(thing: any): IExtHostContext & IExtHostRp }; } +/** Makes a fake {@link SingleProxyRPCProtocol} on which any method can be called */ +export function AnyCallRPCProtocol(useCalls?: { [K in keyof T]: T[K] }) { + return SingleProxyRPCProtocol(new Proxy({}, { + get(_target, prop: string) { + if (useCalls && prop in useCalls) { + return (useCalls as any)[prop]; + } + return () => Promise.resolve(undefined); + } + })); +} + export class TestRPCProtocol implements IExtHostContext, IExtHostRpcService { public _serviceBrand: undefined; diff --git a/src/vs/workbench/browser/actions/layoutActions.ts b/src/vs/workbench/browser/actions/layoutActions.ts index 76f90e4cda5..e728b33e2ca 100644 --- a/src/vs/workbench/browser/actions/layoutActions.ts +++ b/src/vs/workbench/browser/actions/layoutActions.ts @@ -461,7 +461,7 @@ export class HideEditorTabsAction extends Action2 { original: 'Hide Editor Tabs' }, category: Categories.View, - precondition: ContextKeyExpr.equals('config.workbench.editor.showTabs', 'none').negate(), + precondition: ContextKeyExpr.and(ContextKeyExpr.equals('config.workbench.editor.showTabs', 'none').negate(), InEditorZenModeContext.negate()), f1: true }); } @@ -487,7 +487,7 @@ export class ShowMultipleEditorTabsAction extends Action2 { original: 'Show Multiple Editor Tabs' }, category: Categories.View, - precondition: ContextKeyExpr.equals('config.workbench.editor.showTabs', 'multiple').negate(), + precondition: ContextKeyExpr.and(ContextKeyExpr.equals('config.workbench.editor.showTabs', 'multiple').negate(), InEditorZenModeContext.negate()), f1: true }); } @@ -513,7 +513,7 @@ export class ShowSingleEditorTabAction extends Action2 { original: 'Show Single Editor Tab' }, category: Categories.View, - precondition: ContextKeyExpr.equals('config.workbench.editor.showTabs', 'single').negate(), + precondition: ContextKeyExpr.and(ContextKeyExpr.equals('config.workbench.editor.showTabs', 'single').negate(), InEditorZenModeContext.negate()), f1: true }); } @@ -534,6 +534,125 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { order: 10 }); +// --- Show Editor Actions in Title Bar + +export class EditorActionsTitleBarAction extends Action2 { + + static readonly ID = 'workbench.action.editorActionsTitleBar'; + + constructor() { + super({ + id: EditorActionsTitleBarAction.ID, + title: { + value: localize('moveEditorActionsToTitleBar', "Move Editor Actions to Title Bar"), + original: 'Move Editor Actions to Title Bar' + }, + category: Categories.View, + precondition: ContextKeyExpr.and( + ContextKeyExpr.equals('config.workbench.editor.editorActionsLocation', 'titleBar').negate(), + ContextKeyExpr.equals('config.window.style', 'native').negate(), + ), + f1: true + }); + } + + run(accessor: ServicesAccessor): Promise { + const configurationService = accessor.get(IConfigurationService); + return configurationService.updateValue('workbench.editor.editorActionsLocation', 'titleBar'); + } +} +registerAction2(EditorActionsTitleBarAction); + +// --- Editor Actions Default Position + +export class EditorActionsDefaultAction extends Action2 { + + static readonly ID = 'workbench.action.editorActionsDefault'; + + constructor() { + super({ + id: EditorActionsDefaultAction.ID, + title: { + value: localize('moveEditorActionsToTabBar', "Move Editor Actions to Tab Bar"), + original: 'Move Editor Actions to Tab Bar' + }, + category: Categories.View, + precondition: ContextKeyExpr.and( + ContextKeyExpr.equals('config.workbench.editor.editorActionsLocation', 'default').negate(), + ContextKeyExpr.equals('config.workbench.editor.showTabs', 'none').negate(), + ), + f1: true + }); + } + + run(accessor: ServicesAccessor): Promise { + const configurationService = accessor.get(IConfigurationService); + return configurationService.updateValue('workbench.editor.editorActionsLocation', 'default'); + } +} +registerAction2(EditorActionsDefaultAction); + +// --- Hide Editor Actions + +export class HideEditorActionsAction extends Action2 { + + static readonly ID = 'workbench.action.hideEditorActions'; + + constructor() { + super({ + id: HideEditorActionsAction.ID, + title: { + value: localize('hideEditorActons', "Hide Editor Actions"), + original: 'Hide Editor Actions' + }, + category: Categories.View, + precondition: ContextKeyExpr.equals('config.workbench.editor.editorActionsLocation', 'hidden').negate(), + f1: true + }); + } + + run(accessor: ServicesAccessor): Promise { + const configurationService = accessor.get(IConfigurationService); + return configurationService.updateValue('workbench.editor.editorActionsLocation', 'hidden'); + } +} +registerAction2(HideEditorActionsAction); + +// --- Hide Editor Actions + +export class ShowEditorActionsAction extends Action2 { + + static readonly ID = 'workbench.action.showEditorActions'; + + constructor() { + super({ + id: ShowEditorActionsAction.ID, + title: { + value: localize('showEditorActons', "Show Editor Actions"), + original: 'Show Editor Actions' + }, + category: Categories.View, + precondition: ContextKeyExpr.equals('config.workbench.editor.editorActionsLocation', 'hidden'), + f1: true + }); + } + + run(accessor: ServicesAccessor): Promise { + const configurationService = accessor.get(IConfigurationService); + return configurationService.updateValue('workbench.editor.editorActionsLocation', 'default'); + } +} +registerAction2(ShowEditorActionsAction); + +// --- Editor Actions Position Submenu in View Appearance Menu + +MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, { + submenu: MenuId.EditorActionsPositionSubmenu, + title: localize('editorActionsPosition', "Editor Actions Position"), + group: '3_workbench_layout_move', + order: 11 +}); + // --- Toggle Pinned Tabs On Separate Row registerAction2(class extends Action2 { diff --git a/src/vs/workbench/browser/actions/textInputActions.ts b/src/vs/workbench/browser/actions/textInputActions.ts index 053bb122740..680177fcb6a 100644 --- a/src/vs/workbench/browser/actions/textInputActions.ts +++ b/src/vs/workbench/browser/actions/textInputActions.ts @@ -8,7 +8,7 @@ import { localize } from 'vs/nls'; import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Disposable } from 'vs/base/common/lifecycle'; -import { EventHelper, addDisposableListener, getActiveDocument } from 'vs/base/browser/dom'; +import { EventHelper, addDisposableListener, getActiveDocument, getWindow } from 'vs/base/browser/dom'; import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { Registry } from 'vs/platform/registry/common/platform'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -78,12 +78,12 @@ export class TextInputActionsProvider extends Disposable implements IWorkbenchCo // Context menu support in input/textarea this._register(Event.runAndSubscribe(this.layoutService.onDidAddContainer, container => { - const listener = addDisposableListener(container, 'contextmenu', e => this.onContextMenu(e)); + const listener = addDisposableListener(container, 'contextmenu', e => this.onContextMenu(getWindow(container), e)); this._register(Event.filter(this.layoutService.onDidRemoveContainer, removed => removed === container, this._store)(() => listener.dispose())); }, this.layoutService.container)); } - private onContextMenu(e: MouseEvent): void { + private onContextMenu(targetWindow: Window, e: MouseEvent): void { if (e.defaultPrevented) { return; // make sure to not show these actions by accident if component indicated to prevent } @@ -95,7 +95,7 @@ export class TextInputActionsProvider extends Disposable implements IWorkbenchCo EventHelper.stop(e, true); - const event = new StandardMouseEvent(e); + const event = new StandardMouseEvent(targetWindow, e); this.contextMenuService.showContextMenu({ getAnchor: () => event, diff --git a/src/vs/workbench/browser/actions/windowActions.ts b/src/vs/workbench/browser/actions/windowActions.ts index 2466178ff12..14c7b8aa01f 100644 --- a/src/vs/workbench/browser/actions/windowActions.ts +++ b/src/vs/workbench/browser/actions/windowActions.ts @@ -35,6 +35,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { isFolderBackupInfo, isWorkspaceBackupInfo } from 'vs/platform/backup/common/backup'; import { getActiveElement, getActiveWindow } from 'vs/base/browser/dom'; +import { mainWindow } from 'vs/base/browser/window'; export const inRecentFilesPickerContextKey = 'inRecentFilesPicker'; @@ -335,7 +336,7 @@ export class ReloadWindowAction extends Action2 { override async run(accessor: ServicesAccessor): Promise { const hostService = accessor.get(IHostService); - if (getActiveWindow() === window) { + if (getActiveWindow() === mainWindow) { return hostService.reload(); // only supported for main window } } diff --git a/src/vs/workbench/browser/contextkeys.ts b/src/vs/workbench/browser/contextkeys.ts index 190ba3a1d9e..ae859cf8ea4 100644 --- a/src/vs/workbench/browser/contextkeys.ts +++ b/src/vs/workbench/browser/contextkeys.ts @@ -26,6 +26,7 @@ import { WebFileSystemAccess } from 'vs/platform/files/browser/webFileSystemAcce import { IProductService } from 'vs/platform/product/common/productService'; import { FileSystemProviderCapabilities, IFileService } from 'vs/platform/files/common/files'; import { getTitleBarStyle } from 'vs/platform/window/common/window'; +import { mainWindow } from 'vs/base/browser/window'; export class WorkbenchContextKeysHandler extends Disposable { private inputFocusedContext: IContextKey; @@ -112,7 +113,7 @@ export class WorkbenchContextKeysHandler extends Disposable { this.updateWorkspaceContextKeys(); // Capabilities - HasWebFileSystemAccess.bindTo(this.contextKeyService).set(WebFileSystemAccess.supported(window)); + HasWebFileSystemAccess.bindTo(this.contextKeyService).set(WebFileSystemAccess.supported(mainWindow)); // Development const isDevelopment = !this.environmentService.isBuilt || this.environmentService.isExtensionDevelopment; @@ -240,7 +241,7 @@ export class WorkbenchContextKeysHandler extends Disposable { this._register(this.editorGroupService.onDidChangeEditorPartOptions(() => this.updateEditorAreaContextKeys())); - this._register(Event.runAndSubscribe(onDidRegisterWindow, ({ window, disposables }) => disposables.add(addDisposableListener(window, EventType.FOCUS_IN, () => this.updateInputContextKeys(window.document), true)), { window, disposables: this._store })); + this._register(Event.runAndSubscribe(onDidRegisterWindow, ({ window, disposables }) => disposables.add(addDisposableListener(window, EventType.FOCUS_IN, () => this.updateInputContextKeys(window.document), true)), { window: mainWindow, disposables: this._store })); this._register(this.contextService.onDidChangeWorkbenchState(() => this.updateWorkbenchStateContextKey())); this._register(this.contextService.onDidChangeWorkspaceFolders(() => { diff --git a/src/vs/workbench/browser/dnd.ts b/src/vs/workbench/browser/dnd.ts index 3a7ba7f708d..b130fffd421 100644 --- a/src/vs/workbench/browser/dnd.ts +++ b/src/vs/workbench/browser/dnd.ts @@ -4,14 +4,14 @@ *--------------------------------------------------------------------------------------------*/ import { DataTransfers, IDragAndDropData } from 'vs/base/browser/dnd'; -import { DragAndDropObserver, EventType, addDisposableListener } from 'vs/base/browser/dom'; +import { DragAndDropObserver, EventType, addDisposableListener, onDidRegisterWindow } from 'vs/base/browser/dom'; import { DragMouseEvent } from 'vs/base/browser/mouseEvent'; import { IListDragAndDrop } from 'vs/base/browser/ui/list/list'; import { ElementsDragAndDropData } from 'vs/base/browser/ui/list/listView'; import { ITreeDragOverReaction } from 'vs/base/browser/ui/tree/tree'; import { coalesce } from 'vs/base/common/arrays'; import { UriList, VSDataTransfer } from 'vs/base/common/dataTransfer'; -import { Emitter } from 'vs/base/common/event'; +import { Emitter, Event } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable, markAsSingleton } from 'vs/base/common/lifecycle'; import { stringify } from 'vs/base/common/marshalling'; import { Mimes } from 'vs/base/common/mime'; @@ -35,6 +35,8 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; +import { mainWindow } from 'vs/base/browser/window'; +import { BroadcastDataChannel } from 'vs/base/browser/broadcast'; //#region Editor / Resources DND @@ -48,7 +50,6 @@ export class DraggedEditorGroupIdentifier { constructor(readonly identifier: GroupIdentifier) { } } - export async function extractTreeDropData(dataTransfer: VSDataTransfer): Promise> { const editors: IDraggedResourceEditorInput[] = []; const resourcesKey = Mimes.uriList.toLowerCase(); @@ -187,10 +188,10 @@ export class ResourcesDropHandler { } } -export function fillEditorsDragData(accessor: ServicesAccessor, resources: URI[], event: DragMouseEvent | DragEvent): void; -export function fillEditorsDragData(accessor: ServicesAccessor, resources: IResourceStat[], event: DragMouseEvent | DragEvent): void; -export function fillEditorsDragData(accessor: ServicesAccessor, editors: IEditorIdentifier[], event: DragMouseEvent | DragEvent): void; -export function fillEditorsDragData(accessor: ServicesAccessor, resourcesOrEditors: Array, event: DragMouseEvent | DragEvent): void { +export function fillEditorsDragData(accessor: ServicesAccessor, resources: URI[], event: DragMouseEvent | DragEvent, options?: { disableStandardTransfer: boolean }): void; +export function fillEditorsDragData(accessor: ServicesAccessor, resources: IResourceStat[], event: DragMouseEvent | DragEvent, options?: { disableStandardTransfer: boolean }): void; +export function fillEditorsDragData(accessor: ServicesAccessor, editors: IEditorIdentifier[], event: DragMouseEvent | DragEvent, options?: { disableStandardTransfer: boolean }): void; +export function fillEditorsDragData(accessor: ServicesAccessor, resourcesOrEditors: Array, event: DragMouseEvent | DragEvent, options?: { disableStandardTransfer: boolean }): void { if (resourcesOrEditors.length === 0 || !event.dataTransfer) { return; } @@ -217,22 +218,25 @@ export function fillEditorsDragData(accessor: ServicesAccessor, resourcesOrEdito return resourceOrEditor; })); + const fileSystemResources = resources.filter(({ resource }) => fileService.hasProvider(resource)); + if (!options?.disableStandardTransfer) { - // Text: allows to paste into text-capable areas - const lineDelimiter = isWindows ? '\r\n' : '\n'; - event.dataTransfer.setData(DataTransfers.TEXT, fileSystemResources.map(({ resource }) => labelService.getUriLabel(resource, { noPrefix: true })).join(lineDelimiter)); + // Text: allows to paste into text-capable areas + const lineDelimiter = isWindows ? '\r\n' : '\n'; + event.dataTransfer.setData(DataTransfers.TEXT, fileSystemResources.map(({ resource }) => labelService.getUriLabel(resource, { noPrefix: true })).join(lineDelimiter)); - // Download URL: enables support to drag a tab as file to desktop - // Requirements: - // - Chrome/Edge only - // - only a single file is supported - // - only file:/ resources are supported - const firstFile = fileSystemResources.find(({ isDirectory }) => !isDirectory); - if (firstFile) { - const firstFileUri = FileAccess.uriToFileUri(firstFile.resource); // enforce `file:` URIs - if (firstFileUri.scheme === Schemas.file) { - event.dataTransfer.setData(DataTransfers.DOWNLOAD_URL, [Mimes.binary, basename(firstFile.resource), firstFileUri.toString()].join(':')); + // Download URL: enables support to drag a tab as file to desktop + // Requirements: + // - Chrome/Edge only + // - only a single file is supported + // - only file:/ resources are supported + const firstFile = fileSystemResources.find(({ isDirectory }) => !isDirectory); + if (firstFile) { + const firstFileUri = FileAccess.uriToFileUri(firstFile.resource); // enforce `file:` URIs + if (firstFileUri.scheme === Schemas.file) { + event.dataTransfer.setData(DataTransfers.DOWNLOAD_URL, [Mimes.binary, basename(firstFile.resource), firstFileUri.toString()].join(':')); + } } } @@ -348,7 +352,9 @@ export function fillEditorsDragData(accessor: ServicesAccessor, resourcesOrEdito // a single uri for the real `text/uri-list` type. Otherwise all uris end up joined together // However we write the full uri-list to an internal type so that other parts of VS Code // can use the full list. - event.dataTransfer.setData(Mimes.uriList, UriList.create(uriListEntries.slice(0, 1))); + if (!options?.disableStandardTransfer) { + event.dataTransfer.setData(Mimes.uriList, UriList.create(uriListEntries.slice(0, 1))); + } event.dataTransfer.setData(DataTransfers.INTERNAL_URI_LIST, UriList.create(uriListEntries)); } } @@ -467,9 +473,6 @@ export class CompositeDragAndDropObserver extends Disposable { registerTarget(element: HTMLElement, callbacks: ICompositeDragAndDropObserverCallbacks): IDisposable { const disposableStore = new DisposableStore(); disposableStore.add(new DragAndDropObserver(element, { - onDragEnd: e => { - // no-op - }, onDragEnter: e => { e.preventDefault(); @@ -533,16 +536,15 @@ export class CompositeDragAndDropObserver extends Disposable { const disposableStore = new DisposableStore(); - disposableStore.add(addDisposableListener(element, EventType.DRAG_START, e => { - const { id, type } = draggedItemProvider(); - this.writeDragData(id, type); - - e.dataTransfer?.setDragImage(element, 0, 0); - - this.onDragStart.fire({ eventData: e, dragAndDropData: this.readDragData(type)! }); - })); - disposableStore.add(new DragAndDropObserver(element, { + onDragStart: e => { + const { id, type } = draggedItemProvider(); + this.writeDragData(id, type); + + e.dataTransfer?.setDragImage(element, 0, 0); + + this.onDragStart.fire({ eventData: e, dragAndDropData: this.readDragData(type)! }); + }, onDragEnd: e => { const { type } = draggedItemProvider(); const data = this.readDragData(type); @@ -661,3 +663,68 @@ export class ResourceListDnDHandler implements IListDragAndDrop { } //#endregion + +class GlobalWindowDraggedOverTracker extends Disposable { + + private static readonly CHANNEL_NAME = 'monaco-workbench-global-dragged-over'; + + private readonly broadcaster = this._register(new BroadcastDataChannel(GlobalWindowDraggedOverTracker.CHANNEL_NAME)); + + constructor() { + super(); + + this.registerListeners(); + } + + private registerListeners(): void { + this._register(Event.runAndSubscribe(onDidRegisterWindow, ({ window, disposables }) => { + disposables.add(addDisposableListener(window, EventType.DRAG_OVER, () => this.markDraggedOver(false), true)); + disposables.add(addDisposableListener(window, EventType.DRAG_LEAVE, () => this.clearDraggedOver(false), true)); + }, { window: mainWindow, disposables: this._store })); + + this._register(this.broadcaster.onDidReceiveData(data => { + if (data === true) { + this.markDraggedOver(true); + } else { + this.clearDraggedOver(true); + } + })); + } + + private draggedOver = false; + get isDraggedOver(): boolean { return this.draggedOver; } + + private markDraggedOver(fromBroadcast: boolean): void { + if (this.draggedOver === true) { + return; // alrady marked + } + + this.draggedOver = true; + + if (!fromBroadcast) { + this.broadcaster.postData(true); + } + } + + private clearDraggedOver(fromBroadcast: boolean): void { + if (this.draggedOver === false) { + return; // alrady cleared + } + + this.draggedOver = false; + + if (!fromBroadcast) { + this.broadcaster.postData(false); + } + } +} + +const globalDraggedOverTracker = new GlobalWindowDraggedOverTracker(); + +/** + * Returns whether the workbench is currently dragged over in any of + * the opened windows (main windows and auxiliary windows). + */ +export function isWindowDraggedOver(): boolean { + return globalDraggedOverTracker.isDraggedOver; +} diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 0836557e40a..77ba56af13e 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -177,13 +177,13 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi return containers; } - private getContainerFromDocument(document: Document): HTMLElement { - if (document === this.container.ownerDocument) { + private getContainerFromDocument(targetDocument: Document): HTMLElement { + if (targetDocument === this.container.ownerDocument) { // main window return this.container; } else { // auxiliary window - return document.body.getElementsByClassName('monaco-workbench')[0] as HTMLElement; + return targetDocument.body.getElementsByClassName('monaco-workbench')[0] as HTMLElement; } } @@ -230,7 +230,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // main window return this.mainContainerOffset; } else { - // TODO@bpasero auxiliary window: no support for custom title bar or banner yet + // auxiliary window: no support for custom title bar or banner yet return { top: 0, quickPickTop: 0 }; } } @@ -257,6 +257,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi private storageService!: IStorageService; private hostService!: IHostService; private editorService!: IEditorService; + private mainPartEditorService!: IEditorService; private editorGroupService!: IEditorGroupsService; private paneCompositeService!: IPaneCompositePartService; private titleService!: ITitleService; @@ -298,6 +299,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // Parts this.editorService = accessor.get(IEditorService); + this.mainPartEditorService = this.editorService.createScoped('main', this._store); this.editorGroupService = accessor.get(IEditorGroupsService); this.paneCompositeService = accessor.get(IPaneCompositePartService); this.viewDescriptorService = accessor.get(IViewDescriptorService); @@ -326,9 +328,9 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi // is ready to avoid conflicts on startup this.editorGroupService.mainPart.whenRestored.then(() => { - // Restore editor part on any editor change - this._register(this.editorService.onDidVisibleEditorsChange(showEditorIfHidden)); - this._register(this.editorGroupService.onDidActivateGroup(showEditorIfHidden)); + // Restore main editor part on any editor change in main part + this._register(this.mainPartEditorService.onDidVisibleEditorsChange(showEditorIfHidden)); + this._register(this.editorGroupService.mainPart.onDidActivateGroup(showEditorIfHidden)); // Revalidate center layout when active editor changes: diff editor quits centered mode. this._register(this.editorService.onDidActiveEditorChange(() => this.centerEditorLayout(this.stateModel.getRuntimeValue(LayoutStateKeys.EDITOR_CENTERED)))); @@ -1593,22 +1595,22 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi viewSize = this.workbenchGrid.getViewSize(this.editorPartView); // Single Editor Group - if (this.editorGroupService.count === 1) { + if (this.editorGroupService.mainPart.count === 1) { this.workbenchGrid.resizeView(this.editorPartView, { width: viewSize.width + sizeChangePxWidth, height: viewSize.height + sizeChangePxHeight }); } else { - const activeGroup = this.editorGroupService.activeGroup; + const activeGroup = this.editorGroupService.mainPart.activeGroup; - const { width, height } = this.editorGroupService.getSize(activeGroup); - this.editorGroupService.setSize(activeGroup, { width: width + sizeChangePxWidth, height: height + sizeChangePxHeight }); + const { width, height } = this.editorGroupService.mainPart.getSize(activeGroup); + this.editorGroupService.mainPart.setSize(activeGroup, { width: width + sizeChangePxWidth, height: height + sizeChangePxHeight }); // After resizing the editor group // if it does not change in either direction // try resizing the full editor part - const { width: newWidth, height: newHeight } = this.editorGroupService.getSize(activeGroup); + const { width: newWidth, height: newHeight } = this.editorGroupService.mainPart.getSize(activeGroup); if ((sizeChangePxHeight && height === newHeight) || (sizeChangePxWidth && width === newWidth)) { this.workbenchGrid.resizeView(this.editorPartView, { diff --git a/src/vs/workbench/browser/parts/compositeBar.ts b/src/vs/workbench/browser/parts/compositeBar.ts index b345f6a4c34..a2007e18299 100644 --- a/src/vs/workbench/browser/parts/compositeBar.ts +++ b/src/vs/workbench/browser/parts/compositeBar.ts @@ -9,7 +9,7 @@ import { IActivity } from 'vs/workbench/services/activity/common/activity'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ActionBar, ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { CompositeActionViewItem, CompositeOverflowActivityAction, CompositeOverflowActivityActionViewItem, CompositeBarAction, ICompositeBar, ICompositeBarColors, IActivityHoverOptions } from 'vs/workbench/browser/parts/compositeBarActions'; -import { Dimension, $, addDisposableListener, EventType, EventHelper, isAncestor } from 'vs/base/browser/dom'; +import { Dimension, $, addDisposableListener, EventType, EventHelper, isAncestor, getWindow } from 'vs/base/browser/dom'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; import { Widget } from 'vs/base/browser/ui/widget'; @@ -227,9 +227,9 @@ export class CompositeBar extends Widget implements ICompositeBar { })); // Contextmenu for composites - this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, e => this.showContextMenu(e))); + this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, e => this.showContextMenu(getWindow(parent), e))); this._register(Gesture.addTarget(parent)); - this._register(addDisposableListener(parent, TouchEventType.Contextmenu, e => this.showContextMenu(e))); + this._register(addDisposableListener(parent, TouchEventType.Contextmenu, e => this.showContextMenu(getWindow(parent), e))); // Register a drop target on the whole bar to prevent forbidden feedback let insertDropBefore: Before2D | undefined = undefined; @@ -620,10 +620,10 @@ export class CompositeBar extends Widget implements ICompositeBar { return this.model.visibleItems.filter(c => overflowingIds.includes(c.id)).map(item => { return { id: item.id, name: this.getAction(item.id)?.label || item.name }; }); } - private showContextMenu(e: MouseEvent | GestureEvent): void { + private showContextMenu(targetWindow: Window, e: MouseEvent | GestureEvent): void { EventHelper.stop(e, true); - const event = new StandardMouseEvent(e); + const event = new StandardMouseEvent(targetWindow, e); this.contextMenuService.showContextMenu({ getAnchor: () => event, getActions: () => this.getContextMenuActions(e) diff --git a/src/vs/workbench/browser/parts/editor/editor.contribution.ts b/src/vs/workbench/browser/parts/editor/editor.contribution.ts index 8ebad36c34b..fa77674ef5e 100644 --- a/src/vs/workbench/browser/parts/editor/editor.contribution.ts +++ b/src/vs/workbench/browser/parts/editor/editor.contribution.ts @@ -10,7 +10,7 @@ import { IEditorFactoryRegistry, EditorExtensions } from 'vs/workbench/common/ed import { TextCompareEditorActiveContext, ActiveEditorPinnedContext, EditorGroupEditorsCountContext, ActiveEditorStickyContext, ActiveEditorAvailableEditorIdsContext, EditorPartMultipleEditorGroupsContext, ActiveEditorDirtyContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, - EditorTabsVisibleContext, ActiveEditorLastInGroupContext, EditorPartMaximizedEditorGroupContext, MultipleEditorGroupsContext + EditorTabsVisibleContext, ActiveEditorLastInGroupContext, EditorPartMaximizedEditorGroupContext, MultipleEditorGroupsContext, InEditorZenModeContext } from 'vs/workbench/common/contextkeys'; import { SideBySideEditorInput, SideBySideEditorInputSerializer } from 'vs/workbench/common/editor/sideBySideEditorInput'; import { TextResourceEditor } from 'vs/workbench/browser/parts/editor/textResourceEditor'; @@ -65,8 +65,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { UntitledTextEditorInputSerializer, UntitledTextEditorWorkingCopyEditorHandler } from 'vs/workbench/services/untitled/common/untitledTextEditorHandler'; import { DynamicEditorConfigurations } from 'vs/workbench/browser/parts/editor/editorConfiguration'; -import { HideEditorTabsAction, ShowMultipleEditorTabsAction, ShowSingleEditorTabAction } from 'vs/workbench/browser/actions/layoutActions'; -import product from 'vs/platform/product/common/product'; +import { EditorActionsDefaultAction, EditorActionsTitleBarAction, HideEditorActionsAction, HideEditorTabsAction, ShowMultipleEditorTabsAction, ShowSingleEditorTabAction } from 'vs/workbench/browser/actions/layoutActions'; import { ICommandAction } from 'vs/platform/action/common/action'; //#region Editor Registrations @@ -293,11 +292,7 @@ registerAction2(QuickAccessLeastRecentlyUsedEditorAction); registerAction2(QuickAccessPreviousRecentlyUsedEditorInGroupAction); registerAction2(QuickAccessLeastRecentlyUsedEditorInGroupAction); registerAction2(QuickAccessPreviousEditorFromHistoryAction); - -if (product.quality !== 'stable') { - // TODO@bpasero revisit - registerAction2(ExperimentalMoveEditorIntoNewWindowAction); -} +registerAction2(ExperimentalMoveEditorIntoNewWindowAction); const quickAccessNavigateNextInEditorPickerId = 'workbench.action.quickOpenNavigateNextInEditorPicker'; KeybindingsRegistry.registerCommandAndKeybindingRule({ @@ -357,11 +352,17 @@ MenuRegistry.appendMenuItem(MenuId.EditorTabsBarContext, { command: { id: SPLIT_ MenuRegistry.appendMenuItem(MenuId.EditorTabsBarContext, { command: { id: SPLIT_EDITOR_DOWN, title: localize('splitDown', "Split Down") }, group: '2_split', order: 20 }); MenuRegistry.appendMenuItem(MenuId.EditorTabsBarContext, { command: { id: SPLIT_EDITOR_LEFT, title: localize('splitLeft', "Split Left") }, group: '2_split', order: 30 }); MenuRegistry.appendMenuItem(MenuId.EditorTabsBarContext, { command: { id: SPLIT_EDITOR_RIGHT, title: localize('splitRight', "Split Right") }, group: '2_split', order: 40 }); -MenuRegistry.appendMenuItem(MenuId.EditorTabsBarContext, { submenu: MenuId.EditorTabsBarShowTabsSubmenu, title: localize('tabBar', "Tab Bar"), group: '3_config', order: 10 }); + +MenuRegistry.appendMenuItem(MenuId.EditorTabsBarContext, { submenu: MenuId.EditorTabsBarShowTabsSubmenu, title: localize('tabBar', "Tab Bar"), group: '3_config', order: 10, when: InEditorZenModeContext.negate() }); MenuRegistry.appendMenuItem(MenuId.EditorTabsBarShowTabsSubmenu, { command: { id: ShowMultipleEditorTabsAction.ID, title: localize('multipleTabs', "Multiple Tabs"), toggled: ContextKeyExpr.equals('config.workbench.editor.showTabs', 'multiple') }, group: '1_config', order: 10 }); MenuRegistry.appendMenuItem(MenuId.EditorTabsBarShowTabsSubmenu, { command: { id: ShowSingleEditorTabAction.ID, title: localize('singleTab', "Single Tab"), toggled: ContextKeyExpr.equals('config.workbench.editor.showTabs', 'single') }, group: '1_config', order: 20 }); MenuRegistry.appendMenuItem(MenuId.EditorTabsBarShowTabsSubmenu, { command: { id: HideEditorTabsAction.ID, title: localize('hideTabBar', "Hide"), toggled: ContextKeyExpr.equals('config.workbench.editor.showTabs', 'none') }, group: '1_config', order: 30 }); +MenuRegistry.appendMenuItem(MenuId.EditorTabsBarContext, { submenu: MenuId.EditorActionsPositionSubmenu, title: localize('editorActionsPosition', "Editor Actions Position"), group: '3_config', order: 20 }); +MenuRegistry.appendMenuItem(MenuId.EditorActionsPositionSubmenu, { command: { id: EditorActionsDefaultAction.ID, title: localize('tabBar', "Tab Bar"), toggled: ContextKeyExpr.equals('config.workbench.editor.editorActionsLocation', 'default') }, group: '1_config', order: 10, when: ContextKeyExpr.equals('config.workbench.editor.showTabs', 'none').negate() }); +MenuRegistry.appendMenuItem(MenuId.EditorActionsPositionSubmenu, { command: { id: EditorActionsTitleBarAction.ID, title: localize('titleBar', "Title Bar"), toggled: ContextKeyExpr.or(ContextKeyExpr.equals('config.workbench.editor.editorActionsLocation', 'titleBar'), ContextKeyExpr.and(ContextKeyExpr.equals('config.workbench.editor.showTabs', 'none'), ContextKeyExpr.equals('config.workbench.editor.editorActionsLocation', 'default'))) }, group: '1_config', order: 20, when: ContextKeyExpr.equals('config.window.titleBarStyle', 'native').negate() }); +MenuRegistry.appendMenuItem(MenuId.EditorActionsPositionSubmenu, { command: { id: HideEditorActionsAction.ID, title: localize('hidden', "Hidden"), toggled: ContextKeyExpr.or(ContextKeyExpr.equals('config.workbench.editor.editorActionsLocation', 'hidden'), ContextKeyExpr.and(ContextKeyExpr.equals('config.window.titleBarStyle', 'native'), ContextKeyExpr.equals('config.workbench.editor.editorActionsLocation', 'titleBar'))) }, group: '1_config', order: 30 }); + // Editor Title Context Menu MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: CLOSE_EDITOR_COMMAND_ID, title: localize('close', "Close") }, group: '1_close', order: 10 }); MenuRegistry.appendMenuItem(MenuId.EditorTitleContext, { command: { id: CLOSE_OTHER_EDITORS_IN_GROUP_COMMAND_ID, title: localize('closeOthers', "Close Others"), precondition: EditorGroupEditorsCountContext.notEqualsTo('1') }, group: '1_close', order: 20 }); diff --git a/src/vs/workbench/browser/parts/editor/editor.ts b/src/vs/workbench/browser/parts/editor/editor.ts index 0c35bafff59..9622165c2ee 100644 --- a/src/vs/workbench/browser/parts/editor/editor.ts +++ b/src/vs/workbench/browser/parts/editor/editor.ts @@ -17,6 +17,7 @@ import { isObject } from 'vs/base/common/types'; import { IEditorOptions } from 'vs/platform/editor/common/editor'; import { IWindowsConfiguration } from 'vs/platform/window/common/window'; import { BooleanVerifier, EnumVerifier, NumberVerifier, ObjectVerifier, SetVerifier, verifyObject } from 'vs/base/common/verifier'; +import product from 'vs/platform/product/common/product'; export interface IEditorPartCreationOptions { readonly restorePreviousState: boolean; @@ -49,9 +50,10 @@ export const DEFAULT_EDITOR_PART_OPTIONS: IEditorPartOptions = { labelFormat: 'default', splitSizing: 'auto', splitOnDragAndDrop: true, + dragToOpenWindow: product.quality !== 'stable', centeredLayoutFixedWidth: false, doubleClickTabToToggleEditorGroupSizes: 'expand', - showEditorActionsInTitleBar: 'noTabs', + editorActionsLocation: 'default', wrapTabs: false, enablePreviewFromQuickOpen: false, scrollToSwitchTabs: false, @@ -131,6 +133,7 @@ function validateEditorPartOptions(options: IEditorPartOptions): IEditorPartOpti 'mouseBackForwardToNavigate': new BooleanVerifier(DEFAULT_EDITOR_PART_OPTIONS['mouseBackForwardToNavigate']), 'restoreViewState': new BooleanVerifier(DEFAULT_EDITOR_PART_OPTIONS['restoreViewState']), 'splitOnDragAndDrop': new BooleanVerifier(DEFAULT_EDITOR_PART_OPTIONS['splitOnDragAndDrop']), + 'dragToOpenWindow': new BooleanVerifier(DEFAULT_EDITOR_PART_OPTIONS['dragToOpenWindow']), 'centeredLayoutFixedWidth': new BooleanVerifier(DEFAULT_EDITOR_PART_OPTIONS['centeredLayoutFixedWidth']), 'hasIcons': new BooleanVerifier(DEFAULT_EDITOR_PART_OPTIONS['hasIcons']), @@ -150,7 +153,7 @@ function validateEditorPartOptions(options: IEditorPartOptions): IEditorPartOpti 'splitInGroupLayout': new EnumVerifier(DEFAULT_EDITOR_PART_OPTIONS['splitInGroupLayout'], ['vertical', 'horizontal']), 'splitSizing': new EnumVerifier(DEFAULT_EDITOR_PART_OPTIONS['splitSizing'], ['distribute', 'split', 'auto']), 'doubleClickTabToToggleEditorGroupSizes': new EnumVerifier(DEFAULT_EDITOR_PART_OPTIONS['doubleClickTabToToggleEditorGroupSizes'], ['maximize', 'expand', 'off']), - 'showEditorActionsInTitleBar': new EnumVerifier(DEFAULT_EDITOR_PART_OPTIONS['showEditorActionsInTitleBar'], ['noTabs', 'never']), + 'editorActionsLocation': new EnumVerifier(DEFAULT_EDITOR_PART_OPTIONS['editorActionsLocation'], ['default', 'titleBar', 'hidden']), 'autoLockGroups': new SetVerifier(DEFAULT_EDITOR_PART_OPTIONS['autoLockGroups']), 'limit': new ObjectVerifier(DEFAULT_EDITOR_PART_OPTIONS['limit'], { diff --git a/src/vs/workbench/browser/parts/editor/editorCommands.ts b/src/vs/workbench/browser/parts/editor/editorCommands.ts index f8c4178a374..c7cee50d8c3 100644 --- a/src/vs/workbench/browser/parts/editor/editorCommands.ts +++ b/src/vs/workbench/browser/parts/editor/editorCommands.ts @@ -7,7 +7,7 @@ import { localize } from 'vs/nls'; import { isObject, isString, isUndefined, isNumber } from 'vs/base/common/types'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; -import { IEditorIdentifier, IEditorCommandsContext, CloseDirection, IVisibleEditorPane, EditorsOrder, EditorInputCapabilities, isEditorIdentifier, isEditorInputWithOptionsAndGroup, IUntitledTextResourceEditorInput } from 'vs/workbench/common/editor'; +import { IEditorIdentifier, IEditorCommandsContext, CloseDirection, IVisibleEditorPane, EditorsOrder, EditorInputCapabilities, isEditorIdentifier, isEditorInputWithOptionsAndGroup, IUntitledTextResourceEditorInput, IResourceDiffEditorInput } from 'vs/workbench/common/editor'; import { TextCompareEditorVisibleContext, ActiveEditorGroupEmptyContext, MultipleEditorGroupsContext, ActiveEditorStickyContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, TextCompareEditorActiveContext, SideBySideEditorActiveContext } from 'vs/workbench/common/contextkeys'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { EditorGroupColumn, columnToEditorGroup } from 'vs/workbench/services/editor/common/editorGroupColumn'; @@ -632,6 +632,37 @@ function registerOpenEditorAPICommands(): void { await editorService.openEditor({ resource: URI.from(resource, true), options: { ...optionsArg, pinned: true, override: id } }, columnToEditorGroup(editorGroupsService, configurationService, columnArg)); }); + + // partial, renderer-side API command to open diff editor + // complements https://github.com/microsoft/vscode/blob/2b164efb0e6a5de3826bff62683eaeafe032284f/src/vs/workbench/api/common/extHostApiCommands.ts#L397 + CommandsRegistry.registerCommand({ + id: 'vscode.changes', + handler: (accessor, title: string, resources: [UriComponents, UriComponents?, UriComponents?][]) => { + accessor.get(ICommandService).executeCommand('_workbench.changes', title, resources); + }, + metadata: { + description: 'Opens a list of resources in the changes editor to compare their contents.', + args: [ + { name: 'title', description: 'Human readable title for the diff editor' }, + { name: 'resources', description: 'List of resources to open in the changes editor' } + ] + } + }); + + CommandsRegistry.registerCommand('_workbench.changes', async (accessor: ServicesAccessor, title: string, resources: [UriComponents, UriComponents?, UriComponents?][]) => { + const editorService = accessor.get(IEditorService); + + const editor: (IResourceDiffEditorInput & { resource: URI })[] = []; + for (const [label, original, modified] of resources) { + editor.push({ + resource: URI.revive(label), + original: { resource: URI.revive(original) }, + modified: { resource: URI.revive(modified) }, + }); + } + + await editorService.openEditor({ resources: editor, label: title }); + }); } function registerOpenEditorAtIndexCommands(): void { diff --git a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts index 646f5869387..14f3ad5b728 100644 --- a/src/vs/workbench/browser/parts/editor/editorDropTarget.ts +++ b/src/vs/workbench/browser/parts/editor/editorDropTarget.ts @@ -144,7 +144,6 @@ class DropOverlay extends Themable { private registerListeners(container: HTMLElement): void { this._register(new DragAndDropObserver(container, { - onDragEnter: e => undefined, onDragOver: e => { if (this.enableDropIntoEditor && isDragIntoEditorEvent(e)) { this.dispose(); diff --git a/src/vs/workbench/browser/parts/editor/editorGroupView.ts b/src/vs/workbench/browser/parts/editor/editorGroupView.ts index 0da1794a339..735e5730690 100644 --- a/src/vs/workbench/browser/parts/editor/editorGroupView.ts +++ b/src/vs/workbench/browser/parts/editor/editorGroupView.ts @@ -9,9 +9,9 @@ import { GroupIdentifier, CloseDirection, IEditorCloseEvent, IEditorPane, SaveRe import { ActiveEditorGroupLockedContext, ActiveEditorDirtyContext, EditorGroupEditorsCountContext, ActiveEditorStickyContext, ActiveEditorPinnedContext, ActiveEditorLastInGroupContext, ActiveEditorFirstInGroupContext, EditorPinnedAndUnpinnedTabsContext, ResourceContextKey, applyAvailableEditorIds, ActiveEditorAvailableEditorIdsContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext } from 'vs/workbench/common/contextkeys'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { SideBySideEditorInput } from 'vs/workbench/common/editor/sideBySideEditorInput'; -import { Emitter, Relay, Event } from 'vs/base/common/event'; +import { Emitter, Relay } from 'vs/base/common/event'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { Dimension, trackFocus, addDisposableListener, EventType, EventHelper, findParentWithClass, isAncestor, IDomNodePagePosition, isMouseEvent, isActiveElement, focusWindow } from 'vs/base/browser/dom'; +import { Dimension, trackFocus, addDisposableListener, EventType, EventHelper, findParentWithClass, isAncestor, IDomNodePagePosition, isMouseEvent, isActiveElement, focusWindow, getWindow } from 'vs/base/browser/dom'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; @@ -405,7 +405,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { // Find target anchor let anchor: HTMLElement | StandardMouseEvent = this.element; if (e) { - anchor = new StandardMouseEvent(e); + anchor = new StandardMouseEvent(getWindow(this.element), e); } // Show it @@ -1915,7 +1915,7 @@ export class EditorGroupView extends Themable implements IEditorGroupView { const primary: IAction[] = []; const secondary: IAction[] = []; - let onDidChange = Event.None; + let onDidChange; // Editor actions require the editor control to be there, so we retrieve it via service const activeEditorPane = this.activeEditorPane; @@ -1933,6 +1933,12 @@ export class EditorGroupView extends Themable implements IEditorGroupView { 'navigation', shouldInlineGroup ); + } else { + // If there is no active pane in the group (it's the last group and it's empty) + // Trigger the change event when the active editor changes + const _onDidChange = disposables.add(new Emitter()); + onDidChange = _onDidChange.event; + disposables.add(this.onDidActiveEditorChange(() => _onDidChange.fire())); } return { actions: { primary, secondary }, onDidChange }; diff --git a/src/vs/workbench/browser/parts/editor/editorPanes.ts b/src/vs/workbench/browser/parts/editor/editorPanes.ts index 571f0224217..f57b9e54e1c 100644 --- a/src/vs/workbench/browser/parts/editor/editorPanes.ts +++ b/src/vs/workbench/browser/parts/editor/editorPanes.ts @@ -28,6 +28,7 @@ import { ILogService } from 'vs/platform/log/common/log'; import { IDialogService, IPromptButton, IPromptCancelButton } from 'vs/platform/dialogs/common/dialogs'; import { IBoundarySashes } from 'vs/base/browser/ui/sash/sash'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { mainWindow } from 'vs/base/browser/window'; export interface IOpenEditorResult { @@ -130,8 +131,7 @@ export class EditorPanes extends Disposable { try { // Assert the `EditorInputCapabilities.AuxWindowUnsupported` condition - // TODO@bpasero revisit this once all editors can support aux windows - if (getWindow(this.editorPanesParent) !== window && editor.hasCapability(EditorInputCapabilities.AuxWindowUnsupported)) { + if (getWindow(this.editorPanesParent) !== mainWindow && editor.hasCapability(EditorInputCapabilities.AuxWindowUnsupported)) { return await this.doShowError(createEditorOpenError(localize('editorUnsupportedInAuxWindow', "This type of editor cannot be opened in floating windows yet."), [ toAction({ id: 'workbench.editor.action.closeEditor', label: localize('openFolder', "Close Editor"), run: async () => { diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 5df704b041e..9b8d619d2bf 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -759,11 +759,13 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { // Remove empty group if (groupView.isEmpty) { - return this.doRemoveEmptyGroup(groupView, preserveFocus); + this.doRemoveEmptyGroup(groupView, preserveFocus); } // Remove group with editors - this.doRemoveGroupWithEditors(groupView); + else { + this.doRemoveGroupWithEditors(groupView); + } } private doRemoveGroupWithEditors(groupView: IEditorGroupView): void { @@ -809,7 +811,8 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { this.updateContainer(); // Update locked state: clear when we are at just 1 group - if (this.count === 1) { + // in case we are in the main editor part + if (this.count === 1 && !this.isAuxiliary) { firstOrDefault(this.groups)?.lock(false); } @@ -918,7 +921,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupsView { return target; } - private assertGroupView(group: IEditorGroupView | GroupIdentifier): IEditorGroupView { + protected assertGroupView(group: IEditorGroupView | GroupIdentifier): IEditorGroupView { let groupView: IEditorGroupView | undefined; if (typeof group === 'number') { groupView = this.editorPartsView.getGroup(group); @@ -1405,12 +1408,26 @@ export class AuxiliaryEditorPart extends EditorPart implements IAuxiliaryEditorP super(editorPartsView, `workbench.parts.auxiliaryEditor.${id}`, groupsLabel, true, instantiationService, themeService, configurationService, storageService, layoutService, hostService, contextKeyService); } + override removeGroup(group: number | IEditorGroupView, preserveFocus?: boolean | undefined): void { + + // Close aux window when last group removed + const groupView = this.assertGroupView(group); + if (this.count === 1 && this.activeGroup === groupView) { + this.close(); + } + + // Otherwise delegate to parent implementation + else { + super.removeGroup(group, preserveFocus); + } + } + protected override saveState(): void { - return; // TODO@bpasero support auxiliary editor state + return; // TODO support auxiliary editor state } async close(): Promise { - // TODO@bpasero this needs full support for closing all editors, handling vetos and showing dialogs + // TODO this needs full support for closing all editors, handling vetos and showing dialogs this._onDidClose.fire(); } } diff --git a/src/vs/workbench/browser/parts/editor/editorParts.ts b/src/vs/workbench/browser/parts/editor/editorParts.ts index 7b52a57a303..4c6247a3687 100644 --- a/src/vs/workbench/browser/parts/editor/editorParts.ts +++ b/src/vs/workbench/browser/parts/editor/editorParts.ts @@ -40,7 +40,7 @@ export class EditorParts extends Disposable implements IEditorGroupsService, IEd //#region Auxiliary Editor Parts - async createAuxiliaryEditorPart(options?: { position?: IRectangle }): Promise { + async createAuxiliaryEditorPart(options?: { bounds?: Partial }): Promise { const disposables = new DisposableStore(); const auxiliaryWindow = disposables.add(await this.auxiliaryWindowService.open(options)); @@ -51,7 +51,7 @@ export class EditorParts extends Disposable implements IEditorGroupsService, IEd partContainer.setAttribute('role', 'main'); auxiliaryWindow.container.appendChild(partContainer); - const editorPart = disposables.add(this.instantiationService.createInstance(AuxiliaryEditorPart, this, this.getGroupsLabel(this.parts.size))); + const editorPart = disposables.add(this.instantiationService.createInstance(AuxiliaryEditorPart, this, this.getGroupsLabel(this._parts.size))); disposables.add(this.registerEditorPart(editorPart)); disposables.add(Event.once(editorPart.onDidClose)(() => disposables.dispose())); @@ -73,10 +73,11 @@ export class EditorParts extends Disposable implements IEditorGroupsService, IEd //#region Registration - private readonly parts = new Set(); + private readonly _parts = new Set(); + get parts() { return Array.from(this._parts); } private registerEditorPart(part: EditorPart): IDisposable { - this.parts.add(part); + this._parts.add(part); const disposables = this._register(new DisposableStore()); disposables.add(toDisposable(() => this.unregisterEditorPart(part))); @@ -87,12 +88,12 @@ export class EditorParts extends Disposable implements IEditorGroupsService, IEd } private unregisterEditorPart(part: EditorPart): void { - this.parts.delete(part); + this._parts.delete(part); // Notify all parts about a groups label change // given it is computed based on the index - Array.from(this.parts).forEach((part, index) => { + this.parts.forEach((part, index) => { if (part === this.mainPart) { return; } @@ -103,7 +104,7 @@ export class EditorParts extends Disposable implements IEditorGroupsService, IEd private registerEditorPartListeners(part: EditorPart, disposables: DisposableStore): void { disposables.add(part.onDidFocus(() => { - if (this.parts.size > 1) { + if (this._parts.size > 1) { this._onDidActiveGroupChange.fire(this.activeGroup); // this can only happen when we have more than 1 editor part } })); @@ -132,8 +133,8 @@ export class EditorParts extends Disposable implements IEditorGroupsService, IEd } private getPartByDocument(document: Document): EditorPart { - if (this.parts.size > 1) { - for (const part of this.parts) { + if (this._parts.size > 1) { + for (const part of this._parts) { if (part.element?.ownerDocument === document) { return part; } @@ -146,7 +147,7 @@ export class EditorParts extends Disposable implements IEditorGroupsService, IEd getPart(group: IEditorGroupView | GroupIdentifier): EditorPart; getPart(element: HTMLElement): EditorPart; getPart(groupOrElement: IEditorGroupView | GroupIdentifier | HTMLElement): EditorPart { - if (this.parts.size > 1) { + if (this._parts.size > 1) { if (groupOrElement instanceof HTMLElement) { const element = groupOrElement; @@ -161,7 +162,7 @@ export class EditorParts extends Disposable implements IEditorGroupsService, IEd id = group.id; } - for (const part of this.parts) { + for (const part of this._parts) { if (part.hasGroup(id)) { return part; } @@ -221,17 +222,17 @@ export class EditorParts extends Disposable implements IEditorGroupsService, IEd } getGroups(order = GroupsOrder.CREATION_TIME): IEditorGroupView[] { - if (this.parts.size > 1) { + if (this._parts.size > 1) { // TODO@bpasero support non-creation-time group orders across parts - return [...this.parts].map(part => part.getGroups(order)).flat(); + return [...this._parts].map(part => part.getGroups(order)).flat(); } return this.mainPart.getGroups(order); } getGroup(identifier: GroupIdentifier): IEditorGroupView | undefined { - if (this.parts.size > 1) { - for (const part of this.parts) { + if (this._parts.size > 1) { + for (const part of this._parts) { const group = part.getGroup(identifier); if (group) { return group; diff --git a/src/vs/workbench/browser/parts/editor/editorStatus.ts b/src/vs/workbench/browser/parts/editor/editorStatus.ts index d019690429b..1b31558cfc1 100644 --- a/src/vs/workbench/browser/parts/editor/editorStatus.ts +++ b/src/vs/workbench/browser/parts/editor/editorStatus.ts @@ -55,6 +55,7 @@ import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyChord, KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { TabFocus } from 'vs/editor/browser/config/tabFocus'; +import { mainWindow } from 'vs/base/browser/window'; class SideBySideEditorEncodingSupport implements IEncodingSupport { constructor(private primary: IEncodingSupport, private secondary: IEncodingSupport) { } @@ -334,7 +335,6 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { private toRender: StateChange | undefined = undefined; private editorService: IEditorService; - private targetWindow = window; constructor( @IEditorService editorService: IEditorService, @@ -562,7 +562,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { if (!this.toRender) { this.toRender = changed; - this.delayedRender.value = runAtThisOrScheduleAtNextAnimationFrame(() => { + this.delayedRender.value = runAtThisOrScheduleAtNextAnimationFrame(mainWindow, () => { this.delayedRender.clear(); const toRender = this.toRender; @@ -570,7 +570,7 @@ export class EditorStatus extends Disposable implements IWorkbenchContribution { if (toRender) { this.doRenderNow(); } - }, this.targetWindow); + }); } else { this.toRender.combine(changed); } diff --git a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts index 362707e380c..f7ffbc1d84c 100644 --- a/src/vs/workbench/browser/parts/editor/editorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/editorTabsControl.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/editortabscontrol'; import { localize } from 'vs/nls'; import { applyDragImage, DataTransfers } from 'vs/base/browser/dnd'; -import { addDisposableListener, Dimension, EventType, isMouseEvent } from 'vs/base/browser/dom'; +import { Dimension, getWindow, isMouseEvent } from 'vs/base/browser/dom'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { ActionsOrientation, IActionViewItem, prepareActions } from 'vs/base/browser/ui/actionbar/actionbar'; import { IAction, ActionRunner } from 'vs/base/common/actions'; @@ -22,10 +22,10 @@ import { INotificationService } from 'vs/platform/notification/common/notificati import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput'; import { listActiveSelectionBackground, listActiveSelectionForeground } from 'vs/platform/theme/common/colorRegistry'; import { IThemeService, Themable } from 'vs/platform/theme/common/themeService'; -import { DraggedEditorGroupIdentifier, DraggedEditorIdentifier, fillEditorsDragData } from 'vs/workbench/browser/dnd'; +import { DraggedEditorGroupIdentifier, DraggedEditorIdentifier, fillEditorsDragData, isWindowDraggedOver } from 'vs/workbench/browser/dnd'; import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; import { IEditorGroupsView, IEditorGroupView, IEditorPartsView, IInternalEditorOpenOptions } from 'vs/workbench/browser/parts/editor/editor'; -import { IEditorCommandsContext, EditorResourceAccessor, IEditorPartOptions, SideBySideEditor, EditorsOrder, EditorInputCapabilities, IToolbarActions } from 'vs/workbench/common/editor'; +import { IEditorCommandsContext, EditorResourceAccessor, IEditorPartOptions, SideBySideEditor, EditorsOrder, EditorInputCapabilities, IToolbarActions, GroupIdentifier } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { ResourceContextKey, ActiveEditorPinnedContext, ActiveEditorStickyContext, ActiveEditorGroupLockedContext, ActiveEditorCanSplitInGroupContext, SideBySideEditorActiveContext, ActiveEditorFirstInGroupContext, ActiveEditorAvailableEditorIdsContext, applyAvailableEditorIds, ActiveEditorLastInGroupContext } from 'vs/workbench/common/contextkeys'; import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview'; @@ -40,6 +40,8 @@ import { IEditorResolverService } from 'vs/workbench/services/editor/common/edit import { IEditorTitleControlDimensions } from 'vs/workbench/browser/parts/editor/editorTitleControl'; import { IReadonlyEditorGroupModel } from 'vs/workbench/common/editor/editorGroupModel'; import { EDITOR_CORE_NAVIGATION_COMMANDS } from 'vs/workbench/browser/parts/editor/editorCommands'; +import { IEditorGroupsService, MergeGroupMode } from 'vs/workbench/services/editor/common/editorGroupsService'; +import { isMacintosh } from 'vs/base/common/platform'; export class EditorCommandsContextActionRunner extends ActionRunner { @@ -96,8 +98,10 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC compact: 22 as const }; + protected editorActionsToolbarContainer: HTMLElement | undefined; private editorActionsToolbar: WorkbenchToolBar | undefined; - private editorActionsDisposables = this._register(new DisposableStore()); + private readonly editorActionsToolbarDisposables = this._register(new DisposableStore()); + private readonly editorActionsDisposables = this._register(new DisposableStore()); private resourceContext: ResourceContextKey; @@ -127,7 +131,8 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC @INotificationService private readonly notificationService: INotificationService, @IQuickInputService protected quickInputService: IQuickInputService, @IThemeService themeService: IThemeService, - @IEditorResolverService private readonly editorResolverService: IEditorResolverService + @IEditorResolverService private readonly editorResolverService: IEditorResolverService, + @IEditorGroupsService protected readonly editorGroupService: IEditorGroupsService ) { super(themeService); @@ -153,17 +158,47 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC this.updateTabHeight(); } - protected createEditorActionsToolBar(container: HTMLElement): void { + private get editorActionsEnabled(): boolean { + return this.groupsView.partOptions.editorActionsLocation === 'default' && this.groupsView.partOptions.showTabs !== 'none'; + } + + protected createEditorActionsToolBar(parent: HTMLElement, classes: string[]): void { + this.editorActionsToolbarContainer = document.createElement('div'); + this.editorActionsToolbarContainer.classList.add(...classes); + parent.appendChild(this.editorActionsToolbarContainer); + + this.handleEditorActionToolBarVisibility(this.editorActionsToolbarContainer); + } + + private handleEditorActionToolBarVisibility(container: HTMLElement): void { + const editorActionsEnabled = this.editorActionsEnabled; + const editorActionsVisible = !!this.editorActionsToolbar; + + // Create toolbar if it is enabled (and not yet created) + if (editorActionsEnabled && !editorActionsVisible) { + this.doCreateEditorActionsToolBar(container); + } + // Remove toolbar if it is not enabled (and is visible) + else if (!editorActionsEnabled && editorActionsVisible) { + this.editorActionsToolbar?.getElement().remove(); + this.editorActionsToolbar = undefined; + this.editorActionsToolbarDisposables.clear(); + this.editorActionsDisposables.clear(); + } + + container.classList.toggle('hidden', !editorActionsEnabled); + } + + private doCreateEditorActionsToolBar(container: HTMLElement): void { const context: IEditorCommandsContext = { groupId: this.groupView.id }; // Toolbar Widget - - this.editorActionsToolbar = this._register(this.instantiationService.createInstance(WorkbenchToolBar, container, { + this.editorActionsToolbar = this.editorActionsToolbarDisposables.add(this.instantiationService.createInstance(WorkbenchToolBar, container, { actionViewItemProvider: action => this.actionViewItemProvider(action), orientation: ActionsOrientation.HORIZONTAL, ariaLabel: localize('ariaLabelEditorActions', "Editor actions"), getKeyBinding: action => this.getKeybinding(action), - actionRunner: this._register(new EditorCommandsContextActionRunner(context)), + actionRunner: this.editorActionsToolbarDisposables.add(new EditorCommandsContextActionRunner(context)), anchorAlignmentProvider: () => AnchorAlignment.RIGHT, renderDropdownAsChildElement: this.renderDropdownAsChildElement, telemetrySource: 'editorPart', @@ -176,7 +211,7 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC this.editorActionsToolbar.context = context; // Action Run Handling - this._register(this.editorActionsToolbar.actionRunner.onDidRun(e => { + this.editorActionsToolbarDisposables.add(this.editorActionsToolbar.actionRunner.onDidRun(e => { // Notify for Error if (e.error && !isCancellationError(e.error)) { @@ -202,6 +237,10 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC } protected updateEditorActionsToolbar(): void { + if (!this.editorActionsEnabled) { + return; + } + this.editorActionsDisposables.clear(); const editorActions = this.groupView.createEditorActions(this.editorActionsDisposables); @@ -218,61 +257,98 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC } protected clearEditorActionsToolbar(): void { - this.editorActionsToolbar?.setActions([], []); + if (!this.editorActionsEnabled) { + return; + } + + const editorActionsToolbar = assertIsDefined(this.editorActionsToolbar); + editorActionsToolbar.setActions([], []); } - protected enableGroupDragging(element: HTMLElement): void { + protected onGroupDragStart(e: DragEvent, element: HTMLElement): void { + if (e.target !== element) { + return; // only if originating from tabs container + } - // Drag start - this._register(addDisposableListener(element, EventType.DRAG_START, e => { - if (e.target !== element) { - return; // only if originating from tabs container - } + // Set editor group as transfer + this.groupTransfer.setData([new DraggedEditorGroupIdentifier(this.groupView.id)], DraggedEditorGroupIdentifier.prototype); + if (e.dataTransfer) { + e.dataTransfer.effectAllowed = 'copyMove'; + } - // Set editor group as transfer - this.groupTransfer.setData([new DraggedEditorGroupIdentifier(this.groupView.id)], DraggedEditorGroupIdentifier.prototype); - if (e.dataTransfer) { - e.dataTransfer.effectAllowed = 'copyMove'; - } + // Drag all tabs of the group if tabs are enabled + let hasDataTransfer = false; + if (this.groupsView.partOptions.showTabs === 'multiple') { + hasDataTransfer = this.doFillResourceDataTransfers(this.groupView.getEditors(EditorsOrder.SEQUENTIAL), e); + } - // Drag all tabs of the group if tabs are enabled - let hasDataTransfer = false; - if (this.groupsView.partOptions.showTabs === 'multiple') { - hasDataTransfer = this.doFillResourceDataTransfers(this.groupView.getEditors(EditorsOrder.SEQUENTIAL), e); - } - - // Otherwise only drag the active editor - else { - if (this.groupView.activeEditor) { - hasDataTransfer = this.doFillResourceDataTransfers([this.groupView.activeEditor], e); - } - } - - // Firefox: requires to set a text data transfer to get going - if (!hasDataTransfer && isFirefox) { - e.dataTransfer?.setData(DataTransfers.TEXT, String(this.groupView.label)); - } - - // Drag Image + // Otherwise only drag the active editor + else { if (this.groupView.activeEditor) { - let label = this.groupView.activeEditor.getName(); - if (this.groupsView.partOptions.showTabs === 'multiple' && this.groupView.count > 1) { - label = localize('draggedEditorGroup', "{0} (+{1})", label, this.groupView.count - 1); - } - - applyDragImage(e, label, 'monaco-editor-group-drag-image', this.getColor(listActiveSelectionBackground), this.getColor(listActiveSelectionForeground)); + hasDataTransfer = this.doFillResourceDataTransfers([this.groupView.activeEditor], e); } - })); + } - // Drag end - this._register(addDisposableListener(element, EventType.DRAG_END, () => { - this.groupTransfer.clearData(DraggedEditorGroupIdentifier.prototype); - })); + // Firefox: requires to set a text data transfer to get going + if (!hasDataTransfer && isFirefox) { + e.dataTransfer?.setData(DataTransfers.TEXT, String(this.groupView.label)); + } + + // Drag Image + if (this.groupView.activeEditor) { + let label = this.groupView.activeEditor.getName(); + if (this.groupsView.partOptions.showTabs === 'multiple' && this.groupView.count > 1) { + label = localize('draggedEditorGroup', "{0} (+{1})", label, this.groupView.count - 1); + } + + applyDragImage(e, label, 'monaco-editor-group-drag-image', this.getColor(listActiveSelectionBackground), this.getColor(listActiveSelectionForeground)); + } + } + + protected async onGroupDragEnd(e: DragEvent, previousDragEvent: DragEvent | undefined, element: HTMLElement): Promise { + this.groupTransfer.clearData(DraggedEditorGroupIdentifier.prototype); + + if ( + e.target !== element || + !this.isNewWindowOperation(previousDragEvent ?? e) || + isWindowDraggedOver() + ) { + return; // drag to open in new window is disabled + } + + const auxiliaryEditorPart = await this.editorGroupService.createAuxiliaryEditorPart({ + bounds: { x: e.screenX, y: e.screenY } + }); + + const targetGroup = auxiliaryEditorPart.activeGroup; + this.groupsView.mergeGroup(this.groupView, targetGroup.id, { + mode: this.isMoveOperation(previousDragEvent ?? e, targetGroup.id) ? MergeGroupMode.MOVE_EDITORS : MergeGroupMode.COPY_EDITORS + }); + + targetGroup.focus(); + } + + protected isNewWindowOperation(e: DragEvent): boolean { + if (this.groupsView.partOptions.dragToOpenWindow) { + return !e.altKey; + } + + return e.altKey; + } + + protected isMoveOperation(e: DragEvent, sourceGroup: GroupIdentifier, sourceEditor?: EditorInput): boolean { + if (sourceEditor?.hasCapability(EditorInputCapabilities.Singleton)) { + return true; // Singleton editors cannot be split + } + + const isCopy = (e.ctrlKey && !isMacintosh) || (e.altKey && isMacintosh); + + return (!isCopy || sourceGroup === this.groupView.id); } protected doFillResourceDataTransfers(editors: readonly EditorInput[], e: DragEvent): boolean { if (editors.length) { - this.instantiationService.invokeFunction(fillEditorsDragData, editors.map(editor => ({ editor, groupId: this.groupView.id })), e); + this.instantiationService.invokeFunction(fillEditorsDragData, editors.map(editor => ({ editor, groupId: this.groupView.id })), e, { disableStandardTransfer: this.isNewWindowOperation(e) }); return true; } @@ -305,7 +381,7 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC // Find target anchor let anchor: HTMLElement | StandardMouseEvent = node; if (isMouseEvent(e)) { - anchor = new StandardMouseEvent(e); + anchor = new StandardMouseEvent(getWindow(node), e); } // Show it @@ -359,6 +435,17 @@ export abstract class EditorTabsControl extends Themable implements IEditorTabsC if (oldOptions.tabHeight !== newOptions.tabHeight) { this.updateTabHeight(); } + + // Update Editor Actions Toolbar + if ( + oldOptions.editorActionsLocation !== newOptions.editorActionsLocation || + oldOptions.showTabs !== newOptions.showTabs + ) { + if (this.editorActionsToolbarContainer) { + this.handleEditorActionToolBarVisibility(this.editorActionsToolbarContainer); + this.updateEditorActionsToolbar(); + } + } } abstract openEditor(editor: EditorInput): boolean; diff --git a/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css b/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css index 774237775de..f675a11075d 100644 --- a/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/multieditortabscontrol.css @@ -445,6 +445,10 @@ height: var(--editor-group-tab-height); } +.monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions.hidden { + display: none; +} + .monaco-workbench .part.editor > .content .editor-group-container > .title .editor-actions .action-item { margin-right: 4px; } diff --git a/src/vs/workbench/browser/parts/editor/media/singleeditortabscontrol.css b/src/vs/workbench/browser/parts/editor/media/singleeditortabscontrol.css index f3a7d7b2484..d48bcc90491 100644 --- a/src/vs/workbench/browser/parts/editor/media/singleeditortabscontrol.css +++ b/src/vs/workbench/browser/parts/editor/media/singleeditortabscontrol.css @@ -97,6 +97,11 @@ height: var(--editor-group-tab-height); } +.monaco-workbench .part.editor > .content .editor-group-container > .title > .title-actions.hidden { + display: none; +} + + .monaco-workbench .part.editor > .content .editor-group-container > .title > .title-actions .action-item { margin-right: 4px; } diff --git a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts index 9ed758b674a..f277b3a8514 100644 --- a/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/multiEditorTabsControl.ts @@ -6,7 +6,7 @@ import 'vs/css!./media/multieditortabscontrol'; import { isMacintosh, isWindows } from 'vs/base/common/platform'; import { shorten } from 'vs/base/common/labels'; -import { EditorResourceAccessor, GroupIdentifier, Verbosity, IEditorPartOptions, SideBySideEditor, DEFAULT_EDITOR_ASSOCIATION, EditorInputCapabilities, IUntypedEditorInput, preventEditorClose, EditorCloseMethod, EditorsOrder, IToolbarActions } from 'vs/workbench/common/editor'; +import { EditorResourceAccessor, Verbosity, IEditorPartOptions, SideBySideEditor, DEFAULT_EDITOR_ASSOCIATION, EditorInputCapabilities, IUntypedEditorInput, preventEditorClose, EditorCloseMethod, EditorsOrder, IToolbarActions } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { computeEditorAriaLabel } from 'vs/workbench/browser/editor'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; @@ -28,18 +28,18 @@ import { getOrSet } from 'vs/base/common/map'; import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/common/themeService'; import { TAB_INACTIVE_BACKGROUND, TAB_ACTIVE_BACKGROUND, TAB_ACTIVE_FOREGROUND, TAB_INACTIVE_FOREGROUND, TAB_BORDER, EDITOR_DRAG_AND_DROP_BACKGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND, TAB_UNFOCUSED_INACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_BACKGROUND, TAB_UNFOCUSED_ACTIVE_BORDER, TAB_ACTIVE_BORDER, TAB_HOVER_BACKGROUND, TAB_HOVER_BORDER, TAB_UNFOCUSED_HOVER_BACKGROUND, TAB_UNFOCUSED_HOVER_BORDER, EDITOR_GROUP_HEADER_TABS_BACKGROUND, WORKBENCH_BACKGROUND, TAB_ACTIVE_BORDER_TOP, TAB_UNFOCUSED_ACTIVE_BORDER_TOP, TAB_ACTIVE_MODIFIED_BORDER, TAB_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_ACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_MODIFIED_BORDER, TAB_UNFOCUSED_INACTIVE_BACKGROUND, TAB_HOVER_FOREGROUND, TAB_UNFOCUSED_HOVER_FOREGROUND, EDITOR_GROUP_HEADER_TABS_BORDER, TAB_LAST_PINNED_BORDER } from 'vs/workbench/common/theme'; import { activeContrastBorder, contrastBorder, editorBackground } from 'vs/platform/theme/common/colorRegistry'; -import { ResourcesDropHandler, DraggedEditorIdentifier, DraggedEditorGroupIdentifier, extractTreeDropData } from 'vs/workbench/browser/dnd'; +import { ResourcesDropHandler, DraggedEditorIdentifier, DraggedEditorGroupIdentifier, extractTreeDropData, isWindowDraggedOver } from 'vs/workbench/browser/dnd'; import { Color } from 'vs/base/common/color'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { MergeGroupMode, IMergeGroupOptions, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; -import { addDisposableListener, EventType, EventHelper, Dimension, scheduleAtNextAnimationFrame, findParentWithClass, clearNode, DragAndDropObserver, isMouseEvent, getWindow } from 'vs/base/browser/dom'; +import { addDisposableListener, EventType, EventHelper, Dimension, scheduleAtNextAnimationFrame, findParentWithClass, clearNode, DragAndDropObserver, isMouseEvent, getWindow, runWhenWindowIdle } from 'vs/base/browser/dom'; import { localize } from 'vs/nls'; import { IEditorGroupsView, EditorServiceImpl, IEditorGroupView, IInternalEditorOpenOptions, IEditorPartsView } from 'vs/workbench/browser/parts/editor/editor'; import { CloseOneEditorAction, UnpinEditorAction } from 'vs/workbench/browser/parts/editor/editorActions'; import { assertAllDefined, assertIsDefined } from 'vs/base/common/types'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { basenameOrAuthority } from 'vs/base/common/resources'; -import { RunOnceScheduler, runWhenIdle } from 'vs/base/common/async'; +import { RunOnceScheduler } from 'vs/base/common/async'; import { IPathService } from 'vs/workbench/services/path/common/pathService'; import { IPath, win32, posix } from 'vs/base/common/path'; import { coalesce, insert } from 'vs/base/common/arrays'; @@ -106,7 +106,6 @@ export class MultiEditorTabsControl extends EditorTabsControl { private titleContainer: HTMLElement | undefined; private tabsAndActionsContainer: HTMLElement | undefined; private tabsContainer: HTMLElement | undefined; - private editorToolbarContainer: HTMLElement | undefined; private tabsScrollbar: ScrollableElement | undefined; private tabSizingFixedDisposables: DisposableStore | undefined; @@ -148,12 +147,12 @@ export class MultiEditorTabsControl extends EditorTabsControl { @IThemeService themeService: IThemeService, @IEditorService private readonly editorService: EditorServiceImpl, @IPathService private readonly pathService: IPathService, - @IEditorGroupsService private readonly editorGroupService: IEditorGroupsService, + @IEditorGroupsService editorGroupService: IEditorGroupsService, @ITreeViewsDnDService private readonly treeViewsDragAndDropService: ITreeViewsDnDService, @IEditorResolverService editorResolverService: IEditorResolverService, @ILifecycleService private readonly lifecycleService: ILifecycleService ) { - super(parent, editorPartsView, groupsView, groupView, tabsModel, contextMenuService, instantiationService, contextKeyService, keybindingService, notificationService, quickInputService, themeService, editorResolverService); + super(parent, editorPartsView, groupsView, groupView, tabsModel, contextMenuService, instantiationService, contextKeyService, keybindingService, notificationService, quickInputService, themeService, editorResolverService, editorGroupService); // Resolve the correct path library for the OS we are on // If we are connected to remote, this accounts for the @@ -191,13 +190,8 @@ export class MultiEditorTabsControl extends EditorTabsControl { // Tabs Container listeners this.registerTabsContainerListeners(this.tabsContainer, this.tabsScrollbar); - // Editor Toolbar Container - this.editorToolbarContainer = document.createElement('div'); - this.editorToolbarContainer.classList.add('editor-actions'); - this.tabsAndActionsContainer.appendChild(this.editorToolbarContainer); - - // Editor Actions Toolbar - this.createEditorActionsToolBar(this.editorToolbarContainer); + // Create Editor Toolbar + this.createEditorActionsToolBar(this.tabsAndActionsContainer, ['editor-actions']); // Set tabs control visibility this.updateTabsControlVisibility(); @@ -277,9 +271,6 @@ export class MultiEditorTabsControl extends EditorTabsControl { private registerTabsContainerListeners(tabsContainer: HTMLElement, tabsScrollbar: ScrollableElement): void { - // Group dragging - this.enableGroupDragging(tabsContainer); - // Forward scrolling inside the container to our custom scrollbar this._register(addDisposableListener(tabsContainer, EventType.SCROLL, () => { if (tabsContainer.classList.contains('scroll')) { @@ -326,8 +317,17 @@ export class MultiEditorTabsControl extends EditorTabsControl { } })); - // Drop support + // Drag & Drop support + let lastDragEvent: DragEvent | undefined = undefined; this._register(new DragAndDropObserver(tabsContainer, { + onDragStart: e => { + this.onGroupDragStart(e, tabsContainer); + }, + + onDrag: e => { + lastDragEvent = e; + }, + onDragEnter: e => { // Always enable support to scroll while dragging @@ -385,6 +385,8 @@ export class MultiEditorTabsControl extends EditorTabsControl { onDragEnd: e => { this.updateDropFeedback(tabsContainer, false); tabsContainer.classList.remove('scroll'); + + this.onGroupDragEnd(e, lastDragEvent, tabsContainer); }, onDrop: e => { @@ -455,7 +457,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { // Find target anchor let anchor: HTMLElement | StandardMouseEvent = tabsContainer; if (isMouseEvent(e)) { - anchor = new StandardMouseEvent(e); + anchor = new StandardMouseEvent(getWindow(tabsContainer), e); } // Show it @@ -1020,29 +1022,33 @@ export class MultiEditorTabsControl extends EditorTabsControl { } }, true /* use capture to fix https://github.com/microsoft/vscode/issues/19145 */)); - // Drag support - disposables.add(addDisposableListener(tab, EventType.DRAG_START, e => { - const editor = this.tabsModel.getEditorByIndex(tabIndex); - if (!editor) { - return; - } - - this.editorTransfer.setData([new DraggedEditorIdentifier({ editor, groupId: this.groupView.id })], DraggedEditorIdentifier.prototype); - - if (e.dataTransfer) { - e.dataTransfer.effectAllowed = 'copyMove'; - } - - // Apply some datatransfer types to allow for dragging the element outside of the application - this.doFillResourceDataTransfers([editor], e); - - // Fixes https://github.com/microsoft/vscode/issues/18733 - tab.classList.add('dragged'); - scheduleAtNextAnimationFrame(() => tab.classList.remove('dragged'), getWindow(tab)); - })); - - // Drop support + // Drag & Drop support + let lastDragEvent: DragEvent | undefined = undefined; disposables.add(new DragAndDropObserver(tab, { + onDragStart: e => { + const editor = this.tabsModel.getEditorByIndex(tabIndex); + if (!editor) { + return; + } + + this.editorTransfer.setData([new DraggedEditorIdentifier({ editor, groupId: this.groupView.id })], DraggedEditorIdentifier.prototype); + + if (e.dataTransfer) { + e.dataTransfer.effectAllowed = 'copyMove'; + } + + // Apply some datatransfer types to allow for dragging the element outside of the application + this.doFillResourceDataTransfers([editor], e); + + // Fixes https://github.com/microsoft/vscode/issues/18733 + tab.classList.add('dragged'); + scheduleAtNextAnimationFrame(getWindow(tab), () => tab.classList.remove('dragged')); + }, + + onDrag: e => { + lastDragEvent = e; + }, + onDragEnter: e => { // Update class to signal drag operation @@ -1100,11 +1106,33 @@ export class MultiEditorTabsControl extends EditorTabsControl { this.updateDropFeedback(tab, false, tabIndex); }, - onDragEnd: () => { + onDragEnd: async e => { tab.classList.remove('dragged-over'); this.updateDropFeedback(tab, false, tabIndex); this.editorTransfer.clearData(DraggedEditorIdentifier.prototype); + + const editor = this.tabsModel.getEditorByIndex(tabIndex); + if ( + !this.isNewWindowOperation(lastDragEvent ?? e) || + isWindowDraggedOver() || + !editor + ) { + return; // drag to open in new window is disabled + } + + const auxiliaryEditorPart = await this.editorGroupService.createAuxiliaryEditorPart({ + bounds: { x: e.screenX, y: e.screenY } + }); + + const targetGroup = auxiliaryEditorPart.activeGroup; + if (this.isMoveOperation(lastDragEvent ?? e, targetGroup.id, editor)) { + this.groupView.moveEditor(editor, targetGroup); + } else { + this.groupView.copyEditor(editor, targetGroup); + } + + targetGroup.focus(); }, onDrop: e => { @@ -1622,9 +1650,9 @@ export class MultiEditorTabsControl extends EditorTabsControl { let scheduledLayout: IDisposable; if (this.lifecycleService.phase >= LifecyclePhase.Restored) { - scheduledLayout = scheduleAtNextAnimationFrame(layoutFunction, getWindow(this.tabsContainer)); + scheduledLayout = scheduleAtNextAnimationFrame(getWindow(this.tabsContainer), layoutFunction); } else { - scheduledLayout = runWhenIdle(layoutFunction); + scheduledLayout = runWhenWindowIdle(getWindow(this.tabsContainer), layoutFunction); } this.layoutScheduler.value = { options, dispose: () => scheduledLayout.dispose() }; @@ -1683,7 +1711,7 @@ export class MultiEditorTabsControl extends EditorTabsControl { } private doLayoutTabsWrapping(dimensions: IEditorTitleControlDimensions): boolean { - const [tabsAndActionsContainer, tabsContainer, editorToolbarContainer, tabsScrollbar] = assertAllDefined(this.tabsAndActionsContainer, this.tabsContainer, this.editorToolbarContainer, this.tabsScrollbar); + const [tabsAndActionsContainer, tabsContainer, editorToolbarContainer, tabsScrollbar] = assertAllDefined(this.tabsAndActionsContainer, this.tabsContainer, this.editorActionsToolbarContainer, this.tabsScrollbar); // Handle wrapping tabs according to setting: // - enabled: only add class if tabs wrap and don't exceed available dimensions @@ -2095,16 +2123,6 @@ export class MultiEditorTabsControl extends EditorTabsControl { } } - private isMoveOperation(e: DragEvent, sourceGroup: GroupIdentifier, sourceEditor?: EditorInput) { - if (sourceEditor?.hasCapability(EditorInputCapabilities.Singleton)) { - return true; // Singleton editors cannot be split - } - - const isCopy = (e.ctrlKey && !isMacintosh) || (e.altKey && isMacintosh); - - return (!isCopy || sourceGroup === this.groupView.id); - } - override dispose(): void { super.dispose(); diff --git a/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts b/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts index 03c24053b3e..8241b06ed91 100644 --- a/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts +++ b/src/vs/workbench/browser/parts/editor/singleEditorTabsControl.ts @@ -10,7 +10,7 @@ import { EditorTabsControl } from 'vs/workbench/browser/parts/editor/editorTabsC import { ResourceLabel, IResourceLabel } from 'vs/workbench/browser/labels'; import { TAB_ACTIVE_FOREGROUND, TAB_UNFOCUSED_ACTIVE_FOREGROUND } from 'vs/workbench/common/theme'; import { EventType as TouchEventType, GestureEvent, Gesture } from 'vs/base/browser/touch'; -import { addDisposableListener, EventType, EventHelper, Dimension, isAncestor } from 'vs/base/browser/dom'; +import { addDisposableListener, EventType, EventHelper, Dimension, isAncestor, DragAndDropObserver } from 'vs/base/browser/dom'; import { CLOSE_EDITOR_COMMAND_ID, UNLOCK_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { Color } from 'vs/base/common/color'; import { assertIsDefined, assertAllDefined } from 'vs/base/common/types'; @@ -66,19 +66,19 @@ export class SingleEditorTabsControl extends EditorTabsControl { titleContainer.classList.toggle('breadcrumbs', Boolean(this.breadcrumbsControl)); this._register(toDisposable(() => titleContainer.classList.remove('breadcrumbs'))); // important to remove because the container is a shared dom node - // Right Actions Container - const actionsContainer = document.createElement('div'); - actionsContainer.classList.add('title-actions'); - titleContainer.appendChild(actionsContainer); - - // Editor actions toolbar - this.createEditorActionsToolBar(actionsContainer); + // Create editor actions toolbar + this.createEditorActionsToolBar(titleContainer, ['title-actions']); } private registerContainerListeners(titleContainer: HTMLElement): void { - // Group dragging - this.enableGroupDragging(titleContainer); + // Drag & Drop support + let lastDragEvent: DragEvent | undefined = undefined; + this._register(new DragAndDropObserver(titleContainer, { + onDragStart: e => this.onGroupDragStart(e, titleContainer), + onDrag: e => { lastDragEvent = e; }, + onDragEnd: e => { this.onGroupDragEnd(e, lastDragEvent, titleContainer); }, + })); // Pin on double click this._register(addDisposableListener(titleContainer, EventType.DBLCLICK, e => this.onTitleDoubleClick(e))); diff --git a/src/vs/workbench/browser/parts/globalCompositeBar.ts b/src/vs/workbench/browser/parts/globalCompositeBar.ts index b6e16473762..13432c87811 100644 --- a/src/vs/workbench/browser/parts/globalCompositeBar.ts +++ b/src/vs/workbench/browser/parts/globalCompositeBar.ts @@ -18,12 +18,11 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { registerIcon } from 'vs/platform/theme/common/iconRegistry'; import { Action, IAction, Separator, SubmenuAction, toAction } from 'vs/base/common/actions'; import { IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; -import { addDisposableListener, EventType, append, clearNode, hide, show, EventHelper, $ } from 'vs/base/browser/dom'; +import { addDisposableListener, EventType, append, clearNode, hide, show, EventHelper, $, runWhenWindowIdle, getWindow } from 'vs/base/browser/dom'; import { StandardKeyboardEvent } from 'vs/base/browser/keyboardEvent'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { EventType as TouchEventType, GestureEvent } from 'vs/base/browser/touch'; import { AnchorAlignment, AnchorAxisAlignment } from 'vs/base/browser/ui/contextview/contextview'; -import { runWhenIdle } from 'vs/base/common/async'; import { Lazy } from 'vs/base/common/lazy'; import { createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -228,7 +227,7 @@ abstract class AbstractGlobalActivityActionViewItem extends CompoisteBarActionVi const disposables = new DisposableStore(); const actions = await this.resolveContextMenuActions(disposables); - const event = new StandardMouseEvent(e); + const event = new StandardMouseEvent(getWindow(this.container), e); this.contextMenuService.showContextMenu({ getAnchor: () => event, @@ -354,7 +353,7 @@ export class AccountsActivityActionViewItem extends AbstractGlobalActivityAction if (this._store.isDisposed) { return; } - const disposable = this._register(runWhenIdle(async () => { + const disposable = this._register(runWhenWindowIdle(getWindow(this.element), async () => { await this.doInitialize(); disposable.dispose(); })); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts index d341f22ee65..c0cb078cbbc 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsCommands.ts @@ -164,11 +164,11 @@ export function registerNotificationCommands(center: INotificationsCenterControl KeybindingsRegistry.registerCommandAndKeybindingRule({ id: ACCEPT_PRIMARY_ACTION_NOTIFICATION, weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(NotificationFocusedContext), + when: ContextKeyExpr.or(NotificationFocusedContext, NotificationsToastsVisibleContext), primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyA, handler: (accessor) => { const actionRunner = accessor.get(IInstantiationService).createInstance(NotificationActionRunner); - const notification = getNotificationFromContext(accessor.get(IListService)); + const notification = getNotificationFromContext(accessor.get(IListService)) || firstOrDefault(model.notifications); if (!notification) { return; } diff --git a/src/vs/workbench/browser/parts/notifications/notificationsList.ts b/src/vs/workbench/browser/parts/notifications/notificationsList.ts index 5ac8c173685..4dad9770b06 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsList.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsList.ts @@ -5,7 +5,7 @@ import 'vs/css!./media/notificationsList'; import { localize } from 'vs/nls'; -import { isAncestorOfActiveElement, trackFocus } from 'vs/base/browser/dom'; +import { getWindow, isAncestorOfActiveElement, trackFocus } from 'vs/base/browser/dom'; import { WorkbenchList } from 'vs/platform/list/browser/listService'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IListAccessibilityProvider, IListOptions } from 'vs/base/browser/ui/list/listWidget'; @@ -111,7 +111,7 @@ export class NotificationsList extends Disposable { // This ensures that when the focus comes back, the notification is still focused const listFocusTracker = this._register(trackFocus(list.getHTMLElement())); this._register(listFocusTracker.onDidBlur(() => { - if (document.hasFocus()) { + if (getWindow(this.listContainer).document.hasFocus()) { list.setFocus([]); } })); diff --git a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts index fb0f7f5d318..d9b8e5fa9fb 100644 --- a/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts +++ b/src/vs/workbench/browser/parts/notifications/notificationsToasts.ts @@ -150,7 +150,7 @@ export class NotificationsToasts extends Themable implements INotificationsToast // (see also https://github.com/microsoft/vscode/issues/107935) const itemDisposables = new DisposableStore(); this.mapNotificationToDisposable.set(item, itemDisposables); - itemDisposables.add(scheduleAtNextAnimationFrame(() => this.doAddToast(item, itemDisposables), getWindow(this.container))); + itemDisposables.add(scheduleAtNextAnimationFrame(getWindow(this.container), () => this.doAddToast(item, itemDisposables))); } private doAddToast(item: INotificationViewItem, itemDisposables: DisposableStore): void { diff --git a/src/vs/workbench/browser/parts/paneCompositePart.ts b/src/vs/workbench/browser/parts/paneCompositePart.ts index 3ce8ece1afd..cea4e592932 100644 --- a/src/vs/workbench/browser/parts/paneCompositePart.ts +++ b/src/vs/workbench/browser/parts/paneCompositePart.ts @@ -15,7 +15,7 @@ import { IView } from 'vs/base/browser/ui/grid/grid'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { CompositePart, ICompositeTitleLabel } from 'vs/workbench/browser/parts/compositePart'; import { IPaneCompositeBarOptions, PaneCompositeBar } from 'vs/workbench/browser/parts/paneCompositeBar'; -import { Dimension, EventHelper, trackFocus, $, addDisposableListener, EventType, prepend } from 'vs/base/browser/dom'; +import { Dimension, EventHelper, trackFocus, $, addDisposableListener, EventType, prepend, getWindow } from 'vs/base/browser/dom'; import { Registry } from 'vs/platform/registry/common/platform'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IStorageService } from 'vs/platform/storage/common/storage'; @@ -291,11 +291,11 @@ export abstract class AbstractPaneCompositePart extends CompositePart { - this.onTitleAreaContextMenu(new StandardMouseEvent(e)); + this.onTitleAreaContextMenu(new StandardMouseEvent(getWindow(titleArea), e)); })); this._register(Gesture.addTarget(titleArea)); this._register(addDisposableListener(titleArea, GestureEventType.Contextmenu, e => { - this.onTitleAreaContextMenu(new StandardMouseEvent(e)); + this.onTitleAreaContextMenu(new StandardMouseEvent(getWindow(titleArea), e)); })); const globalTitleActionsContainer = titleArea.appendChild($('.global-actions')); @@ -456,7 +456,7 @@ export abstract class AbstractPaneCompositePart extends CompositePart { + this._register(addDisposableListener(mainWindow, EventType.RESIZE, () => { if (this.menubar && !(isIOS && BrowserFeatures.pointerEvents)) { this.menubar.blur(); } diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index 2e34ebb58ef..8398a86e5e2 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -19,7 +19,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { TITLE_BAR_ACTIVE_BACKGROUND, TITLE_BAR_ACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_FOREGROUND, TITLE_BAR_INACTIVE_BACKGROUND, TITLE_BAR_BORDER, WORKBENCH_BACKGROUND } from 'vs/workbench/common/theme'; import { isMacintosh, isWindows, isLinux, isWeb, isNative, platformLocale } from 'vs/base/common/platform'; import { Color } from 'vs/base/common/color'; -import { EventType, EventHelper, Dimension, append, $, addDisposableListener, prepend, reset } from 'vs/base/browser/dom'; +import { EventType, EventHelper, Dimension, append, $, addDisposableListener, prepend, reset, getWindow } from 'vs/base/browser/dom'; import { CustomMenubarControl } from 'vs/workbench/browser/parts/titlebar/menubarControl'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { Emitter, Event } from 'vs/base/common/event'; @@ -27,7 +27,7 @@ import { IStorageService } from 'vs/platform/storage/common/storage'; import { Parts, IWorkbenchLayoutService, ActivityBarPosition, LayoutSettings } from 'vs/workbench/services/layout/browser/layoutService'; import { createActionViewItem, createAndFillInActionBarActions } from 'vs/platform/actions/browser/menuEntryActionViewItem'; import { Action2, IMenu, IMenuService, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; -import { ContextKeyExpr, ContextKeyExpression, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { Codicon } from 'vs/base/common/codicons'; import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry'; @@ -51,6 +51,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { ResolvedKeybinding } from 'vs/base/common/keybindings'; import { EditorCommandsContextActionRunner } from 'vs/workbench/browser/parts/editor/editorTabsControl'; import { IEditorCommandsContext, IToolbarActions } from 'vs/workbench/common/editor'; +import { mainWindow } from 'vs/base/browser/window'; export class TitlebarPart extends Part implements ITitleService { @@ -125,7 +126,7 @@ export class TitlebarPart extends Part implements ITitleService { @IKeybindingService private readonly keybindingService: IKeybindingService, ) { super(Parts.TITLEBAR_PART, { hasTitle: false }, themeService, storageService, layoutService); - this.windowTitle = this._register(instantiationService.createInstance(WindowTitle, window, 'main')); + this.windowTitle = this._register(instantiationService.createInstance(WindowTitle, mainWindow, 'main')); this.titleBarStyle = getTitleBarStyle(this.configurationService); @@ -186,7 +187,7 @@ export class TitlebarPart extends Part implements ITitleService { } if (this.titleBarStyle !== 'native' && this.actionToolBar) { - const affectsEditorActions = event.affectsConfiguration('workbench.editor.showEditorActionsInTitleBar') || event.affectsConfiguration('workbench.editor.showTabs'); + const affectsEditorActions = event.affectsConfiguration('workbench.editor.editorActionsLocation') || event.affectsConfiguration('workbench.editor.showTabs'); const affectsLayoutControl = event.affectsConfiguration('workbench.layoutControl.enabled'); const affectsActivityControl = event.affectsConfiguration(LayoutSettings.ACTIVITY_BAR_LOCATION); if (affectsEditorActions) { @@ -532,7 +533,7 @@ export class TitlebarPart extends Part implements ITitleService { protected onContextMenu(e: MouseEvent, menuId: MenuId): void { // Find target anchor - const event = new StandardMouseEvent(e); + const event = new StandardMouseEvent(getWindow(this.rootContainer), e); // Show it this.contextMenuService.showContextMenu({ @@ -552,7 +553,11 @@ export class TitlebarPart extends Part implements ITitleService { } private get editorActionsEnabled(): boolean { - return this.editorGroupService.partOptions.showEditorActionsInTitleBar !== 'never' && this.editorGroupService.partOptions.showTabs === 'none'; + return this.editorGroupService.partOptions.editorActionsLocation === 'titleBar' || + ( + this.editorGroupService.partOptions.editorActionsLocation === 'default' && + this.editorGroupService.partOptions.showTabs === 'none' + ); } private get activityActionsEnabled(): boolean { @@ -603,12 +608,12 @@ export class TitlebarPart extends Part implements ITitleService { class ToogleConfigAction extends Action2 { - constructor(private readonly section: string, title: string, order: number, when?: ContextKeyExpression) { + constructor(private readonly section: string, title: string, order: number) { super({ id: `toggle.${section}`, title, toggled: ContextKeyExpr.equals(`config.${section}`, true), - menu: { id: MenuId.TitleBarContext, order, when } + menu: { id: MenuId.TitleBarContext, order } }); } @@ -631,12 +636,6 @@ registerAction2(class ToogleLayoutControl extends ToogleConfigAction { } }); -registerAction2(class ToogleEditorActionsControl extends ToogleConfigAction { - constructor() { - super('workbench.editor.showEditorActionsInTitleBar', localize('toggle.editorActions', 'Editor Actions'), 2, ContextKeyExpr.equals('config.workbench.editor.showTabs', 'none')); - } -}); - const ACCOUNTS_ACTIVITY_TILE_ACTION: IAction = { id: ACCOUNTS_ACTIVITY_ID, label: localize('accounts', "Accounts"), diff --git a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts index 8f6e6b7e277..a185956cd94 100644 --- a/src/vs/workbench/browser/parts/views/viewPaneContainer.ts +++ b/src/vs/workbench/browser/parts/views/viewPaneContainer.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { addDisposableListener, Dimension, DragAndDropObserver, EventType, focusWindow, isAncestor } from 'vs/base/browser/dom'; +import { addDisposableListener, Dimension, DragAndDropObserver, EventType, focusWindow, getWindow, isAncestor } from 'vs/base/browser/dom'; import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { EventType as TouchEventType, Gesture } from 'vs/base/browser/touch'; import { IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -146,7 +146,6 @@ class ViewPaneDropOverlay extends Themable { private registerListeners(): void { this._register(new DragAndDropObserver(this.container, { - onDragEnter: e => undefined, onDragOver: e => { // Position overlay @@ -410,9 +409,9 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { this._register(this.paneview.onDidDrop(({ from, to }) => this.movePane(from as ViewPane, to as ViewPane))); this._register(this.paneview.onDidScroll(_ => this.onDidScrollPane())); this._register(this.paneview.onDidSashReset((index) => this.onDidSashReset(index))); - this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, (e: MouseEvent) => this.showContextMenu(new StandardMouseEvent(e)))); + this._register(addDisposableListener(parent, EventType.CONTEXT_MENU, (e: MouseEvent) => this.showContextMenu(new StandardMouseEvent(getWindow(parent), e)))); this._register(Gesture.addTarget(parent)); - this._register(addDisposableListener(parent, TouchEventType.Contextmenu, (e: MouseEvent) => this.showContextMenu(new StandardMouseEvent(e)))); + this._register(addDisposableListener(parent, TouchEventType.Contextmenu, (e: MouseEvent) => this.showContextMenu(new StandardMouseEvent(getWindow(parent), e)))); this._menuActions = this._register(this.instantiationService.createInstance(ViewContainerMenuActions, this.paneview.element, this.viewContainer)); this._register(this._menuActions.onDidChange(() => this.updateTitleArea())); @@ -789,7 +788,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer { const contextMenuDisposable = addDisposableListener(pane.draggableElement, 'contextmenu', e => { e.stopPropagation(); e.preventDefault(); - this.onContextMenu(new StandardMouseEvent(e), pane); + this.onContextMenu(new StandardMouseEvent(getWindow(pane.draggableElement), e), pane); }); const collapseDisposable = Event.latch(Event.map(pane.onDidChange, () => !pane.isExpanded()))(collapsed => { diff --git a/src/vs/workbench/browser/style.ts b/src/vs/workbench/browser/style.ts index d70a5c0e797..23f2e2bac99 100644 --- a/src/vs/workbench/browser/style.ts +++ b/src/vs/workbench/browser/style.ts @@ -10,6 +10,7 @@ import { isWeb, isIOS, isMacintosh, isWindows } from 'vs/base/common/platform'; import { createMetaElement } from 'vs/base/browser/dom'; import { isSafari, isStandalone } from 'vs/base/browser/browser'; import { selectionBackground } from 'vs/platform/theme/common/colorRegistry'; +import { mainWindow } from 'vs/base/browser/window'; registerThemingParticipant((theme, collector) => { @@ -28,7 +29,7 @@ registerThemingParticipant((theme, collector) => { const titleBackground = theme.getColor(TITLE_BAR_ACTIVE_BACKGROUND); if (titleBackground) { const metaElementId = 'monaco-workbench-meta-theme-color'; - let metaElement = document.getElementById(metaElementId) as HTMLMetaElement | null; + let metaElement = mainWindow.document.getElementById(metaElementId) as HTMLMetaElement | null; if (!metaElement) { metaElement = createMetaElement(); metaElement.name = 'theme-color'; diff --git a/src/vs/workbench/browser/web.main.ts b/src/vs/workbench/browser/web.main.ts index c1ec79928ae..c0243a0785c 100644 --- a/src/vs/workbench/browser/web.main.ts +++ b/src/vs/workbench/browser/web.main.ts @@ -95,6 +95,7 @@ import { EncryptionService } from 'vs/workbench/services/encryption/browser/encr import { IEncryptionService } from 'vs/platform/encryption/common/encryptionService'; import { ISecretStorageService } from 'vs/platform/secrets/common/secrets'; import { TunnelSource } from 'vs/workbench/services/remote/common/tunnelModel'; +import { mainWindow } from 'vs/base/browser/window'; export class BrowserMain extends Disposable { @@ -113,7 +114,7 @@ export class BrowserMain extends Disposable { private init(): void { // Browser config - setFullscreen(!!detectFullscreen()); + setFullscreen(!!detectFullscreen(mainWindow)); } async open(): Promise { @@ -467,7 +468,7 @@ export class BrowserMain extends Disposable { fileService.registerProvider(Schemas.vscodeUserData, userDataProvider); // Local file access (if supported by browser) - if (WebFileSystemAccess.supported(window)) { + if (WebFileSystemAccess.supported(mainWindow)) { fileService.registerProvider(Schemas.file, new HTMLFileSystemProvider(indexedDB, handlesStore, logService)); } diff --git a/src/vs/workbench/browser/window.ts b/src/vs/workbench/browser/window.ts index 94b46dd7381..47d532a7251 100644 --- a/src/vs/workbench/browser/window.ts +++ b/src/vs/workbench/browser/window.ts @@ -27,6 +27,7 @@ import { BrowserLifecycleService } from 'vs/workbench/services/lifecycle/browser import { ILifecycleService } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { registerWindowDriver } from 'vs/workbench/services/driver/browser/driver'; +import { mainWindow } from 'vs/base/browser/window'; export class BrowserWindow extends Disposable { @@ -53,13 +54,13 @@ export class BrowserWindow extends Disposable { this._register(this.lifecycleService.onWillShutdown(() => this.onWillShutdown())); // Layout - const viewport = isIOS && window.visualViewport ? window.visualViewport /** Visual viewport */ : window /** Layout viewport */; + const viewport = isIOS && mainWindow.visualViewport ? mainWindow.visualViewport /** Visual viewport */ : mainWindow /** Layout viewport */; this._register(addDisposableListener(viewport, EventType.RESIZE, () => { this.layoutService.layout(); // Sometimes the keyboard appearing scrolls the whole workbench out of view, as a workaround scroll back into view #121206 if (isIOS) { - window.scrollTo(0, 0); + mainWindow.scrollTo(0, 0); } })); @@ -74,12 +75,12 @@ export class BrowserWindow extends Disposable { // Fullscreen (Browser) for (const event of [EventType.FULLSCREEN_CHANGE, EventType.WK_FULLSCREEN_CHANGE]) { - this._register(addDisposableListener(document, event, () => setFullscreen(!!detectFullscreen()))); + this._register(addDisposableListener(mainWindow.document, event, () => setFullscreen(!!detectFullscreen(mainWindow)))); } // Fullscreen (Native) this._register(addDisposableThrottledListener(viewport, EventType.RESIZE, () => { - setFullscreen(!!detectFullscreen()); + setFullscreen(!!detectFullscreen(mainWindow)); }, undefined, isMacintosh ? 2000 /* adjust for macOS animation */ : 800 /* can be throttled */)); } @@ -89,8 +90,8 @@ export class BrowserWindow extends Disposable { // when shutdown has happened to not show the dialog e.g. // when navigation takes a longer time. Event.toPromise(Event.any( - Event.once(new DomEmitter(document.body, EventType.KEY_DOWN, true).event), - Event.once(new DomEmitter(document.body, EventType.MOUSE_DOWN, true).event) + Event.once(new DomEmitter(mainWindow.document.body, EventType.KEY_DOWN, true).event), + Event.once(new DomEmitter(mainWindow.document.body, EventType.MOUSE_DOWN, true).event) )).then(async () => { // Delay the dialog in case the user interacted @@ -108,7 +109,7 @@ export class BrowserWindow extends Disposable { buttons: [ { label: localize({ key: 'reload', comment: ['&& denotes a mnemonic'] }, "&&Reload"), - run: () => window.location.reload() // do not use any services at this point since they are likely not functional at this point + run: () => mainWindow.location.reload() // do not use any services at this point since they are likely not functional at this point } ] }); @@ -191,7 +192,7 @@ export class BrowserWindow extends Disposable { // handling explicitly to prevent the workbench from going down. else { const invokeProtocolHandler = () => { - this.lifecycleService.withExpectedShutdown({ disableShutdownHandling: true }, () => window.location.href = href); + this.lifecycleService.withExpectedShutdown({ disableShutdownHandling: true }, () => mainWindow.location.href = href); }; invokeProtocolHandler(); diff --git a/src/vs/workbench/browser/workbench.contribution.ts b/src/vs/workbench/browser/workbench.contribution.ts index 1c16044a043..69109a59d2d 100644 --- a/src/vs/workbench/browser/workbench.contribution.ts +++ b/src/vs/workbench/browser/workbench.contribution.ts @@ -12,6 +12,7 @@ import { isStandalone } from 'vs/base/browser/browser'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/contributions'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { ActivityBarPosition, EditorTabsMode, LayoutSettings } from 'vs/workbench/services/layout/browser/layoutService'; +import product from 'vs/platform/product/common/product'; const registry = Registry.as(ConfigurationExtensions.Configuration); @@ -49,15 +50,16 @@ const registry = Registry.as(ConfigurationExtensions.Con 'description': localize('showEditorTabs', "Controls whether opened editors should show as individual tabs, one single large tab or if the title area should not be shown."), 'default': 'multiple' }, - 'workbench.editor.showEditorActionsInTitleBar': { + 'workbench.editor.editorActionsLocation': { 'type': 'string', - 'enum': ['noTabs', 'never'], + 'enum': ['default', 'titleBar', 'hidden'], 'enumDescriptions': [ - localize('workbench.editor.showEditorActionsInTitleBar.noTabs', "Show editor actions in the editor title bar only when `#workbench.editor.showTabs#` is set to `none`. Otherwise, editor actions show up in the tab bar."), - localize('workbench.editor.showEditorActionsInTitleBar.never', "Do not show editor actions in the editor title bar"), + localize('workbench.editor.editorActionsLocation.default', "Show editor actions in the window title bar when `#workbench.editor.showTabs#` is set to `none`. Otherwise, editor actions are shown in the editor tab bar."), + localize('workbench.editor.editorActionsLocation.titleBar', "Show editor actions in the window title bar. If `#window.titleBarStyle#` is set to `native`, editor actions are hidden."), + localize('workbench.editor.editorActionsLocation.hidden', "Editor actions are not shown."), ], - 'markdownDescription': localize('showEditorActionsInTitleBar', "Controls whether editor actions are shown in the title bar. This value only applies when `#window.titleBarStyle#` is set to `custom`."), - 'default': 'noTabs' + 'markdownDescription': localize('editorActionsLocation', "Controls where the editor actions are shown."), + 'default': 'default' }, 'workbench.editor.wrapTabs': { 'type': 'boolean', @@ -242,6 +244,11 @@ const registry = Registry.as(ConfigurationExtensions.Con 'default': true, 'description': localize('splitOnDragAndDrop', "Controls if editor groups can be split from drag and drop operations by dropping an editor or file on the edges of the editor area.") }, + 'workbench.editor.dragToOpenWindow': { + 'type': 'boolean', + 'default': product.quality !== 'stable', + 'markdownDescription': localize('dragToOpenWindow', "Controls if editors can be dragged out of the window to open them in a new floating window. Press and hold `Alt`-key while dragging to toggle this dynamically.") + }, 'workbench.editor.focusRecentEditorAfterClose': { 'type': 'boolean', 'description': localize('focusRecentEditorAfterClose', "Controls whether editors are closed in most recently used order or from left to right."), diff --git a/src/vs/workbench/browser/workbench.ts b/src/vs/workbench/browser/workbench.ts index d1a308a9f49..931ad9fd882 100644 --- a/src/vs/workbench/browser/workbench.ts +++ b/src/vs/workbench/browser/workbench.ts @@ -5,9 +5,9 @@ import 'vs/workbench/browser/style'; import { localize } from 'vs/nls'; -import { addDisposableListener } from 'vs/base/browser/dom'; +import { addDisposableListener, runWhenWindowIdle } from 'vs/base/browser/dom'; import { Event, Emitter, setGlobalLeakWarningThreshold } from 'vs/base/common/event'; -import { RunOnceScheduler, runWhenIdle, timeout } from 'vs/base/common/async'; +import { RunOnceScheduler, timeout } from 'vs/base/common/async'; import { isFirefox, isSafari, isChrome, PixelRatio } from 'vs/base/browser/browser'; import { mark } from 'vs/base/common/performance'; import { onUnexpectedError, setUnexpectedErrorHandler } from 'vs/base/common/errors'; @@ -41,6 +41,7 @@ import { InstantiationService } from 'vs/platform/instantiation/common/instantia import { Layout } from 'vs/workbench/browser/layout'; import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IDialogService } from 'vs/platform/dialogs/common/dialogs'; +import { mainWindow } from 'vs/base/browser/window'; export interface IWorkbenchOptions { @@ -75,7 +76,7 @@ export class Workbench extends Layout { private registerErrorHandler(logService: ILogService): void { // Listen on unhandled rejection events - this._register(addDisposableListener(window, 'unhandledrejection', event => { + this._register(addDisposableListener(mainWindow, 'unhandledrejection', event => { // See https://developer.mozilla.org/en-US/docs/Web/API/PromiseRejectionEvent onUnexpectedError(event.reason); @@ -102,8 +103,8 @@ export class Workbench extends Layout { } type AnnotatedError = AnnotatedLoadingError | AnnotatedFactoryError | AnnotatedValidationError; - if (typeof window.require?.config === 'function') { - window.require.config({ + if (typeof mainWindow.require?.config === 'function') { + mainWindow.require.config({ onError: (err: AnnotatedError) => { if (err.phase === 'loading') { onUnexpectedError(new Error(localize('loaderErrorNative', "Failed to load a required file. Please restart the application to try again. Details: {0}", JSON.stringify(err)))); @@ -329,10 +330,10 @@ export class Workbench extends Layout { ]); this.container.classList.add(...workbenchClasses); - document.body.classList.add(platformClass); // used by our fonts + mainWindow.document.body.classList.add(platformClass); // used by our fonts if (isWeb) { - document.body.classList.add('web'); + mainWindow.document.body.classList.add('web'); } // Apply font aliasing @@ -450,7 +451,7 @@ export class Workbench extends Layout { // Set lifecycle phase to `Eventually` after a short delay and when idle (min 2.5sec, max 5sec) const eventuallyPhaseScheduler = this._register(new RunOnceScheduler(() => { - this._register(runWhenIdle(() => lifecycleService.phase = LifecyclePhase.Eventually, 2500)); + this._register(runWhenWindowIdle(mainWindow, () => lifecycleService.phase = LifecyclePhase.Eventually, 2500)); }, 2500)); eventuallyPhaseScheduler.schedule(); }) diff --git a/src/vs/workbench/common/contributions.ts b/src/vs/workbench/common/contributions.ts index 2b8ebeeac55..a4400f5ae44 100644 --- a/src/vs/workbench/common/contributions.ts +++ b/src/vs/workbench/common/contributions.ts @@ -6,7 +6,7 @@ import { IInstantiationService, IConstructorSignature, ServicesAccessor, BrandedService } from 'vs/platform/instantiation/common/instantiation'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { Registry } from 'vs/platform/registry/common/platform'; -import { runWhenIdle, IdleDeadline, DeferredPromise } from 'vs/base/common/async'; +import { IdleDeadline, DeferredPromise, runWhenGlobalIdle } from 'vs/base/common/async'; import { mark } from 'vs/base/common/performance'; import { ILogService } from 'vs/platform/log/common/log'; import { IEnvironmentService } from 'vs/platform/environment/common/environment'; @@ -155,7 +155,7 @@ class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry this.safeCreateContribution(instantiationService, logService, environmentService, contribution, phase); if (idle.timeRemaining() < 1) { // time is up -> reschedule - runWhenIdle(instantiateSome, forcedTimeout); + runWhenGlobalIdle(instantiateSome, forcedTimeout); break; } } @@ -169,7 +169,7 @@ class WorkbenchContributionsRegistry implements IWorkbenchContributionsRegistry } }; - runWhenIdle(instantiateSome, forcedTimeout); + runWhenGlobalIdle(instantiateSome, forcedTimeout); } private safeCreateContribution(instantiationService: IInstantiationService, logService: ILogService, environmentService: IEnvironmentService, contribution: IConstructorSignature, phase: LifecyclePhase): void { diff --git a/src/vs/workbench/common/editor.ts b/src/vs/workbench/common/editor.ts index e9957f64ede..2c0acaa442a 100644 --- a/src/vs/workbench/common/editor.ts +++ b/src/vs/workbench/common/editor.ts @@ -489,6 +489,17 @@ export interface IResourceDiffEditorInput extends IBaseUntypedEditorInput { readonly modified: IResourceEditorInput | ITextResourceEditorInput | IUntitledTextResourceEditorInput; } +/** + * A resource list diff editor input compares multiple resources side by side + * highlighting the differences. + */ +export interface IResourceMultiDiffEditorInput extends IBaseUntypedEditorInput { + /** + * The list of resources to compare. + */ + readonly resources: (IResourceDiffEditorInput & { readonly resource: URI })[]; +} + export type IResourceMergeEditorInputSide = (IResourceEditorInput | ITextResourceEditorInput) & { detail?: string }; /** @@ -541,6 +552,16 @@ export function isResourceDiffEditorInput(editor: unknown): editor is IResourceD return candidate?.original !== undefined && candidate.modified !== undefined; } +export function isResourceDiffListEditorInput(editor: unknown): editor is IResourceMultiDiffEditorInput { + if (isEditorInput(editor)) { + return false; // make sure to not accidentally match on typed editor inputs + } + + const candidate = editor as IResourceMultiDiffEditorInput | undefined; + + return Array.isArray(candidate?.resources); +} + export function isResourceSideBySideEditorInput(editor: unknown): editor is IResourceSideBySideEditorInput { if (isEditorInput(editor)) { return false; // make sure to not accidentally match on typed editor inputs @@ -760,7 +781,7 @@ export const enum EditorInputCapabilities { AuxWindowUnsupported = 1 << 10 } -export type IUntypedEditorInput = IResourceEditorInput | ITextResourceEditorInput | IUntitledTextResourceEditorInput | IResourceDiffEditorInput | IResourceSideBySideEditorInput | IResourceMergeEditorInput; +export type IUntypedEditorInput = IResourceEditorInput | ITextResourceEditorInput | IUntitledTextResourceEditorInput | IResourceDiffEditorInput | IResourceMultiDiffEditorInput | IResourceSideBySideEditorInput | IResourceMergeEditorInput; export abstract class AbstractEditorInput extends Disposable { // Marker class for implementing `isEditorInput` @@ -1145,9 +1166,10 @@ interface IEditorPartConfiguration { splitInGroupLayout?: 'vertical' | 'horizontal'; splitSizing?: 'auto' | 'split' | 'distribute'; splitOnDragAndDrop?: boolean; + dragToOpenWindow?: boolean; centeredLayoutFixedWidth?: boolean; doubleClickTabToToggleEditorGroupSizes?: 'maximize' | 'expand' | 'off'; - showEditorActionsInTitleBar?: 'noTabs' | 'never'; + editorActionsLocation?: 'default' | 'titleBar' | 'hidden'; limit?: IEditorPartLimitConfiguration; decorations?: IEditorPartDecorationsConfiguration; } @@ -1260,7 +1282,7 @@ class EditorResourceAccessorImpl { } } - if (isResourceDiffEditorInput(editor) || isResourceSideBySideEditorInput(editor) || isResourceMergeEditorInput(editor)) { + if (isResourceDiffEditorInput(editor) || isResourceDiffListEditorInput(editor) || isResourceSideBySideEditorInput(editor) || isResourceMergeEditorInput(editor)) { return undefined; } @@ -1329,7 +1351,7 @@ class EditorResourceAccessorImpl { } } - if (isResourceDiffEditorInput(editor) || isResourceSideBySideEditorInput(editor) || isResourceMergeEditorInput(editor)) { + if (isResourceDiffEditorInput(editor) || isResourceDiffListEditorInput(editor) || isResourceSideBySideEditorInput(editor) || isResourceMergeEditorInput(editor)) { return undefined; } diff --git a/src/vs/workbench/common/editor/sideBySideEditorInput.ts b/src/vs/workbench/common/editor/sideBySideEditorInput.ts index beee39963a2..e852dd7b2bb 100644 --- a/src/vs/workbench/common/editor/sideBySideEditorInput.ts +++ b/src/vs/workbench/common/editor/sideBySideEditorInput.ts @@ -9,7 +9,7 @@ import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { Registry } from 'vs/platform/registry/common/platform'; -import { EditorInputCapabilities, GroupIdentifier, ISaveOptions, IRevertOptions, EditorExtensions, IEditorFactoryRegistry, IEditorSerializer, ISideBySideEditorInput, IUntypedEditorInput, isResourceSideBySideEditorInput, isDiffEditorInput, isResourceDiffEditorInput, IResourceSideBySideEditorInput, findViewStateForEditor, IMoveResult, isEditorInput, isResourceEditorInput, Verbosity, isResourceMergeEditorInput } from 'vs/workbench/common/editor'; +import { EditorInputCapabilities, GroupIdentifier, ISaveOptions, IRevertOptions, EditorExtensions, IEditorFactoryRegistry, IEditorSerializer, ISideBySideEditorInput, IUntypedEditorInput, isResourceSideBySideEditorInput, isDiffEditorInput, isResourceDiffEditorInput, IResourceSideBySideEditorInput, findViewStateForEditor, IMoveResult, isEditorInput, isResourceEditorInput, Verbosity, isResourceMergeEditorInput, isResourceDiffListEditorInput } from 'vs/workbench/common/editor'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; @@ -190,7 +190,7 @@ export class SideBySideEditorInput extends EditorInput implements ISideBySideEdi return new SideBySideEditorInput(this.preferredName, this.preferredDescription, primarySaveResult, primarySaveResult, this.editorService); } - if (!isResourceDiffEditorInput(primarySaveResult) && !isResourceSideBySideEditorInput(primarySaveResult) && !isResourceMergeEditorInput(primarySaveResult)) { + if (!isResourceDiffEditorInput(primarySaveResult) && !isResourceDiffListEditorInput(primarySaveResult) && !isResourceSideBySideEditorInput(primarySaveResult) && !isResourceMergeEditorInput(primarySaveResult)) { return { primary: primarySaveResult, secondary: primarySaveResult, @@ -259,6 +259,7 @@ export class SideBySideEditorInput extends EditorInput implements ISideBySideEdi if ( primaryResourceEditorInput && secondaryResourceEditorInput && !isResourceDiffEditorInput(primaryResourceEditorInput) && !isResourceDiffEditorInput(secondaryResourceEditorInput) && + !isResourceDiffListEditorInput(primaryResourceEditorInput) && !isResourceDiffListEditorInput(secondaryResourceEditorInput) && !isResourceSideBySideEditorInput(primaryResourceEditorInput) && !isResourceSideBySideEditorInput(secondaryResourceEditorInput) && !isResourceMergeEditorInput(primaryResourceEditorInput) && !isResourceMergeEditorInput(secondaryResourceEditorInput) ) { diff --git a/src/vs/workbench/common/editor/textResourceEditorInput.ts b/src/vs/workbench/common/editor/textResourceEditorInput.ts index 07b06ab96cb..02217560ffd 100644 --- a/src/vs/workbench/common/editor/textResourceEditorInput.ts +++ b/src/vs/workbench/common/editor/textResourceEditorInput.ts @@ -99,7 +99,7 @@ export class TextResourceEditorInput extends AbstractTextResourceEditorInput imp private description: string | undefined, private preferredLanguageId: string | undefined, private preferredContents: string | undefined, - @ITextModelService private readonly textModelResolverService: ITextModelService, + @ITextModelService private readonly textModelService: ITextModelService, @ITextFileService textFileService: ITextFileService, @IEditorService editorService: IEditorService, @IFileService fileService: IFileService, @@ -160,7 +160,7 @@ export class TextResourceEditorInput extends AbstractTextResourceEditorInput imp this.preferredLanguageId = undefined; if (!this.modelReference) { - this.modelReference = this.textModelResolverService.createModelReference(this.resource); + this.modelReference = this.textModelService.createModelReference(this.resource); } const ref = await this.modelReference; diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts index 4cf07eb4bda..0f337d22218 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibilityConfiguration.ts @@ -33,6 +33,11 @@ export const enum ViewDimUnfocusedOpacityProperties { Maximum = 1 } +export const enum AccessibilityVoiceSettingId { + SpeechTimeout = 'accessibility.voice.speechTimeout', +} +export const SpeechTimeoutDefault = 1200; + export const enum AccessibilityVerbositySettingId { Terminal = 'accessibility.verbosity.terminal', DiffEditor = 'accessibility.verbosity.diffEditor', @@ -148,6 +153,13 @@ const configuration: IConfigurationNode = { ], tags: ['accessibility'] }, + [AccessibilityVoiceSettingId.SpeechTimeout]: { + 'markdownDescription': localize('voice.speechTimeout', "Define the duration for which the voice speech recognition remains active after you stop speaking. For example in a chat session the transcribed text is submitted automatically after the timeout is met. Set to `0` to disable this feature."), + 'type': 'number', + 'default': SpeechTimeoutDefault, + 'minimum': 0, + 'tags': ['accessibility'] + } } }; diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts index b348ffd8111..f2300e1afaf 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleView.ts @@ -501,10 +501,10 @@ export class AccessibleView extends Disposable { this._updateToolbar(provider.actions, provider.options.type); const hide = (e: KeyboardEvent | IKeyboardEvent): void => { + provider.onClose(); e.stopPropagation(); this._contextViewService.hideContextView(); this._updateContextKeys(provider, false); - provider.onClose(); }; const disposableStore = new DisposableStore(); disposableStore.add(this._editorWidget.onKeyDown((e) => { diff --git a/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts b/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts index d931e39bf79..fa6f0f8d633 100644 --- a/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts +++ b/src/vs/workbench/contrib/accessibility/browser/accessibleViewActions.ts @@ -120,7 +120,8 @@ export const AccessibilityHelpAction = registerCommand(new MultiCommand({ linux: { primary: KeyMod.Alt | KeyMod.Shift | KeyCode.F1, secondary: [KeyMod.Alt | KeyCode.F1] - } + }, + kbExpr: accessibilityHelpIsShown.toNegated() }, menuOpts: [{ menuId: MenuId.CommandPalette, diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts index 8d70003a263..f83b8389b31 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatActions.ts @@ -32,6 +32,8 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle import { AccessibilityHelpAction } from 'vs/workbench/contrib/accessibility/browser/accessibleViewActions'; import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { chatAgentLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; +import { IViewsService } from 'vs/workbench/common/views'; export const CHAT_CATEGORY = { value: localize('chat.category', "Chat"), original: 'Chat' }; export const CHAT_OPEN_ACTION_ID = 'workbench.action.chat.open'; @@ -256,7 +258,8 @@ export function getHistoryAction(viewId: string, providerId: string) { async runInView(accessor: ServicesAccessor, view: ChatViewPane) { const chatService = accessor.get(IChatService); const quickInputService = accessor.get(IQuickInputService); - const editorService = accessor.get(IEditorService); + const chatContribService = accessor.get(IChatContributionService); + const viewsService = accessor.get(IViewsService); const items = chatService.getHistory(); const picks = items.map(i => ({ label: i.title, @@ -268,7 +271,7 @@ export function getHistoryAction(viewId: string, providerId: string) { })); const selection = await quickInputService.pick(picks, { - placeHolder: localize('interactiveSession.history.pick', "Select a chat session to restore"), + placeHolder: localize('interactiveSession.history.pick', "Switch to chat session"), onDidTriggerItemButton: context => { chatService.removeHistoryEntry(context.item.chat.sessionId); context.removeItem(); @@ -276,9 +279,12 @@ export function getHistoryAction(viewId: string, providerId: string) { }); if (selection) { const sessionId = selection.chat.sessionId; - await editorService.openEditor({ - resource: ChatEditorInput.getNewEditorUri(), options: { target: { sessionId }, pinned: true } - }); + const provider = chatContribService.registeredProviders[0]?.id; + if (provider) { + const viewId = chatContribService.getViewIdForProvider(provider); + const view = await viewsService.openView(viewId) as ChatViewPane; + view.loadSession(sessionId); + } } } }; diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts index c7650d78ca8..f60e54ab615 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatClearActions.ts @@ -6,7 +6,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; -import { localize } from 'vs/nls'; +import { localize2 } from 'vs/nls'; import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; import { Action2, IAction2Options, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -28,11 +28,8 @@ export function registerClearActions() { constructor() { super({ id: 'workbench.action.chatEditor.clear', - title: { - value: localize('interactiveSession.clear.label', "Clear"), - original: 'Clear' - }, - icon: Codicon.clearAll, + title: localize2('chat.newSession.label', "New Session"), + icon: Codicon.plus, f1: false, precondition: CONTEXT_PROVIDER_EXISTS, menu: [{ @@ -54,10 +51,7 @@ export function registerClearActions() { constructor() { super({ id: ACTION_ID_CLEAR_CHAT, - title: { - value: localize('interactiveSession.clear.label', "Clear"), - original: 'Clear' - }, + title: localize2('chat.newSession.label', "New Session"), category: CHAT_CATEGORY, icon: Codicon.clearAll, precondition: CONTEXT_PROVIDER_EXISTS, @@ -90,10 +84,7 @@ export function registerClearActions() { const getClearChatActionDescriptorForViewTitle = (viewId: string, providerId: string): Readonly & { viewId: string } => ({ viewId, id: `workbench.action.chat.${providerId}.clear`, - title: { - value: localize('interactiveSession.clear.label', "Clear"), - original: 'Clear' - }, + title: localize2('chat.newSession.label', "New Session"), menu: { id: MenuId.ViewTitle, when: ContextKeyExpr.equals('view', viewId), @@ -102,7 +93,7 @@ const getClearChatActionDescriptorForViewTitle = (viewId: string, providerId: st }, precondition: CONTEXT_PROVIDER_EXISTS, category: CHAT_CATEGORY, - icon: Codicon.clearAll, + icon: Codicon.plus, f1: false }); diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts index 4a5e08e11f5..c21627a7a10 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatCodeblockActions.ts @@ -18,7 +18,6 @@ import { ITextModel } from 'vs/editor/common/model'; import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures'; import { CopyAction } from 'vs/editor/contrib/clipboard/browser/clipboard'; import { localize } from 'vs/nls'; -import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; @@ -29,7 +28,7 @@ import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatAct import { IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { ICodeBlockActionContext } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; import { CONTEXT_IN_CHAT_SESSION, CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; -import { IChatCopyAction, IChatService, IDocumentContext, InteractiveSessionCopyKind } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatService, IDocumentContext, InteractiveSessionCopyKind } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatResponseViewModel, isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { CTX_INLINE_CHAT_VISIBLE } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { insertCell } from 'vs/workbench/contrib/notebook/browser/controller/cellOperations'; @@ -112,9 +111,8 @@ export function registerChatCodeBlockActions() { agentId: context.element.agent?.id, sessionId: context.element.sessionId, requestId: context.element.requestId, - action: { + action: { kind: 'copy', - responseId: context.element.providerResponseId, codeBlockIndex: context.codeBlockIndex, copyType: InteractiveSessionCopyKind.Toolbar, copiedCharacters: context.code.length, @@ -150,24 +148,21 @@ export function registerChatCodeBlockActions() { const totalCharacters = editorModel.getValueLength(); // Report copy to extensions - if (context.element.providerResponseId) { - const chatService = accessor.get(IChatService); - chatService.notifyUserAction({ - providerId: context.element.providerId, - agentId: context.element.agent?.id, - sessionId: context.element.sessionId, - requestId: context.element.requestId, - action: { - kind: 'copy', - codeBlockIndex: context.codeBlockIndex, - responseId: context.element.providerResponseId, - copyType: InteractiveSessionCopyKind.Action, - copiedText, - copiedCharacters: copiedText.length, - totalCharacters, - } - }); - } + const chatService = accessor.get(IChatService); + chatService.notifyUserAction({ + providerId: context.element.providerId, + agentId: context.element.agent?.id, + sessionId: context.element.sessionId, + requestId: context.element.requestId, + action: { + kind: 'copy', + codeBlockIndex: context.codeBlockIndex, + copyType: InteractiveSessionCopyKind.Action, + copiedText, + copiedCharacters: copiedText.length, + totalCharacters, + } + }); // Copy full cell if no selection, otherwise fall back on normal editor implementation if (noSelection) { @@ -196,7 +191,7 @@ export function registerChatCodeBlockActions() { when: CONTEXT_IN_CHAT_SESSION, }, keybinding: { - when: ContextKeyExpr.and(CONTEXT_IN_CHAT_SESSION, CONTEXT_ACCESSIBILITY_MODE_ENABLED), + when: CONTEXT_IN_CHAT_SESSION, primary: KeyMod.CtrlCmd | KeyCode.Enter, mac: { primary: KeyMod.WinCtrl | KeyCode.Enter }, weight: KeybindingWeight.WorkbenchContrib @@ -338,7 +333,6 @@ export function registerChatCodeBlockActions() { requestId: context.element.requestId, action: { kind: 'insert', - responseId: context.element.providerResponseId!, codeBlockIndex: context.codeBlockIndex, totalCharacters: context.code.length, } @@ -353,8 +347,8 @@ export function registerChatCodeBlockActions() { super({ id: 'workbench.action.chat.insertIntoNewFile', title: { - value: localize('interactive.insertIntoNewFile.label', "Insert Into New File"), - original: 'Insert Into New File' + value: localize('interactive.insertIntoNewFile.label', "Insert into New File"), + original: 'Insert into New File' }, precondition: CONTEXT_PROVIDER_EXISTS, f1: true, @@ -387,7 +381,6 @@ export function registerChatCodeBlockActions() { requestId: context.element.requestId, action: { kind: 'insert', - responseId: context.element.providerResponseId!, codeBlockIndex: context.codeBlockIndex, totalCharacters: context.code.length, newFile: true @@ -438,16 +431,10 @@ export function registerChatCodeBlockActions() { keybinding: [{ primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.Enter, mac: { - primary: KeyMod.WinCtrl | KeyCode.Enter, + primary: KeyMod.WinCtrl | KeyMod.Alt | KeyCode.Enter }, weight: KeybindingWeight.EditorContrib, - when: ContextKeyExpr.and(CONTEXT_IN_CHAT_SESSION, CONTEXT_ACCESSIBILITY_MODE_ENABLED.negate()), - }, - { - primary: KeyMod.CtrlCmd | KeyCode.Slash, - mac: { primary: KeyMod.WinCtrl | KeyCode.Slash }, - weight: KeybindingWeight.WorkbenchContrib, - when: ContextKeyExpr.and(CONTEXT_IN_CHAT_SESSION, CONTEXT_ACCESSIBILITY_MODE_ENABLED), + when: CONTEXT_IN_CHAT_SESSION, }] }); } @@ -489,7 +476,6 @@ export function registerChatCodeBlockActions() { requestId: context.element.requestId, action: { kind: 'runInTerminal', - responseId: context.element.providerResponseId!, codeBlockIndex: context.codeBlockIndex, languageId: context.languageId, } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts index 07776d570a6..817844feb3b 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatExecuteActions.ts @@ -8,19 +8,15 @@ import { ServicesAccessor } from 'vs/editor/browser/editorExtensions'; import { localize } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; -import { IChatWidget } from 'vs/workbench/contrib/chat/browser/chat'; +import { IChatWidget, IChatWidgetService } from 'vs/workbench/contrib/chat/browser/chat'; import { CONTEXT_CHAT_INPUT_HAS_TEXT, CONTEXT_CHAT_REQUEST_IN_PROGRESS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; export interface IChatExecuteActionContext { - widget: IChatWidget; + widget?: IChatWidget; inputValue?: string; } -export function isExecuteActionContext(thing: unknown): thing is IChatExecuteActionContext { - return typeof thing === 'object' && thing !== null && 'widget' in thing; -} - export class SubmitAction extends Action2 { static readonly ID = 'workbench.action.chat.submit'; @@ -44,12 +40,11 @@ export class SubmitAction extends Action2 { } run(accessor: ServicesAccessor, ...args: any[]) { - const context = args[0]; - if (!isExecuteActionContext(context)) { - return; - } + const context: IChatExecuteActionContext = args[0]; - context.widget.acceptInput(context.inputValue); + const widgetService = accessor.get(IChatWidgetService); + const widget = context.widget ?? widgetService.lastFocusedWidget; + widget?.acceptInput(context.inputValue); } } @@ -76,8 +71,8 @@ export function registerChatExecuteActions() { } run(accessor: ServicesAccessor, ...args: any[]) { - const context = args[0]; - if (!isExecuteActionContext(context)) { + const context: IChatExecuteActionContext = args[0]; + if (!context.widget) { return; } diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts index fda8281ddc5..b1eff8d2a94 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatMoveActions.ts @@ -6,7 +6,7 @@ import { localize } from 'vs/nls'; import { Action2, IAction2Options, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; -import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; +import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; import { ActiveEditorContext } from 'vs/workbench/common/contextkeys'; import { IViewsService } from 'vs/workbench/common/views'; @@ -21,11 +21,16 @@ import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService'; import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; -const getMoveToEditorChatActionDescriptorForViewTitle = (viewId: string, providerId: string): Readonly & { viewId: string } => ({ - id: `workbench.action.chat.${providerId}.openInEditor`, +enum MoveToNewLocation { + Editor = 'Editor', + Window = 'Window' +} + +const getMoveToChatActionDescriptorForViewTitle = (viewId: string, providerId: string, moveTo: MoveToNewLocation): Readonly & { viewId: string } => ({ + id: `workbench.action.chat.${providerId}.openIn${moveTo}`, title: { - value: localize('chat.openInEditor.label', "Open Session In Editor"), - original: 'Open Session In Editor' + value: moveTo === MoveToNewLocation.Editor ? localize('chat.openInEditor.label', "Open Session In Editor") : localize('chat.openInNewWindow.label', "Open Session In New Window"), + original: moveTo === MoveToNewLocation.Editor ? 'Open Session In Editor' : 'Open Session In New Window', }, category: CHAT_CATEGORY, precondition: CONTEXT_PROVIDER_EXISTS, @@ -39,9 +44,17 @@ const getMoveToEditorChatActionDescriptorForViewTitle = (viewId: string, provide }); export function getMoveToEditorAction(viewId: string, providerId: string) { - return class MoveToEditorAction extends ViewAction { + return getMoveToAction(viewId, providerId, MoveToNewLocation.Editor); +} + +export function getMoveToNewWindowAction(viewId: string, providerId: string) { + return getMoveToAction(viewId, providerId, MoveToNewLocation.Window); +} + +export function getMoveToAction(viewId: string, providerId: string, moveTo: MoveToNewLocation) { + return class MoveToAction extends ViewAction { constructor() { - super(getMoveToEditorChatActionDescriptorForViewTitle(viewId, providerId)); + super(getMoveToChatActionDescriptorForViewTitle(viewId, providerId, moveTo)); } async runInView(accessor: ServicesAccessor, view: ChatViewPane) { @@ -50,33 +63,31 @@ export function getMoveToEditorAction(viewId: string, providerId: string) { return; } + const editorGroupService = accessor.get(IEditorGroupsService); + const instantiationService = accessor.get(IInstantiationService); const editorService = accessor.get(IEditorService); + const sessionId = viewModel.sessionId; view.clear(); - await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { target: { sessionId: viewModel.sessionId }, pinned: true } }); + + switch (moveTo) { + case (MoveToNewLocation.Editor): { + await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { target: { sessionId: viewModel.sessionId }, pinned: true } }); + break; + } + case (MoveToNewLocation.Window): { + const auxiliaryEditorPart = await editorGroupService.createAuxiliaryEditorPart(); + const chatEditorInput = instantiationService.createInstance(ChatEditorInput, ChatEditorInput.getNewEditorUri(), { target: { sessionId } }); + await auxiliaryEditorPart.activeGroup.openEditor(chatEditorInput, { pinned: true }); + break; + } + default: { + throw new Error(`Unexpected move to location : ${moveTo}`); + } + } } }; } -async function moveToSidebar(accessor: ServicesAccessor): Promise { - const viewsService = accessor.get(IViewsService); - const editorService = accessor.get(IEditorService); - const chatContribService = accessor.get(IChatContributionService); - const editorGroupService = accessor.get(IEditorGroupsService); - - const chatEditorInput = editorService.activeEditor; - if (chatEditorInput instanceof ChatEditorInput && chatEditorInput.sessionId && chatEditorInput.providerId) { - await editorService.closeEditor({ editor: chatEditorInput, groupId: editorGroupService.activeGroup.id }); - const viewId = chatContribService.getViewIdForProvider(chatEditorInput.providerId); - const view = await viewsService.openView(viewId) as ChatViewPane; - view.loadSession(chatEditorInput.sessionId); - } else { - const chatService = accessor.get(IChatService); - const providerId = chatService.getProviderInfos()[0].id; - const viewId = chatContribService.getViewIdForProvider(providerId); - await viewsService.openView(viewId); - } -} - export function registerMoveActions() { registerAction2(class GlobalMoveToEditorAction extends Action2 { constructor() { @@ -93,27 +104,27 @@ export function registerMoveActions() { } async run(accessor: ServicesAccessor, ...args: any[]) { - const widgetService = accessor.get(IChatWidgetService); - const viewService = accessor.get(IViewsService); - const editorService = accessor.get(IEditorService); - const chatService = accessor.get(IChatService); + executeMoveToAction(accessor, MoveToNewLocation.Editor); + } + }); - const widget = widgetService.lastFocusedWidget; - if (!widget || !('viewId' in widget.viewContext)) { - const providerId = chatService.getProviderInfos()[0].id; - await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { target: { providerId }, pinned: true } }); - return; - } + registerAction2(class GlobalMoveToNewWindowAction extends Action2 { - const viewModel = widget.viewModel; - if (!viewModel) { - return; - } + constructor() { + super({ + id: `workbench.action.chat.openInNewWindow`, + title: { + value: localize('interactiveSession.openInNewWindow.label', "Open Session In New Window"), + original: 'Open Session In New Window' + }, + category: CHAT_CATEGORY, + precondition: CONTEXT_PROVIDER_EXISTS, + f1: true + }); + } - const sessionId = viewModel.sessionId; - const view = await viewService.openView(widget.viewContext.viewId) as ChatViewPane; - view.clear(); - await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { target: { sessionId: sessionId }, pinned: true } }); + async run(accessor: ServicesAccessor, ...args: any[]) { + executeMoveToAction(accessor, MoveToNewLocation.Window); } }); @@ -141,3 +152,78 @@ export function registerMoveActions() { } }); } + +async function executeMoveToAction(accessor: ServicesAccessor, moveTo: MoveToNewLocation) { + const widgetService = accessor.get(IChatWidgetService); + const viewService = accessor.get(IViewsService); + const chatService = accessor.get(IChatService); + const editorService = accessor.get(IEditorService); + const instantiationService = accessor.get(IInstantiationService); + const editorGroupService = accessor.get(IEditorGroupsService); + + const widget = widgetService.lastFocusedWidget; + if (!widget || !('viewId' in widget.viewContext)) { + const providerId = chatService.getProviderInfos()[0].id; + + switch (moveTo) { + case (MoveToNewLocation.Editor): { + await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { target: { providerId }, pinned: true } }); + break; + } + case (MoveToNewLocation.Window): { + const auxiliaryEditorPart = await editorGroupService.createAuxiliaryEditorPart(); + const chatEditorInput = instantiationService.createInstance(ChatEditorInput, ChatEditorInput.getNewEditorUri(), { target: { providerId } }); + auxiliaryEditorPart.activeGroup.openEditor(chatEditorInput, { pinned: true }); + break; + } + default: { + throw new Error(`Unexpected move to location : ${moveTo}`); + } + } + return; + } + + const viewModel = widget.viewModel; + if (!viewModel) { + return; + } + + const sessionId = viewModel.sessionId; + const view = await viewService.openView(widget.viewContext.viewId) as ChatViewPane; + view.clear(); + + switch (moveTo) { + case (MoveToNewLocation.Editor): { + await editorService.openEditor({ resource: ChatEditorInput.getNewEditorUri(), options: { target: { sessionId: sessionId }, pinned: true } }); + break; + } + case (MoveToNewLocation.Window): { + const auxiliaryEditorPart = await editorGroupService.createAuxiliaryEditorPart(); + const chatEditorInput = instantiationService.createInstance(ChatEditorInput, ChatEditorInput.getNewEditorUri(), { target: { sessionId } }); + await auxiliaryEditorPart.activeGroup.openEditor(chatEditorInput, { pinned: true }); + } + default: { + throw new Error(`Unexpected move to location : ${moveTo}`); + } + } +} + +async function moveToSidebar(accessor: ServicesAccessor): Promise { + const viewsService = accessor.get(IViewsService); + const editorService = accessor.get(IEditorService); + const chatContribService = accessor.get(IChatContributionService); + const editorGroupService = accessor.get(IEditorGroupsService); + + const chatEditorInput = editorService.activeEditor; + if (chatEditorInput instanceof ChatEditorInput && chatEditorInput.sessionId && chatEditorInput.providerId) { + await editorService.closeEditor({ editor: chatEditorInput, groupId: editorGroupService.activeGroup.id }); + const viewId = chatContribService.getViewIdForProvider(chatEditorInput.providerId); + const view = await viewsService.openView(viewId) as ChatViewPane; + view.loadSession(chatEditorInput.sessionId); + } else { + const chatService = accessor.get(IChatService); + const providerId = chatService.getProviderInfos()[0].id; + const viewId = chatContribService.getViewIdForProvider(providerId); + await viewsService.openView(viewId); + } +} diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts index 3525bbea2b9..6007992437b 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatQuickInputActions.ts @@ -5,6 +5,7 @@ import { Codicon } from 'vs/base/common/codicons'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { Selection } from 'vs/editor/common/core/selection'; import { localize } from 'vs/nls'; import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions'; @@ -13,6 +14,7 @@ import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegis import { CHAT_CATEGORY } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { IQuickChatService, IQuickChatOpenOptions } from 'vs/workbench/contrib/chat/browser/chat'; import { CONTEXT_PROVIDER_EXISTS } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { InlineChatController } from 'vs/workbench/contrib/inlineChat/browser/inlineChatController'; export const ASK_QUICK_QUESTION_ACTION_ID = 'workbench.action.quickchat.toggle'; export function registerQuickChatActions() { @@ -67,6 +69,40 @@ export function registerQuickChatActions() { quickChatService.close(); } }); + + registerAction2(class LaunchInlineChatFromQuickChatAction extends Action2 { + constructor() { + super({ + id: 'workbench.action.quickchat.launchInlineChat', + title: { + value: localize('chat.launchInlineChat.label', "Launch Inline Chat"), + original: 'Launch Inline Chat' + }, + f1: false, + category: CHAT_CATEGORY + }); + } + + async run(accessor: ServicesAccessor) { + const quickChatService = accessor.get(IQuickChatService); + const codeEditorService = accessor.get(ICodeEditorService); + if (quickChatService.focused) { + quickChatService.close(); + } + const codeEditor = codeEditorService.getActiveCodeEditor(); + if (!codeEditor) { + return; + } + + const controller = InlineChatController.get(codeEditor); + if (!controller) { + return; + } + + await controller.run(); + controller.focus(); + } + }); } class QuickChatGlobalAction extends Action2 { diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts index 4d9bf1fe75b..2d960c61a33 100644 --- a/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts +++ b/src/vs/workbench/contrib/chat/browser/actions/chatTitleActions.ts @@ -60,7 +60,6 @@ export function registerChatTitleActions() { action: { kind: 'vote', direction: InteractiveSessionVoteDirection.Up, - responseId: item.providerResponseId!, } }); item.setVote(InteractiveSessionVoteDirection.Up); @@ -103,7 +102,6 @@ export function registerChatTitleActions() { action: { kind: 'vote', direction: InteractiveSessionVoteDirection.Down, - responseId: item.providerResponseId!, } }); item.setVote(InteractiveSessionVoteDirection.Down); @@ -222,12 +220,12 @@ export function registerChatTitleActions() { item = widget?.getFocus(); } - const providerRequestId = isRequestVM(item) ? item.providerRequestId : - isResponseVM(item) ? item.providerResponseId : undefined; + const requestId = isRequestVM(item) ? item.id : + isResponseVM(item) ? item.requestId : undefined; - if (providerRequestId) { + if (requestId) { const chatService = accessor.get(IChatService); - chatService.removeRequest(item.sessionId, providerRequestId); + chatService.removeRequest(item.sessionId, requestId); } } }); diff --git a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts index cbe23536b18..e990302b9ff 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.contribution.ts @@ -18,7 +18,7 @@ import { EditorExtensions, IEditorFactoryRegistry } from 'vs/workbench/common/ed import { registerChatActions } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { registerChatCodeBlockActions } from 'vs/workbench/contrib/chat/browser/actions/chatCodeblockActions'; import { registerChatCopyActions } from 'vs/workbench/contrib/chat/browser/actions/chatCopyActions'; -import { registerChatExecuteActions } from 'vs/workbench/contrib/chat/browser/actions/chatExecuteActions'; +import { IChatExecuteActionContext, SubmitAction, registerChatExecuteActions } from 'vs/workbench/contrib/chat/browser/actions/chatExecuteActions'; import { registerQuickChatActions } from 'vs/workbench/contrib/chat/browser/actions/chatQuickInputActions'; import { registerChatTitleActions } from 'vs/workbench/contrib/chat/browser/actions/chatTitleActions'; import { registerChatExportActions } from 'vs/workbench/contrib/chat/browser/actions/chatImportExport'; @@ -45,7 +45,7 @@ import { ChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService'; import { AccessibilityVerbositySettingId, AccessibleViewProviderId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; import { ChatWelcomeMessageModel } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IMarkdownString } from 'vs/base/common/htmlContent'; +import { IMarkdownString, MarkdownString } from 'vs/base/common/htmlContent'; import { ChatProviderService, IChatProviderService } from 'vs/workbench/contrib/chat/common/chatProvider'; import { ChatSlashCommandService, IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { alertFocusChange } from 'vs/workbench/contrib/accessibility/browser/accessibilityContributions'; @@ -56,6 +56,8 @@ import { registerChatFileTreeActions } from 'vs/workbench/contrib/chat/browser/a import { QuickChatService } from 'vs/workbench/contrib/chat/browser/chatQuick'; import { ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatVariablesService } from 'vs/workbench/contrib/chat/browser/chatVariables'; +import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; +import { CancellationToken } from 'vs/base/common/cancellation'; // Register configuration const configurationRegistry = Registry.as(ConfigurationExtensions.Configuration); @@ -221,16 +223,52 @@ class ChatSlashStaticSlashCommandsContribution extends Disposable { constructor( @IChatSlashCommandService slashCommandService: IChatSlashCommandService, @ICommandService commandService: ICommandService, + @IChatAgentService chatAgentService: IChatAgentService, ) { super(); this._store.add(slashCommandService.registerSlashCommand({ command: 'clear', detail: nls.localize('clear', "Clear the session"), - sortText: 'z_clear', + sortText: 'z2_clear', executeImmediately: true }, async () => { commandService.executeCommand(ACTION_ID_CLEAR_CHAT); })); + this._store.add(slashCommandService.registerSlashCommand({ + command: 'help', + detail: '', + sortText: 'z1_help', + executeImmediately: true + }, async (prompt, progress) => { + const defaultAgent = chatAgentService.getDefaultAgent(); + const agents = chatAgentService.getAgents(); + if (defaultAgent?.metadata.helpTextPrefix) { + progress.report({ content: defaultAgent.metadata.helpTextPrefix }); + progress.report({ content: '\n\n' }); + } + + const agentText = (await Promise.all(agents + .filter(a => a.id !== defaultAgent?.id) + .map(async a => { + const agentWithLeader = `${chatAgentLeader}${a.id}`; + const actionArg: IChatExecuteActionContext = { inputValue: `${agentWithLeader} ${a.metadata.sampleRequest}` }; + const urlSafeArg = encodeURIComponent(JSON.stringify(actionArg)); + const agentLine = `* [\`${agentWithLeader}\`](command:${SubmitAction.ID}?${urlSafeArg}) - ${a.metadata.description}`; + const commands = await a.provideSlashCommands(CancellationToken.None); + const commandText = commands.map(c => { + const actionArg: IChatExecuteActionContext = { inputValue: `${agentWithLeader} ${chatSubcommandLeader}${c.name} ${c.sampleRequest ?? ''}` }; + const urlSafeArg = encodeURIComponent(JSON.stringify(actionArg)); + return `\t* [\`${chatSubcommandLeader}${c.name}\`](command:${SubmitAction.ID}?${urlSafeArg}) - ${c.description}`; + }).join('\n'); + + return agentLine + '\n' + commandText; + }))).join('\n'); + progress.report({ content: new MarkdownString(agentText, { isTrusted: { enabledCommands: [SubmitAction.ID] } }) }); + if (defaultAgent?.metadata.helpTextPostfix) { + progress.report({ content: '\n\n' }); + progress.report({ content: defaultAgent.metadata.helpTextPostfix }); + } + })); } } diff --git a/src/vs/workbench/contrib/chat/browser/chat.ts b/src/vs/workbench/contrib/chat/browser/chat.ts index ee80bfeacb9..3df1e83af19 100644 --- a/src/vs/workbench/contrib/chat/browser/chat.ts +++ b/src/vs/workbench/contrib/chat/browser/chat.ts @@ -3,14 +3,13 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; -import { ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService'; -import { IChatRequestViewModel, IChatResponseViewModel, IChatViewModel, IChatWelcomeMessageViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { Event } from 'vs/base/common/event'; import { URI } from 'vs/base/common/uri'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; +import { Selection } from 'vs/editor/common/core/selection'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatWidgetContrib } from 'vs/workbench/contrib/chat/browser/chatWidget'; -import { Selection } from 'vs/editor/common/core/selection'; +import { IChatRequestViewModel, IChatResponseViewModel, IChatViewModel, IChatWelcomeMessageViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; export const IChatWidgetService = createDecorator('chatWidgetService'); export const IQuickChatService = createDecorator('quickChatService'); @@ -39,6 +38,7 @@ export interface IQuickChatService { readonly _serviceBrand: undefined; readonly onDidClose: Event; readonly enabled: boolean; + readonly focused: boolean; toggle(providerId?: string, options?: IQuickChatOpenOptions): void; focus(): void; open(providerId?: string, options?: IQuickChatOpenOptions): void; @@ -120,7 +120,6 @@ export interface IChatWidget { focusLastMessage(): void; focusInput(): void; hasInputFocus(): boolean; - getSlashCommands(): Promise; getCodeBlockInfoForEditor(uri: URI): IChatCodeBlockInfo | undefined; getCodeBlockInfosForResponse(response: IChatResponseViewModel): IChatCodeBlockInfo[]; getFileTreeInfosForResponse(response: IChatResponseViewModel): IChatFileTreeInfo[]; diff --git a/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts b/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts index b7173483cd1..1a19c1d3850 100644 --- a/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts +++ b/src/vs/workbench/contrib/chat/browser/chatAccessibilityService.ts @@ -7,48 +7,60 @@ import { status } from 'vs/base/browser/ui/aria/aria'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import { AudioCue, IAudioCueService } from 'vs/platform/audioCues/browser/audioCueService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { IChatAccessibilityService } from 'vs/workbench/contrib/chat/browser/chat'; import { IChatResponseViewModel } from 'vs/workbench/contrib/chat/common/chatViewModel'; -const CHAT_RESPONSE_PENDING_AUDIO_CUE_LOOP_MS = 5000; -const CHAT_RESPONSE_PENDING_ALLOWANCE_MS = 4000; export class ChatAccessibilityService extends Disposable implements IChatAccessibilityService { declare readonly _serviceBrand: undefined; - private _pendingCueMap: Map = new Map(); + private _pendingCueMap: Map = new Map(); - private _runOnceScheduler: RunOnceScheduler; - private _lastResponse: string | undefined; private _requestId: number = 0; - constructor(@IAudioCueService private readonly _audioCueService: IAudioCueService) { + constructor(@IAudioCueService private readonly _audioCueService: IAudioCueService, @IInstantiationService private readonly _instantiationService: IInstantiationService) { super(); - this._register(this._runOnceScheduler = new RunOnceScheduler(() => { - this._pendingCueMap.set(this._requestId, this._audioCueService.playAudioCueLoop(AudioCue.chatResponsePending, CHAT_RESPONSE_PENDING_AUDIO_CUE_LOOP_MS)); - }, CHAT_RESPONSE_PENDING_ALLOWANCE_MS)); } acceptRequest(): number { this._requestId++; this._audioCueService.playAudioCue(AudioCue.chatRequestSent, { allowManyInParallel: true }); - this._runOnceScheduler.schedule(); + this._pendingCueMap.set(this._requestId, this._register(this._instantiationService.createInstance(AudioCueScheduler))); return this._requestId; } acceptResponse(response: IChatResponseViewModel | string | undefined, requestId: number): void { this._pendingCueMap.get(requestId)?.dispose(); this._pendingCueMap.delete(requestId); const isPanelChat = typeof response !== 'string'; - this._runOnceScheduler?.cancel(); const responseContent = typeof response === 'string' ? response : response?.response.asString(); - if (this._lastResponse === responseContent) { - return; - } this._audioCueService.playAudioCue(AudioCue.chatResponseReceived, { allowManyInParallel: true }); if (!response) { return; } const errorDetails = isPanelChat && response.errorDetails ? ` ${response.errorDetails.message}` : ''; - this._lastResponse = responseContent; status(responseContent + errorDetails); } } + +const CHAT_RESPONSE_PENDING_AUDIO_CUE_LOOP_MS = 5000; +const CHAT_RESPONSE_PENDING_ALLOWANCE_MS = 4000; +/** + * Schedules an audio cue to play when a chat response is pending for too long. + */ +class AudioCueScheduler extends Disposable { + private _scheduler: RunOnceScheduler; + private _audioCueLoop: IDisposable | undefined; + constructor(@IAudioCueService private readonly _audioCueService: IAudioCueService) { + super(); + this._scheduler = new RunOnceScheduler(() => { + this._audioCueLoop = this._audioCueService.playAudioCueLoop(AudioCue.chatResponsePending, CHAT_RESPONSE_PENDING_AUDIO_CUE_LOOP_MS); + }, CHAT_RESPONSE_PENDING_ALLOWANCE_MS); + this._scheduler.schedule(); + } + override dispose(): void { + super.dispose(); + this._audioCueLoop?.dispose(); + this._scheduler.cancel(); + this._scheduler.dispose(); + } +} diff --git a/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts b/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts index 37ac18570a3..78ff2e9fa61 100644 --- a/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/browser/chatContributionServiceImpl.ts @@ -15,7 +15,7 @@ import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as import { IViewContainersRegistry, IViewDescriptor, IViewsRegistry, ViewContainer, ViewContainerLocation, Extensions as ViewExtensions } from 'vs/workbench/common/views'; import { getHistoryAction, getOpenChatEditorAction } from 'vs/workbench/contrib/chat/browser/actions/chatActions'; import { getClearAction } from 'vs/workbench/contrib/chat/browser/actions/chatClearActions'; -import { getMoveToEditorAction } from 'vs/workbench/contrib/chat/browser/actions/chatMoveActions'; +import { getMoveToEditorAction, getMoveToNewWindowAction } from 'vs/workbench/contrib/chat/browser/actions/chatMoveActions'; import { getQuickChatActionForProvider } from 'vs/workbench/contrib/chat/browser/actions/chatQuickInputActions'; import { CHAT_SIDEBAR_PANEL_ID, ChatViewPane, IChatViewOptions } from 'vs/workbench/contrib/chat/browser/chatViewPane'; import { IChatContributionService, IChatProviderContribution, IRawChatProviderContribution } from 'vs/workbench/contrib/chat/common/chatContributionService'; @@ -137,6 +137,7 @@ export class ChatExtensionPointHandler implements IWorkbenchContribution { disposables.add(registerAction2(getHistoryAction(viewId, providerDescriptor.id))); disposables.add(registerAction2(getClearAction(viewId, providerDescriptor.id))); disposables.add(registerAction2(getMoveToEditorAction(viewId, providerDescriptor.id))); + disposables.add(registerAction2(getMoveToNewWindowAction(viewId, providerDescriptor.id))); // "Open Chat" Actions disposables.add(registerAction2(getOpenChatEditorAction(providerDescriptor.id, providerDescriptor.label, providerDescriptor.when))); diff --git a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts index c7f1c97fd14..ebfb67e8d2a 100644 --- a/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts +++ b/src/vs/workbench/contrib/chat/browser/chatListRenderer.ts @@ -16,7 +16,7 @@ import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree'; import { IAsyncDataSource, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; import { IAction } from 'vs/base/common/actions'; import { distinct } from 'vs/base/common/arrays'; -import { IntervalTimer, disposableTimeout } from 'vs/base/common/async'; +import { disposableTimeout } from 'vs/base/common/async'; import { Codicon } from 'vs/base/common/codicons'; import { Emitter, Event } from 'vs/base/common/event'; import { FuzzyScore } from 'vs/base/common/filters'; @@ -54,10 +54,10 @@ import { convertParsedRequestToMarkdown, reduceInlineContentReferences, walkTree import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; import { CodeBlockPart, ICodeBlockData, ICodeBlockPart } from 'vs/workbench/contrib/chat/browser/codeBlockPart'; import { IChatAgentMetadata } from 'vs/workbench/contrib/chat/common/chatAgents'; -import { CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_HAS_PROVIDER_ID, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; +import { CONTEXT_REQUEST, CONTEXT_RESPONSE, CONTEXT_RESPONSE_FILTERED, CONTEXT_RESPONSE_VOTE } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IPlaceholderMarkdownString } from 'vs/workbench/contrib/chat/common/chatModel'; import { chatAgentLeader, chatSubcommandLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatContentReference, IChatReplyFollowup, IChatResponseProgressFileTreeData, IChatService, ISlashCommand, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatContentReference, IChatReplyFollowup, IChatResponseProgressFileTreeData, IChatService, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatResponseMarkdownRenderData, IChatResponseRenderData, IChatResponseViewModel, IChatWelcomeMessageViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { IWordCountResult, getNWords } from 'vs/workbench/contrib/chat/common/chatWordCounter'; import { createFileIconThemableTreeContainerScope } from 'vs/workbench/contrib/files/browser/views/explorerView'; @@ -89,7 +89,6 @@ const forceVerboseLayoutTracing = false; export interface IChatRendererDelegate { getListLength(): number; - getSlashCommands(): ISlashCommand[]; } export interface IChatListItemRendererOptions { @@ -264,7 +263,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { try { if (this.doNextProgressiveRender(element, index, templateData, !!initial, progressiveRenderingDisposables)) { @@ -311,7 +309,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { + const disposable = templateData.elementDisposables.add(dom.scheduleAtNextAnimationFrame(dom.getWindow(templateData.value), () => { disposable.dispose(); this._onDidChangeItemHeight.fire({ element, height: newHeight }); - }, dom.getWindow(templateData.value))); + })); } } private renderWelcomeMessage(element: IChatWelcomeMessageViewModel, templateData: IChatListItemTemplate, height?: number) { dom.clearNode(templateData.value); dom.clearNode(templateData.referencesListContainer); - const slashCommands = this.delegate.getSlashCommands(); for (const item of element.content) { if (Array.isArray(item)) { @@ -477,11 +474,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer codeElement.textContent === `/${command.command}`)) { - codeElement.classList.add('interactive-slash-command'); - } - } templateData.value.appendChild(result.element); templateData.elementDisposables.add(result); } @@ -493,10 +485,10 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer { + const disposable = templateData.elementDisposables.add(dom.scheduleAtNextAnimationFrame(dom.getWindow(templateData.value), () => { disposable.dispose(); this._onDidChangeItemHeight.fire({ element, height: newHeight }); - }, dom.getWindow(templateData.value))); + })); } } @@ -779,11 +771,7 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer markdown.value.startsWith(`/${s.command} `)); - const toRender = usedSlashCommand ? markdown.value.slice(usedSlashCommand.command.length + 2) : markdown.value; - markdown = new MarkdownString(toRender, { + markdown = new MarkdownString(markdown.value, { isTrusted: { // Disable all other config options except isTrusted enabledCommands: typeof markdown.isTrusted === 'object' ? markdown.isTrusted?.enabledCommands : [] ?? [] @@ -831,15 +819,6 @@ export class ChatListItemRenderer extends Disposable implements ITreeRenderer disposables.add(d)); return { element: result.element, diff --git a/src/vs/workbench/contrib/chat/browser/chatWidget.ts b/src/vs/workbench/contrib/chat/browser/chatWidget.ts index c6f79e430df..7634b50e741 100644 --- a/src/vs/workbench/contrib/chat/browser/chatWidget.ts +++ b/src/vs/workbench/contrib/chat/browser/chatWidget.ts @@ -6,7 +6,6 @@ import * as dom from 'vs/base/browser/dom'; import { ITreeContextMenuEvent, ITreeElement } from 'vs/base/browser/ui/tree/tree'; import { disposableTimeout } from 'vs/base/common/async'; -import { CancellationToken } from 'vs/base/common/cancellation'; import { toErrorMessage } from 'vs/base/common/errorMessage'; import { Emitter } from 'vs/base/common/event'; import { Disposable, DisposableStore, IDisposable, MutableDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -23,7 +22,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle import { WorkbenchObjectTree } from 'vs/platform/list/browser/listService'; import { ILogService } from 'vs/platform/log/common/log'; import { IViewsService } from 'vs/workbench/common/views'; -import { ChatTreeItem, IChatWidgetViewOptions, IChatAccessibilityService, IChatCodeBlockInfo, IChatFileTreeInfo, IChatWidget, IChatWidgetService, IChatWidgetViewContext } from 'vs/workbench/contrib/chat/browser/chat'; +import { ChatTreeItem, IChatAccessibilityService, IChatCodeBlockInfo, IChatFileTreeInfo, IChatWidget, IChatWidgetService, IChatWidgetViewContext, IChatWidgetViewOptions } from 'vs/workbench/contrib/chat/browser/chat'; import { ChatInputPart } from 'vs/workbench/contrib/chat/browser/chatInputPart'; import { ChatAccessibilityProvider, ChatListDelegate, ChatListItemRenderer, IChatListItemRendererOptions, IChatRendererDelegate } from 'vs/workbench/contrib/chat/browser/chatListRenderer'; import { ChatEditorOptions } from 'vs/workbench/contrib/chat/browser/chatOptions'; @@ -31,7 +30,7 @@ import { ChatViewPane } from 'vs/workbench/contrib/chat/browser/chatViewPane'; import { CONTEXT_CHAT_REQUEST_IN_PROGRESS, CONTEXT_IN_CHAT_SESSION } from 'vs/workbench/contrib/chat/common/chatContextKeys'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { ChatModelInitState, IChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChatReplyFollowup, IChatService, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatReplyFollowup, IChatService } from 'vs/workbench/contrib/chat/common/chatService'; import { ChatViewModel, IChatResponseViewModel, isRequestVM, isResponseVM, isWelcomeVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; const $ = dom.$; @@ -109,15 +108,6 @@ export class ChatWidget extends Disposable implements IChatWidget { this.viewModelDisposables.add(viewModel); } - this.slashCommandsPromise = undefined; - this.lastSlashCommands = undefined; - - this.getSlashCommands().then(() => { - if (!this._isDisposed) { - this.onDidChangeItems(); - } - }); - this._onDidChangeViewModel.fire(); } @@ -125,9 +115,6 @@ export class ChatWidget extends Disposable implements IChatWidget { return this._viewModel; } - private lastSlashCommands: ISlashCommand[] | undefined; - private slashCommandsPromise: Promise | undefined; - constructor( readonly viewContext: IChatWidgetViewContext, private readonly viewOptions: IChatWidgetViewOptions, @@ -164,12 +151,6 @@ export class ChatWidget extends Disposable implements IChatWidget { return this.inputPart.inputUri; } - private _isDisposed: boolean = false; - public override dispose(): void { - this._isDisposed = true; - super.dispose(); - } - render(parent: HTMLElement): void { const viewId = 'viewId' in this.viewContext ? this.viewContext.viewId : undefined; this.editorOptions = this._register(this.instantiationService.createInstance(ChatEditorOptions, viewId, this.styles.listForeground, this.styles.inputEditorBackground, this.styles.resultEditorBackground)); @@ -260,7 +241,7 @@ export class ChatWidget extends Disposable implements IChatWidget { // TODO? We can give the welcome message a proper VM or get rid of the rest of the VMs ((isWelcomeVM(element) && this.viewModel) ? `_${ChatModelInitState[this.viewModel.initState]}` : '') + // Ensure re-rendering an element once slash commands are loaded, so the colorization can be applied. - `${(isRequestVM(element) || isWelcomeVM(element)) && !!this.lastSlashCommands ? '_scLoaded' : ''}` + + `${(isRequestVM(element) || isWelcomeVM(element)) /* && !!this.lastSlashCommands ? '_scLoaded' : '' */}` + // If a response is in the process of progressive rendering, we need to ensure that it will // be re-rendered so progressive rendering is restarted, even if the model wasn't updated. `${isResponseVM(element) && element.renderData ? `_${this.visibleChangeCount}` : ''}` + @@ -309,27 +290,11 @@ export class ChatWidget extends Disposable implements IChatWidget { } } - async getSlashCommands(): Promise { - if (!this.viewModel) { - return; - } - - if (!this.slashCommandsPromise) { - this.slashCommandsPromise = this.chatService.getSlashCommands(this.viewModel.sessionId, CancellationToken.None).then(commands => { - this.lastSlashCommands = commands ?? []; - return this.lastSlashCommands; - }); - } - - return this.slashCommandsPromise; - } - private createList(listContainer: HTMLElement, options: IChatListItemRendererOptions): void { const scopedInstantiationService = this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.contextKeyService])); const delegate = scopedInstantiationService.createInstance(ChatListDelegate); const rendererDelegate: IChatRendererDelegate = { getListLength: () => this.tree.getNode(null).visibleChildrenCount, - getSlashCommands: () => this.lastSlashCommands ?? [], }; this.renderer = this._register(scopedInstantiationService.createInstance( ChatListItemRenderer, @@ -404,10 +369,10 @@ export class ChatWidget extends Disposable implements IChatWidget { // Consider the tree to be scrolled all the way down if it is within 2px of the bottom. const lastElementWasVisible = this.tree.scrollTop + this.tree.renderHeight >= this.previousTreeScrollHeight - 2; if (lastElementWasVisible) { - dom.scheduleAtNextAnimationFrame(() => { + dom.scheduleAtNextAnimationFrame(dom.getWindow(this.listContainer), () => { // Can't set scrollTop during this event listener, the list might overwrite the change revealLastElement(this.tree); - }, dom.getWindow(this.listContainer), 0); + }, 0); } } @@ -462,7 +427,6 @@ export class ChatWidget extends Disposable implements IChatWidget { this.container.setAttribute('data-session-id', model.sessionId); this.viewModel = this.instantiationService.createInstance(ChatViewModel, model); this.viewModelDisposables.add(this.viewModel.onDidChange(e => { - this.slashCommandsPromise = undefined; this.requestInProgress.set(this.viewModel!.requestInProgress); this.onDidChangeItems(); if (e?.kind === 'addRequest') { @@ -536,8 +500,7 @@ export class ChatWidget extends Disposable implements IChatWidget { 'query' in opts ? opts.query : `${opts.prefix} ${editorValue}`; const isUserQuery = !opts || 'query' in opts; - const usedSlashCommand = this.lookupSlashCommand(input); - const result = await this.chatService.sendRequest(this.viewModel.sessionId, input, usedSlashCommand); + const result = await this.chatService.sendRequest(this.viewModel.sessionId, input); if (result) { this.inputPart.acceptInput(isUserQuery ? input : undefined); @@ -552,10 +515,6 @@ export class ChatWidget extends Disposable implements IChatWidget { } } - private lookupSlashCommand(input: string): ISlashCommand | undefined { - return this.lastSlashCommands?.find(sc => input.startsWith(`/${sc.command}`)); - } - getCodeBlockInfosForResponse(response: IChatResponseViewModel): IChatCodeBlockInfo[] { return this.renderer.getCodeBlockInfosForResponse(response); } @@ -625,7 +584,7 @@ export class ChatWidget extends Disposable implements IChatWidget { if (!this._dynamicMessageLayoutData?.enabled) { return; } - mutableDisposable.value = dom.scheduleAtNextAnimationFrame(() => { + mutableDisposable.value = dom.scheduleAtNextAnimationFrame(dom.getWindow(this.listContainer), () => { if (!e.scrollTopChanged || e.heightChanged || e.scrollHeightChanged) { return; } @@ -640,7 +599,7 @@ export class ChatWidget extends Disposable implements IChatWidget { const inputPartHeight = this.inputPart.layout(possibleMaxHeight, width); const newHeight = Math.min(renderHeight + diff, possibleMaxHeight - inputPartHeight); this.layout(newHeight + inputPartHeight, width); - }, dom.getWindow(this.listContainer)); + }); })); } diff --git a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts index 4bc5d908414..fd5350bf367 100644 --- a/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts +++ b/src/vs/workbench/contrib/chat/browser/codeBlockPart.ts @@ -167,7 +167,7 @@ export class CodeBlockPart extends Disposable implements ICodeBlockPart { WordHighlighterContribution.get(this.editor)?.restoreViewState(true); })); - this.textModel = this._register(this.modelService.createModel('', null, undefined)); + this.textModel = this._register(this.modelService.createModel('', null, undefined, true)); this.editor.setModel(this.textModel); } diff --git a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts index 6db8aa47a21..a470b14d1e3 100644 --- a/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts +++ b/src/vs/workbench/contrib/chat/browser/contrib/chatInputEditorContrib.ts @@ -32,6 +32,7 @@ import { chatSlashCommandBackground, chatSlashCommandForeground } from 'vs/workb import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestVariablePart, chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; import { IChatService, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { isResponseVM } from 'vs/workbench/contrib/chat/common/chatViewModel'; import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; @@ -49,7 +50,7 @@ class InputEditorDecorations extends Disposable { public readonly id = 'inputEditorDecorations'; - private readonly previouslyUsedSlashCommands = new Set(); + private readonly previouslyUsedAgents = new Set(); private readonly viewModelDisposables = this._register(new MutableDisposable()); @@ -71,16 +72,12 @@ class InputEditorDecorations extends Disposable { this._register(this.widget.inputEditor.onDidChangeModelContent(() => this.updateInputEditorDecorations())); this._register(this.widget.onDidChangeViewModel(() => { this.registerViewModelListeners(); - this.previouslyUsedSlashCommands.clear(); + this.previouslyUsedAgents.clear(); this.updateInputEditorDecorations(); })); - this._register(this.chatService.onDidSubmitSlashCommand((e) => { + this._register(this.chatService.onDidSubmitAgent((e) => { if (e.sessionId === this.widget.viewModel?.sessionId) { - if ('agent' in e) { - this.previouslyUsedSlashCommands.add(agentAndCommandToKey(e.agent.id, e.slashCommand.name)); - } else { - this.previouslyUsedSlashCommands.add(e.slashCommand); - } + this.previouslyUsedAgents.add(agentAndCommandToKey(e.agent.id, e.slashCommand.name)); } })); @@ -187,7 +184,7 @@ class InputEditorDecorations extends Disposable { const onlyAgentCommandAndWhitespace = agentPart && agentSubcommandPart && parsedRequest.every(p => p instanceof ChatRequestTextPart && !p.text.trim().length || p instanceof ChatRequestAgentPart || p instanceof ChatRequestAgentSubcommandPart); if (onlyAgentCommandAndWhitespace) { // Agent reference and subcommand with no other text - show the placeholder - const isFollowupSlashCommand = this.previouslyUsedSlashCommands.has(agentAndCommandToKey(agentPart.agent.id, agentSubcommandPart.command.name)); + const isFollowupSlashCommand = this.previouslyUsedAgents.has(agentAndCommandToKey(agentPart.agent.id, agentSubcommandPart.command.name)); const shouldRenderFollowupPlaceholder = isFollowupSlashCommand && agentSubcommandPart.command.followupPlaceholder; if (agentSubcommandPart?.command.description) { placeholderDecoration = [{ @@ -210,7 +207,7 @@ class InputEditorDecorations extends Disposable { const onlySlashCommandAndWhitespace = slashCommandPart && parsedRequest.every(p => p instanceof ChatRequestTextPart && !p.text.trim().length || p instanceof ChatRequestSlashCommandPart); if (onlySlashCommandAndWhitespace) { // Command reference with no other text - show the placeholder - const isFollowupSlashCommand = this.previouslyUsedSlashCommands.has(slashCommandPart.slashCommand.command); + const isFollowupSlashCommand = this.previouslyUsedAgents.has(slashCommandPart.slashCommand.command); const shouldRenderFollowupPlaceholder = isFollowupSlashCommand && slashCommandPart.slashCommand.followupPlaceholder; if (shouldRenderFollowupPlaceholder || slashCommandPart.slashCommand.detail) { placeholderDecoration = [{ @@ -264,15 +261,12 @@ class InputEditorSlashCommandMode extends Disposable { @IChatService private readonly chatService: IChatService ) { super(); - this._register(this.chatService.onDidSubmitSlashCommand(e => { + this._register(this.chatService.onDidSubmitAgent(e => { if (this.widget.viewModel?.sessionId !== e.sessionId) { return; } - if ('agent' in e) { - this.repopulateAgentCommand(e.agent, e.slashCommand); - } else { - this.repopulateSlashCommand(e.slashCommand); - } + + this.repopulateAgentCommand(e.agent, e.slashCommand); })); } @@ -283,20 +277,6 @@ class InputEditorSlashCommandMode extends Disposable { this.widget.inputEditor.setPosition({ lineNumber: 1, column: value.length + 1 }); } } - - private async repopulateSlashCommand(slashCommand: string) { - const slashCommands = await this.widget.getSlashCommands(); - - if (this.widget.inputEditor.getValue().trim().length !== 0) { - return; - } - - if (slashCommands?.find(c => c.command === slashCommand)?.shouldRepopulate) { - const value = `/${slashCommand} `; - this.widget.inputEditor.setValue(value); - this.widget.inputEditor.setPosition({ lineNumber: 1, column: value.length + 1 }); - } - } } ChatWidget.CONTRIBS.push(InputEditorDecorations, InputEditorSlashCommandMode); @@ -306,11 +286,12 @@ class SlashCommandCompletions extends Disposable { @ILanguageFeaturesService private readonly languageFeaturesService: ILanguageFeaturesService, @IChatWidgetService private readonly chatWidgetService: IChatWidgetService, @IInstantiationService private readonly instantiationService: IInstantiationService, + @IChatSlashCommandService private readonly chatSlashCommandService: IChatSlashCommandService ) { super(); this._register(this.languageFeaturesService.completionProvider.register({ scheme: ChatInputPart.INPUT_SCHEME, hasAccessToAllModels: true }, { - _debugDisplayName: 'chatSlashCommand', + _debugDisplayName: 'globalSlashCommands', triggerCharacters: ['/'], provideCompletionItems: async (model: ITextModel, _position: Position, _context: CompletionContext, _token: CancellationToken) => { const widget = this.chatWidgetService.getWidgetByInputUri(model.uri); @@ -329,7 +310,7 @@ class SlashCommandCompletions extends Disposable { return; } - const slashCommands = await widget.getSlashCommands(); + const slashCommands = this.chatSlashCommandService.getCommands(); if (!slashCommands) { return null; } @@ -550,6 +531,7 @@ class BuiltinDynamicCompletions extends Disposable { range: { insert, replace }, kind: CompletionItemKind.Text, command: { id: SelectAndInsertFileAction.ID, title: SelectAndInsertFileAction.ID, arguments: [{ widget, range }] }, + sortText: 'z' } ] }; @@ -701,7 +683,8 @@ class VariableCompletions extends Disposable { range, insertText: withLeader + ' ', detail: v.description, - kind: CompletionItemKind.Text, // The icons are disabled here anyway, + kind: CompletionItemKind.Text, // The icons are disabled here anyway + sortText: 'z' }; }); diff --git a/src/vs/workbench/contrib/chat/browser/media/chat.css b/src/vs/workbench/contrib/chat/browser/media/chat.css index 1d3f4d3123e..9c46adfed29 100644 --- a/src/vs/workbench/contrib/chat/browser/media/chat.css +++ b/src/vs/workbench/contrib/chat/browser/media/chat.css @@ -270,8 +270,8 @@ } .interactive-item-container.interactive-item-compact .header .avatar { - width: 16px; - height: 16px; + width: 18px; + height: 18px; } .interactive-item-container.interactive-item-compact .header .avatar .icon { @@ -279,7 +279,7 @@ height: 16px; } -.interactive-item-container.interactive-item-compact .header .avatar+.avatar .codicon { +.interactive-item-container.interactive-item-compact .header .codicon-avatar .codicon { font-size: 12px; } @@ -390,10 +390,6 @@ color: var(--vscode-notificationsInfoIcon-foreground) !important; /* Have to override default styles which apply to all lists */ } -.interactive-item-container .value .interactive-slash-command { - color: var(--vscode-textLink-foreground); -} - .interactive-session .interactive-input-part { padding: 12px 0px; display: flex; @@ -546,6 +542,7 @@ border: 1px solid var(--vscode-chat-requestBorder); border-radius: 4px; margin-bottom: 8px; + padding: 4px; } .interactive-session .chat-used-context-list .monaco-list .monaco-list-row { diff --git a/src/vs/workbench/contrib/chat/common/chatAgents.ts b/src/vs/workbench/contrib/chat/common/chatAgents.ts index 1845706b098..a705799a065 100644 --- a/src/vs/workbench/contrib/chat/common/chatAgents.ts +++ b/src/vs/workbench/contrib/chat/common/chatAgents.ts @@ -5,13 +5,14 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; +import { IMarkdownString } from 'vs/base/common/htmlContent'; import { Iterable } from 'vs/base/common/iterator'; import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; -import { IChatFollowup, IChatProgress, IChatResponseErrorDetails, IChatResponseProgressFileTreeData } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatFollowup, IChatProgress, IChatResponseErrorDetails } from 'vs/workbench/contrib/chat/common/chatService'; import { IChatRequestVariableValue } from 'vs/workbench/contrib/chat/common/chatVariables'; //#region agent service, commands etc @@ -27,40 +28,44 @@ export interface IChatAgent extends IChatAgentData { provideSlashCommands(token: CancellationToken): Promise; } -export interface IChatAgentFragment { - content: string | { treeData: IChatResponseProgressFileTreeData }; -} - export interface IChatAgentCommand { name: string; description: string; + /** * Whether the command should execute as soon * as it is entered. Defaults to `false`. */ executeImmediately?: boolean; + /** * Whether executing the command puts the * chat into a persistent mode, where the * slash command is prepended to the chat input. */ shouldRepopulate?: boolean; + /** * Placeholder text to render in the chat input * when the slash command has been repopulated. * Has no effect if `shouldRepopulate` is `false`. */ followupPlaceholder?: string; + + sampleRequest?: string; } export interface IChatAgentMetadata { description?: string; isDefault?: boolean; // The agent invoked when no agent is specified + helpTextPrefix?: string | IMarkdownString; + helpTextPostfix?: string | IMarkdownString; isSecondary?: boolean; // Invoked by ctrl/cmd+enter fullName?: string; icon?: URI; iconDark?: URI; themeIcon?: ThemeIcon; + sampleRequest?: string; } export interface IChatAgentRequest { diff --git a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts index 631f0af7d00..ed242d3b415 100644 --- a/src/vs/workbench/contrib/chat/common/chatContextKeys.ts +++ b/src/vs/workbench/contrib/chat/common/chatContextKeys.ts @@ -6,7 +6,6 @@ import { localize } from 'vs/nls'; import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; -export const CONTEXT_RESPONSE_HAS_PROVIDER_ID = new RawContextKey('chatSessionResponseHasProviderId', false, { type: 'boolean', description: localize('interactiveSessionResponseHasProviderId', "True when the provider has assigned an id to this response.") }); export const CONTEXT_RESPONSE_VOTE = new RawContextKey('chatSessionResponseVote', '', { type: 'string', description: localize('interactiveSessionResponseVote', "When the response has been voted up, is set to 'up'. When voted down, is set to 'down'. Otherwise an empty string.") }); export const CONTEXT_RESPONSE_FILTERED = new RawContextKey('chatSessionResponseFiltered', false, { type: 'boolean', description: localize('chatResponseFiltered', "True when the chat response was filtered out by the server.") }); export const CONTEXT_CHAT_REQUEST_IN_PROGRESS = new RawContextKey('chatSessionRequestInProgress', false, { type: 'boolean', description: localize('interactiveSessionRequestInProgress', "True when the current request is still in progress.") }); diff --git a/src/vs/workbench/contrib/chat/common/chatModel.ts b/src/vs/workbench/contrib/chat/common/chatModel.ts index 471764b058f..b743e6915fe 100644 --- a/src/vs/workbench/contrib/chat/common/chatModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatModel.ts @@ -20,7 +20,6 @@ import { IChat, IChatContentInlineReference, IChatContentReference, IChatFollowu export interface IChatRequestModel { readonly id: string; - readonly providerRequestId: string | undefined; readonly username: string; readonly avatarIconUri?: URI; readonly session: IChatModel; @@ -53,7 +52,6 @@ export interface IChatResponseModel { readonly onDidChange: Event; readonly id: string; readonly providerId: string; - readonly providerResponseId: string | undefined; readonly requestId: string; readonly username: string; readonly avatarIconUri?: URI; @@ -79,10 +77,6 @@ export class ChatRequestModel implements IChatRequestModel { return this._id; } - public get providerRequestId(): string | undefined { - return this._providerRequestId; - } - public get username(): string { return this.session.requesterUsername; } @@ -93,14 +87,9 @@ export class ChatRequestModel implements IChatRequestModel { constructor( public readonly session: ChatModel, - public readonly message: IParsedChatRequest, - private _providerRequestId?: string) { + public readonly message: IParsedChatRequest) { this._id = 'request_' + ChatRequestModel.nextId++; } - - setProviderRequestId(providerRequestId: string) { - this._providerRequestId = providerRequestId; - } } export interface IPlaceholderMarkdownString extends IMarkdownString { @@ -169,7 +158,11 @@ export class Response implements IResponse { } else if (lastResponsePart) { // Combine this part with the last, non-resolving string part if (isMarkdownString(responsePart)) { - this._responseParts[responsePartLength] = { string: new MarkdownString(lastResponsePart.string.value + responsePart.value, responsePart) }; + // Merge all enabled commands + const lastPartEnabledCommands = typeof lastResponsePart.string.isTrusted === 'object' ? lastResponsePart.string.isTrusted.enabledCommands : []; + const thisPartEnabledCommands = typeof responsePart.isTrusted === 'object' ? responsePart.isTrusted.enabledCommands : []; + const enabledCommands = [...lastPartEnabledCommands, ...thisPartEnabledCommands]; + this._responseParts[responsePartLength] = { string: new MarkdownString(lastResponsePart.string.value + responsePart.value, { isTrusted: { enabledCommands } }) }; } else { this._responseParts[responsePartLength] = { string: new MarkdownString(lastResponsePart.string.value + responsePart, lastResponsePart.string) }; } @@ -252,10 +245,6 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel return this._id; } - public get providerResponseId(): string | undefined { - return this._providerResponseId; - } - public get isComplete(): boolean { return this._isComplete; } @@ -313,7 +302,6 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel private _isComplete: boolean = false, private _isCanceled = false, private _vote?: InteractiveSessionVoteDirection, - private _providerResponseId?: string, private _errorDetails?: IChatResponseErrorDetails, followups?: ReadonlyArray ) { @@ -335,10 +323,6 @@ export class ChatResponseModel extends Disposable implements IChatResponseModel this._onDidChange.fire(); } - setProviderResponseId(providerResponseId: string) { - this._providerResponseId = providerResponseId; - } - setErrorDetails(errorDetails?: IChatResponseErrorDetails): void { this._errorDetails = errorDetails; this._onDidChange.fire(); @@ -388,7 +372,6 @@ export interface ISerializableChatsData { export type ISerializableChatAgentData = UriDto; export interface ISerializableChatRequestData { - providerRequestId: string | undefined; message: string | IParsedChatRequest; response: ReadonlyArray | undefined; agent?: ISerializableChatAgentData; @@ -410,7 +393,6 @@ export interface IExportableChatData { responderUsername: string; requesterAvatarIconUri: UriComponents | undefined; responderAvatarIconUri: UriComponents | undefined; - providerState: any; } export interface ISerializableChatData extends IExportableChatData { @@ -486,11 +468,6 @@ export class ChatModel extends Disposable implements IChatModel { return this._welcomeMessage; } - private _providerState: any; - get providerState(): any { - return this._providerState; - } - // TODO to be clear, this is not the same as the id from the session object, which belongs to the provider. // It's easier to be able to identify this model before its async initialization is complete private _sessionId: string; @@ -556,7 +533,6 @@ export class ChatModel extends Disposable implements IChatModel { this._isImported = (!!initialData && !isSerializableSessionData(initialData)) || (initialData?.isImported ?? false); this._sessionId = (isSerializableSessionData(initialData) && initialData.sessionId) || generateUuid(); this._requests = initialData ? this._deserialize(initialData) : []; - this._providerState = initialData ? initialData.providerState : undefined; this._creationDate = (isSerializableSessionData(initialData) && initialData.creationDate) || Date.now(); this._initialRequesterAvatarIconUri = initialData?.requesterAvatarIconUri && URI.revive(initialData.requesterAvatarIconUri); @@ -581,11 +557,11 @@ export class ChatModel extends Disposable implements IChatModel { typeof raw.message === 'string' ? this.getParsedRequestFromString(raw.message) : reviveParsedChatRequest(raw.message); - const request = new ChatRequestModel(this, parsedRequest, raw.providerRequestId); + const request = new ChatRequestModel(this, parsedRequest); if (raw.response || raw.responseErrorDetails) { const agent = (raw.agent && 'metadata' in raw.agent) ? // Check for the new format, ignore entries in the old format revive(raw.agent) : undefined; - request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, request.id, true, raw.isCanceled, raw.vote, raw.providerRequestId, raw.responseErrorDetails, raw.followups); + request.response = new ChatResponseModel(raw.response ?? [new MarkdownString(raw.response)], this, agent, request.id, true, raw.isCanceled, raw.vote, raw.responseErrorDetails, raw.followups); if (raw.usedContext) { // @ulugbekna: if this's a new vscode sessions, doc versions are incorrect anyway? request.response.updateContent(raw.usedContext); } @@ -638,13 +614,6 @@ export class ChatModel extends Disposable implements IChatModel { } this._isInitializedDeferred.complete(); - - if (session.onDidChangeState) { - this._register(session.onDidChangeState(state => { - this._providerState = state; - this.logService.trace('ChatModel#acceptNewSessionState'); - })); - } this._onDidChange.fire({ kind: 'initialize' }); } @@ -703,21 +672,15 @@ export class ChatModel extends Disposable implements IChatModel { if (agent) { request.response.setAgent(agent, progress.command); } - } else { - request.setProviderRequestId(progress.requestId); - request.response.setProviderResponseId(progress.requestId); } } - removeRequest(requestId: string): void { - const index = this._requests.findIndex(request => request.providerRequestId === requestId); + removeRequest(id: string): void { + const index = this._requests.findIndex(request => request.id === id); const request = this._requests[index]; - if (!request.providerRequestId) { - return; - } if (index !== -1) { - this._onDidChange.fire({ kind: 'removeRequest', requestId: request.providerRequestId, responseId: request.response?.providerResponseId }); + this._onDidChange.fire({ kind: 'removeRequest', requestId: request.id, responseId: request.response?.id }); this._requests.splice(index, 1); request.response?.dispose(); } @@ -778,21 +741,19 @@ export class ChatModel extends Disposable implements IChatModel { }), requests: this._requests.map((r): ISerializableChatRequestData => { return { - providerRequestId: r.providerRequestId, message: r.message, response: r.response ? r.response.response.value : undefined, responseErrorDetails: r.response?.errorDetails, followups: r.response?.followups, isCanceled: r.response?.isCanceled, vote: r.response?.vote, - agent: r.response?.agent, + agent: r.response?.agent ? { id: r.response.agent.id, metadata: r.response.agent.metadata } : undefined, // May actually be the full IChatAgent instance, just take the data props slashCommand: r.response?.slashCommand, usedContext: r.response?.response.usedContext, contentReferences: r.response?.response.contentReferences }; }), providerId: this.providerId, - providerState: this._providerState }; } diff --git a/src/vs/workbench/contrib/chat/common/chatRequestParser.ts b/src/vs/workbench/contrib/chat/common/chatRequestParser.ts index da606aac369..4a47172525c 100644 --- a/src/vs/workbench/contrib/chat/common/chatRequestParser.ts +++ b/src/vs/workbench/contrib/chat/common/chatRequestParser.ts @@ -9,7 +9,7 @@ import { IPosition, Position } from 'vs/editor/common/core/position'; import { Range } from 'vs/editor/common/core/range'; import { IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestDynamicReferencePart, ChatRequestSlashCommandPart, ChatRequestTextPart, ChatRequestVariablePart, IParsedChatRequest, IParsedChatRequestPart, chatAgentLeader, chatSubcommandLeader, chatVariableLeader } from 'vs/workbench/contrib/chat/common/chatParserTypes'; -import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService, IDynamicReference } from 'vs/workbench/contrib/chat/common/chatVariables'; const agentReg = /^@([\w_\-]+)(?=(\s|$|\b))/i; // An @-agent @@ -21,7 +21,7 @@ export class ChatRequestParser { constructor( @IChatAgentService private readonly agentService: IChatAgentService, @IChatVariablesService private readonly variableService: IChatVariablesService, - @IChatService private readonly chatService: IChatService, + @IChatSlashCommandService private readonly slashCommandService: IChatSlashCommandService ) { } async parseChatRequest(sessionId: string, message: string): Promise { @@ -174,7 +174,7 @@ export class ChatRequestParser { return new ChatRequestAgentSubcommandPart(slashRange, slashEditorRange, subCommand); } } else { - const slashCommands = await this.chatService.getSlashCommands(sessionId, CancellationToken.None); + const slashCommands = this.slashCommandService.getCommands(); const slashCommand = slashCommands.find(c => c.command === command); if (slashCommand) { // Valid standalone slash command diff --git a/src/vs/workbench/contrib/chat/common/chatService.ts b/src/vs/workbench/contrib/chat/common/chatService.ts index 24d969082d1..8f5d6054790 100644 --- a/src/vs/workbench/contrib/chat/common/chatService.ts +++ b/src/vs/workbench/contrib/chat/common/chatService.ts @@ -23,7 +23,6 @@ export interface IChat { responderUsername: string; responderAvatarIconUri?: URI; inputPlaceholder?: string; - onDidChangeState?: Event; dispose?(): void; } @@ -100,7 +99,6 @@ export interface IChatAgentDetection { export type IChatProgress = | { content: string | IMarkdownString } - | { requestId: string } | { treeData: IChatResponseProgressFileTreeData } | { placeholder: string; resolvedContent: Promise } | IUsedContext @@ -108,18 +106,13 @@ export type IChatProgress = | IChatContentInlineReference | IChatAgentDetection; -export interface IPersistedChatState { } export interface IChatProvider { readonly id: string; readonly displayName: string; readonly iconUrl?: string; - prepareSession(initialState: IPersistedChatState | undefined, token: CancellationToken): ProviderResult; + prepareSession(token: CancellationToken): ProviderResult; provideWelcomeMessage?(token: CancellationToken): ProviderResult<(string | IMarkdownString | IChatReplyFollowup[])[] | undefined>; provideSampleQuestions?(token: CancellationToken): ProviderResult; - provideFollowups?(session: IChat, token: CancellationToken): ProviderResult; - provideReply(request: IChatRequest, progress: (progress: IChatProgress) => void, token: CancellationToken): ProviderResult; - provideSlashCommands?(session: IChat, token: CancellationToken): ProviderResult; - removeRequest?(session: IChat, requestId: string): void; } export interface ISlashCommand { @@ -156,7 +149,6 @@ export interface IChatReplyFollowup { message: string; title?: string; tooltip?: string; - metadata?: any; } export interface IChatResponseCommandFollowup { @@ -177,7 +169,6 @@ export enum InteractiveSessionVoteDirection { export interface IChatVoteAction { kind: 'vote'; - responseId: string; direction: InteractiveSessionVoteDirection; } @@ -189,7 +180,6 @@ export enum InteractiveSessionCopyKind { export interface IChatCopyAction { kind: 'copy'; - responseId: string; codeBlockIndex: number; copyType: InteractiveSessionCopyKind; copiedCharacters: number; @@ -199,7 +189,6 @@ export interface IChatCopyAction { export interface IChatInsertAction { kind: 'insert'; - responseId: string; codeBlockIndex: number; totalCharacters: number; newFile?: boolean; @@ -207,7 +196,6 @@ export interface IChatInsertAction { export interface IChatTerminalAction { kind: 'runInTerminal'; - responseId: string; codeBlockIndex: number; languageId?: string; } @@ -271,7 +259,7 @@ export interface IChatService { _serviceBrand: undefined; transferredSessionData: IChatTransferredSessionData | undefined; - onDidSubmitSlashCommand: Event<{ slashCommand: string; sessionId: string } | { agent: IChatAgentData; slashCommand: IChatAgentCommand; sessionId: string }>; + onDidSubmitAgent: Event<{ agent: IChatAgentData; slashCommand: IChatAgentCommand; sessionId: string }>; onDidRegisterProvider: Event<{ providerId: string }>; registerProvider(provider: IChatProvider): IDisposable; hasSessions(providerId: string): boolean; @@ -285,10 +273,9 @@ export interface IChatService { /** * Returns whether the request was accepted. */ - sendRequest(sessionId: string, message: string, usedSlashCommand?: ISlashCommand): Promise<{ responseCompletePromise: Promise } | undefined>; + sendRequest(sessionId: string, message: string): Promise<{ responseCompletePromise: Promise } | undefined>; removeRequest(sessionid: string, requestId: string): Promise; cancelCurrentRequestForSession(sessionId: string): void; - getSlashCommands(sessionId: string, token: CancellationToken): Promise; clearSession(sessionId: string): void; addCompleteRequest(sessionId: string, message: IParsedChatRequest | string, response: IChatCompleteResponse): void; sendRequestToProvider(sessionId: string, message: IChatDynamicRequest): void; diff --git a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts index 7b2154ffa31..f1c3f8454ee 100644 --- a/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts +++ b/src/vs/workbench/contrib/chat/common/chatServiceImpl.ts @@ -8,7 +8,7 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; import { MarkdownString, isMarkdownString } from 'vs/base/common/htmlContent'; import { Iterable } from 'vs/base/common/iterator'; -import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, DisposableMap, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { revive } from 'vs/base/common/marshalling'; import { StopWatch } from 'vs/base/common/stopwatch'; import { URI, UriComponents } from 'vs/base/common/uri'; @@ -27,8 +27,8 @@ import { ChatModel, ChatModelInitState, ChatRequestModel, ChatWelcomeMessageMode import { ChatRequestAgentPart, ChatRequestAgentSubcommandPart, ChatRequestSlashCommandPart } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { ChatMessageRole, IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; -import { IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatRequest, IChatResponse, IChatService, IChatTransferredSessionData, IChatUserActionEvent, ISlashCommand, InteractiveSessionCopyKind, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; -import { IChatSlashCommandService, IChatSlashFragment } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; +import { IChat, IChatCompleteResponse, IChatDetail, IChatDynamicRequest, IChatFollowup, IChatProgress, IChatProvider, IChatProviderInfo, IChatResponse, IChatService, IChatTransferredSessionData, IChatUserActionEvent, InteractiveSessionCopyKind, InteractiveSessionVoteDirection } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; @@ -134,7 +134,7 @@ export class ChatService extends Disposable implements IChatService { private readonly _providers = new Map(); - private readonly _sessionModels = new Map(); + private readonly _sessionModels = this._register(new DisposableMap()); private readonly _pendingRequests = new Map>(); private readonly _persistedSessions: ISerializableChatsData; private readonly _hasProvider: IContextKey; @@ -147,8 +147,8 @@ export class ChatService extends Disposable implements IChatService { private readonly _onDidPerformUserAction = this._register(new Emitter()); public readonly onDidPerformUserAction: Event = this._onDidPerformUserAction.event; - private readonly _onDidSubmitSlashCommand = this._register(new Emitter<{ slashCommand: string; sessionId: string } | { agent: IChatAgentData; slashCommand: IChatAgentCommand; sessionId: string }>()); - public readonly onDidSubmitSlashCommand = this._onDidSubmitSlashCommand.event; + private readonly _onDidSubmitAgent = this._register(new Emitter<{ agent: IChatAgentData; slashCommand: IChatAgentCommand; sessionId: string }>()); + public readonly onDidSubmitAgent = this._onDidSubmitAgent.event; private readonly _onDidDisposeSession = this._register(new Emitter<{ sessionId: string; providerId: string; reason: 'initializationFailed' | 'cleared' }>()); public readonly onDidDisposeSession = this._onDidDisposeSession.event; @@ -306,6 +306,12 @@ export class ChatService extends Disposable implements IChatService { return transferred; } + /** + * Returns an array of chat details for all persisted chat sessions that have at least one request. + * The array is sorted by creation date in descending order. + * Chat sessions that have already been loaded into the chat view are excluded from the result. + * Imported chat sessions are also excluded from the result. + */ getHistory(): IChatDetail[] { const sessions = Object.values(this._persistedSessions) .filter(session => session.requests.length > 0); @@ -359,7 +365,7 @@ export class ChatService extends Disposable implements IChatService { let session: IChat | undefined; try { - session = await provider.prepareSession(model.providerState, token) ?? undefined; + session = await provider.prepareSession(token) ?? undefined; } catch (err) { this.trace('initializeSession', `Provider initializeSession threw: ${err}`); } @@ -381,8 +387,7 @@ export class ChatService extends Disposable implements IChatService { } catch (err) { this.trace('startSession', `initializeSession failed: ${err}`); model.setInitializationError(err); - model.dispose(); - this._sessionModels.delete(model.sessionId); + this._sessionModels.deleteAndDispose(model.sessionId); this._onDidDisposeSession.fire({ sessionId: model.sessionId, providerId: model.providerId, reason: 'initializationFailed' }); } } @@ -418,7 +423,7 @@ export class ChatService extends Disposable implements IChatService { return this._startSession(data.providerId, data, CancellationToken.None); } - async sendRequest(sessionId: string, request: string, usedSlashCommand?: ISlashCommand): Promise<{ responseCompletePromise: Promise } | undefined> { + async sendRequest(sessionId: string, request: string): Promise<{ responseCompletePromise: Promise } | undefined> { this.trace('sendRequest', `sessionId: ${sessionId}, message: ${request.substring(0, 20)}${request.length > 20 ? '[...]' : ''}}`); if (!request.trim()) { this.trace('sendRequest', 'Rejected empty message'); @@ -442,10 +447,10 @@ export class ChatService extends Disposable implements IChatService { } // This method is only returning whether the request was accepted - don't block on the actual request - return { responseCompletePromise: this._sendRequestAsync(model, sessionId, provider, request, usedSlashCommand) }; + return { responseCompletePromise: this._sendRequestAsync(model, sessionId, provider, request) }; } - private async _sendRequestAsync(model: ChatModel, sessionId: string, provider: IChatProvider, message: string, usedSlashCommand?: ISlashCommand): Promise { + private async _sendRequestAsync(model: ChatModel, sessionId: string, provider: IChatProvider, message: string): Promise { const parsedRequest = await this.instantiationService.createInstance(ChatRequestParser).parseChatRequest(sessionId, message); let request: ChatRequestModel; @@ -480,7 +485,7 @@ export class ChatService extends Disposable implements IChatService { } else if ('agentName' in progress) { this.trace('sendRequest', `Provider returned an agent detection for session ${model.sessionId}:\n ${JSON.stringify(progress, null, '\t')}`); } else { - this.trace('sendRequest', `Provider returned id for session ${model.sessionId}, ${progress.requestId}`); + this.trace('sendRequest', `Provider returned unknown progress for session ${model.sessionId}:\n ${JSON.stringify(progress, null, '\t')}`); } model.acceptResponseProgress(request, progress); @@ -497,7 +502,7 @@ export class ChatService extends Disposable implements IChatService { result: 'cancelled', requestType, agent: agentPart?.agent.id ?? '', - slashCommand: agentSlashCommandPart ? agentSlashCommandPart.command.name : usedSlashCommand?.command, + slashCommand: agentSlashCommandPart ? agentSlashCommandPart.command.name : commandPart?.slashCommand.command, chatSessionId: model.sessionId }); @@ -505,10 +510,8 @@ export class ChatService extends Disposable implements IChatService { }); try { - if (usedSlashCommand?.command) { - this._onDidSubmitSlashCommand.fire({ slashCommand: usedSlashCommand.command, sessionId: model.sessionId }); - } else if (agentPart && agentSlashCommandPart?.command) { - this._onDidSubmitSlashCommand.fire({ agent: agentPart.agent, slashCommand: agentSlashCommandPart.command, sessionId: model.sessionId }); + if (agentPart && agentSlashCommandPart?.command) { + this._onDidSubmitAgent.fire({ agent: agentPart.agent, slashCommand: agentSlashCommandPart.command, sessionId: model.sessionId }); } let rawResponse: IChatResponse | null | undefined; @@ -561,28 +564,14 @@ export class ChatService extends Disposable implements IChatService { history.push({ role: ChatMessageRole.User, content: request.message.text }); history.push({ role: ChatMessageRole.Assistant, content: request.response.response.asString() }); } - const commandResult = await this.chatSlashCommandService.executeCommand(commandPart.slashCommand.command, message.substring(commandPart.slashCommand.command.length + 1).trimStart(), new Progress(p => { - const { content } = p; - const data = isCompleteInteractiveProgressTreeData(content) ? content : { content }; - progressCallback(data); + const commandResult = await this.chatSlashCommandService.executeCommand(commandPart.slashCommand.command, message.substring(commandPart.slashCommand.command.length + 1).trimStart(), new Progress(p => { + progressCallback(p); }), history, token); agentOrCommandFollowups = Promise.resolve(commandResult?.followUp); rawResponse = { session: model.session! }; } else { - request = model.addRequest(parsedRequest); - const requestProps: IChatRequest = { - session: model.session!, - message, - variables: {} - }; - - if ('parts' in parsedRequest) { - const varResult = await this.chatVariablesService.resolveVariables(parsedRequest, model, token); - requestProps.variables = varResult.variables; - requestProps.message = varResult.prompt; - } - rawResponse = await provider.provideReply(requestProps, progressCallback, token); + throw new Error(`Can't handle request`); } if (token.isCancellationRequested) { @@ -604,7 +593,7 @@ export class ChatService extends Disposable implements IChatService { result, requestType, agent: agentPart?.agent.id ?? '', - slashCommand: agentSlashCommandPart ? agentSlashCommandPart.command.name : usedSlashCommand?.command, + slashCommand: agentSlashCommandPart ? agentSlashCommandPart.command.name : commandPart?.slashCommand.command, chatSessionId: model.sessionId }); model.setResponse(request, rawResponse); @@ -616,11 +605,6 @@ export class ChatService extends Disposable implements IChatService { model.setFollowups(request, followups); model.completeResponse(request); }); - } else if (provider.provideFollowups) { - Promise.resolve(provider.provideFollowups(model.session!, CancellationToken.None)).then(providerFollowups => { - model.setFollowups(request, providerFollowups ?? undefined); - model.completeResponse(request); - }); } else { model.completeResponse(request); } @@ -649,43 +633,6 @@ export class ChatService extends Disposable implements IChatService { } model.removeRequest(requestId); - provider.removeRequest?.(model.session!, requestId); - } - - async getSlashCommands(sessionId: string, token: CancellationToken): Promise { - const model = this._sessionModels.get(sessionId); - if (!model) { - throw new Error(`Unknown session: ${sessionId}`); - } - - await model.waitForInitialization(); - const provider = this._providers.get(model.providerId); - if (!provider) { - throw new Error(`Unknown provider: ${model.providerId}`); - } - - const serviceResults = this.chatSlashCommandService.getCommands().map(data => { - return { - command: data.command, - detail: data.detail, - sortText: data.sortText, - executeImmediately: data.executeImmediately - }; - }); - - const mainProviderRequest = provider.provideSlashCommands?.(model.session!, token); - - try { - const providerResults = await mainProviderRequest; - if (providerResults) { - return providerResults.concat(serviceResults); - } - return serviceResults; - - } catch (e) { - this.logService.error(e); - return serviceResults; - } } async sendRequestToProvider(sessionId: string, message: IChatDynamicRequest): Promise<{ responseCompletePromise: Promise } | undefined> { @@ -742,8 +689,7 @@ export class ChatService extends Disposable implements IChatService { this._persistedSessions[sessionId] = model.toJSON(); - model.dispose(); - this._sessionModels.delete(sessionId); + this._sessionModels.deleteAndDispose(sessionId); this._pendingRequests.get(sessionId)?.cancel(); this._onDidDisposeSession.fire({ sessionId, providerId: model.providerId, reason: 'cleared' }); } diff --git a/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts b/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts index e7bbdf2b299..3d758da3113 100644 --- a/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts +++ b/src/vs/workbench/contrib/chat/common/chatSlashCommands.ts @@ -5,11 +5,11 @@ import { CancellationToken } from 'vs/base/common/cancellation'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, IDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; import { createDecorator } from 'vs/platform/instantiation/common/instantiation'; import { IProgress } from 'vs/platform/progress/common/progress'; import { IChatMessage } from 'vs/workbench/contrib/chat/common/chatProvider'; -import { IChatFollowup, IChatResponseProgressFileTreeData } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatFollowup, IChatProgress, IChatResponseProgressFileTreeData } from 'vs/workbench/contrib/chat/common/chatService'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; //#region slash service, commands etc @@ -29,21 +29,18 @@ export interface IChatSlashData { export interface IChatSlashFragment { content: string | { treeData: IChatResponseProgressFileTreeData }; } - -export type IChatSlashCallback = { (prompt: string, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise<{ followUp: IChatFollowup[] } | void> }; +export type IChatSlashCallback = { (prompt: string, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise<{ followUp: IChatFollowup[] } | void> }; export const IChatSlashCommandService = createDecorator('chatSlashCommandService'); /** - * This currently only exists to drive /clear. Delete this when the agent service can handle that scenario + * This currently only exists to drive /clear and /help */ export interface IChatSlashCommandService { _serviceBrand: undefined; readonly onDidChangeCommands: Event; - registerSlashData(data: IChatSlashData): IDisposable; - registerSlashCallback(id: string, command: IChatSlashCallback): IDisposable; registerSlashCommand(data: IChatSlashData, command: IChatSlashCallback): IDisposable; - executeCommand(id: string, prompt: string, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise<{ followUp: IChatFollowup[] } | void>; + executeCommand(id: string, prompt: string, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise<{ followUp: IChatFollowup[] } | void>; getCommands(): Array; hasCommand(id: string): boolean; } @@ -68,11 +65,12 @@ export class ChatSlashCommandService extends Disposable implements IChatSlashCom this._commands.clear(); } - registerSlashData(data: IChatSlashData): IDisposable { + registerSlashCommand(data: IChatSlashData, command: IChatSlashCallback): IDisposable { if (this._commands.has(data.command)) { throw new Error(`Already registered a command with id ${data.command}}`); } - this._commands.set(data.command, { data }); + + this._commands.set(data.command, { data, command }); this._onDidChangeCommands.fire(); return toDisposable(() => { @@ -82,22 +80,6 @@ export class ChatSlashCommandService extends Disposable implements IChatSlashCom }); } - registerSlashCallback(id: string, command: IChatSlashCallback): IDisposable { - const data = this._commands.get(id); - if (!data) { - throw new Error(`No command with id ${id} registered`); - } - data.command = command; - return toDisposable(() => data.command = undefined); - } - - registerSlashCommand(data: IChatSlashData, command: IChatSlashCallback): IDisposable { - return combinedDisposable( - this.registerSlashData(data), - this.registerSlashCallback(data.command, command) - ); - } - getCommands(): Array { return Array.from(this._commands.values(), v => v.data); } @@ -106,7 +88,7 @@ export class ChatSlashCommandService extends Disposable implements IChatSlashCom return this._commands.has(id); } - async executeCommand(id: string, prompt: string, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise<{ followUp: IChatFollowup[] } | void> { + async executeCommand(id: string, prompt: string, progress: IProgress, history: IChatMessage[], token: CancellationToken): Promise<{ followUp: IChatFollowup[] } | void> { const data = this._commands.get(id); if (!data) { throw new Error('No command with id ${id} NOT registered'); diff --git a/src/vs/workbench/contrib/chat/common/chatViewModel.ts b/src/vs/workbench/contrib/chat/common/chatViewModel.ts index d6337756df3..3a64a27a3c8 100644 --- a/src/vs/workbench/contrib/chat/common/chatViewModel.ts +++ b/src/vs/workbench/contrib/chat/common/chatViewModel.ts @@ -55,7 +55,6 @@ export interface IChatViewModel { export interface IChatRequestViewModel { readonly id: string; - readonly providerRequestId: string | undefined; readonly sessionId: string; /** This ID updates every time the underlying data changes */ readonly dataId: string; @@ -88,7 +87,6 @@ export interface IChatResponseViewModel { /** This ID updates every time the underlying data changes */ readonly dataId: string; readonly providerId: string; - readonly providerResponseId: string | undefined; /** The ID of the associated IChatRequestViewModel */ readonly requestId: string; readonly username: string; @@ -173,12 +171,12 @@ export class ChatViewModel extends Disposable implements IChatViewModel { } else if (e.kind === 'addResponse') { this.onAddResponse(e.response); } else if (e.kind === 'removeRequest') { - const requestIdx = this._items.findIndex(item => isRequestVM(item) && item.providerRequestId === e.requestId); + const requestIdx = this._items.findIndex(item => isRequestVM(item) && item.id === e.requestId); if (requestIdx >= 0) { this._items.splice(requestIdx, 1); } - const responseIdx = e.responseId && this._items.findIndex(item => isResponseVM(item) && item.providerResponseId === e.responseId); + const responseIdx = e.responseId && this._items.findIndex(item => isResponseVM(item) && item.id === e.responseId); if (typeof responseIdx === 'number' && responseIdx >= 0) { const items = this._items.splice(responseIdx, 1); const item = items[0]; @@ -218,10 +216,6 @@ export class ChatRequestViewModel implements IChatRequestViewModel { return this._model.id; } - get providerRequestId() { - return this._model.providerRequestId; - } - get dataId() { return this.id + `_${ChatModelInitState[this._model.session.initState]}`; } @@ -269,10 +263,6 @@ export class ChatResponseViewModel extends Disposable implements IChatResponseVi return this._model.providerId; } - get providerResponseId() { - return this._model.providerResponseId; - } - get sessionId() { return this._model.session.sessionId; } diff --git a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts index 6a12e2b7a6f..e82a6afea81 100644 --- a/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts +++ b/src/vs/workbench/contrib/chat/electron-sandbox/actions/voiceChatActions.ts @@ -29,7 +29,6 @@ import { IViewsService } from 'vs/workbench/common/views'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeyCode } from 'vs/base/common/keyCodes'; -import { isExecuteActionContext } from 'vs/workbench/contrib/chat/browser/actions/chatExecuteActions'; import { IWorkbenchLayoutService, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { HasSpeechProvider, ISpeechService, SpeechToTextStatus } from 'vs/workbench/contrib/speech/common/speechService'; import { RunOnceScheduler } from 'vs/base/common/async'; @@ -38,6 +37,10 @@ import { ACTIVITY_BAR_BADGE_BACKGROUND } from 'vs/workbench/common/theme'; import { ColorScheme } from 'vs/platform/theme/common/theme'; import { Color } from 'vs/base/common/color'; import { contrastBorder, focusBorder } from 'vs/platform/theme/common/colorRegistry'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { isNumber } from 'vs/base/common/types'; +import { AccessibilityVoiceSettingId, SpeechTimeoutDefault } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; +import { IChatExecuteActionContext } from 'vs/workbench/contrib/chat/browser/actions/chatExecuteActions'; const CONTEXT_VOICE_CHAT_GETTING_READY = new RawContextKey('voiceChatGettingReady', false, { type: 'boolean', description: localize('voiceChatGettingReady', "True when getting ready for receiving voice input from the microphone for voice chat.") }); const CONTEXT_VOICE_CHAT_IN_PROGRESS = new RawContextKey('voiceChatInProgress', false, { type: 'boolean', description: localize('voiceChatInProgress', "True when voice recording from microphone is in progress for voice chat.") }); @@ -237,7 +240,8 @@ class VoiceChatSessions { constructor( @IContextKeyService private readonly contextKeyService: IContextKeyService, - @ISpeechService private readonly speechService: ISpeechService + @ISpeechService private readonly speechService: ISpeechService, + @IConfigurationService private readonly configurationService: IConfigurationService ) { } async start(controller: IVoiceChatSessionController): Promise { @@ -263,7 +267,13 @@ class VoiceChatSessions { const speechToTextSession = session.disposables.add(this.speechService.createSpeechToTextSession(cts.token)); let inputValue = controller.getInput(); - const acceptTranscriptionScheduler = session.disposables.add(new RunOnceScheduler(() => session.controller.acceptInput(), 1200)); + + let voiceChatTimeout = this.configurationService.getValue(AccessibilityVoiceSettingId.SpeechTimeout); + if (!isNumber(voiceChatTimeout) || voiceChatTimeout < 0) { + voiceChatTimeout = SpeechTimeoutDefault; + } + + const acceptTranscriptionScheduler = session.disposables.add(new RunOnceScheduler(() => session.controller.acceptInput(), voiceChatTimeout)); session.disposables.add(speechToTextSession.onDidChange(({ status, text }) => { if (cts.token.isCancellationRequested) { return; @@ -276,14 +286,18 @@ class VoiceChatSessions { case SpeechToTextStatus.Recognizing: if (text) { session.controller.updateInput([inputValue, text].join(' ')); - acceptTranscriptionScheduler.cancel(); + if (voiceChatTimeout > 0) { + acceptTranscriptionScheduler.cancel(); + } } break; case SpeechToTextStatus.Recognized: if (text) { inputValue = [inputValue, text].join(' '); session.controller.updateInput(inputValue); - acceptTranscriptionScheduler.schedule(); + if (voiceChatTimeout > 0) { + acceptTranscriptionScheduler.schedule(); + } } break; case SpeechToTextStatus.Stopped: @@ -472,7 +486,8 @@ export class StartVoiceChatAction extends Action2 { const instantiationService = accessor.get(IInstantiationService); const commandService = accessor.get(ICommandService); - if (isExecuteActionContext(context)) { + const widget = (context as IChatExecuteActionContext)?.widget; + if (widget) { // if we already get a context when the action is executed // from a toolbar within the chat widget, then make sure // to move focus into the input field so that the controller @@ -480,7 +495,7 @@ export class StartVoiceChatAction extends Action2 { // TODO@bpasero this will actually not work if the button // is clicked from the inline editor while focus is in a // chat input field in a view or picker - context.widget.focusInput(); + widget.focusInput(); } const controller = await VoiceChatSessionControllerFactory.create(accessor, 'focused'); diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap index 3d2c801f677..e5e02c302c8 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_deserialize.0.snap @@ -6,32 +6,53 @@ welcomeMessage: undefined, requests: [ { - providerRequestId: undefined, message: { - text: "test request", + text: "@ChatProviderWithUsedContext test request", parts: [ { range: { start: 0, - endExclusive: 12 + endExclusive: 28 }, editorRange: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, - endColumn: 13 + endColumn: 29 }, - text: "test request", + agent: { + id: "ChatProviderWithUsedContext", + metadata: { }, + provideSlashCommands: [Function provideSlashCommands], + invoke: [Function invoke] + }, + kind: "agent" + }, + { + range: { + start: 28, + endExclusive: 41 + }, + editorRange: { + startLineNumber: 1, + startColumn: 29, + endLineNumber: 1, + endColumn: 42 + }, + text: " test request", kind: "text" } ] }, response: [ ], responseErrorDetails: undefined, - followups: undefined, + followups: [ ], isCanceled: false, vote: undefined, - agent: undefined, + agent: { + id: "ChatProviderWithUsedContext", + metadata: { } + }, slashCommand: undefined, usedContext: { documents: [ { @@ -58,6 +79,5 @@ contentReferences: [ ] } ], - providerId: "ChatProviderWithUsedContext", - providerState: undefined + providerId: "testProvider" } \ No newline at end of file diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.0.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.0.snap index ba62978586c..0939983222f 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.0.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.0.snap @@ -5,6 +5,5 @@ responderAvatarIconUri: undefined, welcomeMessage: undefined, requests: [ ], - providerId: "ChatProviderWithUsedContext", - providerState: undefined + providerId: "testProvider" } \ No newline at end of file diff --git a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap index 140a7b80d27..178b1177890 100644 --- a/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap +++ b/src/vs/workbench/contrib/chat/test/common/__snapshots__/Chat_can_serialize.1.snap @@ -6,32 +6,53 @@ welcomeMessage: undefined, requests: [ { - providerRequestId: undefined, message: { parts: [ { range: { start: 0, - endExclusive: 12 + endExclusive: 28 }, editorRange: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, - endColumn: 13 + endColumn: 29 }, - text: "test request", + agent: { + id: "ChatProviderWithUsedContext", + metadata: { }, + provideSlashCommands: [Function provideSlashCommands], + invoke: [Function invoke] + }, + kind: "agent" + }, + { + range: { + start: 28, + endExclusive: 41 + }, + editorRange: { + startLineNumber: 1, + startColumn: 29, + endLineNumber: 1, + endColumn: 42 + }, + text: " test request", kind: "text" } ], - text: "test request" + text: "@ChatProviderWithUsedContext test request" }, response: [ ], responseErrorDetails: undefined, - followups: undefined, + followups: [ ], isCanceled: false, vote: undefined, - agent: undefined, + agent: { + id: "ChatProviderWithUsedContext", + metadata: { } + }, slashCommand: undefined, usedContext: { documents: [ { @@ -58,6 +79,5 @@ contentReferences: [ ] } ], - providerId: "ChatProviderWithUsedContext", - providerState: undefined + providerId: "testProvider" } \ No newline at end of file diff --git a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts index fdd44be5c58..56d9cf5d323 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatModel.test.ts @@ -6,11 +6,14 @@ import * as assert from 'assert'; import { timeout } from 'vs/base/common/async'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; +import { Range } from 'vs/editor/common/core/range'; +import { OffsetRange } from 'vs/editor/common/core/offsetRange'; import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock'; import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatModel } from 'vs/workbench/contrib/chat/common/chatModel'; +import { ChatRequestTextPart } from 'vs/workbench/contrib/chat/common/chatParserTypes'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; @@ -98,4 +101,18 @@ suite('ChatModel', () => { assert.throws(() => model.initialize({} as any, undefined)); }); + + test('removeRequest', async () => { + const model = testDisposables.add(instantiationService.createInstance(ChatModel, 'provider', undefined)); + + model.startInitialize(); + model.initialize({} as any, undefined); + const text = 'hello'; + model.addRequest({ text, parts: [new ChatRequestTextPart(new OffsetRange(0, text.length), new Range(1, text.length, 1, text.length), text)] }); + const requests = model.getRequests(); + assert.strictEqual(requests.length, 1); + + model.removeRequest(requests[0].id); + assert.strictEqual(model.getRequests().length, 0); + }); }); diff --git a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts index 7619ef46437..cd2aefa23cc 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatRequestParser.test.ts @@ -11,7 +11,7 @@ import { ILogService, NullLogService } from 'vs/platform/log/common/log'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { ChatAgentService, IChatAgent, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { ChatRequestParser } from 'vs/workbench/contrib/chat/common/chatRequestParser'; -import { IChatService } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { TestExtensionService, TestStorageService } from 'vs/workbench/test/common/workbenchTestServices'; @@ -48,9 +48,9 @@ suite('ChatRequestParser', () => { }); test('slash command', async () => { - const chatService = mockObject()({}); - chatService.getSlashCommands.returns(Promise.resolve([{ command: 'fix' }])); - instantiationService.stub(IChatService, chatService as any); + const slashCommandService = mockObject()({}); + slashCommandService.getCommands.returns([{ command: 'fix' }]); + instantiationService.stub(IChatSlashCommandService, slashCommandService as any); parser = instantiationService.createInstance(ChatRequestParser); const text = '/fix this'; @@ -59,9 +59,9 @@ suite('ChatRequestParser', () => { }); test('invalid slash command', async () => { - const chatService = mockObject()({}); - chatService.getSlashCommands.returns(Promise.resolve([{ command: 'fix' }])); - instantiationService.stub(IChatService, chatService as any); + const slashCommandService = mockObject()({}); + slashCommandService.getCommands.returns([{ command: 'fix' }]); + instantiationService.stub(IChatSlashCommandService, slashCommandService as any); parser = instantiationService.createInstance(ChatRequestParser); const text = '/explain this'; @@ -70,9 +70,9 @@ suite('ChatRequestParser', () => { }); test('multiple slash commands', async () => { - const chatService = mockObject()({}); - chatService.getSlashCommands.returns(Promise.resolve([{ command: 'fix' }])); - instantiationService.stub(IChatService, chatService as any); + const slashCommandService = mockObject()({}); + slashCommandService.getCommands.returns([{ command: 'fix' }]); + instantiationService.stub(IChatSlashCommandService, slashCommandService as any); parser = instantiationService.createInstance(ChatRequestParser); const text = '/fix /fix'; diff --git a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts index eebf8589487..e2ae43a90e1 100644 --- a/src/vs/workbench/contrib/chat/test/common/chatService.test.ts +++ b/src/vs/workbench/contrib/chat/test/common/chatService.test.ts @@ -5,7 +5,6 @@ import * as assert from 'assert'; import { CancellationToken } from 'vs/base/common/cancellation'; -import { Emitter } from 'vs/base/common/event'; import { Disposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { assertSnapshot } from 'vs/base/test/common/snapshot'; @@ -22,10 +21,10 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { IViewsService } from 'vs/workbench/common/views'; -import { ChatAgentService, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; +import { ChatAgentService, IChatAgent, IChatAgentService } from 'vs/workbench/contrib/chat/common/chatAgents'; import { IChatContributionService } from 'vs/workbench/contrib/chat/common/chatContributionService'; import { ISerializableChatData } from 'vs/workbench/contrib/chat/common/chatModel'; -import { IChat, IChatProgress, IChatProvider, IChatRequest, IChatResponse, IPersistedChatState, ISlashCommand } from 'vs/workbench/contrib/chat/common/chatService'; +import { IChat, IChatProgress, IChatProvider, IChatRequest } from 'vs/workbench/contrib/chat/common/chatService'; import { ChatService } from 'vs/workbench/contrib/chat/common/chatServiceImpl'; import { ChatSlashCommandService, IChatSlashCommandService } from 'vs/workbench/contrib/chat/common/chatSlashCommands'; import { IChatVariablesService } from 'vs/workbench/contrib/chat/common/chatVariables'; @@ -36,29 +35,18 @@ import { TestContextService, TestExtensionService, TestStorageService } from 'vs class SimpleTestProvider extends Disposable implements IChatProvider { private static sessionId = 0; - lastInitialState = undefined; - readonly displayName = 'Test'; - private _onDidChangeState = this._register(new Emitter()); - constructor(readonly id: string) { super(); } - prepareSession(initialState: any) { - this.lastInitialState = initialState; - return Promise.resolve({ + async prepareSession(): Promise { + return { id: SimpleTestProvider.sessionId++, - username: 'test', responderUsername: 'test', requesterUsername: 'test', - onDidChangeState: this._onDidChangeState.event - }); - } - - changeState(state: any) { - this._onDidChangeState.fire(state); + }; } async provideReply(request: IChatRequest, progress: (progress: IChatProgress) => void): Promise<{ session: IChat; followups: never[] }> { @@ -66,10 +54,14 @@ class SimpleTestProvider extends Disposable implements IChatProvider { } } -/** Chat provider for testing that returns used context */ -class ChatProviderWithUsedContext extends SimpleTestProvider implements IChatProvider { - override provideReply(request: IChatRequest, progress: (progress: IChatProgress) => void): Promise<{ session: IChat; followups: never[] }> { - +const chatAgentWithUsedContextId = 'ChatProviderWithUsedContext'; +const chatAgentWithUsedContext: IChatAgent = { + id: chatAgentWithUsedContextId, + metadata: {}, + async provideSlashCommands(token) { + return []; + }, + async invoke(request, progress, history, token) { progress({ documents: [ { @@ -82,9 +74,9 @@ class ChatProviderWithUsedContext extends SimpleTestProvider implements IChatPro ] }); - return super.provideReply(request, progress); - } -} + return {}; + }, +}; suite('Chat', () => { const testDisposables = ensureNoDisposablesAreLeakedInTestSuite(); @@ -92,6 +84,8 @@ suite('Chat', () => { let storageService: IStorageService; let instantiationService: TestInstantiationService; + let chatAgentService: IChatAgentService; + setup(async () => { instantiationService = testDisposables.add(new TestInstantiationService(new ServiceCollection( [IChatVariablesService, new MockChatVariablesService()], @@ -105,7 +99,18 @@ suite('Chat', () => { instantiationService.stub(IChatContributionService, new TestExtensionService()); instantiationService.stub(IWorkspaceContextService, new TestContextService()); instantiationService.stub(IChatSlashCommandService, testDisposables.add(instantiationService.createInstance(ChatSlashCommandService))); - instantiationService.stub(IChatAgentService, testDisposables.add(instantiationService.createInstance(ChatAgentService))); + + chatAgentService = testDisposables.add(instantiationService.createInstance(ChatAgentService)); + instantiationService.stub(IChatAgentService, chatAgentService); + + const agent = { + id: 'testAgent', + metadata: { isDefault: true }, + async invoke(request, progress, history, token) { + return {}; + }, + } as IChatAgent; + testDisposables.add(chatAgentService.registerAgent(agent)); }); test('retrieveSession', async () => { @@ -123,12 +128,7 @@ suite('Chat', () => { await session2.waitForInitialization(); session2!.addRequest({ parts: [], text: 'request 2' }); - assert.strictEqual(provider1.lastInitialState, undefined); - assert.strictEqual(provider2.lastInitialState, undefined); - provider1.changeState({ state: 'provider1_state' }); - provider2.changeState({ state: 'provider2_state' }); storageService.flush(); - const testService2 = testDisposables.add(instantiationService.createInstance(ChatService)); testDisposables.add(testService2.registerProvider(provider1)); testDisposables.add(testService2.registerProvider(provider2)); @@ -136,8 +136,8 @@ suite('Chat', () => { await retrieved1!.waitForInitialization(); const retrieved2 = testDisposables.add(testService2.getOrRestoreSession(session2.sessionId)!); await retrieved2!.waitForInitialization(); - assert.deepStrictEqual(provider1.lastInitialState, { state: 'provider1_state' }); - assert.deepStrictEqual(provider2.lastInitialState, { state: 'provider2_state' }); + assert.deepStrictEqual(retrieved1.getRequests()[0]?.message.text, 'request 1'); + assert.deepStrictEqual(retrieved2.getRequests()[0]?.message.text, 'request 2'); }); test('Handles failed session startup', async () => { @@ -172,10 +172,7 @@ suite('Chat', () => { testDisposables.add(testService.registerProvider({ id, displayName: 'Test', - prepareSession: function (initialState: IPersistedChatState | undefined, token: CancellationToken): ProviderResult { - throw new Error('Function not implemented.'); - }, - provideReply: function (request: IChatRequest, progress: (progress: IChatProgress) => void, token: CancellationToken): ProviderResult { + prepareSession: function (token: CancellationToken): ProviderResult { throw new Error('Function not implemented.'); } })); @@ -184,45 +181,13 @@ suite('Chat', () => { testDisposables.add(testService.registerProvider({ id, displayName: 'Test', - prepareSession: function (initialState: IPersistedChatState | undefined, token: CancellationToken): ProviderResult { - throw new Error('Function not implemented.'); - }, - provideReply: function (request: IChatRequest, progress: (progress: IChatProgress) => void, token: CancellationToken): ProviderResult { + prepareSession: function (token: CancellationToken): ProviderResult { throw new Error('Function not implemented.'); } })); }, 'Expected to throw for dupe provider'); }); - test('getSlashCommands', async () => { - const testService = testDisposables.add(instantiationService.createInstance(ChatService)); - const provider = testDisposables.add(new class extends SimpleTestProvider { - constructor() { - super('testProvider'); - } - - provideSlashCommands(): ProviderResult { - return [ - { - command: 'command', - detail: 'detail', - sortText: 'sortText', - } - ]; - } - }); - - testDisposables.add(testService.registerProvider(provider)); - - const model = testDisposables.add(testService.startSession('testProvider', CancellationToken.None)); - const commands = await testService.getSlashCommands(model.sessionId, CancellationToken.None); - - assert.strictEqual(commands?.length, 1); - assert.strictEqual(commands?.[0].command, 'command'); - assert.strictEqual(commands?.[0].detail, 'detail'); - assert.strictEqual(commands?.[0].sortText, 'sortText'); - }); - test('sendRequestToProvider', async () => { const testService = testDisposables.add(instantiationService.createInstance(ChatService)); testDisposables.add(testService.registerProvider(testDisposables.add(new SimpleTestProvider('testProvider')))); @@ -249,18 +214,16 @@ suite('Chat', () => { }); test('can serialize', async () => { - + testDisposables.add(chatAgentService.registerAgent(chatAgentWithUsedContext)); const testService = testDisposables.add(instantiationService.createInstance(ChatService)); - const providerId = 'ChatProviderWithUsedContext'; + testDisposables.add(testService.registerProvider(testDisposables.add(new SimpleTestProvider('testProvider')))); - testDisposables.add(testService.registerProvider(testDisposables.add(new ChatProviderWithUsedContext(providerId)))); - - const model = testDisposables.add(testService.startSession(providerId, CancellationToken.None)); + const model = testDisposables.add(testService.startSession('testProvider', CancellationToken.None)); assert.strictEqual(model.getRequests().length, 0); await assertSnapshot(model.toExport()); - const response = await testService.sendRequest(model.sessionId, 'test request'); + const response = await testService.sendRequest(model.sessionId, `@${chatAgentWithUsedContextId} test request`); assert(response); await response.responseCompletePromise; @@ -271,21 +234,18 @@ suite('Chat', () => { }); test('can deserialize', async () => { - let serializedChatData: ISerializableChatData; - - const providerId = 'ChatProviderWithUsedContext'; + testDisposables.add(chatAgentService.registerAgent(chatAgentWithUsedContext)); // create the first service, send request, get response, and serialize the state { // serapate block to not leak variables in outer scope const testService = testDisposables.add(instantiationService.createInstance(ChatService)); + testDisposables.add(testService.registerProvider(testDisposables.add(new SimpleTestProvider('testProvider')))); - testDisposables.add(testService.registerProvider(testDisposables.add(new ChatProviderWithUsedContext(providerId)))); - - const chatModel1 = testDisposables.add(testService.startSession(providerId, CancellationToken.None)); + const chatModel1 = testDisposables.add(testService.startSession('testProvider', CancellationToken.None)); assert.strictEqual(chatModel1.getRequests().length, 0); - const response = await testService.sendRequest(chatModel1.sessionId, 'test request'); + const response = await testService.sendRequest(chatModel1.sessionId, `@${chatAgentWithUsedContextId} test request`); assert(response); await response.responseCompletePromise; @@ -296,17 +256,11 @@ suite('Chat', () => { // try deserializing the state into a new service const testService2 = testDisposables.add(instantiationService.createInstance(ChatService)); - - testDisposables.add(testService2.registerProvider(testDisposables.add(new ChatProviderWithUsedContext(providerId)))); + testDisposables.add(testService2.registerProvider(testDisposables.add(new SimpleTestProvider('testProvider')))); const chatModel2 = testService2.loadSessionFromContent(serializedChatData); assert(chatModel2); - // should `loadSessionFromContent` return `ChatModel` that's disposable instead of `IChatModel`? - testDisposables.add({ - dispose: () => testService2.clearSession(serializedChatData.sessionId) - }); - await assertSnapshot(chatModel2.toExport()); }); }); diff --git a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts index e5331b3b34d..666ea74dd6a 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/diffEditorHelper.ts @@ -43,7 +43,7 @@ class DiffEditorHelperContribution extends Disposable implements IDiffEditorCont const isEmbeddedDiffEditor = this._diffEditor instanceof EmbeddedDiffEditorWidget; if (!isEmbeddedDiffEditor) { - const computationResult = observableFromEvent(e => this._diffEditor.onDidUpdateDiff(e), () => this._diffEditor.getDiffComputationResult()); + const computationResult = observableFromEvent(e => this._diffEditor.onDidUpdateDiff(e), () => /** @description diffEditor.diffComputationResult */ this._diffEditor.getDiffComputationResult()); const onlyWhiteSpaceChange = computationResult.map(r => r && !r.identical && r.changes2.length === 0); this._register(autorunWithStore((reader, store) => { diff --git a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts index 98342adaf6d..666faa5f6aa 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget.ts @@ -289,6 +289,10 @@ export abstract class SimpleFindWidget extends Widget implements IVerticalSashLa return this._domNode; } + public getFindInputDomNode() { + return this._findInput.domNode; + } + public reveal(initialInput?: string, animated = true): void { if (initialInput) { this._findInput.setValue(initialInput); diff --git a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts index 3e7bdeec9c0..e088be4a01b 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/outline/documentSymbolsTree.ts @@ -19,11 +19,11 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur import { MarkerSeverity } from 'vs/platform/markers/common/markers'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { listErrorForeground, listWarningForeground } from 'vs/platform/theme/common/colorRegistry'; -import { IdleValue } from 'vs/base/common/async'; import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfiguration'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IOutlineComparator, OutlineConfigKeys } from 'vs/workbench/services/outline/browser/outline'; import { ThemeIcon } from 'vs/base/common/themables'; +import { mainWindow } from 'vs/base/browser/window'; export type DocumentSymbolItem = OutlineGroup | OutlineElement; @@ -251,7 +251,7 @@ export class DocumentSymbolFilter implements ITreeFilter { export class DocumentSymbolComparator implements IOutlineComparator { - private readonly _collator = new IdleValue(() => new Intl.Collator(undefined, { numeric: true })); + private readonly _collator = new dom.WindowIdleValue(mainWindow, () => new Intl.Collator(undefined, { numeric: true })); compareByPosition(a: DocumentSymbolItem, b: DocumentSymbolItem): number { if (a instanceof OutlineGroup && b instanceof OutlineGroup) { diff --git a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts index 2593f25e9ac..6c498870ddd 100644 --- a/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts +++ b/src/vs/workbench/contrib/codeEditor/browser/toggleWordWrap.ts @@ -23,6 +23,7 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle import { IEditorService } from 'vs/workbench/services/editor/common/editorService'; import { Event } from 'vs/base/common/event'; import { addDisposableListener, onDidRegisterWindow } from 'vs/base/browser/dom'; +import { mainWindow } from 'vs/base/browser/window'; const transientWordWrapState = 'transientWordWrapState'; const isWordWrapMinifiedKey = 'isWordWrapMinified'; @@ -273,7 +274,7 @@ class EditorWordWrapContextKeyTracker extends Disposable implements IWorkbenchCo this._register(Event.runAndSubscribe(onDidRegisterWindow, ({ window, disposables }) => { disposables.add(addDisposableListener(window, 'focus', () => this._update(), true)); disposables.add(addDisposableListener(window, 'blur', () => this._update(), true)); - }, { window, disposables: this._store })); + }, { window: mainWindow, disposables: this._store })); this._editorService.onDidActiveEditorChange(() => this._update()); this._canToggleWordWrap = CAN_TOGGLE_WORD_WRAP.bindTo(this._contextService); this._editorWordWrap = EDITOR_WORD_WRAP.bindTo(this._contextService); diff --git a/src/vs/workbench/contrib/codeEditor/electron-sandbox/inputClipboardActions.ts b/src/vs/workbench/contrib/codeEditor/electron-sandbox/inputClipboardActions.ts index 5eec3dce8a7..e1469be3129 100644 --- a/src/vs/workbench/contrib/codeEditor/electron-sandbox/inputClipboardActions.ts +++ b/src/vs/workbench/contrib/codeEditor/electron-sandbox/inputClipboardActions.ts @@ -6,6 +6,7 @@ import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry'; import * as platform from 'vs/base/common/platform'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; +import { getActiveWindow } from 'vs/base/browser/dom'; if (platform.isMacintosh) { @@ -37,7 +38,7 @@ if (platform.isMacintosh) { function bindExecuteCommand(command: 'cut' | 'copy' | 'paste') { return () => { - document.execCommand(command); + getActiveWindow().document.execCommand(command); }; } } diff --git a/src/vs/workbench/contrib/codeEditor/electron-sandbox/selectionClipboard.ts b/src/vs/workbench/contrib/codeEditor/electron-sandbox/selectionClipboard.ts index eadd523ded5..025c038bfe4 100644 --- a/src/vs/workbench/contrib/codeEditor/electron-sandbox/selectionClipboard.ts +++ b/src/vs/workbench/contrib/codeEditor/electron-sandbox/selectionClipboard.ts @@ -21,6 +21,9 @@ import { Registry } from 'vs/platform/registry/common/platform'; import { Extensions as WorkbenchExtensions, IWorkbenchContribution, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { EditorContextKeys } from 'vs/editor/common/editorContextKeys'; +import { mainWindow } from 'vs/base/browser/window'; +import { Event } from 'vs/base/common/event'; +import { addDisposableListener, onDidRegisterWindow } from 'vs/base/browser/dom'; export class SelectionClipboard extends Disposable implements IEditorContribution { private static readonly SELECTION_LENGTH_LIMIT = 65536; @@ -89,22 +92,26 @@ export class SelectionClipboard extends Disposable implements IEditorContributio } } -class SelectionClipboardPastePreventer implements IWorkbenchContribution { +class SelectionClipboardPastePreventer extends Disposable implements IWorkbenchContribution { constructor( @IConfigurationService configurationService: IConfigurationService ) { + super(); + if (platform.isLinux) { - document.addEventListener('mouseup', (e) => { - if (e.button === 1) { - // middle button - const config = configurationService.getValue<{ selectionClipboard: boolean }>('editor'); - if (!config.selectionClipboard) { - // selection clipboard is disabled - // try to stop the upcoming paste - e.preventDefault(); + this._register(Event.runAndSubscribe(onDidRegisterWindow, ({ window, disposables }) => { + disposables.add(addDisposableListener(window.document, 'mouseup', e => { + if (e.button === 1) { + // middle button + const config = configurationService.getValue<{ selectionClipboard: boolean }>('editor'); + if (!config.selectionClipboard) { + // selection clipboard is disabled + // try to stop the upcoming paste + e.preventDefault(); + } } - } - }); + })); + }, { window: mainWindow, disposables: this._store })); } } } diff --git a/src/vs/workbench/contrib/comments/browser/commentColors.ts b/src/vs/workbench/contrib/comments/browser/commentColors.ts index a300a745a56..4f91ef6dda7 100644 --- a/src/vs/workbench/contrib/comments/browser/commentColors.ts +++ b/src/vs/workbench/contrib/comments/browser/commentColors.ts @@ -5,6 +5,7 @@ import { Color } from 'vs/base/common/color'; import * as languages from 'vs/editor/common/languages'; +import { peekViewTitleBackground } from 'vs/editor/contrib/peekView/browser/peekView'; import * as nls from 'vs/nls'; import { contrastBorder, disabledForeground, listFocusOutline, registerColor, transparent } from 'vs/platform/theme/common/colorRegistry'; import { IColorTheme } from 'vs/platform/theme/common/themeService'; @@ -12,6 +13,7 @@ import { IColorTheme } from 'vs/platform/theme/common/themeService'; const resolvedCommentViewIcon = registerColor('commentsView.resolvedIcon', { dark: disabledForeground, light: disabledForeground, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('resolvedCommentIcon', 'Icon color for resolved comments.')); const unresolvedCommentViewIcon = registerColor('commentsView.unresolvedIcon', { dark: listFocusOutline, light: listFocusOutline, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('unresolvedCommentIcon', 'Icon color for unresolved comments.')); +registerColor('editorCommentsWidget.replyButtonBackground', { dark: peekViewTitleBackground, light: peekViewTitleBackground, hcDark: peekViewTitleBackground, hcLight: peekViewTitleBackground }, nls.localize('commentReplyButtonBackground', 'Background color for comment reply button.')); const resolvedCommentBorder = registerColor('editorCommentsWidget.resolvedBorder', { dark: resolvedCommentViewIcon, light: resolvedCommentViewIcon, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('resolvedCommentBorder', 'Color of borders and arrow for resolved comments.')); const unresolvedCommentBorder = registerColor('editorCommentsWidget.unresolvedBorder', { dark: unresolvedCommentViewIcon, light: unresolvedCommentViewIcon, hcDark: contrastBorder, hcLight: contrastBorder }, nls.localize('unresolvedCommentBorder', 'Color of borders and arrow for unresolved comments.')); export const commentThreadRangeBackground = registerColor('editorCommentsWidget.rangeBackground', { dark: transparent(unresolvedCommentBorder, .1), light: transparent(unresolvedCommentBorder, .1), hcDark: transparent(unresolvedCommentBorder, .1), hcLight: transparent(unresolvedCommentBorder, .1) }, nls.localize('commentThreadRangeBackground', 'Color of background for comment ranges.')); diff --git a/src/vs/workbench/contrib/comments/browser/commentNode.ts b/src/vs/workbench/contrib/comments/browser/commentNode.ts index 65ad313fff7..a62d62d1c86 100644 --- a/src/vs/workbench/contrib/comments/browser/commentNode.ts +++ b/src/vs/workbench/contrib/comments/browser/commentNode.ts @@ -170,7 +170,7 @@ export class CommentNode extends Disposable { this._scrollable = new Scrollable({ forceIntegerValues: true, smoothScrollDuration: 125, - scheduleAtNextAnimationFrame: cb => dom.scheduleAtNextAnimationFrame(cb, dom.getWindow(container)) + scheduleAtNextAnimationFrame: cb => dom.scheduleAtNextAnimationFrame(dom.getWindow(container), cb) }); this._scrollableElement = this._register(new SmoothScrollableElement(body, { horizontal: ScrollbarVisibility.Visible, @@ -486,10 +486,10 @@ export class CommentNode extends Disposable { this._commentEditor.layout({ width: container.clientWidth - 14, height: this._editorHeight }); this._commentEditor.focus(); - dom.scheduleAtNextAnimationFrame(() => { + dom.scheduleAtNextAnimationFrame(dom.getWindow(editContainer), () => { this._commentEditor!.layout({ width: container.clientWidth - 14, height: this._editorHeight }); this._commentEditor!.focus(); - }, dom.getWindow(editContainer)); + }); const lastLine = this._commentEditorModel.getLineCount(); const lastColumn = this._commentEditorModel.getLineLength(lastLine) + 1; @@ -729,7 +729,7 @@ export class CommentNode extends Disposable { private onContextMenu(e: MouseEvent) { - const event = new StandardMouseEvent(e); + const event = new StandardMouseEvent(dom.getWindow(this._domNode), e); this.contextMenuService.showContextMenu({ getAnchor: () => event, diff --git a/src/vs/workbench/contrib/comments/browser/commentThreadHeader.ts b/src/vs/workbench/contrib/comments/browser/commentThreadHeader.ts index f83748a565d..b206d26b011 100644 --- a/src/vs/workbench/contrib/comments/browser/commentThreadHeader.ts +++ b/src/vs/workbench/contrib/comments/browser/commentThreadHeader.ts @@ -117,7 +117,7 @@ export class CommentThreadHeader extends Disposable { if (!actions.length) { return; } - const event = new StandardMouseEvent(e); + const event = new StandardMouseEvent(dom.getWindow(this._headElement), e); this._contextMenuService.showContextMenu({ getAnchor: () => event, getActions: () => actions, diff --git a/src/vs/workbench/contrib/comments/browser/media/review.css b/src/vs/workbench/contrib/comments/browser/media/review.css index 1cf8bdb2c2b..9b4610b66eb 100644 --- a/src/vs/workbench/contrib/comments/browser/media/review.css +++ b/src/vs/workbench/contrib/comments/browser/media/review.css @@ -338,7 +338,7 @@ white-space: nowrap; border: 0px; outline: 1px solid transparent; - background-color: var(--vscode-peekViewTitle-background); + background-color: var(--vscode-editorCommentsWidget-replyButtonBackground); color: var(--vscode-editor-foreground); font-size: inherit; font-family: var(--monaco-monospace-font); diff --git a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts index 4a412b0e3df..3e154e4df71 100644 --- a/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts +++ b/src/vs/workbench/contrib/debug/browser/breakpointEditorContribution.ts @@ -726,7 +726,7 @@ class InlineBreakpointWidget implements IContentWidget, IDisposable { } })); this.toDispose.push(dom.addDisposableListener(this.domNode, dom.EventType.CONTEXT_MENU, e => { - const event = new StandardMouseEvent(e); + const event = new StandardMouseEvent(dom.getWindow(this.domNode), e); const actions = this.getContextMenuActions(); this.contextMenuService.showContextMenu({ getAnchor: () => event, diff --git a/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts b/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts index 55314e58a47..423316207be 100644 --- a/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts +++ b/src/vs/workbench/contrib/debug/browser/debugAdapterManager.ts @@ -341,9 +341,10 @@ export class AdapterManager extends Disposable implements IAdapterManager { // Or if a breakpoint can be set in the current file (good hint that an extension can handle it) if ((!languageLabel || gettingConfigurations || (model && this.canSetBreakpointsIn(model))) && candidates.length === 0) { await this.activateDebuggers('onDebugInitialConfigurations'); + candidates = this.debuggers .filter(a => a.enabled) - .filter(dbg => dbg.hasInitialConfiguration() || dbg.hasConfigurationProvider()); + .filter(dbg => dbg.hasInitialConfiguration() || dbg.hasDynamicConfigurationProviders() || dbg.hasConfigurationProvider()); } if (candidates.length === 0 && languageLabel) { diff --git a/src/vs/workbench/contrib/debug/browser/debugSession.ts b/src/vs/workbench/contrib/debug/browser/debugSession.ts index 2c8712b26c4..8e4c0b250d0 100644 --- a/src/vs/workbench/contrib/debug/browser/debugSession.ts +++ b/src/vs/workbench/contrib/debug/browser/debugSession.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { mainWindow } from 'vs/base/browser/window'; import * as aria from 'vs/base/browser/ui/aria/aria'; import { distinct } from 'vs/base/common/arrays'; import { Queue, RunOnceScheduler } from 'vs/base/common/async'; @@ -1018,7 +1019,7 @@ export class DebugSession implements IDebugSession, IDisposable { } if (this.configurationService.getValue('debug').focusWindowOnBreak && !this.workbenchEnvironmentService.extensionTestsLocationURI) { - await this.hostService.focus(window, { force: true /* Application may not be active */ }); + await this.hostService.focus(mainWindow, { force: true /* Application may not be active */ }); } } } diff --git a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts index 397f5414af1..caa3161d223 100644 --- a/src/vs/workbench/contrib/debug/browser/debugToolBar.ts +++ b/src/vs/workbench/contrib/debug/browser/debugToolBar.ts @@ -38,6 +38,7 @@ import * as icons from 'vs/workbench/contrib/debug/browser/debugIcons'; import { CONTEXT_DEBUG_STATE, CONTEXT_FOCUSED_SESSION_IS_ATTACH, CONTEXT_FOCUSED_SESSION_IS_NO_DEBUG, CONTEXT_IN_DEBUG_MODE, CONTEXT_MULTI_SESSION_DEBUG, CONTEXT_STEP_BACK_SUPPORTED, CONTEXT_SUSPEND_DEBUGGEE_SUPPORTED, CONTEXT_TERMINATE_DEBUGGEE_SUPPORTED, IDebugConfiguration, IDebugService, State, VIEWLET_ID } from 'vs/workbench/contrib/debug/common/debug'; import { EditorTabsMode, IWorkbenchLayoutService, LayoutSettings, Parts } from 'vs/workbench/services/layout/browser/layoutService'; import { Codicon } from 'vs/base/common/codicons'; +import { mainWindow } from 'vs/base/browser/window'; const DEBUG_TOOLBAR_POSITION_KEY = 'debug.actionswidgetposition'; const DEBUG_TOOLBAR_Y_KEY = 'debug.actionswidgety'; @@ -147,14 +148,14 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { // log in telemetry this.telemetryService.publicLog2('workbenchActionExecuted', { id: e.action.id, from: 'debugActionsWidget' }); })); - this._register(dom.addDisposableListener(window, dom.EventType.RESIZE, () => this.setCoordinates())); + this._register(dom.addDisposableListener(mainWindow, dom.EventType.RESIZE, () => this.setCoordinates())); this._register(dom.addDisposableGenericMouseUpListener(this.dragArea, (event: MouseEvent) => { - const mouseClickEvent = new StandardMouseEvent(event); + const mouseClickEvent = new StandardMouseEvent(dom.getWindow(this.dragArea), event); if (mouseClickEvent.detail === 2) { // double click on debug bar centers it again #8250 const widgetWidth = this.$el.clientWidth; - this.setCoordinates(0.5 * window.innerWidth - 0.5 * widgetWidth, 0); + this.setCoordinates(0.5 * mainWindow.innerWidth - 0.5 * widgetWidth, 0); this.storePosition(); } })); @@ -162,15 +163,15 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { this._register(dom.addDisposableGenericMouseDownListener(this.dragArea, (event: MouseEvent) => { this.dragArea.classList.add('dragged'); - const mouseMoveListener = dom.addDisposableGenericMouseMoveListener(window, (e: MouseEvent) => { - const mouseMoveEvent = new StandardMouseEvent(e); + const mouseMoveListener = dom.addDisposableGenericMouseMoveListener(mainWindow, (e: MouseEvent) => { + const mouseMoveEvent = new StandardMouseEvent(mainWindow, e); // Prevent default to stop editor selecting text #8524 mouseMoveEvent.preventDefault(); // Reduce x by width of drag handle to reduce jarring #16604 this.setCoordinates(mouseMoveEvent.posx - 14, mouseMoveEvent.posy - 14); }); - const mouseUpListener = dom.addDisposableGenericMouseUpListener(window, (e: MouseEvent) => { + const mouseUpListener = dom.addDisposableGenericMouseUpListener(mainWindow, (e: MouseEvent) => { this.storePosition(); this.dragArea.classList.remove('dragged'); @@ -186,7 +187,7 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { private storePosition(): void { const left = dom.getComputedStyle(this.$el).left; if (left) { - const position = parseFloat(left) / window.innerWidth; + const position = parseFloat(left) / mainWindow.innerWidth; this.storageService.store(DEBUG_TOOLBAR_POSITION_KEY, position, StorageScope.PROFILE, StorageTarget.MACHINE); } if (this.yCoordinate) { @@ -224,10 +225,10 @@ export class DebugToolBar extends Themable implements IWorkbenchContribution { const widgetWidth = this.$el.clientWidth; if (x === undefined) { const positionPercentage = this.storageService.get(DEBUG_TOOLBAR_POSITION_KEY, StorageScope.PROFILE); - x = positionPercentage !== undefined ? parseFloat(positionPercentage) * window.innerWidth : (0.5 * window.innerWidth - 0.5 * widgetWidth); + x = positionPercentage !== undefined ? parseFloat(positionPercentage) * mainWindow.innerWidth : (0.5 * mainWindow.innerWidth - 0.5 * widgetWidth); } - x = Math.max(0, Math.min(x, window.innerWidth - widgetWidth)); // do not allow the widget to overflow on the right + x = Math.max(0, Math.min(x, mainWindow.innerWidth - widgetWidth)); // do not allow the widget to overflow on the right this.$el.style.left = `${x}px`; if (y === undefined) { diff --git a/src/vs/workbench/contrib/debug/common/debug.ts b/src/vs/workbench/contrib/debug/common/debug.ts index 6664f7925b2..514f28b5638 100644 --- a/src/vs/workbench/contrib/debug/common/debug.ts +++ b/src/vs/workbench/contrib/debug/common/debug.ts @@ -905,7 +905,7 @@ export interface IConfigurationManager { */ onDidSelectConfiguration: Event; - hasDebugConfigurationProvider(debugType: string): boolean; + hasDebugConfigurationProvider(debugType: string, triggerKind?: DebugConfigurationProviderTriggerKind): boolean; getDynamicProviders(): Promise<{ label: string; type: string; pick: () => Promise<{ launch: ILaunch; config: IConfig } | undefined> }[]>; registerDebugConfigurationProvider(debugConfigurationProvider: IDebugConfigurationProvider): IDisposable; diff --git a/src/vs/workbench/contrib/debug/common/debugger.ts b/src/vs/workbench/contrib/debug/common/debugger.ts index e18a400fe91..da2de14c249 100644 --- a/src/vs/workbench/contrib/debug/common/debugger.ts +++ b/src/vs/workbench/contrib/debug/common/debugger.ts @@ -7,7 +7,7 @@ import * as nls from 'vs/nls'; import { isObject } from 'vs/base/common/types'; import { IJSONSchema, IJSONSchemaMap, IJSONSchemaSnippet } from 'vs/base/common/jsonSchema'; import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; -import { IConfig, IDebuggerContribution, IDebugAdapter, IDebugger, IDebugSession, IAdapterManager, IDebugService, debuggerDisabledMessage, IDebuggerMetadata } from 'vs/workbench/contrib/debug/common/debug'; +import { IConfig, IDebuggerContribution, IDebugAdapter, IDebugger, IDebugSession, IAdapterManager, IDebugService, debuggerDisabledMessage, IDebuggerMetadata, DebugConfigurationProviderTriggerKind } from 'vs/workbench/contrib/debug/common/debug'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationResolverService } from 'vs/workbench/services/configurationResolver/common/configurationResolver'; import * as ConfigurationResolverUtils from 'vs/workbench/services/configurationResolver/common/configurationResolverUtils'; @@ -176,6 +176,10 @@ export class Debugger implements IDebugger, IDebuggerMetadata { return !!this.debuggerContribution.initialConfigurations; } + hasDynamicConfigurationProviders(): boolean { + return this.debugService.getConfigurationManager().hasDebugConfigurationProvider(this.type, DebugConfigurationProviderTriggerKind.Dynamic); + } + hasConfigurationProvider(): boolean { return this.debugService.getConfigurationManager().hasDebugConfigurationProvider(this.type); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts index 3b7f3a69780..db5a46e3525 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensions.contribution.ts @@ -13,7 +13,7 @@ import { EnablementState, IExtensionManagementServerService, IWorkbenchExtension import { IExtensionIgnoredRecommendationsService, IExtensionRecommendationsService } from 'vs/workbench/services/extensionRecommendations/common/extensionRecommendations'; import { IWorkbenchContributionsRegistry, Extensions as WorkbenchExtensions, IWorkbenchContribution } from 'vs/workbench/common/contributions'; import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; -import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionsViewPaneContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, WORKSPACE_RECOMMENDATIONS_VIEW_ID, IWorkspaceRecommendedExtensionsView, AutoUpdateConfigurationKey, HasOutdatedExtensionsContext, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID, ExtensionEditorTab, THEME_ACTIONS_GROUP, INSTALL_ACTIONS_GROUP, OUTDATED_EXTENSIONS_VIEW_ID, CONTEXT_HAS_GALLERY, IExtension } from 'vs/workbench/contrib/extensions/common/extensions'; +import { VIEWLET_ID, IExtensionsWorkbenchService, IExtensionsViewPaneContainer, TOGGLE_IGNORE_EXTENSION_ACTION_ID, INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, WORKSPACE_RECOMMENDATIONS_VIEW_ID, IWorkspaceRecommendedExtensionsView, AutoUpdateConfigurationKey, HasOutdatedExtensionsContext, SELECT_INSTALL_VSIX_EXTENSION_COMMAND_ID, LIST_WORKSPACE_UNSUPPORTED_EXTENSIONS_COMMAND_ID, ExtensionEditorTab, THEME_ACTIONS_GROUP, INSTALL_ACTIONS_GROUP, OUTDATED_EXTENSIONS_VIEW_ID, CONTEXT_HAS_GALLERY, IExtension, extensionsSearchActionsMenu } from 'vs/workbench/contrib/extensions/common/extensions'; import { ReinstallAction, InstallSpecificVersionOfExtensionAction, ConfigureWorkspaceRecommendedExtensionsAction, ConfigureWorkspaceFolderRecommendedExtensionsAction, PromptExtensionInstallFailureAction, SearchExtensionsAction, SwitchToPreReleaseVersionAction, SwitchToReleasedVersionAction, SetColorThemeAction, SetFileIconThemeAction, SetProductIconThemeAction, ClearLanguageAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { ExtensionsInput } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { ExtensionEditor } from 'vs/workbench/contrib/extensions/browser/extensionEditor'; @@ -885,12 +885,11 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi }); const extensionsFilterSubMenu = new MenuId('extensionsFilterSubMenu'); - MenuRegistry.appendMenuItem(MenuId.ViewContainerTitle, { + MenuRegistry.appendMenuItem(extensionsSearchActionsMenu, { submenu: extensionsFilterSubMenu, title: localize('filterExtensions', "Filter Extensions..."), - when: ContextKeyExpr.equals('viewContainer', VIEWLET_ID), group: 'navigation', - order: 1, + order: 2, icon: filterIcon, }); @@ -1128,10 +1127,9 @@ class ExtensionsContributions extends Disposable implements IWorkbenchContributi f1: true, precondition: SearchHasTextContext, menu: { - id: MenuId.ViewContainerTitle, - when: ContextKeyExpr.equals('viewContainer', VIEWLET_ID), + id: extensionsSearchActionsMenu, group: 'navigation', - order: 3, + order: 1, }, run: async (accessor: ServicesAccessor) => { const viewPaneContainer = accessor.get(IViewsService).getActiveViewPaneContainerWithId(VIEWLET_ID); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts index b9627230820..33683a48388 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewer.ts @@ -73,7 +73,7 @@ export class ExtensionsGridView extends Disposable { e.preventDefault(); }; - this.disposableStore.add(dom.addDisposableListener(template.name, dom.EventType.CLICK, (e: MouseEvent) => handleEvent(new StandardMouseEvent(e)))); + this.disposableStore.add(dom.addDisposableListener(template.name, dom.EventType.CLICK, (e: MouseEvent) => handleEvent(new StandardMouseEvent(dom.getWindow(template.name), e)))); this.disposableStore.add(dom.addDisposableListener(template.name, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => handleEvent(new StandardKeyboardEvent(e)))); this.disposableStore.add(dom.addDisposableListener(extensionContainer, dom.EventType.KEY_DOWN, (e: KeyboardEvent) => handleEvent(new StandardKeyboardEvent(e)))); diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts index 7957a1598ec..05469dbf48a 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsViewlet.ts @@ -16,7 +16,7 @@ import { append, $, Dimension, hide, show, DragAndDropObserver, trackFocus } fro import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; -import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, CloseExtensionDetailsOnViewChangeKey, INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, WORKSPACE_RECOMMENDATIONS_VIEW_ID, AutoCheckUpdatesConfigurationKey, OUTDATED_EXTENSIONS_VIEW_ID, CONTEXT_HAS_GALLERY } from '../common/extensions'; +import { IExtensionsWorkbenchService, IExtensionsViewPaneContainer, VIEWLET_ID, CloseExtensionDetailsOnViewChangeKey, INSTALL_EXTENSION_FROM_VSIX_COMMAND_ID, WORKSPACE_RECOMMENDATIONS_VIEW_ID, AutoCheckUpdatesConfigurationKey, OUTDATED_EXTENSIONS_VIEW_ID, CONTEXT_HAS_GALLERY, extensionsSearchActionsMenu } from '../common/extensions'; import { InstallLocalExtensionsInRemoteAction, InstallRemoteExtensionsInLocalAction } from 'vs/workbench/contrib/extensions/browser/extensionsActions'; import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement'; import { IWorkbenchExtensionEnablementService, IExtensionManagementServerService, IExtensionManagementServer } from 'vs/workbench/services/extensionManagement/common/extensionManagement'; @@ -62,6 +62,8 @@ import { extname } from 'vs/base/common/resources'; import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil'; import { ILocalizedString } from 'vs/platform/action/common/action'; import { registerNavigableContainer } from 'vs/workbench/browser/actions/widgetNavigationCommands'; +import { MenuWorkbenchToolBar } from 'vs/platform/actions/browser/toolbar'; +import { createActionViewItem } from 'vs/platform/actions/browser/menuEntryActionViewItem'; export const DefaultViewsContext = new RawContextKey('defaultExtensionViews', true); export const ExtensionsSortByContext = new RawContextKey('extensionsSortByValue', ''); @@ -554,7 +556,9 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE const searchValue = this.searchViewletState['query.value'] ? this.searchViewletState['query.value'] : ''; - this.searchBox = this._register(this.instantiationService.createInstance(SuggestEnabledInput, `${VIEWLET_ID}.searchbox`, header, { + const searchContainer = append(header, $('.extensions-search-container')); + + this.searchBox = this._register(this.instantiationService.createInstance(SuggestEnabledInput, `${VIEWLET_ID}.searchbox`, searchContainer, { triggerCharacters: ['@'], sortKey: (item: string) => { if (item.indexOf(':') === -1) { return 'a'; } @@ -571,15 +575,22 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE } this._register(this.searchBox.onInputDidChange(() => { - this.sortByContextKey.set(Query.parse(this.searchBox!.getValue() || '').sortBy); + this.sortByContextKey.set(Query.parse(this.searchBox?.getValue() ?? '').sortBy); this.triggerSearch(); }, this)); this._register(this.searchBox.onShouldFocusResults(() => this.focusListView(), this)); + const controlElement = append(searchContainer, $('.extensions-search-actions-container')); + this._register(this.instantiationService.createInstance(MenuWorkbenchToolBar, controlElement, extensionsSearchActionsMenu, { + toolbarOptions: { + primaryGroup: () => true, + }, + actionViewItemProvider: action => createActionViewItem(this.instantiationService, action) + })); + // Register DragAndDrop support this._register(new DragAndDropObserver(this.root, { - onDragEnd: (e: DragEvent) => undefined, onDragEnter: (e: DragEvent) => { if (this.isSupportedDragElement(e)) { show(overlay); @@ -644,7 +655,7 @@ export class ExtensionsViewPaneContainer extends ViewPaneContainer implements IE this.root.classList.toggle('narrow', dimension.width <= 250); this.root.classList.toggle('mini', dimension.width <= 200); } - this.searchBox?.layout(new Dimension(dimension.width - 34 - /*padding*/8, 20)); + this.searchBox?.layout(new Dimension(dimension.width - 34 - /*padding*/8 - (24 * 2), 20)); super.layout(new Dimension(dimension.width, dimension.height - 41)); } diff --git a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts index 55c422a9310..f7fe57a1760 100644 --- a/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts +++ b/src/vs/workbench/contrib/extensions/browser/extensionsWorkbenchService.ts @@ -51,6 +51,7 @@ import { ILocaleService } from 'vs/workbench/services/localization/common/locale import { TelemetryTrustedValue } from 'vs/platform/telemetry/common/telemetryUtils'; import { ILifecycleService, LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { mainWindow } from 'vs/base/browser/window'; interface IExtensionStateProvider { (extension: Extension): T; @@ -1928,7 +1929,7 @@ export class ExtensionsWorkbenchService extends Disposable implements IExtension [extension] = await this.getExtensions([{ id: extensionId }], { source: 'uri' }, CancellationToken.None); } if (extension) { - await this.hostService.focus(window); + await this.hostService.focus(mainWindow); await this.open(extension); } }).then(undefined, error => this.onError(error)); diff --git a/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css b/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css index 47e19378fa0..552e9cf54c2 100644 --- a/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css +++ b/src/vs/workbench/contrib/extensions/browser/media/extensionsViewlet.css @@ -23,7 +23,11 @@ padding: 5px 12px 6px 20px; } -.extensions-viewlet > .header > .search-box { +.extensions-viewlet > .header > .extensions-search-container { + position: relative; +} + +.extensions-viewlet > .header > .extensions-search-container > .search-box { width: 100%; height: 28px; line-height: 18px; @@ -34,6 +38,16 @@ -moz-appearance: textfield; } +.extensions-viewlet > .header > .extensions-search-container > .extensions-search-actions-container { + display: flex; + align-items: center; + position: absolute; + top: 0; + right: 0; + height: 100%; +} + + .extensions-viewlet > .extensions { height: calc(100% - 41px); } diff --git a/src/vs/workbench/contrib/extensions/common/extensions.ts b/src/vs/workbench/contrib/extensions/common/extensions.ts index 5582a39fea9..3b77ce18a1e 100644 --- a/src/vs/workbench/contrib/extensions/common/extensions.ts +++ b/src/vs/workbench/contrib/extensions/common/extensions.ts @@ -18,6 +18,7 @@ import { RawContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IExtensionsStatus } from 'vs/workbench/services/extensions/common/extensions'; import { IExtensionEditorOptions } from 'vs/workbench/contrib/extensions/common/extensionsInput'; import { ProgressLocation } from 'vs/platform/progress/common/progress'; +import { MenuId } from 'vs/platform/actions/common/actions'; export const VIEWLET_ID = 'workbench.view.extensions'; @@ -199,3 +200,5 @@ export const CONTEXT_HAS_GALLERY = new RawContextKey('hasGallery', fals // Context Menu Groups export const THEME_ACTIONS_GROUP = '_theme_'; export const INSTALL_ACTIONS_GROUP = '0_install'; + +export const extensionsSearchActionsMenu = new MenuId('extensionsSearchActionsMenu'); diff --git a/src/vs/workbench/contrib/extensions/electron-sandbox/extensionProfileService.ts b/src/vs/workbench/contrib/extensions/electron-sandbox/extensionProfileService.ts index 032712be224..bd147b70b7e 100644 --- a/src/vs/workbench/contrib/extensions/electron-sandbox/extensionProfileService.ts +++ b/src/vs/workbench/contrib/extensions/electron-sandbox/extensionProfileService.ts @@ -3,9 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { disposableWindowInterval } from 'vs/base/browser/dom'; +import { mainWindow } from 'vs/base/browser/window'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Emitter, Event } from 'vs/base/common/event'; -import { Disposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { randomPort } from 'vs/base/common/ports'; import * as nls from 'vs/nls'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; @@ -92,10 +94,10 @@ export class ExtensionHostProfileService extends Disposable implements IExtensio }; const timeStarted = Date.now(); - const handle = setInterval(() => { + const handle = disposableWindowInterval(mainWindow, () => { this.profilingStatusBarIndicator?.update({ ...indicator, text: nls.localize('profilingExtensionHostTime', "Profiling Extension Host ({0} sec)", Math.round((new Date().getTime() - timeStarted) / 1000)), }); }, 1000); - this.profilingStatusBarIndicatorLabelUpdater.value = toDisposable(() => clearInterval(handle)); + this.profilingStatusBarIndicatorLabelUpdater.value = handle; if (!this.profilingStatusBarIndicator) { this.profilingStatusBarIndicator = this._statusbarService.addEntry(indicator, 'status.profiler', StatusbarAlignment.RIGHT); diff --git a/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts b/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts index d6c151043f0..0a0cbcc764d 100644 --- a/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts +++ b/src/vs/workbench/contrib/files/browser/editors/fileEditorInput.ts @@ -93,7 +93,7 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements preferredContents: string | undefined, @IInstantiationService private readonly instantiationService: IInstantiationService, @ITextFileService textFileService: ITextFileService, - @ITextModelService private readonly textModelResolverService: ITextModelService, + @ITextModelService private readonly textModelService: ITextModelService, @ILabelService labelService: ILabelService, @IFileService fileService: IFileService, @IFilesConfigurationService filesConfigurationService: IFilesConfigurationService, @@ -348,7 +348,7 @@ export class FileEditorInput extends AbstractTextResourceEditorInput implements // resolve() ensures we are not creating model references for these kind of resources. // In addition we have a bit of payload to take into account (encoding, reload) that the text resolver does not handle yet. if (!this.cachedTextFileModelReference) { - this.cachedTextFileModelReference = await this.textModelResolverService.createModelReference(this.resource) as IReference; + this.cachedTextFileModelReference = await this.textModelService.createModelReference(this.resource) as IReference; } const model = this.cachedTextFileModelReference.object; diff --git a/src/vs/workbench/contrib/files/browser/fileImportExport.ts b/src/vs/workbench/contrib/files/browser/fileImportExport.ts index 10a1453fd05..86f4f228580 100644 --- a/src/vs/workbench/contrib/files/browser/fileImportExport.ts +++ b/src/vs/workbench/contrib/files/browser/fileImportExport.ts @@ -23,7 +23,7 @@ import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace import { extractEditorsAndFilesDropData } from 'vs/platform/dnd/browser/dnd'; import { IWorkspaceEditingService } from 'vs/workbench/services/workspaces/common/workspaceEditing'; import { isWeb } from 'vs/base/common/platform'; -import { isDragEvent, triggerDownload } from 'vs/base/browser/dom'; +import { getActiveWindow, isDragEvent, triggerDownload } from 'vs/base/browser/dom'; import { ILogService } from 'vs/platform/log/common/log'; import { FileAccess, Schemas } from 'vs/base/common/network'; import { mnemonicButtonLabel } from 'vs/base/common/labels'; @@ -654,9 +654,10 @@ export class FileDownload { const preferFileSystemAccessWebApis = stat.isDirectory || stat.size > maxBlobDownloadSize; // Folder: use FS APIs to download files and folders if available and preferred - if (preferFileSystemAccessWebApis && WebFileSystemAccess.supported(window)) { + const activeWindow = getActiveWindow(); + if (preferFileSystemAccessWebApis && WebFileSystemAccess.supported(activeWindow)) { try { - const parentFolder: FileSystemDirectoryHandle = await window.showDirectoryPicker(); + const parentFolder: FileSystemDirectoryHandle = await activeWindow.showDirectoryPicker(); const operation: IDownloadOperation = { startTime: Date.now(), progressScheduler: new RunOnceWorker(steps => { progress.report(steps[steps.length - 1]); }, 1000), diff --git a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts index 4ab203190e3..d42faed523e 100644 --- a/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts +++ b/src/vs/workbench/contrib/files/browser/views/explorerViewer.ts @@ -66,6 +66,7 @@ import { IHoverDelegate, IHoverDelegateOptions, IHoverWidget } from 'vs/base/bro import { IHoverService } from 'vs/workbench/services/hover/browser/hover'; import { HoverPosition } from 'vs/base/browser/ui/hover/hoverWidget'; import { IFilesConfigurationService } from 'vs/workbench/services/filesConfiguration/common/filesConfigurationService'; +import { mainWindow } from 'vs/base/browser/window'; export class ExplorerDelegate implements IListVirtualDelegate { @@ -1262,9 +1263,9 @@ export class FileDragAndDrop implements ITreeDragAndDrop { // External file DND (Import/Upload file) if (data instanceof NativeDragAndDropData) { // Use local file import when supported - if (!isWeb || (isTemporaryWorkspace(this.contextService.getWorkspace()) && WebFileSystemAccess.supported(window))) { + if (!isWeb || (isTemporaryWorkspace(this.contextService.getWorkspace()) && WebFileSystemAccess.supported(mainWindow))) { const fileImport = this.instantiationService.createInstance(ExternalFileImport); - await fileImport.import(resolvedTarget, originalEvent, window); + await fileImport.import(resolvedTarget, originalEvent, mainWindow); } // Otherwise fallback to browser based file upload else { @@ -1464,7 +1465,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop { } private static getCompressedStatFromDragEvent(stat: ExplorerItem, dragEvent: DragEvent): ExplorerItem { - const target = document.elementFromPoint(dragEvent.clientX, dragEvent.clientY); + const target = DOM.getWindow(dragEvent).document.elementFromPoint(dragEvent.clientX, dragEvent.clientY); const iconLabelName = getIconLabelNameFromHTMLElement(target); if (iconLabelName) { diff --git a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts index d08e8d92b43..0494ecabb75 100644 --- a/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts +++ b/src/vs/workbench/contrib/files/browser/views/openEditorsView.ts @@ -52,6 +52,7 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { Schemas } from 'vs/base/common/network'; import { extUriIgnorePathCase } from 'vs/base/common/resources'; import { ILocalizedString } from 'vs/platform/action/common/action'; +import { mainWindow } from 'vs/base/browser/window'; const $ = dom.$; @@ -733,7 +734,7 @@ class OpenEditorsDragAndDrop implements IListDragAndDrop group, () => group.focus(), { index }); + this.dropHandler.handleDrop(originalEvent, mainWindow, () => group, () => group.focus(), { index }); } } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css index 356105ef6ad..5abc0995871 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChat.css @@ -331,14 +331,23 @@ /* gutter decoration */ -.monaco-editor .glyph-margin-widgets .cgmr.codicon-inline-chat { +.monaco-editor .glyph-margin-widgets .cgmr.codicon-inline-chat-opaque, +.monaco-editor .glyph-margin-widgets .cgmr.codicon-inline-chat-transparent { display: block; cursor: pointer; - opacity: 0.5; transition: opacity .2s ease-in-out; } -.monaco-editor .glyph-margin-widgets .cgmr.codicon-inline-chat:hover { +.monaco-editor .glyph-margin-widgets .cgmr.codicon-inline-chat-opaque { + opacity: 0.5; +} + +.monaco-editor .glyph-margin-widgets .cgmr.codicon-inline-chat-transparent { + opacity: 0; +} + +.monaco-editor .glyph-margin-widgets .cgmr.codicon-inline-chat-opaque:hover, +.monaco-editor .glyph-margin-widgets .cgmr.codicon-inline-chat-transparent:hover { opacity: 1; } diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts index 71f0d80893f..645b908b2dc 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatController.ts @@ -677,11 +677,10 @@ export class InlineChatController implements IEditorContribution { response = new EmptyResponse(); a11yResponse = localize('empty', "No results, please refine your input and try again"); } else { - - const replyResponse = response = new ReplyResponse(reply, markdownContents, this._activeSession.textModelN.uri, modelAltVersionIdNow, progressEdits); - if (reply.type === InlineChatResponseType.Message) { + if (reply.message) { markdownContents.appendMarkdown(reply.message.value); } + const replyResponse = response = new ReplyResponse(reply, markdownContents, this._activeSession.textModelN.uri, modelAltVersionIdNow, progressEdits); for (let i = progressEdits.length; i < replyResponse.allLocalEdits.length; i++) { await this._makeChanges(replyResponse.allLocalEdits[i], undefined); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatDecorations.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatDecorations.ts index b518fdf294b..bb11b789857 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatDecorations.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatDecorations.ts @@ -16,7 +16,7 @@ import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/co import { DisposableStore, Disposable } from 'vs/base/common/lifecycle'; import { GutterActionsRegistry } from 'vs/workbench/contrib/codeEditor/browser/editorLineNumberMenu'; import { Action } from 'vs/base/common/actions'; -import { IInlineChatService } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; +import { IInlineChatService, ShowGutterIcon } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { RunOnceScheduler } from 'vs/base/common/async'; import { Iterable } from 'vs/base/common/iterator'; import { Range } from 'vs/editor/common/core/range'; @@ -24,18 +24,23 @@ import { IInlineChatSessionService } from 'vs/workbench/contrib/inlineChat/brows import { MarkdownString } from 'vs/base/common/htmlContent'; import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { LOCALIZED_START_INLINE_CHAT_STRING } from 'vs/workbench/contrib/inlineChat/browser/inlineChatActions'; +import { IDebugService } from 'vs/workbench/contrib/debug/common/debug'; +import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; -const GUTTER_INLINE_CHAT_ICON = registerIcon('inline-chat', Codicon.sparkle, localize('startInlineChatIcon', 'Icon which spawns the inline chat from the gutter')); +const GUTTER_INLINE_CHAT_OPAQUE_ICON = registerIcon('inline-chat-opaque', Codicon.sparkle, localize('startInlineChatOpaqueIcon', 'Icon which spawns the inline chat from the gutter. It is half opaque by default and becomes completely opaque on hover.')); +const GUTTER_INLINE_CHAT_TRANSPARENT_ICON = registerIcon('inline-chat-transparent', Codicon.sparkle, localize('startInlineChatTransparentIcon', 'Icon which spawns the inline chat from the gutter. It is transparent by default and becomes opaque on hover.')); export class InlineChatDecorationsContribution extends Disposable implements IEditorContribution { private _gutterDecorationID: string | undefined; private _inlineChatKeybinding: string | undefined; private readonly _localToDispose = new DisposableStore(); - private readonly _gutterDecoration: IModelDecorationOptions; + private readonly _gutterDecorationOpaque: IModelDecorationOptions; + private readonly _gutterDecorationTransparent: IModelDecorationOptions; public static readonly GUTTER_SETTING_ID = 'inlineChat.showGutterIcon'; - private static readonly GUTTER_ICON_CLASSNAME = 'codicon-inline-chat'; + private static readonly GUTTER_ICON_OPAQUE_CLASSNAME = 'codicon-inline-chat-opaque'; + private static readonly GUTTER_ICON_TRANSPARENT_CLASSNAME = 'codicon-inline-chat-transparent'; constructor( private readonly _editor: ICodeEditor, @@ -43,14 +48,11 @@ export class InlineChatDecorationsContribution extends Disposable implements IEd @IInlineChatSessionService private readonly _inlineChatSessionService: IInlineChatSessionService, @IConfigurationService private readonly _configurationService: IConfigurationService, @IKeybindingService private readonly _keybindingService: IKeybindingService, + @IDebugService private readonly _debugService: IDebugService ) { super(); - this._gutterDecoration = ModelDecorationOptions.register({ - description: 'inline-chat-decoration', - glyphMarginClassName: ThemeIcon.asClassName(GUTTER_INLINE_CHAT_ICON), - glyphMargin: { position: GlyphMarginLane.Left }, - stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, - }); + this._gutterDecorationTransparent = this._registerGutterDecoration(true); + this._gutterDecorationOpaque = this._registerGutterDecoration(false); this._register(this._configurationService.onDidChangeConfiguration((e: IConfigurationChangeEvent) => { if (!e.affectsConfiguration(InlineChatDecorationsContribution.GUTTER_SETTING_ID)) { return; @@ -59,6 +61,7 @@ export class InlineChatDecorationsContribution extends Disposable implements IEd })); this._register(this._inlineChatService.onDidChangeProviders(() => this._onEnablementOrModelChanged())); this._register(this._editor.onDidChangeModel(() => this._onEnablementOrModelChanged())); + this._register(this._debugService.getModel().onDidChangeBreakpoints(() => this._onEnablementOrModelChanged())); this._register(this._keybindingService.onDidUpdateKeybindings(() => { this._updateDecorationHover(); this._onEnablementOrModelChanged(); @@ -67,19 +70,30 @@ export class InlineChatDecorationsContribution extends Disposable implements IEd this._onEnablementOrModelChanged(); } + private _registerGutterDecoration(isTransparent: boolean): ModelDecorationOptions { + return ModelDecorationOptions.register({ + description: 'inline-chat-decoration', + glyphMarginClassName: ThemeIcon.asClassName(isTransparent ? GUTTER_INLINE_CHAT_TRANSPARENT_ICON : GUTTER_INLINE_CHAT_OPAQUE_ICON), + glyphMargin: { position: GlyphMarginLane.Left }, + stickiness: TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges, + }); + } + private _updateDecorationHover(): void { const keybinding = this._keybindingService.lookupKeybinding('inlineChat.start')?.getLabel() ?? undefined; if (this._inlineChatKeybinding === keybinding) { return; } this._inlineChatKeybinding = keybinding; - this._gutterDecoration.glyphMarginHoverMessage = new MarkdownString(keybinding ? localize('runWithKeybinding', 'Start Inline Chat [{0}]', keybinding) : LOCALIZED_START_INLINE_CHAT_STRING); + const hoverMessage = new MarkdownString(keybinding ? localize('runWithKeybinding', 'Start Inline Chat [{0}]', keybinding) : LOCALIZED_START_INLINE_CHAT_STRING); + this._gutterDecorationTransparent.glyphMarginHoverMessage = hoverMessage; + this._gutterDecorationOpaque.glyphMarginHoverMessage = hoverMessage; } private _onEnablementOrModelChanged(): void { // cancels the scheduler, removes editor listeners / removes decoration this._localToDispose.clear(); - if (!this._editor.hasModel() || !this._isSettingEnabled() || !this._hasProvider()) { + if (!this._editor.hasModel() || this._showGutterIconMode() === ShowGutterIcon.Never || !this._hasProvider()) { return; } const editor = this._editor; @@ -91,7 +105,12 @@ export class InlineChatDecorationsContribution extends Disposable implements IEd this._localToDispose.add(this._inlineChatSessionService.onWillStartSession(onInlineChatSessionChanged)); this._localToDispose.add(this._inlineChatSessionService.onDidEndSession(onInlineChatSessionChanged)); this._localToDispose.add(this._editor.onMouseDown(async (e: IEditorMouseEvent) => { - if (!e.target.element?.classList.contains(InlineChatDecorationsContribution.GUTTER_ICON_CLASSNAME)) { + const showGutterIconMode = this._showGutterIconMode(); + const gutterDecorationClassName = showGutterIconMode === ShowGutterIcon.Always ? + InlineChatDecorationsContribution.GUTTER_ICON_OPAQUE_CLASSNAME : + (showGutterIconMode === ShowGutterIcon.MouseOver ? + InlineChatDecorationsContribution.GUTTER_ICON_TRANSPARENT_CLASSNAME : undefined); + if (!gutterDecorationClassName || !e.target.element?.classList.contains(gutterDecorationClassName)) { return; } InlineChatController.get(this._editor)?.run(); @@ -108,15 +127,19 @@ export class InlineChatDecorationsContribution extends Disposable implements IEd private _onSelectionOrContentChanged(editor: IActiveCodeEditor): void { const selection = editor.getSelection(); - const isInlineChatVisible = this._inlineChatSessionService.getSession(editor, editor.getModel().uri); - const isEnabled = selection.isEmpty() && /^\s*$/g.test(editor.getModel().getLineContent(selection.startLineNumber)) && !isInlineChatVisible; + const model = editor.getModel(); + const uri = model.uri; + const isInlineChatVisible = this._inlineChatSessionService.getSession(editor, uri); + const startLineNumber = selection.startLineNumber; + const hasBreakpoint = this._debugService.getModel().getBreakpoints({ uri: uri, lineNumber: startLineNumber }).length > 0; + const isEnabled = selection.isEmpty() && /^\s*$/g.test(model.getLineContent(startLineNumber)) && !isInlineChatVisible && !hasBreakpoint; if (isEnabled) { if (this._gutterDecorationID === undefined) { - this._addGutterDecoration(selection.startLineNumber); + this._addGutterDecoration(startLineNumber); } else { - const decorationRange = editor.getModel().getDecorationRange(this._gutterDecorationID); - if (decorationRange?.startLineNumber !== selection.startLineNumber) { - this._updateGutterDecoration(this._gutterDecorationID, selection.startLineNumber); + const decorationRange = model.getDecorationRange(this._gutterDecorationID); + if (decorationRange?.startLineNumber !== startLineNumber) { + this._updateGutterDecoration(this._gutterDecorationID, startLineNumber); } } } else if (this._gutterDecorationID) { @@ -124,8 +147,8 @@ export class InlineChatDecorationsContribution extends Disposable implements IEd } } - private _isSettingEnabled(): boolean { - return this._configurationService.getValue(InlineChatDecorationsContribution.GUTTER_SETTING_ID); + private _showGutterIconMode(): ShowGutterIcon { + return this._configurationService.getValue(InlineChatDecorationsContribution.GUTTER_SETTING_ID); } private _hasProvider(): boolean { @@ -134,7 +157,11 @@ export class InlineChatDecorationsContribution extends Disposable implements IEd private _addGutterDecoration(lineNumber: number) { this._editor.changeDecorations((accessor: IModelDecorationsChangeAccessor) => { - this._gutterDecorationID = accessor.addDecoration(new Range(lineNumber, 0, lineNumber, 0), this._gutterDecoration); + const showGutterIconMode = this._showGutterIconMode(); + if (showGutterIconMode === ShowGutterIcon.Never) { + return; + } + this._gutterDecorationID = accessor.addDecoration(new Range(lineNumber, 0, lineNumber, 0), showGutterIconMode === ShowGutterIcon.Always ? this._gutterDecorationOpaque : this._gutterDecorationTransparent); }); } @@ -163,12 +190,12 @@ GutterActionsRegistry.registerGutterActionsGenerator(({ lineNumber, editor, acce if (noProviders) { return; } - const configurationService = accessor.get(IConfigurationService); + const preferencesService = accessor.get(IPreferencesService); result.push(new Action( - 'inlineChat.toggleShowGutterIcon', - localize('toggleShowGutterIcon', "Toggle Inline Chat Icon"), + 'inlineChat.configureShowGutterIcon', + localize('configureShowGutterIcon', "Configure Inline Chat Icon"), undefined, true, - () => { configurationService.updateValue(InlineChatDecorationsContribution.GUTTER_SETTING_ID, !configurationService.getValue(InlineChatDecorationsContribution.GUTTER_SETTING_ID)); } + () => { preferencesService.openUserSettings({ query: 'inlineChat.showGutterIcon' }); } )); }); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts index 31bf2d50717..a4eb23d1431 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatLivePreviewWidget.ts @@ -3,10 +3,10 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Dimension, h } from 'vs/base/browser/dom'; +import { Dimension, getWindow, h, runAtThisOrScheduleAtNextAnimationFrame } from 'vs/base/browser/dom'; import { MutableDisposable } from 'vs/base/common/lifecycle'; import { assertType } from 'vs/base/common/types'; -import { ICodeEditor, IDiffEditor } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor } from 'vs/editor/browser/editorBrowser'; import { EmbeddedCodeEditorWidget, EmbeddedDiffEditorWidget } from 'vs/editor/browser/widget/embeddedCodeEditorWidget'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { Range } from 'vs/editor/common/core/range'; @@ -35,6 +35,7 @@ import { ILanguageService } from 'vs/editor/common/languages/language'; import { FoldingController } from 'vs/editor/contrib/folding/browser/folding'; import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility'; import { generateUuid } from 'vs/base/common/uuid'; +import { DiffEditorWidget } from 'vs/editor/browser/widget/diffEditor/diffEditorWidget'; export class InlineChatLivePreviewWidget extends ZoneWidget { @@ -43,7 +44,7 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { private readonly _elements = h('div.inline-chat-diff-widget@domNode'); private readonly _decorationCollection: IEditorDecorationsCollection; - private readonly _diffEditor: IDiffEditor; + private readonly _diffEditor: DiffEditorWidget; private _dim: Dimension | undefined; private _isVisible: boolean = false; @@ -99,6 +100,19 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { if (onDidChangeDiff) { this._disposables.add(this._diffEditor.onDidUpdateDiff(() => { onDidChangeDiff(); })); + + const render = this._disposables.add(new MutableDisposable()); + this._disposables.add(this._diffEditor.onDidContentSizeChange(e => { + if (!this._isVisible || !e.contentHeightChanged) { + return; + } + render.value = runAtThisOrScheduleAtNextAnimationFrame(getWindow(this._diffEditor.getContainerDomNode()), () => { + const lineHeight = this.editor.getOption(EditorOption.lineHeight); + const heightInLines = e.contentHeight / lineHeight; + this._logService.debug(`[IE] relaying with ${heightInLines} lines height`); + this._relayout(heightInLines); + }); + })); } @@ -211,7 +225,7 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { const heightInLines = Math.max(lineCountModified, lineCountOriginal); super.show(ranges.anchor, heightInLines); - this._logService.debug(`[IE] diff SHOWING at ${ranges.anchor} with ${heightInLines} lines height`); + this._logService.debug(`[IE] diff SHOWING at ${ranges.anchor} with ${heightInLines} (approx) lines height`); } private _cleanupFullDiff() { @@ -219,6 +233,7 @@ export class InlineChatLivePreviewWidget extends ZoneWidget { this._diffEditor.getOriginalEditor().setHiddenAreas([], this._hideId); this._diffEditor.getModifiedEditor().setHiddenAreas([], this._hideId); super.hide(); + this._isVisible = false; } private _computeHiddenRanges(model: ITextModel, changes: readonly LineRangeMapping[]) { diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts index 41271d9c977..8c2bddd6259 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatStrategies.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { disposableWindowInterval } from 'vs/base/browser/dom'; +import { $window } from 'vs/base/browser/window'; import { equals, tail } from 'vs/base/common/arrays'; import { AsyncIterableObject, AsyncIterableSource } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; @@ -421,7 +423,6 @@ export class LivePreviewStrategy extends LiveStrategy { if (!diff || diff.changes.length === 0) { for (const zone of this._diffZonePool) { zone.hide(); - zone.dispose(); } return; } @@ -583,13 +584,13 @@ export function asProgressiveEdit(edit: IIdentifiedSingleEditOperation, wordsPer let newText = edit.text ?? ''; // const wordCount = countWords(newText); - const handle = setInterval(() => { + const handle = disposableWindowInterval($window, () => { const r = getNWords(newText, 1); stream.emitOne(r.value); newText = newText.substring(r.value.length); if (r.isFullString) { - clearInterval(handle); + handle.dispose(); stream.resolve(); d.dispose(); } @@ -598,7 +599,7 @@ export function asProgressiveEdit(edit: IIdentifiedSingleEditOperation, wordsPer // cancel ASAP const d = token.onCancellationRequested(() => { - clearTimeout(handle); + handle.dispose(); stream.resolve(); d.dispose(); }); diff --git a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts index e33a5ea2193..b4fc93c757d 100644 --- a/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts +++ b/src/vs/workbench/contrib/inlineChat/browser/inlineChatWidget.ts @@ -14,7 +14,7 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti import { ZoneWidget } from 'vs/editor/contrib/zoneWidget/browser/zoneWidget'; import { CTX_INLINE_CHAT_FOCUSED, CTX_INLINE_CHAT_EMPTY, CTX_INLINE_CHAT_OUTER_CURSOR_POSITION, CTX_INLINE_CHAT_VISIBLE, MENU_INLINE_CHAT_WIDGET, MENU_INLINE_CHAT_WIDGET_STATUS, MENU_INLINE_CHAT_WIDGET_MARKDOWN_MESSAGE, CTX_INLINE_CHAT_MESSAGE_CROP_STATE, IInlineChatSlashCommand, MENU_INLINE_CHAT_WIDGET_FEEDBACK, ACTION_REGENERATE_RESPONSE, ACTION_VIEW_IN_CHAT, MENU_INLINE_CHAT_WIDGET_TOGGLE, CTX_INLINE_CHAT_INNER_CURSOR_FIRST, CTX_INLINE_CHAT_INNER_CURSOR_LAST, CTX_INLINE_CHAT_INNER_CURSOR_START, CTX_INLINE_CHAT_INNER_CURSOR_END, CTX_INLINE_CHAT_RESPONSE_FOCUSED } from 'vs/workbench/contrib/inlineChat/common/inlineChat'; import { IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model'; -import { EventType, Dimension, addDisposableListener, getActiveElement, getTotalHeight, getTotalWidth, h, reset } from 'vs/base/browser/dom'; +import { EventType, Dimension, addDisposableListener, getActiveElement, getTotalHeight, getTotalWidth, h, reset, getWindow } from 'vs/base/browser/dom'; import { Emitter, Event, MicrotaskEmitter } from 'vs/base/common/event'; import { IEditorConstructionOptions } from 'vs/editor/browser/config/editorConfiguration'; import { ICodeEditorWidgetOptions } from 'vs/editor/browser/widget/codeEditorWidget'; @@ -403,7 +403,7 @@ export class InlineChatWidget { } private _onContextMenu(e: MouseEvent) { - const event = new StandardMouseEvent(e); + const event = new StandardMouseEvent(getWindow(this._elements.root), e); this._contextMenuService.showContextMenu({ menuId: MENU_INLINE_CHAT_WIDGET_TOGGLE, getAnchor: () => event, diff --git a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts index cb32deac5f7..2d0894d1bb8 100644 --- a/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts +++ b/src/vs/workbench/contrib/inlineChat/common/inlineChat.ts @@ -64,6 +64,7 @@ export interface IInlineChatEditResponse { id: number; type: InlineChatResponseType.EditorEdit; edits: TextEdit[]; + message?: IMarkdownString; placeholder?: string; wholeRange?: IRange; } @@ -72,6 +73,7 @@ export interface IInlineChatBulkEditResponse { id: number; type: InlineChatResponseType.BulkEdit; edits: WorkspaceEdit; + message?: IMarkdownString; placeholder?: string; wholeRange?: IRange; } @@ -184,6 +186,12 @@ export const enum EditMode { Preview = 'preview' } +export const enum ShowGutterIcon { + Always = 'always', + MouseOver = 'mouseover', + Never = 'never' +} + Registry.as(ExtensionsMigration.ConfigurationMigration).registerConfigurationMigrations( [{ key: 'interactiveEditor.editMode', migrateFn: (value: any) => { @@ -212,9 +220,15 @@ Registry.as(Extensions.Configuration).registerConfigurat type: 'boolean' }, 'inlineChat.showGutterIcon': { - description: localize('showGutterIcon', "Show/hide a gutter icon for spawning inline chat on empty lines."), - default: false, - type: 'boolean' + description: localize('showGutterIcon', "Controls when the gutter icon for spawning inline chat is shown."), + default: ShowGutterIcon.Always, + type: 'string', + enum: [ShowGutterIcon.Always, ShowGutterIcon.MouseOver, ShowGutterIcon.Never], + markdownEnumDescriptions: [ + localize('showGutterIcon.always', "Always show the gutter icon."), + localize('showGutterIcon.mouseover', "Show the gutter icon when the mouse is over the icon."), + localize('showGutterIcon.never', "Never show the gutter icon."), + ] } } }); diff --git a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts index d291c4c130f..92add5ea20e 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactive.contribution.ts @@ -490,7 +490,7 @@ registerAction2(class extends Action2 { historyService.addToHistory(notebookDocument.uri, ''); textModel.setValue(''); - const collapseState = editorControl.notebookEditor.notebookOptions.getLayoutConfiguration().interactiveWindowCollapseCodeCells === 'fromEditor' ? + const collapseState = editorControl.notebookEditor.notebookOptions.getDisplayOptions().interactiveWindowCollapseCodeCells === 'fromEditor' ? { inputCollapsed: false, outputCollapsed: false diff --git a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts index 4fd7bd3ea7d..5031e3976cd 100644 --- a/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts +++ b/src/vs/workbench/contrib/interactive/browser/interactiveEditor.ts @@ -219,11 +219,13 @@ export class InteractiveEditor extends EditorPane { const styleSheets: string[] = []; const { - focusIndicator, codeCellLeftMargin, cellRunGutter } = this._notebookOptions.getLayoutConfiguration(); - const leftMargin = codeCellLeftMargin + cellRunGutter; + const { + focusIndicator + } = this._notebookOptions.getDisplayOptions(); + const leftMargin = this._notebookOptions.getCellEditorContainerLeftMargin(); styleSheets.push(` .interactive-editor .input-cell-container { @@ -405,11 +407,7 @@ export class InteractiveEditor extends EditorPane { if (this._lastLayoutDimensions) { this._notebookEditorContainer.style.height = `${this._lastLayoutDimensions.dimension.height - this.inputCellContainerHeight}px`; this._notebookWidget.value!.layout(new DOM.Dimension(this._lastLayoutDimensions.dimension.width, this._lastLayoutDimensions.dimension.height - this.inputCellContainerHeight), this._notebookEditorContainer); - const { - codeCellLeftMargin, - cellRunGutter - } = this._notebookOptions.getLayoutConfiguration(); - const leftMargin = codeCellLeftMargin + cellRunGutter; + const leftMargin = this._notebookOptions.getCellEditorContainerLeftMargin(); const maxHeight = Math.min(this._lastLayoutDimensions.dimension.height / 2, this.inputCellEditorHeight); this._codeEditorWidget.layout(this._validateDimension(this._lastLayoutDimensions.dimension.width - leftMargin - INPUT_CELL_HORIZONTAL_PADDING_RIGHT, maxHeight)); this._inputFocusIndicator.style.height = `${this.inputCellEditorHeight}px`; @@ -615,11 +613,7 @@ export class InteractiveEditor extends EditorPane { private _layoutWidgets(dimension: DOM.Dimension, position: DOM.IDomPosition) { const contentHeight = this._codeEditorWidget.hasModel() ? this._codeEditorWidget.getContentHeight() : this.inputCellEditorHeight; const maxHeight = Math.min(dimension.height / 2, contentHeight); - const { - codeCellLeftMargin, - cellRunGutter - } = this._notebookOptions.getLayoutConfiguration(); - const leftMargin = codeCellLeftMargin + cellRunGutter; + const leftMargin = this._notebookOptions.getCellEditorContainerLeftMargin(); const inputCellContainerHeight = maxHeight + INPUT_CELL_VERTICAL_PADDING * 2; this._notebookEditorContainer.style.height = `${dimension.height - inputCellContainerHeight}px`; diff --git a/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts b/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts index b2af996d57e..d0f622842b7 100644 --- a/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts +++ b/src/vs/workbench/contrib/languageStatus/browser/languageStatus.contribution.ts @@ -31,6 +31,7 @@ import { Action2, registerAction2 } from 'vs/platform/actions/common/actions'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; import { IAccessibilityInformation } from 'vs/platform/accessibility/common/accessibility'; +import { mainWindow } from 'vs/base/browser/window'; class LanguageStatusViewModel { @@ -202,8 +203,8 @@ class EditorStatusContribution implements IWorkbenchContribution { // animate the status bar icon whenever language status changes, repeat animation // when severity is warning or error, don't show animation when showing progress/busy const userHasInteractedWithStatus = this._interactionCounter.value >= 3; - const node = document.querySelector('.monaco-workbench .statusbar DIV#status\\.languageStatus A>SPAN.codicon'); - const container = document.querySelector('.monaco-workbench .statusbar DIV#status\\.languageStatus'); + const node = mainWindow.document.querySelector('.monaco-workbench .statusbar DIV#status\\.languageStatus A>SPAN.codicon'); + const container = mainWindow.document.querySelector('.monaco-workbench .statusbar DIV#status\\.languageStatus'); if (node instanceof HTMLElement && container) { const _wiggle = 'wiggle'; const _flash = 'flash'; @@ -223,10 +224,10 @@ class EditorStatusContribution implements IWorkbenchContribution { // track when the hover shows (this is automagic and DOM mutation spying is needed...) // use that as signal that the user has interacted/learned language status items work if (!userHasInteractedWithStatus) { - const hoverTarget = document.querySelector('.monaco-workbench .context-view'); + const hoverTarget = mainWindow.document.querySelector('.monaco-workbench .context-view'); if (hoverTarget instanceof HTMLElement) { const observer = new MutationObserver(() => { - if (document.contains(element)) { + if (mainWindow.document.contains(element)) { this._interactionCounter.increment(); observer.disconnect(); } diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts new file mode 100644 index 00000000000..19a8cc28e66 --- /dev/null +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.contribution.ts @@ -0,0 +1,98 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { Disposable } from 'vs/base/common/lifecycle'; +import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { Registry } from 'vs/platform/registry/common/platform'; +import { EditorPaneDescriptor, IEditorPaneRegistry } from 'vs/workbench/browser/editor'; +import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions'; +import { DEFAULT_EDITOR_ASSOCIATION, EditorExtensions, EditorInputWithOptions, IEditorFactoryRegistry, IEditorSerializer, IResourceMultiDiffEditorInput } from 'vs/workbench/common/editor'; +import { EditorInput } from 'vs/workbench/common/editor/editorInput'; +import { IEditorResolverService, RegisteredEditorPriority } from 'vs/workbench/services/editor/common/editorResolverService'; +import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle'; +import { parse } from 'vs/base/common/marshalling'; +import { onUnexpectedError } from 'vs/base/common/errors'; +import { MultiDiffEditorInput, MultiDiffEditorInputData } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput'; +import { MultiDiffEditor } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor'; + +class MultiDiffEditorResolverContribution extends Disposable { + + constructor( + @IEditorResolverService editorResolverService: IEditorResolverService, + @IInstantiationService instantiationService: IInstantiationService, + ) { + super(); + + this._register(editorResolverService.registerEditor( + `*`, + { + id: DEFAULT_EDITOR_ASSOCIATION.id, + label: DEFAULT_EDITOR_ASSOCIATION.displayName, + detail: DEFAULT_EDITOR_ASSOCIATION.providerDisplayName, + priority: RegisteredEditorPriority.builtin + }, + {}, + { + createMultiDiffEditorInput: (diffListEditor: IResourceMultiDiffEditorInput): EditorInputWithOptions => { + return { + editor: instantiationService.createInstance( + MultiDiffEditorInput, + diffListEditor.label, + diffListEditor.resources.map(resource => { + return new MultiDiffEditorInputData( + resource.resource, + resource.original.resource, + resource.modified.resource + ); + })) + }; + } + } + )); + } +} + +Registry + .as(WorkbenchExtensions.Workbench) + .registerWorkbenchContribution(MultiDiffEditorResolverContribution, LifecyclePhase.Starting); + +class MultiDiffEditorSerializer implements IEditorSerializer { + + canSerialize(editor: EditorInput): boolean { + return true; + } + + serialize(editor: MultiDiffEditorInput): string | undefined { + return JSON.stringify({ label: editor.label, resources: editor.resources }); + } + + deserialize(instantiationService: IInstantiationService, serializedEditor: string): EditorInput | undefined { + try { + const data = parse(serializedEditor) as { label: string | undefined; resources: MultiDiffEditorInputData[] }; + return instantiationService.createInstance(MultiDiffEditorInput, data.label, data.resources); + } catch (err) { + onUnexpectedError(err); + return undefined; + } + } +} + +Registry.as(EditorExtensions.EditorPane).registerEditorPane( + EditorPaneDescriptor.create( + MultiDiffEditor, + MultiDiffEditor.ID, + localize('name', "Multi Diff Editor") + ), + [ + new SyncDescriptor(MultiDiffEditorInput) + ] +); + +Registry.as(EditorExtensions.EditorFactory).registerEditorSerializer( + MultiDiffEditorInput.ID, + MultiDiffEditorSerializer +); diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts new file mode 100644 index 00000000000..6ca92292e4c --- /dev/null +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditor.ts @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as DOM from 'vs/base/browser/dom'; +import { EditorPane } from 'vs/workbench/browser/parts/editor/editorPane'; +import { IEditorOpenContext } from 'vs/workbench/common/editor'; +import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IStorageService } from 'vs/platform/storage/common/storage'; +import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { CancellationToken } from 'vs/base/common/cancellation'; +import { IEditorOptions } from 'vs/platform/editor/common/editor'; +import { ITextModelService } from 'vs/editor/common/services/resolverService'; +import { MultiDiffEditorInput } from 'vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput'; +import { MultiDiffEditorWidget } from 'vs/editor/browser/widget/multiDiffEditorWidget/multiDiffEditorWidget'; +import { toDisposable } from 'vs/base/common/lifecycle'; +import { ConstLazyPromise, IDiffEntry } from 'vs/editor/browser/widget/multiDiffEditorWidget/model'; + +export class MultiDiffEditor extends EditorPane { + static readonly ID = 'multiDiffEditor'; + + private _multiDiffEditorWidget: MultiDiffEditorWidget | undefined = undefined; + + constructor( + @IInstantiationService private readonly instantiationService: InstantiationService, + @ITelemetryService telemetryService: ITelemetryService, + @ITextModelService private readonly textModelService: ITextModelService, + @IThemeService themeService: IThemeService, + @IStorageService storageService: IStorageService + ) { + super(MultiDiffEditor.ID, telemetryService, themeService, storageService); + } + + protected createEditor(parent: HTMLElement): void { + this._multiDiffEditorWidget = this._register(this.instantiationService.createInstance(MultiDiffEditorWidget, parent)); + } + + override async setInput(input: MultiDiffEditorInput, options: IEditorOptions | undefined, context: IEditorOpenContext, token: CancellationToken): Promise { + await super.setInput(input, options, context, token); + + const rs = await Promise.all(input.resources.map(async r => ({ + originalRef: await this.textModelService.createModelReference(r.original!), + modifiedRef: await this.textModelService.createModelReference(r.modified!), + title: r.resource.fsPath, + }))); + + this._multiDiffEditorWidget?.setModel({ + onDidChange: () => toDisposable(() => { }), + diffs: rs.map(r => new ConstLazyPromise({ + original: r.originalRef.object.textEditorModel, + modified: r.modifiedRef.object.textEditorModel, + title: r.title, + })), + }); + } + + layout(dimension: DOM.Dimension): void { + this._multiDiffEditorWidget?.layout(dimension); + } +} diff --git a/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts new file mode 100644 index 00000000000..59b39f6e714 --- /dev/null +++ b/src/vs/workbench/contrib/multiDiffEditor/browser/multiDiffEditorInput.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * 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 { URI } from 'vs/base/common/uri'; +import { DEFAULT_EDITOR_ASSOCIATION, EditorInputCapabilities } from 'vs/workbench/common/editor'; +import { EditorInput } from 'vs/workbench/common/editor/editorInput'; + +export class MultiDiffEditorInput extends EditorInput { + static readonly ID: string = 'workbench.input.multiDiffEditor'; + + get resource(): URI | undefined { + return undefined; + } + + override get capabilities(): EditorInputCapabilities { + return EditorInputCapabilities.Readonly; + } + + override get typeId(): string { + return MultiDiffEditorInput.ID; + } + + override getName(): string { + return this.label ?? localize('name', "Multi Diff Editor"); + } + + override get editorId(): string { + return DEFAULT_EDITOR_ASSOCIATION.id; + } + + constructor(readonly label: string | undefined, readonly resources: readonly MultiDiffEditorInputData[]) { + super(); + } +} + +export class MultiDiffEditorInputData { + constructor( + readonly resource: URI, + readonly original: URI | undefined, + readonly modified: URI | undefined + ) { } +} diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts index a6ede9416aa..a80df1911de 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/clipboard/notebookClipboard.ts @@ -161,7 +161,7 @@ export function runCopyCells(accessor: ServicesAccessor, editor: INotebookEditor } if (editor.hasOutputTextSelection()) { - document.execCommand('copy'); + getWindow(editor.getDomNode()).document.execCommand('copy'); return true; } diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts index 633dedb4a4b..9dd8e757117 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/editorStatusBar/editorStatusBar.ts @@ -96,7 +96,7 @@ export class KernelStatus extends Disposable implements IWorkbenchContribution { } const updateStatus = () => { - if (activeEditor.notebookOptions.getLayoutConfiguration().globalToolbar) { + if (activeEditor.notebookOptions.getDisplayOptions().globalToolbar) { // kernel info rendered in the notebook toolbar already this._kernelInfoElement.clear(); return; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts index e0c09afffbc..ea51ae067ca 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/find/notebookFindWidget.ts @@ -262,13 +262,13 @@ class NotebookFindWidget extends SimpleFindReplaceWidget implements INotebookEdi if (this._showTimeout === null) { if (this._hideTimeout !== null) { - window.clearTimeout(this._hideTimeout); + DOM.getWindow(this.getDomNode()).clearTimeout(this._hideTimeout); this._hideTimeout = null; this._notebookEditor.removeClassName(FIND_HIDE_TRANSITION); } this._notebookEditor.addClassName(FIND_SHOW_TRANSITION); - this._showTimeout = window.setTimeout(() => { + this._showTimeout = DOM.getWindow(this.getDomNode()).setTimeout(() => { this._notebookEditor.removeClassName(FIND_SHOW_TRANSITION); this._showTimeout = null; }, 200); @@ -284,13 +284,13 @@ class NotebookFindWidget extends SimpleFindReplaceWidget implements INotebookEdi if (this._showTimeout === null) { if (this._hideTimeout !== null) { - window.clearTimeout(this._hideTimeout); + DOM.getWindow(this.getDomNode()).clearTimeout(this._hideTimeout); this._hideTimeout = null; this._notebookEditor.removeClassName(FIND_HIDE_TRANSITION); } this._notebookEditor.addClassName(FIND_SHOW_TRANSITION); - this._showTimeout = window.setTimeout(() => { + this._showTimeout = DOM.getWindow(this.getDomNode()).setTimeout(() => { this._notebookEditor.removeClassName(FIND_SHOW_TRANSITION); this._showTimeout = null; }, 200); @@ -308,12 +308,12 @@ class NotebookFindWidget extends SimpleFindReplaceWidget implements INotebookEdi if (this._hideTimeout === null) { if (this._showTimeout !== null) { - window.clearTimeout(this._showTimeout); + DOM.getWindow(this.getDomNode()).clearTimeout(this._showTimeout); this._showTimeout = null; this._notebookEditor.removeClassName(FIND_SHOW_TRANSITION); } this._notebookEditor.addClassName(FIND_HIDE_TRANSITION); - this._hideTimeout = window.setTimeout(() => { + this._hideTimeout = DOM.getWindow(this.getDomNode()).setTimeout(() => { this._notebookEditor.removeClassName(FIND_HIDE_TRANSITION); }, 200); } else { diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts index 4996b79f268..d4abdb13eef 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/outline/notebookOutline.ts @@ -8,7 +8,6 @@ import { IIconLabelValueOptions, IconLabel } from 'vs/base/browser/ui/iconLabel/ import { IKeyboardNavigationLabelProvider, IListVirtualDelegate } from 'vs/base/browser/ui/list/list'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; import { IDataSource, ITreeNode, ITreeRenderer } from 'vs/base/browser/ui/tree/tree'; -import { IdleValue } from 'vs/base/common/async'; import { Emitter, Event } from 'vs/base/common/event'; import { FuzzyScore, createMatches } from 'vs/base/common/filters'; import { Disposable, DisposableStore, IDisposable, toDisposable } from 'vs/base/common/lifecycle'; @@ -37,6 +36,8 @@ import { OutlineEntry } from 'vs/workbench/contrib/notebook/browser/viewModel/Ou import { CancellationToken } from 'vs/base/common/cancellation'; import { IModelDeltaDecoration } from 'vs/editor/common/model'; import { Range } from 'vs/editor/common/core/range'; +import { mainWindow } from 'vs/base/browser/window'; +import { WindowIdleValue } from 'vs/base/browser/dom'; class NotebookOutlineTemplate { @@ -177,7 +178,7 @@ class NotebookQuickPickProvider implements IQuickPickDataSource { class NotebookComparator implements IOutlineComparator { - private readonly _collator = new IdleValue(() => new Intl.Collator(undefined, { numeric: true })); + private readonly _collator = new WindowIdleValue(mainWindow, () => new Intl.Collator(undefined, { numeric: true })); compareByPosition(a: OutlineEntry, b: OutlineEntry): number { return a.index - b.index; diff --git a/src/vs/workbench/contrib/notebook/browser/contrib/saveParticipants/saveParticipants.ts b/src/vs/workbench/contrib/notebook/browser/contrib/saveParticipants/saveParticipants.ts index 1195c7767b2..04cdc764d38 100644 --- a/src/vs/workbench/contrib/notebook/browser/contrib/saveParticipants/saveParticipants.ts +++ b/src/vs/workbench/contrib/notebook/browser/contrib/saveParticipants/saveParticipants.ts @@ -253,7 +253,10 @@ class FinalNewLineParticipant implements IStoredFileWorkingCopySaveParticipant { ) { } async participate(workingCopy: IStoredFileWorkingCopy, context: { reason: SaveReason }, progress: IProgress, _token: CancellationToken): Promise { - if (this.configurationService.getValue('files.insertFinalNewline')) { + // waiting on notebook-specific override before this feature can sync with 'files.insertFinalNewline' + // if (this.configurationService.getValue('files.insertFinalNewline')) { + + if (this.configurationService.getValue(NotebookSetting.insertFinalNewline)) { await this.doInsertFinalNewLine(workingCopy, context.reason === SaveReason.AUTO, progress); } } diff --git a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts index 92327e752c6..bee5a8db7c4 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/diffComponents.ts @@ -971,7 +971,7 @@ export class DeletedElement extends SingleSideDiffElement { } layout(state: IDiffElementLayoutState) { - DOM.scheduleAtNextAnimationFrame(() => { + DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this._diffEditorContainer), () => { if (state.editorHeight || state.outerWidth) { this._editor.layout({ width: this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, false), @@ -998,7 +998,7 @@ export class DeletedElement extends SingleSideDiffElement { } this.layoutNotebookCell(); - }, DOM.getWindow(this._diffEditorContainer)); + }); } _buildOutputRendererContainer() { @@ -1174,7 +1174,7 @@ export class InsertElement extends SingleSideDiffElement { } layout(state: IDiffElementLayoutState) { - DOM.scheduleAtNextAnimationFrame(() => { + DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this._diffEditorContainer), () => { if (state.editorHeight || state.outerWidth) { this._editor.layout({ width: this.cell.getComputedCellContainerWidth(this.notebookEditor.getLayoutInfo(), false, false), @@ -1201,7 +1201,7 @@ export class InsertElement extends SingleSideDiffElement { if (this._diagonalFill) { this._diagonalFill.style.height = `${this.cell.layoutInfo.editorHeight + this.cell.layoutInfo.editorMargin + this.cell.layoutInfo.metadataStatusHeight + this.cell.layoutInfo.metadataHeight + this.cell.layoutInfo.outputTotalHeight + this.cell.layoutInfo.outputStatusHeight}px`; } - }, DOM.getWindow(this._diffEditorContainer)); + }); } override dispose() { @@ -1591,7 +1591,7 @@ export class ModifiedElement extends AbstractElementRenderer { } layout(state: IDiffElementLayoutState) { - DOM.scheduleAtNextAnimationFrame(() => { + DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this._diffEditorContainer), () => { if (state.editorHeight) { this._editorContainer.style.height = `${this.cell.layoutInfo.editorHeight}px`; this._editor!.layout({ @@ -1626,7 +1626,7 @@ export class ModifiedElement extends AbstractElementRenderer { } this.layoutNotebookCell(); - }, DOM.getWindow(this._diffEditorContainer)); + }); } override dispose() { diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts index 35c1e3cf692..5b87a0024a2 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffEditor.ts @@ -447,7 +447,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD private _attachModel() { this._eventDispatcher = new NotebookDiffEditorEventDispatcher(); const updateInsets = () => { - DOM.scheduleAtNextAnimationFrame(() => { + DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this._listViewContainer), () => { if (this._isDisposed) { return; } @@ -463,7 +463,7 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD return diffElement.original; }, DiffSide.Original); } - }, DOM.getWindow(this._listViewContainer)); + }); }; this._localStore.add(this._list.onDidChangeContentHeight(() => { @@ -761,9 +761,9 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD const webview = diffSide === DiffSide.Modified ? this._modifiedWebview : this._originalWebview; - DOM.scheduleAtNextAnimationFrame(() => { + DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this._listViewContainer), () => { webview?.ackHeight([{ cellId: cellInfo.cellId, outputId, height }]); - }, DOM.getWindow(this._listViewContainer), 10); + }, 10); } private pendingLayouts = new WeakMap(); @@ -779,12 +779,12 @@ export class NotebookTextDiffEditor extends EditorPane implements INotebookTextD } let r: () => void; - const layoutDisposable = DOM.scheduleAtNextAnimationFrame(() => { + const layoutDisposable = DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this._listViewContainer), () => { this.pendingLayouts.delete(cell); relayout(cell, height); r(); - }, DOM.getWindow(this._listViewContainer)); + }); this.pendingLayouts.set(cell, toDisposable(() => { layoutDisposable.dispose(); diff --git a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffOverviewRuler.ts b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffOverviewRuler.ts index 57938e24695..71032920fcb 100644 --- a/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffOverviewRuler.ts +++ b/src/vs/workbench/contrib/notebook/browser/diff/notebookDiffOverviewRuler.ts @@ -114,7 +114,7 @@ export class NotebookDiffOverviewRuler extends Themable { private _scheduleRender(): void { if (this._renderAnimationFrame === null) { - this._renderAnimationFrame = DOM.runAtThisOrScheduleAtNextAnimationFrame(this._onRenderScheduled.bind(this), DOM.getWindow(this._domNode.domNode), 16); + this._renderAnimationFrame = DOM.runAtThisOrScheduleAtNextAnimationFrame(DOM.getWindow(this._domNode.domNode), this._onRenderScheduled.bind(this), 16); } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts index 96edb0787a4..9689de4040b 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebook.contribution.ts @@ -964,6 +964,12 @@ configurationRegistry.registerConfiguration({ tags: ['notebookLayout'], default: false }, + [NotebookSetting.insertFinalNewline]: { + markdownDescription: nls.localize('notebook.insertFinalNewline', "When enabled, insert a final new line into the end of code cells when saving a notebook."), + type: 'boolean', + tags: ['notebookLayout'], + default: false + }, [NotebookSetting.codeActionsOnSave]: { markdownDescription: nls.localize('notebook.codeActionsOnSave', 'Run a series of Code Actions for a notebook on save. Code Actions must be specified, the file must not be saved after delay, and the editor must not be shutting down. Example: `"notebook.source.organizeImports": "explicit"`'), type: 'object', @@ -1044,5 +1050,3 @@ configurationRegistry.registerConfiguration({ } } }); - - diff --git a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts index f1a902a7271..e7a948878f3 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookEditorWidget.ts @@ -21,7 +21,7 @@ import { PixelRatio } from 'vs/base/browser/browser'; import * as DOM from 'vs/base/browser/dom'; import { IMouseWheelEvent, StandardMouseEvent } from 'vs/base/browser/mouseEvent'; import { IListContextMenuEvent } from 'vs/base/browser/ui/list/list'; -import { DeferredPromise, runWhenIdle, SequencerByKey } from 'vs/base/common/async'; +import { DeferredPromise, SequencerByKey } from 'vs/base/common/async'; import { CancellationToken } from 'vs/base/common/cancellation'; import { Color, RGBA } from 'vs/base/common/color'; import { onUnexpectedError } from 'vs/base/common/errors'; @@ -558,7 +558,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD const cellToolbarLocation = this._notebookOptions.computeCellToolbarLocation(this.viewModel?.viewType); this._overlayContainer.classList.add(`cell-title-toolbar-${cellToolbarLocation}`); - const cellToolbarInteraction = this._notebookOptions.getLayoutConfiguration().cellToolbarInteraction; + const cellToolbarInteraction = this._notebookOptions.getDisplayOptions().cellToolbarInteraction; let cellToolbarInteractionState = 'hover'; this._overlayContainer.classList.remove('cell-toolbar-hover'); this._overlayContainer.classList.remove('cell-toolbar-click'); @@ -619,16 +619,21 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD markdownCellBottomMargin, markdownCellTopMargin, collapsedIndicatorHeight, - compactView, focusIndicator, insertToolbarPosition, - insertToolbarAlignment, - fontSize, outputFontSize, focusIndicatorLeftMargin, focusIndicatorGap } = this._notebookOptions.getLayoutConfiguration(); + const { + insertToolbarAlignment, + compactView, + fontSize + } = this._notebookOptions.getDisplayOptions(); + + const getCellEditorContainerLeftMargin = this._notebookOptions.getCellEditorContainerLeftMargin(); + const { bottomToolbarGap, bottomToolbarHeight } = this._notebookOptions.computeBottomToolbarDimensions(this.viewModel?.viewType); const styleSheets: string[] = []; @@ -647,7 +652,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD `); if (compactView) { - styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row div.cell.code { margin-left: ${codeCellLeftMargin + cellRunGutter}px; }`); + styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row div.cell.code { margin-left: ${getCellEditorContainerLeftMargin}px; }`); } else { styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row div.cell.code { margin-left: ${codeCellLeftMargin}px; }`); } @@ -777,18 +782,18 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD }`); } - styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .code-cell-row div.cell.code { margin-left: ${codeCellLeftMargin + cellRunGutter}px; }`); + styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .code-cell-row div.cell.code { margin-left: ${getCellEditorContainerLeftMargin}px; }`); styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row div.cell { margin-right: ${cellRightMargin}px; }`); styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row > .cell-inner-container { padding-top: ${cellTopMargin}px; }`); styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row > .cell-inner-container { padding-bottom: ${markdownCellBottomMargin}px; padding-top: ${markdownCellTopMargin}px; }`); styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row > .cell-inner-container.webview-backed-markdown-cell { padding: 0; }`); styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .markdown-cell-row > .webview-backed-markdown-cell.markdown-cell-edit-mode .cell.code { padding-bottom: ${markdownCellBottomMargin}px; padding-top: ${markdownCellTopMargin}px; }`); - styleSheets.push(`.notebookOverlay .output { margin: 0px ${cellRightMargin}px 0px ${codeCellLeftMargin + cellRunGutter}px; }`); - styleSheets.push(`.notebookOverlay .output { width: calc(100% - ${codeCellLeftMargin + cellRunGutter + cellRightMargin}px); }`); + styleSheets.push(`.notebookOverlay .output { margin: 0px ${cellRightMargin}px 0px ${getCellEditorContainerLeftMargin}px; }`); + styleSheets.push(`.notebookOverlay .output { width: calc(100% - ${getCellEditorContainerLeftMargin + cellRightMargin}px); }`); // comment - styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-comment-container { left: ${codeCellLeftMargin + cellRunGutter}px; }`); - styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-comment-container { width: calc(100% - ${codeCellLeftMargin + cellRunGutter + cellRightMargin}px); }`); + styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-comment-container { left: ${getCellEditorContainerLeftMargin}px; }`); + styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-comment-container { width: calc(100% - ${getCellEditorContainerLeftMargin + cellRightMargin}px); }`); // output collapse button styleSheets.push(`.monaco-workbench .notebookOverlay .output .output-collapse-container .expandButton { left: -${cellRunGutter}px; }`); @@ -799,15 +804,15 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD }`); // show more container - styleSheets.push(`.notebookOverlay .output-show-more-container { margin: 0px ${cellRightMargin}px 0px ${codeCellLeftMargin + cellRunGutter}px; }`); - styleSheets.push(`.notebookOverlay .output-show-more-container { width: calc(100% - ${codeCellLeftMargin + cellRunGutter + cellRightMargin}px); }`); + styleSheets.push(`.notebookOverlay .output-show-more-container { margin: 0px ${cellRightMargin}px 0px ${getCellEditorContainerLeftMargin}px; }`); + styleSheets.push(`.notebookOverlay .output-show-more-container { width: calc(100% - ${getCellEditorContainerLeftMargin + cellRightMargin}px); }`); styleSheets.push(`.notebookOverlay .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row div.cell.markdown { padding-left: ${cellRunGutter}px; }`); styleSheets.push(`.monaco-workbench .notebookOverlay > .cell-list-container .notebook-folding-indicator { left: ${(markdownCellGutter - 20) / 2 + markdownCellLeftMargin}px; }`); styleSheets.push(`.notebookOverlay > .cell-list-container .notebook-folded-hint { left: ${markdownCellGutter + markdownCellLeftMargin + 8}px; }`); styleSheets.push(`.notebookOverlay .monaco-list .monaco-list-row :not(.webview-backed-markdown-cell) .cell-focus-indicator-top { height: ${cellTopMargin}px; }`); styleSheets.push(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-side { bottom: ${bottomToolbarGap}px; }`); - styleSheets.push(`.notebookOverlay .monaco-list .monaco-list-row.code-cell-row .cell-focus-indicator-left { width: ${codeCellLeftMargin + cellRunGutter}px; }`); + styleSheets.push(`.notebookOverlay .monaco-list .monaco-list-row.code-cell-row .cell-focus-indicator-left { width: ${getCellEditorContainerLeftMargin}px; }`); styleSheets.push(`.notebookOverlay .monaco-list .monaco-list-row.markdown-cell-row .cell-focus-indicator-left { width: ${codeCellLeftMargin}px; }`); styleSheets.push(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator.cell-focus-indicator-right { width: ${cellRightMargin}px; }`); styleSheets.push(`.notebookOverlay .monaco-list .monaco-list-row .cell-focus-indicator-bottom { height: ${cellBottomMargin}px; }`); @@ -838,7 +843,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD right: ${cellRightMargin + 26}px; } .monaco-workbench .notebookOverlay.cell-title-toolbar-left > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-title-toolbar { - left: ${codeCellLeftMargin + cellRunGutter + 16}px; + left: ${getCellEditorContainerLeftMargin + 16}px; } .monaco-workbench .notebookOverlay.cell-title-toolbar-hidden > .cell-list-container > .monaco-list > .monaco-scrollable-element > .monaco-list-rows > .monaco-list-row .cell-title-toolbar { display: none; @@ -1154,7 +1159,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD } this._backgroundMarkdownRenderRunning = true; - runWhenIdle((deadline) => { + DOM.runWhenWindowIdle(DOM.getWindow(this._body), (deadline) => { this._backgroundMarkdownRenderingWithDeadline(deadline); }); } @@ -1449,10 +1454,10 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD } hasPendingChangeContentHeight = true; - this._localStore.add(DOM.scheduleAtNextAnimationFrame(() => { + this._localStore.add(DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this._body), () => { hasPendingChangeContentHeight = false; this._updateScrollHeight(); - }, DOM.getWindow(this._body), 100)); + }, 100)); })); this._localStore.add(this._list.onDidRemoveOutputs(outputs => { @@ -2257,7 +2262,7 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD }; if (this._list.inRenderingTransaction) { - const layoutDisposable = DOM.scheduleAtNextAnimationFrame(doLayout, DOM.getWindow(this._body)); + const layoutDisposable = DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this._body), doLayout); this._pendingLayouts?.set(cell, toDisposable(() => { layoutDisposable.dispose(); @@ -2964,14 +2969,14 @@ export class NotebookEditorWidget extends Disposable implements INotebookEditorD this._pendingOutputHeightAcks.set(outputId, { cellId: cellInfo.cellId, outputId, height }); if (wasEmpty) { - DOM.scheduleAtNextAnimationFrame(() => { + DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this._body), () => { this._debug('ack height'); this._updateScrollHeight(); this._webview?.ackHeight([...this._pendingOutputHeightAcks.values()]); this._pendingOutputHeightAcks.clear(); - }, DOM.getWindow(this._body), -1); // -1 priority because this depends on calls to layoutNotebookCell, and that may be called multiple times before this runs + }, -1); // -1 priority because this depends on calls to layoutNotebookCell, and that may be called multiple times before this runs } } diff --git a/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts b/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts index 58249689286..f5d87fee186 100644 --- a/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts +++ b/src/vs/workbench/contrib/notebook/browser/notebookOptions.ts @@ -32,6 +32,32 @@ export function getEditorTopPadding() { export const OutputInnerContainerTopPadding = 4; +export interface NotebookDisplayOptions { + showCellStatusBar: ShowCellStatusBarType; + cellToolbarLocation: string | { [key: string]: string }; + cellToolbarInteraction: string; + compactView: boolean; + focusIndicator: 'border' | 'gutter'; + insertToolbarPosition: 'betweenCells' | 'notebookToolbar' | 'both' | 'hidden'; + insertToolbarAlignment: 'left' | 'center'; + globalToolbar: boolean; + stickyScroll: boolean; + consolidatedOutputButton: boolean; + consolidatedRunButton: boolean; + showFoldingControls: 'always' | 'never' | 'mouseover'; + dragAndDropEnabled: boolean; + interactiveWindowCollapseCodeCells: InteractiveWindowCollapseCodeCells; + outputScrolling: boolean; + outputWordWrap: boolean; + outputLineLimit: number; + fontSize: number; + outputFontSize: number; + outputFontFamily: string; + outputLineHeight: number; + markupFontSize: number; + editorOptionsCustomizations: any | undefined; +} + export interface NotebookLayoutConfiguration { cellRightMargin: number; cellRunGutter: number; @@ -45,39 +71,14 @@ export interface NotebookLayoutConfiguration { markdownCellBottomMargin: number; markdownPreviewPadding: number; markdownFoldHintHeight: number; - // bottomToolbarGap: number; - // bottomToolbarHeight: number; editorToolbarHeight: number; editorTopPadding: number; editorBottomPadding: number; editorBottomPaddingWithoutStatusBar: number; collapsedIndicatorHeight: number; - showCellStatusBar: ShowCellStatusBarType; cellStatusBarHeight: number; - cellToolbarLocation: string | { [key: string]: string }; - cellToolbarInteraction: string; - compactView: boolean; - focusIndicator: 'border' | 'gutter'; - insertToolbarPosition: 'betweenCells' | 'notebookToolbar' | 'both' | 'hidden'; - insertToolbarAlignment: 'left' | 'center'; - globalToolbar: boolean; - stickyScroll: boolean; - consolidatedOutputButton: boolean; - consolidatedRunButton: boolean; - showFoldingControls: 'always' | 'never' | 'mouseover'; - dragAndDropEnabled: boolean; - fontSize: number; - outputFontSize: number; - outputFontFamily: string; - outputLineHeight: number; - markupFontSize: number; focusIndicatorLeftMargin: number; - editorOptionsCustomizations: any | undefined; focusIndicatorGap: number; - interactiveWindowCollapseCodeCells: InteractiveWindowCollapseCodeCells; - outputScrolling: boolean; - outputWordWrap: boolean; - outputLineLimit: number; } export interface NotebookOptionsChangeEvent { @@ -128,7 +129,7 @@ const compactConfigConstants = Object.freeze({ }); export class NotebookOptions extends Disposable { - private _layoutConfiguration: NotebookLayoutConfiguration; + private _layoutConfiguration: NotebookLayoutConfiguration & NotebookDisplayOptions; protected readonly _onDidChangeOptions = this._register(new Emitter()); readonly onDidChangeOptions = this._onDidChangeOptions.event; @@ -536,10 +537,22 @@ export class NotebookOptions extends Disposable { }; } - getLayoutConfiguration(): NotebookLayoutConfiguration { + getLayoutConfiguration(): NotebookLayoutConfiguration & NotebookDisplayOptions { return this._layoutConfiguration; } + getDisplayOptions(): NotebookDisplayOptions { + return this._layoutConfiguration; + } + + getCellEditorContainerLeftMargin() { + const { + codeCellLeftMargin, + cellRunGutter + } = this._layoutConfiguration; + return codeCellLeftMargin + cellRunGutter; + } + computeCollapsedMarkdownCellHeight(viewType: string): number { const { bottomToolbarGap } = this.computeBottomToolbarDimensions(viewType); return this._layoutConfiguration.markdownCellTopMargin diff --git a/src/vs/workbench/contrib/notebook/browser/services/notebookKernelServiceImpl.ts b/src/vs/workbench/contrib/notebook/browser/services/notebookKernelServiceImpl.ts index 7eb95b9c062..adc3249aab0 100644 --- a/src/vs/workbench/contrib/notebook/browser/services/notebookKernelServiceImpl.ts +++ b/src/vs/workbench/contrib/notebook/browser/services/notebookKernelServiceImpl.ts @@ -11,12 +11,13 @@ import { LRUCache, ResourceMap } from 'vs/base/common/map'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { URI } from 'vs/base/common/uri'; import { INotebookService } from 'vs/workbench/contrib/notebook/common/notebookService'; -import { runWhenIdle } from 'vs/base/common/async'; import { IMenu, IMenuService, MenuId } from 'vs/platform/actions/common/actions'; import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { IAction } from 'vs/base/common/actions'; import { MarshalledId } from 'vs/base/common/marshallingIds'; import { Schemas } from 'vs/base/common/network'; +import { $window } from 'vs/base/browser/window'; +import { runWhenWindowIdle } from 'vs/base/browser/dom'; class KernelInfo { @@ -168,7 +169,7 @@ export class NotebookKernelService extends Disposable implements INotebookKernel private _persistMementos(): void { this._persistSoonHandle?.dispose(); - this._persistSoonHandle = runWhenIdle(() => { + this._persistSoonHandle = runWhenWindowIdle($window, () => { this._storageService.store(NotebookKernelService._storageNotebookBinding, JSON.stringify(this._notebookBindings), StorageScope.WORKSPACE, StorageTarget.MACHINE); }, 100); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellPart.ts b/src/vs/workbench/contrib/notebook/browser/view/cellPart.ts index 32dc2032748..af28b7b63e7 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellPart.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellPart.ts @@ -158,11 +158,11 @@ export class CellPartsCollection extends Disposable { part.renderCell(element); } - this._scheduledOverlayRendering.value = DOM.modify(() => { + this._scheduledOverlayRendering.value = DOM.modify(this.targetWindow, () => { for (const part of this.overlayParts) { part.renderCell(element); } - }, this.targetWindow); + }); } unrenderCell(element: ICellViewModel): void { @@ -200,11 +200,11 @@ export class CellPartsCollection extends Disposable { part.updateState(viewCell, e); } - this._scheduledOverlayUpdateState.value = DOM.modify(() => { + this._scheduledOverlayUpdateState.value = DOM.modify(this.targetWindow, () => { for (const part of this.overlayParts) { part.updateState(viewCell, e); } - }, this.targetWindow); + }); } updateForExecutionState(viewCell: ICellViewModel, e: ICellExecutionStateChangedEvent) { @@ -212,10 +212,10 @@ export class CellPartsCollection extends Disposable { part.updateForExecutionState(viewCell, e); } - this._scheduledOverlayUpdateExecutionState.value = DOM.modify(() => { + this._scheduledOverlayUpdateExecutionState.value = DOM.modify(this.targetWindow, () => { for (const part of this.overlayParts) { part.updateForExecutionState(viewCell, e); } - }, this.targetWindow); + }); } } diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts index ae080f9f65a..5ab12dc04a3 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellDnd.ts @@ -307,7 +307,7 @@ export class CellDragAndDropController extends Disposable { } const onDragEnd = () => { - if (!this.notebookEditor.notebookOptions.getLayoutConfiguration().dragAndDropEnabled || !!this.notebookEditor.isReadOnly) { + if (!this.notebookEditor.notebookOptions.getDisplayOptions().dragAndDropEnabled || !!this.notebookEditor.isReadOnly) { return; } @@ -324,7 +324,7 @@ export class CellDragAndDropController extends Disposable { return; } - if (!this.notebookEditor.notebookOptions.getLayoutConfiguration().dragAndDropEnabled || !!this.notebookEditor.isReadOnly) { + if (!this.notebookEditor.notebookOptions.getDisplayOptions().dragAndDropEnabled || !!this.notebookEditor.isReadOnly) { return; } @@ -343,7 +343,7 @@ export class CellDragAndDropController extends Disposable { } public startExplicitDrag(cell: ICellViewModel, _dragOffsetY: number) { - if (!this.notebookEditor.notebookOptions.getLayoutConfiguration().dragAndDropEnabled || !!this.notebookEditor.isReadOnly) { + if (!this.notebookEditor.notebookOptions.getDisplayOptions().dragAndDropEnabled || !!this.notebookEditor.isReadOnly) { return; } @@ -352,7 +352,7 @@ export class CellDragAndDropController extends Disposable { } public explicitDrag(cell: ICellViewModel, dragOffsetY: number) { - if (!this.notebookEditor.notebookOptions.getLayoutConfiguration().dragAndDropEnabled || !!this.notebookEditor.isReadOnly) { + if (!this.notebookEditor.notebookOptions.getDisplayOptions().dragAndDropEnabled || !!this.notebookEditor.isReadOnly) { return; } diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts index 39491a8f5c9..f60ac478dab 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellOutput.ts @@ -286,7 +286,7 @@ class CellOutputElement extends Disposable { return; } - const useConsolidatedButton = this.notebookEditor.notebookOptions.getLayoutConfiguration().consolidatedOutputButton; + const useConsolidatedButton = this.notebookEditor.notebookOptions.getDisplayOptions().consolidatedOutputButton; outputItemDiv.style.position = 'relative'; const mimeTypePicker = DOM.$('.cell-output-toolbar'); diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts index 9afa376b4cf..3a38688600a 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/cellToolbars.ts @@ -46,7 +46,7 @@ export class BetweenCellToolbar extends CellOverlayPart { const betweenCellToolbar = this._register(new ToolBar(this._bottomCellToolbarContainer, this.contextMenuService, { actionViewItemProvider: action => { if (action instanceof MenuItemAction) { - if (this._notebookEditor.notebookOptions.getLayoutConfiguration().insertToolbarAlignment === 'center') { + if (this._notebookEditor.notebookOptions.getDisplayOptions().insertToolbarAlignment === 'center') { return this.instantiationService.createInstance(CodiconActionViewItem, action, undefined); } else { return this.instantiationService.createInstance(MenuEntryActionViewItem, action, undefined); diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts index 9f727ebfe88..31f4337c70a 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/codeCell.ts @@ -144,9 +144,9 @@ export class CodeCell extends Disposable { private updateForLayout(): void { this._pendingLayout?.dispose(); - this._pendingLayout = DOM.modify(() => { + this._pendingLayout = DOM.modify(DOM.getWindow(this.templateData.container), () => { this.cellParts.updateInternalLayoutNow(this.viewCell); - }, DOM.getWindow(this.templateData.container)); + }); } private updateForOutputHover() { diff --git a/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts b/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts index b8a9a6b9e0e..e6b32bcef21 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/cellParts/markupCell.ts @@ -233,7 +233,7 @@ export class MarkupCell extends Disposable { } private updateFoldingIconShowClass() { - const showFoldingIcon = this.notebookEditor.notebookOptions.getLayoutConfiguration().showFoldingControls; + const showFoldingIcon = this.notebookEditor.notebookOptions.getDisplayOptions().showFoldingControls; this.templateData.foldingIndicator.classList.remove('mouseover', 'always'); this.templateData.foldingIndicator.classList.add(showFoldingIcon); } diff --git a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts index 827a3195e21..bd6e7b68c7c 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/notebookCellList.ts @@ -282,17 +282,17 @@ export class NotebookCellList extends WorkbenchList implements ID this._localDisposableStore.add(this.view.onDidChangeContentHeight(() => { if (this._isInLayout) { - DOM.scheduleAtNextAnimationFrame(() => { + DOM.scheduleAtNextAnimationFrame(DOM.getWindow(container), () => { updateVisibleRanges(); - }, DOM.getWindow(container)); + }); } updateVisibleRanges(); })); this._localDisposableStore.add(this.view.onDidScroll(() => { if (this._isInLayout) { - DOM.scheduleAtNextAnimationFrame(() => { + DOM.scheduleAtNextAnimationFrame(DOM.getWindow(container), () => { updateVisibleRanges(); - }, DOM.getWindow(container)); + }); } updateVisibleRanges(); })); @@ -358,13 +358,13 @@ export class NotebookCellList extends WorkbenchList implements ID if (e.synchronous) { this._updateElementsInWebview(viewDiffs); } else { - this._viewModelStore.add(DOM.scheduleAtNextAnimationFrame(() => { + this._viewModelStore.add(DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this.rowsContainer), () => { if (this._isDisposed) { return; } this._updateElementsInWebview(viewDiffs); - }, DOM.getWindow(this.rowsContainer))); + })); } })); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts index 0816ebffe43..c2151962a4d 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/backLayerWebView.ts @@ -6,7 +6,7 @@ import * as osPath from 'vs/base/common/path'; import { IMouseWheelEvent } from 'vs/base/browser/mouseEvent'; import { coalesce } from 'vs/base/common/arrays'; -import { DeferredPromise, runWhenIdle } from 'vs/base/common/async'; +import { DeferredPromise, runWhenGlobalIdle } from 'vs/base/common/async'; import { decodeBase64 } from 'vs/base/common/buffer'; import { Emitter, Event } from 'vs/base/common/event'; import { getExtensionForMimeType } from 'vs/base/common/mime'; @@ -55,6 +55,7 @@ import { INotebookLoggingService } from 'vs/workbench/contrib/notebook/common/no import { IDisposable } from 'vs/base/common/lifecycle'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { WorkbenchActionExecutedClassification, WorkbenchActionExecutedEvent } from 'vs/base/common/actions'; +import { getWindow } from 'vs/base/browser/dom'; const LINE_COLUMN_REGEX = /:([\d]+)(?::([\d]+))?$/; const LineQueryRegex = /line=(\d+)$/; @@ -547,7 +548,7 @@ export class BackLayerWebView extends Themable { } private _initialize(content: string): Promise { - if (!document.body.contains(this.element)) { + if (!getWindow(this.element).document.body.contains(this.element)) { throw new Error('Element is already detached from the DOM tree'); } @@ -1435,7 +1436,7 @@ export class BackLayerWebView extends Themable { return; } - this.pendingWebviewIdleCreationRequest.set(content.source, runWhenIdle(() => { + this.pendingWebviewIdleCreationRequest.set(content.source, runWhenGlobalIdle(() => { const { message, renderer, transfer: transferable } = this._createOutputCreationMessage(cellInfo, content, cellTop, offset, true, true); this._sendMessageToWebview(message, transferable); this.pendingWebviewIdleInsetMapping.set(content.source, { outputId: message.outputId, versionId: content.source.model.versionId, cellInfo: cellInfo, renderer, cachedCreation: message }); diff --git a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts index 2ca6b9cda0f..802a46145f8 100644 --- a/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts +++ b/src/vs/workbench/contrib/notebook/browser/view/renderers/webviewPreloads.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import type * as DOM from 'vs/base/browser/window'; import type { Event } from 'vs/base/common/event'; import type { IDisposable } from 'vs/base/common/lifecycle'; import type * as webviewMessages from 'vs/workbench/contrib/notebook/browser/view/renderers/webviewMessages'; @@ -87,6 +88,8 @@ declare function cancelIdleCallback(handle: number): void; declare function __import(path: string): Promise; async function webviewPreloads(ctx: PreloadContext) { + // eslint-disable-next-line no-restricted-globals + const $window = window as typeof DOM.$window; const textEncoder = new TextEncoder(); const textDecoder = new TextDecoder(); @@ -143,7 +146,7 @@ async function webviewPreloads(ctx: PreloadContext) { // check if an input element is focused within the output element const checkOutputInputFocus = () => { - const activeElement = document.activeElement; + const activeElement = $window.document.activeElement; if (!activeElement) { return; } @@ -255,8 +258,8 @@ async function webviewPreloads(ctx: PreloadContext) { } }; - document.body.addEventListener('click', handleInnerClick); - document.body.addEventListener('focusin', checkOutputInputFocus); + $window.document.body.addEventListener('click', handleInnerClick); + $window.document.body.addEventListener('focusin', checkOutputInputFocus); interface RendererContext extends rendererApi.RendererContext { readonly onDidChangeSettings: Event; @@ -357,7 +360,7 @@ async function webviewPreloads(ctx: PreloadContext) { constructor() { this._observer = new ResizeObserver(entries => { for (const entry of entries) { - if (!document.body.contains(entry.target)) { + if (!$window.document.body.contains(entry.target)) { continue; } @@ -389,7 +392,7 @@ async function webviewPreloads(ctx: PreloadContext) { if (shouldUpdatePadding) { // Do not update dimension in resize observer - window.requestAnimationFrame(() => { + $window.requestAnimationFrame(() => { if (newHeight !== 0) { entry.target.style.padding = `${ctx.style.outputNodePadding}px ${ctx.style.outputNodePadding}px ${ctx.style.outputNodePadding}px ${ctx.style.outputNodeLeftPadding}px`; } else { @@ -457,7 +460,7 @@ async function webviewPreloads(ctx: PreloadContext) { } // if the node is not scrollable, we can continue. We don't check the computed style always as it's expensive - if (window.getComputedStyle(node).overflowY === 'hidden' || window.getComputedStyle(node).overflowY === 'visible') { + if ($window.getComputedStyle(node).overflowY === 'hidden' || $window.getComputedStyle(node).overflowY === 'visible') { continue; } @@ -489,10 +492,10 @@ async function webviewPreloads(ctx: PreloadContext) { }; function focusFirstFocusableOrContainerInOutput(cellOrOutputId: string, alternateId?: string) { - const cellOutputContainer = document.getElementById(cellOrOutputId) ?? - (alternateId ? document.getElementById(alternateId!) : undefined); + const cellOutputContainer = $window.document.getElementById(cellOrOutputId) ?? + (alternateId ? $window.document.getElementById(alternateId!) : undefined); if (cellOutputContainer) { - if (cellOutputContainer.contains(document.activeElement)) { + if (cellOutputContainer.contains($window.document.activeElement)) { return; } @@ -671,7 +674,7 @@ async function webviewPreloads(ctx: PreloadContext) { } function selectRange(_range: ICommonRange) { - const sel = window.getSelection(); + const sel = $window.getSelection(); if (sel) { try { sel.removeAllRanges(); @@ -704,8 +707,8 @@ async function webviewPreloads(ctx: PreloadContext) { } }; } else { - window.document.execCommand('hiliteColor', false, matchColor); - const cloneRange = window.getSelection()!.getRangeAt(0).cloneRange(); + $window.document.execCommand('hiliteColor', false, matchColor); + const cloneRange = $window.getSelection()!.getRangeAt(0).cloneRange(); const _range = { collapsed: cloneRange.collapsed, commonAncestorContainer: cloneRange.commonAncestorContainer, @@ -720,9 +723,9 @@ async function webviewPreloads(ctx: PreloadContext) { selectRange(_range); try { document.designMode = 'On'; - document.execCommand('removeFormat', false, undefined); + $window.document.execCommand('removeFormat', false, undefined); document.designMode = 'Off'; - window.getSelection()?.removeAllRanges(); + $window.getSelection()?.removeAllRanges(); } catch (e) { console.log(e); } @@ -731,10 +734,10 @@ async function webviewPreloads(ctx: PreloadContext) { selectRange(_range); try { document.designMode = 'On'; - document.execCommand('removeFormat', false, undefined); - window.document.execCommand('hiliteColor', false, color); + $window.document.execCommand('removeFormat', false, undefined); + $window.document.execCommand('hiliteColor', false, color); document.designMode = 'Off'; - window.getSelection()?.removeAllRanges(); + $window.getSelection()?.removeAllRanges(); } catch (e) { console.log(e); } @@ -906,12 +909,12 @@ async function webviewPreloads(ctx: PreloadContext) { const onDidReceiveKernelMessage = createEmitter(); - const ttPolicy = window.trustedTypes?.createPolicy('notebookRenderer', { + const ttPolicy = $window.trustedTypes?.createPolicy('notebookRenderer', { createHTML: value => value, // CodeQL [SM03712] The rendered content is provided by renderer extensions, which are responsible for sanitizing their content themselves. The notebook webview is also sandboxed. createScript: value => value, // CodeQL [SM03712] The rendered content is provided by renderer extensions, which are responsible for sanitizing their content themselves. The notebook webview is also sandboxed. }); - window.addEventListener('wheel', handleWheel); + $window.addEventListener('wheel', handleWheel); interface IFindMatch { type: 'preview' | 'output'; @@ -945,8 +948,8 @@ async function webviewPreloads(ctx: PreloadContext) { currentMatchIndex: number; } - const matchColor = window.getComputedStyle(document.getElementById('_defaultColorPalatte')!).color; - const currentMatchColor = window.getComputedStyle(document.getElementById('_defaultColorPalatte')!).backgroundColor; + const matchColor = $window.getComputedStyle($window.document.getElementById('_defaultColorPalatte')!).color; + const currentMatchColor = $window.getComputedStyle($window.document.getElementById('_defaultColorPalatte')!).backgroundColor; class JSHighlighter implements IHighlighter { private _activeHighlightInfo: Map; @@ -992,11 +995,11 @@ async function webviewPreloads(ctx: PreloadContext) { const match = highlightInfo.matches[index]; highlightInfo.currentMatchIndex = index; - const sel = window.getSelection(); + const sel = $window.getSelection(); if (!!match && !!sel && match.highlightResult) { let offset = 0; try { - const outputOffset = document.getElementById(match.id)!.getBoundingClientRect().top; + const outputOffset = $window.document.getElementById(match.id)!.getBoundingClientRect().top; const tempRange = document.createRange(); tempRange.selectNode(match.highlightResult.range.startContainer); @@ -1012,7 +1015,7 @@ async function webviewPreloads(ctx: PreloadContext) { match.highlightResult?.update(currentMatchColor, match.isShadow ? undefined : 'current-find-match'); - document.getSelection()?.removeAllRanges(); + $window.document.getSelection()?.removeAllRanges(); postNotebookMessage('didFindHighlightCurrent', { offset }); @@ -1031,7 +1034,7 @@ async function webviewPreloads(ctx: PreloadContext) { } dispose() { - document.getSelection()?.removeAllRanges(); + $window.document.getSelection()?.removeAllRanges(); this._activeHighlightInfo.forEach(highlightInfo => { highlightInfo.matches.forEach(match => { match.highlightResult?.dispose(); @@ -1106,7 +1109,7 @@ async function webviewPreloads(ctx: PreloadContext) { if (match) { let offset = 0; try { - const outputOffset = document.getElementById(match.id)!.getBoundingClientRect().top; + const outputOffset = $window.document.getElementById(match.id)!.getBoundingClientRect().top; match.originalRange.startContainer.parentElement?.scrollIntoView({ behavior: 'auto', block: 'end', inline: 'nearest' }); const rangeOffset = match.originalRange.getBoundingClientRect().top; offset = rangeOffset - outputOffset; @@ -1135,7 +1138,7 @@ async function webviewPreloads(ctx: PreloadContext) { } dispose(): void { - document.getSelection()?.removeAllRanges(); + $window.document.getSelection()?.removeAllRanges(); this._currentMatchesHighlight.clear(); this._matchesHighlight.clear(); } @@ -1236,8 +1239,8 @@ async function webviewPreloads(ctx: PreloadContext) { const matches: IFindMatch[] = []; const range = document.createRange(); - range.selectNodeContents(document.getElementById('findStart')!); - const sel = window.getSelection(); + range.selectNodeContents($window.document.getElementById('findStart')!); + const sel = $window.getSelection(); sel?.removeAllRanges(); sel?.addRange(range); @@ -1247,7 +1250,7 @@ async function webviewPreloads(ctx: PreloadContext) { document.designMode = 'On'; while (find && matches.length < 500) { - find = (window as any).find(query, /* caseSensitive*/ !!options.caseSensitive, + find = ($window as any).find(query, /* caseSensitive*/ !!options.caseSensitive, /* backwards*/ false, /* wrapAround*/ false, /* wholeWord */ !!options.wholeWord, @@ -1255,7 +1258,7 @@ async function webviewPreloads(ctx: PreloadContext) { false); if (find) { - const selection = window.getSelection(); + const selection = $window.getSelection(); if (!selection) { console.log('no selection'); break; @@ -1344,7 +1347,7 @@ async function webviewPreloads(ctx: PreloadContext) { break; } - if (node.id === 'container' || node === document.body) { + if (node.id === 'container' || node === $window.document.body) { break; } } @@ -1360,7 +1363,7 @@ async function webviewPreloads(ctx: PreloadContext) { } _highlighter.addHighlights(matches, options.ownerID); - document.getSelection()?.removeAllRanges(); + $window.document.getSelection()?.removeAllRanges(); viewModel.toggleDragDropEnabled(currentOptions.dragAndDropEnabled); @@ -1378,7 +1381,7 @@ async function webviewPreloads(ctx: PreloadContext) { }; const copyOutputImage = async (outputId: string, altOutputId: string, retries = 5) => { - if (!document.hasFocus() && retries > 0) { + if (!$window.document.hasFocus() && retries > 0) { // copyImage can be called from outside of the webview, which means this function may be running whilst the webview is gaining focus. // Since navigator.clipboard.write requires the document to be focused, we need to wait for focus. // We cannot use a listener, as there is a high chance the focus is gained during the setup of the listener resulting in us missing it. @@ -1387,8 +1390,8 @@ async function webviewPreloads(ctx: PreloadContext) { } try { - const image = document.getElementById(outputId)?.querySelector('img') - ?? document.getElementById(altOutputId)?.querySelector('img'); + const image = $window.document.getElementById(outputId)?.querySelector('img') + ?? $window.document.getElementById(altOutputId)?.querySelector('img'); if (image) { await navigator.clipboard.write([new ClipboardItem({ 'image/png': new Promise((resolve) => { @@ -1415,7 +1418,7 @@ async function webviewPreloads(ctx: PreloadContext) { } }; - window.addEventListener('message', async rawEvent => { + $window.addEventListener('message', async rawEvent => { const event = rawEvent as ({ data: webviewMessages.ToWebviewMessage }); switch (event.data.type) { @@ -1489,7 +1492,7 @@ async function webviewPreloads(ctx: PreloadContext) { case 'clear': renderers.clearAll(); viewModel.clearAll(); - document.getElementById('container')!.innerText = ''; + $window.document.getElementById('container')!.innerText = ''; break; case 'clearOutput': { @@ -1541,10 +1544,10 @@ async function webviewPreloads(ctx: PreloadContext) { focusFirstFocusableOrContainerInOutput(event.data.cellOrOutputId, event.data.alternateId); break; case 'decorations': { - let outputContainer = document.getElementById(event.data.cellId); + let outputContainer = $window.document.getElementById(event.data.cellId); if (!outputContainer) { viewModel.ensureOutputCell(event.data.cellId, -100000, true); - outputContainer = document.getElementById(event.data.cellId); + outputContainer = $window.document.getElementById(event.data.cellId); } outputContainer?.classList.add(...event.data.addedClassNames); outputContainer?.classList.remove(...event.data.removedClassNames); @@ -1557,7 +1560,7 @@ async function webviewPreloads(ctx: PreloadContext) { renderers.getRenderer(event.data.rendererId)?.receiveMessage(event.data.message); break; case 'notebookStyles': { - const documentStyle = document.documentElement.style; + const documentStyle = $window.document.documentElement.style; for (let i = documentStyle.length - 1; i >= 0; i--) { const property = documentStyle[i]; @@ -1937,7 +1940,7 @@ async function webviewPreloads(ctx: PreloadContext) { public async render(item: ExtendedOutputItem, preferredRendererId: string | undefined, element: HTMLElement, signal: AbortSignal): Promise { const primaryRenderer = this.findRenderer(preferredRendererId, item); if (!primaryRenderer) { - const errorMessage = (document.documentElement.style.getPropertyValue('--notebook-cell-renderer-not-found-error') || '').replace('$0', () => item.mime); + const errorMessage = ($window.document.documentElement.style.getPropertyValue('--notebook-cell-renderer-not-found-error') || '').replace('$0', () => item.mime); this.showRenderError(item, element, errorMessage); return; } @@ -1969,7 +1972,7 @@ async function webviewPreloads(ctx: PreloadContext) { } // All renderers have failed and there is nothing left to fallback to - const errorMessage = (document.documentElement.style.getPropertyValue('--notebook-cell-renderer-fallbacks-exhausted') || '').replace('$0', () => item.mime); + const errorMessage = ($window.document.documentElement.style.getPropertyValue('--notebook-cell-renderer-fallbacks-exhausted') || '').replace('$0', () => item.mime); this.showRenderError(item, element, errorMessage); } @@ -2290,7 +2293,7 @@ async function webviewPreloads(ctx: PreloadContext) { }] }); - const root = document.getElementById('container')!; + const root = $window.document.getElementById('container')!; const markupCell = document.createElement('div'); markupCell.className = 'markup'; markupCell.style.position = 'absolute'; @@ -2458,7 +2461,7 @@ async function webviewPreloads(ctx: PreloadContext) { private readonly outputElements = new Map(); constructor(cellId: string) { - const container = document.getElementById('container')!; + const container = $window.document.getElementById('container')!; const upperWrapperElement = createFocusSink(cellId); container.appendChild(upperWrapperElement); @@ -2751,12 +2754,12 @@ async function webviewPreloads(ctx: PreloadContext) { private dragOverlay?: HTMLElement; constructor() { - document.addEventListener('dragover', e => { + $window.document.addEventListener('dragover', e => { // Allow dropping dragged markup cells e.preventDefault(); }); - document.addEventListener('drop', e => { + $window.document.addEventListener('drop', e => { e.preventDefault(); const drag = this.currentDrag; @@ -2795,7 +2798,7 @@ async function webviewPreloads(ctx: PreloadContext) { this.dragOverlay.style.width = '100%'; this.dragOverlay.style.height = '100%'; this.dragOverlay.style.background = 'transparent'; - document.body.appendChild(this.dragOverlay); + $window.document.body.appendChild(this.dragOverlay); } (e.target as HTMLElement).style.zIndex = `${overlayZIndex + 1}`; (e.target as HTMLElement).classList.add('dragging'); @@ -2816,9 +2819,9 @@ async function webviewPreloads(ctx: PreloadContext) { cellId: cellId, dragOffsetY: this.currentDrag.clientY, }); - requestAnimationFrame(trySendDragUpdate); + $window.requestAnimationFrame(trySendDragUpdate); }; - requestAnimationFrame(trySendDragUpdate); + $window.requestAnimationFrame(trySendDragUpdate); } updateDrag(e: DragEvent, cellId: string) { @@ -2837,7 +2840,7 @@ async function webviewPreloads(ctx: PreloadContext) { }); if (this.dragOverlay) { - document.body.removeChild(this.dragOverlay); + $window.document.body.removeChild(this.dragOverlay); this.dragOverlay = undefined; } diff --git a/src/vs/workbench/contrib/notebook/browser/viewModel/cellEditorOptions.ts b/src/vs/workbench/contrib/notebook/browser/viewModel/cellEditorOptions.ts index 8fdeeaf4fd0..e093b79bc40 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewModel/cellEditorOptions.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewModel/cellEditorOptions.ts @@ -83,8 +83,7 @@ export class BaseCellEditorOptions extends Disposable implements IBaseCellEditor private _computeEditorOptions() { const editorOptions = deepClone(this.configurationService.getValue('editor', { overrideIdentifier: this.language })); - const layoutConfig = this.notebookOptions.getLayoutConfiguration(); - const editorOptionsOverrideRaw = layoutConfig.editorOptionsCustomizations ?? {}; + const editorOptionsOverrideRaw = this.notebookOptions.getDisplayOptions().editorOptionsCustomizations ?? {}; const editorOptionsOverride: { [key: string]: any } = {}; for (const key in editorOptionsOverrideRaw) { if (key.indexOf('editor.') === 0) { diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts index be4036595ac..f7b02b2e465 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorStickyScroll.ts @@ -116,7 +116,7 @@ export class NotebookStickyScroll extends Disposable { ) { super(); - if (this.notebookEditor.notebookOptions.getLayoutConfiguration().stickyScroll) { + if (this.notebookEditor.notebookOptions.getDisplayOptions().stickyScroll) { this.init(); } @@ -135,7 +135,7 @@ export class NotebookStickyScroll extends Disposable { } private onContextMenu(e: MouseEvent) { - const event = new StandardMouseEvent(e); + const event = new StandardMouseEvent(DOM.getWindow(this.domNode), e); this._contextMenuService.showContextMenu({ menuId: MenuId.NotebookStickyScrollContext, getAnchor: () => event, @@ -143,7 +143,7 @@ export class NotebookStickyScroll extends Disposable { } private updateConfig() { - if (this.notebookEditor.notebookOptions.getLayoutConfiguration().stickyScroll) { + if (this.notebookEditor.notebookOptions.getDisplayOptions().stickyScroll) { this.init(); } else { this._disposables.clear(); @@ -154,7 +154,7 @@ export class NotebookStickyScroll extends Disposable { } private setTop() { - if (this.notebookEditor.notebookOptions.getLayoutConfiguration().globalToolbar) { + if (this.notebookEditor.notebookOptions.getDisplayOptions().globalToolbar) { this.domNode.style.top = '26px'; } else { this.domNode.style.top = '0px'; diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts index 9da6712195e..d7cb7317788 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorToolbar.ts @@ -253,7 +253,7 @@ export class NotebookEditorWorkbenchToolbar extends Disposable { this._notebookGlobalActionsMenu = this._register(this.menuService.createMenu(this.notebookEditor.creationOptions.menuIds.notebookToolbar, this.contextKeyService)); this._register(this._notebookGlobalActionsMenu); - this._useGlobalToolbar = this.notebookOptions.getLayoutConfiguration().globalToolbar; + this._useGlobalToolbar = this.notebookOptions.getDisplayOptions().globalToolbar; this._renderLabel = this._convertConfiguration(this.configurationService.getValue(NotebookSetting.globalToolbarShowLabel)); this._updateStrategy(); @@ -337,7 +337,7 @@ export class NotebookEditorWorkbenchToolbar extends Disposable { this._register(this.notebookOptions.onDidChangeOptions(e => { if (e.globalToolbar !== undefined) { - this._useGlobalToolbar = this.notebookOptions.getLayoutConfiguration().globalToolbar; + this._useGlobalToolbar = this.notebookOptions.getDisplayOptions().globalToolbar; this._showNotebookActionsinEditorToolbar(); } })); diff --git a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts index 390ed06f677..19830132b60 100644 --- a/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts +++ b/src/vs/workbench/contrib/notebook/browser/viewParts/notebookEditorWidgetContextKeys.ts @@ -113,9 +113,9 @@ export class NotebookEditorContextKeys { return c.model.onDidChangeOutputs(() => { layoutDisposable.clear(); - layoutDisposable.add(DOM.scheduleAtNextAnimationFrame(() => { + layoutDisposable.add(DOM.scheduleAtNextAnimationFrame(DOM.getWindow(this._editor.getDomNode()), () => { recomputeOutputsExistence(); - }, DOM.getWindow(this._editor.getDomNode()))); + })); }); }; @@ -194,7 +194,7 @@ export class NotebookEditorContextKeys { } private _updateForNotebookOptions(): void { - const layout = this._editor.notebookOptions.getLayoutConfiguration(); + const layout = this._editor.notebookOptions.getDisplayOptions(); this._useConsolidatedOutputButton.set(layout.consolidatedOutputButton); this._cellToolbarLocation.set(this._editor.notebookOptions.computeCellToolbarLocation(this._editor.textModel?.viewType)); } diff --git a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts index 98d58e5bf3f..3c5e49d88c6 100644 --- a/src/vs/workbench/contrib/notebook/common/notebookCommon.ts +++ b/src/vs/workbench/contrib/notebook/common/notebookCommon.ts @@ -950,6 +950,7 @@ export const NotebookSetting = { outputScrolling: 'notebook.output.scrolling', textOutputLineLimit: 'notebook.output.textLineLimit', formatOnSave: 'notebook.formatOnSave.enabled', + insertFinalNewline: 'notebook.insertFinalNewline', formatOnCellExecution: 'notebook.formatOnCellExecution', codeActionsOnSave: 'notebook.codeActionsOnSave', outputWordWrap: 'notebook.output.wordWrap', diff --git a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts index 9bcd33dd4a2..274de133d37 100644 --- a/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts +++ b/src/vs/workbench/contrib/preferences/browser/settingsEditor2.ts @@ -10,7 +10,7 @@ import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar'; import { Button } from 'vs/base/browser/ui/button/button'; import { ITreeElement } from 'vs/base/browser/ui/tree/tree'; import { Action } from 'vs/base/common/actions'; -import { Delayer, IntervalTimer, ThrottledDelayer } from 'vs/base/common/async'; +import { Delayer, ThrottledDelayer } from 'vs/base/common/async'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; import { fromNow } from 'vs/base/common/date'; import { isCancellationError } from 'vs/base/common/errors'; @@ -1411,7 +1411,7 @@ export class SettingsEditor2 extends EditorPane { // If the context view is focused, delay rendering settings if (this.contextViewFocused()) { - const element = document.querySelector('.context-view'); + const element = DOM.getWindow(this.settingsTree.getHTMLElement()).document.querySelector('.context-view'); if (element) { this.scheduleRefresh(element as HTMLElement, key); } @@ -1867,8 +1867,8 @@ class SyncControls extends Disposable { this.updateLastSyncedTime(); })); - const updateLastSyncedTimer = this._register(new IntervalTimer()); - updateLastSyncedTimer.cancelAndSet(() => this.updateLastSyncedTime(), 60 * 1000); + const updateLastSyncedTimer = this._register(new DOM.WindowIntervalTimer()); + updateLastSyncedTimer.cancelAndSet(() => this.updateLastSyncedTime(), 60 * 1000, DOM.getWindow(container)); this.update(); this._register(this.userDataSyncService.onDidChangeStatus(() => { diff --git a/src/vs/workbench/contrib/remote/browser/remote.ts b/src/vs/workbench/contrib/remote/browser/remote.ts index f5d29d537e9..ffa5cdd0204 100644 --- a/src/vs/workbench/contrib/remote/browser/remote.ts +++ b/src/vs/workbench/contrib/remote/browser/remote.ts @@ -53,6 +53,7 @@ import { getRemoteName } from 'vs/platform/remote/common/remoteHosts'; import { getVirtualWorkspaceLocation } from 'vs/platform/workspace/common/virtualWorkspace'; import { IWalkthroughsService } from 'vs/workbench/contrib/welcomeGettingStarted/browser/gettingStartedService'; import { Schemas } from 'vs/base/common/network'; +import { mainWindow } from 'vs/base/browser/window'; interface IViewModel { onDidChangeHelpInformation: Event; @@ -746,17 +747,17 @@ class VisibleProgress { class ReconnectionTimer implements IDisposable { private readonly _parent: VisibleProgress; private readonly _completionTime: number; - private readonly _token: any; + private readonly _renderInterval: IDisposable; constructor(parent: VisibleProgress, completionTime: number) { this._parent = parent; this._completionTime = completionTime; - this._token = setInterval(() => this._render(), 1000); + this._renderInterval = dom.disposableWindowInterval(mainWindow, () => this._render(), 1000); this._render(); } public dispose(): void { - clearInterval(this._token); + this._renderInterval.dispose(); } private _render() { diff --git a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts index f3785c75aed..0c437d96d6e 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteExplorer.ts @@ -33,8 +33,13 @@ import { IHostService } from 'vs/workbench/services/host/browser/host'; import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry'; import { ILogService } from 'vs/platform/log/common/log'; import { IWorkbenchConfigurationService } from 'vs/workbench/services/configuration/common/configuration'; +import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment'; +import { Action } from 'vs/base/common/actions'; +import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; +import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences'; export const VIEWLET_ID = 'workbench.view.remote'; +export const CAN_PROC_FALLBACK = 'processPortForwardingFallback'; export class ForwardedPortsView extends Disposable implements IWorkbenchContribution { private contextKeyListener?: IDisposable; @@ -175,21 +180,26 @@ export class PortRestore implements IWorkbenchContribution { export class AutomaticPortForwarding extends Disposable implements IWorkbenchContribution { + private procForwarder: ProcAutomaticPortForwarding | undefined; + private outputForwarder: OutputAutomaticPortForwarding | undefined; + private portListener: IDisposable | undefined; constructor( - @ITerminalService terminalService: ITerminalService, - @INotificationService notificationService: INotificationService, - @IOpenerService openerService: IOpenerService, - @IExternalUriOpenerService externalOpenerService: IExternalUriOpenerService, - @IRemoteExplorerService remoteExplorerService: IRemoteExplorerService, - @IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService, - @IContextKeyService contextKeyService: IContextKeyService, - @IWorkbenchConfigurationService configurationService: IWorkbenchConfigurationService, - @IDebugService debugService: IDebugService, - @IRemoteAgentService remoteAgentService: IRemoteAgentService, - @ITunnelService tunnelService: ITunnelService, - @IHostService hostService: IHostService, - @ILogService logService: ILogService + @ITerminalService private readonly terminalService: ITerminalService, + @INotificationService private readonly notificationService: INotificationService, + @IOpenerService private readonly openerService: IOpenerService, + @IExternalUriOpenerService private readonly externalOpenerService: IExternalUriOpenerService, + @IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService, + @IWorkbenchEnvironmentService readonly environmentService: IWorkbenchEnvironmentService, + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IWorkbenchConfigurationService private readonly configurationService: IWorkbenchConfigurationService, + @IDebugService private readonly debugService: IDebugService, + @IRemoteAgentService readonly remoteAgentService: IRemoteAgentService, + @ITunnelService private readonly tunnelService: ITunnelService, + @IHostService private readonly hostService: IHostService, + @ILogService private readonly logService: ILogService, + @IStorageService private readonly storageService: IStorageService, + @IPreferencesService private readonly preferencesService: IPreferencesService, ) { super(); if (!environmentService.remoteAuthority) { @@ -197,25 +207,78 @@ export class AutomaticPortForwarding extends Disposable implements IWorkbenchCon } configurationService.whenRemoteConfigurationLoaded().then(() => remoteAgentService.getEnvironment()).then(environment => { - if (environment?.os !== OperatingSystem.Linux) { - Registry.as(ConfigurationExtensions.Configuration) - .registerDefaultConfigurations([{ overrides: { 'remote.autoForwardPortsSource': PORT_AUTO_SOURCE_SETTING_OUTPUT } }]); - this._register(new OutputAutomaticPortForwarding(terminalService, notificationService, openerService, externalOpenerService, - remoteExplorerService, configurationService, debugService, tunnelService, hostService, logService, contextKeyService, () => false)); - } else { - const useProc = () => (configurationService.getValue(PORT_AUTO_SOURCE_SETTING) === PORT_AUTO_SOURCE_SETTING_PROCESS); - if (useProc()) { - this._register(new ProcAutomaticPortForwarding(false, configurationService, remoteExplorerService, notificationService, - openerService, externalOpenerService, tunnelService, hostService, logService, contextKeyService)); - } else if (configurationService.getValue(PORT_AUTO_SOURCE_SETTING) === PORT_AUTO_SOURCE_SETTING_HYBRID) { - this._register(new ProcAutomaticPortForwarding(true, configurationService, remoteExplorerService, notificationService, - openerService, externalOpenerService, tunnelService, hostService, logService, contextKeyService)); + this.setup(environment); + this._register(configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(PORT_AUTO_SOURCE_SETTING)) { + this.setup(environment); } - this._register(new OutputAutomaticPortForwarding(terminalService, notificationService, openerService, externalOpenerService, - remoteExplorerService, configurationService, debugService, tunnelService, hostService, logService, contextKeyService, useProc)); - } + })); }); } + + private canFallbackToHybrid(): boolean { + return this.storageService.getBoolean(CAN_PROC_FALLBACK, StorageScope.WORKSPACE, true); + } + + private listenForPorts() { + if (this.procForwarder && !this.portListener && this.canFallbackToHybrid() && this.configurationService.getValue(PORT_AUTO_SOURCE_SETTING) === PORT_AUTO_SOURCE_SETTING_PROCESS) { + this.portListener = this._register(this.remoteExplorerService.tunnelModel.onForwardPort(async () => { + if (this.procForwarder?.forwarded && this.procForwarder.forwarded.size > 20) { + await this.configurationService.updateValue(PORT_AUTO_SOURCE_SETTING, PORT_AUTO_SOURCE_SETTING_HYBRID); + this.notificationService.notify({ + message: nls.localize('remote.autoForwardPortsSource.fallback', "Over 20 ports have been automatically forwarded. The `process` based automatic port forwarding has been switched to `hybrid` in settings. Some ports may no longer be detected."), + severity: Severity.Warning, + actions: { + primary: [ + new Action('switchBack', nls.localize('remote.autoForwardPortsSource.fallback.switchBack', "Undo"), undefined, true, async () => { + await this.configurationService.updateValue(PORT_AUTO_SOURCE_SETTING, PORT_AUTO_SOURCE_SETTING_PROCESS); + this.storageService.store(CAN_PROC_FALLBACK, false, StorageScope.WORKSPACE, StorageTarget.USER); + this.portListener?.dispose(); + this.portListener = undefined; + }), + new Action('showPortSourceSetting', nls.localize('remote.autoForwardPortsSource.fallback.showPortSourceSetting', "Show Setting"), undefined, true, async () => { + await this.preferencesService.openSettings({ + query: 'remote.autoForwardPortsSource' + }); + }) + ] + } + }); + } + })); + } else { + this.portListener?.dispose(); + this.portListener = undefined; + } + } + + + private setup(environment: IRemoteAgentEnvironment | null) { + const alreadyForwarded = this.procForwarder?.forwarded; + const isSwitch = this.outputForwarder || this.procForwarder; + this.procForwarder?.dispose(); + this.procForwarder = undefined; + this.outputForwarder?.dispose(); + this.outputForwarder = undefined; + if (environment?.os !== OperatingSystem.Linux) { + Registry.as(ConfigurationExtensions.Configuration) + .registerDefaultConfigurations([{ overrides: { 'remote.autoForwardPortsSource': PORT_AUTO_SOURCE_SETTING_OUTPUT } }]); + this.outputForwarder = this._register(new OutputAutomaticPortForwarding(this.terminalService, this.notificationService, this.openerService, this.externalOpenerService, + this.remoteExplorerService, this.configurationService, this.debugService, this.tunnelService, this.hostService, this.logService, this.contextKeyService, () => false)); + } else { + const useProc = () => (this.configurationService.getValue(PORT_AUTO_SOURCE_SETTING) === PORT_AUTO_SOURCE_SETTING_PROCESS); + if (useProc()) { + this.procForwarder = this._register(new ProcAutomaticPortForwarding(false, alreadyForwarded, !isSwitch, this.configurationService, this.remoteExplorerService, this.notificationService, + this.openerService, this.externalOpenerService, this.tunnelService, this.hostService, this.logService, this.contextKeyService)); + } else if (this.configurationService.getValue(PORT_AUTO_SOURCE_SETTING) === PORT_AUTO_SOURCE_SETTING_HYBRID) { + this.procForwarder = this._register(new ProcAutomaticPortForwarding(true, alreadyForwarded, !isSwitch, this.configurationService, this.remoteExplorerService, this.notificationService, + this.openerService, this.externalOpenerService, this.tunnelService, this.hostService, this.logService, this.contextKeyService)); + } + this.outputForwarder = this._register(new OutputAutomaticPortForwarding(this.terminalService, this.notificationService, this.openerService, this.externalOpenerService, + this.remoteExplorerService, this.configurationService, this.debugService, this.tunnelService, this.hostService, this.logService, this.contextKeyService, useProc)); + } + this.listenForPorts(); + } } class OnAutoForwardedAction extends Disposable { @@ -517,6 +580,8 @@ class ProcAutomaticPortForwarding extends Disposable { constructor( private readonly unforwardOnly: boolean, + readonly alreadyAutoForwarded: Set | undefined, + private readonly needsInitialCandidates: boolean, private readonly configurationService: IConfigurationService, readonly remoteExplorerService: IRemoteExplorerService, readonly notificationService: INotificationService, @@ -529,9 +594,14 @@ class ProcAutomaticPortForwarding extends Disposable { ) { super(); this.notifier = new OnAutoForwardedAction(notificationService, remoteExplorerService, openerService, externalOpenerService, tunnelService, hostService, logService, contextKeyService); + alreadyAutoForwarded?.forEach(port => this.autoForwarded.add(port)); this.initialize(); } + get forwarded(): Set { + return this.autoForwarded; + } + private async initialize() { if (!this.remoteExplorerService.tunnelModel.environmentTunnelsSet) { await new Promise(resolve => this.remoteExplorerService.tunnelModel.onEnvironmentTunnelsSet(() => resolve())); @@ -581,6 +651,10 @@ class ProcAutomaticPortForwarding extends Disposable { } private async setInitialCandidates() { + if (!this.needsInitialCandidates) { + this.logService.debug(`ForwardedPorts: (ProcForwarding) Not setting initial candidates`); + return; + } let startingCandidates = this.remoteExplorerService.tunnelModel.candidatesOrUndefined; if (!startingCandidates) { await new Promise(resolve => this.remoteExplorerService.tunnelModel.onCandidatesChanged(() => resolve())); diff --git a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts index 83e168989e4..582978e9052 100644 --- a/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts +++ b/src/vs/workbench/contrib/remote/browser/remoteIndicator.ts @@ -48,6 +48,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { infoIcon } from 'vs/workbench/contrib/extensions/browser/extensionsIcons'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { URI } from 'vs/base/common/uri'; +import { mainWindow } from 'vs/base/browser/window'; type ActionGroup = [string, Array]; @@ -274,8 +275,8 @@ export class RemoteStatusIndicator extends Disposable implements IWorkbenchContr // Online / Offline changes (web only) if (isWeb) { this._register(Event.any( - this._register(new DomEmitter(window, 'online')).event, - this._register(new DomEmitter(window, 'offline')).event + this._register(new DomEmitter(mainWindow, 'online')).event, + this._register(new DomEmitter(mainWindow, 'offline')).event )(() => this.setNetworkState(navigator.onLine ? 'online' : 'offline'))); } diff --git a/src/vs/workbench/contrib/remote/common/remote.contribution.ts b/src/vs/workbench/contrib/remote/common/remote.contribution.ts index 193ec3506a0..285c280ff46 100644 --- a/src/vs/workbench/contrib/remote/common/remote.contribution.ts +++ b/src/vs/workbench/contrib/remote/common/remote.contribution.ts @@ -314,7 +314,7 @@ Registry.as(ConfigurationExtensions.Configuration) }, 'remote.autoForwardPortsSource': { type: 'string', - markdownDescription: localize('remote.autoForwardPortsSource', "Sets the source from which ports are automatically forwarded when {0} is true. On Windows and Mac remotes, the `process` and `hybrid` options have no effect and `output` will be used. Requires a reload to take effect.", '`#remote.autoForwardPorts#`'), + markdownDescription: localize('remote.autoForwardPortsSource', "Sets the source from which ports are automatically forwarded when {0} is true. On Windows and Mac remotes, the `process` and `hybrid` options have no effect and `output` will be used.", '`#remote.autoForwardPorts#`'), enum: ['process', 'output', 'hybrid'], enumDescriptions: [ localize('remote.autoForwardPortsSource.process', "Ports will be automatically forwarded when discovered by watching for processes that are started and include a port."), diff --git a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts index f330ff686e1..f75a2ed4e20 100644 --- a/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts +++ b/src/vs/workbench/contrib/scm/browser/dirtydiffDecorator.ts @@ -281,6 +281,7 @@ class DirtyDiffWidget extends PeekViewWidget { if (usePosition) { this.show(position, height); } + this.editor.setPosition(position); this.editor.focus(); } diff --git a/src/vs/workbench/contrib/scm/browser/media/scm.css b/src/vs/workbench/contrib/scm/browser/media/scm.css index 0d13de2bb2f..3dd6c025c9b 100644 --- a/src/vs/workbench/contrib/scm/browser/media/scm.css +++ b/src/vs/workbench/contrib/scm/browser/media/scm.css @@ -144,18 +144,91 @@ padding-right: 4px; } -.scm-sync-view .monaco-list-row .history-item .monaco-icon-label .icon-container { +.scm-view .monaco-list-row .history-item { + display: flex; + align-items: center; +} + +.scm-view .monaco-list-row .history-item .stats-container { + display: flex; + font-size: 11px; + line-height: 11px; + margin-left: 6px; + color: var(--vscode-badge-foreground); +} + +.scm-view .monaco-list .monaco-list-row.focused .history-item .stats-container, +.scm-view .monaco-list .monaco-list-row.selected .history-item .stats-container, +.scm-view .monaco-list .monaco-list-row.focused .history-item .stats-container .monaco-icon-label .codicon, +.scm-view .monaco-list .monaco-list-row.selected .history-item .stats-container .monaco-icon-label .codicon { + color: var(--vscode-badge-foreground); +} + +.scm-view .monaco-list-row .history-item .stats-container .monaco-icon-label { + min-height: 18px; + padding: 0 2px; + border-radius: 2px; + border: 1px solid var(--vscode-contrastBorder); + background-color: var(--vscode-badge-background); +} + +.scm-view .monaco-list-row .history-item .stats-container .monaco-icon-label .codicon { + margin: 0 1px; +} + +.scm-view .monaco-list-row .history-item .stats-container .monaco-icon-label .codicon.codicon-diff-added, +.scm-view .monaco-list-row.focused .history-item .stats-container .monaco-icon-label .codicon.codicon-diff-added, +.scm-view .monaco-list-row.selected .history-item .stats-container .monaco-icon-label .codicon.codicon-diff-added { + color: var(--vscode-editorGutter-addedBackground); + margin-top: 1px; +} + +.scm-view .monaco-list-row .history-item .stats-container .monaco-icon-label .codicon.codicon-diff-removed, +.scm-view .monaco-list-row.focused .history-item .stats-container .monaco-icon-label .codicon.codicon-diff-removed, +.scm-view .monaco-list-row.selected .history-item .stats-container .monaco-icon-label .codicon.codicon-diff-removed { + color: var(--vscode-editorGutter-deletedBackground); + margin-top: 1px; +} + +.scm-view .monaco-list-row .history-item .stats-container .monaco-icon-label .monaco-highlighted-label { + display: flex; + align-items: center; +} + +.scm-view .monaco-list-row .history-item .stats-container .codicon { + font-size: 11px; +} + +.scm-view .monaco-list-row .history-item .monaco-icon-label .icon-container { display: flex; font-size: 14px; padding-right: 4px; } -.scm-sync-view .monaco-list-row .history-item .monaco-icon-label .avatar { +.scm-view .monaco-list-row .history-item .monaco-icon-label .avatar { width: 14px; height: 14px; border-radius: 14px; } +.scm-view .monaco-list-row .separator-container { + display: flex; + align-items: center; + padding-left: 11px; +} + +.scm-view .monaco-list-row .separator-container .label-name { + font-size: 10px; +} + +.scm-view .monaco-list-row .separator-container .separator { + display: flex; + flex-grow: 1; + height: 0; + margin-left: 6px; + border-top: 1px solid var(--vscode-sideBar-border); +} + .scm-view .monaco-list-row .history > .name, .scm-view .monaco-list-row .history-item-group > .name, .scm-view .monaco-list-row .resource-group > .name { @@ -200,7 +273,8 @@ } .scm-view .monaco-list .monaco-list-row .resource-group > .actions, -.scm-view .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions { +.scm-view .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions, +.scm-view .monaco-list .monaco-list-row .history-item > .actions { display: none; max-width: fit-content; } @@ -211,7 +285,10 @@ .scm-view .monaco-list .monaco-list-row:hover .resource > .name > .monaco-icon-label > .actions, .scm-view .monaco-list .monaco-list-row.selected .resource > .name > .monaco-icon-label > .actions, .scm-view .monaco-list .monaco-list-row.focused .resource > .name > .monaco-icon-label > .actions, -.scm-view .monaco-list:not(.selection-multiple) .monaco-list-row .resource:hover > .actions { +.scm-view .monaco-list:not(.selection-multiple) .monaco-list-row .resource:hover > .actions, +.scm-view .monaco-list .monaco-list-row:hover .history-item > .actions, +.scm-view .monaco-list .monaco-list-row.selected .history-item > .actions, +.scm-view .monaco-list .monaco-list-row.focused .history-item > .actions { display: block; } @@ -229,7 +306,8 @@ .scm-view.show-actions .scm-provider > .actions, .scm-view.show-actions > .monaco-list .monaco-list-row .scm-input > .scm-editor > .actions, .scm-view.show-actions > .monaco-list .monaco-list-row .resource-group > .actions, -.scm-view.show-actions > .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions { +.scm-view.show-actions > .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions, +.scm-view.show-actions > .monaco-list .monaco-list-row .history-item > .actions { display: block; } diff --git a/src/vs/workbench/contrib/scm/browser/menus.ts b/src/vs/workbench/contrib/scm/browser/menus.ts index e0100b69f5b..63e14262027 100644 --- a/src/vs/workbench/contrib/scm/browser/menus.ts +++ b/src/vs/workbench/contrib/scm/browser/menus.ts @@ -15,6 +15,7 @@ import { equals } from 'vs/base/common/arrays'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection'; import { localize } from 'vs/nls'; +import { ISCMHistoryItem, ISCMHistoryProviderMenus } from 'vs/workbench/contrib/scm/common/history'; function actionEquals(a: IAction, b: IAction): boolean { return a.id === b.id; @@ -159,12 +160,22 @@ export class SCMRepositoryMenus implements ISCMRepositoryMenus, IDisposable { return this._repositoryMenu; } + private _historyProviderMenu: SCMHistoryProviderMenus | undefined; + get historyProviderMenu(): SCMHistoryProviderMenus | undefined { + if (this.provider.historyProvider && !this._historyProviderMenu) { + this._historyProviderMenu = this.instantiationService.createInstance(SCMHistoryProviderMenus); + this.disposables.add(this._historyProviderMenu); + } + + return this._historyProviderMenu; + } + private readonly disposables = new DisposableStore(); constructor( private readonly provider: ISCMProvider, @IContextKeyService contextKeyService: IContextKeyService, - @IInstantiationService instantiationService: IInstantiationService, + @IInstantiationService private readonly instantiationService: IInstantiationService, @IMenuService private readonly menuService: IMenuService ) { this.contextKeyService = contextKeyService.createOverlay([ @@ -223,6 +234,39 @@ export class SCMRepositoryMenus implements ISCMRepositoryMenus, IDisposable { } } +export class SCMHistoryProviderMenus implements ISCMHistoryProviderMenus, IDisposable { + + private readonly historyItemMenus = new Map(); + private readonly disposables = new DisposableStore(); + + constructor( + @IContextKeyService private readonly contextKeyService: IContextKeyService, + @IMenuService private readonly menuService: IMenuService) { } + + getHistoryItemMenu(historyItem: ISCMHistoryItem): IMenu { + return this.getOrCreateHistoryItemMenu(historyItem); + } + + private getOrCreateHistoryItemMenu(historyItem: ISCMHistoryItem): IMenu { + let result = this.historyItemMenus.get(historyItem); + + if (!result) { + const contextKeyService = this.contextKeyService.createOverlay([ + ['scmHistoryItem', historyItem.id], + ]); + + result = this.menuService.createMenu(MenuId.SCMHistoryItem, contextKeyService); + this.historyItemMenus.set(historyItem, result); + } + + return result; + } + + dispose(): void { + this.disposables.dispose(); + } +} + export class SCMMenus implements ISCMMenus, IDisposable { readonly titleMenu: SCMTitleMenu; diff --git a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts index 5df3f7ae41d..0c11c5ecebd 100644 --- a/src/vs/workbench/contrib/scm/browser/scm.contribution.ts +++ b/src/vs/workbench/contrib/scm/browser/scm.contribution.ts @@ -32,8 +32,8 @@ import { Context as SuggestContext } from 'vs/editor/contrib/suggest/browser/sug import { MANAGE_TRUST_COMMAND_ID, WorkspaceTrustContext } from 'vs/workbench/contrib/workspace/common/workspace'; import { IQuickDiffService } from 'vs/workbench/contrib/scm/common/quickDiff'; import { QuickDiffService } from 'vs/workbench/contrib/scm/common/quickDiffService'; -import { SCMSyncViewPane } from 'vs/workbench/contrib/scm/browser/scmSyncViewPane'; import { getActiveElement } from 'vs/base/browser/dom'; +import { SCMSyncViewPane } from 'vs/workbench/contrib/scm/browser/scmSyncViewPane'; ModesRegistry.registerLanguage({ id: 'scminput', @@ -81,7 +81,7 @@ viewsRegistry.registerViews([{ ctorDescriptor: new SyncDescriptor(SCMViewPane), canToggleVisibility: true, canMoveView: true, - weight: 60, + weight: 80, order: -999, containerIcon: sourceControlViewIcon, openCommandActionDescriptor: { @@ -302,6 +302,25 @@ Registry.as(ConfigurationExtensions.Configuration).regis type: 'boolean', description: localize('showSyncView', "Controls whether the Source Control Sync view is shown."), default: false + }, + 'scm.experimental.showSyncInformation': { + type: 'object', + description: localize('showSyncInformation', "Controls whether incoming/outgoing changes are shown in the Source Control view."), + additionalProperties: false, + properties: { + 'incoming': { + type: 'boolean', + description: localize('showSyncInformationIncoming', "Show incoming changes in the Source Control view."), + }, + 'outgoing': { + type: 'boolean', + description: localize('showSyncInformationOutgoing', "Show outgoing changes in the Source Control view."), + }, + }, + default: { + 'incoming': false, + 'outgoing': false + } } } }); diff --git a/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts index b449ea6d28a..4805711e193 100644 --- a/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmSyncViewPane.ts @@ -32,7 +32,6 @@ import { ActionButtonRenderer } from 'vs/workbench/contrib/scm/browser/scmViewPa import { getActionViewItemProvider, isSCMActionButton, isSCMRepository, isSCMRepositoryArray } from 'vs/workbench/contrib/scm/browser/util'; import { ISCMActionButton, ISCMRepository, ISCMViewService, ISCMViewVisibleRepositoryChangeEvent, SYNC_VIEW_PANE_ID } from 'vs/workbench/contrib/scm/common/scm'; import { comparePaths } from 'vs/base/common/comparers'; -import { ISCMHistoryItem, ISCMHistoryItemChange, ISCMHistoryItemGroup } from 'vs/workbench/contrib/scm/common/history'; import { localize } from 'vs/nls'; import { Iterable } from 'vs/base/common/iterator'; import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget'; @@ -50,6 +49,7 @@ import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree'; import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel'; import { IResourceNode, ResourceTree } from 'vs/base/common/resourceTree'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { SCMHistoryItemChangeTreeElement, SCMHistoryItemGroupTreeElement, SCMHistoryItemTreeElement } from 'vs/workbench/contrib/scm/common/history'; type SCMHistoryItemChangeResourceTreeNode = IResourceNode; type TreeElement = ISCMRepository[] | ISCMRepository | ISCMActionButton | SCMHistoryItemGroupTreeElement | SCMHistoryItemTreeElement | SCMHistoryItemChangeTreeElement | SCMHistoryItemChangeResourceTreeNode; @@ -117,24 +117,6 @@ const ContextKeys = { ViewMode: new RawContextKey('scmSyncViewMode', ViewMode.List), }; -interface SCMHistoryItemGroupTreeElement extends ISCMHistoryItemGroup { - readonly description?: string; - readonly ancestor?: string; - readonly count?: number; - readonly repository: ISCMRepository; - readonly type: 'historyItemGroup'; -} - -interface SCMHistoryItemTreeElement extends ISCMHistoryItem { - readonly historyItemGroup: SCMHistoryItemGroupTreeElement; - readonly type: 'historyItem'; -} - -interface SCMHistoryItemChangeTreeElement extends ISCMHistoryItemChange { - readonly historyItem: SCMHistoryItemTreeElement; - readonly type: 'historyItemChange'; -} - class ListDelegate implements IListVirtualDelegate { getHeight(element: any): number { diff --git a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts index b52d25709ba..21214286731 100644 --- a/src/vs/workbench/contrib/scm/browser/scmViewPane.ts +++ b/src/vs/workbench/contrib/scm/browser/scmViewPane.ts @@ -8,8 +8,9 @@ import { Event, Emitter } from 'vs/base/common/event'; import { basename, dirname } from 'vs/base/common/resources'; import { IDisposable, Disposable, DisposableStore, combinedDisposable, dispose, toDisposable, MutableDisposable, IReference, DisposableMap } from 'vs/base/common/lifecycle'; import { ViewPane, IViewPaneOptions, ViewAction } from 'vs/workbench/browser/parts/views/viewPane'; -import { append, $, Dimension, asCSSUrl, trackFocus, clearNode } from 'vs/base/browser/dom'; +import { append, $, Dimension, asCSSUrl, trackFocus, clearNode, prepend } from 'vs/base/browser/dom'; import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list'; +import { SCMHistoryItemChangeTreeElement, SCMHistoryItemGroupTreeElement, SCMHistoryItemTreeElement, SCMViewSeparatorElement } from 'vs/workbench/contrib/scm/common/history'; import { ISCMResourceGroup, ISCMResource, InputValidationType, ISCMRepository, ISCMInput, IInputValidation, ISCMViewService, ISCMViewVisibleRepositoryChangeEvent, ISCMService, SCMInputChangeReason, VIEW_PANE_ID, ISCMActionButton, ISCMActionButtonDescriptor, ISCMRepositorySortKey, REPOSITORIES_VIEW_PANE_ID } from 'vs/workbench/contrib/scm/common/scm'; import { ResourceLabels, IResourceLabel, IFileLabelOptions } from 'vs/workbench/browser/labels'; import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge'; @@ -23,7 +24,7 @@ import { MenuItemAction, IMenuService, registerAction2, MenuId, IAction2Options, import { IAction, ActionRunner, Action, Separator } from 'vs/base/common/actions'; import { ActionBar, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; import { IThemeService, IFileIconTheme } from 'vs/platform/theme/common/themeService'; -import { isSCMResource, isSCMResourceGroup, connectPrimaryMenuToInlineActionBar, isSCMRepository, isSCMInput, collectContextMenuActions, getActionViewItemProvider, isSCMActionButton, isSCMViewService } from './util'; +import { isSCMResource, isSCMResourceGroup, connectPrimaryMenuToInlineActionBar, isSCMRepository, isSCMInput, collectContextMenuActions, getActionViewItemProvider, isSCMActionButton, isSCMViewService, isSCMHistoryItemGroupTreeElement, isSCMHistoryItemTreeElement, isSCMHistoryItemChangeTreeElement, toDiffEditorArguments, isSCMResourceNode, isSCMHistoryItemChangeNode, isSCMViewSeparator } from './util'; import { WorkbenchCompressibleAsyncDataTree, IOpenEvent } from 'vs/platform/list/browser/listService'; import { IConfigurationService, ConfigurationTarget, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration'; import { disposableTimeout, ThrottledDelayer, Sequencer } from 'vs/base/common/async'; @@ -96,8 +97,24 @@ import { FormatOnType } from 'vs/editor/contrib/format/browser/formatActions'; import { EditorOption } from 'vs/editor/common/config/editorOptions'; import { IAsyncDataTreeViewState, ITreeCompressionDelegate } from 'vs/base/browser/ui/tree/asyncDataTree'; import { IUriIdentityService } from 'vs/platform/uriIdentity/common/uriIdentity'; +import { EditOperation } from 'vs/editor/common/core/editOperation'; +import { stripIcons } from 'vs/base/common/iconLabels'; +import { IconLabel } from 'vs/base/browser/ui/iconLabel/iconLabel'; -type TreeElement = ISCMRepository | ISCMInput | ISCMActionButton | ISCMResourceGroup | IResourceNode | ISCMResource; +// type SCMResourceTreeNode = IResourceNode; +// type SCMHistoryItemChangeResourceTreeNode = IResourceNode; +type TreeElement = + ISCMRepository | + ISCMInput | + ISCMActionButton | + ISCMResourceGroup | + ISCMResource | + IResourceNode | + SCMHistoryItemGroupTreeElement | + SCMHistoryItemTreeElement | + SCMHistoryItemChangeTreeElement | + IResourceNode | + SCMViewSeparatorElement; interface ISCMLayout { height: number | undefined; @@ -677,6 +694,239 @@ class ResourceRenderer implements ICompressibleTreeRenderer { + + static readonly TEMPLATE_ID = 'history-item-group'; + get templateId(): string { return HistoryItemGroupRenderer.TEMPLATE_ID; } + + renderTemplate(container: HTMLElement) { + // hack + (container.parentElement!.parentElement!.querySelector('.monaco-tl-twistie')! as HTMLElement).classList.add('force-twistie'); + + const element = append(container, $('.history-item-group')); + const label = new IconLabel(element, { supportIcons: true }); + const countContainer = append(element, $('.count')); + const count = new CountBadge(countContainer, {}, defaultCountBadgeStyles); + + return { label, count, disposables: new DisposableStore() }; + } + + renderElement(node: ITreeNode, index: number, templateData: HistoryItemGroupTemplate, height: number | undefined): void { + const historyItemGroup = node.element; + templateData.label.setLabel(historyItemGroup.label, historyItemGroup.description); + templateData.count.setCount(historyItemGroup.count ?? 0); + } + + renderCompressedElements(node: ITreeNode, void>, index: number, templateData: HistoryItemGroupTemplate, height: number | undefined): void { + throw new Error('Should never happen since node is incompressible'); + } + disposeTemplate(templateData: HistoryItemGroupTemplate): void { + templateData.disposables.dispose(); + } +} + +interface HistoryItemTemplate { + readonly iconContainer: HTMLElement; + readonly label: IconLabel; + readonly statsContainer: HTMLElement; + readonly statsLabel: IconLabel; + readonly actionBar: ActionBar; + readonly elementDisposables: DisposableStore; + readonly disposables: IDisposable; +} + +class HistoryItemRenderer implements ICompressibleTreeRenderer { + + static readonly TEMPLATE_ID = 'history-item'; + get templateId(): string { return HistoryItemRenderer.TEMPLATE_ID; } + + constructor( + private actionViewItemProvider: IActionViewItemProvider, + @ISCMViewService private scmViewService: ISCMViewService) { } + + renderTemplate(container: HTMLElement): HistoryItemTemplate { + // hack + (container.parentElement!.parentElement!.querySelector('.monaco-tl-twistie')! as HTMLElement).classList.add('force-twistie'); + + const element = append(container, $('.history-item')); + + const iconLabel = new IconLabel(element, { supportIcons: true }); + const iconContainer = prepend(iconLabel.element, $('.icon-container')); + + const disposables = new DisposableStore(); + const actionsContainer = append(element, $('.actions')); + const actionBar = new ActionBar(actionsContainer, { actionViewItemProvider: this.actionViewItemProvider }); + disposables.add(actionBar); + + const statsContainer = append(element, $('.stats-container')); + const statsLabel = new IconLabel(statsContainer, { supportIcons: true }); + + return { iconContainer, label: iconLabel, actionBar, statsContainer, statsLabel, elementDisposables: new DisposableStore(), disposables }; + } + + renderElement(node: ITreeNode, index: number, templateData: HistoryItemTemplate, height: number | undefined): void { + const historyItem = node.element; + + templateData.iconContainer.className = 'icon-container'; + if (historyItem.icon && ThemeIcon.isThemeIcon(historyItem.icon)) { + templateData.iconContainer.classList.add(...ThemeIcon.asClassNameArray(historyItem.icon)); + } + + templateData.label.setLabel(historyItem.label, historyItem.description); + + templateData.actionBar.clear(); + templateData.actionBar.context = historyItem; + + const menus = this.scmViewService.menus.getRepositoryMenus(historyItem.historyItemGroup.repository.provider); + if (menus.historyProviderMenu) { + templateData.elementDisposables.add(connectPrimaryMenuToInlineActionBar(menus.historyProviderMenu.getHistoryItemMenu(historyItem), templateData.actionBar)); + } + + this.renderStatistics(node, index, templateData, height); + } + + renderCompressedElements(node: ITreeNode, void>, index: number, templateData: HistoryItemTemplate, height: number | undefined): void { + throw new Error('Should never happen since node is incompressible'); + } + + private renderStatistics(node: ITreeNode, index: number, templateData: HistoryItemTemplate, height: number | undefined): void { + const historyItem = node.element; + + if (historyItem.statistics?.files || historyItem.statistics?.insertions || historyItem.statistics?.deletions) { + const statsLabelTitle: string[] = []; + + const filesLabel = historyItem.statistics?.files ? `${historyItem.statistics.files}$(files)` : ''; + const insertionsLabel = historyItem.statistics?.insertions ? ` ${historyItem.statistics.insertions}$(diff-added)` : ''; + const deletionsLabel = historyItem.statistics?.deletions ? ` ${historyItem.statistics.deletions}$(diff-removed)` : ''; + + if (historyItem.statistics?.files) { + const filesDescription = historyItem.statistics.files === 1 ? + localize('fileChanged', "file changed") : localize('filesChanged', "files changed"); + statsLabelTitle.push(`${historyItem.statistics.files} ${filesDescription}`); + } + + if (historyItem.statistics?.insertions) { + const insertionsDescription = localize('insertions', "insertions{0}", '(+)'); + statsLabelTitle.push(`${historyItem.statistics.insertions} ${insertionsDescription}`); + } + + if (historyItem.statistics?.deletions) { + const deletionsDescription = localize('deletions', "deletions{0}", '(-)'); + statsLabelTitle.push(`${historyItem.statistics.deletions} ${deletionsDescription}`); + } + + templateData.statsLabel.setLabel(`${filesLabel}${insertionsLabel}${deletionsLabel}`, undefined, { title: statsLabelTitle.join(', ') }); + templateData.statsContainer.style.display = ''; + } else { + templateData.statsContainer.style.display = 'none'; + } + } + + disposeElement(element: ITreeNode, index: number, templateData: HistoryItemTemplate, height: number | undefined): void { + templateData.elementDisposables.clear(); + } + disposeTemplate(templateData: HistoryItemTemplate): void { + templateData.disposables.dispose(); + } +} + +interface HistoryItemChangeTemplate { + readonly element: HTMLElement; + readonly name: HTMLElement; + readonly fileLabel: IResourceLabel; + readonly decorationIcon: HTMLElement; + readonly disposables: IDisposable; +} + +class HistoryItemChangeRenderer implements ICompressibleTreeRenderer, void, HistoryItemChangeTemplate> { + + static readonly TEMPLATE_ID = 'historyItemChange'; + get templateId(): string { return HistoryItemChangeRenderer.TEMPLATE_ID; } + + constructor( + private readonly viewMode: () => ViewMode, + private readonly labels: ResourceLabels, + @ILabelService private labelService: ILabelService) { } + + renderTemplate(container: HTMLElement): HistoryItemChangeTemplate { + const element = append(container, $('.change')); + const name = append(element, $('.name')); + const fileLabel = this.labels.create(name, { supportDescriptionHighlights: true, supportHighlights: true }); + const decorationIcon = append(element, $('.decoration-icon')); + + return { element, name, fileLabel, decorationIcon, disposables: new DisposableStore() }; + } + + renderElement(node: ITreeNode, void>, index: number, templateData: HistoryItemChangeTemplate, height: number | undefined): void { + const historyItemChangeOrFolder = node.element; + const uri = ResourceTree.isResourceNode(historyItemChangeOrFolder) ? historyItemChangeOrFolder.element?.uri ?? historyItemChangeOrFolder.uri : historyItemChangeOrFolder.uri; + const fileKind = ResourceTree.isResourceNode(historyItemChangeOrFolder) ? FileKind.FOLDER : FileKind.FILE; + const hidePath = this.viewMode() === ViewMode.Tree; + + templateData.fileLabel.setFile(uri, { fileDecorations: { colors: false, badges: true }, fileKind, hidePath, }); + } + + renderCompressedElements(node: ITreeNode>, void>, index: number, templateData: HistoryItemChangeTemplate, height: number | undefined): void { + const compressed = node.element as ICompressedTreeNode>; + + const folder = compressed.elements[compressed.elements.length - 1]; + const label = compressed.elements.map(e => e.name); + + templateData.fileLabel.setResource({ resource: folder.uri, name: label }, { + fileDecorations: { colors: false, badges: true }, + fileKind: FileKind.FOLDER, + separator: this.labelService.getSeparator(folder.uri.scheme) + }); + } + + disposeTemplate(templateData: HistoryItemChangeTemplate): void { + templateData.disposables.dispose(); + } +} + +interface SeparatorTemplate { + readonly label: IconLabel; + readonly disposables: IDisposable; +} + +class SeparatorRenderer implements ICompressibleTreeRenderer { + + static readonly TEMPLATE_ID = 'separator'; + get templateId(): string { return SeparatorRenderer.TEMPLATE_ID; } + + renderTemplate(container: HTMLElement): SeparatorTemplate { + // hack + (container.parentElement!.parentElement!.querySelector('.monaco-tl-twistie')! as HTMLElement).classList.add('force-no-twistie'); + + // Use default cursor & disable hover for list item + container.parentElement!.parentElement!.classList.add('cursor-default', 'force-no-hover'); + + const element = append(container, $('.separator-container')); + const label = new IconLabel(element, { supportIcons: true, }); + append(element, $('.separator')); + + return { label, disposables: new DisposableStore() }; + } + renderElement(element: ITreeNode, index: number, templateData: SeparatorTemplate, height: number | undefined): void { + templateData.label.setLabel(element.element.label); + } + + renderCompressedElements(node: ITreeNode, void>, index: number, templateData: SeparatorTemplate, height: number | undefined): void { + throw new Error('Should never happen since node is incompressible'); + } + + disposeTemplate(templateData: SeparatorTemplate): void { + throw new Error('Method not implemented.'); + } + +} + class ListDelegate implements IListVirtualDelegate { constructor(private readonly inputRenderer: InputRenderer) { } @@ -698,10 +948,20 @@ class ListDelegate implements IListVirtualDelegate { return InputRenderer.TEMPLATE_ID; } else if (isSCMActionButton(element)) { return ActionButtonRenderer.TEMPLATE_ID; - } else if (ResourceTree.isResourceNode(element) || isSCMResource(element)) { - return ResourceRenderer.TEMPLATE_ID; - } else { + } else if (isSCMResourceGroup(element)) { return ResourceGroupRenderer.TEMPLATE_ID; + } else if (isSCMResource(element) || isSCMResourceNode(element)) { + return ResourceRenderer.TEMPLATE_ID; + } else if (isSCMHistoryItemGroupTreeElement(element)) { + return HistoryItemGroupRenderer.TEMPLATE_ID; + } else if (isSCMHistoryItemTreeElement(element)) { + return HistoryItemRenderer.TEMPLATE_ID; + } else if (isSCMHistoryItemChangeTreeElement(element) || isSCMHistoryItemChangeNode(element)) { + return HistoryItemChangeRenderer.TEMPLATE_ID; + } else if (isSCMViewSeparator(element)) { + return SeparatorRenderer.TEMPLATE_ID; + } else { + throw new Error('Unknown element'); } } } @@ -725,6 +985,10 @@ class SCMTreeFilter implements ITreeFilter { return true; } else if (isSCMResourceGroup(element)) { return element.resources.length > 0 || !element.hideWhenEmpty; + } else if (isSCMHistoryItemGroupTreeElement(element)) { + return (element.count ?? 0) > 0; + } else if (isSCMViewSeparator(element)) { + return element.repository.provider.groups.some(g => g.resources.length > 0); } else { return true; } @@ -758,6 +1022,14 @@ export class SCMTreeSorter implements ITreeSorter { return 1; } + if (isSCMViewSeparator(one)) { + if (!isSCMHistoryItemGroupTreeElement(other) && !isSCMResourceGroup(other)) { + throw new Error('Invalid comparison'); + } + + return 0; + } + if (isSCMResourceGroup(one)) { if (!isSCMResourceGroup(other)) { throw new Error('Invalid comparison'); @@ -766,7 +1038,44 @@ export class SCMTreeSorter implements ITreeSorter { return 0; } - // List + if (isSCMHistoryItemGroupTreeElement(one)) { + if (!isSCMHistoryItemGroupTreeElement(other) && !isSCMResourceGroup(other) && !isSCMViewSeparator(other)) { + throw new Error('Invalid comparison'); + } + + return 0; + } + + if (isSCMHistoryItemTreeElement(one)) { + if (!isSCMHistoryItemTreeElement(other)) { + throw new Error('Invalid comparison'); + } + + return 0; + } + + if (isSCMHistoryItemChangeTreeElement(one) || isSCMHistoryItemChangeNode(one)) { + // List + if (this.viewMode() === ViewMode.List) { + if (!isSCMHistoryItemChangeTreeElement(other)) { + throw new Error('Invalid comparison'); + } + + return comparePaths(one.uri.fsPath, other.uri.fsPath); + } + + // Tree + if (!isSCMHistoryItemChangeTreeElement(other) && !isSCMHistoryItemChangeNode(other)) { + throw new Error('Invalid comparison'); + } + + const oneName = isSCMHistoryItemChangeNode(one) ? one.name : basename(one.uri); + const otherName = isSCMHistoryItemChangeNode(other) ? other.name : basename(other.uri); + + return compareFileNames(oneName, otherName); + } + + // Resource (List) if (this.viewMode() === ViewMode.List) { // FileName if (this.viewSortKey() === ViewSortKey.Name) { @@ -793,7 +1102,7 @@ export class SCMTreeSorter implements ITreeSorter { return comparePaths(onePath, otherPath); } - // Tree + // Resource (Tree) const oneIsDirectory = ResourceTree.isResourceNode(one); const otherIsDirectory = ResourceTree.isResourceNode(other); @@ -822,19 +1131,23 @@ export class SCMTreeKeyboardNavigationLabelProvider implements ICompressibleKeyb return undefined; } else if (isSCMResourceGroup(element)) { return element.label; + } else if (isSCMHistoryItemGroupTreeElement(element)) { + return element.label; + } else if (isSCMHistoryItemTreeElement(element)) { + return element.label; + } else if (isSCMViewSeparator(element)) { + return element.label; } else { if (this.viewMode() === ViewMode.List) { // In List mode match using the file name and the path. // Since we want to match both on the file name and the // full path we return an array of labels. A match in the // file name takes precedence over a match in the path. - const fileName = basename(element.sourceUri); - const filePath = this.labelService.getUriLabel(element.sourceUri, { relative: true }); - - return [fileName, filePath]; + const uri = isSCMResource(element) ? element.sourceUri : element.uri; + return [basename(uri), this.labelService.getUriLabel(uri, { relative: true })]; } else { // In Tree mode only match using the file name - return basename(element.sourceUri); + return basename(isSCMResource(element) ? element.sourceUri : element.uri); } } } @@ -846,10 +1159,7 @@ export class SCMTreeKeyboardNavigationLabelProvider implements ICompressibleKeyb } function getSCMResourceId(element: TreeElement): string { - if (ResourceTree.isResourceNode(element)) { - const group = element.context; - return `folder:${group.provider.id}/${group.id}/$FOLDER/${element.uri.toString()}`; - } else if (isSCMRepository(element)) { + if (isSCMRepository(element)) { const provider = element.provider; return `repo:${provider.id}`; } else if (isSCMInput(element)) { @@ -858,13 +1168,38 @@ function getSCMResourceId(element: TreeElement): string { } else if (isSCMActionButton(element)) { const provider = element.repository.provider; return `actionButton:${provider.id}`; + } else if (isSCMResourceGroup(element)) { + const provider = element.provider; + return `resourceGroup:${provider.id}/${element.id}`; } else if (isSCMResource(element)) { const group = element.resourceGroup; const provider = group.provider; return `resource:${provider.id}/${group.id}/${element.sourceUri.toString()}`; + } else if (isSCMResourceNode(element)) { + const group = element.context; + return `folder:${group.provider.id}/${group.id}/$FOLDER/${element.uri.toString()}`; + } else if (isSCMHistoryItemGroupTreeElement(element)) { + const provider = element.repository.provider; + return `historyItemGroup:${provider.id}/${element.id}`; + } else if (isSCMHistoryItemTreeElement(element)) { + const historyItemGroup = element.historyItemGroup; + const provider = historyItemGroup.repository.provider; + return `historyItem:${provider.id}/${historyItemGroup.id}/${element.id}`; + } else if (isSCMHistoryItemChangeTreeElement(element)) { + const historyItem = element.historyItem; + const historyItemGroup = historyItem.historyItemGroup; + const provider = historyItemGroup.repository.provider; + return `historyItemChange:${provider.id}/${historyItemGroup.id}/${historyItem.id}/${element.uri.toString()}`; + } else if (isSCMHistoryItemChangeNode(element)) { + const historyItem = element.context; + const historyItemGroup = historyItem.historyItemGroup; + const provider = historyItemGroup.repository.provider; + return `folder:${provider.id}/${historyItemGroup.id}/${historyItem.id}/$FOLDER/${element.uri.toString()}`; + } else if (isSCMViewSeparator(element)) { + const provider = element.repository.provider; + return `separator:${provider.id}`; } else { - const provider = element.provider; - return `group:${provider.id}/${element.id}`; + throw new Error('Invalid tree element'); } } @@ -907,6 +1242,21 @@ export class SCMAccessibilityProvider implements IListAccessibilityProvider { registerAction2(CollapseAllRepositoriesAction); registerAction2(ExpandAllRepositoriesAction); +class HistoryItemViewChangesAction extends Action2 { + + constructor() { + super({ + id: `workbench.scm.action.historyItemViewChanges`, + title: localize('historyItemViewChanges', "View Changes"), + icon: Codicon.search, + f1: false, + menu: { + id: MenuId.SCMHistoryItem, + group: 'inline' + } + }); + } + + async run(accessor: ServicesAccessor, historyItem: SCMHistoryItemTreeElement): Promise { + const commandService = accessor.get(ICommandService); + + const historyProvider = historyItem.historyItemGroup.repository.provider.historyProvider; + if (!historyProvider) { + return; + } + + const historyItemChanges = await historyProvider.provideHistoryItemChanges(historyItem.id); + if (!historyItemChanges || historyItemChanges.length === 0) { + return; + } + + let [originalRef, modifiedRef] = historyItem.id.includes('..') + ? historyItem.id.split('..') : [undefined, historyItem.id]; + + if (!originalRef) { + originalRef = historyItem.parentIds.length > 0 ? historyItem.parentIds[0] : `${modifiedRef}^`; + } + + const title = localize('historyItemChangesTitle', "Changes ({0} ↔ {1})", originalRef.substring(0, 8), modifiedRef.substring(0, 8)); + const args = historyItemChanges.map(change => [change.uri, change.originalUri, change.modifiedUri]); + + return commandService.executeCommand('_workbench.changes', title, args); + } + +} + +registerAction2(HistoryItemViewChangesAction); + class SCMInputWidget { private static readonly ValidationTimeouts: { [severity: number]: number } = { @@ -1364,8 +1759,8 @@ class SCMInputWidget { } const uri = input.repository.provider.inputBoxDocumentUri; - if (this.configurationService.getValue('editor.wordBasedSuggestions', { resource: uri }) !== false) { - this.configurationService.updateValue('editor.wordBasedSuggestions', false, { resource: uri }, ConfigurationTarget.MEMORY); + if (this.configurationService.getValue('editor.wordBasedSuggestions', { resource: uri }) !== 'off') { + this.configurationService.updateValue('editor.wordBasedSuggestions', 'off', { resource: uri }, ConfigurationTarget.MEMORY); } const modelValue: typeof this.model = { input, textModelRef: undefined }; @@ -1407,10 +1802,13 @@ class SCMInputWidget { // Keep model in sync with API textModel.setValue(input.value); this.repositoryDisposables.add(input.onDidChange(({ value, reason }) => { - if (value === textModel.getValue()) { // circuit breaker + const currentValue = textModel.getValue(); + if (value === currentValue) { // circuit breaker return; } - textModel.setValue(value); + + textModel.pushStackElement(); + textModel.pushEditOperations(null, [EditOperation.replaceMove(textModel.getFullModelRange(), value)], () => []); const position = reason === SCMInputChangeReason.HistoryPrevious ? textModel.getFullModelRange().getStartPosition() @@ -1914,6 +2312,9 @@ export class SCMViewPane extends ViewPane { private _alwaysShowRepositories = false; get alwaysShowRepositories(): boolean { return this._alwaysShowRepositories; } + private _showSyncInformation: { incoming: boolean; outgoing: boolean } = { incoming: false, outgoing: false }; + get showSyncInformation(): { incoming: boolean; outgoing: boolean } { return this._showSyncInformation; } + private readonly items = new DisposableMap(); private readonly visibilityDisposables = new DisposableStore(); private readonly asyncOperationSequencer = new Sequencer(); @@ -2041,6 +2442,14 @@ export class SCMViewPane extends ViewPane { Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.alwaysShowRepositories'), this.visibilityDisposables)(updateRepositoryVisibility, this, this.visibilityDisposables); updateRepositoryVisibility(); + const updateSyncInformationVisibility = () => { + const setting = this.configurationService.getValue<{ incoming: boolean; outgoing: boolean }>('scm.experimental.showSyncInformation'); + this._showSyncInformation = { incoming: setting.incoming === true, outgoing: setting.outgoing === true }; + this.updateChildren(); + }; + Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.experimental.showSyncInformation'), this.visibilityDisposables)(updateSyncInformationVisibility, this, this.visibilityDisposables); + updateSyncInformationVisibility(); + // Add visible repositories this.scmViewService.onDidChangeVisibleRepositories(this.onDidChangeVisibleRepositories, this, this.visibilityDisposables); this.onDidChangeVisibleRepositories({ added: this.scmViewService.visibleRepositories, removed: Iterable.empty() }); @@ -2093,9 +2502,13 @@ export class SCMViewPane extends ViewPane { this.actionButtonRenderer, this.instantiationService.createInstance(RepositoryRenderer, getActionViewItemProvider(this.instantiationService)), this.instantiationService.createInstance(ResourceGroupRenderer, getActionViewItemProvider(this.instantiationService)), - this.instantiationService.createInstance(ResourceRenderer, () => this.viewMode, this.listLabels, getActionViewItemProvider(this.instantiationService), actionRunner) + this.instantiationService.createInstance(ResourceRenderer, () => this.viewMode, this.listLabels, getActionViewItemProvider(this.instantiationService), actionRunner), + this.instantiationService.createInstance(HistoryItemGroupRenderer), + this.instantiationService.createInstance(HistoryItemRenderer, getActionViewItemProvider(this.instantiationService)), + this.instantiationService.createInstance(HistoryItemChangeRenderer, () => this.viewMode, this.listLabels), + this.instantiationService.createInstance(SeparatorRenderer) ], - this.instantiationService.createInstance(SCMTreeDataSource, () => this.viewMode, () => this.alwaysShowRepositories, () => this.showActionButton), + this.instantiationService.createInstance(SCMTreeDataSource, () => this.viewMode, () => this.alwaysShowRepositories, () => this.showActionButton, () => this.showSyncInformation), { horizontalScrolling: false, setRowLineHeight: false, @@ -2108,7 +2521,7 @@ export class SCMViewPane extends ViewPane { overrideStyles: { listBackground: this.viewDescriptorService.getViewLocationById(this.id) === ViewContainerLocation.Panel ? PANEL_BACKGROUND : SIDE_BAR_BACKGROUND }, - collapseByDefault: (e) => false, + collapseByDefault: (e: unknown) => isSCMHistoryItemGroupTreeElement(e) || isSCMHistoryItemTreeElement(e) || isSCMHistoryItemChangeTreeElement(e), accessibilityProvider: this.instantiationService.createInstance(SCMAccessibilityProvider) }) as WorkbenchCompressibleAsyncDataTree; @@ -2128,20 +2541,6 @@ export class SCMViewPane extends ViewPane { } else if (isSCMRepository(e.element)) { this.scmViewService.focus(e.element); return; - } else if (isSCMResourceGroup(e.element)) { - const provider = e.element.provider; - const repository = Iterable.find(this.scmService.repositories, r => r.provider === provider); - if (repository) { - this.scmViewService.focus(repository); - } - return; - } else if (ResourceTree.isResourceNode(e.element)) { - const provider = e.element.context.provider; - const repository = Iterable.find(this.scmService.repositories, r => r.provider === provider); - if (repository) { - this.scmViewService.focus(repository); - } - return; } else if (isSCMInput(e.element)) { this.scmViewService.focus(e.element.repository); @@ -2167,26 +2566,55 @@ export class SCMViewPane extends ViewPane { this.tree.setFocus([], e.browserEvent); return; - } - - // ISCMResource - if (e.element.command?.id === API_OPEN_EDITOR_COMMAND_ID || e.element.command?.id === API_OPEN_DIFF_EDITOR_COMMAND_ID) { - await this.commandService.executeCommand(e.element.command.id, ...(e.element.command.arguments || []), e); - } else { - await e.element.open(!!e.editorOptions.preserveFocus); - - if (e.editorOptions.pinned) { - const activeEditorPane = this.editorService.activeEditorPane; - - activeEditorPane?.group.pinEditor(activeEditorPane.input); + } else if (isSCMResourceGroup(e.element)) { + const provider = e.element.provider; + const repository = Iterable.find(this.scmService.repositories, r => r.provider === provider); + if (repository) { + this.scmViewService.focus(repository); } - } + return; + } else if (isSCMResource(e.element)) { + if (e.element.command?.id === API_OPEN_EDITOR_COMMAND_ID || e.element.command?.id === API_OPEN_DIFF_EDITOR_COMMAND_ID) { + await this.commandService.executeCommand(e.element.command.id, ...(e.element.command.arguments || []), e); + } else { + await e.element.open(!!e.editorOptions.preserveFocus); - const provider = e.element.resourceGroup.provider; - const repository = Iterable.find(this.scmService.repositories, r => r.provider === provider); + if (e.editorOptions.pinned) { + const activeEditorPane = this.editorService.activeEditorPane; - if (repository) { - this.scmViewService.focus(repository); + activeEditorPane?.group.pinEditor(activeEditorPane.input); + } + } + + const provider = e.element.resourceGroup.provider; + const repository = Iterable.find(this.scmService.repositories, r => r.provider === provider); + + if (repository) { + this.scmViewService.focus(repository); + } + } else if (isSCMResourceNode(e.element)) { + const provider = e.element.context.provider; + const repository = Iterable.find(this.scmService.repositories, r => r.provider === provider); + if (repository) { + this.scmViewService.focus(repository); + } + return; + } else if (isSCMHistoryItemGroupTreeElement(e.element)) { + this.scmViewService.focus(e.element.repository); + return; + } else if (isSCMHistoryItemTreeElement(e.element)) { + this.scmViewService.focus(e.element.historyItemGroup.repository); + return; + } else if (isSCMHistoryItemChangeTreeElement(e.element)) { + if (e.element.originalUri && e.element.modifiedUri) { + await this.commandService.executeCommand(API_OPEN_DIFF_EDITOR_COMMAND_ID, ...toDiffEditorArguments(e.element.uri, e.element.originalUri, e.element.modifiedUri), e); + } + + this.scmViewService.focus(e.element.historyItem.historyItemGroup.repository); + return; + } else if (isSCMHistoryItemChangeNode(e.element)) { + this.scmViewService.focus(e.element.context.historyItemGroup.repository); + return; } } @@ -2308,7 +2736,11 @@ export class SCMViewPane extends ViewPane { const menus = this.scmViewService.menus.getRepositoryMenus(element.provider); const menu = menus.getResourceGroupMenu(element); actions = collectContextMenuActions(menu); - } else if (ResourceTree.isResourceNode(element)) { + } else if (isSCMResource(element)) { + const menus = this.scmViewService.menus.getRepositoryMenus(element.resourceGroup.provider); + const menu = menus.getResourceMenu(element); + actions = collectContextMenuActions(menu); + } else if (isSCMResourceNode(element)) { if (element.element) { const menus = this.scmViewService.menus.getRepositoryMenus(element.element.resourceGroup.provider); const menu = menus.getResourceMenu(element.element); @@ -2318,10 +2750,6 @@ export class SCMViewPane extends ViewPane { const menu = menus.getResourceFolderMenu(element.context); actions = collectContextMenuActions(menu); } - } else { - const menus = this.scmViewService.menus.getRepositoryMenus(element.resourceGroup.provider); - const menu = menus.getResourceMenu(element); - actions = collectContextMenuActions(menu); } const actionRunner = new RepositoryPaneActionRunner(() => this.getSelectedResources()); @@ -2486,7 +2914,10 @@ class SCMTreeDataSource implements IAsyncDataSource ViewMode, private readonly alwaysShowRepositories: () => boolean, private readonly showActionButton: () => boolean, - @ISCMViewService private readonly scmViewService: ISCMViewService) { } + private readonly showSyncInformation: () => { incoming: boolean; outgoing: boolean }, + @ISCMViewService private readonly scmViewService: ISCMViewService, + @IUriIdentityService private uriIdentityService: IUriIdentityService, + ) { } hasChildren(inputOrElement: ISCMViewService | TreeElement): boolean { if (isSCMViewService(inputOrElement)) { @@ -2503,12 +2934,20 @@ class SCMTreeDataSource implements IAsyncDataSource 0; + } else if (isSCMHistoryItemGroupTreeElement(inputOrElement)) { + return (inputOrElement.count ?? 0) > 0; + } else if (isSCMHistoryItemTreeElement(inputOrElement)) { + return true; + } else if (isSCMHistoryItemChangeTreeElement(inputOrElement)) { + return false; + } else if (isSCMViewSeparator(inputOrElement)) { + return false; } else { throw new Error('hasChildren not implemented.'); } } - getChildren(inputOrElement: ISCMViewService | TreeElement): Iterable | Promise> { + async getChildren(inputOrElement: ISCMViewService | TreeElement): Promise> { const repositoryCount = this.scmViewService.visibleRepositories.length; const alwaysShowRepositories = this.alwaysShowRepositories(); @@ -2542,6 +2981,26 @@ class SCMTreeDataSource implements IAsyncDataSource h.count ?? 0 > 0)) { + // Separator + children.push({ + label: localize('syncSeparatorHeader', "Incoming/Outgoing"), + repository: inputOrElement, + type: 'separator' + } as SCMViewSeparatorElement); + } + + children.push(...historyItemGroups); + } + } + return children; } else if (isSCMResourceGroup(inputOrElement)) { if (this.viewMode() === ViewMode.List) { @@ -2551,26 +3010,129 @@ class SCMTreeDataSource implements IAsyncDataSource { + const scmProvider = element.provider; + const historyProvider = scmProvider.historyProvider; + const currentHistoryItemGroup = historyProvider?.currentHistoryItemGroup; + + if (!historyProvider || !currentHistoryItemGroup || (this.showSyncInformation().incoming === false && this.showSyncInformation().outgoing === false)) { + return []; + } + + // History item group base + const historyItemGroupBase = await historyProvider.resolveHistoryItemGroupBase(currentHistoryItemGroup.id); + if (!historyItemGroupBase) { + return []; + } + + // Common ancestor, ahead, behind + const ancestor = await historyProvider.resolveHistoryItemGroupCommonAncestor(currentHistoryItemGroup.id, historyItemGroupBase.id); + + const children: SCMHistoryItemGroupTreeElement[] = []; + // Incoming + if (historyItemGroupBase && this.showSyncInformation().incoming) { + children.push({ + id: historyItemGroupBase.id, + label: `$(cloud-download) ${historyItemGroupBase.label}`, + // description: localize('incoming', "Incoming Changes"), + ancestor: ancestor?.id, + count: ancestor?.behind ?? 0, + repository: element, + type: 'historyItemGroup' + }); + } + + // Outgoing + if (this.showSyncInformation().outgoing) { + children.push({ + id: currentHistoryItemGroup.id, + label: `$(cloud-upload) ${currentHistoryItemGroup.label}`, + // description: localize('outgoing', "Outgoing Changes"), + ancestor: ancestor?.id, + count: ancestor?.ahead ?? 0, + repository: element, + type: 'historyItemGroup' + }); + } + + return children; + } + + private async getHistoryItems(element: SCMHistoryItemGroupTreeElement): Promise { + const scmProvider = element.repository.provider; + const historyProvider = scmProvider.historyProvider; + + if (!historyProvider) { + return []; + } + + const historyItems = await historyProvider.provideHistoryItems(element.id, { limit: { id: element.ancestor } }) ?? []; + return historyItems.map(historyItem => ({ + ...historyItem, + historyItemGroup: element, + type: 'historyItem' + })); + } + + private async getHistoryItemChanges(element: SCMHistoryItemTreeElement): Promise<(SCMHistoryItemChangeTreeElement | IResourceNode)[]> { + const repository = element.historyItemGroup.repository; + const historyProvider = repository.provider.historyProvider; + + if (!historyProvider) { + return []; + } + + // History Item Changes + const changes = await historyProvider.provideHistoryItemChanges(element.id) ?? []; + + if (this.viewMode() === ViewMode.List) { + // List + return changes.map(change => ({ + ...change, + historyItem: element, + type: 'historyItemChange' + })); + } + + // Tree + const tree = new ResourceTree(element, repository.provider.rootUri ?? URI.file('/'), this.uriIdentityService.extUri); + for (const change of changes) { + tree.add(change.uri, { + ...change, + historyItem: element, + type: 'historyItemChange' + }); + } + + return [...tree.root.children]; + } + getParent(element: TreeElement): ISCMViewService | TreeElement { - if (ResourceTree.isResourceNode(element)) { + if (isSCMResourceNode(element)) { if (element.parent === element.context.resourceTree.root) { return element.context; } else if (!element.parent) { diff --git a/src/vs/workbench/contrib/scm/browser/util.ts b/src/vs/workbench/contrib/scm/browser/util.ts index ad83f6369bb..f98f68c4bb4 100644 --- a/src/vs/workbench/contrib/scm/browser/util.ts +++ b/src/vs/workbench/contrib/scm/browser/util.ts @@ -3,6 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as path from 'vs/base/common/path'; +import { SCMHistoryItemChangeTreeElement, SCMHistoryItemGroupTreeElement, SCMHistoryItemTreeElement, SCMViewSeparatorElement } from 'vs/workbench/contrib/scm/common/history'; import { ISCMResource, ISCMRepository, ISCMResourceGroup, ISCMInput, ISCMActionButton, ISCMViewService } from 'vs/workbench/contrib/scm/common/scm'; import { IMenu } from 'vs/platform/actions/common/actions'; import { ActionBar, IActionViewItemProvider } from 'vs/base/browser/ui/actionbar/actionbar'; @@ -16,6 +18,8 @@ import { ICommandService } from 'vs/platform/commands/common/commands'; import { Command } from 'vs/editor/common/languages'; import { reset } from 'vs/base/browser/dom'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { URI } from 'vs/base/common/uri'; +import { IResourceNode, ResourceTree } from 'vs/base/common/resourceTree'; export function isSCMRepositoryArray(element: any): element is ISCMRepository[] { return Array.isArray(element) && element.every(r => isSCMRepository(r)); @@ -45,6 +49,41 @@ export function isSCMResource(element: any): element is ISCMResource { return !!(element as ISCMResource).sourceUri && isSCMResourceGroup((element as ISCMResource).resourceGroup); } +export function isSCMResourceNode(element: any): element is IResourceNode { + return ResourceTree.isResourceNode(element) && isSCMResourceGroup(element.context); +} + +export function isSCMHistoryItemGroupTreeElement(element: any): element is SCMHistoryItemGroupTreeElement { + return (element as SCMHistoryItemGroupTreeElement).type === 'historyItemGroup'; +} + +export function isSCMHistoryItemTreeElement(element: any): element is SCMHistoryItemTreeElement { + return (element as SCMHistoryItemTreeElement).type === 'historyItem'; +} + +export function isSCMHistoryItemChangeTreeElement(element: any): element is SCMHistoryItemChangeTreeElement { + return (element as SCMHistoryItemChangeTreeElement).type === 'historyItemChange'; +} + +export function isSCMHistoryItemChangeNode(element: any): element is IResourceNode { + return ResourceTree.isResourceNode(element) && isSCMHistoryItemTreeElement(element.context); +} + +export function isSCMViewSeparator(element: any): element is SCMViewSeparatorElement { + return (element as SCMViewSeparatorElement).type === 'separator'; +} + +export function toDiffEditorArguments(uri: URI, originalUri: URI, modifiedUri: URI): unknown[] { + const basename = path.basename(uri.fsPath); + const originalQuery = JSON.parse(originalUri.query) as { path: string; ref: string }; + const modifiedQuery = JSON.parse(modifiedUri.query) as { path: string; ref: string }; + + const originalShortRef = originalQuery.ref.substring(0, 8).concat(originalQuery.ref.endsWith('^') ? '^' : ''); + const modifiedShortRef = modifiedQuery.ref.substring(0, 8).concat(modifiedQuery.ref.endsWith('^') ? '^' : ''); + + return [originalUri, modifiedUri, `${basename} (${originalShortRef}) ↔ ${basename} (${modifiedShortRef})`, null]; +} + const compareActions = (a: IAction, b: IAction) => a.id === b.id && a.enabled === b.enabled; export function connectPrimaryMenu(menu: IMenu, callback: (primary: IAction[], secondary: IAction[]) => void, primaryGroup?: string): IDisposable { diff --git a/src/vs/workbench/contrib/scm/common/history.ts b/src/vs/workbench/contrib/scm/common/history.ts index b9f6792c9ea..c21018c1018 100644 --- a/src/vs/workbench/contrib/scm/common/history.ts +++ b/src/vs/workbench/contrib/scm/common/history.ts @@ -6,7 +6,12 @@ import { Event } from 'vs/base/common/event'; import { ThemeIcon } from 'vs/base/common/themables'; import { URI } from 'vs/base/common/uri'; -import { ISCMActionButtonDescriptor } from 'vs/workbench/contrib/scm/common/scm'; +import { IMenu } from 'vs/platform/actions/common/actions'; +import { ISCMActionButtonDescriptor, ISCMRepository } from 'vs/workbench/contrib/scm/common/scm'; + +export interface ISCMHistoryProviderMenus { + getHistoryItemMenu(historyItem: ISCMHistoryItem): IMenu; +} export interface ISCMHistoryProvider { @@ -30,15 +35,29 @@ export interface ISCMHistoryOptions { readonly limit?: number | { id?: string }; } +export interface ISCMRemoteHistoryItemGroup { + readonly id: string; + readonly label: string; +} + export interface ISCMHistoryItemGroup { readonly id: string; readonly label: string; readonly upstream?: ISCMRemoteHistoryItemGroup; } -export interface ISCMRemoteHistoryItemGroup { - readonly id: string; - readonly label: string; +export interface SCMHistoryItemGroupTreeElement extends ISCMHistoryItemGroup { + readonly description?: string; + readonly ancestor?: string; + readonly count?: number; + readonly repository: ISCMRepository; + readonly type: 'historyItemGroup'; +} + +export interface ISCMHistoryItemStatistics { + readonly files: number; + readonly insertions: number; + readonly deletions: number; } export interface ISCMHistoryItem { @@ -48,6 +67,12 @@ export interface ISCMHistoryItem { readonly description?: string; readonly icon?: URI | { light: URI; dark: URI } | ThemeIcon; readonly timestamp?: number; + readonly statistics?: ISCMHistoryItemStatistics; +} + +export interface SCMHistoryItemTreeElement extends ISCMHistoryItem { + readonly historyItemGroup: SCMHistoryItemGroupTreeElement; + readonly type: 'historyItem'; } export interface ISCMHistoryItemChange { @@ -56,3 +81,14 @@ export interface ISCMHistoryItemChange { readonly modifiedUri?: URI; readonly renameUri?: URI; } + +export interface SCMHistoryItemChangeTreeElement extends ISCMHistoryItemChange { + readonly historyItem: SCMHistoryItemTreeElement; + readonly type: 'historyItemChange'; +} + +export interface SCMViewSeparatorElement { + readonly label: string; + readonly repository: ISCMRepository; + readonly type: 'separator'; +} diff --git a/src/vs/workbench/contrib/scm/common/scm.ts b/src/vs/workbench/contrib/scm/common/scm.ts index 5f36dd36800..f9622326577 100644 --- a/src/vs/workbench/contrib/scm/common/scm.ts +++ b/src/vs/workbench/contrib/scm/common/scm.ts @@ -13,7 +13,7 @@ import { IMenu } from 'vs/platform/actions/common/actions'; import { ThemeIcon } from 'vs/base/common/themables'; import { IMarkdownString } from 'vs/base/common/htmlContent'; import { ResourceTree } from 'vs/base/common/resourceTree'; -import { ISCMHistoryProvider } from 'vs/workbench/contrib/scm/common/history'; +import { ISCMHistoryProvider, ISCMHistoryProviderMenus } from 'vs/workbench/contrib/scm/common/history'; export const VIEWLET_ID = 'workbench.view.scm'; export const VIEW_PANE_ID = 'workbench.scm'; @@ -184,6 +184,7 @@ export interface ISCMTitleMenu { export interface ISCMRepositoryMenus { readonly titleMenu: ISCMTitleMenu; + readonly historyProviderMenu: ISCMHistoryProviderMenus | undefined; readonly repositoryMenu: IMenu; getResourceGroupMenu(group: ISCMResourceGroup): IMenu; getResourceMenu(resource: ISCMResource): IMenu; @@ -227,3 +228,7 @@ export interface ISCMViewService { readonly onDidFocusRepository: Event; focus(repository: ISCMRepository): void; } + +export const SCM_CHANGES_EDITOR_ID = 'workbench.editor.scmChangesEditor'; + +export interface ISCMChangesEditor { } diff --git a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts index 5c79e50d35a..e734b015004 100644 --- a/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts +++ b/src/vs/workbench/contrib/snippets/browser/snippetsFile.ts @@ -11,12 +11,12 @@ import { KnownSnippetVariableNames } from 'vs/editor/contrib/snippet/browser/sni import { URI } from 'vs/base/common/uri'; import { IFileService } from 'vs/platform/files/common/files'; import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions'; -import { IdleValue } from 'vs/base/common/async'; import { IExtensionResourceLoaderService } from 'vs/platform/extensionResourceLoader/common/extensionResourceLoader'; import { relativePath } from 'vs/base/common/resources'; import { isObject } from 'vs/base/common/types'; import { tail } from 'vs/base/common/arrays'; import { Iterable } from 'vs/base/common/iterator'; +import { WindowIdleValue, getActiveWindow } from 'vs/base/browser/dom'; class SnippetBodyInsights { @@ -100,7 +100,7 @@ class SnippetBodyInsights { export class Snippet { - private readonly _bodyInsights: IdleValue; + private readonly _bodyInsights: WindowIdleValue; readonly prefixLow: string; @@ -117,7 +117,7 @@ export class Snippet { readonly extensionId?: ExtensionIdentifier, ) { this.prefixLow = prefix.toLowerCase(); - this._bodyInsights = new IdleValue(() => new SnippetBodyInsights(this.body)); + this._bodyInsights = new WindowIdleValue(getActiveWindow(), () => new SnippetBodyInsights(this.body)); } get codeSnippet(): string { diff --git a/src/vs/workbench/contrib/splash/browser/partsSplash.ts b/src/vs/workbench/contrib/splash/browser/partsSplash.ts index 08fd7e5a8d5..4053f34507a 100644 --- a/src/vs/workbench/contrib/splash/browser/partsSplash.ts +++ b/src/vs/workbench/contrib/splash/browser/partsSplash.ts @@ -18,8 +18,8 @@ import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editor import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import * as perf from 'vs/base/common/performance'; import { assertIsDefined } from 'vs/base/common/types'; -import { runWhenIdle } from 'vs/base/common/async'; import { ISplashStorageService } from 'vs/workbench/contrib/splash/browser/splash'; +import { mainWindow } from 'vs/base/browser/window'; export class PartsSplash { @@ -45,7 +45,7 @@ export class PartsSplash { let lastIdleSchedule: IDisposable | undefined; Event.any(onDidChangeFullscreen, editorGroupsService.mainPart.onDidLayout, _themeService.onDidColorThemeChange)(() => { lastIdleSchedule?.dispose(); - lastIdleSchedule = runWhenIdle(() => this._savePartsSplash(), 800); + lastIdleSchedule = dom.runWhenWindowIdle(mainWindow, () => this._savePartsSplash(), 800); }, undefined, this._disposables); _configService.onDidChangeConfiguration(e => { @@ -95,15 +95,15 @@ export class PartsSplash { } private _removePartsSplash(): void { - const element = document.getElementById(PartsSplash._splashElementId); + const element = mainWindow.document.getElementById(PartsSplash._splashElementId); if (element) { element.style.display = 'none'; } // remove initial colors - const defaultStyles = document.head.getElementsByClassName('initialShellColors'); + const defaultStyles = mainWindow.document.head.getElementsByClassName('initialShellColors'); if (defaultStyles.length) { - document.head.removeChild(defaultStyles[0]); + mainWindow.document.head.removeChild(defaultStyles[0]); } } } diff --git a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts index 428f6388799..f09a58bd88f 100644 --- a/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts +++ b/src/vs/workbench/contrib/tags/electron-sandbox/workspaceTagsService.ts @@ -170,7 +170,8 @@ const ModulesToLookFor = [ '@azure/web-pubsub', '@azure/web-pubsub-client', '@azure/web-pubsub-client-protobuf', - '@azure/web-pubsub-express' + '@azure/web-pubsub-express', + '@azure/openai' ]; const PyMetaModulesToLookFor = [ @@ -206,6 +207,7 @@ const PyModulesToLookFor = [ 'azure-cognitiveservices-nspkg', 'azure-cognitiveservices-language-nspkg', 'azure-cognitiveservices-knowledge-nspkg', + 'azure-containerregistry', 'azure-communication-identity', 'azure-communication-phonenumbers', 'azure-communication-email', @@ -538,6 +540,7 @@ export class WorkspaceTagsService implements IWorkspaceTagsService { "workspace.npm.@azure/web-pubsub-client" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.@azure/web-pubsub-client-protobuf" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.@azure/web-pubsub-express" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, + "workspace.npm.@azure/openai" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.react-native-macos" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.npm.react-native-windows" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, "workspace.bower" : { "classification": "SystemMetaData", "purpose": "FeatureInsight", "isMeasurement": true }, diff --git a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts index 037abbeff2c..273cd93c13a 100644 --- a/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts +++ b/src/vs/workbench/contrib/tasks/browser/abstractTaskService.ts @@ -337,10 +337,14 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this._willRestart = e.reason !== ShutdownReason.RELOAD; }); this._register(this.onDidStateChange(e => { + this._log(nls.localize('taskEvent', 'Task Event kind: {0}', e.kind), true); if (e.kind === TaskEventKind.Changed) { // no-op } else if ((this._willRestart || (e.kind === TaskEventKind.Terminated && e.exitReason === TerminalExitReason.User)) && e.taskId) { - this.removePersistentTask(e.taskId); + const key = e.__task.getKey(); + if (key) { + this.removePersistentTask(key); + } } else if (e.kind === TaskEventKind.Start && e.__task && e.__task.getWorkspaceFolder()) { this._setPersistentTask(e.__task); } @@ -387,15 +391,19 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private _attemptTaskReconnection(): void { if (this._lifecycleService.startupKind !== StartupKind.ReloadedWindow) { + this._log(nls.localize('TaskService.skippingReconnection', 'Startup kind not window reload, setting connected and removing persistent tasks'), true); this._tasksReconnected = true; this._storageService.remove(AbstractTaskService.PersistentTasks_Key, StorageScope.WORKSPACE); } if (!this._configurationService.getValue(TaskSettingId.Reconnection) || this._tasksReconnected) { + this._log(nls.localize('TaskService.notConnecting', 'Setting tasks connected configured value {0}, tasks were already reconnected {1}', this._configurationService.getValue(TaskSettingId.Reconnection), this._tasksReconnected), true); this._tasksReconnected = true; return; } + this._log(nls.localize('TaskService.reconnecting', 'Reconnecting to running tasks...'), true); this.getWorkspaceTasks(TaskRunSource.Reconnect).then(async () => { this._tasksReconnected = await this._reconnectTasks(); + this._log(nls.localize('TaskService.reconnected', 'Reconnected to running tasks.'), true); this._onDidReconnectToTasks.fire(); }); } @@ -403,8 +411,11 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private async _reconnectTasks(): Promise { const tasks = await this.getSavedTasks('persistent'); if (!tasks.length) { + this._log(nls.localize('TaskService.noTasks', 'No persistent tasks to reconnect.'), true); return true; } + const taskLabels = tasks.map(task => task._label).join(', '); + this._log(nls.localize('TaskService.reconnectingTasks', 'Reconnecting to {0} tasks...', taskLabels), true); for (const task of tasks) { if (ConfiguringTask.is(task)) { const resolved = await this.tryResolveTask(task); @@ -483,7 +494,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } }); CommandsRegistry.registerCommand('workbench.action.tasks.showLog', () => { - this._showOutput(); + this._showOutput(undefined, true); }); CommandsRegistry.registerCommand('workbench.action.tasks.build', async () => { @@ -625,15 +636,19 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer this._workspace = setup[4]; } - protected _showOutput(runSource: TaskRunSource = TaskRunSource.User): void { + protected _showOutput(runSource: TaskRunSource = TaskRunSource.User, userRequested?: boolean): void { if (!VirtualWorkspaceContext.getValue(this._contextKeyService) && ((runSource === TaskRunSource.User) || (runSource === TaskRunSource.ConfigurationChange))) { - this._notificationService.prompt(Severity.Warning, nls.localize('taskServiceOutputPrompt', 'There are task errors. See the output for details.'), - [{ - label: nls.localize('showOutput', "Show output"), - run: () => { - this._outputService.showChannel(this._outputChannel.id, true); - } - }]); + if (userRequested) { + this._outputService.showChannel(this._outputChannel.id, true); + } else { + this._notificationService.prompt(Severity.Warning, nls.localize('taskServiceOutputPrompt', 'There are task errors. See the output for details.'), + [{ + label: nls.localize('showOutput', "Show output"), + run: () => { + this._outputService.showChannel(this._outputChannel.id, true); + } + }]); + } } } @@ -812,9 +827,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (!matchingProvider) { if (matchingProviderUnavailable) { - this._outputChannel.append(nls.localize( + this._log(nls.localize( 'TaskService.providerUnavailable', - 'Warning: {0} tasks are unavailable in the current environment.\n', + 'Warning: {0} tasks are unavailable in the current environment.', configuringTask.configures.type )); } @@ -965,6 +980,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer private _getPersistentTasks(): LRUCache { if (this._persistentTasks) { + this._log(nls.localize('taskService.gettingCachedTasks', 'Returning cached tasks {0}', this._persistentTasks.size), true); return this._persistentTasks; } //TODO: should this # be configurable? @@ -1001,7 +1017,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer const workspaceToTaskMap: Map = new Map(); const storedTasks = this._getTasksFromStorage(type); const tasks: (Task | ConfiguringTask)[] = []; - + this._log(nls.localize('taskService.getSavedTasks', 'Fetching tasks from task storage.'), true); function addTaskToMap(map: Map, folder: string | undefined, task: any) { if (folder && !map.has(folder)) { map.set(folder, []); @@ -1014,6 +1030,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer const key = entry[0]; const task = JSON.parse(entry[1]); const folderInfo = this._getFolderFromTaskKey(key); + this._log(nls.localize('taskService.getSavedTasks.reading', 'Reading tasks from task storage, {0}, {1}, {2}', key, task, folderInfo.folder), true); addTaskToMap(folderInfo.isWorkspaceFile ? workspaceToTaskMap : folderToTasksMap, folderInfo.folder, task); } @@ -1032,13 +1049,13 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer tasks: map.get(key) }, TaskRunSource.System, custom, customized, taskConfigSource, true); custom.forEach(task => { - const taskKey = task.getRecentlyUsedKey(); + const taskKey = task.getKey(); if (taskKey) { readTasksMap.set(taskKey, task); } }); for (const configuration in customized) { - const taskKey = customized[configuration].getRecentlyUsedKey(); + const taskKey = customized[configuration].getKey(); if (taskKey) { readTasksMap.set(taskKey, customized[configuration]); } @@ -1050,6 +1067,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer for (const key of storedTasks.keys()) { if (readTasksMap.has(key)) { tasks.push(readTasksMap.get(key)!); + this._log(nls.localize('taskService.getSavedTasks.resolved', 'Resolved task {0}', key), true); + } else { + this._log(nls.localize('taskService.getSavedTasks.unresolved', 'Unable to resolve task {0} ', key), true); } } return tasks; @@ -1063,6 +1083,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } public removePersistentTask(key: string) { + this._log(nls.localize('taskService.removePersistentTask', 'Removing persistent task {0}', key), true); if (this._getTasksFromStorage('persistent').has(key)) { this._getTasksFromStorage('persistent').delete(key); this._savePersistentTasks(); @@ -1077,7 +1098,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } private async _setRecentlyUsedTask(task: Task): Promise { - let key = task.getRecentlyUsedKey(); + let key = task.getKey(); if (!InMemoryTask.is(task) && key) { const customizations = this._createCustomizableTask(task); if (ContributedTask.is(task) && customizations) { @@ -1088,7 +1109,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer tasks: [customizations] }, TaskRunSource.System, custom, customized, TaskConfig.TaskConfigSource.TasksJson, true); for (const configuration in customized) { - key = customized[configuration].getRecentlyUsedKey()!; + key = customized[configuration].getKey()!; } } this._getTasksFromStorage('historical').set(key, JSON.stringify(customizations)); @@ -1120,7 +1141,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer if (!this._configurationService.getValue(TaskSettingId.Reconnection)) { return; } - let key = task.getRecentlyUsedKey(); + let key = task.getKey(); if (!InMemoryTask.is(task) && key) { const customizations = this._createCustomizableTask(task); if (ContributedTask.is(task) && customizations) { @@ -1131,26 +1152,26 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer tasks: [customizations] }, TaskRunSource.System, custom, customized, TaskConfig.TaskConfigSource.TasksJson, true); for (const configuration in customized) { - key = customized[configuration].getRecentlyUsedKey()!; + key = customized[configuration].getKey()!; } } if (!task.configurationProperties.isBackground) { return; } + this._log(nls.localize('taskService.setPersistentTask', 'Setting persistent task {0}', key), true); this._getTasksFromStorage('persistent').set(key, JSON.stringify(customizations)); this._savePersistentTasks(); } } private _savePersistentTasks(): void { - if (!this._persistentTasks) { - return; - } + this._persistentTasks = this._getTasksFromStorage('persistent'); const keys = [...this._persistentTasks.keys()]; const keyValues: [string, string][] = []; for (const key of keys) { keyValues.push([key, this._persistentTasks.get(key, Touch.None)!]); } + this._log(nls.localize('savePersistentTask', 'Saving persistent tasks: {0}', keys.join(', ')), true); this._storageService.store(AbstractTaskService.PersistentTasks_Key, JSON.stringify(keyValues), StorageScope.WORKSPACE, StorageTarget.MACHINE); } @@ -1704,7 +1725,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer }; if (workspaceTasks.length > 0) { if (workspaceTasks.length > 1) { - this._outputChannel.append(nls.localize('moreThanOneBuildTask', 'There are many build tasks defined in the tasks.json. Executing the first one.\n')); + this._log(nls.localize('moreThanOneBuildTask', 'There are many build tasks defined in the tasks.json. Executing the first one.')); } return { task: workspaceTasks[0], resolver }; } @@ -1995,12 +2016,10 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer const error = (error: any) => { try { if (error && Types.isString(error.message)) { - this._outputChannel.append('Error: '); - this._outputChannel.append(error.message); - this._outputChannel.append('\n'); + this._log(`Error: ${error.message}\n`); this._showOutput(); } else { - this._outputChannel.append('Unknown error received while collecting tasks from providers.\n'); + this._log('Unknown error received while collecting tasks from providers.'); this._showOutput(); } } finally { @@ -2023,7 +2042,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer // Check that the tasks provided are of the correct type for (const task of taskSet.tasks) { if (task.type !== this._providerTypes.get(handle)) { - this._outputChannel.append(nls.localize('unexpectedTaskType', "The task provider for \"{0}\" tasks unexpectedly provided a task of type \"{1}\".\n", this._providerTypes.get(handle), task.type)); + this._log(nls.localize('unexpectedTaskType', "The task provider for \"{0}\" tasks unexpectedly provided a task of type \"{1}\".\n", this._providerTypes.get(handle), task.type)); if ((task.type !== 'shell') && (task.type !== 'process')) { this._showOutput(); } @@ -2151,15 +2170,15 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } if (requiredTaskProviderUnavailable) { - this._outputChannel.append(nls.localize( + this._log(nls.localize( 'TaskService.providerUnavailable', - 'Warning: {0} tasks are unavailable in the current environment.\n', + 'Warning: {0} tasks are unavailable in the current environment.', configuringTask.configures.type )); } else { - this._outputChannel.append(nls.localize( + this._log(nls.localize( 'TaskService.noConfiguration', - 'Error: The {0} task detection didn\'t contribute a task for the following configuration:\n{1}\nThe task will be ignored.\n', + 'Error: The {0} task detection didn\'t contribute a task for the following configuration:\n{1}\nThe task will be ignored.', configuringTask.configures.type, JSON.stringify(configuringTask._source.config.element, undefined, 4) )); @@ -2329,7 +2348,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } if (isAffected) { - this._outputChannel.append(nls.localize({ key: 'TaskSystem.invalidTaskJsonOther', comment: ['Message notifies of an error in one of several places there is tasks related json, not necessarily in a file named tasks.json'] }, 'Error: The content of the tasks json in {0} has syntax errors. Please correct them before executing a task.\n', location)); + this._log(nls.localize({ key: 'TaskSystem.invalidTaskJsonOther', comment: ['Message notifies of an error in one of several places there is tasks related json, not necessarily in a file named tasks.json'] }, 'Error: The content of the tasks json in {0} has syntax errors. Please correct them before executing a task.', location)); this._showOutput(); return { config, hasParseErrors: true }; } @@ -2337,6 +2356,12 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer return { config, hasParseErrors: false }; } + private _log(value: string, verbose?: boolean): void { + if (!verbose || this._configurationService.getValue(TaskSettingId.VerboseLogging)) { + this._outputChannel.append(value + '\n'); + } + } + private async _computeWorkspaceFileTasks(workspaceFolder: IWorkspaceFolder, runSource: TaskRunSource = TaskRunSource.User): Promise { if (this._executionEngine === ExecutionEngine.Process) { return this._emptyWorkspaceTaskResults(workspaceFolder); @@ -2448,9 +2473,9 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer workspaceFolders.push(workspaceFolder); } else { ignoredWorkspaceFolders.push(workspaceFolder); - this._outputChannel.append(nls.localize( + this._log(nls.localize( 'taskService.ignoreingFolder', - 'Ignoring task configurations for workspace folder {0}. Multi folder workspace task support requires that all folders use task version 2.0.0\n', + 'Ignoring task configurations for workspace folder {0}. Multi folder workspace task support requires that all folders use task version 2.0.0', workspaceFolder.uri.fsPath)); } } @@ -2511,7 +2536,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer } } if (isAffected) { - this._outputChannel.append(nls.localize('TaskSystem.invalidTaskJson', 'Error: The content of the tasks.json file has syntax errors. Please correct them before executing a task.\n')); + this._log(nls.localize('TaskSystem.invalidTaskJson', 'Error: The content of the tasks.json file has syntax errors. Please correct them before executing a task.')); this._showOutput(); return { config: undefined, hasParseErrors: true }; } @@ -2723,7 +2748,7 @@ export abstract class AbstractTaskService extends Disposable implements ITaskSer const recentlyUsedTasks = this.getRecentlyUsedTasksV1(); const taskMap: IStringDictionary = Object.create(null); tasks.forEach(task => { - const key = task.getRecentlyUsedKey(); + const key = task.getKey(); if (key) { taskMap[key] = task; } diff --git a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts index f4388813f7c..764209f2d01 100644 --- a/src/vs/workbench/contrib/tasks/browser/task.contribution.ts +++ b/src/vs/workbench/contrib/tasks/browser/task.contribution.ts @@ -553,5 +553,10 @@ configurationRegistry.registerConfiguration({ ], default: 'always', }, + [TaskSettingId.VerboseLogging]: { + type: 'boolean', + description: nls.localize('task.verboseLogging', "Enable verbose logging for tasks."), + default: false + }, } }); diff --git a/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts b/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts index 3c15935f60a..6dde509a9d4 100644 --- a/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts +++ b/src/vs/workbench/contrib/tasks/browser/taskQuickPick.ts @@ -149,11 +149,11 @@ export class TaskQuickPick extends Disposable { const definition = configuredTasks[j].getDefinition()?._key; const type = configuredTasks[j].type; const label = configuredTasks[j]._label; - const recentKey = configuredTasks[j].getRecentlyUsedKey(); + const recentKey = configuredTasks[j].getKey(); const findIndex = recentTasks.findIndex((value) => { return (workspaceFolder && definition && value.getWorkspaceFolder()?.uri.toString() === workspaceFolder && ((value.getDefinition()?._key === definition) || (value.type === type && value._label === label))) - || (recentKey && value.getRecentlyUsedKey() === recentKey); + || (recentKey && value.getKey() === recentKey); }); if (findIndex === -1) { dedupedConfiguredTasks.push(configuredTasks[j]); @@ -231,7 +231,7 @@ export class TaskQuickPick extends Disposable { picker.onDidTriggerItemButton(async (context) => { const task = context.item.task; if (context.button.iconClass === ThemeIcon.asClassName(removeTaskIcon)) { - const key = (task && !Types.isString(task)) ? task.getRecentlyUsedKey() : undefined; + const key = (task && !Types.isString(task)) ? task.getKey() : undefined; if (key) { this._taskService.removeRecentlyUsedTask(key); } diff --git a/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts b/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts index dd605f15b54..51e6ae001b0 100644 --- a/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts +++ b/src/vs/workbench/contrib/tasks/browser/tasksQuickAccess.ts @@ -65,7 +65,7 @@ export class TasksQuickAccessProvider extends PickerQuickAccessProvider { if ((index === 1) && (quickAccessEntry.buttons?.length === 2)) { - const key = (task && !isString(task)) ? task.getRecentlyUsedKey() : undefined; + const key = (task && !isString(task)) ? task.getKey() : undefined; if (key) { this._taskService.removeRecentlyUsedTask(key); } diff --git a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts index dd2d581c170..d61e13e8f71 100644 --- a/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts +++ b/src/vs/workbench/contrib/tasks/browser/terminalTaskSystem.ts @@ -386,9 +386,9 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { } public getLastInstance(task: Task): Task | undefined { - const recentKey = task.getRecentlyUsedKey(); + const recentKey = task.getKey(); return Object.values(this._activeTasks).reverse().find( - (value) => recentKey && recentKey === value.task.getRecentlyUsedKey())?.task; + (value) => recentKey && recentKey === value.task.getKey())?.task; } public getBusyTasks(): Task[] { @@ -408,9 +408,9 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { } private _getInstances(task: Task): IActiveTerminalData[] { - const recentKey = task.getRecentlyUsedKey(); + const recentKey = task.getKey(); return Object.values(this._activeTasks).filter( - (value) => recentKey && recentKey === value.task.getRecentlyUsedKey()); + (value) => recentKey && recentKey === value.task.getKey()); } private _removeFromActiveTasks(task: Task | string): void { @@ -588,12 +588,6 @@ export class TerminalTaskSystem extends Disposable implements ITaskSystem { } else { dependencyTask.configurationProperties.icon = task.configurationProperties.icon; } - - if (dependencyTask.configurationProperties.hide) { - dependencyTask.configurationProperties.hide ||= task.configurationProperties.hide; - } else { - dependencyTask.configurationProperties.hide = task.configurationProperties.hide; - } } private async _getDependencyPromise(task: IActiveTerminalData): Promise { diff --git a/src/vs/workbench/contrib/tasks/common/tasks.ts b/src/vs/workbench/contrib/tasks/common/tasks.ts index fded77d7f8d..b7884faa838 100644 --- a/src/vs/workbench/contrib/tasks/common/tasks.ts +++ b/src/vs/workbench/contrib/tasks/common/tasks.ts @@ -617,7 +617,7 @@ export abstract class CommonTask { return this._id; } - public getRecentlyUsedKey(): string | undefined { + public getKey(): string | undefined { return undefined; } @@ -788,10 +788,13 @@ export class CustomTask extends CommonTask { } public override getCommonTaskId(): string { - return this._source.customizes ? super.getCommonTaskId() : (this.getRecentlyUsedKey() ?? super.getCommonTaskId()); + return this._source.customizes ? super.getCommonTaskId() : (this.getKey() ?? super.getCommonTaskId()); } - public override getRecentlyUsedKey(): string | undefined { + /** + * @returns A key representing the task + */ + public override getKey(): string | undefined { interface ICustomKey { type: string; folder: string; @@ -875,7 +878,7 @@ export class ConfiguringTask extends CommonTask { return this._source.kind === TaskSourceKind.User ? USER_TASKS_GROUP_KEY : this._source.config.workspaceFolder?.uri.toString(); } - public override getRecentlyUsedKey(): string | undefined { + public override getKey(): string | undefined { interface ICustomKey { type: string; folder: string; @@ -963,7 +966,7 @@ export class ContributedTask extends CommonTask { return undefined; } - public override getRecentlyUsedKey(): string | undefined { + public override getKey(): string | undefined { interface IContributedKey { type: string; scope: number; @@ -1266,7 +1269,8 @@ export const enum TaskSettingId { QuickOpenSkip = 'task.quickOpen.skip', QuickOpenShowAll = 'task.quickOpen.showAll', AllowAutomaticTasks = 'task.allowAutomaticTasks', - Reconnection = 'task.reconnection' + Reconnection = 'task.reconnection', + VerboseLogging = 'task.verboseLogging' } export const enum TasksSchemaProperties { diff --git a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts index 1b4ad1bc098..91a902f4b20 100644 --- a/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts +++ b/src/vs/workbench/contrib/telemetry/browser/telemetry.contribution.ts @@ -26,6 +26,7 @@ import { hash } from 'vs/base/common/hash'; import { IPaneCompositePartService } from 'vs/workbench/services/panecomposite/browser/panecomposite'; import { ViewContainerLocation } from 'vs/workbench/common/views'; import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile'; +import { mainWindow } from 'vs/base/browser/window'; type TelemetryData = { mimeType: TelemetryTrustedValue; @@ -108,7 +109,7 @@ export class TelemetryContribution extends Disposable implements IWorkbenchContr }; telemetryService.publicLog2('workspaceLoad', { - windowSize: { innerHeight: window.innerHeight, innerWidth: window.innerWidth, outerHeight: window.outerHeight, outerWidth: window.outerWidth }, + windowSize: { innerHeight: mainWindow.innerHeight, innerWidth: mainWindow.innerWidth, outerHeight: mainWindow.outerHeight, outerWidth: mainWindow.outerWidth }, emptyWorkbench: contextService.getWorkbenchState() === WorkbenchState.EMPTY, 'workbench.filesToOpenOrCreate': filesToOpenOrCreate && filesToOpenOrCreate.length || 0, 'workbench.filesToDiff': filesToDiff && filesToDiff.length || 0, diff --git a/src/vs/workbench/contrib/terminal/browser/detachedTerminal.ts b/src/vs/workbench/contrib/terminal/browser/detachedTerminal.ts index 9902a5483e5..d8b53f3989f 100644 --- a/src/vs/workbench/contrib/terminal/browser/detachedTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/detachedTerminal.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as dom from 'vs/base/browser/dom'; import { Delayer } from 'vs/base/common/async'; import { onUnexpectedError } from 'vs/base/common/errors'; import { Disposable } from 'vs/base/common/lifecycle'; @@ -19,11 +20,13 @@ import { XtermTerminal } from 'vs/workbench/contrib/terminal/browser/xterm/xterm import { IEnvironmentVariableInfo } from 'vs/workbench/contrib/terminal/common/environmentVariable'; import { ITerminalProcessInfo, ProcessState } from 'vs/workbench/contrib/terminal/common/terminal'; -export class DeatachedTerminal extends Disposable implements IDetachedTerminalInstance { +export class DetachedTerminal extends Disposable implements IDetachedTerminalInstance { private readonly _widgets = this._register(new TerminalWidgetManager()); public readonly capabilities = new TerminalCapabilityStore(); private readonly _contributions: Map = new Map(); + public domElement?: HTMLElement; + public get xterm(): IDetachedXtermTerminal { return this._xterm; } @@ -66,10 +69,41 @@ export class DeatachedTerminal extends Disposable implements IDetachedTerminalIn }); } + get selection(): string | undefined { + return this._xterm && this.hasSelection() ? this._xterm.raw.getSelection() : undefined; + } + + hasSelection(): boolean { + return this._xterm.hasSelection(); + } + + clearSelection(): void { + this._xterm.clearSelection(); + } + + focus(force?: boolean): void { + if (force || !dom.getActiveWindow().getSelection()?.toString()) { + this.xterm.focus(); + } + } + attachToElement(container: HTMLElement, options?: Partial | undefined): void { + this.domElement = container; const screenElement = this._xterm.attachToElement(container, options); this._widgets.attachToElement(screenElement); } + + forceScrollbarVisibility(): void { + this.domElement?.classList.add('force-scrollbar'); + } + + resetScrollbarVisibility(): void { + this.domElement?.classList.remove('force-scrollbar'); + } + + getContribution(id: string): T | null { + return this._contributions.get(id) as T | null; + } } /** diff --git a/src/vs/workbench/contrib/terminal/browser/media/terminal.css b/src/vs/workbench/contrib/terminal/browser/media/terminal.css index 5c30094e8b9..3d462bd7524 100644 --- a/src/vs/workbench/contrib/terminal/browser/media/terminal.css +++ b/src/vs/workbench/contrib/terminal/browser/media/terminal.css @@ -53,28 +53,6 @@ outline: 0 !important; } -.monaco-workbench .xterm.dev-mode .xterm-helper-textarea { - z-index: 36 !important; - opacity: 0.8 !important; - right: 0 !important; - left: initial !important; - width: 50% !important; - background-color: var(--vscode-terminal-background, var(--vscode-panel-background)); - color: var(--vscode-terminal-foreground); - transform: translateY(-100%); -} -.monaco-workbench .xterm.dev-mode .xterm-helper-textarea:focus { - opacity: 0.8 !important; -} -.monaco-workbench .xterm.dev-mode .xterm-helpers { - /* This could maybe be done outside of .dev-mode, but I'm scared to break something */ - left: 0; - right: 0; -} -.monaco-workbench .xterm.dev-mode .xterm-helper-textarea:hover { - opacity: 0.25 !important; -} - .monaco-workbench .xterm .xterm-helper-textarea:focus { /* Override the general vscode style applies `opacity:1!important` to textareas */ opacity: 0 !important; @@ -502,11 +480,19 @@ } .terminal-scroll-highlight-outline { - border: 1px solid #ffffff; + border-left: 1px solid #ffffff; + border-right: 1px solid #ffffff; pointer-events: none; } +.terminal-scroll-highlight-outline.top { + border-top: 1px solid #ffffff; +} +.terminal-scroll-highlight-outline.bottom { + border-bottom: 1px solid #ffffff; +} -.terminal-scroll-highlight { +.terminal-scroll-highlight, +.terminal-scroll-highlight.terminal-scroll-highlight-outline { border-color: var(--vscode-focusBorder); } .hc-black .xterm-find-result-decoration, diff --git a/src/vs/workbench/contrib/terminal/browser/terminal.ts b/src/vs/workbench/contrib/terminal/browser/terminal.ts index ce5c9213f6f..39f195272ee 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminal.ts @@ -20,12 +20,12 @@ import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { EditorInput } from 'vs/workbench/common/editor/editorInput'; import { IEditableData } from 'vs/workbench/common/views'; import { ITerminalStatusList } from 'vs/workbench/contrib/terminal/browser/terminalStatusList'; -import { ScrollPosition } from 'vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon'; import { XtermTerminal } from 'vs/workbench/contrib/terminal/browser/xterm/xtermTerminal'; import { IRegisterContributedProfileArgs, IRemoteTerminalAttachTarget, IStartExtensionTerminalRequest, ITerminalConfigHelper, ITerminalFont, ITerminalProcessExtHostProxy, ITerminalProcessInfo } from 'vs/workbench/contrib/terminal/common/terminal'; import { EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn'; import { ISimpleSelectedSuggestion } from 'vs/workbench/services/suggest/browser/simpleSuggestWidget'; -import type { IMarker, Terminal as RawXtermTerminal } from '@xterm/xterm'; +import type { IMarker, ITheme, Terminal as RawXtermTerminal } from '@xterm/xterm'; +import { ScrollPosition } from 'vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon'; export const ITerminalService = createDecorator('terminalService'); export const ITerminalEditorService = createDecorator('terminalEditorService'); @@ -39,6 +39,7 @@ export const ITerminalInstanceService = createDecorator(id: string): T | null; +} + /** * A {@link ITerminalInstance}-like object that emulates a subset of * capabilities. This instance is returned from {@link ITerminalService.createDetachedTerminal} * to represent terminals that appear in other parts of the VS Code UI outside * of the "Terminal" view or editors. */ -export interface IDetachedTerminalInstance extends IDisposable { +export interface IDetachedTerminalInstance extends IDisposable, IBaseTerminalInstance { readonly xterm: IDetachedXtermTerminal; - readonly capabilities: ITerminalCapabilityStore; /** * Attached the terminal to the given element. This should be preferred over @@ -178,7 +234,7 @@ export interface ITerminalService extends ITerminalInstanceHost { /** Gets all terminal instances, including editor and terminal view (group) instances. */ readonly instances: readonly ITerminalInstance[]; /** Gets detached terminal instances created via {@link createDetachedXterm}. */ - readonly detachedXterms: Iterable; + readonly detachedInstances: Iterable; readonly configHelper: ITerminalConfigHelper; readonly defaultLocation: TerminalLocation; @@ -233,7 +289,7 @@ export interface ITerminalService extends ITerminalInstanceHost { getReconnectedTerminals(reconnectionOwner: string): ITerminalInstance[] | undefined; getActiveOrCreateInstance(options?: { acceptsInput?: boolean }): Promise; - revealActiveTerminal(): Promise; + revealActiveTerminal(preserveFocus?: boolean): Promise; moveToEditor(source: ITerminalInstance): void; moveToTerminalView(source: ITerminalInstance | URI): Promise; getPrimaryBackend(): ITerminalBackend | undefined; @@ -478,7 +534,7 @@ export interface ISearchOptions { incremental?: boolean; } -export interface ITerminalInstance { +export interface ITerminalInstance extends IBaseTerminalInstance { /** * The ID of the terminal instance, this is an arbitrary number only used to uniquely identify * terminal instances within a window. @@ -510,7 +566,6 @@ export interface ITerminalInstance { readonly cwd?: string; readonly initialCwd?: string; readonly os?: OperatingSystem; - readonly capabilities: ITerminalCapabilityStore; readonly usedShellIntegrationInjection: boolean; readonly injectedArgs: string[] | undefined; readonly extEnvironmentVariableCollection: IMergedEnvironmentVariableCollection | undefined; @@ -749,39 +804,17 @@ export interface ITerminalInstance { */ detachProcessAndDispose(reason: TerminalExitReason): Promise; - /** - * Check if anything is selected in terminal. - */ - hasSelection(): boolean; - /** * Copies the terminal selection to the clipboard. */ copySelection(asHtml?: boolean, command?: ITerminalCommand): Promise; - /** - * Current selection in the terminal. - */ - readonly selection: string | undefined; - - /** - * Clear current selection. - */ - clearSelection(): void; - /** * When the panel is hidden or a terminal in the editor area becomes inactive, reset the focus context key * to avoid issues like #147180. */ resetFocusContextKey(): void; - /** - * Focuses the terminal instance if it's able to (the xterm.js instance must exist). - * - * @param force Force focus even if there is a selection. - */ - focus(force?: boolean): void; - /** * Focuses the terminal instance when it's ready (the xterm.js instance much exist). This is the * best focus call when the terminal is being shown for example. @@ -927,14 +960,14 @@ export interface ITerminalInstance { rename(title?: string): Promise; /** - * Triggers a quick pick to change the icon of this terminal. + * Sets or triggers a quick pick to change the icon of this terminal. */ - changeIcon(): Promise; + changeIcon(icon?: TerminalIcon): Promise; /** - * Triggers a quick pick to change the color of the associated terminal tab icon. + * Sets or triggers a quick pick to change the color of the associated terminal tab icon. */ - changeColor(): Promise; + changeColor(color?: string): Promise; /** * Triggers a quick pick that displays recent commands or cwds. Selecting one will @@ -977,22 +1010,6 @@ export interface ITerminalInstance { * Hides the suggest widget. */ hideSuggestWidget(): void; - - /** - * Force the scroll bar to be visible until {@link resetScrollbarVisibility} is called. - */ - forceScrollbarVisibility(): void; - - /** - * Resets the scroll bar to only be visible when needed, this does nothing unless - * {@link forceScrollbarVisibility} was called. - */ - resetScrollbarVisibility(): void; - - /** - * Gets a terminal contribution by its ID. - */ - getContribution(id: string): T | null; } export const enum XtermTerminalConstants { @@ -1041,6 +1058,11 @@ export interface IXtermTerminal extends IDisposable { */ readonly isFocused: boolean; + /** + * Whether a canvas-based renderer is being used. + */ + readonly isGpuAccelerated: boolean; + /** * Attached the terminal to the given element * @param container Container the terminal will be rendered in @@ -1109,12 +1131,13 @@ export interface IXtermTerminal extends IDisposable { */ focus(): void; - /** Scroll the terminal buffer down 1 line. */ scrollDownLine(): void; - /** Scroll the terminal buffer down 1 page. */ scrollDownPage(): void; + /** Scroll the terminal buffer down 1 line. */ scrollDownLine(): void; + /** Scroll the terminal buffer down 1 page. */ scrollDownPage(): void; /** Scroll the terminal buffer to the bottom. */ scrollToBottom(): void; - /** Scroll the terminal buffer up 1 line. */ scrollUpLine(): void; - /** Scroll the terminal buffer up 1 page. */ scrollUpPage(): void; - /** Scroll the terminal buffer to the top. */ scrollToTop(): void; + /** Scroll the terminal buffer up 1 line. */ scrollUpLine(): void; + /** Scroll the terminal buffer up 1 page. */ scrollUpPage(): void; + /** Scroll the terminal buffer to the top. */ scrollToTop(): void; + /** Scroll the terminal buffer to a set line */ scrollToLine(line: number, position?: ScrollPosition): void; /** * Clears the terminal buffer, leaving only the prompt line and moving it to the top of the @@ -1146,6 +1169,8 @@ export interface IXtermTerminal extends IDisposable { * Refreshes the terminal after it has been moved. */ refresh(): void; + + getXtermTheme(theme?: IColorTheme): ITheme; } export interface IDetachedXtermTerminal extends IXtermTerminal { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts index 9d5896ce8f6..e6d64efe98b 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalActions.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalActions.ts @@ -27,12 +27,12 @@ import { IListService } from 'vs/platform/list/browser/listService'; import { INotificationService, Severity } from 'vs/platform/notification/common/notification'; import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IPickOptions, IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput'; -import { ITerminalProfile, TerminalExitReason, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { ITerminalProfile, TerminalExitReason, TerminalIcon, TerminalLocation, TerminalSettingId } from 'vs/platform/terminal/common/terminal'; import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { PICK_WORKSPACE_FOLDER_COMMAND_ID } from 'vs/workbench/browser/actions/workspaceCommands'; import { CLOSE_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands'; import { ResourceContextKey } from 'vs/workbench/common/contextkeys'; -import { Direction, ICreateTerminalOptions, ITerminalEditorService, ITerminalGroupService, ITerminalInstance, ITerminalInstanceService, ITerminalService, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { Direction, ICreateTerminalOptions, IDetachedTerminalInstance, ITerminalEditorService, ITerminalGroupService, ITerminalInstance, ITerminalInstanceService, ITerminalService, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalQuickAccessProvider } from 'vs/workbench/contrib/terminal/browser/terminalQuickAccess'; import { IRemoteTerminalAttachTarget, ITerminalConfigHelper, ITerminalProfileResolverService, ITerminalProfileService, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; @@ -251,15 +251,15 @@ export function registerActiveInstanceAction( * This includes detached xterm terminals that are not managed by an {@link ITerminalInstance}. */ export function registerActiveXtermAction( - options: IAction2Options & { run: (activeTerminal: IXtermTerminal, accessor: ServicesAccessor, instance?: ITerminalInstance, args?: unknown) => void | Promise } + options: IAction2Options & { run: (activeTerminal: IXtermTerminal, accessor: ServicesAccessor, instance: ITerminalInstance | IDetachedTerminalInstance, args?: unknown) => void | Promise } ): IDisposable { const originalRun = options.run; return registerTerminalAction({ ...options, run: (c, accessor, args) => { - const activeDetached = Iterable.find(c.service.detachedXterms, d => d.isFocused); + const activeDetached = Iterable.find(c.service.detachedInstances, d => d.xterm.isFocused); if (activeDetached) { - return originalRun(activeDetached, accessor, undefined, args); + return originalRun(activeDetached.xterm, accessor, activeDetached, args); } const activeInstance = c.service.activeInstance; @@ -609,7 +609,7 @@ export function registerTerminalActions() { text = editor.getModel().getValueInRange(selection, endOfLinePreference); } instance.sendText(text, true, true); - await c.service.revealActiveTerminal(); + await c.service.revealActiveTerminal(true); } }); @@ -749,7 +749,12 @@ export function registerTerminalActions() { title: terminalStrings.changeIcon, f1: false, precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.tabsSingularSelection), - run: (c, accessor) => getSelectedInstances(accessor)?.[0].changeIcon() + run: async (c, accessor) => { + let icon: TerminalIcon | undefined; + for (const terminal of getSelectedInstances(accessor) ?? []) { + icon = await terminal.changeIcon(icon); + } + } }); registerTerminalAction({ @@ -764,7 +769,12 @@ export function registerTerminalActions() { title: terminalStrings.changeColor, f1: false, precondition: ContextKeyExpr.and(ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), TerminalContextKeys.tabsSingularSelection), - run: (c, accessor) => getSelectedInstances(accessor)?.[0].changeColor() + run: async (c, accessor) => { + let color: string | undefined; + for (const terminal of getSelectedInstances(accessor) ?? []) { + color = await terminal.changeColor(color); + } + } }); registerTerminalAction({ @@ -1307,7 +1317,7 @@ export function registerTerminalActions() { run: (c, accessor) => accessor.get(ICommandService).executeCommand(CLOSE_EDITOR_COMMAND_ID) }); - registerContextualInstanceAction({ + registerTerminalAction({ id: TerminalCommandId.KillActiveTab, title: terminalStrings.kill, f1: false, @@ -1321,8 +1331,12 @@ export function registerTerminalActions() { weight: KeybindingWeight.WorkbenchContrib, when: TerminalContextKeys.tabsFocus }, - run: (instance, c) => c.service.safeDisposeTerminal(instance), - runAfter: (instances, c) => c.groupService.focusTabs() + run: async (c, accessor) => { + for (const terminal of getSelectedInstances(accessor) ?? []) { + c.service.safeDisposeTerminal(terminal); + } + c.groupService.focusTabs(); + } }); registerTerminalAction({ diff --git a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts index 552d69c615e..44271b827be 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalConfigHelper.ts @@ -20,6 +20,7 @@ import { IXtermCore } from 'vs/workbench/contrib/terminal/browser/xterm-private' import { IShellLaunchConfig } from 'vs/platform/terminal/common/terminal'; import { isLinux, isWindows } from 'vs/base/common/platform'; import { Disposable } from 'vs/base/common/lifecycle'; +import { $window } from 'vs/base/browser/window'; const enum FontConstants { MinimumFontSize = 6, @@ -142,10 +143,10 @@ export class TerminalConfigHelper extends Disposable implements IBrowserTerminal if (this.config.gpuAcceleration === 'off') { this._lastFontMeasurement.charWidth = rect.width; } else { - const deviceCharWidth = Math.floor(rect.width * window.devicePixelRatio); + const deviceCharWidth = Math.floor(rect.width * $window.devicePixelRatio); const deviceCellWidth = deviceCharWidth + Math.round(letterSpacing); - const cssCellWidth = deviceCellWidth / window.devicePixelRatio; - this._lastFontMeasurement.charWidth = cssCellWidth - Math.round(letterSpacing) / window.devicePixelRatio; + const cssCellWidth = deviceCellWidth / $window.devicePixelRatio; + this._lastFontMeasurement.charWidth = cssCellWidth - Math.round(letterSpacing) / $window.devicePixelRatio; } } @@ -200,7 +201,7 @@ export class TerminalConfigHelper extends Disposable implements IBrowserTerminal letterSpacing, lineHeight, charHeight: cellDims.height / lineHeight, - charWidth: cellDims.width - Math.round(letterSpacing) / window.devicePixelRatio + charWidth: cellDims.width - Math.round(letterSpacing) / $window.devicePixelRatio }; } } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalContextMenu.ts b/src/vs/workbench/contrib/terminal/browser/terminalContextMenu.ts index d8fcacd6ce2..6a8b0097e43 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalContextMenu.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalContextMenu.ts @@ -48,8 +48,8 @@ export class TerminalContextActionRunner extends ActionRunner { } } -export function openContextMenu(event: MouseEvent, contextInstances: SingleOrMany | undefined, menu: IMenu, contextMenuService: IContextMenuService, extraActions?: IAction[]): void { - const standardEvent = new StandardMouseEvent(event); +export function openContextMenu(targetWindow: Window, event: MouseEvent, contextInstances: SingleOrMany | undefined, menu: IMenu, contextMenuService: IContextMenuService, extraActions?: IAction[]): void { + const standardEvent = new StandardMouseEvent(targetWindow, event); const actions: IAction[] = []; diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts index 5e00daed7ff..16ebecab2c9 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditor.ts @@ -142,7 +142,7 @@ export class TerminalEditor extends EditorPane { // copyPaste: Shift+right click should open context menu if (rightClickBehavior === 'copyPaste' && event.shiftKey) { - openContextMenu(event, this._editorInput?.terminalInstance, this._instanceMenu, this._contextMenuService); + openContextMenu(dom.getWindow(this._editorInstanceElement), event, this._editorInput?.terminalInstance, this._instanceMenu, this._contextMenuService); return; } @@ -180,7 +180,7 @@ export class TerminalEditor extends EditorPane { else if (!this._cancelContextMenu && rightClickBehavior !== 'copyPaste' && rightClickBehavior !== 'paste') { if (!this._cancelContextMenu) { - openContextMenu(event, this._editorInput?.terminalInstance, this._instanceMenu, this._contextMenuService); + openContextMenu(dom.getWindow(this._editorInstanceElement), event, this._editorInput?.terminalInstance, this._instanceMenu, this._contextMenuService); } event.preventDefault(); event.stopImmediatePropagation(); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts b/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts index ef8bbd795f3..2c4cafe5172 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalEditorInput.ts @@ -57,7 +57,7 @@ export class TerminalEditorInput extends EditorInput implements IEditorCloseHand } override get capabilities(): EditorInputCapabilities { - return EditorInputCapabilities.Readonly | EditorInputCapabilities.Singleton | EditorInputCapabilities.CanDropIntoEditor; + return EditorInputCapabilities.Readonly | EditorInputCapabilities.Singleton | EditorInputCapabilities.CanDropIntoEditor | EditorInputCapabilities.ForceDescription; } setTerminalInstance(instance: ITerminalInstance): void { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts index 4345fac23a4..6af540e3357 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalInstance.ts @@ -52,13 +52,13 @@ import { IProcessDataEvent, IProcessPropertyMap, IReconnectionProperties, IShell import { formatMessageForTerminal } from 'vs/platform/terminal/common/terminalStrings'; import { editorBackground } from 'vs/platform/theme/common/colorRegistry'; import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry'; -import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { IWorkspaceContextService, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace'; import { IWorkspaceTrustRequestService } from 'vs/platform/workspace/common/workspaceTrust'; import { PANEL_BACKGROUND, SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme'; import { IViewDescriptorService, IViewsService, ViewContainerLocation } from 'vs/workbench/common/views'; import { AccessibilityVerbositySettingId } from 'vs/workbench/contrib/accessibility/browser/accessibilityConfiguration'; -import { IRequestAddInstanceToGroupEvent, ITerminalContribution, ITerminalInstance, TerminalDataTransfers } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { IRequestAddInstanceToGroupEvent, ITerminalContribution, ITerminalInstance, IXtermColorProvider, TerminalDataTransfers } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalLaunchHelpAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { TerminalEditorInput } from 'vs/workbench/contrib/terminal/browser/terminalEditorInput'; @@ -535,13 +535,13 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { // Clear out initial data events after 10 seconds, hopefully extension hosts are up and // running at that point. - let initialDataEventsTimeout: number | undefined = window.setTimeout(() => { + let initialDataEventsTimeout: number | undefined = dom.getWindow(this._container).setTimeout(() => { initialDataEventsTimeout = undefined; this._initialDataEvents = undefined; }, 10000); this._register(toDisposable(() => { if (initialDataEventsTimeout) { - window.clearTimeout(initialDataEventsTimeout); + dom.getWindow(this._container).clearTimeout(initialDataEventsTimeout); } })); @@ -611,7 +611,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { return; } - const computedStyle = window.getComputedStyle(this._container); + const computedStyle = dom.getWindow(this._container).getComputedStyle(this._container); const width = parseInt(computedStyle.width); const height = parseInt(computedStyle.height); @@ -675,7 +675,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { if (!this.xterm?.raw.element) { return undefined; } - const computedStyle = window.getComputedStyle(this.xterm.raw.element); + const computedStyle = dom.getWindow(this.xterm.raw.element).getComputedStyle(this.xterm.raw.element); const horizontalPadding = parseInt(computedStyle.paddingLeft) + parseInt(computedStyle.paddingRight); const verticalPadding = parseInt(computedStyle.paddingTop) + parseInt(computedStyle.paddingBottom); TerminalInstance._lastKnownCanvasDimensions = new dom.Dimension( @@ -718,22 +718,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { this._configHelper, this._cols, this._rows, - { - getBackgroundColor: (theme) => { - const terminalBackground = theme.getColor(TERMINAL_BACKGROUND_COLOR); - if (terminalBackground) { - return terminalBackground; - } - if (this.target === TerminalLocation.Editor) { - return theme.getColor(editorBackground); - } - const location = this._viewDescriptorService.getViewLocationById(TERMINAL_VIEW_ID)!; - if (location === ViewContainerLocation.Panel) { - return theme.getColor(PANEL_BACKGROUND); - } - return theme.getColor(SIDE_BAR_BACKGROUND); - } - }, + this._scopedInstantiationService.createInstance(TerminalInstanceColorProvider, this), this.capabilities, this._processManager.shellIntegrationNonce, this._terminalSuggestWidgetVisibleContextKey, @@ -902,6 +887,15 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { const screenElement = xterm.attachToElement(xtermElement); + // Fire xtermOpen on all contributions + for (const contribution of this._contributions.values()) { + if (!this.xterm) { + this._xtermReadyPromise.then(xterm => contribution.xtermOpen?.(xterm)); + } else { + contribution.xtermOpen?.(this.xterm); + } + } + this._register(xterm.shellIntegration.onDidChangeStatus(() => { if (this.hasFocus) { this._setShellIntegrationContextKey(); @@ -1210,7 +1204,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { if (!this.xterm) { return; } - if (force || !window.getSelection()?.toString()) { + if (force || !dom.getActiveWindow().getSelection()?.toString()) { this.xterm.raw.focus(); this._onDidRequestFocus.fire(); } @@ -1634,9 +1628,9 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } let retries = 0; return new Promise(r => { - const interval = setInterval(() => { + const interval = dom.disposableWindowInterval(dom.getActiveWindow().window, () => { if (this._latestXtermWriteData === this._latestXtermParseData || ++retries === 5) { - clearInterval(interval); + interval.dispose(); r(); } }, 20); @@ -2177,7 +2171,12 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { } } - async changeIcon() { + async changeIcon(icon?: TerminalIcon): Promise { + if (icon) { + this._icon = icon; + this._onIconChanged.fire({ instance: this, userInitiated: true }); + return icon; + } type Item = IQuickPickItem & { icon: TerminalIcon }; const items: Item[] = []; for (const icon of getAllCodicons()) { @@ -2190,10 +2189,17 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { if (result) { this._icon = result.icon; this._onIconChanged.fire({ instance: this, userInitiated: true }); + return this._icon; } + return; } - async changeColor() { + async changeColor(color?: string): Promise { + if (color) { + this.shellLaunchConfig.color = color; + this._onIconChanged.fire({ instance: this, userInitiated: true }); + return color; + } const icon = this._getIcon(); if (!icon) { return; @@ -2231,6 +2237,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance { quickPick.hide(); colorStyleDisposable.dispose(); + return result?.id; } selectPreviousSuggestion(): void { @@ -2565,3 +2572,27 @@ export function parseExitResult( return { code, message }; } + + +export class TerminalInstanceColorProvider implements IXtermColorProvider { + constructor( + private readonly _instance: ITerminalInstance, + @IViewDescriptorService private readonly _viewDescriptorService: IViewDescriptorService, + ) { + } + + getBackgroundColor(theme: IColorTheme) { + const terminalBackground = theme.getColor(TERMINAL_BACKGROUND_COLOR); + if (terminalBackground) { + return terminalBackground; + } + if (this._instance.target === TerminalLocation.Editor) { + return theme.getColor(editorBackground); + } + const location = this._viewDescriptorService.getViewLocationById(TERMINAL_VIEW_ID)!; + if (location === ViewContainerLocation.Panel) { + return theme.getColor(PANEL_BACKGROUND); + } + return theme.getColor(SIDE_BAR_BACKGROUND); + } +} diff --git a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts index b1add9a6c84..37fe47f9991 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalProcessManager.ts @@ -38,7 +38,8 @@ import Severity from 'vs/base/common/severity'; import { INotificationService } from 'vs/platform/notification/common/notification'; import { IEnvironmentVariableCollection, IMergedEnvironmentVariableCollection } from 'vs/platform/terminal/common/environmentVariable'; import { generateUuid } from 'vs/base/common/uuid'; -import { runWhenIdle } from 'vs/base/common/async'; +import { $window } from 'vs/base/browser/window'; +import { runWhenWindowIdle } from 'vs/base/browser/dom'; const enum ProcessConstants { /** @@ -394,7 +395,7 @@ export class TerminalProcessManager extends Disposable implements ITerminalProce } // Report the latency to the pty host when idle - runWhenIdle(() => { + runWhenWindowIdle($window, () => { this.backend?.getLatency().then(measurements => { this._logService.info(`Latency measurements for ${this.remoteAuthority ?? 'local'} backend\n${measurements.map(e => `${e.label}: ${e.latency.toFixed(2)}ms`).join('\n')}`); }); @@ -748,7 +749,7 @@ class SeamlessRelaunchDataFilter extends Disposable { this.triggerSwap(); } - this._swapTimeout = window.setTimeout(() => this.triggerSwap(), SeamlessRelaunchConstants.SwapWaitMaximumDuration); + this._swapTimeout = $window.setTimeout(() => this.triggerSwap(), SeamlessRelaunchConstants.SwapWaitMaximumDuration); // Pause all outgoing data events this._dataListener?.dispose(); @@ -773,7 +774,7 @@ class SeamlessRelaunchDataFilter extends Disposable { triggerSwap() { // Clear the swap timeout if it exists if (this._swapTimeout) { - window.clearTimeout(this._swapTimeout); + $window.clearTimeout(this._swapTimeout); this._swapTimeout = undefined; } diff --git a/src/vs/workbench/contrib/terminal/browser/terminalService.ts b/src/vs/workbench/contrib/terminal/browser/terminalService.ts index f5ea4fd4228..23a90398319 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalService.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalService.ts @@ -29,7 +29,7 @@ import { ThemeIcon } from 'vs/base/common/themables'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { VirtualWorkspaceContext } from 'vs/workbench/common/contextkeys'; import { IEditableData, IViewsService } from 'vs/workbench/common/views'; -import { ICreateTerminalOptions, IDetachedTerminalInstance, IDetachedXTermOptions, IRequestAddInstanceToGroupEvent, ITerminalEditorService, ITerminalGroup, ITerminalGroupService, ITerminalInstance, ITerminalInstanceHost, ITerminalInstanceService, ITerminalLocationOptions, ITerminalService, ITerminalServiceNativeDelegate, IXtermTerminal, TerminalConnectionState, TerminalEditorLocation } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { ICreateTerminalOptions, IDetachedTerminalInstance, IDetachedXTermOptions, IRequestAddInstanceToGroupEvent, ITerminalEditorService, ITerminalGroup, ITerminalGroupService, ITerminalInstance, ITerminalInstanceHost, ITerminalInstanceService, ITerminalLocationOptions, ITerminalService, ITerminalServiceNativeDelegate, TerminalConnectionState, TerminalEditorLocation } from 'vs/workbench/contrib/terminal/browser/terminal'; import { getCwdForSplit } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { TerminalConfigHelper } from 'vs/workbench/contrib/terminal/browser/terminalConfigHelper'; import { TerminalEditorInput } from 'vs/workbench/contrib/terminal/browser/terminalEditorInput'; @@ -52,16 +52,17 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { TerminalCapabilityStore } from 'vs/platform/terminal/common/capabilities/terminalCapabilityStore'; import { ITimerService } from 'vs/workbench/services/timer/browser/timerService'; import { mark } from 'vs/base/common/performance'; -import { DeatachedTerminal } from 'vs/workbench/contrib/terminal/browser/detachedTerminal'; +import { DetachedTerminal } from 'vs/workbench/contrib/terminal/browser/detachedTerminal'; import { ITerminalCapabilityImplMap, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; import { createInstanceCapabilityEventMultiplexer } from 'vs/workbench/contrib/terminal/browser/terminalEvents'; +import { mainWindow } from 'vs/base/browser/window'; export class TerminalService extends Disposable implements ITerminalService { declare _serviceBrand: undefined; private _hostActiveTerminals: Map = new Map(); - private _detachedXterms = new Set(); + private _detachedXterms = new Set(); private _terminalEditorActive: IContextKey; private readonly _terminalShellTypeContextKey: IContextKey; @@ -94,7 +95,7 @@ export class TerminalService extends Disposable implements ITerminalService { get instances(): ITerminalInstance[] { return this._terminalGroupService.instances.concat(this._terminalEditorService.instances); } - get detachedXterms(): Iterable { + get detachedInstances(): Iterable { return this._detachedXterms; } @@ -228,7 +229,7 @@ export class TerminalService extends Disposable implements ITerminalService { this.initializePrimaryBackend(); // Create async as the class depends on `this` - timeout(0).then(() => this._register(this._instantiationService.createInstance(TerminalEditorStyle, document.head))); + timeout(0).then(() => this._register(this._instantiationService.createInstance(TerminalEditorStyle, mainWindow.document.head))); } async showProfileQuickPick(type: 'setDefault' | 'createInstance', cwd?: string | URI): Promise { @@ -553,13 +554,13 @@ export class TerminalService extends Disposable implements ITerminalService { return instance; } - async revealActiveTerminal(): Promise { + async revealActiveTerminal(preserveFocus?: boolean): Promise { const instance = this.activeInstance; if (!instance) { return; } if (instance.target === TerminalLocation.Editor) { - await this._terminalEditorService.revealActiveEditor(); + await this._terminalEditorService.revealActiveEditor(preserveFocus); } else { await this._terminalGroupService.showPanel(); } @@ -967,7 +968,7 @@ export class TerminalService extends Disposable implements ITerminalService { }); const instanceHost = resolvedLocation === TerminalLocation.Editor ? this._terminalEditorService : this._terminalGroupService; const instance = instanceHost.instances[instanceHost.instances.length - 1]; - await instance.focusWhenReady(); + await instance?.focusWhenReady(); this._terminalHasBeenCreated.set(true); return instance; } @@ -1022,13 +1023,14 @@ export class TerminalService extends Disposable implements ITerminalService { xterm.raw.attachCustomKeyEventHandler(() => false); } - this._detachedXterms.add(xterm); + const instance = new DetachedTerminal(xterm, options, this._instantiationService); + this._detachedXterms.add(instance); const l = xterm.onDidDispose(() => { - this._detachedXterms.delete(xterm); + this._detachedXterms.delete(instance); l.dispose(); }); - return new DeatachedTerminal(xterm, options, this._instantiationService); + return instance; } private async _resolveCwd(shellLaunchConfig: IShellLaunchConfig, splitActiveTerminal: boolean, options?: ICreateTerminalOptions): Promise { diff --git a/src/vs/workbench/contrib/terminal/browser/terminalStatusList.ts b/src/vs/workbench/contrib/terminal/browser/terminalStatusList.ts index 897d9184586..246ad9ec2f2 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalStatusList.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalStatusList.ts @@ -13,6 +13,7 @@ import { listErrorForeground, listWarningForeground } from 'vs/platform/theme/co import { spinningLoading } from 'vs/platform/theme/common/iconRegistry'; import { ThemeIcon } from 'vs/base/common/themables'; import { ITerminalStatus } from 'vs/workbench/contrib/terminal/common/terminal'; +import { $window } from 'vs/base/browser/window'; /** * The set of _internal_ terminal statuses, other components building on the terminal should put @@ -84,11 +85,11 @@ export class TerminalStatusList extends Disposable implements ITerminalStatusLis status = this._applyAnimationSetting(status); const outTimeout = this._statusTimeouts.get(status.id); if (outTimeout) { - window.clearTimeout(outTimeout); + $window.clearTimeout(outTimeout); this._statusTimeouts.delete(status.id); } if (duration && duration > 0) { - const timeout = window.setTimeout(() => this.remove(status), duration); + const timeout = $window.setTimeout(() => this.remove(status), duration); this._statusTimeouts.set(status.id, timeout); } const existingStatus = this._statuses.get(status.id); diff --git a/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts b/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts index 4ce64354ac6..7022de5de53 100644 --- a/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts +++ b/src/vs/workbench/contrib/terminal/browser/terminalTabbedView.ts @@ -201,7 +201,7 @@ export class TerminalTabbedView extends Disposable { offscreenCanvas.height = 1; const ctx = offscreenCanvas.getContext('2d'); if (ctx) { - const style = window.getComputedStyle(this._tabListElement); + const style = dom.getWindow(this._tabListElement).getComputedStyle(this._tabListElement); ctx.font = `${style.fontStyle} ${style.fontSize} ${style.fontFamily}`; const maxInstanceWidth = this._terminalGroupService.instances.reduce((p, c) => { return Math.max(p, ctx.measureText(c.title + (c.description || '')).width + this._getAdditionalWidth(c)); @@ -285,16 +285,15 @@ export class TerminalTabbedView extends Disposable { } private _addSashListener() { - let interval: number; + let interval: IDisposable; this._sashDisposables = [ this._splitView.sashes[0].onDidStart(e => { - interval = window.setInterval(() => { + interval = dom.disposableWindowInterval(dom.getWindow(this._splitView.el), () => { this.rerenderTabs(); }, 100); }), this._splitView.sashes[0].onDidEnd(e => { - window.clearInterval(interval); - interval = 0; + interval.dispose(); }) ]; } @@ -354,7 +353,7 @@ export class TerminalTabbedView extends Disposable { else if (rightClickBehavior === 'copyPaste' || rightClickBehavior === 'paste') { // copyPaste: Shift+right click should open context menu if (rightClickBehavior === 'copyPaste' && event.shiftKey) { - openContextMenu(event, terminal, this._instanceMenu, this._contextMenuService); + openContextMenu(dom.getWindow(terminalContainer), event, terminal, this._instanceMenu, this._contextMenuService); return; } @@ -388,7 +387,7 @@ export class TerminalTabbedView extends Disposable { } terminalContainer.focus(); if (!this._cancelContextMenu) { - openContextMenu(event, this._terminalGroupService.activeInstance!, this._instanceMenu, this._contextMenuService); + openContextMenu(dom.getWindow(terminalContainer), event, this._terminalGroupService.activeInstance!, this._instanceMenu, this._contextMenuService); } event.preventDefault(); event.stopImmediatePropagation(); @@ -413,7 +412,7 @@ export class TerminalTabbedView extends Disposable { selectedInstances.unshift(focusedInstance); } - openContextMenu(event, selectedInstances, emptyList ? this._tabsListEmptyMenu : this._tabsListMenu, this._contextMenuService, emptyList ? this._getTabActions() : undefined); + openContextMenu(dom.getWindow(this._tabContainer), event, selectedInstances, emptyList ? this._tabsListEmptyMenu : this._tabsListMenu, this._contextMenuService, emptyList ? this._getTabActions() : undefined); } event.preventDefault(); event.stopImmediatePropagation(); diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts b/src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts index 3ec8a0f746f..32eacd9dc73 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon.ts @@ -6,11 +6,13 @@ import { coalesce } from 'vs/base/common/arrays'; import { Disposable, dispose } from 'vs/base/common/lifecycle'; import { IMarkTracker } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { ITerminalCapabilityStore, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; +import { ITerminalCapabilityStore, ITerminalCommand, TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; import type { Terminal, IMarker, ITerminalAddon, IDecoration } from '@xterm/xterm'; import { timeout } from 'vs/base/common/async'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { TERMINAL_OVERVIEW_RULER_CURSOR_FOREGROUND_COLOR } from 'vs/workbench/contrib/terminal/common/terminalColorRegistry'; +import { getWindow } from 'vs/base/browser/dom'; +import { getPromptRowCount } from 'vs/platform/terminal/common/capabilities/commandDetectionCapability'; enum Boundary { Top, @@ -66,6 +68,14 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe return markers; } + private _findCommand(marker: IMarker): ITerminalCommand | undefined { + const commandCapability = this._capabilities.get(TerminalCapability.CommandDetection); + if (commandCapability) { + return commandCapability.commands.find(e => e.marker?.line === marker.line); + } + return undefined; + } + clearMarker(): void { // Clear the current marker so successive focus/selection actions are performed from the // bottom of the buffer @@ -103,7 +113,7 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe let markerIndex; const currentLineY = typeof this._currentMarker === 'object' - ? this._getTargetScrollLine(this._terminal, this._currentMarker, scrollPosition) + ? this.getTargetScrollLine(this._currentMarker.line, scrollPosition) : Math.min(getLine(this._terminal, this._currentMarker), this._terminal.buffer.active.baseY); const viewportY = this._terminal.buffer.active.viewportY; if (typeof this._currentMarker === 'object' ? !this._isMarkerInViewport(this._terminal, this._currentMarker) : currentLineY !== viewportY) { @@ -136,7 +146,7 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe } this._currentMarker = this._getMarkers(skipEmptyCommands)[markerIndex]; - this._scrollToMarker(this._currentMarker, scrollPosition); + this._scrollToCommand(this._currentMarker, scrollPosition); } scrollToNextMark(scrollPosition: ScrollPosition = ScrollPosition.Middle, retainSelection: boolean = false, skipEmptyCommands: boolean = true): void { @@ -149,7 +159,7 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe let markerIndex; const currentLineY = typeof this._currentMarker === 'object' - ? this._getTargetScrollLine(this._terminal, this._currentMarker, scrollPosition) + ? this.getTargetScrollLine(this._currentMarker.line, scrollPosition) : Math.min(getLine(this._terminal, this._currentMarker), this._terminal.buffer.active.baseY); const viewportY = this._terminal.buffer.active.viewportY; if (typeof this._currentMarker === 'object' ? !this._isMarkerInViewport(this._terminal, this._currentMarker) : currentLineY !== viewportY) { @@ -182,43 +192,65 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe } this._currentMarker = this._getMarkers(skipEmptyCommands)[markerIndex]; - this._scrollToMarker(this._currentMarker, scrollPosition); + this._scrollToCommand(this._currentMarker, scrollPosition); } - private _scrollToMarker(marker: IMarker, position: ScrollPosition, endMarker?: IMarker, hideDecoration?: boolean): void { + private _scrollToCommand(marker: IMarker, position: ScrollPosition): void { + const command = this._findCommand(marker); + if (command) { + this.revealCommand(command, position); + } else { + this._scrollToMarker(marker, position); + } + } + + private _scrollToMarker(start: IMarker | number, position: ScrollPosition, end?: IMarker | number, hideDecoration?: boolean): void { if (!this._terminal) { return; } - if (!this._isMarkerInViewport(this._terminal, marker)) { - const line = this._getTargetScrollLine(this._terminal, marker, position); + if (!this._isMarkerInViewport(this._terminal, start)) { + const line = this.getTargetScrollLine(toLineIndex(start), position); this._terminal.scrollToLine(line); } if (!hideDecoration) { - this._registerTemporaryDecoration(marker, endMarker); + this.registerTemporaryDecoration(start, end); } } - private _createMarkerForOffset(marker: IMarker, offset: number): IMarker { - if (offset === 0) { + private _createMarkerForOffset(marker: IMarker | number, offset: number): IMarker { + if (offset === 0 && isMarker(marker)) { return marker; } else { - const offsetMarker = this._terminal?.registerMarker(-this._terminal.buffer.active.cursorY + marker.line - this._terminal.buffer.active.baseY + offset); + const offsetMarker = this._terminal?.registerMarker(-this._terminal.buffer.active.cursorY + toLineIndex(marker) - this._terminal.buffer.active.baseY + offset); if (offsetMarker) { return offsetMarker; } else { - throw new Error(`Could not register marker with offset ${marker.line}, ${offset}`); + throw new Error(`Could not register marker with offset ${toLineIndex(marker)}, ${offset}`); } } } - private _registerTemporaryDecoration(marker: IMarker, endMarker?: IMarker): void { + revealCommand(command: ITerminalCommand, position: ScrollPosition = ScrollPosition.Middle): void { + if (!this._terminal || !command.marker) { + return; + } + + const promptRowCount = getPromptRowCount(command, this._terminal.buffer.active); + this._scrollToMarker( + command.marker.line - (promptRowCount - 1), + position, + command.marker + ); + } + + registerTemporaryDecoration(marker: IMarker | number, endMarker?: IMarker | number): void { if (!this._terminal) { return; } this._resetNavigationDecorations(); const color = this._themeService.getColorTheme().getColor(TERMINAL_OVERVIEW_RULER_CURSOR_FOREGROUND_COLOR); - const startLine = marker.line; - const decorationCount = endMarker ? endMarker.line - startLine + 1 : 1; + const startLine = toLineIndex(marker); + const decorationCount = endMarker ? toLineIndex(endMarker) - startLine + 1 : 1; for (let i = 0; i < decorationCount; i++) { const decoration = this._terminal.registerDecoration({ @@ -235,14 +267,18 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe decoration.onRender(element => { if (!renderedElement) { renderedElement = element; - if (decorationCount > 1) { - element.classList.add('terminal-scroll-highlight'); - } else { - element.classList.add('terminal-scroll-highlight', 'terminal-scroll-highlight-outline'); + element.classList.add('terminal-scroll-highlight', 'terminal-scroll-highlight-outline'); + if (i === 0) { + element.classList.add('top'); } - if (this._terminal?.element) { - element.style.marginLeft = `-${getComputedStyle(this._terminal.element).paddingLeft}`; + if (i === decorationCount - 1) { + element.classList.add('bottom'); } + } else { + element.classList.add('terminal-scroll-highlight', 'terminal-scroll-highlight-outline'); + } + if (this._terminal?.element) { + element.style.marginLeft = `-${getWindow(this._terminal.element).getComputedStyle(this._terminal.element).paddingLeft}`; } }); decoration.onDispose(() => { this._navigationDecorations = this._navigationDecorations?.filter(d => d !== decoration); }); @@ -256,18 +292,23 @@ export class MarkNavigationAddon extends Disposable implements IMarkTracker, ITe } } - private _getTargetScrollLine(terminal: Terminal, marker: IMarker, position: ScrollPosition) { - // Middle is treated at 1/4 of the viewport's size because context below is almost always - // more important than context above in the terminal. - if (position === ScrollPosition.Middle) { - return Math.max(marker.line - Math.floor(terminal.rows / 4), 0); - } - return marker.line; + scrollToLine(line: number, position: ScrollPosition): void { + this._terminal?.scrollToLine(this.getTargetScrollLine(line, position)); } - private _isMarkerInViewport(terminal: Terminal, marker: IMarker) { + getTargetScrollLine(line: number, position: ScrollPosition): number { + // Middle is treated at 1/4 of the viewport's size because context below is almost always + // more important than context above in the terminal. + if (this._terminal && position === ScrollPosition.Middle) { + return Math.max(line - Math.floor(this._terminal.rows / 4), 0); + } + return line; + } + + private _isMarkerInViewport(terminal: Terminal, marker: IMarker | number) { const viewportY = terminal.buffer.active.viewportY; - return marker.line >= viewportY && marker.line < viewportY + terminal.rows; + const line = toLineIndex(marker); + return line >= viewportY && line < viewportY + terminal.rows; } scrollToClosestMarker(startMarkerId: string, endMarkerId?: string, highlight?: boolean | undefined): void { @@ -469,3 +510,11 @@ export function selectLines(xterm: Terminal, start: IMarker | Boundary, end: IMa xterm.selectLines(startLine, endLine); } + +function isMarker(value: IMarker | number): value is IMarker { + return typeof value !== 'number'; +} + +function toLineIndex(line: IMarker | number): number { + return isMarker(line) ? line.line : line; +} diff --git a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts index 4963c4f95b6..e83503fdb89 100644 --- a/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts +++ b/src/vs/workbench/contrib/terminal/browser/xterm/xtermTerminal.ts @@ -24,7 +24,7 @@ import { LogLevel } from 'vs/platform/log/common/log'; import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage'; import { TerminalStorageKeys } from 'vs/workbench/contrib/terminal/common/terminalStorageKeys'; import { INotificationService, IPromptChoice, Severity } from 'vs/platform/notification/common/notification'; -import { MarkNavigationAddon } from 'vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon'; +import { MarkNavigationAddon, ScrollPosition } from 'vs/workbench/contrib/terminal/browser/xterm/markNavigationAddon'; import { localize } from 'vs/nls'; import { IColorTheme, IThemeService } from 'vs/platform/theme/common/themeService'; import { PANEL_BACKGROUND } from 'vs/workbench/common/theme'; @@ -45,6 +45,7 @@ import { MouseWheelClassifier } from 'vs/base/browser/ui/scrollbar/scrollableEle import { IMouseWheelEvent, StandardWheelEvent } from 'vs/base/browser/mouseEvent'; import { AccessibleNotificationEvent, IAccessibleNotificationService } from 'vs/platform/accessibility/common/accessibility'; import { ILayoutService } from 'vs/platform/layout/browser/layoutService'; +import { $window } from 'vs/base/browser/window'; const enum RenderConstants { /** @@ -148,6 +149,7 @@ export class XtermTerminal extends Disposable implements IXtermTerminal, IDetach get findResult(): { resultIndex: number; resultCount: number } | undefined { return this._lastFindResult; } get isStdinDisabled(): boolean { return !!this.raw.options.disableStdin; } + get isGpuAccelerated(): boolean { return !!(this._canvasAddon || this._webglAddon); } private readonly _onDidRequestRunCommand = this._register(new Emitter<{ command: ITerminalCommand; copyAsHtml?: boolean; noNewLine?: boolean }>()); readonly onDidRequestRunCommand = this._onDidRequestRunCommand.event; @@ -194,7 +196,7 @@ export class XtermTerminal extends Disposable implements IXtermTerminal, IDetach private readonly _configHelper: TerminalConfigHelper, cols: number, rows: number, - private readonly _backgroundColorProvider: IXtermColorProvider, + private readonly _xtermColorProvider: IXtermColorProvider, private readonly _capabilities: ITerminalCapabilityStore, shellIntegrationNonce: string, private readonly _terminalSuggestWidgetVisibleContextKey: IContextKey | undefined, @@ -223,7 +225,7 @@ export class XtermTerminal extends Disposable implements IXtermTerminal, IDetach documentOverride: layoutService.container.ownerDocument, altClickMovesCursor: config.altClickMovesCursor && editorOptions.multiCursorModifier === 'alt', scrollback: config.scrollback, - theme: this._getXtermTheme(), + theme: this.getXtermTheme(), drawBoldTextInBrightColors: config.drawBoldTextInBrightColors, fontFamily: font.fontFamily, fontWeight: config.fontWeight, @@ -590,6 +592,10 @@ export class XtermTerminal extends Disposable implements IXtermTerminal, IDetach this.raw.scrollToTop(); } + scrollToLine(line: number, position: ScrollPosition = ScrollPosition.Top): void { + this.markTracker.scrollToLine(line, position); + } + clearBuffer(): void { this.raw.clear(); // xterm.js does not clear the first prompt, so trigger these to simulate @@ -645,9 +651,9 @@ export class XtermTerminal extends Disposable implements IXtermTerminal, IDetach e.clipboardData.setData('text/html', textAsHtml); e.preventDefault(); } - document.addEventListener('copy', listener); - document.execCommand('copy'); - document.removeEventListener('copy', listener); + $window.document.addEventListener('copy', listener); + $window.document.execCommand('copy'); + $window.document.removeEventListener('copy', listener); } else { await this._clipboardService.writeText(this.raw.getSelection()); } @@ -897,13 +903,13 @@ export class XtermTerminal extends Disposable implements IXtermTerminal, IDetach }; } - private _getXtermTheme(theme?: IColorTheme): ITheme { + getXtermTheme(theme?: IColorTheme): ITheme { if (!theme) { theme = this._themeService.getColorTheme(); } const foregroundColor = theme.getColor(TERMINAL_FOREGROUND_COLOR); - const backgroundColor = this._backgroundColorProvider.getBackgroundColor(theme); + const backgroundColor = this._xtermColorProvider.getBackgroundColor(theme); const cursorColor = theme.getColor(TERMINAL_CURSOR_FOREGROUND_COLOR) || foregroundColor; const cursorAccentColor = theme.getColor(TERMINAL_CURSOR_BACKGROUND_COLOR) || backgroundColor; const selectionBackgroundColor = theme.getColor(TERMINAL_SELECTION_BACKGROUND_COLOR); @@ -938,7 +944,7 @@ export class XtermTerminal extends Disposable implements IXtermTerminal, IDetach } private _updateTheme(theme?: IColorTheme): void { - this.raw.options.theme = this._getXtermTheme(theme); + this.raw.options.theme = this.getXtermTheme(theme); } refresh() { @@ -979,13 +985,13 @@ export function getXtermScaledDimensions(font: ITerminalFont, width: number, hei // the use of canvas, window.devicePixelRatio needs to be used here in // order to be precise. font.charWidth/charHeight alone as insufficient // when window.devicePixelRatio changes. - const scaledWidthAvailable = width * window.devicePixelRatio; + const scaledWidthAvailable = width * $window.devicePixelRatio; - const scaledCharWidth = font.charWidth * window.devicePixelRatio + font.letterSpacing; + const scaledCharWidth = font.charWidth * $window.devicePixelRatio + font.letterSpacing; const cols = Math.max(Math.floor(scaledWidthAvailable / scaledCharWidth), 1); - const scaledHeightAvailable = height * window.devicePixelRatio; - const scaledCharHeight = Math.ceil(font.charHeight * window.devicePixelRatio); + const scaledHeightAvailable = height * $window.devicePixelRatio; + const scaledCharHeight = Math.ceil(font.charHeight * $window.devicePixelRatio); const scaledLineHeight = Math.floor(scaledCharHeight * font.lineHeight); const rows = Math.max(Math.floor(scaledHeightAvailable / scaledLineHeight), 1); diff --git a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts index d26dfe59220..7149dfd2fa6 100644 --- a/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts +++ b/src/vs/workbench/contrib/terminal/common/terminalConfiguration.ts @@ -625,6 +625,13 @@ const terminalConfiguration: IConfigurationNode = { type: 'boolean', default: false }, + [TerminalSettingId.EnableStickyScroll]: { + markdownDescription: localize('terminal.integrated.enableStickyScroll', "Experimental: Whether to enable the sticky scroll overlay at the top of the terminal."), + type: 'boolean', + default: false, + // TODO: Prevent setting at folder level after it becomes stable, + // scope: ConfigurationScope.RESOURCE + } } }; diff --git a/src/vs/workbench/contrib/terminal/electron-sandbox/terminalNativeContribution.ts b/src/vs/workbench/contrib/terminal/electron-sandbox/terminalNativeContribution.ts index 2b541d9fbbd..61bcdaca33f 100644 --- a/src/vs/workbench/contrib/terminal/electron-sandbox/terminalNativeContribution.ts +++ b/src/vs/workbench/contrib/terminal/electron-sandbox/terminalNativeContribution.ts @@ -13,6 +13,8 @@ import { INativeHostService } from 'vs/platform/native/common/native'; import { Disposable } from 'vs/base/common/lifecycle'; import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal'; import { IWorkbenchContribution } from 'vs/workbench/common/contributions'; +import { disposableWindowInterval } from 'vs/base/browser/dom'; +import { $window } from 'vs/base/browser/window'; export class TerminalNativeContribution extends Disposable implements IWorkbenchContribution { declare _serviceBrand: undefined; @@ -61,14 +63,14 @@ export class TerminalNativeContribution extends Disposable implements IWorkbench // Complete when wait marker file is deleted return new Promise(resolve => { let running = false; - const interval = setInterval(async () => { + const interval = disposableWindowInterval($window, async () => { if (!running) { running = true; const exists = await this._fileService.exists(path); running = false; if (!exists) { - clearInterval(interval); + interval.dispose(); resolve(undefined); } } diff --git a/src/vs/workbench/contrib/terminal/terminal.all.ts b/src/vs/workbench/contrib/terminal/terminal.all.ts index d2a0d591c81..563bf065317 100644 --- a/src/vs/workbench/contrib/terminal/terminal.all.ts +++ b/src/vs/workbench/contrib/terminal/terminal.all.ts @@ -18,5 +18,6 @@ import 'vs/workbench/contrib/terminalContrib/developer/browser/terminal.develope import 'vs/workbench/contrib/terminalContrib/environmentChanges/browser/terminal.environmentChanges.contribution'; import 'vs/workbench/contrib/terminalContrib/find/browser/terminal.find.contribution'; import 'vs/workbench/contrib/terminalContrib/links/browser/terminal.links.contribution'; +import 'vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminal.stickyScroll.contribution'; import 'vs/workbench/contrib/terminalContrib/quickFix/browser/terminal.quickFix.contribution'; import 'vs/workbench/contrib/terminalContrib/typeAhead/browser/terminal.typeAhead.contribution'; diff --git a/src/vs/workbench/contrib/terminal/test/browser/terminalConfigHelper.test.ts b/src/vs/workbench/contrib/terminal/test/browser/terminalConfigHelper.test.ts index 8764db8e257..7df51a71941 100644 --- a/src/vs/workbench/contrib/terminal/test/browser/terminalConfigHelper.test.ts +++ b/src/vs/workbench/contrib/terminal/test/browser/terminalConfigHelper.test.ts @@ -10,6 +10,7 @@ import { TestConfigurationService } from 'vs/platform/configuration/test/common/ import { LinuxDistro } from 'vs/workbench/contrib/terminal/browser/terminal'; import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils'; import { DisposableStore } from 'vs/base/common/lifecycle'; +import { mainWindow } from 'vs/base/browser/window'; class TestTerminalConfigHelper extends TerminalConfigHelper { set linuxDistro(distro: LinuxDistro) { @@ -28,7 +29,7 @@ suite('Workbench - TerminalConfigHelper', function () { setup(() => { store = new DisposableStore(); - fixture = document.body; + fixture = mainWindow.document.body; }); teardown(() => store.dispose()); diff --git a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts index d5a1082b647..9cd1cb1446d 100644 --- a/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/accessibility/browser/terminal.accessibility.contribution.ts @@ -66,7 +66,7 @@ export class TerminalAccessibleViewContribution extends Disposable implements IT private _bufferTracker: BufferContentTracker | undefined; private _bufferProvider: TerminalAccessibleBufferProvider | undefined; private _xterm: Pick & { raw: Terminal } | undefined; - private _onCommandExecutedShowListener: MutableDisposable = new MutableDisposable(); + private _onDidRunCommand: MutableDisposable = new MutableDisposable(); constructor( private readonly _instance: ITerminalInstance, @@ -129,14 +129,14 @@ export class TerminalAccessibleViewContribution extends Disposable implements IT return; } if (!this._configurationService.getValue(TerminalSettingId.AccessibleViewFocusOnCommandExecution)) { - this._onCommandExecutedShowListener.clear(); + this._onDidRunCommand.clear(); return; - } else if (this._onCommandExecutedShowListener.value) { + } else if (this._onDidRunCommand.value) { return; } const capability = this._instance.capabilities.get(TerminalCapability.CommandDetection)!; - this._onCommandExecutedShowListener.value = this._register(capability.onCommandExecuted(() => { + this._onDidRunCommand.value = this._register(capability.onCommandExecuted(() => { if (this._instance.hasFocus) { this.show(); } diff --git a/src/vs/workbench/contrib/terminalContrib/developer/browser/media/developer.css b/src/vs/workbench/contrib/terminalContrib/developer/browser/media/developer.css new file mode 100644 index 00000000000..bf20da05443 --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/developer/browser/media/developer.css @@ -0,0 +1,45 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-workbench .xterm.dev-mode .xterm-helper-textarea { + z-index: 36 !important; + opacity: 0.8 !important; + right: 0 !important; + left: initial !important; + width: 50% !important; + background-color: var(--vscode-terminal-background, var(--vscode-panel-background)); + color: var(--vscode-terminal-foreground); + transform: translateY(-100%); +} +.monaco-workbench .xterm.dev-mode .xterm-helper-textarea:focus { + opacity: 0.8 !important; +} +.monaco-workbench .xterm.dev-mode .xterm-helpers { + /* This could maybe be done outside of .dev-mode, but I'm scared to break something */ + left: 0; + right: 0; +} +.monaco-workbench .xterm.dev-mode .xterm-helper-textarea:hover { + opacity: 0.25 !important; +} + +.monaco-workbench .xterm.dev-mode .xterm-sequence-decoration { + background-color: var(--vscode-terminal-background, var(--vscode-panel-background)); +} +.monaco-workbench .xterm.dev-mode .xterm-sequence-decoration.left { + direction: rtl; +} +.monaco-workbench .xterm.dev-mode .xterm-sequence-decoration.top.left { + transform: scale(.5) translate(-50%, -50%); +} +.monaco-workbench .xterm.dev-mode .xterm-sequence-decoration.top.right { + transform: scale(.5) translate(50%, -50%); +} +.monaco-workbench .xterm.dev-mode .xterm-sequence-decoration.bottom.left { + transform: scale(.5) translate(-50%, 50%); +} +.monaco-workbench .xterm.dev-mode .xterm-sequence-decoration.bottom.right { + transform: scale(.5) translate(50%, 50%); +} diff --git a/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts b/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts index 679001437ce..c3161cfb897 100644 --- a/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/developer/browser/terminal.developer.contribution.ts @@ -3,8 +3,9 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import 'vs/css!./media/developer'; import { VSBuffer } from 'vs/base/common/buffer'; -import { DisposableStore } from 'vs/base/common/lifecycle'; +import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; import { URI } from 'vs/base/common/uri'; import { localize } from 'vs/nls'; import { Categories } from 'vs/platform/action/common/actionCommonCategories'; @@ -22,6 +23,7 @@ import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/wid import { ITerminalProcessManager, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import type { Terminal } from '@xterm/xterm'; +import { TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; registerTerminalAction({ id: TerminalCommandId.ShowTextureAtlas, @@ -108,38 +110,105 @@ registerTerminalAction({ } }); -class DevModeContribution extends DisposableStore implements ITerminalContribution { +class DevModeContribution extends Disposable implements ITerminalContribution { static readonly ID = 'terminal.devMode'; - private _xterm: IXtermTerminal & { raw: Terminal } | undefined; static get(instance: ITerminalInstance): DevModeContribution | null { return instance.getContribution(DevModeContribution.ID); } + + private _xterm: IXtermTerminal & { raw: Terminal } | undefined; + private _activeDevModeDisposables = new MutableDisposable(); + constructor( - instance: ITerminalInstance, + private readonly _instance: ITerminalInstance, processManager: ITerminalProcessManager, widgetManager: TerminalWidgetManager, @IConfigurationService private readonly _configurationService: IConfigurationService, - @ITerminalService private readonly _terminalService: ITerminalService) { + @ITerminalService private readonly _terminalService: ITerminalService + ) { super(); - this.add(this._configurationService.onDidChangeConfiguration(e => { + this._register(this._configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration(TerminalSettingId.DevMode)) { this._updateDevMode(); } })); } + xtermReady(xterm: IXtermTerminal & { raw: Terminal }): void { this._xterm = xterm; this._updateDevMode(); } private _updateDevMode() { - const devMode: boolean = this._configurationService.getValue(TerminalSettingId.DevMode) || false; + const devMode: boolean = this._isEnabled(); this._xterm?.raw.element?.classList.toggle('dev-mode', devMode); + + // Text area syncing if (this._xterm?.raw.textarea) { const font = this._terminalService.configHelper.getFont(); this._xterm.raw.textarea.style.fontFamily = font.fontFamily; this._xterm.raw.textarea.style.fontSize = `${font.fontSize}px`; } + + // Sequence markers + const commandDetection = this._instance.capabilities.get(TerminalCapability.CommandDetection); + if (devMode) { + if (commandDetection) { + this._activeDevModeDisposables.value = commandDetection.onCommandFinished(command => { + if (command.promptStartMarker) { + const d = this._instance.xterm!.raw?.registerDecoration({ + marker: command.promptStartMarker + }); + d?.onRender(e => { + e.textContent = 'A'; + e.classList.add('xterm-sequence-decoration', 'top', 'left'); + }); + } + if (command.marker) { + const d = this._instance.xterm!.raw?.registerDecoration({ + marker: command.marker, + x: command.startX + }); + d?.onRender(e => { + e.textContent = 'B'; + e.classList.add('xterm-sequence-decoration', 'top', 'right'); + }); + } + if (command.executedMarker) { + const d = this._instance.xterm!.raw?.registerDecoration({ + marker: command.executedMarker, + x: command.executedX + }); + d?.onRender(e => { + e.textContent = 'C'; + e.classList.add('xterm-sequence-decoration', 'bottom', 'left'); + }); + } + if (command.endMarker) { + const d = this._instance.xterm!.raw?.registerDecoration({ + marker: command.endMarker + }); + d?.onRender(e => { + e.textContent = 'D'; + e.classList.add('xterm-sequence-decoration', 'bottom', 'right'); + }); + } + }); + } else { + this._activeDevModeDisposables.value = this._instance.capabilities.onDidAddCapabilityType(e => { + if (e === TerminalCapability.CommandDetection) { + this._updateDevMode(); + } + }); + } + } else { + this._activeDevModeDisposables.clear(); + } + } + + private _isEnabled(): boolean { + return this._configurationService.getValue(TerminalSettingId.DevMode) || false; } } + registerTerminalContribution(DevModeContribution.ID, DevModeContribution); diff --git a/src/vs/workbench/contrib/terminalContrib/find/browser/terminal.find.contribution.ts b/src/vs/workbench/contrib/terminalContrib/find/browser/terminal.find.contribution.ts index 83404f7cb1e..cf9c16b27f6 100644 --- a/src/vs/workbench/contrib/terminalContrib/find/browser/terminal.find.contribution.ts +++ b/src/vs/workbench/contrib/terminalContrib/find/browser/terminal.find.contribution.ts @@ -12,11 +12,11 @@ import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { findInFilesCommand } from 'vs/workbench/contrib/search/browser/searchActionsFind'; -import { ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; -import { registerActiveInstanceAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; +import { IDetachedTerminalInstance, ITerminalContribution, ITerminalInstance, ITerminalService, IXtermTerminal, isDetachedTerminalInstance } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { registerActiveInstanceAction, registerActiveXtermAction } from 'vs/workbench/contrib/terminal/browser/terminalActions'; import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; -import { ITerminalProcessManager, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; +import { ITerminalProcessInfo, ITerminalProcessManager, TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { TerminalFindWidget } from 'vs/workbench/contrib/terminalContrib/find/browser/terminalFindWidget'; import type { Terminal as RawXtermTerminal } from '@xterm/xterm'; @@ -24,7 +24,13 @@ import type { Terminal as RawXtermTerminal } from '@xterm/xterm'; class TerminalFindContribution extends Disposable implements ITerminalContribution { static readonly ID = 'terminal.find'; - static get(instance: ITerminalInstance): TerminalFindContribution | null { + /** + * Currently focused find widget. This is used to track action context since + * 'active terminals' are only tracked for non-detached terminal instanecs. + */ + static activeFindWidget?: TerminalFindContribution; + + static get(instance: ITerminalInstance | IDetachedTerminalInstance): TerminalFindContribution | null { return instance.getContribution(TerminalFindContribution.ID); } @@ -34,8 +40,8 @@ class TerminalFindContribution extends Disposable implements ITerminalContributi get findWidget(): TerminalFindWidget { return this._findWidget.value; } constructor( - private readonly _instance: ITerminalInstance, - processManager: ITerminalProcessManager, + private readonly _instance: ITerminalInstance | IDetachedTerminalInstance, + processManager: ITerminalProcessManager | ITerminalProcessInfo, widgetManager: TerminalWidgetManager, @IInstantiationService instantiationService: IInstantiationService, @ITerminalService terminalService: ITerminalService @@ -47,14 +53,22 @@ class TerminalFindContribution extends Disposable implements ITerminalContributi // Track focus and set state so we can force the scroll bar to be visible findWidget.focusTracker.onDidFocus(() => { + TerminalFindContribution.activeFindWidget = this; this._instance.forceScrollbarVisibility(); - terminalService.setActiveInstance(this._instance); + if (!isDetachedTerminalInstance(this._instance)) { + terminalService.setActiveInstance(this._instance); + } }); findWidget.focusTracker.onDidBlur(() => { + TerminalFindContribution.activeFindWidget = undefined; this._instance.resetScrollbarVisibility(); }); - this._instance.domElement.appendChild(findWidget.getDomNode()); + if (!this._instance.domElement) { + throw new Error('FindWidget expected terminal DOM to be initialized'); + } + + this._instance.domElement?.appendChild(findWidget.getDomNode()); if (this._lastLayoutDimensions) { findWidget.layout(this._lastLayoutDimensions.width); } @@ -63,7 +77,7 @@ class TerminalFindContribution extends Disposable implements ITerminalContributi }); } - layout(xterm: IXtermTerminal & { raw: RawXtermTerminal }, dimension: IDimension): void { + layout(_xterm: IXtermTerminal & { raw: RawXtermTerminal }, dimension: IDimension): void { this._lastLayoutDimensions = dimension; this._findWidget.rawValue?.layout(dimension.width); } @@ -73,98 +87,106 @@ class TerminalFindContribution extends Disposable implements ITerminalContributi } override dispose() { + if (TerminalFindContribution.activeFindWidget === this) { + TerminalFindContribution.activeFindWidget = undefined; + } super.dispose(); this._findWidget.rawValue?.dispose(); } } -registerTerminalContribution(TerminalFindContribution.ID, TerminalFindContribution); +registerTerminalContribution(TerminalFindContribution.ID, TerminalFindContribution, true); -registerActiveInstanceAction({ +registerActiveXtermAction({ id: TerminalCommandId.FindFocus, title: { value: localize('workbench.action.terminal.focusFind', "Focus Find"), original: 'Focus Find' }, keybinding: { primary: KeyMod.CtrlCmd | KeyCode.KeyF, - when: ContextKeyExpr.or(TerminalContextKeys.findFocus, TerminalContextKeys.focus), + when: ContextKeyExpr.or(TerminalContextKeys.findFocus, TerminalContextKeys.focusInAny), weight: KeybindingWeight.WorkbenchContrib }, precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - run: (activeInstance) => { - TerminalFindContribution.get(activeInstance)?.findWidget.reveal(); + run: (_xterm, _accessor, activeInstance) => { + const contr = TerminalFindContribution.activeFindWidget || TerminalFindContribution.get(activeInstance); + contr?.findWidget.reveal(); } }); -registerActiveInstanceAction({ +registerActiveXtermAction({ id: TerminalCommandId.FindHide, title: { value: localize('workbench.action.terminal.hideFind', "Hide Find"), original: 'Hide Find' }, keybinding: { primary: KeyCode.Escape, secondary: [KeyMod.Shift | KeyCode.Escape], - when: ContextKeyExpr.and(TerminalContextKeys.focus, TerminalContextKeys.findVisible), + when: ContextKeyExpr.and(TerminalContextKeys.focusInAny, TerminalContextKeys.findVisible), weight: KeybindingWeight.WorkbenchContrib }, precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - run: (activeInstance) => { - TerminalFindContribution.get(activeInstance)?.findWidget.hide(); + run: (_xterm, _accessor, activeInstance) => { + const contr = TerminalFindContribution.activeFindWidget || TerminalFindContribution.get(activeInstance); + contr?.findWidget.hide(); } }); -registerActiveInstanceAction({ +registerActiveXtermAction({ id: TerminalCommandId.ToggleFindRegex, title: { value: localize('workbench.action.terminal.toggleFindRegex', "Toggle Find Using Regex"), original: 'Toggle Find Using Regex' }, keybinding: { primary: KeyMod.Alt | KeyCode.KeyR, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyR }, - when: ContextKeyExpr.or(TerminalContextKeys.focus, TerminalContextKeys.findFocus), + when: ContextKeyExpr.or(TerminalContextKeys.focusInAny, TerminalContextKeys.findFocus), weight: KeybindingWeight.WorkbenchContrib }, precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - run: (activeInstance) => { - const state = TerminalFindContribution.get(activeInstance)?.findWidget.state; + run: (_xterm, _accessor, activeInstance) => { + const contr = TerminalFindContribution.activeFindWidget || TerminalFindContribution.get(activeInstance); + const state = contr?.findWidget.state; state?.change({ isRegex: !state.isRegex }, false); } }); -registerActiveInstanceAction({ +registerActiveXtermAction({ id: TerminalCommandId.ToggleFindWholeWord, title: { value: localize('workbench.action.terminal.toggleFindWholeWord', "Toggle Find Using Whole Word"), original: 'Toggle Find Using Whole Word' }, keybinding: { primary: KeyMod.Alt | KeyCode.KeyW, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyW }, - when: ContextKeyExpr.or(TerminalContextKeys.focus, TerminalContextKeys.findFocus), + when: ContextKeyExpr.or(TerminalContextKeys.focusInAny, TerminalContextKeys.findFocus), weight: KeybindingWeight.WorkbenchContrib }, precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - run: (activeInstance) => { - const state = TerminalFindContribution.get(activeInstance)?.findWidget.state; + run: (_xterm, _accessor, activeInstance) => { + const contr = TerminalFindContribution.activeFindWidget || TerminalFindContribution.get(activeInstance); + const state = contr?.findWidget.state; state?.change({ wholeWord: !state.wholeWord }, false); } }); -registerActiveInstanceAction({ +registerActiveXtermAction({ id: TerminalCommandId.ToggleFindCaseSensitive, title: { value: localize('workbench.action.terminal.toggleFindCaseSensitive', "Toggle Find Using Case Sensitive"), original: 'Toggle Find Using Case Sensitive' }, keybinding: { primary: KeyMod.Alt | KeyCode.KeyC, mac: { primary: KeyMod.CtrlCmd | KeyMod.Alt | KeyCode.KeyC }, - when: ContextKeyExpr.or(TerminalContextKeys.focus, TerminalContextKeys.findFocus), + when: ContextKeyExpr.or(TerminalContextKeys.focusInAny, TerminalContextKeys.findFocus), weight: KeybindingWeight.WorkbenchContrib }, precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - run: (activeInstance) => { - const state = TerminalFindContribution.get(activeInstance)?.findWidget.state; + run: (_xterm, _accessor, activeInstance) => { + const contr = TerminalFindContribution.activeFindWidget || TerminalFindContribution.get(activeInstance); + const state = contr?.findWidget.state; state?.change({ matchCase: !state.matchCase }, false); } }); -registerActiveInstanceAction({ +registerActiveXtermAction({ id: TerminalCommandId.FindNext, title: { value: localize('workbench.action.terminal.findNext', "Find Next"), original: 'Find Next' }, keybinding: [ { primary: KeyCode.F3, mac: { primary: KeyMod.CtrlCmd | KeyCode.KeyG, secondary: [KeyCode.F3] }, - when: ContextKeyExpr.or(TerminalContextKeys.focus, TerminalContextKeys.findFocus), + when: ContextKeyExpr.or(TerminalContextKeys.focusInAny, TerminalContextKeys.findFocus), weight: KeybindingWeight.WorkbenchContrib }, { @@ -174,8 +196,9 @@ registerActiveInstanceAction({ } ], precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - run: (activeInstance) => { - const widget = TerminalFindContribution.get(activeInstance)?.findWidget; + run: (_xterm, _accessor, activeInstance) => { + const contr = TerminalFindContribution.activeFindWidget || TerminalFindContribution.get(activeInstance); + const widget = contr?.findWidget; if (widget) { widget.show(); widget.find(false); @@ -183,14 +206,14 @@ registerActiveInstanceAction({ } }); -registerActiveInstanceAction({ +registerActiveXtermAction({ id: TerminalCommandId.FindPrevious, title: { value: localize('workbench.action.terminal.findPrevious', "Find Previous"), original: 'Find Previous' }, keybinding: [ { primary: KeyMod.Shift | KeyCode.F3, mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KeyG, secondary: [KeyMod.Shift | KeyCode.F3] }, - when: ContextKeyExpr.or(TerminalContextKeys.focus, TerminalContextKeys.findFocus), + when: ContextKeyExpr.or(TerminalContextKeys.focusInAny, TerminalContextKeys.findFocus), weight: KeybindingWeight.WorkbenchContrib }, { @@ -200,8 +223,9 @@ registerActiveInstanceAction({ } ], precondition: ContextKeyExpr.or(TerminalContextKeys.processSupported, TerminalContextKeys.terminalHasBeenCreated), - run: (activeInstance) => { - const widget = TerminalFindContribution.get(activeInstance)?.findWidget; + run: (_xterm, _accessor, activeInstance) => { + const contr = TerminalFindContribution.activeFindWidget || TerminalFindContribution.get(activeInstance); + const widget = contr?.findWidget; if (widget) { widget.show(); widget.find(true); diff --git a/src/vs/workbench/contrib/terminalContrib/find/browser/terminalFindWidget.ts b/src/vs/workbench/contrib/terminalContrib/find/browser/terminalFindWidget.ts index dbdc049730b..59b05ccdd36 100644 --- a/src/vs/workbench/contrib/terminalContrib/find/browser/terminalFindWidget.ts +++ b/src/vs/workbench/contrib/terminalContrib/find/browser/terminalFindWidget.ts @@ -3,10 +3,11 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import * as dom from 'vs/base/browser/dom'; import { SimpleFindWidget } from 'vs/workbench/contrib/codeEditor/browser/find/simpleFindWidget'; -import { IContextViewService } from 'vs/platform/contextview/browser/contextView'; +import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView'; import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; -import { ITerminalInstance, IXtermTerminal, XtermTerminalConstants } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { IDetachedTerminalInstance, ITerminalInstance, IXtermTerminal, XtermTerminalConstants } from 'vs/workbench/contrib/terminal/browser/terminal'; import { TerminalContextKeys } from 'vs/workbench/contrib/terminal/common/terminalContextKey'; import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -14,6 +15,8 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; import { Event } from 'vs/base/common/event'; import type { ISearchOptions } from '@xterm/addon-search'; import { TerminalCommandId } from 'vs/workbench/contrib/terminal/common/terminal'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { openContextMenu } from 'vs/workbench/contrib/terminalContrib/find/browser/textInputContextMenu'; const TERMINAL_FIND_WIDGET_INITIAL_WIDTH = 419; @@ -23,10 +26,12 @@ export class TerminalFindWidget extends SimpleFindWidget { private _findWidgetVisible: IContextKey; constructor( - private _instance: ITerminalInstance, + private _instance: ITerminalInstance | IDetachedTerminalInstance, @IContextViewService _contextViewService: IContextViewService, @IKeybindingService keybindingService: IKeybindingService, @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IContextMenuService _contextMenuService: IContextMenuService, + @IClipboardService _clipboardService: IClipboardService, @IThemeService private readonly _themeService: IThemeService, @IConfigurationService private readonly _configurationService: IConfigurationService ) { @@ -52,6 +57,20 @@ export class TerminalFindWidget extends SimpleFindWidget { this._findInputFocused = TerminalContextKeys.findInputFocus.bindTo(this._contextKeyService); this._findWidgetFocused = TerminalContextKeys.findFocus.bindTo(this._contextKeyService); this._findWidgetVisible = TerminalContextKeys.findVisible.bindTo(this._contextKeyService); + const innerDom = this.getDomNode().firstChild; + if (innerDom) { + this._register(dom.addDisposableListener(innerDom, 'mousedown', (event) => { + event.stopPropagation(); + })); + this._register(dom.addDisposableListener(innerDom, 'contextmenu', (event) => { + event.stopPropagation(); + })); + } + const findInputDomNode = this.getFindInputDomNode(); + this._register(dom.addDisposableListener(findInputDomNode, 'contextmenu', (event) => { + openContextMenu(dom.getWindow(findInputDomNode), event, _clipboardService, _contextMenuService); + event.stopPropagation(); + })); this._register(this._themeService.onDidColorThemeChange(() => { if (this.isVisible()) { this.find(true, true); diff --git a/src/vs/workbench/contrib/terminalContrib/find/browser/textInputContextMenu.ts b/src/vs/workbench/contrib/terminalContrib/find/browser/textInputContextMenu.ts new file mode 100644 index 00000000000..7253be4ca8e --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/find/browser/textInputContextMenu.ts @@ -0,0 +1,63 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { getActiveWindow } from 'vs/base/browser/dom'; +import { StandardMouseEvent } from 'vs/base/browser/mouseEvent'; +import { Action, IAction, Separator } from 'vs/base/common/actions'; +import { isNative } from 'vs/base/common/platform'; +import { localize } from 'vs/nls'; +import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; +import { IContextMenuService } from 'vs/platform/contextview/browser/contextView'; + +export function openContextMenu(targetWindow: Window, event: MouseEvent, clipboardService: IClipboardService, contextMenuService: IContextMenuService): void { + const standardEvent = new StandardMouseEvent(targetWindow, event); + + // Actions from workbench/browser/actions/textInputActions + const actions: IAction[] = []; + actions.push( + + // Undo/Redo + new Action('undo', localize('undo', "Undo"), undefined, true, async () => getActiveWindow().document.execCommand('undo')), + new Action('redo', localize('redo', "Redo"), undefined, true, async () => getActiveWindow().document.execCommand('redo')), + new Separator(), + + // Cut / Copy / Paste + new Action('editor.action.clipboardCutAction', localize('cut', "Cut"), undefined, true, async () => getActiveWindow().document.execCommand('cut')), + new Action('editor.action.clipboardCopyAction', localize('copy', "Copy"), undefined, true, async () => getActiveWindow().document.execCommand('copy')), + new Action('editor.action.clipboardPasteAction', localize('paste', "Paste"), undefined, true, async element => { + + // Native: paste is supported + if (isNative) { + getActiveWindow().document.execCommand('paste'); + } + + // Web: paste is not supported due to security reasons + else { + const clipboardText = await clipboardService.readText(); + if ( + element instanceof HTMLTextAreaElement || + element instanceof HTMLInputElement + ) { + const selectionStart = element.selectionStart || 0; + const selectionEnd = element.selectionEnd || 0; + + element.value = `${element.value.substring(0, selectionStart)}${clipboardText}${element.value.substring(selectionEnd, element.value.length)}`; + element.selectionStart = selectionStart + clipboardText.length; + element.selectionEnd = element.selectionStart; + } + } + }), + new Separator(), + + // Select All + new Action('editor.action.selectAll', localize('selectAll', "Select All"), undefined, true, async () => getActiveWindow().document.execCommand('selectAll')) + ); + + contextMenuService.showContextMenu({ + getAnchor: () => standardEvent, + getActions: () => actions, + getActionsContext: () => event.target, + }); +} diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLink.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLink.ts index e221a2e999a..a06be140f57 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLink.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLink.ts @@ -13,6 +13,7 @@ import { Emitter, Event } from 'vs/base/common/event'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { TerminalLinkType } from 'vs/workbench/contrib/terminalContrib/links/browser/links'; import { IHoverAction } from 'vs/workbench/services/hover/browser/hover'; +import { $window } from 'vs/base/browser/window'; export class TerminalLink extends DisposableStore implements ILink { decorations: ILinkDecorations; @@ -63,12 +64,12 @@ export class TerminalLink extends DisposableStore implements ILink { hover(event: MouseEvent, text: string): void { // Listen for modifier before handing it off to the hover to handle so it gets disposed correctly this._hoverListeners = new DisposableStore(); - this._hoverListeners.add(dom.addDisposableListener(document, 'keydown', e => { + this._hoverListeners.add(dom.addDisposableListener($window.document, 'keydown', e => { if (!e.repeat && this._isModifierDown(e)) { this._enableDecorations(); } })); - this._hoverListeners.add(dom.addDisposableListener(document, 'keyup', e => { + this._hoverListeners.add(dom.addDisposableListener($window.document, 'keyup', e => { if (!e.repeat && !this._isModifierDown(e)) { this._disableDecorations(); } @@ -101,7 +102,7 @@ export class TerminalLink extends DisposableStore implements ILink { } const origin = { x: event.pageX, y: event.pageY }; - this._hoverListeners.add(dom.addDisposableListener(document, dom.EventType.MOUSE_MOVE, e => { + this._hoverListeners.add(dom.addDisposableListener($window.document, dom.EventType.MOUSE_MOVE, e => { // Update decorations if (this._isModifierDown(e)) { this._enableDecorations(); @@ -110,7 +111,7 @@ export class TerminalLink extends DisposableStore implements ILink { } // Reset the scheduler if the mouse moves too much - if (Math.abs(e.pageX - origin.x) > window.devicePixelRatio * 2 || Math.abs(e.pageY - origin.y) > window.devicePixelRatio * 2) { + if (Math.abs(e.pageX - origin.x) > $window.devicePixelRatio * 2 || Math.abs(e.pageY - origin.y) > $window.devicePixelRatio * 2) { origin.x = e.pageX; origin.y = e.pageY; this._tooltipScheduler?.schedule(); diff --git a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkResolver.ts b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkResolver.ts index 75bf93bf994..4d217fde4af 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkResolver.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/browser/terminalLinkResolver.ts @@ -12,6 +12,7 @@ import { isWindows, OperatingSystem, OS } from 'vs/base/common/platform'; import { IFileService } from 'vs/platform/files/common/files'; import { IPath, posix, win32 } from 'vs/base/common/path'; import { ITerminalBackend } from 'vs/platform/terminal/common/terminal'; +import { $window } from 'vs/base/browser/window'; export class TerminalLinkResolver implements ITerminalLinkResolver { declare _serviceBrand: undefined; @@ -169,9 +170,9 @@ class LinkCache { set(link: string | URI, value: ResolvedLink) { // Reset cached link TTL on any set if (this._cacheTilTimeout) { - window.clearTimeout(this._cacheTilTimeout); + $window.clearTimeout(this._cacheTilTimeout); } - this._cacheTilTimeout = window.setTimeout(() => this._cache.clear(), LinkCacheConstants.TTL); + this._cacheTilTimeout = $window.setTimeout(() => this._cache.clear(), LinkCacheConstants.TTL); this._cache.set(this._getKey(link), value); } diff --git a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkOpeners.test.ts b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkOpeners.test.ts index 61a1edcc7f0..2d3ed9de648 100644 --- a/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkOpeners.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/links/test/browser/terminalLinkOpeners.test.ts @@ -141,6 +141,8 @@ suite('Workbench - TerminalLinkOpeners', () => { isTrusted: true, cwd: '/initial/cwd', timestamp: 0, + executedX: undefined, + startX: undefined, getOutput() { return undefined; }, getOutputMatch(outputMatcher: ITerminalOutputMatcher) { return undefined; }, marker: { @@ -279,6 +281,8 @@ suite('Workbench - TerminalLinkOpeners', () => { isTrusted: true, cwd, timestamp: 0, + executedX: undefined, + startX: undefined, getOutput() { return undefined; }, getOutputMatch(outputMatcher: ITerminalOutputMatcher) { return undefined; }, marker: { @@ -372,6 +376,8 @@ suite('Workbench - TerminalLinkOpeners', () => { command: '', isTrusted: true, cwd, + executedX: undefined, + startX: undefined, timestamp: 0, getOutput() { return undefined; }, getOutputMatch(outputMatcher: ITerminalOutputMatcher) { return undefined; }, diff --git a/src/vs/workbench/contrib/terminalContrib/quickFix/test/browser/quickFixAddon.test.ts b/src/vs/workbench/contrib/terminalContrib/quickFix/test/browser/quickFixAddon.test.ts index f6dbee14fb3..260db2ac9c8 100644 --- a/src/vs/workbench/contrib/terminalContrib/quickFix/test/browser/quickFixAddon.test.ts +++ b/src/vs/workbench/contrib/terminalContrib/quickFix/test/browser/quickFixAddon.test.ts @@ -444,6 +444,8 @@ function createCommand(command: string, output: string, outputMatcher?: RegExp | cwd: '', commandStartLineContent: '', markProperties: {}, + executedX: undefined, + startX: undefined, command, isTrusted: true, exitCode, diff --git a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/media/stickyScroll.css b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/media/stickyScroll.css new file mode 100644 index 00000000000..e4ed9dfcf73 --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/media/stickyScroll.css @@ -0,0 +1,42 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.terminal-sticky-scroll { + display: none; + position: absolute; + left: 0; + right: 0; + top: 0; + z-index: 32; /* Must be higher than .xterm-viewport and decorations */ + background: var(--vscode-terminalStickyScroll-background, var(--vscode-terminal-background, var(--vscode-panel-background))); + box-shadow: var(--vscode-scrollbar-shadow) 0 3px 2px -2px; +} + +.terminal-sticky-scroll.visible { + display:block; +} + +.terminal-sticky-scroll:hover { + cursor: pointer !important; +} + +.terminal-sticky-scroll .xterm { + position: relative !important; + pointer-events: none; + padding-bottom: 0 !important; +} + +.terminal-sticky-scroll .hover-overlay { + position: absolute; + left: 0; + right: 0; + top: 0; + bottom: 0; + cursor: pointer !important; +} + +.terminal-sticky-scroll .hover-overlay:hover { + background-color: var(--vscode-terminalStickyScrollHover-background, transparent); +} diff --git a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminal.stickyScroll.contribution.ts b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminal.stickyScroll.contribution.ts new file mode 100644 index 00000000000..93a11026354 --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminal.stickyScroll.contribution.ts @@ -0,0 +1,12 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { registerTerminalContribution } from 'vs/workbench/contrib/terminal/browser/terminalExtensions'; +import { TerminalStickyScrollContribution } from 'vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollContribution'; + +import 'vs/css!./media/stickyScroll'; +import './terminalStickyScrollColorRegistry'; + +registerTerminalContribution(TerminalStickyScrollContribution.ID, TerminalStickyScrollContribution, true); diff --git a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollColorRegistry.ts b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollColorRegistry.ts new file mode 100644 index 00000000000..3fb4a9f9322 --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollColorRegistry.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 { Color } from 'vs/base/common/color'; +import 'vs/css!./media/stickyScroll'; +import { localize } from 'vs/nls'; +import { registerColor } from 'vs/platform/theme/common/colorRegistry'; + +export const terminalStickyScrollBackground = registerColor('terminalStickyScroll.background', { + light: null, + dark: null, + hcDark: null, + hcLight: null +}, localize('terminalStickyScroll.background', 'The background color of the sticky scroll overlay in the terminal.')); + +export const terminalStickyScrollHoverBackground = registerColor('terminalStickyScrollHover.background', { + dark: '#2A2D2E', + light: '#F0F0F0', + hcDark: null, + hcLight: Color.fromHex('#0F4A85').transparent(0.1) +}, localize('terminalStickyScrollHover.background', 'The background color of the sticky scroll overlay in the terminal when hovered.')); diff --git a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollContribution.ts b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollContribution.ts new file mode 100644 index 00000000000..76db45f8a1a --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollContribution.ts @@ -0,0 +1,114 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import type { Terminal as RawXtermTerminal } from '@xterm/xterm'; +import { Event } from 'vs/base/common/event'; +import { Disposable, MutableDisposable } from 'vs/base/common/lifecycle'; +import 'vs/css!./media/stickyScroll'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; +import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; +import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding'; +import { TerminalCapability } from 'vs/platform/terminal/common/capabilities/capabilities'; +import { TerminalSettingId } from 'vs/platform/terminal/common/terminal'; +import { ITerminalContribution, ITerminalInstance, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { TerminalInstance, TerminalInstanceColorProvider } from 'vs/workbench/contrib/terminal/browser/terminalInstance'; +import { TerminalWidgetManager } from 'vs/workbench/contrib/terminal/browser/widgets/widgetManager'; +import { ITerminalProcessInfo, ITerminalProcessManager } from 'vs/workbench/contrib/terminal/common/terminal'; +import { TerminalStickyScrollOverlay } from 'vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay'; + +export class TerminalStickyScrollContribution extends Disposable implements ITerminalContribution { + static readonly ID = 'terminal.stickyScroll'; + + static get(instance: ITerminalInstance): TerminalStickyScrollContribution | null { + return instance.getContribution(TerminalStickyScrollContribution.ID); + } + + private _xterm?: IXtermTerminal & { raw: RawXtermTerminal }; + + private _overlay = this._register(new MutableDisposable()); + + private _enableListeners = this._register(new MutableDisposable()); + private _disableListeners = this._register(new MutableDisposable()); + + constructor( + private readonly _instance: ITerminalInstance, + processManager: ITerminalProcessManager | ITerminalProcessInfo, + widgetManager: TerminalWidgetManager, + @IConfigurationService private readonly _configurationService: IConfigurationService, + @IContextKeyService private readonly _contextKeyService: IContextKeyService, + @IInstantiationService private readonly _instantiationService: IInstantiationService, + @IKeybindingService private readonly _keybindingService: IKeybindingService, + ) { + super(); + + this._register(Event.runAndSubscribe(this._configurationService.onDidChangeConfiguration, e => { + if (!e || e.affectsConfiguration(TerminalSettingId.EnableStickyScroll)) { + this._refreshState(); + } + })); + } + + xtermReady(xterm: IXtermTerminal & { raw: RawXtermTerminal }): void { + this._xterm = xterm; + this._refreshState(); + } + + xtermOpen(xterm: IXtermTerminal & { raw: RawXtermTerminal }): void { + this._refreshState(); + } + + private _refreshState(): void { + if (this._overlay.value) { + this._tryDisable(); + } else { + this._tryEnable(); + } + + if (this._overlay.value) { + this._enableListeners.clear(); + if (!this._disableListeners.value) { + this._disableListeners.value = this._instance.capabilities.onDidRemoveCapability(e => { + if (e.id === TerminalCapability.CommandDetection) { + this._refreshState(); + } + }); + } + } else { + this._disableListeners.clear(); + if (!this._enableListeners.value) { + this._enableListeners.value = this._instance.capabilities.onDidAddCapability(e => { + if (e.id === TerminalCapability.CommandDetection) { + this._refreshState(); + } + }); + } + } + } + + private _tryEnable(): void { + if (this._shouldBeEnabled()) { + const xtermCtorEventually = TerminalInstance.getXtermConstructor(this._keybindingService, this._contextKeyService); + this._overlay.value = this._instantiationService.createInstance( + TerminalStickyScrollOverlay, + this._xterm!, + this._instantiationService.createInstance(TerminalInstanceColorProvider, this._instance), + this._instance.capabilities.get(TerminalCapability.CommandDetection)!, + xtermCtorEventually + ); + } + } + + private _tryDisable(): void { + if (!this._shouldBeEnabled()) { + this._overlay.clear(); + } + } + + private _shouldBeEnabled(): boolean { + const capability = this._instance.capabilities.get(TerminalCapability.CommandDetection); + return !!(this._configurationService.getValue(TerminalSettingId.EnableStickyScroll) && capability && this._xterm?.raw?.element); + } +} diff --git a/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts new file mode 100644 index 00000000000..b5bf7c808a2 --- /dev/null +++ b/src/vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollOverlay.ts @@ -0,0 +1,312 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import type { CanvasAddon as CanvasAddonType } from '@xterm/addon-canvas'; +import type { SerializeAddon as SerializeAddonType } from '@xterm/addon-serialize'; +import type { ITerminalOptions, Terminal as RawXtermTerminal, Terminal as XTermTerminal } from '@xterm/xterm'; +import { importAMDNodeModule } from 'vs/amdX'; +import { $, addStandardDisposableListener } from 'vs/base/browser/dom'; +import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async'; +import { memoize, throttle } from 'vs/base/common/decorators'; +import { Event } from 'vs/base/common/event'; +import { Disposable, MutableDisposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle'; +import 'vs/css!./media/stickyScroll'; +import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { ICommandDetectionCapability, ITerminalCommand } from 'vs/platform/terminal/common/capabilities/capabilities'; +import { getPromptRowCount } from 'vs/platform/terminal/common/capabilities/commandDetectionCapability'; +import { IThemeService } from 'vs/platform/theme/common/themeService'; +import { IXtermColorProvider, IXtermTerminal } from 'vs/workbench/contrib/terminal/browser/terminal'; +import { TERMINAL_CONFIG_SECTION } from 'vs/workbench/contrib/terminal/common/terminal'; +import { terminalStickyScrollHoverBackground } from 'vs/workbench/contrib/terminalContrib/stickyScroll/browser/terminalStickyScrollColorRegistry'; + +const enum OverlayState { + /** Initial state/disabled by the alt buffer. */ + Off = 0, + On = 1 +} + +const enum CssClasses { + Visible = 'visible' +} + +export class TerminalStickyScrollOverlay extends Disposable { + private _stickyScrollOverlay?: RawXtermTerminal; + private _serializeAddon?: SerializeAddonType; + + private _canvasAddon = this._register(new MutableDisposable()); + private _pendingCanvasAddon?: CancelablePromise; + + private _element?: HTMLElement; + private _currentStickyCommand?: ITerminalCommand; + private _currentContent?: string; + + private _refreshListeners = this._register(new MutableDisposable()); + + private _state: OverlayState = OverlayState.Off; + + constructor( + private readonly _xterm: IXtermTerminal & { raw: RawXtermTerminal }, + private readonly _xtermColorProvider: IXtermColorProvider, + private readonly _commandDetection: ICommandDetectionCapability, + xtermCtor: Promise, + @IConfigurationService configurationService: IConfigurationService, + @IThemeService private readonly _themeService: IThemeService, + ) { + super(); + + // Only show sticky scroll in the normal buffer + this._register(Event.runAndSubscribe(this._xterm.raw.buffer.onBufferChange, buffer => { + this._setState((buffer ?? this._xterm.raw.buffer.active).type === 'normal' ? OverlayState.On : OverlayState.Off); + })); + + // React to option changes + this._register(configurationService.onDidChangeConfiguration(e => { + if (e.affectsConfiguration(TERMINAL_CONFIG_SECTION)) { + this._syncOptions(); + } + })); + this._register(this._themeService.onDidColorThemeChange(() => { + this._syncOptions(); + })); + + // Eagerly create the overlay + xtermCtor.then(ctor => { + this._stickyScrollOverlay = this._register(new ctor({ + rows: 1, + cols: this._xterm.raw.cols, + allowProposedApi: true, + ...this._getOptions() + })); + + this._getSerializeAddonConstructor().then(SerializeAddon => { + this._serializeAddon = this._register(new SerializeAddon()); + this._xterm.raw.loadAddon(this._serializeAddon); + // Trigger a render as the serialize addon is required to render + this._refresh(); + }); + + this._syncGpuAccelerationState(); + }); + } + + private _setState(state: OverlayState) { + if (this._state === state) { + return; + } + switch (state) { + case OverlayState.Off: { + this._setVisible(false); + this._uninstallRefreshListeners(); + break; + } + case OverlayState.On: { + this._refresh(); + this._installRefreshListeners(); + break; + } + } + } + + private _installRefreshListeners() { + if (!this._refreshListeners.value) { + this._refreshListeners.value = combinedDisposable( + this._xterm.raw.onScroll(() => this._refresh()), + this._xterm.raw.onLineFeed(() => this._refresh()), + addStandardDisposableListener(this._xterm.raw.element!.querySelector('.xterm-viewport')!, 'scroll', () => this._refresh()), + ); + } + } + + private _uninstallRefreshListeners() { + this._refreshListeners.clear(); + } + + private _setVisible(isVisible: boolean) { + if (isVisible) { + this._ensureElement(); + // The GPU acceleration state may be changes at any time and there is no event to listen + // to currently. + this._syncGpuAccelerationState(); + } + this._element?.classList.toggle(CssClasses.Visible, isVisible); + } + + @throttle(0) + private _refresh(): void { + if (!this._xterm.raw.element?.parentElement || !this._stickyScrollOverlay || !this._serializeAddon) { + return; + } + + // The command from viewportY + 1 is used because this one will not be obscured by sticky + // scroll. + const command = this._commandDetection.getCommandForLine(this._xterm.raw.buffer.active.viewportY + 1); + this._currentStickyCommand = undefined; + + // Sticky scroll only works with non-partial commands + if (!command || !('marker' in command)) { + this._setVisible(false); + return; + } + + const buffer = this._xterm.raw.buffer.active; + const marker = command.marker; + if ( + // The marker doesn't exist + !marker || + // The marker was trimmed from the scrollback + marker.line === -1 || + // Hide sticky scroll if it's on the same line + marker.line === buffer.viewportY + ) { + this._setVisible(false); + return; + } + + // TODO: Support multi-line commands + + // Determine prompt length + const promptRowCount = getPromptRowCount(command, this._xterm.raw.buffer.active); + + // Clear attrs, reset cursor position, clear right + const content = this._serializeAddon.serialize({ + range: { + start: marker.line - (promptRowCount - 1), + end: marker.line + } + }); + + // Write content if it differs + if (content && this._currentContent !== content) { + if (this._stickyScrollOverlay.rows !== promptRowCount) { + this._stickyScrollOverlay.resize(this._stickyScrollOverlay.cols, promptRowCount); + } + this._stickyScrollOverlay.write('\x1b[0m\x1b[H\x1b[2J'); + this._stickyScrollOverlay.write(content); + this._currentContent = content; + // Debug log to show the command + // this._stickyScrollOverlay.write(` [${command?.command}]`); + } + + if (content && command.exitCode !== undefined) { + this._currentStickyCommand = command; + this._setVisible(true); + } else { + this._setVisible(false); + } + } + + private _ensureElement() { + if ( + // The element is already created + this._element || + // If the overlay is yet to be created, the terminal cannot be opened so defer to next call + !this._stickyScrollOverlay || + // The xterm.js instance isn't opened yet + !this._xterm?.raw.element?.parentElement + ) { + return; + } + + const overlay = this._stickyScrollOverlay; + + this._element = $('.terminal-sticky-scroll'); + const hoverOverlay = $('.hover-overlay'); + this._element.append(hoverOverlay); + this._xterm.raw.element.parentElement.append(this._element); + this._register(toDisposable(() => this._element?.remove())); + + this._stickyScrollOverlay.open(this._element); + + // Scroll to the command on click + this._register(addStandardDisposableListener(hoverOverlay, 'click', () => { + if (this._xterm && this._currentStickyCommand) { + this._xterm.markTracker.revealCommand(this._currentStickyCommand); + } + })); + + // Instead of juggling decorations for hover styles, use the selection to indicate the + // hover state as the selection is inaccessible anyway + this._register(addStandardDisposableListener(hoverOverlay, 'mouseover', () => overlay.selectAll())); + this._register(addStandardDisposableListener(hoverOverlay, 'mouseleave', () => overlay.clearSelection())); + } + + @throttle(0) + private _syncOptions() { + if (!this._stickyScrollOverlay) { + return; + } + this._stickyScrollOverlay.resize(this._xterm.raw.cols, 1); + this._stickyScrollOverlay.options = this._getOptions(); + this._syncGpuAccelerationState(); + } + + private _syncGpuAccelerationState() { + if (!this._stickyScrollOverlay) { + return; + } + const overlay = this._stickyScrollOverlay; + + // The Webgl renderer isn't used here as there are a limited number of webgl contexts + // available within a given page. This is a single row that isn't rendered to often so the + // performance isn't as important + if (this._xterm.isGpuAccelerated) { + if (!this._canvasAddon.value && !this._pendingCanvasAddon) { + this._pendingCanvasAddon = createCancelablePromise(async token => { + const CanvasAddon = await this._getCanvasAddonConstructor(); + if (!token.isCancellationRequested) { + this._canvasAddon.value = new CanvasAddon(); + overlay.loadAddon(this._canvasAddon.value); + } + this._pendingCanvasAddon = undefined; + }); + } + } else { + this._canvasAddon.clear(); + this._pendingCanvasAddon?.cancel(); + this._pendingCanvasAddon = undefined; + } + } + + private _getOptions(): ITerminalOptions { + const o = this._xterm.raw.options; + const theme = this._themeService.getColorTheme(); + return { + cursorInactiveStyle: 'none', + scrollback: 0, + logLevel: 'off', + + // Selection is used for hover state in the overlay + theme: { + ...this._xterm.getXtermTheme(), + background: this._xtermColorProvider.getBackgroundColor(theme)?.toString(), + selectionBackground: theme.getColor(terminalStickyScrollHoverBackground)?.toString(), + selectionInactiveBackground: undefined + }, + + documentOverride: o.documentOverride, + fontFamily: o.fontFamily, + fontWeight: o.fontWeight, + fontWeightBold: o.fontWeightBold, + fontSize: o.fontSize, + letterSpacing: o.letterSpacing, + lineHeight: o.lineHeight, + drawBoldTextInBrightColors: o.drawBoldTextInBrightColors, + minimumContrastRatio: o.minimumContrastRatio, + tabStopWidth: o.tabStopWidth, + overviewRulerWidth: o.overviewRulerWidth, + }; + } + + @memoize + private async _getCanvasAddonConstructor(): Promise { + const m = await importAMDNodeModule('@xterm/addon-canvas', 'lib/xterm-addon-canvas.js'); + return m.CanvasAddon; + } + + @memoize + private async _getSerializeAddonConstructor(): Promise { + const m = await importAMDNodeModule('@xterm/addon-serialize', 'lib/addon-serialize.js'); + return m.SerializeAddon; + } +} diff --git a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts index 072fa071e36..c8439af52c8 100644 --- a/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts +++ b/src/vs/workbench/contrib/testing/browser/testingOutputPeek.ts @@ -1534,7 +1534,7 @@ class TerminalMessagePeek extends Disposable implements IPeekOutputRenderer { private attachTerminalToDom(terminal: IDetachedTerminalInstance) { terminal.xterm.write('\x1b[?25l'); // hide cursor - dom.scheduleAtNextAnimationFrame(() => this.layoutTerminal(terminal), dom.getWindow(this.container)); + dom.scheduleAtNextAnimationFrame(dom.getWindow(this.container), () => this.layoutTerminal(terminal)); terminal.attachToElement(this.container, { enableGpu: false }); } diff --git a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts index 090221014d8..9f8044bbb1b 100644 --- a/src/vs/workbench/contrib/themes/browser/themes.contribution.ts +++ b/src/vs/workbench/contrib/themes/browser/themes.contribution.ts @@ -44,6 +44,7 @@ import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storag import { isWeb } from 'vs/base/common/platform'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IHostService } from 'vs/workbench/services/host/browser/host'; +import { getActiveWindow } from 'vs/base/browser/dom'; export const manageExtensionIcon = registerIcon('theme-selection-manage-extension', Codicon.gear, localize('manageExtensionIcon', 'Icon for the \'Manage\' action in the theme selection quick pick.')); @@ -306,7 +307,7 @@ class InstalledThemesPicker { if (selectThemeTimeout) { clearTimeout(selectThemeTimeout); } - selectThemeTimeout = window.setTimeout(() => { + selectThemeTimeout = getActiveWindow().setTimeout(() => { selectThemeTimeout = undefined; const newTheme = (theme ?? currentTheme) as IWorkbenchTheme; this.setTheme(newTheme, applyTheme ? 'auto' : 'preview').then(undefined, @@ -701,7 +702,7 @@ registerAction2(class extends Action2 { if (selectThemeTimeout) { clearTimeout(selectThemeTimeout); } - selectThemeTimeout = window.setTimeout(() => { + selectThemeTimeout = getActiveWindow().setTimeout(() => { selectThemeTimeout = undefined; const newTheme = (theme ?? currentTheme) as IWorkbenchTheme; themeService.setColorTheme(newTheme as IWorkbenchColorTheme, applyTheme ? 'auto' : 'preview').then(undefined, diff --git a/src/vs/workbench/contrib/url/browser/trustedDomainsValidator.ts b/src/vs/workbench/contrib/url/browser/trustedDomainsValidator.ts index 075c0882f9f..20cb4e67a50 100644 --- a/src/vs/workbench/contrib/url/browser/trustedDomainsValidator.ts +++ b/src/vs/workbench/contrib/url/browser/trustedDomainsValidator.ts @@ -18,17 +18,18 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; -import { IdleValue } from 'vs/base/common/async'; import { IAuthenticationService } from 'vs/workbench/services/authentication/common/authentication'; import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { testUrlMatchesGlob } from 'vs/workbench/contrib/url/common/urlGlob'; import { IWorkspaceTrustManagementService } from 'vs/platform/workspace/common/workspaceTrust'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; +import { mainWindow } from 'vs/base/browser/window'; +import { WindowIdleValue } from 'vs/base/browser/dom'; export class OpenerValidatorContributions implements IWorkbenchContribution { - private _readWorkspaceTrustedDomainsResult: IdleValue>; - private _readAuthenticationTrustedDomainsResult: IdleValue>; + private _readWorkspaceTrustedDomainsResult: WindowIdleValue>; + private _readAuthenticationTrustedDomainsResult: WindowIdleValue>; constructor( @IOpenerService private readonly _openerService: IOpenerService, @@ -47,19 +48,19 @@ export class OpenerValidatorContributions implements IWorkbenchContribution { ) { this._openerService.registerValidator({ shouldOpen: (uri, options) => this.validateLink(uri, options) }); - this._readAuthenticationTrustedDomainsResult = new IdleValue(() => + this._readAuthenticationTrustedDomainsResult = new WindowIdleValue(mainWindow, () => this._instantiationService.invokeFunction(readAuthenticationTrustedDomains)); this._authenticationService.onDidRegisterAuthenticationProvider(() => { this._readAuthenticationTrustedDomainsResult?.dispose(); - this._readAuthenticationTrustedDomainsResult = new IdleValue(() => + this._readAuthenticationTrustedDomainsResult = new WindowIdleValue(mainWindow, () => this._instantiationService.invokeFunction(readAuthenticationTrustedDomains)); }); - this._readWorkspaceTrustedDomainsResult = new IdleValue(() => + this._readWorkspaceTrustedDomainsResult = new WindowIdleValue(mainWindow, () => this._instantiationService.invokeFunction(readWorkspaceTrustedDomains)); this._workspaceContextService.onDidChangeWorkspaceFolders(() => { this._readWorkspaceTrustedDomainsResult?.dispose(); - this._readWorkspaceTrustedDomainsResult = new IdleValue(() => + this._readWorkspaceTrustedDomainsResult = new WindowIdleValue(mainWindow, () => this._instantiationService.invokeFunction(readWorkspaceTrustedDomains)); }); } diff --git a/src/vs/workbench/contrib/webview/browser/webview.contribution.ts b/src/vs/workbench/contrib/webview/browser/webview.contribution.ts index 0f13a054471..6fde8271d07 100644 --- a/src/vs/workbench/contrib/webview/browser/webview.contribution.ts +++ b/src/vs/workbench/contrib/webview/browser/webview.contribution.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { getActiveElement } from 'vs/base/browser/dom'; import { MultiCommand, RedoCommand, SelectAllCommand, UndoCommand } from 'vs/editor/browser/editorExtensions'; import { CopyAction, CutAction, PasteAction } from 'vs/editor/contrib/clipboard/browser/clipboard'; import * as nls from 'vs/nls'; @@ -26,7 +27,7 @@ function overrideCommandForWebview(command: MultiCommand | undefined, f: (webvie // When focused in a custom menu try to fallback to the active webview // This is needed for context menu actions and the menubar - if (document.activeElement?.classList.contains('action-menu-item')) { + if (getActiveElement()?.classList.contains('action-menu-item')) { const editorService = accessor.get(IEditorService); if (editorService.activeEditor instanceof WebviewInput) { f(editorService.activeEditor.webview); diff --git a/src/vs/workbench/contrib/webview/browser/webviewElement.ts b/src/vs/workbench/contrib/webview/browser/webviewElement.ts index a860270eca9..b442277e920 100644 --- a/src/vs/workbench/contrib/webview/browser/webviewElement.ts +++ b/src/vs/workbench/contrib/webview/browser/webviewElement.ts @@ -37,6 +37,7 @@ import { WebviewFindDelegate, WebviewFindWidget } from 'vs/workbench/contrib/web import { FromWebviewMessage, KeyEvent, ToWebviewMessage } from 'vs/workbench/contrib/webview/browser/webviewMessages'; import { decodeAuthority, webviewGenericCspSource, webviewRootResourceAuthority } from 'vs/workbench/contrib/webview/common/webview'; import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService'; +import { $window } from 'vs/base/browser/window'; interface WebviewContent { readonly html: string; @@ -102,7 +103,7 @@ export class WebviewElement extends Disposable implements IWebview, WebviewFindD if (!this._focused) { return false; } - if (document.activeElement && document.activeElement !== this.element) { + if ($window.document.activeElement && $window.document.activeElement !== this.element) { // looks like https://github.com/microsoft/vscode/issues/132641 // where the focus is actually not in the `