diff --git a/.vscode/settings.json b/.vscode/settings.json index 02813fa947e..ae8d69938e2 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -99,5 +99,10 @@ "*.js": "$(capture).*.js", "bootstrap.js": "bootstrap-*.js" }, - "explorer.experimental.fileNesting.enabled": true + "explorer.experimental.fileNesting.enabled": true, + "editor.quickSuggestions": { + "other": "inline", + "comments": "inline", + "strings": "inline" + }, } diff --git a/.yarnrc b/.yarnrc index 9cbcfebdd18..0ef74229647 100644 --- a/.yarnrc +++ b/.yarnrc @@ -1,4 +1,4 @@ disturl "https://electronjs.org/headers" -target "17.3.0" +target "17.3.1" runtime "electron" build_from_source "true" diff --git a/build/azure-pipelines/darwin/product-build-darwin.yml b/build/azure-pipelines/darwin/product-build-darwin.yml index 6f4e8661cb2..475aa86b7ca 100644 --- a/build/azure-pipelines/darwin/product-build-darwin.yml +++ b/build/azure-pipelines/darwin/product-build-darwin.yml @@ -236,7 +236,7 @@ steps: - script: | set -e VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-darwin-$(VSCODE_ARCH)" \ - yarn smoketest-no-compile --web --headless + yarn smoketest-no-compile --web --tracing --headless timeoutInMinutes: 10 displayName: Run smoke tests (Browser, Chromium) condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) @@ -245,10 +245,11 @@ steps: set -e APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) APP_NAME="`ls $APP_ROOT | head -n 1`" - yarn smoketest-no-compile --build "$APP_ROOT/$APP_NAME" + yarn smoketest-no-compile --tracing --build "$APP_ROOT/$APP_NAME" # Increased timeout because this test downloads stable code timeoutInMinutes: 20 displayName: Run smoke tests (Electron) + # continueOnError: true condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - script: | @@ -256,11 +257,32 @@ steps: APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) APP_NAME="`ls $APP_ROOT | head -n 1`" VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin-$(VSCODE_ARCH)" \ - yarn smoketest-no-compile --build "$APP_ROOT/$APP_NAME" --remote + yarn smoketest-no-compile --tracing --remote --build "$APP_ROOT/$APP_NAME" timeoutInMinutes: 10 displayName: Run smoke tests (Remote) + # continueOnError: true condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + # - script: | + # set -e + # APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) + # APP_NAME="`ls $APP_ROOT | head -n 1`" + # yarn smoketest-no-compile --legacy --tracing --build "$APP_ROOT/$APP_NAME" + # # Increased timeout because this test downloads stable code + # timeoutInMinutes: 20 + # displayName: Run smoke tests (Legacy, Electron) + # condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + + # - script: | + # set -e + # APP_ROOT=$(agent.builddirectory)/VSCode-darwin-$(VSCODE_ARCH) + # APP_NAME="`ls $APP_ROOT | head -n 1`" + # VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin-$(VSCODE_ARCH)" \ + # yarn smoketest-no-compile --legacy --tracing --remote --build "$APP_ROOT/$APP_NAME" + # timeoutInMinutes: 10 + # displayName: Run smoke tests (Legacy, Remote) + # condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - task: PublishPipelineArtifact@0 inputs: artifactName: crash-dump-macos-$(VSCODE_ARCH) diff --git a/build/azure-pipelines/linux/product-build-linux-client.yml b/build/azure-pipelines/linux/product-build-linux-client.yml index 35aad220604..5995d0c5939 100644 --- a/build/azure-pipelines/linux/product-build-linux-client.yml +++ b/build/azure-pipelines/linux/product-build-linux-client.yml @@ -259,7 +259,7 @@ steps: - script: | set -e VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-web-linux-$(VSCODE_ARCH)" \ - yarn smoketest-no-compile --web --headless --electronArgs="--disable-dev-shm-usage" + yarn smoketest-no-compile --web --tracing --headless --electronArgs="--disable-dev-shm-usage" timeoutInMinutes: 10 displayName: Run smoke tests (Browser, Chromium) condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) @@ -267,21 +267,41 @@ steps: - script: | set -e APP_PATH=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) - yarn smoketest-no-compile --build "$APP_PATH" --electronArgs="--disable-dev-shm-usage" + yarn smoketest-no-compile --tracing --build "$APP_PATH" # Increased timeout because this test downloads stable code timeoutInMinutes: 20 displayName: Run smoke tests (Electron) + # continueOnError: true condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) - script: | set -e APP_PATH=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \ - yarn smoketest-no-compile --build "$APP_PATH" --remote --electronArgs="--disable-dev-shm-usage" + yarn smoketest-no-compile --tracing --remote --build "$APP_PATH" timeoutInMinutes: 10 displayName: Run smoke tests (Remote) + # continueOnError: true condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + # - script: | + # set -e + # APP_PATH=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) + # yarn smoketest-no-compile --legacy --tracing --build "$APP_PATH" + # # Increased timeout because this test downloads stable code + # timeoutInMinutes: 20 + # displayName: Run smoke tests (Legacy, Electron) + # condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + + # - script: | + # set -e + # APP_PATH=$(agent.builddirectory)/VSCode-linux-$(VSCODE_ARCH) + # VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \ + # yarn smoketest-no-compile --legacy --tracing --remote --build "$APP_PATH" + # timeoutInMinutes: 10 + # displayName: Run smoke tests (Legacy, Remote) + # condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false')) + - task: PublishPipelineArtifact@0 inputs: artifactName: crash-dump-linux-$(VSCODE_ARCH) diff --git a/build/azure-pipelines/win32/product-build-win32.yml b/build/azure-pipelines/win32/product-build-win32.yml index 001ce407218..b68783b2e2c 100644 --- a/build/azure-pipelines/win32/product-build-win32.yml +++ b/build/azure-pipelines/win32/product-build-win32.yml @@ -220,7 +220,7 @@ steps: . build/azure-pipelines/win32/exec.ps1 $ErrorActionPreference = "Stop" $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-web-win32-$(VSCODE_ARCH)" - exec { yarn smoketest-no-compile --web --headless } + exec { yarn smoketest-no-compile --web --tracing --headless } displayName: Run smoke tests (Browser, Chromium) timeoutInMinutes: 10 condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) @@ -229,8 +229,9 @@ steps: . build/azure-pipelines/win32/exec.ps1 $ErrorActionPreference = "Stop" $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" - exec { yarn smoketest-no-compile --build "$AppRoot" } + exec { yarn smoketest-no-compile --tracing --build "$AppRoot" } displayName: Run smoke tests (Electron) + continueOnError: true # Increased timeout because this test downloads stable code timeoutInMinutes: 20 condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) @@ -240,8 +241,29 @@ steps: $ErrorActionPreference = "Stop" $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)" - exec { yarn smoketest-no-compile --build "$AppRoot" --remote } + exec { yarn smoketest-no-compile --tracing --remote --build "$AppRoot" } displayName: Run smoke tests (Remote) + continueOnError: true + timeoutInMinutes: 10 + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" + exec { yarn smoketest-no-compile --legacy --tracing --build "$AppRoot" } + displayName: Run smoke tests (Legacy, Electron) + # Increased timeout because this test downloads stable code + timeoutInMinutes: 20 + condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) + + - powershell: | + . build/azure-pipelines/win32/exec.ps1 + $ErrorActionPreference = "Stop" + $AppRoot = "$(agent.builddirectory)\VSCode-win32-$(VSCODE_ARCH)" + $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)" + exec { yarn smoketest-no-compile --legacy --tracing --remote --build "$AppRoot" } + displayName: Run smoke tests (Legacy, Remote) timeoutInMinutes: 10 condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64')) diff --git a/build/darwin/create-universal-app.js b/build/darwin/create-universal-app.js index 1206a8e4c22..197bf5b2f1b 100644 --- a/build/darwin/create-universal-app.js +++ b/build/darwin/create-universal-app.js @@ -33,6 +33,7 @@ async function main() { 'CodeResources', 'fsevents.node', 'Info.plist', + 'MainMenu.nib', '.npmrc' ], outAppPath, diff --git a/build/darwin/create-universal-app.ts b/build/darwin/create-universal-app.ts index 332e81dbb2c..7d145eaec71 100644 --- a/build/darwin/create-universal-app.ts +++ b/build/darwin/create-universal-app.ts @@ -38,6 +38,7 @@ async function main() { 'CodeResources', 'fsevents.node', 'Info.plist', // TODO@deepak1556: regressed with 11.4.2 internal builds + 'MainMenu.nib', // Generated sequence is not deterministic with Xcode 13 '.npmrc' ], outAppPath, diff --git a/build/lib/layersChecker.js b/build/lib/layersChecker.js index 113f15245b6..8caccef815e 100644 --- a/build/lib/layersChecker.js +++ b/build/lib/layersChecker.js @@ -29,30 +29,13 @@ const CORE_TYPES = [ 'setInterval', 'clearInterval', 'console', - 'log', - 'info', - 'warn', - 'error', - 'trace', - 'group', - 'groupEnd', - 'table', - 'assert', + 'Console', 'Error', + 'ErrorConstructor', 'String', - 'throws', - 'stack', - 'captureStackTrace', - 'stackTraceLimit', 'TextDecoder', 'TextEncoder', - 'encode', - 'decode', 'self', - 'trimStart', - 'trimEnd', - 'trimLeft', - 'trimRight', 'queueMicrotask', 'Array', 'Uint8Array', @@ -70,7 +53,9 @@ const CORE_TYPES = [ 'atob', 'AbortSignal', 'MessageChannel', - 'MessagePort' + 'MessagePort', + 'URL', + 'URLSearchParams' ]; // Types that are defined in a common layer but are known to be only // available in native environments should not be allowed in browser @@ -94,7 +79,6 @@ const RULES = [ ...CORE_TYPES, // Safe access to postMessage() and friends 'MessageEvent', - 'data' ], disallowedTypes: NATIVE_TYPES, disallowedDefinitions: [ @@ -180,19 +164,7 @@ const RULES = [ // node.js { target: '**/vs/**/node/**', - allowedTypes: [ - ...CORE_TYPES, - // --> types from node.d.ts that duplicate from lib.dom.d.ts - 'URL', - 'protocol', - 'hostname', - 'port', - 'pathname', - 'search', - 'username', - 'password', - 'origin' - ], + allowedTypes: CORE_TYPES, disallowedDefinitions: [ 'lib.dom.d.ts' // no DOM ] @@ -232,43 +204,49 @@ function checkFile(program, sourceFile, rule) { if (node.kind !== ts.SyntaxKind.Identifier) { return ts.forEachChild(node, checkNode); // recurse down } - const text = node.getText(sourceFile); + const checker = program.getTypeChecker(); + const symbol = checker.getSymbolAtLocation(node); + if (!symbol) { + return; + } + let _parentSymbol = symbol; + while (_parentSymbol.parent) { + _parentSymbol = _parentSymbol.parent; + } + const parentSymbol = _parentSymbol; + const text = parentSymbol.getName(); if (rule.allowedTypes?.some(allowed => allowed === text)) { return; // override } if (rule.disallowedTypes?.some(disallowed => disallowed === text)) { const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); - console.log(`[build/lib/layersChecker.ts]: Reference to '${text}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1})`); + console.log(`[build/lib/layersChecker.ts]: Reference to type '${text}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1})`); hasErrors = true; return; } - const checker = program.getTypeChecker(); - const symbol = checker.getSymbolAtLocation(node); - if (symbol) { - const declarations = symbol.declarations; - if (Array.isArray(declarations)) { - DeclarationLoop: for (const declaration of declarations) { - if (declaration) { - const parent = declaration.parent; - if (parent) { - const parentSourceFile = parent.getSourceFile(); - if (parentSourceFile) { - const definitionFileName = parentSourceFile.fileName; - if (rule.allowedDefinitions) { - for (const allowedDefinition of rule.allowedDefinitions) { - if (definitionFileName.indexOf(allowedDefinition) >= 0) { - continue DeclarationLoop; - } + const declarations = symbol.declarations; + if (Array.isArray(declarations)) { + DeclarationLoop: for (const declaration of declarations) { + if (declaration) { + const parent = declaration.parent; + if (parent) { + const parentSourceFile = parent.getSourceFile(); + if (parentSourceFile) { + const definitionFileName = parentSourceFile.fileName; + if (rule.allowedDefinitions) { + for (const allowedDefinition of rule.allowedDefinitions) { + if (definitionFileName.indexOf(allowedDefinition) >= 0) { + continue DeclarationLoop; } } - if (rule.disallowedDefinitions) { - for (const disallowedDefinition of rule.disallowedDefinitions) { - if (definitionFileName.indexOf(disallowedDefinition) >= 0) { - const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); - console.log(`[build/lib/layersChecker.ts]: Reference to '${text}' from '${disallowedDefinition}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1})`); - hasErrors = true; - return; - } + } + if (rule.disallowedDefinitions) { + for (const disallowedDefinition of rule.disallowedDefinitions) { + if (definitionFileName.indexOf(disallowedDefinition) >= 0) { + const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); + console.log(`[build/lib/layersChecker.ts]: Reference to symbol '${text}' from '${disallowedDefinition}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1})`); + hasErrors = true; + return; } } } diff --git a/build/lib/layersChecker.ts b/build/lib/layersChecker.ts index e8cea015a75..af9ccd0ae92 100644 --- a/build/lib/layersChecker.ts +++ b/build/lib/layersChecker.ts @@ -30,30 +30,13 @@ const CORE_TYPES = [ 'setInterval', 'clearInterval', 'console', - 'log', - 'info', - 'warn', - 'error', - 'trace', - 'group', - 'groupEnd', - 'table', - 'assert', + 'Console', 'Error', + 'ErrorConstructor', 'String', - 'throws', - 'stack', - 'captureStackTrace', - 'stackTraceLimit', 'TextDecoder', 'TextEncoder', - 'encode', - 'decode', 'self', - 'trimStart', - 'trimEnd', - 'trimLeft', - 'trimRight', 'queueMicrotask', 'Array', 'Uint8Array', @@ -71,7 +54,9 @@ const CORE_TYPES = [ 'atob', 'AbortSignal', 'MessageChannel', - 'MessagePort' + 'MessagePort', + 'URL', + 'URLSearchParams' ]; // Types that are defined in a common layer but are known to be only @@ -100,7 +85,6 @@ const RULES = [ // Safe access to postMessage() and friends 'MessageEvent', - 'data' ], disallowedTypes: NATIVE_TYPES, disallowedDefinitions: [ @@ -195,20 +179,7 @@ const RULES = [ // node.js { target: '**/vs/**/node/**', - allowedTypes: [ - ...CORE_TYPES, - - // --> types from node.d.ts that duplicate from lib.dom.d.ts - 'URL', - 'protocol', - 'hostname', - 'port', - 'pathname', - 'search', - 'username', - 'password', - 'origin' - ], + allowedTypes: CORE_TYPES, disallowedDefinitions: [ 'lib.dom.d.ts' // no DOM ] @@ -266,7 +237,21 @@ function checkFile(program: ts.Program, sourceFile: ts.SourceFile, rule: IRule) return ts.forEachChild(node, checkNode); // recurse down } - const text = node.getText(sourceFile); + const checker = program.getTypeChecker(); + const symbol = checker.getSymbolAtLocation(node); + + if (!symbol) { + return; + } + + let _parentSymbol: any = symbol; + + while (_parentSymbol.parent) { + _parentSymbol = _parentSymbol.parent; + } + + const parentSymbol = _parentSymbol as ts.Symbol; + const text = parentSymbol.getName(); if (rule.allowedTypes?.some(allowed => allowed === text)) { return; // override @@ -274,40 +259,37 @@ function checkFile(program: ts.Program, sourceFile: ts.SourceFile, rule: IRule) if (rule.disallowedTypes?.some(disallowed => disallowed === text)) { const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); - console.log(`[build/lib/layersChecker.ts]: Reference to '${text}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1})`); + console.log(`[build/lib/layersChecker.ts]: Reference to type '${text}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1})`); hasErrors = true; return; } - const checker = program.getTypeChecker(); - const symbol = checker.getSymbolAtLocation(node); - if (symbol) { - const declarations = symbol.declarations; - if (Array.isArray(declarations)) { - DeclarationLoop: for (const declaration of declarations) { - if (declaration) { - const parent = declaration.parent; - if (parent) { - const parentSourceFile = parent.getSourceFile(); - if (parentSourceFile) { - const definitionFileName = parentSourceFile.fileName; - if (rule.allowedDefinitions) { - for (const allowedDefinition of rule.allowedDefinitions) { - if (definitionFileName.indexOf(allowedDefinition) >= 0) { - continue DeclarationLoop; - } + const declarations = symbol.declarations; + if (Array.isArray(declarations)) { + DeclarationLoop: for (const declaration of declarations) { + if (declaration) { + const parent = declaration.parent; + if (parent) { + const parentSourceFile = parent.getSourceFile(); + if (parentSourceFile) { + const definitionFileName = parentSourceFile.fileName; + if (rule.allowedDefinitions) { + for (const allowedDefinition of rule.allowedDefinitions) { + if (definitionFileName.indexOf(allowedDefinition) >= 0) { + continue DeclarationLoop; } } - if (rule.disallowedDefinitions) { - for (const disallowedDefinition of rule.disallowedDefinitions) { - if (definitionFileName.indexOf(disallowedDefinition) >= 0) { - const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); - console.log(`[build/lib/layersChecker.ts]: Reference to '${text}' from '${disallowedDefinition}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1})`); + } + if (rule.disallowedDefinitions) { + for (const disallowedDefinition of rule.disallowedDefinitions) { + if (definitionFileName.indexOf(disallowedDefinition) >= 0) { + const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart()); - hasErrors = true; - return; - } + console.log(`[build/lib/layersChecker.ts]: Reference to symbol '${text}' from '${disallowedDefinition}' violates layer '${rule.target}' (${sourceFile.fileName} (${line + 1},${character + 1})`); + + hasErrors = true; + return; } } } diff --git a/cgmanifest.json b/cgmanifest.json index ca83f087f7a..cb20604a3a4 100644 --- a/cgmanifest.json +++ b/cgmanifest.json @@ -60,12 +60,12 @@ "git": { "name": "electron", "repositoryUrl": "https://github.com/electron/electron", - "commitHash": "1401284b44c37853072d502b86f30009c9901d00" + "commitHash": "9ace5b3491881cb4230a921bb4809edeb393858c" } }, "isOnlyProductionDependency": true, "license": "MIT", - "version": "17.3.0" + "version": "17.3.1" }, { "component": { diff --git a/extensions/css-language-features/client/src/cssClient.ts b/extensions/css-language-features/client/src/cssClient.ts index 35774d6ed29..282c347bdd8 100644 --- a/extensions/css-language-features/client/src/cssClient.ts +++ b/extensions/css-language-features/client/src/cssClient.ts @@ -32,9 +32,12 @@ interface CSSFormatSettings { newlineBetweenSelectors?: boolean; newlineBetweenRules?: boolean; spaceAroundSelectorSeparator?: boolean; + braceStyle?: 'collapse' | 'expand'; + preserveNewLines?: boolean; + maxPreserveNewLines?: number | null; } -const cssFormatSettingKeys: (keyof CSSFormatSettings)[] = ['newlineBetweenSelectors', 'newlineBetweenRules', 'spaceAroundSelectorSeparator']; +const cssFormatSettingKeys: (keyof CSSFormatSettings)[] = ['newlineBetweenSelectors', 'newlineBetweenRules', 'spaceAroundSelectorSeparator', 'braceStyle', 'preserveNewLines', 'maxPreserveNewLines']; export function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime) { @@ -196,7 +199,7 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua if (formatterSettings) { for (const key of cssFormatSettingKeys) { const val = formatterSettings[key]; - if (val !== undefined) { + if (val !== undefined && val !== null) { params.options[key] = val; } } diff --git a/extensions/css-language-features/package.json b/extensions/css-language-features/package.json index 12ec5c76cd0..e9ea587d4db 100644 --- a/extensions/css-language-features/package.json +++ b/extensions/css-language-features/package.json @@ -331,6 +331,28 @@ "scope": "resource", "default": false, "markdownDescription": "%css.format.spaceAroundSelectorSeparator.desc%" + }, + "css.format.braceStyle": { + "type": "string", + "scope": "resource", + "default": "collapse", + "enum": ["collapse", "expand"], + "markdownDescription": "%css.format.braceStyle.desc%" + }, + "css.format.preserveNewLines": { + "type": "boolean", + "scope": "resource", + "default": true, + "markdownDescription": "%css.format.preserveNewLines.desc%" + }, + "css.format.maxPreserveNewLines": { + "type": [ + "number", + "null" + ], + "scope": "resource", + "default": null, + "markdownDescription": "%css.format.maxPreserveNewLines.desc%" } } }, @@ -611,6 +633,28 @@ "scope": "resource", "default": false, "markdownDescription": "%scss.format.spaceAroundSelectorSeparator.desc%" + }, + "scss.format.braceStyle": { + "type": "string", + "scope": "resource", + "default": "collapse", + "enum": ["collapse", "expand"], + "markdownDescription": "%scss.format.braceStyle.desc%" + }, + "scss.format.preserveNewLines": { + "type": "boolean", + "scope": "resource", + "default": true, + "markdownDescription": "%scss.format.preserveNewLines.desc%" + }, + "scss.format.maxPreserveNewLines": { + "type": [ + "number", + "null" + ], + "scope": "resource", + "default": null, + "markdownDescription": "%scss.format.maxPreserveNewLines.desc%" } } }, @@ -892,6 +936,28 @@ "scope": "resource", "default": false, "markdownDescription": "%less.format.spaceAroundSelectorSeparator.desc%" + }, + "less.format.braceStyle": { + "type": "string", + "scope": "resource", + "default": "collapse", + "enum": ["collapse", "expand"], + "markdownDescription": "%less.format.braceStyle.desc%" + }, + "less.format.preserveNewLines": { + "type": "boolean", + "scope": "resource", + "default": true, + "markdownDescription": "%less.format.preserveNewLines.desc%" + }, + "less.format.maxPreserveNewLines": { + "type": [ + "number", + "null" + ], + "scope": "resource", + "default": null, + "markdownDescription": "%less.format.maxPreserveNewLines.desc%" } } } diff --git a/extensions/css-language-features/package.nls.json b/extensions/css-language-features/package.nls.json index fbcaed2113a..784eaa531b7 100644 --- a/extensions/css-language-features/package.nls.json +++ b/extensions/css-language-features/package.nls.json @@ -34,6 +34,9 @@ "css.format.newlineBetweenSelectors.desc": "Separate selectors with a new line.", "css.format.newlineBetweenRules.desc": "Separate rulesets by a blank line.", "css.format.spaceAroundSelectorSeparator.desc": "Ensure a space character around selector separators '>', '+', '~' (e.g. `a > b`).", + "css.format.braceStyle.desc": "Put braces on the same line as rules (`collapse`) or put braces on own line (`expand`).", + "css.format.preserveNewLines.desc": "Whether existing line breaks before elements should be preserved.", + "css.format.maxPreserveNewLines.desc": "Maximum number of line breaks to be preserved in one chunk, when `#css.format.preserveNewLines#` is enabled.", "less.title": "LESS", "less.completion.triggerPropertyValueCompletion.desc": "By default, VS Code triggers property value completion after selecting a CSS property. Use this setting to disable this behavior.", "less.completion.completePropertyWithSemicolon.desc": "Insert semicolon at end of line when completing CSS properties.", @@ -65,6 +68,9 @@ "less.format.newlineBetweenSelectors.desc": "Separate selectors with a new line.", "less.format.newlineBetweenRules.desc": "Separate rulesets by a blank line.", "less.format.spaceAroundSelectorSeparator.desc": "Ensure a space character around selector separators '>', '+', '~' (e.g. `a > b`).", + "less.format.braceStyle.desc": "Put braces on the same line as rules (`collapse`) or put braces on own line (`expand`).", + "less.format.preserveNewLines.desc": "Whether existing line breaks before elements should be preserved.", + "less.format.maxPreserveNewLines.desc": "Maximum number of line breaks to be preserved in one chunk, when `#less.format.preserveNewLines#` is enabled.", "scss.title": "SCSS (Sass)", "scss.completion.triggerPropertyValueCompletion.desc": "By default, VS Code triggers property value completion after selecting a CSS property. Use this setting to disable this behavior.", "scss.completion.completePropertyWithSemicolon.desc": "Insert semicolon at end of line when completing CSS properties.", @@ -96,6 +102,9 @@ "scss.format.newlineBetweenSelectors.desc": "Separate selectors with a new line.", "scss.format.newlineBetweenRules.desc": "Separate rulesets by a blank line.", "scss.format.spaceAroundSelectorSeparator.desc": "Ensure a space character around selector separators '>', '+', '~' (e.g. `a > b`).", + "scss.format.braceStyle.desc": "Put braces on the same line as rules (`collapse`) or put braces on own line (`expand`).", + "scss.format.preserveNewLines.desc": "Whether existing line breaks before elements should be preserved.", + "scss.format.maxPreserveNewLines.desc": "Maximum number of line breaks to be preserved in one chunk, when `#scss.format.preserveNewLines#` is enabled.", "css.colorDecorators.enable.deprecationMessage": "The setting `css.colorDecorators.enable` has been deprecated in favor of `editor.colorDecorators`.", "scss.colorDecorators.enable.deprecationMessage": "The setting `scss.colorDecorators.enable` has been deprecated in favor of `editor.colorDecorators`.", "less.colorDecorators.enable.deprecationMessage": "The setting `less.colorDecorators.enable` has been deprecated in favor of `editor.colorDecorators`." diff --git a/extensions/css-language-features/server/package.json b/extensions/css-language-features/server/package.json index fc604a23125..865414065cf 100644 --- a/extensions/css-language-features/server/package.json +++ b/extensions/css-language-features/server/package.json @@ -10,7 +10,7 @@ "main": "./out/node/cssServerMain", "browser": "./dist/browser/cssServerMain", "dependencies": { - "vscode-css-languageservice": "^5.3.0", + "vscode-css-languageservice": "^5.4.1", "vscode-languageserver": "^7.0.0", "vscode-uri": "^3.0.3" }, diff --git a/extensions/css-language-features/server/yarn.lock b/extensions/css-language-features/server/yarn.lock index 6615d59d27a..a781f65f7cf 100644 --- a/extensions/css-language-features/server/yarn.lock +++ b/extensions/css-language-features/server/yarn.lock @@ -12,10 +12,10 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.6.tgz#6bef7a2a0ad684cf6e90fcfe31cecabd9ce0a3ae" integrity sha512-ua7PgUoeQFjmWPcoo9khiPum3Pd60k4/2ZGXt18sm2Slk0W0xZTqt5Y0Ny1NyBiN1EVQ/+FaF9NcY4Qe6rwk5w== -vscode-css-languageservice@^5.3.0: - version "5.3.0" - resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-5.3.0.tgz#48b800865c5b6ca7ab43225c63e8a4f1f24ec9b0" - integrity sha512-ujWW855AoJlE4ETU17Gff7unlZZTHDA0w26itk9EQFMfJqi9lE6S67zOsMvcPmJf55MrnGQbojDYZRiDVaFjdA== +vscode-css-languageservice@^5.4.1: + version "5.4.1" + resolved "https://registry.yarnpkg.com/vscode-css-languageservice/-/vscode-css-languageservice-5.4.1.tgz#6bb309d617c40f5b4b7752d05f1cbb9b2eaab102" + integrity sha512-W7D3GKFXf97ReAaU4EZ2nxVO1kQhztbycJgc1b/Ipr0h8zYWr88BADmrXu02z+lsCS84D7Sr4hoUzDKeaFn2Kg== dependencies: vscode-languageserver-textdocument "^1.0.4" vscode-languageserver-types "^3.16.0" diff --git a/extensions/emmet/src/defaultCompletionProvider.ts b/extensions/emmet/src/defaultCompletionProvider.ts index afb90b64418..1462a2ec4d7 100644 --- a/extensions/emmet/src/defaultCompletionProvider.ts +++ b/extensions/emmet/src/defaultCompletionProvider.ts @@ -161,25 +161,27 @@ export class DefaultCompletionItemProvider implements vscode.CompletionItemProvi return; } - let noiseCheckPromise: Thenable = Promise.resolve(); + let isNoisePromise: Thenable = Promise.resolve(false); // Fix for https://github.com/microsoft/vscode/issues/32647 // Check for document symbols in js/ts/jsx/tsx and avoid triggering emmet for abbreviations of the form symbolName.sometext // Presence of > or * or + in the abbreviation denotes valid abbreviation that should trigger emmet if (!isStyleSheet(syntax) && (document.languageId === 'javascript' || document.languageId === 'javascriptreact' || document.languageId === 'typescript' || document.languageId === 'typescriptreact')) { let abbreviation: string = extractAbbreviationResults.abbreviation; - if (abbreviation.startsWith('this.')) { - noiseCheckPromise = Promise.resolve(true); + // For the second condition, we don't want abbreviations that have [] characters but not ='s in them to expand + // In turn, users must explicitly expand abbreviations of the form Component[attr1 attr2], but it means we don't try to expand a[i]. + if (abbreviation.startsWith('this.') || /\[[^\]=]*\]/.test(abbreviation)) { + isNoisePromise = Promise.resolve(true); } else { - noiseCheckPromise = vscode.commands.executeCommand('vscode.executeDocumentSymbolProvider', document.uri).then((symbols: vscode.SymbolInformation[] | undefined) => { - return symbols && symbols.find(x => abbreviation === x.name || (abbreviation.startsWith(x.name + '.') && !/>|\*|\+/.test(abbreviation))); + isNoisePromise = vscode.commands.executeCommand('vscode.executeDocumentSymbolProvider', document.uri).then(symbols => { + return !!symbols && symbols.some(x => abbreviation === x.name || (abbreviation.startsWith(x.name + '.') && !/>|\*|\+/.test(abbreviation))); }); } } - return noiseCheckPromise.then((noise): vscode.CompletionList | undefined => { - if (noise) { - return; + return isNoisePromise.then((isNoise): vscode.CompletionList | undefined => { + if (isNoise) { + return undefined; } const config = getEmmetConfiguration(syntax!); diff --git a/extensions/emmet/src/test/completion.test.ts b/extensions/emmet/src/test/completion.test.ts index 755b11aea18..ec94a0f4928 100644 --- a/extensions/emmet/src/test/completion.test.ts +++ b/extensions/emmet/src/test/completion.test.ts @@ -15,32 +15,40 @@ suite('Tests for completion in CSS embedded in HTML', () => { teardown(closeAllEditors); test('style attribute & attribute value in html', async () => { - await testHtmlCompletionProvider('
{ - await testHtmlCompletionProvider(`
di|
`, [ + await testCompletionProvider('html', `
di|
`, [ { label: 'div', documentation: `
|
` } ]); }); // https://github.com/microsoft/vscode/issues/86941 test('#86941, widows should not be completed', async () => { - await testCssCompletionProvider(`.foo { wi| }`, [ - { label: 'widows: ;', documentation: `widows: ;` } - ]); + await testCompletionProvider('css', `.foo { wi| }`, undefined); }); // https://github.com/microsoft/vscode/issues/117020 test('#117020, ! at end of abbreviation should have completion', async () => { - await testCssCompletionProvider(`.foo { bdbn!| }`, [ + await testCompletionProvider('css', `.foo { bdbn!| }`, [ { label: 'border-bottom: none !important;', documentation: `border-bottom: none !important;` } ]); }); + + // https://github.com/microsoft/vscode/issues/138461 + test('#138461, JSX array noise', async () => { + await testCompletionProvider('jsx', 'a[i]', undefined); + await testCompletionProvider('jsx', 'Component[a b]', undefined); + await testCompletionProvider('jsx', '[a, b]', undefined); + await testCompletionProvider('jsx', '[a=b]', [ + { label: '
', documentation: '
|
' } + ]); + }); }); interface TestCompletionItem { @@ -49,11 +57,11 @@ interface TestCompletionItem { documentation?: string; } -function testHtmlCompletionProvider(contents: string, expectedItems: TestCompletionItem[]): Thenable { +function testCompletionProvider(fileExtension: string, contents: string, expectedItems: TestCompletionItem[] | undefined): Thenable { const cursorPos = contents.indexOf('|'); - const htmlContents = contents.slice(0, cursorPos) + contents.slice(cursorPos + 1); + const slicedContents = contents.slice(0, cursorPos) + contents.slice(cursorPos + 1); - return withRandomFileEditor(htmlContents, 'html', async (editor, _doc) => { + return withRandomFileEditor(slicedContents, fileExtension, async (editor, _doc) => { const selection = new Selection(editor.document.positionAt(cursorPos), editor.document.positionAt(cursorPos)); editor.selection = selection; const cancelSrc = new CancellationTokenSource(); @@ -69,51 +77,14 @@ function testHtmlCompletionProvider(contents: string, expectedItems: TestComplet const completionList = await completionPromise; if (!completionList || !completionList.items || !completionList.items.length) { + if (completionList === undefined) { + assert.strictEqual(expectedItems, completionList); + } return Promise.resolve(); } - expectedItems.forEach(eItem => { - const matches = completionList.items.filter(i => i.label === eItem.label); - const match = matches && matches.length > 0 ? matches[0] : undefined; - assert.ok(match, `Didn't find completion item with label ${eItem.label}`); - - if (match) { - assert.strictEqual(match.detail, 'Emmet Abbreviation', `Match needs to come from Emmet`); - - if (eItem.documentation) { - assert.strictEqual(match.documentation, eItem.documentation, `Emmet completion Documentation doesn't match`); - } - } - }); - - return Promise.resolve(); - }); -} - -function testCssCompletionProvider(contents: string, expectedItems: TestCompletionItem[]): Thenable { - const cursorPos = contents.indexOf('|'); - const cssContents = contents.slice(0, cursorPos) + contents.slice(cursorPos + 1); - - return withRandomFileEditor(cssContents, 'css', async (editor, _doc) => { - const selection = new Selection(editor.document.positionAt(cursorPos), editor.document.positionAt(cursorPos)); - editor.selection = selection; - const cancelSrc = new CancellationTokenSource(); - const completionPromise = completionProvider.provideCompletionItems( - editor.document, - editor.selection.active, - cancelSrc.token, - { triggerKind: CompletionTriggerKind.Invoke, triggerCharacter: undefined } - ); - if (!completionPromise) { - return Promise.resolve(); - } - - const completionList = await completionPromise; - if (!completionList || !completionList.items || !completionList.items.length) { - return Promise.resolve(); - } - - expectedItems.forEach(eItem => { + assert.strictEqual(expectedItems === undefined, false); + expectedItems!.forEach(eItem => { const matches = completionList.items.filter(i => i.label === eItem.label); const match = matches && matches.length > 0 ? matches[0] : undefined; assert.ok(match, `Didn't find completion item with label ${eItem.label}`); diff --git a/extensions/emmet/src/util.ts b/extensions/emmet/src/util.ts index e81d25bae3b..ee45de467e6 100644 --- a/extensions/emmet/src/util.ts +++ b/extensions/emmet/src/util.ts @@ -226,7 +226,7 @@ export function parsePartialStylesheet(document: vscode.TextDocument, position: } function consumeBlockCommentBackwards() { - if (stream.peek() === slash) { + if (!stream.sof() && stream.peek() === slash) { if (stream.backUp(1) === star) { stream.pos = findOpeningCommentBeforePosition(stream.pos) ?? startOffset; } else { diff --git a/extensions/git/package.json b/extensions/git/package.json index 97696d22028..1122b41fc4c 100644 --- a/extensions/git/package.json +++ b/extensions/git/package.json @@ -986,6 +986,11 @@ "group": "1_header@4", "when": "scmProvider == git" }, + { + "command": "git.fetch", + "group": "1_header@5", + "when": "scmProvider == git" + }, { "submenu": "git.commit", "group": "2_main@1", diff --git a/extensions/git/src/repository.ts b/extensions/git/src/repository.ts index 92c78a44969..7899d1bb8ad 100644 --- a/extensions/git/src/repository.ts +++ b/extensions/git/src/repository.ts @@ -1272,7 +1272,7 @@ export class Repository implements Disposable { const diffEditorTabsToClose: Tab[] = []; - for (const tab of window.tabGroups.groups.map(g => g.tabs).flat()) { + for (const tab of window.tabGroups.all.map(g => g.tabs).flat()) { const { kind } = tab; if (kind instanceof TabKindTextDiff || kind instanceof TabKindNotebookDiff) { if (kind.modified.scheme === 'git' && indexResources.some(r => pathEquals(r, kind.modified.fsPath))) { diff --git a/extensions/github/package.json b/extensions/github/package.json index f86f98683af..af7b544f596 100644 --- a/extensions/github/package.json +++ b/extensions/github/package.json @@ -49,6 +49,15 @@ "scope": "resource", "default": true, "description": "%config.gitAuthentication%" + }, + "github.gitProtocol": { + "type": "string", + "enum": [ + "https", + "ssh" + ], + "default": "https", + "description": "%config.gitProtocol%" } } } diff --git a/extensions/github/package.nls.json b/extensions/github/package.nls.json index 472c5ab7e4c..ebebab0e0e2 100644 --- a/extensions/github/package.nls.json +++ b/extensions/github/package.nls.json @@ -2,6 +2,7 @@ "displayName": "GitHub", "description": "GitHub features for VS Code", "config.gitAuthentication": "Controls whether to enable automatic GitHub authentication for git commands within VS Code.", + "config.gitProtocol": "Controls which protocol is used to clone a GitHub repository", "welcome.publishFolder": "You can also directly publish this folder to a GitHub repository. Once published, you'll have access to source control features powered by git and GitHub.\n[$(github) Publish to GitHub](command:github.publish)", "welcome.publishWorkspaceFolder": "You can also directly publish a workspace folder to a GitHub repository. Once published, you'll have access to source control features powered by git and GitHub.\n[$(github) Publish to GitHub](command:github.publish)" } diff --git a/extensions/github/src/auth.ts b/extensions/github/src/auth.ts index a0a0244255a..af91374e91f 100644 --- a/extensions/github/src/auth.ts +++ b/extensions/github/src/auth.ts @@ -53,4 +53,3 @@ export function getOctokit(): Promise { return _octokit; } - diff --git a/extensions/github/src/publish.ts b/extensions/github/src/publish.ts index 0954e2c67ff..fe8a31bbc67 100644 --- a/extensions/github/src/publish.ts +++ b/extensions/github/src/publish.ts @@ -197,7 +197,9 @@ export async function publishRepository(gitAPI: GitAPI, repository?: Repository) progress.report({ message: localize('publishing_uploading', "Uploading files"), increment: 25 }); const branch = await repository.getBranch('HEAD'); - await repository.addRemote('origin', createdGithubRepository.clone_url); + const protocol = vscode.workspace.getConfiguration('github').get<'https' | 'ssh'>('gitProtocol'); + const remoteUrl = protocol === 'https' ? createdGithubRepository.clone_url : createdGithubRepository.ssh_url; + await repository.addRemote('origin', remoteUrl); await repository.push('origin', branch.name, true); return createdGithubRepository; diff --git a/extensions/github/src/pushErrorHandler.ts b/extensions/github/src/pushErrorHandler.ts index 5948c526ea1..9e8af7dbcc9 100644 --- a/extensions/github/src/pushErrorHandler.ts +++ b/extensions/github/src/pushErrorHandler.ts @@ -73,7 +73,9 @@ async function handlePushError(repository: Repository, remote: Remote, refspec: await repository.renameRemote(remote.name, 'upstream'); // Issue: what if there's already another `origin` repo? - await repository.addRemote('origin', ghRepository.clone_url); + const protocol = workspace.getConfiguration('github').get<'https' | 'ssh'>('gitProtocol'); + const remoteUrl = protocol === 'https' ? ghRepository.clone_url : ghRepository.ssh_url; + await repository.addRemote('origin', remoteUrl); try { await repository.fetch('origin', remoteName); diff --git a/extensions/github/src/remoteSourceProvider.ts b/extensions/github/src/remoteSourceProvider.ts index 7f3f9ae41eb..6e0b5fadbf7 100644 --- a/extensions/github/src/remoteSourceProvider.ts +++ b/extensions/github/src/remoteSourceProvider.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ +import { workspace } from 'vscode'; import { RemoteSourceProvider, RemoteSource } from './typings/git-base'; import { getOctokit } from './auth'; import { Octokit } from '@octokit/rest'; @@ -14,10 +15,11 @@ function parse(url: string): { owner: string; repo: string } | undefined { } function asRemoteSource(raw: any): RemoteSource { + const protocol = workspace.getConfiguration('github').get<'https' | 'ssh'>('gitProtocol'); return { name: `$(github) ${raw.full_name}`, description: raw.description || undefined, - url: raw.clone_url + url: protocol === 'https' ? raw.clone_url : raw.ssh_url }; } diff --git a/extensions/html-language-features/server/src/modes/embeddedSupport.ts b/extensions/html-language-features/server/src/modes/embeddedSupport.ts index f0ad3d9676a..941510d3deb 100644 --- a/extensions/html-language-features/server/src/modes/embeddedSupport.ts +++ b/extensions/html-language-features/server/src/modes/embeddedSupport.ts @@ -181,7 +181,6 @@ function getPrefix(c: EmbeddedRegion) { if (c.attributeValue) { switch (c.languageId) { case 'css': return CSS_STYLE_RULE + '{'; - case 'javascript': return '()=>{'; } } return ''; @@ -190,7 +189,7 @@ function getSuffix(c: EmbeddedRegion) { if (c.attributeValue) { switch (c.languageId) { case 'css': return '}'; - case 'javascript': return '};'; + case 'javascript': return ';'; } } return ''; diff --git a/extensions/html-language-features/server/src/modes/javascriptMode.ts b/extensions/html-language-features/server/src/modes/javascriptMode.ts index 36a41173b30..b4780e87715 100644 --- a/extensions/html-language-features/server/src/modes/javascriptMode.ts +++ b/extensions/html-language-features/server/src/modes/javascriptMode.ts @@ -87,7 +87,7 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache { + return syntaxDiagnostics.concat(semanticDiagnostics).filter(d => d.code !== 1108).map((diag: ts.Diagnostic): Diagnostic => { return { range: convertRange(jsDocument, diag), severity: DiagnosticSeverity.Error, diff --git a/extensions/html-language-features/server/src/test/embedded.test.ts b/extensions/html-language-features/server/src/test/embedded.test.ts index 94974abb19b..1e63a72ec89 100644 --- a/extensions/html-language-features/server/src/test/embedded.test.ts +++ b/extensions/html-language-features/server/src/test/embedded.test.ts @@ -120,7 +120,9 @@ suite('HTML Embedded Support', () => { assertEmbeddedLanguageContent('', 'javascript', ' var i = 0; '); assertEmbeddedLanguageContent('', 'javascript', ' var i = 0; '); - assertEmbeddedLanguageContent('
', 'javascript', ' ()=>{foo()}; ()=>{bar()}; '); + assertEmbeddedLanguageContent('
', 'javascript', ' foo(); bar(); '); + assertEmbeddedLanguageContent('
', 'javascript', ' return; '); + }); }); diff --git a/extensions/javascript/snippets/javascript.code-snippets b/extensions/javascript/snippets/javascript.code-snippets index ac5cd5549f1..47a6f40d202 100644 --- a/extensions/javascript/snippets/javascript.code-snippets +++ b/extensions/javascript/snippets/javascript.code-snippets @@ -149,12 +149,12 @@ ], "description": "Set Interval Function" }, - "Import external module.": { - "prefix": "import statement", + "Import Statement": { + "prefix": "import", "body": [ "import { $0 } from \"${1:module}\";" ], - "description": "Import external module." + "description": "Import external module" }, "Region Start": { "prefix": "#region", diff --git a/extensions/json-language-features/client/src/jsonClient.ts b/extensions/json-language-features/client/src/jsonClient.ts index 7fd2b32ca2f..1044fa22599 100644 --- a/extensions/json-language-features/client/src/jsonClient.ts +++ b/extensions/json-language-features/client/src/jsonClient.ts @@ -68,7 +68,7 @@ interface Settings { }; } -interface JSONSchemaSettings { +export interface JSONSchemaSettings { fileMatch?: string[]; url?: string; schema?: any; diff --git a/extensions/json-language-features/client/src/languageStatus.ts b/extensions/json-language-features/client/src/languageStatus.ts index 63541be773d..152759c39a1 100644 --- a/extensions/json-language-features/client/src/languageStatus.ts +++ b/extensions/json-language-features/client/src/languageStatus.ts @@ -3,8 +3,8 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { window, languages, Uri, LanguageStatusSeverity, Disposable, commands, QuickPickItem, extensions, workspace } from 'vscode'; -import { JSONLanguageStatus } from './jsonClient'; +import { window, languages, Uri, LanguageStatusSeverity, Disposable, commands, QuickPickItem, extensions, workspace, Extension, WorkspaceFolder, QuickPickItemKind, ThemeIcon } from 'vscode'; +import { JSONLanguageStatus, JSONSchemaSettings } from './jsonClient'; import * as nls from 'vscode-nls'; @@ -16,75 +16,160 @@ type ShowSchemasInput = { }; interface ShowSchemasItem extends QuickPickItem { - uri: Uri; + uri?: Uri; + buttonCommands?: (() => void)[]; } -function equalsIgnoreCase(a: string, b: string): boolean { - return a.length === b.length && a.toLowerCase().localeCompare(b.toLowerCase()) === 0; -} +function getExtensionSchemaAssociations() { + const associations: { fullUri: string; extension: Extension; label: string }[] = []; -function isEqualAuthority(a1: string | undefined, a2: string | undefined) { - return a1 === a2 || (a1 !== undefined && a2 !== undefined && equalsIgnoreCase(a1, a2)); -} - -function findExtension(uri: Uri) { - for (const ext of extensions.all) { - const parent = ext.extensionUri; - if (uri.scheme === parent.scheme && isEqualAuthority(uri.authority, parent.authority) && uri.path.startsWith(parent.path + '/')) { - return ext; - } - } - return undefined; -} - -function findWorkspaceFolder(uri: Uri) { - if (workspace.workspaceFolders) { - for (const wf of workspace.workspaceFolders) { - const parent = wf.uri; - if (uri.scheme === parent.scheme && isEqualAuthority(uri.authority, parent.authority) && uri.path.startsWith(parent.path + '/')) { - return wf; + for (const extension of extensions.all) { + const jsonValidations = extension.packageJSON?.contributes?.jsonValidation; + if (Array.isArray(jsonValidations)) { + for (const jsonValidation of jsonValidations) { + let uri = jsonValidation.url; + if (typeof uri === 'string') { + if (uri[0] === '.' && uri[1] === '/') { + uri = Uri.joinPath(extension.extensionUri, uri).toString(false); + } + associations.push({ fullUri: uri, extension, label: jsonValidation.url }); + } } } } - return undefined; + return { + findExtension(uri: string): ShowSchemasItem | undefined { + for (const association of associations) { + if (association.fullUri === uri) { + return { + label: association.label, + detail: localize('schemaFromextension', 'Configured by extension: {0}', association.extension.id), + uri: Uri.parse(association.fullUri), + buttons: [{ iconPath: new ThemeIcon('extensions'), tooltip: localize('openExtension', 'Open Extension') }], + buttonCommands: [() => commands.executeCommand('workbench.extensions.action.showExtensionsWithIds', [[association.extension.id]])] + }; + } + } + return undefined; + } + }; } -function renderShowSchemasItem(schema: string): ShowSchemasItem { - const uri = Uri.parse(schema); - const extension = findExtension(uri); - if (extension) { - return { label: extension.id, description: uri.path.substring(extension.extensionUri.path.length + 1), uri }; +// + +function getSettingsSchemaAssociations(uri: string) { + const resourceUri = Uri.parse(uri); + const workspaceFolder = workspace.getWorkspaceFolder(resourceUri); + + const settings = workspace.getConfiguration('json', resourceUri).inspect('schemas'); + + const associations: { fullUri: string; workspaceFolder: WorkspaceFolder | undefined; label: string }[] = []; + + const folderSettingSchemas = settings?.workspaceFolderValue; + if (workspaceFolder && Array.isArray(folderSettingSchemas)) { + for (const setting of folderSettingSchemas) { + const uri = setting.url; + if (typeof uri === 'string') { + let fullUri = uri; + if (uri[0] === '.' && uri[1] === '/') { + fullUri = Uri.joinPath(workspaceFolder.uri, uri).toString(false); + } + associations.push({ fullUri, workspaceFolder, label: uri }); + } + } } - const wf = findWorkspaceFolder(uri); - if (wf) { - return { label: uri.path.substring(wf.uri.path.length + 1), description: 'Workspace', uri }; + const userSettingSchemas = settings?.globalValue; + if (Array.isArray(userSettingSchemas)) { + for (const setting of userSettingSchemas) { + const uri = setting.url; + if (typeof uri === 'string') { + let fullUri = uri; + if (workspaceFolder && uri[0] === '.' && uri[1] === '/') { + fullUri = Uri.joinPath(workspaceFolder.uri, uri).toString(false); + } + associations.push({ fullUri, workspaceFolder: undefined, label: uri }); + } + } } - if (uri.scheme === 'file') { - return { label: uri.fsPath, uri }; - } else if (uri.scheme === 'vscode') { - return { label: schema, description: 'internally generated', uri }; - } - return { label: schema, uri }; + return { + findSetting(uri: string): ShowSchemasItem | undefined { + for (const association of associations) { + if (association.fullUri === uri) { + return { + label: association.label, + detail: association.workspaceFolder ? localize('schemaFromFolderSettings', 'Configured in workspace settings') : localize('schemaFromUserSettings', 'Configured in user settings'), + uri: Uri.parse(association.fullUri), + buttons: [{ iconPath: new ThemeIcon('gear'), tooltip: localize('openSettings', 'Open Settings') }], + buttonCommands: [() => commands.executeCommand(association.workspaceFolder ? 'workbench.action.openWorkspaceSettingsFile' : 'workbench.action.openSettingsJson', ['json.schemas'])] + }; + } + } + return undefined; + } + }; } +function showSchemaList(input: ShowSchemasInput) { + + const extensionSchemaAssocations = getExtensionSchemaAssociations(); + const settingsSchemaAssocations = getSettingsSchemaAssociations(input.uri); + + const extensionEntries = []; + const settingsEntries = []; + const otherEntries = []; + + for (const schemaUri of input.schemas) { + const extensionEntry = extensionSchemaAssocations.findExtension(schemaUri); + if (extensionEntry) { + extensionEntries.push(extensionEntry); + continue; + } + const settingsEntry = settingsSchemaAssocations.findSetting(schemaUri); + if (settingsEntry) { + settingsEntries.push(settingsEntry); + continue; + } + otherEntries.push({ label: schemaUri, uri: Uri.parse(schemaUri) }); + } + + const items: ShowSchemasItem[] = [...extensionEntries, ...settingsEntries, ...otherEntries]; + if (items.length === 0) { + items.push({ + label: localize('schema.noSchema', 'No schema configured for this file'), + buttons: [{ iconPath: new ThemeIcon('gear'), tooltip: localize('openSettings', 'Open Settings') }], + buttonCommands: [() => commands.executeCommand('workbench.action.openSettingsJson', ['json.schemas'])] + }); + } + + items.push({ label: '', kind: QuickPickItemKind.Separator }); + items.push({ label: localize('schema.showdocs', 'Learn more about JSON schema configuration...'), uri: Uri.parse('https://code.visualstudio.com/docs/languages/json#_json-schemas-and-settings') }); + + const quickPick = window.createQuickPick(); + quickPick.title = localize('schemaPicker.title', 'JSON Schemas used for {0}', input.uri); + // quickPick.placeholder = items.length ? localize('schemaPicker.placeholder', 'Select the schema to open') : undefined; + quickPick.items = items; + quickPick.show(); + quickPick.onDidAccept(() => { + const uri = quickPick.selectedItems[0].uri; + if (uri) { + commands.executeCommand('vscode.open', uri); + quickPick.dispose(); + } + }); + quickPick.onDidTriggerItemButton(b => { + const index = b.item.buttons?.indexOf(b.button); + if (index !== undefined && index >= 0 && b.item.buttonCommands && b.item.buttonCommands[index]) { + b.item.buttonCommands[index](); + } + }); +} export function createLanguageStatusItem(documentSelector: string[], statusRequest: (uri: string) => Promise): Disposable { const statusItem = languages.createLanguageStatusItem('json.projectStatus', documentSelector); statusItem.name = localize('statusItem.name', "JSON Validation Status"); statusItem.severity = LanguageStatusSeverity.Information; - const showSchemasCommand = commands.registerCommand('_json.showAssociatedSchemaList', (arg: ShowSchemasInput) => { - const items: ShowSchemasItem[] = arg.schemas.sort().map(renderShowSchemasItem); - const quickPick = window.createQuickPick(); - quickPick.title = localize('schemaPicker.title', 'JSON Schemas used for {0}', arg.uri.toString()); - quickPick.placeholder = localize('schemaPicker.placeholder', 'Select the schema to open'); - quickPick.items = items; - quickPick.show(); - quickPick.onDidAccept(() => { - commands.executeCommand('vscode.open', quickPick.selectedItems[0].uri); - quickPick.dispose(); - }); - }); + const showSchemasCommand = commands.registerCommand('_json.showAssociatedSchemaList', showSchemaList); const activeEditorListener = window.onDidChangeActiveTextEditor(() => { updateLanguageStatus(); @@ -101,29 +186,24 @@ export function createLanguageStatusItem(documentSelector: string[], statusReque const schemas = (await statusRequest(document.uri.toString())).schemas; statusItem.detail = undefined; if (schemas.length === 0) { - statusItem.text = localize('status.noSchema', 'Validated without JSON schema'); + statusItem.text = localize('status.noSchema.short', "No Schema Validation"); + statusItem.detail = localize('status.noSchema', 'No JSON schema configured.'); } else if (schemas.length === 1) { - const item = renderShowSchemasItem(schemas[0]); - statusItem.text = localize('status.singleSchema', 'Validated with JSON schema'); - statusItem.command = { - command: 'vscode.open', - title: localize('status.openSchemaLink', 'Open Schema'), - tooltip: item.description ? `${item.label} - ${item.description}` : item.label, - arguments: [item.uri] - }; + statusItem.text = localize('status.withSchema.short', "Schema Validated"); + statusItem.detail = localize('status.singleSchema', 'JSON schema configured.'); } else { - statusItem.text = localize('status.multipleSchema', 'Validated with multiple JSON schemas'); - statusItem.command = { - command: '_json.showAssociatedSchemaList', - title: localize('status.openSchemasLink', 'Show Schemas'), - arguments: [{ schemas, uri: document.uri.toString() } as ShowSchemasInput] - }; + statusItem.text = localize('status.withSchemas.short', "Schema Validated"); + statusItem.detail = localize('status.multipleSchema', 'Multiple JSON schemas configured.'); } + statusItem.command = { + command: '_json.showAssociatedSchemaList', + title: localize('status.openSchemasLink', 'Show Schemas'), + arguments: [{ schemas, uri: document.uri.toString() } as ShowSchemasInput] + }; } catch (e) { statusItem.text = localize('status.error', 'Unable to compute used schemas'); statusItem.detail = undefined; statusItem.command = undefined; - console.log(e); } } else { statusItem.text = localize('status.notJSON', 'Not a JSON editor'); diff --git a/extensions/markdown-language-features/notebook/index.ts b/extensions/markdown-language-features/notebook/index.ts index dc4798fc896..7b4652a9795 100644 --- a/extensions/markdown-language-features/notebook/index.ts +++ b/extensions/markdown-language-features/notebook/index.ts @@ -204,7 +204,8 @@ export const activate: ActivationFunction = (ctx) => { previewNode.classList.add('emptyMarkdownCell'); } else { previewNode.classList.remove('emptyMarkdownCell'); - const markdownText = outputInfo.mime.startsWith('text/x-') ? `\`\`\`${outputInfo.mime.substr(7)}\n${text}\n\`\`\`` : text; + const markdownText = outputInfo.mime.startsWith('text/x-') ? `\`\`\`${outputInfo.mime.substr(7)}\n${text}\n\`\`\`` + : (outputInfo.mime.startsWith('application/') ? `\`\`\`${outputInfo.mime.substr(12)}\n${text}\n\`\`\`` : text); const unsanitizedRenderedMarkdown = markdownIt.render(markdownText); previewNode.innerHTML = (ctx.workspace.isTrusted ? unsanitizedRenderedMarkdown diff --git a/extensions/markdown-language-features/package.json b/extensions/markdown-language-features/package.json index 8fa1a37eaae..33d3be13b78 100644 --- a/extensions/markdown-language-features/package.json +++ b/extensions/markdown-language-features/package.json @@ -29,6 +29,7 @@ "onCommand:markdown.showPreviewSecuritySelector", "onCommand:markdown.api.render", "onCommand:markdown.api.reloadPlugins", + "onCommand:markdown.findAllFileReferences", "onWebviewPanel:markdown.preview", "onCustomEditor:vscode.markdown.preview.editor" ], @@ -122,7 +123,8 @@ "text/x-typescript", "text/x-vb", "text/x-xml", - "text/x-yaml" + "text/x-yaml", + "application/json" ] } ], @@ -168,6 +170,11 @@ "command": "markdown.preview.toggleLock", "title": "%markdown.preview.toggleLock.title%", "category": "Markdown" + }, + { + "command": "markdown.findAllFileReferences", + "title": "%markdown.findAllFileReferences%", + "category": "Markdown" } ], "menus": { @@ -204,6 +211,11 @@ "command": "markdown.showPreview", "when": "resourceLangId == markdown && !hasCustomMarkdownPreview", "group": "navigation" + }, + { + "command": "markdown.findAllFileReferences", + "when": "resourceLangId == markdown", + "group": "4_search" } ], "editor/title/context": [ @@ -211,6 +223,10 @@ "command": "markdown.showPreview", "when": "resourceLangId == markdown && !hasCustomMarkdownPreview", "group": "1_open" + }, + { + "command": "markdown.findAllFileReferences", + "when": "resourceLangId == markdown" } ], "commandPalette": [ @@ -253,6 +269,10 @@ { "command": "markdown.preview.refresh", "when": "markdownPreviewFocus" + }, + { + "command": "markdown.findAllFileReferences", + "when": "editorLangId == markdown" } ] }, diff --git a/extensions/markdown-language-features/package.nls.json b/extensions/markdown-language-features/package.nls.json index ba4d239f0e1..78927ee2a78 100644 --- a/extensions/markdown-language-features/package.nls.json +++ b/extensions/markdown-language-features/package.nls.json @@ -20,6 +20,7 @@ "markdown.trace.desc": "Enable debug logging for the Markdown extension.", "markdown.preview.refresh.title": "Refresh Preview", "markdown.preview.toggleLock.title": "Toggle Preview Locking", + "markdown.findAllFileReferences": "Find File References", "configuration.markdown.preview.openMarkdownLinks.description": "Controls how links to other Markdown files in the Markdown preview should be opened.", "configuration.markdown.preview.openMarkdownLinks.inEditor": "Try to open links in the editor.", "configuration.markdown.preview.openMarkdownLinks.inPreview": "Try to open links in the Markdown preview.", diff --git a/extensions/markdown-language-features/src/commandManager.ts b/extensions/markdown-language-features/src/commandManager.ts index 359d5b9ca96..86609dbe2b2 100644 --- a/extensions/markdown-language-features/src/commandManager.ts +++ b/extensions/markdown-language-features/src/commandManager.ts @@ -21,9 +21,11 @@ export class CommandManager { this.commands.clear(); } - public register(command: T): T { + public register(command: T): vscode.Disposable { this.registerCommand(command.id, command.execute, command); - return command; + return new vscode.Disposable(() => { + this.commands.delete(command.id); + }); } private registerCommand(id: string, impl: (...args: any[]) => void, thisArg?: any) { @@ -33,4 +35,4 @@ export class CommandManager { this.commands.set(id, vscode.commands.registerCommand(id, impl, thisArg)); } -} \ No newline at end of file +} diff --git a/extensions/markdown-language-features/src/extension.ts b/extensions/markdown-language-features/src/extension.ts index eac0433c22b..e0c0477cce4 100644 --- a/extensions/markdown-language-features/src/extension.ts +++ b/extensions/markdown-language-features/src/extension.ts @@ -9,6 +9,7 @@ import * as commands from './commands/index'; import { MdLinkProvider } from './languageFeatures/documentLinkProvider'; import { MdDocumentSymbolProvider } from './languageFeatures/documentSymbolProvider'; import { registerDropIntoEditor } from './languageFeatures/dropIntoEditor'; +import { registerFindFileReferences } from './languageFeatures/fileReferences'; import { MdFoldingProvider } from './languageFeatures/foldingProvider'; import { MdPathCompletionProvider } from './languageFeatures/pathCompletions'; import { MdReferencesProvider } from './languageFeatures/references'; @@ -36,24 +37,24 @@ export function activate(context: vscode.ExtensionContext) { const cspArbiter = new ExtensionContentSecurityPolicyArbiter(context.globalState, context.workspaceState); const engine = new MarkdownEngine(contributions, githubSlugifier); const logger = new Logger(); + const commandManager = new CommandManager(); const contentProvider = new MarkdownContentProvider(engine, context, cspArbiter, contributions, logger); const symbolProvider = new MdDocumentSymbolProvider(engine); const previewManager = new MarkdownPreviewManager(contentProvider, logger, contributions, engine); context.subscriptions.push(previewManager); - context.subscriptions.push(registerMarkdownLanguageFeatures(symbolProvider, engine)); - context.subscriptions.push(registerMarkdownCommands(previewManager, telemetryReporter, cspArbiter, engine)); + context.subscriptions.push(registerMarkdownLanguageFeatures(commandManager, symbolProvider, engine)); + context.subscriptions.push(registerMarkdownCommands(commandManager, previewManager, telemetryReporter, cspArbiter, engine)); context.subscriptions.push(vscode.workspace.onDidChangeConfiguration(() => { logger.updateConfiguration(); previewManager.updateConfiguration(); })); - - context.subscriptions.push(registerDropIntoEditor()); } function registerMarkdownLanguageFeatures( + commandManager: CommandManager, symbolProvider: MdDocumentSymbolProvider, engine: MarkdownEngine ): vscode.Disposable { @@ -72,10 +73,13 @@ function registerMarkdownLanguageFeatures( vscode.languages.registerReferenceProvider(selector, referencesProvider), vscode.languages.registerRenameProvider(selector, new MdRenameProvider(referencesProvider, githubSlugifier)), MdPathCompletionProvider.register(selector, engine, linkProvider), + registerDropIntoEditor(selector), + registerFindFileReferences(commandManager, referencesProvider), ); } function registerMarkdownCommands( + commandManager: CommandManager, previewManager: MarkdownPreviewManager, telemetryReporter: TelemetryReporter, cspArbiter: ContentSecurityPolicyArbiter, @@ -83,7 +87,6 @@ function registerMarkdownCommands( ): vscode.Disposable { const previewSecuritySelector = new PreviewSecuritySelector(cspArbiter, previewManager); - const commandManager = new CommandManager(); commandManager.register(new commands.ShowPreviewCommand(previewManager, telemetryReporter)); commandManager.register(new commands.ShowPreviewToSideCommand(previewManager, telemetryReporter)); commandManager.register(new commands.ShowLockedPreviewToSideCommand(previewManager, telemetryReporter)); diff --git a/extensions/markdown-language-features/src/languageFeatures/documentLinkProvider.ts b/extensions/markdown-language-features/src/languageFeatures/documentLinkProvider.ts index 4d474658591..2f5a63cafaf 100644 --- a/extensions/markdown-language-features/src/languageFeatures/documentLinkProvider.ts +++ b/extensions/markdown-language-features/src/languageFeatures/documentLinkProvider.ts @@ -87,26 +87,25 @@ function getWorkspaceFolder(document: SkinnyTextDocument) { || vscode.workspace.workspaceFolders?.[0]?.uri; } +interface MdLinkSource { + readonly text: string; + readonly resource: vscode.Uri; + readonly hrefRange: vscode.Range; +} + export interface MdInlineLink { readonly kind: 'link'; - + readonly source: MdLinkSource; readonly href: LinkHref; - - readonly sourceText: string; - readonly sourceResource: vscode.Uri; - readonly sourceHrefRange: vscode.Range; } export interface MdLinkDefinition { readonly kind: 'definition'; - - readonly sourceText: string; - readonly sourceResource: vscode.Uri; - readonly sourceHrefRange: vscode.Range; - - readonly refRange: vscode.Range; - - readonly ref: string; + readonly source: MdLinkSource; + readonly ref: { + readonly range: vscode.Range; + readonly text: string; + }; readonly href: ExternalHref | InternalHref; } @@ -129,9 +128,11 @@ function extractDocumentLink( return { kind: 'link', href: linkTarget, - sourceText: link, - sourceResource: document.uri, - sourceHrefRange: new vscode.Range(linkStart, linkEnd) + source: { + text: link, + resource: document.uri, + hrefRange: new vscode.Range(linkStart, linkEnd) + } }; } catch { return undefined; @@ -192,8 +193,8 @@ async function findCode(document: SkinnyTextDocument, engine: MarkdownEngine): P } function isLinkInsideCode(code: CodeInDocument, link: MdLink) { - return code.multiline.some(interval => link.sourceHrefRange.start.line >= interval[0] && link.sourceHrefRange.start.line < interval[1]) || - code.inline.some(position => position.intersection(link.sourceHrefRange)); + return code.multiline.some(interval => link.source.hrefRange.start.line >= interval[0] && link.source.hrefRange.start.line < interval[1]) || + code.inline.some(position => position.intersection(link.source.hrefRange)); } export class MdLinkProvider implements vscode.DocumentLinkProvider { @@ -219,11 +220,11 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider { private toValidDocumentLink(link: MdLink, definitionSet: LinkDefinitionSet): vscode.DocumentLink | undefined { switch (link.href.kind) { case 'external': { - return new vscode.DocumentLink(link.sourceHrefRange, link.href.uri); + return new vscode.DocumentLink(link.source.hrefRange, link.href.uri); } case 'internal': { - const uri = OpenDocumentLinkCommand.createCommandUri(link.sourceResource, link.href.path, link.href.fragment); - const documentLink = new vscode.DocumentLink(link.sourceHrefRange, uri); + const uri = OpenDocumentLinkCommand.createCommandUri(link.source.resource, link.href.path, link.href.fragment); + const documentLink = new vscode.DocumentLink(link.source.hrefRange, uri); documentLink.tooltip = localize('documentLink.tooltip', 'Follow link'); return documentLink; } @@ -231,8 +232,8 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider { const def = definitionSet.lookup(link.href.ref); if (def) { return new vscode.DocumentLink( - link.sourceHrefRange, - vscode.Uri.parse(`command:_markdown.moveCursorToPosition?${encodeURIComponent(JSON.stringify([def.sourceHrefRange.start.line, def.sourceHrefRange.start.character]))}`)); + link.source.hrefRange, + vscode.Uri.parse(`command:_markdown.moveCursorToPosition?${encodeURIComponent(JSON.stringify([def.source.hrefRange.start.line, def.source.hrefRange.start.character]))}`)); } else { return undefined; } @@ -288,9 +289,11 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider { yield { kind: 'link', - sourceText: reference, - sourceHrefRange: new vscode.Range(linkStart, linkEnd), - sourceResource: document.uri, + source: { + text: reference, + hrefRange: new vscode.Range(linkStart, linkEnd), + resource: document.uri, + }, href: { kind: 'reference', ref: reference, @@ -318,11 +321,12 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider { if (target) { yield { kind: 'definition', - sourceText: link, - sourceResource: document.uri, - sourceHrefRange: new vscode.Range(linkStart, linkEnd), - refRange, - ref: reference, + source: { + text: link, + resource: document.uri, + hrefRange: new vscode.Range(linkStart, linkEnd), + }, + ref: { text: reference, range: refRange }, href: target, }; } @@ -333,11 +337,12 @@ export class MdLinkProvider implements vscode.DocumentLinkProvider { if (target) { yield { kind: 'definition', - sourceText: link, - sourceResource: document.uri, - sourceHrefRange: new vscode.Range(linkStart, linkEnd), - refRange, - ref: reference, + source: { + text: link, + resource: document.uri, + hrefRange: new vscode.Range(linkStart, linkEnd), + }, + ref: { text: reference, range: refRange }, href: target, }; } @@ -352,7 +357,7 @@ export class LinkDefinitionSet { constructor(links: Iterable) { for (const link of links) { if (link.kind === 'definition') { - this._map.set(link.ref, link); + this._map.set(link.ref.text, link); } } } diff --git a/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts b/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts index dba36394bc0..14a2ace9db6 100644 --- a/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts +++ b/extensions/markdown-language-features/src/languageFeatures/dropIntoEditor.ts @@ -7,12 +7,12 @@ import * as path from 'path'; import * as vscode from 'vscode'; import * as URI from 'vscode-uri'; -export function registerDropIntoEditor() { - return vscode.workspace.onWillDropOnTextEditor(e => { - e.waitUntil((async () => { - const urlList = await e.dataTransfer.get('text/uri-list')?.asString(); +export function registerDropIntoEditor(selector: vscode.DocumentSelector) { + return vscode.languages.registerDocumentOnDropProvider(selector, new class implements vscode.DocumentOnDropProvider { + async provideDocumentOnDropEdits(document: vscode.TextDocument, position: vscode.Position, dataTransfer: vscode.DataTransfer, _token: vscode.CancellationToken): Promise { + const urlList = await dataTransfer.get('text/uri-list')?.asString(); if (!urlList) { - return; + return undefined; } const uris: vscode.Uri[] = []; @@ -30,7 +30,7 @@ export function registerDropIntoEditor() { const snippet = new vscode.SnippetString(); uris.forEach((uri, i) => { - const rel = path.relative(URI.Utils.dirname(e.editor.document.uri).fsPath, uri.fsPath); + const rel = path.relative(URI.Utils.dirname(document.uri).fsPath, uri.fsPath); snippet.appendText('['); snippet.appendTabstop(); @@ -41,7 +41,7 @@ export function registerDropIntoEditor() { } }); - return e.editor.insertSnippet(snippet, e.position); - })()); + return new vscode.SnippetTextEdit(new vscode.Range(position, position), snippet); + } }); } diff --git a/extensions/markdown-language-features/src/languageFeatures/fileReferences.ts b/extensions/markdown-language-features/src/languageFeatures/fileReferences.ts new file mode 100644 index 00000000000..b9f1321cf06 --- /dev/null +++ b/extensions/markdown-language-features/src/languageFeatures/fileReferences.ts @@ -0,0 +1,54 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as vscode from 'vscode'; +import * as nls from 'vscode-nls'; +import { Command, CommandManager } from '../commandManager'; +import { MdReferencesProvider } from './references'; + +const localize = nls.loadMessageBundle(); + + +export class FindFileReferencesCommand implements Command { + + public readonly id = 'markdown.findAllFileReferences'; + + constructor( + private readonly referencesProvider: MdReferencesProvider, + ) { } + + public async execute(resource?: vscode.Uri) { + if (!resource) { + resource = vscode.window.activeTextEditor?.document.uri; + } + + if (!resource) { + vscode.window.showErrorMessage(localize('error.noResource', "Find file references failed. No resource provided.")); + return; + } + + await vscode.window.withProgress({ + location: vscode.ProgressLocation.Window, + title: localize('progress.title', "Finding file references") + }, async (_progress, token) => { + const references = await this.referencesProvider.getAllReferencesToFile(resource!, token); + const locations = references.map(ref => ref.location); + + const config = vscode.workspace.getConfiguration('references'); + const existingSetting = config.inspect('preferredLocation'); + + await config.update('preferredLocation', 'view'); + try { + await vscode.commands.executeCommand('editor.action.showReferences', resource, new vscode.Position(0, 0), locations); + } finally { + await config.update('preferredLocation', existingSetting?.workspaceFolderValue ?? existingSetting?.workspaceValue); + } + }); + } +} + +export function registerFindFileReferences(commandManager: CommandManager, referencesProvider: MdReferencesProvider): vscode.Disposable { + return commandManager.register(new FindFileReferencesCommand(referencesProvider)); +} diff --git a/extensions/markdown-language-features/src/languageFeatures/pathCompletions.ts b/extensions/markdown-language-features/src/languageFeatures/pathCompletions.ts index aea8efa3386..612698acbc6 100644 --- a/extensions/markdown-language-features/src/languageFeatures/pathCompletions.ts +++ b/extensions/markdown-language-features/src/languageFeatures/pathCompletions.ts @@ -240,7 +240,7 @@ export class MdPathCompletionProvider implements vscode.CompletionItemProvider { for (const def of definitions) { yield { kind: vscode.CompletionItemKind.Reference, - label: def.ref, + label: def.ref.text, range: { inserting: insertionRange, replacing: replacementRange, diff --git a/extensions/markdown-language-features/src/languageFeatures/references.ts b/extensions/markdown-language-features/src/languageFeatures/references.ts index c2d56f8e3fb..43f129922ec 100644 --- a/extensions/markdown-language-features/src/languageFeatures/references.ts +++ b/extensions/markdown-language-features/src/languageFeatures/references.ts @@ -16,7 +16,7 @@ import { MdWorkspaceCache } from './workspaceCache'; /** * A link in a markdown file. */ -interface MdLinkReference { +export interface MdLinkReference { readonly kind: 'link'; readonly isTriggerLocation: boolean; readonly isDefinition: boolean; @@ -30,7 +30,7 @@ interface MdLinkReference { /** * A header in a markdown file. */ -interface MdHeaderReference { +export interface MdHeaderReference { readonly kind: 'header'; readonly isTriggerLocation: boolean; @@ -43,6 +43,13 @@ interface MdHeaderReference { */ readonly location: vscode.Location; + /** + * The text of the header. + * + * In `# a b c #` this would be `a b c` + */ + readonly headerText: string; + /** * The range of the header text itself. * @@ -55,12 +62,12 @@ export type MdReference = MdLinkReference | MdHeaderReference; function getFragmentLocation(link: MdLink): vscode.Location | undefined { - const index = link.sourceText.indexOf('#'); + const index = link.source.text.indexOf('#'); if (index < 0) { return undefined; } - return new vscode.Location(link.sourceResource, link.sourceHrefRange.with({ - start: link.sourceHrefRange.start.translate({ characterDelta: index + 1 }), + return new vscode.Location(link.source.resource, link.source.hrefRange.with({ + start: link.source.hrefRange.start.translate({ characterDelta: index + 1 }), })); } @@ -80,14 +87,14 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference } async provideReferences(document: SkinnyTextDocument, position: vscode.Position, context: vscode.ReferenceContext, token: vscode.CancellationToken): Promise { - const allRefs = await this.getAllReferences(document, position, token); + const allRefs = await this.getAllReferencesAtPosition(document, position, token); return allRefs .filter(ref => context.includeDeclaration || !ref.isDefinition) .map(ref => ref.location); } - public async getAllReferences(document: SkinnyTextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise { + public async getAllReferencesAtPosition(document: SkinnyTextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise { const toc = await TableOfContents.create(this.engine, document); if (token.isCancellationRequested) { return []; @@ -97,7 +104,7 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference if (header) { return this.getReferencesToHeader(document, header); } else { - return this.getReferencesToLinkAtPosition(document, position); + return this.getReferencesToLinkAtPosition(document, position, token); } } @@ -111,12 +118,13 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference isTriggerLocation: true, isDefinition: true, location: header.headerLocation, + headerText: header.text, headerTextLocation: header.headerTextLocation }); for (const link of links) { if (link.href.kind === 'internal' - && this.looksLikeLinkToDoc(link.href, document) + && this.looksLikeLinkToDoc(link.href, document.uri) && this.slugifier.fromHeading(link.href.fragment).value === header.slug.value ) { references.push({ @@ -124,7 +132,7 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference isTriggerLocation: false, isDefinition: false, link, - location: new vscode.Location(link.sourceResource, link.sourceHrefRange), + location: new vscode.Location(link.source.resource, link.source.hrefRange), fragmentLocation: getFragmentLocation(link), }); } @@ -133,20 +141,20 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference return references; } - private async getReferencesToLinkAtPosition(document: SkinnyTextDocument, position: vscode.Position): Promise { + private async getReferencesToLinkAtPosition(document: SkinnyTextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise { const docLinks = await this.linkProvider.getAllLinks(document); for (const link of docLinks) { if (link.kind === 'definition') { // We could be in either the ref name or the definition - if (link.refRange.contains(position)) { - return Array.from(this.getReferencesToLinkReference(docLinks, link.ref, { resource: document.uri, range: link.refRange })); - } else if (link.sourceHrefRange.contains(position)) { - return this.getReferencesToLink(link); + if (link.ref.range.contains(position)) { + return Array.from(this.getReferencesToLinkReference(docLinks, link.ref.text, { resource: document.uri, range: link.ref.range })); + } else if (link.source.hrefRange.contains(position)) { + return this.getReferencesToLink(link, token); } } else { - if (link.sourceHrefRange.contains(position)) { - return this.getReferencesToLink(link); + if (link.source.hrefRange.contains(position)) { + return this.getReferencesToLink(link, token); } } } @@ -154,11 +162,14 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference return []; } - private async getReferencesToLink(sourceLink: MdLink): Promise { + private async getReferencesToLink(sourceLink: MdLink, token: vscode.CancellationToken): Promise { const allLinksInWorkspace = (await this._linkCache.getAll()).flat(); + if (token.isCancellationRequested) { + return []; + } if (sourceLink.href.kind === 'reference') { - return Array.from(this.getReferencesToLinkReference(allLinksInWorkspace, sourceLink.href.ref, { resource: sourceLink.sourceResource, range: sourceLink.sourceHrefRange })); + return Array.from(this.getReferencesToLinkReference(allLinksInWorkspace, sourceLink.href.ref, { resource: sourceLink.source.resource, range: sourceLink.source.hrefRange })); } if (sourceLink.href.kind !== 'internal') { @@ -174,7 +185,7 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference } } - if (!targetDoc) { + if (!targetDoc || token.isCancellationRequested) { return []; } @@ -189,77 +200,88 @@ export class MdReferencesProvider extends Disposable implements vscode.Reference isTriggerLocation: false, isDefinition: true, location: entry.headerLocation, + headerText: entry.text, headerTextLocation: entry.headerTextLocation }); } } - for (const link of allLinksInWorkspace) { - if (link.href.kind !== 'internal') { - continue; - } + if (sourceLink.href.fragment) { + for (const link of allLinksInWorkspace) { + if (link.href.kind !== 'internal' || !this.looksLikeLinkToDoc(link.href, targetDoc.uri)) { + continue; + } - if (!this.looksLikeLinkToDoc(link.href, targetDoc)) { - continue; - } - - const isTriggerLocation = sourceLink.sourceResource.fsPath === link.sourceResource.fsPath && sourceLink.sourceHrefRange.isEqual(link.sourceHrefRange); - - if (sourceLink.href.fragment) { if (this.slugifier.fromHeading(link.href.fragment).equals(this.slugifier.fromHeading(sourceLink.href.fragment))) { + const isTriggerLocation = sourceLink.source.resource.fsPath === link.source.resource.fsPath && sourceLink.source.hrefRange.isEqual(link.source.hrefRange); references.push({ kind: 'link', isTriggerLocation, isDefinition: false, link, - location: new vscode.Location(link.sourceResource, link.sourceHrefRange), - fragmentLocation: getFragmentLocation(link), - }); - } - } else { // Triggered on a link without a fragment so we only require matching the file and ignore fragments - - // But exclude cases where the file is referencing itself - if (link.sourceResource.fsPath !== targetDoc.uri.fsPath) { - references.push({ - kind: 'link', - isTriggerLocation, - isDefinition: false, - link, - location: new vscode.Location(link.sourceResource, link.sourceHrefRange), + location: new vscode.Location(link.source.resource, link.source.hrefRange), fragmentLocation: getFragmentLocation(link), }); } } + } else { // Triggered on a link without a fragment so we only require matching the file and ignore fragments + references.push(...this.findAllLinksToFile(targetDoc.uri, allLinksInWorkspace, sourceLink)); } return references; } - private looksLikeLinkToDoc(href: InternalHref, targetDoc: SkinnyTextDocument) { - return href.path.fsPath === targetDoc.uri.fsPath - || uri.Utils.extname(href.path) === '' && href.path.with({ path: href.path.path + '.md' }).fsPath === targetDoc.uri.fsPath; + private looksLikeLinkToDoc(href: InternalHref, targetDoc: vscode.Uri) { + return href.path.fsPath === targetDoc.fsPath + || uri.Utils.extname(href.path) === '' && href.path.with({ path: href.path.path + '.md' }).fsPath === targetDoc.fsPath; + } + + public async getAllReferencesToFile(resource: vscode.Uri, _token: vscode.CancellationToken): Promise { + const allLinksInWorkspace = (await this._linkCache.getAll()).flat(); + return Array.from(this.findAllLinksToFile(resource, allLinksInWorkspace, undefined)); + } + + private *findAllLinksToFile(resource: vscode.Uri, allLinksInWorkspace: readonly MdLink[], sourceLink: MdLink | undefined): Iterable { + for (const link of allLinksInWorkspace) { + if (link.href.kind !== 'internal' || !this.looksLikeLinkToDoc(link.href, resource)) { + continue; + } + + // Exclude cases where the file is implicitly referencing itself + if (!link.source.text.startsWith('#') || link.source.resource.fsPath !== resource.fsPath) { + const isTriggerLocation = !!sourceLink && sourceLink.source.resource.fsPath === link.source.resource.fsPath && sourceLink.source.hrefRange.isEqual(link.source.hrefRange); + yield { + kind: 'link', + isTriggerLocation, + isDefinition: false, + link, + location: new vscode.Location(link.source.resource, link.source.hrefRange), + fragmentLocation: getFragmentLocation(link), + }; + } + } } private *getReferencesToLinkReference(allLinks: Iterable, refToFind: string, from: { resource: vscode.Uri; range: vscode.Range }): Iterable { for (const link of allLinks) { let ref: string; if (link.kind === 'definition') { - ref = link.ref; + ref = link.ref.text; } else if (link.href.kind === 'reference') { ref = link.href.ref; } else { continue; } - if (ref === refToFind && link.sourceResource.fsPath === from.resource.fsPath) { - const isTriggerLocation = from.resource.fsPath === link.sourceResource.fsPath && ( - (link.href.kind === 'reference' && from.range.isEqual(link.sourceHrefRange)) || (link.kind === 'definition' && from.range.isEqual(link.refRange))); + if (ref === refToFind && link.source.resource.fsPath === from.resource.fsPath) { + const isTriggerLocation = from.resource.fsPath === link.source.resource.fsPath && ( + (link.href.kind === 'reference' && from.range.isEqual(link.source.hrefRange)) || (link.kind === 'definition' && from.range.isEqual(link.ref.range))); yield { kind: 'link', isTriggerLocation, isDefinition: link.kind === 'definition', link, - location: new vscode.Location(from.resource, link.sourceHrefRange), + location: new vscode.Location(from.resource, link.source.hrefRange), fragmentLocation: getFragmentLocation(link), }; } diff --git a/extensions/markdown-language-features/src/languageFeatures/rename.ts b/extensions/markdown-language-features/src/languageFeatures/rename.ts index 19478795e65..5e613db365f 100644 --- a/extensions/markdown-language-features/src/languageFeatures/rename.ts +++ b/extensions/markdown-language-features/src/languageFeatures/rename.ts @@ -7,7 +7,7 @@ import * as nls from 'vscode-nls'; import { Slugifier } from '../slugify'; import { Disposable } from '../util/dispose'; import { SkinnyTextDocument } from '../workspaceContents'; -import { MdReference, MdReferencesProvider } from './references'; +import { MdHeaderReference, MdReference, MdReferencesProvider } from './references'; const localize = nls.loadMessageBundle(); @@ -18,6 +18,7 @@ export class MdRenameProvider extends Disposable implements vscode.RenameProvide readonly resource: vscode.Uri; readonly version: number; readonly position: vscode.Position; + readonly triggerRef: MdReference; readonly references: MdReference[]; } | undefined; @@ -28,57 +29,60 @@ export class MdRenameProvider extends Disposable implements vscode.RenameProvide super(); } - public async prepareRename(document: SkinnyTextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise { - const references = await this.referencesProvider.getAllReferences(document, position, token); + public async prepareRename(document: SkinnyTextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise { + const allRefsInfo = await this.getAllReferences(document, position, token); if (token.isCancellationRequested) { return undefined; } - if (!references?.length) { + if (!allRefsInfo || !allRefsInfo.references.length) { throw new Error(localize('invalidRenameLocation', "Rename not supported at location")); } - const triggerRef = references.find(ref => ref.isTriggerLocation); - if (!triggerRef) { - return undefined; - } - + const triggerRef = allRefsInfo.triggerRef; switch (triggerRef.kind) { case 'header': - return triggerRef.headerTextLocation.range; + return { range: triggerRef.headerTextLocation.range, placeholder: triggerRef.headerText }; case 'link': if (triggerRef.link.kind === 'definition') { // We may have been triggered on the ref or the definition itself - if (triggerRef.link.refRange.contains(position)) { - return triggerRef.link.refRange; - } else { - return triggerRef.link.sourceHrefRange; + if (triggerRef.link.ref.range.contains(position)) { + return { range: triggerRef.link.ref.range, placeholder: triggerRef.link.ref.text }; } - } else { - return triggerRef.fragmentLocation?.range ?? triggerRef.location.range; } + + if (triggerRef.fragmentLocation) { + const declaration = this.findHeaderDeclaration(allRefsInfo.references); + if (declaration) { + return { range: triggerRef.fragmentLocation.range, placeholder: declaration.headerText }; + } + return { range: triggerRef.fragmentLocation.range, placeholder: document.getText(triggerRef.fragmentLocation.range) }; + } + + throw new Error(localize('renameNoFiles', "Renaming files is currently not supported")); } } + private findHeaderDeclaration(references: readonly MdReference[]): MdHeaderReference | undefined { + return references.find(ref => ref.isDefinition && ref.kind === 'header') as MdHeaderReference | undefined; + } + public async provideRenameEdits(document: SkinnyTextDocument, position: vscode.Position, newName: string, token: vscode.CancellationToken): Promise { - const references = await this.getAllReferences(document, position, token); - if (token.isCancellationRequested || !references?.length) { + const allRefsInfo = await this.getAllReferences(document, position, token); + if (token.isCancellationRequested || !allRefsInfo || !allRefsInfo.references.length) { return undefined; } - const triggerRef = references.find(ref => ref.isTriggerLocation); - if (!triggerRef) { - return undefined; - } + const triggerRef = allRefsInfo.triggerRef; const isRefRename = triggerRef.kind === 'link' && ( - (triggerRef.link.kind === 'definition' && triggerRef.link.refRange.contains(position)) || triggerRef.link.href.kind === 'reference' + (triggerRef.link.kind === 'definition' && triggerRef.link.ref.range.contains(position)) || triggerRef.link.href.kind === 'reference' ); const slug = this.slugifier.fromHeading(newName).value; const edit = new vscode.WorkspaceEdit(); - for (const ref of references) { + for (const ref of allRefsInfo.references) { switch (ref.kind) { case 'header': edit.replace(ref.location.uri, ref.headerTextLocation.range, newName); @@ -88,13 +92,11 @@ export class MdRenameProvider extends Disposable implements vscode.RenameProvide if (ref.link.kind === 'definition') { // We may be renaming either the reference or the definition itself if (isRefRename) { - edit.replace(ref.link.sourceResource, ref.link.refRange, newName); - } else { - edit.replace(ref.link.sourceResource, ref.fragmentLocation?.range ?? ref.link.sourceHrefRange, ref.fragmentLocation ? slug : newName); + edit.replace(ref.link.source.resource, ref.link.ref.range, newName); + continue; } - } else { - edit.replace(ref.location.uri, ref.fragmentLocation?.range ?? ref.location.range, ref.link.href.kind === 'reference' ? newName : slug); } + edit.replace(ref.link.source.resource, ref.fragmentLocation?.range ?? ref.location.range, isRefRename && !ref.fragmentLocation ? newName : slug); break; } } @@ -102,7 +104,7 @@ export class MdRenameProvider extends Disposable implements vscode.RenameProvide return edit; } - private async getAllReferences(document: SkinnyTextDocument, position: vscode.Position, token: vscode.CancellationToken) { + private async getAllReferences(document: SkinnyTextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<{ references: MdReference[]; triggerRef: MdReference } | undefined> { const version = document.version; if (this.cachedRefs @@ -110,16 +112,22 @@ export class MdRenameProvider extends Disposable implements vscode.RenameProvide && this.cachedRefs.version === document.version && this.cachedRefs.position.isEqual(position) ) { - return this.cachedRefs.references; + return this.cachedRefs; + } + + const references = await this.referencesProvider.getAllReferencesAtPosition(document, position, token); + const triggerRef = references.find(ref => ref.isTriggerLocation); + if (!triggerRef) { + return undefined; } - const references = await this.referencesProvider.getAllReferences(document, position, token); this.cachedRefs = { resource: document.uri, version, position, - references + references, + triggerRef }; - return references; + return this.cachedRefs; } } diff --git a/extensions/markdown-language-features/src/test/fileReferences.test.ts b/extensions/markdown-language-features/src/test/fileReferences.test.ts new file mode 100644 index 00000000000..b949f7e51bf --- /dev/null +++ b/extensions/markdown-language-features/src/test/fileReferences.test.ts @@ -0,0 +1,118 @@ +/*--------------------------------------------------------------------------------------------- + * 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 * as vscode from 'vscode'; +import { MdLinkProvider } from '../languageFeatures/documentLinkProvider'; +import { MdReference, MdReferencesProvider } from '../languageFeatures/references'; +import { githubSlugifier } from '../slugify'; +import { InMemoryDocument } from '../util/inMemoryDocument'; +import { MdWorkspaceContents } from '../workspaceContents'; +import { createNewMarkdownEngine } from './engine'; +import { InMemoryWorkspaceMarkdownDocuments } from './inMemoryWorkspace'; +import { joinLines, noopToken, workspacePath } from './util'; + + +function getFileReferences(resource: vscode.Uri, workspaceContents: MdWorkspaceContents) { + const engine = createNewMarkdownEngine(); + const linkProvider = new MdLinkProvider(engine); + const provider = new MdReferencesProvider(linkProvider, workspaceContents, engine, githubSlugifier); + return provider.getAllReferencesToFile(resource, noopToken); +} + +function assertReferencesEqual(actualRefs: readonly MdReference[], ...expectedRefs: { uri: vscode.Uri; line: number }[]) { + assert.strictEqual(actualRefs.length, expectedRefs.length, `Reference counts should match`); + + for (let i = 0; i < actualRefs.length; ++i) { + const actual = actualRefs[i].location; + const expected = expectedRefs[i]; + assert.strictEqual(actual.uri.toString(), expected.uri.toString(), `Ref '${i}' has expected document`); + assert.strictEqual(actual.range.start.line, expected.line, `Ref '${i}' has expected start line`); + assert.strictEqual(actual.range.end.line, expected.line, `Ref '${i}' has expected end line`); + } +} + +suite('markdown: find file references', () => { + + test('Should find basic references', async () => { + const docUri = workspacePath('doc.md'); + const otherUri = workspacePath('other.md'); + + const refs = await getFileReferences(otherUri, new InMemoryWorkspaceMarkdownDocuments([ + new InMemoryDocument(docUri, joinLines( + `# header`, + `[link 1](./other.md)`, + `[link 2](./other.md)`, + )), + new InMemoryDocument(otherUri, joinLines( + `# header`, + `pre`, + `[link 3](./other.md)`, + `post`, + )), + ])); + + assertReferencesEqual(refs!, + { uri: docUri, line: 1 }, + { uri: docUri, line: 2 }, + { uri: otherUri, line: 2 }, + ); + }); + + test('Should find references with and without file extensions', async () => { + const docUri = workspacePath('doc.md'); + const otherUri = workspacePath('other.md'); + + const refs = await getFileReferences(otherUri, new InMemoryWorkspaceMarkdownDocuments([ + new InMemoryDocument(docUri, joinLines( + `# header`, + `[link 1](./other.md)`, + `[link 2](./other)`, + )), + new InMemoryDocument(otherUri, joinLines( + `# header`, + `pre`, + `[link 3](./other.md)`, + `[link 4](./other)`, + `post`, + )), + ])); + + assertReferencesEqual(refs!, + { uri: docUri, line: 1 }, + { uri: docUri, line: 2 }, + { uri: otherUri, line: 2 }, + { uri: otherUri, line: 3 }, + ); + }); + + test('Should find references with headers on links', async () => { + const docUri = workspacePath('doc.md'); + const otherUri = workspacePath('other.md'); + + const refs = await getFileReferences(otherUri, new InMemoryWorkspaceMarkdownDocuments([ + new InMemoryDocument(docUri, joinLines( + `# header`, + `[link 1](./other.md#sub-bla)`, + `[link 2](./other#sub-bla)`, + )), + new InMemoryDocument(otherUri, joinLines( + `# header`, + `pre`, + `[link 3](./other.md#sub-bla)`, + `[link 4](./other#sub-bla)`, + `post`, + )), + ])); + + assertReferencesEqual(refs!, + { uri: docUri, line: 1 }, + { uri: docUri, line: 2 }, + { uri: otherUri, line: 2 }, + { uri: otherUri, line: 3 }, + ); + }); +}); diff --git a/extensions/markdown-language-features/src/test/references.test.ts b/extensions/markdown-language-features/src/test/references.test.ts index 4b7f9532cec..319fcec7817 100644 --- a/extensions/markdown-language-features/src/test/references.test.ts +++ b/extensions/markdown-language-features/src/test/references.test.ts @@ -312,7 +312,7 @@ suite('markdown: find all references', () => { const otherUri = workspacePath('sub', 'other.md'); const doc = new InMemoryDocument(docUri, joinLines( - `[other](./sub/other)`, + `[other](./sub/other)`, // trigger here )); const refs = await getReferences(doc, new vscode.Position(0, 15), new InMemoryWorkspaceMarkdownDocuments([ @@ -328,6 +328,22 @@ suite('markdown: find all references', () => { ); }); + test('Should find explicit references to own file ', async () => { + const uri = workspacePath('doc.md'); + const doc = new InMemoryDocument(uri, joinLines( + `[bare](doc.md)`, // trigger here + `[rel](./doc.md)`, + `[abs](/doc.md)`, + )); + + const refs = await getReferences(doc, new vscode.Position(0, 12), new InMemoryWorkspaceMarkdownDocuments([doc])); + assertReferencesEqual(refs!, + { uri, line: 0 }, + { uri, line: 1 }, + { uri, line: 2 }, + ); + }); + suite('Reference links', () => { test('Should find reference links within file from link', async () => { const docUri = workspacePath('doc.md'); diff --git a/extensions/markdown-language-features/src/test/rename.test.ts b/extensions/markdown-language-features/src/test/rename.test.ts index e22453f3e02..cc3ad0617a9 100644 --- a/extensions/markdown-language-features/src/test/rename.test.ts +++ b/extensions/markdown-language-features/src/test/rename.test.ts @@ -18,9 +18,9 @@ import { assertRangeEqual, joinLines, noopToken, workspacePath } from './util'; /** - * Get the range that the rename should happen on. + * Get prepare rename info. */ -function getRenameRange(doc: InMemoryDocument, pos: vscode.Position, workspaceContents: MdWorkspaceContents) { +function prepareRename(doc: InMemoryDocument, pos: vscode.Position, workspaceContents: MdWorkspaceContents): Promise { const engine = createNewMarkdownEngine(); const linkProvider = new MdLinkProvider(engine); const referencesProvider = new MdReferencesProvider(linkProvider, workspaceContents, engine, githubSlugifier); @@ -72,8 +72,8 @@ suite('markdown: rename', () => { `# abc` )); - const range = await getRenameRange(doc, new vscode.Position(0, 0), new InMemoryWorkspaceMarkdownDocuments([doc])); - assertRangeEqual(range!, new vscode.Range(0, 2, 0, 5)); + const info = await prepareRename(doc, new vscode.Position(0, 0), new InMemoryWorkspaceMarkdownDocuments([doc])); + assertRangeEqual(info!.range, new vscode.Range(0, 2, 0, 5)); const edit = await getRenameEdits(doc, new vscode.Position(0, 0), "New Header", new InMemoryWorkspaceMarkdownDocuments([doc])); assertEditsEqual(edit!, { @@ -89,8 +89,8 @@ suite('markdown: rename', () => { `### abc ###` )); - const range = await getRenameRange(doc, new vscode.Position(0, 0), new InMemoryWorkspaceMarkdownDocuments([doc])); - assertRangeEqual(range!, new vscode.Range(0, 4, 0, 7)); + const info = await prepareRename(doc, new vscode.Position(0, 0), new InMemoryWorkspaceMarkdownDocuments([doc])); + assertRangeEqual(info!.range, new vscode.Range(0, 4, 0, 7)); const edit = await getRenameEdits(doc, new vscode.Position(0, 0), "New Header", new InMemoryWorkspaceMarkdownDocuments([doc])); assertEditsEqual(edit!, { @@ -208,7 +208,53 @@ suite('markdown: rename', () => { }); }); - test('Rename on ref should rename refs and def', async () => { + test('Rename on link in other file should pick up all refs', async () => { + const uri = workspacePath('doc.md'); + const otherUri = workspacePath('other.md'); + const doc = new InMemoryDocument(uri, joinLines( + `### A b C`, + `[text](#a-b-c)`, + )); + + const otherDoc = new InMemoryDocument(otherUri, joinLines( + `[text](#a-b-c)`, + `[text](./doc.md#a-b-c)`, + `[text](./doc#a-b-c)` + )); + + const expectedEdits = [ + { + uri: uri, edits: [ + new vscode.TextEdit(new vscode.Range(0, 4, 0, 9), 'New Header'), + new vscode.TextEdit(new vscode.Range(1, 8, 1, 13), 'new-header'), + ] + }, { + uri: otherUri, edits: [ + new vscode.TextEdit(new vscode.Range(1, 16, 1, 21), 'new-header'), + new vscode.TextEdit(new vscode.Range(2, 13, 2, 18), 'new-header'), + ] + } + ]; + + { + // Rename on header with file extension + const edit = await getRenameEdits(otherDoc, new vscode.Position(1, 17), "New Header", new InMemoryWorkspaceMarkdownDocuments([ + doc, + otherDoc + ])); + assertEditsEqual(edit!, ...expectedEdits); + } + { + // Rename on header without extension + const edit = await getRenameEdits(otherDoc, new vscode.Position(2, 15), "New Header", new InMemoryWorkspaceMarkdownDocuments([ + doc, + otherDoc + ])); + assertEditsEqual(edit!, ...expectedEdits); + } + }); + + test('Rename on reference should rename references and definition', async () => { const uri = workspacePath('doc.md'); const doc = new InMemoryDocument(uri, joinLines( `[text][ref]`, // rename here @@ -227,7 +273,7 @@ suite('markdown: rename', () => { }); }); - test('Rename on def should rename refs and def', async () => { + test('Rename on definition should rename references and definitions', async () => { const uri = workspacePath('doc.md'); const doc = new InMemoryDocument(uri, joinLines( `[text][ref]`, @@ -246,6 +292,28 @@ suite('markdown: rename', () => { }); }); + test('Rename on definition entry should rename header and references', async () => { + const uri = workspacePath('doc.md'); + const doc = new InMemoryDocument(uri, joinLines( + `# a B c`, + `[ref text][ref]`, + `[direct](#a-b-c)`, + `[ref]: #a-b-c`, // rename here + )); + + const preparedInfo = await prepareRename(doc, new vscode.Position(3, 10), new InMemoryWorkspaceMarkdownDocuments([doc])); + assert.strictEqual(preparedInfo!.placeholder, 'a B c'); + assertRangeEqual(preparedInfo!.range, new vscode.Range(3, 8, 3, 13)); + + const edit = await getRenameEdits(doc, new vscode.Position(3, 10), "x Y z", new InMemoryWorkspaceMarkdownDocuments([doc])); + assertEditsEqual(edit!, { + uri, edits: [ + new vscode.TextEdit(new vscode.Range(0, 2, 0, 7), 'x Y z'), + new vscode.TextEdit(new vscode.Range(2, 10, 2, 15), 'x-y-z'), + new vscode.TextEdit(new vscode.Range(3, 8, 3, 13), 'x-y-z'), + ] + }); + }); test('Rename should not be supported on link text', async () => { const uri = workspacePath('doc.md'); @@ -254,6 +322,38 @@ suite('markdown: rename', () => { `[text](#header)`, )); - await assert.rejects(getRenameRange(doc, new vscode.Position(1, 2), new InMemoryWorkspaceMarkdownDocuments([doc]))); + await assert.rejects(prepareRename(doc, new vscode.Position(1, 2), new InMemoryWorkspaceMarkdownDocuments([doc]))); + }); + + test('Rename should not be supported on bare file link', async () => { + const uri = workspacePath('doc.md'); + const doc = new InMemoryDocument(uri, joinLines( + `[text](./doc.md)`, + `[other](./doc.md)`, + )); + + await assert.rejects(prepareRename(doc, new vscode.Position(0, 10), new InMemoryWorkspaceMarkdownDocuments([doc]))); + }); + + test('Rename should not be supported on bare file link in definition', async () => { + const uri = workspacePath('doc.md'); + const doc = new InMemoryDocument(uri, joinLines( + `[text](./doc.md)`, + `[ref]: ./doc.md`, + )); + + await assert.rejects(prepareRename(doc, new vscode.Position(1, 10), new InMemoryWorkspaceMarkdownDocuments([doc]))); + }); + + test('Rename on link should use header text as placeholder', async () => { + const uri = workspacePath('doc.md'); + const doc = new InMemoryDocument(uri, joinLines( + `### a B c ###`, + `[text](#a-b-c)`, + )); + + const info = await prepareRename(doc, new vscode.Position(1, 10), new InMemoryWorkspaceMarkdownDocuments([doc])); + assert.strictEqual(info!.placeholder, 'a B c'); + assertRangeEqual(info!.range, new vscode.Range(1, 8, 1, 13)); }); }); diff --git a/extensions/markdown-language-features/src/workspaceContents.ts b/extensions/markdown-language-features/src/workspaceContents.ts index 43d9b9128f6..14609d29b5f 100644 --- a/extensions/markdown-language-features/src/workspaceContents.ts +++ b/extensions/markdown-language-features/src/workspaceContents.ts @@ -24,7 +24,7 @@ export interface SkinnyTextDocument { readonly version: number; readonly lineCount: number; - getText(): string; + getText(range?: vscode.Range): string; lineAt(line: number): SkinnyTextLine; positionAt(offset: number): vscode.Position; } diff --git a/extensions/typescript-basics/snippets/typescript.code-snippets b/extensions/typescript-basics/snippets/typescript.code-snippets index 49ebefbac9a..0162ef50975 100644 --- a/extensions/typescript-basics/snippets/typescript.code-snippets +++ b/extensions/typescript-basics/snippets/typescript.code-snippets @@ -44,12 +44,12 @@ ], "description": "Private Method Definition" }, - "Import external module.": { - "prefix": "import statement", + "Import Statement": { + "prefix": "import", "body": [ "import { $0 } from \"${1:module}\";" ], - "description": "Import external module." + "description": "Import external module" }, "Property getter": { "prefix": "get", diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts index dc839863d67..b241b7170ec 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/commands.test.ts @@ -112,15 +112,15 @@ suite('vscode API - commands', () => { await commands.executeCommand('vscode.open', uri); assert.strictEqual(window.activeTextEditor?.viewColumn, ViewColumn.One); - assert.strictEqual(window.tabGroups.groups[0].activeTab?.group.viewColumn, ViewColumn.One); + assert.strictEqual(window.tabGroups.all[0].activeTab?.group.viewColumn, ViewColumn.One); await commands.executeCommand('vscode.open', uri, ViewColumn.Two); assert.strictEqual(window.activeTextEditor?.viewColumn, ViewColumn.Two); - assert.strictEqual(window.tabGroups.groups[1].activeTab?.group.viewColumn, ViewColumn.Two); + assert.strictEqual(window.tabGroups.all[1].activeTab?.group.viewColumn, ViewColumn.Two); await commands.executeCommand('vscode.open', uri, ViewColumn.One); assert.strictEqual(window.activeTextEditor?.viewColumn, ViewColumn.One); - assert.strictEqual(window.tabGroups.groups[0].activeTab?.group.viewColumn, ViewColumn.One); + assert.strictEqual(window.tabGroups.all[0].activeTab?.group.viewColumn, ViewColumn.One); let e1: Error | undefined = undefined; try { diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts index 0b62d4e0549..eeffb282f91 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/notebook.editor.test.ts @@ -107,8 +107,8 @@ import * as utils from '../utils'; assert.strictEqual(editor.document.uri.toString(), resource.toString()); }); - test.skip('Active/Visible Editor', async function () { // TODO@rebornix https://github.com/microsoft/vscode/issues/139078 - const firstEditorOpen = utils.asPromise(vscode.window.onDidChangeActiveNotebookEditor); + test('Active/Visible Editor', async function () { + const firstEditorOpen = onDidOpenNotebookEditor(); const resource = await utils.createRandomFile(undefined, undefined, '.nbdtest'); const firstEditor = await vscode.window.showNotebookDocument(resource); await firstEditorOpen; @@ -116,14 +116,16 @@ import * as utils from '../utils'; assert.strictEqual(vscode.window.visibleNotebookEditors.includes(firstEditor), true); const secondEditor = await vscode.window.showNotebookDocument(resource, { viewColumn: vscode.ViewColumn.Beside }); - assert.strictEqual(secondEditor === vscode.window.activeNotebookEditor, true); + // There is no guarantee that when `showNotebookDocument` resolves, the active notebook editor is already updated correctly. + // assert.strictEqual(secondEditor === vscode.window.activeNotebookEditor, true); assert.notStrictEqual(firstEditor, secondEditor); assert.strictEqual(vscode.window.visibleNotebookEditors.includes(secondEditor), true); assert.strictEqual(vscode.window.visibleNotebookEditors.includes(firstEditor), true); assert.strictEqual(vscode.window.visibleNotebookEditors.length, 2); + await utils.closeAllEditors(); }); - test.skip('Notebook Editor Event - onDidChangeVisibleNotebookEditors on open/close', async function () { // TODO@rebornix https://github.com/microsoft/vscode/issues/139958 + test('Notebook Editor Event - onDidChangeVisibleNotebookEditors on open/close', async function () { const openedEditor = utils.asPromise(vscode.window.onDidChangeVisibleNotebookEditors); const resource = await utils.createRandomFile(undefined, undefined, '.nbdtest'); await vscode.window.showNotebookDocument(resource); diff --git a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts index 299f7c7287c..463c78c40bb 100644 --- a/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts +++ b/extensions/vscode-api-tests/src/singlefolder-tests/window.test.ts @@ -383,12 +383,12 @@ suite('vscode API - window', () => { await window.showTextDocument(docC, { viewColumn: ViewColumn.Two, preview: false }); const tabGroups = window.tabGroups; - assert.strictEqual(tabGroups.groups.length, 2); + assert.strictEqual(tabGroups.all.length, 2); - const group1Tabs = tabGroups.groups[0].tabs; + const group1Tabs = tabGroups.all[0].tabs; assert.strictEqual(group1Tabs.length, 2); - const group2Tabs = tabGroups.groups[1].tabs; + const group2Tabs = tabGroups.all[1].tabs; assert.strictEqual(group2Tabs.length, 1); await tabGroups.move(group1Tabs[0], ViewColumn.One, 1); @@ -398,7 +398,7 @@ suite('vscode API - window', () => { test('Tabs - vscode.open & vscode.diff', async function () { // Simple function to get the active tab const getActiveTab = () => { - return window.tabGroups.groups.find(g => g.isActive)?.activeTab; + return window.tabGroups.all.find(g => g.isActive)?.activeTab; }; const [docA, docB, docC] = await Promise.all([ @@ -421,7 +421,7 @@ suite('vscode API - window', () => { await commands.executeCommand('vscode.diff', leftDiff, rightDiff, 'Diff', { viewColumn: ViewColumn.Four, preview: false }); assert.strictEqual(getActiveTab()?.group.viewColumn, ViewColumn.Four); - const tabs = window.tabGroups.groups.map(g => g.tabs).flat(1); + const tabs = window.tabGroups.all.map(g => g.tabs).flat(1); assert.strictEqual(tabs.length, 5); assert.ok(tabs[0].kind instanceof TabKindText); assert.strictEqual(tabs[0].kind.uri.toString(), docA.uri.toString()); @@ -455,7 +455,7 @@ suite('vscode API - window', () => { const rightDiff = await createRandomFile(); await commands.executeCommand('vscode.diff', leftDiff, rightDiff, 'Diff', { viewColumn: ViewColumn.Three, preview: false }); - const tabs = window.tabGroups.groups.map(g => g.tabs).flat(1); + const tabs = window.tabGroups.all.map(g => g.tabs).flat(1); assert.strictEqual(tabs.length, 5); // All resources should match the text documents as they're the only tabs currently open @@ -488,7 +488,7 @@ suite('vscode API - window', () => { // Function to acquire the active tab within the active group const getActiveTabInActiveGroup = () => { - const activeGroup = window.tabGroups.groups.filter(group => group.isActive)[0]; + const activeGroup = window.tabGroups.all.filter(group => group.isActive)[0]; return activeGroup?.activeTab; }; diff --git a/package.json b/package.json index a6ef958814f..d64caa828e2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "code-oss-dev", "version": "1.67.0", - "distro": "7ee3b198b317d29648533b3713783234658d7968", + "distro": "cd09b5b6e923bac5a9d1a9db408c7e21f864e9ea", "author": { "name": "Microsoft Corporation" }, @@ -84,18 +84,18 @@ "vscode-proxy-agent": "^0.12.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "6.0.0", - "xterm": "4.19.0-beta.17", - "xterm-addon-search": "0.9.0-beta.17", - "xterm-addon-serialize": "0.7.0-beta.11", + "xterm": "4.19.0-beta.20", + "xterm-addon-search": "0.9.0-beta.18", + "xterm-addon-serialize": "0.7.0-beta.12", "xterm-addon-unicode11": "0.4.0-beta.3", - "xterm-addon-webgl": "0.12.0-beta.25", - "xterm-headless": "4.19.0-beta.17", + "xterm-addon-webgl": "0.12.0-beta.27", + "xterm-headless": "4.19.0-beta.20", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, "devDependencies": { "7zip": "0.0.6", - "@playwright/test": "1.18.0", + "@playwright/test": "1.20.2", "@types/applicationinsights": "0.20.0", "@types/cookie": "^0.3.3", "@types/copy-webpack-plugin": "^6.0.3", @@ -123,7 +123,7 @@ "@types/yazl": "^2.4.2", "@typescript-eslint/eslint-plugin": "^5.10.0", "@typescript-eslint/parser": "^5.10.0", - "@vscode/telemetry-extractor": "^1.9.5", + "@vscode/telemetry-extractor": "^1.9.6", "@vscode/test-web": "^0.0.22", "ansi-colors": "^3.2.3", "asar": "^3.0.3", @@ -135,7 +135,7 @@ "cssnano": "^4.1.11", "debounce": "^1.0.0", "deemon": "^1.4.0", - "electron": "17.3.0", + "electron": "17.3.1", "eslint": "8.7.0", "eslint-plugin-header": "3.1.1", "eslint-plugin-jsdoc": "^19.1.0", diff --git a/product.json b/product.json index 234b38250d6..ac6b2de18ac 100644 --- a/product.json +++ b/product.json @@ -31,7 +31,7 @@ "builtInExtensions": [ { "name": "ms-vscode.references-view", - "version": "0.0.86", + "version": "0.0.89", "repo": "https://github.com/microsoft/vscode-references-view", "metadata": { "id": "dc489f46-520d-4556-ae85-1f9eab3c412d", diff --git a/remote/package.json b/remote/package.json index 3ac3c183714..203c90916b6 100644 --- a/remote/package.json +++ b/remote/package.json @@ -24,12 +24,12 @@ "vscode-proxy-agent": "^0.12.0", "vscode-regexpp": "^3.1.0", "vscode-textmate": "6.0.0", - "xterm": "4.19.0-beta.17", - "xterm-addon-search": "0.9.0-beta.17", - "xterm-addon-serialize": "0.7.0-beta.11", + "xterm": "4.19.0-beta.20", + "xterm-addon-search": "0.9.0-beta.18", + "xterm-addon-serialize": "0.7.0-beta.12", "xterm-addon-unicode11": "0.4.0-beta.3", - "xterm-addon-webgl": "0.12.0-beta.25", - "xterm-headless": "4.19.0-beta.17", + "xterm-addon-webgl": "0.12.0-beta.27", + "xterm-headless": "4.19.0-beta.20", "yauzl": "^2.9.2", "yazl": "^2.4.3" }, diff --git a/remote/web/package.json b/remote/web/package.json index fb333057023..1d3e920919e 100644 --- a/remote/web/package.json +++ b/remote/web/package.json @@ -10,9 +10,9 @@ "tas-client-umd": "0.1.4", "vscode-oniguruma": "1.6.1", "vscode-textmate": "6.0.0", - "xterm": "4.19.0-beta.17", - "xterm-addon-search": "0.9.0-beta.17", + "xterm": "4.19.0-beta.20", + "xterm-addon-search": "0.9.0-beta.18", "xterm-addon-unicode11": "0.4.0-beta.3", - "xterm-addon-webgl": "0.12.0-beta.25" + "xterm-addon-webgl": "0.12.0-beta.27" } } diff --git a/remote/web/yarn.lock b/remote/web/yarn.lock index 1185ff5dc1b..0fbfec54b2c 100644 --- a/remote/web/yarn.lock +++ b/remote/web/yarn.lock @@ -113,22 +113,22 @@ vscode-textmate@6.0.0: resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-6.0.0.tgz#a3777197235036814ac9a92451492f2748589210" integrity sha512-gu73tuZfJgu+mvCSy4UZwd2JXykjK9zAZsfmDeut5dx/1a7FeTk0XwJsSuqQn+cuMCGVbIBfl+s53X4T19DnzQ== -xterm-addon-search@0.9.0-beta.17: - version "0.9.0-beta.17" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0-beta.17.tgz#4cd575fd67a010af3d0b224092527747bb87c219" - integrity sha512-/Q6xQ/HunP2BxJU4NTsLX9aH376tsT9dpcS+NFBDVbwB1iPF02JhedHmA4mxY3KGNyQflefhwyGGMKZfTau2iA== +xterm-addon-search@0.9.0-beta.18: + version "0.9.0-beta.18" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0-beta.18.tgz#5317aed1dc747f468ccb7ecd151fb00d82a8a19d" + integrity sha512-SAeA3thc2WJNYXwjOEJFLpZ1ZVOs22RLmz9a6WcrzXkvCjLZRvbRGwX25Ms+Dd7dVDQNbKVUzUJohspP/vYr0Q== xterm-addon-unicode11@0.4.0-beta.3: version "0.4.0-beta.3" resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.3.tgz#f350184155fafd5ad0d6fbf31d13e6ca7dea1efa" integrity sha512-FryZAVwbUjKTmwXnm1trch/2XO60F5JsDvOkZhzobV1hm10sFLVuZpFyHXiUx7TFeeFsvNP+S77LAtWoeT5z+Q== -xterm-addon-webgl@0.12.0-beta.25: - version "0.12.0-beta.25" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.25.tgz#67fd222d245c4bf749b2abdf0acec640fb575bcd" - integrity sha512-QK0r6hi+plkMtHZrOlUO/IcDa9mnYxOKrqrmzbjxT6cuhXJf2EiWpBBY6Rkk0cVXtDOlkGO1b7hWvD3qeWeWvQ== +xterm-addon-webgl@0.12.0-beta.27: + version "0.12.0-beta.27" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.27.tgz#afc5bc01d1ef3af9005fb9f6325a4db9c92aa8d9" + integrity sha512-P948trotU8FMHtaA7C2x97VpLq6QLSjO53kWNvONS0/XwEKQBIYCI7Jfri2wcLgfQg6Cn4OQGLoj2YBK3MMyww== -xterm@4.19.0-beta.17: - version "4.19.0-beta.17" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.17.tgz#79b177eedd3bc835e1951f38318b5c0c04039896" - integrity sha512-NvfGJLfZa0SHGR6WttNfugRYizM20gVbWM/nVJAlyHruMZ/rfJj29MuPERJAZTNHtRnYplhAP8bSc3r7/2l+dg== +xterm@4.19.0-beta.20: + version "4.19.0-beta.20" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.20.tgz#d8e970d8a8460c1d1a5ec9866f78f607a44c1349" + integrity sha512-IYI4ngSWzpV4sJXLWGEDF7vgLuUHn0CUQ42+TGv4H/hCGo4uru4s/D3Yws0ETb3a9VwRpZEPsigULaWTnhFusg== diff --git a/remote/yarn.lock b/remote/yarn.lock index 2d50c3bc983..381610e5a8d 100644 --- a/remote/yarn.lock +++ b/remote/yarn.lock @@ -914,35 +914,35 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -xterm-addon-search@0.9.0-beta.17: - version "0.9.0-beta.17" - resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0-beta.17.tgz#4cd575fd67a010af3d0b224092527747bb87c219" - integrity sha512-/Q6xQ/HunP2BxJU4NTsLX9aH376tsT9dpcS+NFBDVbwB1iPF02JhedHmA4mxY3KGNyQflefhwyGGMKZfTau2iA== +xterm-addon-search@0.9.0-beta.18: + version "0.9.0-beta.18" + resolved "https://registry.yarnpkg.com/xterm-addon-search/-/xterm-addon-search-0.9.0-beta.18.tgz#5317aed1dc747f468ccb7ecd151fb00d82a8a19d" + integrity sha512-SAeA3thc2WJNYXwjOEJFLpZ1ZVOs22RLmz9a6WcrzXkvCjLZRvbRGwX25Ms+Dd7dVDQNbKVUzUJohspP/vYr0Q== -xterm-addon-serialize@0.7.0-beta.11: - version "0.7.0-beta.11" - resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.7.0-beta.11.tgz#d9d6a862b3d70fc6160e39bc235a2a9ef45e20d6" - integrity sha512-okirLeH8VpOxlnZkER2lN0emaTgWe/DHVGpY9ByfLIoYw506Ci2LRcOjyYwJBRTJcfLp3TGnwQSYfooCZHDndw== +xterm-addon-serialize@0.7.0-beta.12: + version "0.7.0-beta.12" + resolved "https://registry.yarnpkg.com/xterm-addon-serialize/-/xterm-addon-serialize-0.7.0-beta.12.tgz#4f845d8b1a9f9b7ae3f910455ce8c58b041babc7" + integrity sha512-b4Ug0B/RSJMux+KAcp+PXVqubVyXjN1yCQw1FOkgVYTpmd9AH/X+EcxKml5Lz8DsKmsXqfD9AlV3WpEeT+OtMw== xterm-addon-unicode11@0.4.0-beta.3: version "0.4.0-beta.3" resolved "https://registry.yarnpkg.com/xterm-addon-unicode11/-/xterm-addon-unicode11-0.4.0-beta.3.tgz#f350184155fafd5ad0d6fbf31d13e6ca7dea1efa" integrity sha512-FryZAVwbUjKTmwXnm1trch/2XO60F5JsDvOkZhzobV1hm10sFLVuZpFyHXiUx7TFeeFsvNP+S77LAtWoeT5z+Q== -xterm-addon-webgl@0.12.0-beta.25: - version "0.12.0-beta.25" - resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.25.tgz#67fd222d245c4bf749b2abdf0acec640fb575bcd" - integrity sha512-QK0r6hi+plkMtHZrOlUO/IcDa9mnYxOKrqrmzbjxT6cuhXJf2EiWpBBY6Rkk0cVXtDOlkGO1b7hWvD3qeWeWvQ== +xterm-addon-webgl@0.12.0-beta.27: + version "0.12.0-beta.27" + resolved "https://registry.yarnpkg.com/xterm-addon-webgl/-/xterm-addon-webgl-0.12.0-beta.27.tgz#afc5bc01d1ef3af9005fb9f6325a4db9c92aa8d9" + integrity sha512-P948trotU8FMHtaA7C2x97VpLq6QLSjO53kWNvONS0/XwEKQBIYCI7Jfri2wcLgfQg6Cn4OQGLoj2YBK3MMyww== -xterm-headless@4.19.0-beta.17: - version "4.19.0-beta.17" - resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.19.0-beta.17.tgz#0540b4526f31f2aab6d026f91ac56475260d45e0" - integrity sha512-A6CbJauiCDur5BZ6kHOCJ3rPLPAdX4JHMGazt0UoYkgbi0xQun+3P2M8C7Ll/NS/zZtv6aPJHAHeXu2TWqV5Lg== +xterm-headless@4.19.0-beta.20: + version "4.19.0-beta.20" + resolved "https://registry.yarnpkg.com/xterm-headless/-/xterm-headless-4.19.0-beta.20.tgz#9e401920fcc24c2474e0bd45df932c62413594da" + integrity sha512-twp0vCyfdI4wVgDrwxaHk1FtC4UhTNNgbIPT6yVPjICOUkUTOvFjrQCNKHv2uMOJo9uAH2gyOsIqHdEP549rJA== -xterm@4.19.0-beta.17: - version "4.19.0-beta.17" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.17.tgz#79b177eedd3bc835e1951f38318b5c0c04039896" - integrity sha512-NvfGJLfZa0SHGR6WttNfugRYizM20gVbWM/nVJAlyHruMZ/rfJj29MuPERJAZTNHtRnYplhAP8bSc3r7/2l+dg== +xterm@4.19.0-beta.20: + version "4.19.0-beta.20" + resolved "https://registry.yarnpkg.com/xterm/-/xterm-4.19.0-beta.20.tgz#d8e970d8a8460c1d1a5ec9866f78f607a44c1349" + integrity sha512-IYI4ngSWzpV4sJXLWGEDF7vgLuUHn0CUQ42+TGv4H/hCGo4uru4s/D3Yws0ETb3a9VwRpZEPsigULaWTnhFusg== yallist@^4.0.0: version "4.0.0" diff --git a/src/vs/base/common/glob.ts b/src/vs/base/common/glob.ts index 3eb569d70e7..958eae2d839 100644 --- a/src/vs/base/common/glob.ts +++ b/src/vs/base/common/glob.ts @@ -584,13 +584,38 @@ function parsedExpression(expression: IExpression, options: IGlobOptions): Parse } const resultExpression: ParsedStringPattern = function (path: string, basename?: string) { - for (let i = 0, n = parsedPatterns.length; i < n; i++) { + let resultPromises: Promise[] | undefined = undefined; - // Check if pattern matches path + for (let i = 0, n = parsedPatterns.length; i < n; i++) { const result = parsedPatterns[i](path, basename); - if (result) { - return result; + if (typeof result === 'string') { + return result; // immediately return as soon as the first expression matches } + + // If the result is a promise, we have to keep it for + // later processing and await the result properly. + if (isThenable(result)) { + if (!resultPromises) { + resultPromises = []; + } + + resultPromises.push(result); + } + } + + // With result promises, we have to loop over each and + // await the result before we can return any result. + if (resultPromises) { + return (async () => { + for (const resultPromise of resultPromises) { + const result = await resultPromise; + if (typeof result === 'string') { + return result; + } + } + + return null; + })(); } return null; @@ -611,6 +636,7 @@ function parsedExpression(expression: IExpression, options: IGlobOptions): Parse const resultExpression: ParsedStringPattern = function (path: string, base?: string, hasSibling?: (name: string) => boolean | Promise) { let name: string | undefined = undefined; + let resultPromises: Promise[] | undefined = undefined; for (let i = 0, n = parsedPatterns.length; i < n; i++) { @@ -627,9 +653,34 @@ function parsedExpression(expression: IExpression, options: IGlobOptions): Parse } const result = parsedPattern(path, base, name, hasSibling); - if (result) { - return result; + if (typeof result === 'string') { + return result; // immediately return as soon as the first expression matches } + + // If the result is a promise, we have to keep it for + // later processing and await the result properly. + if (isThenable(result)) { + if (!resultPromises) { + resultPromises = []; + } + + resultPromises.push(result); + } + } + + // With result promises, we have to loop over each and + // await the result before we can return any result. + if (resultPromises) { + return (async () => { + for (const resultPromise of resultPromises) { + const result = await resultPromise; + if (typeof result === 'string') { + return result; + } + } + + return null; + })(); } return null; diff --git a/src/vs/base/parts/quickinput/browser/quickInput.ts b/src/vs/base/parts/quickinput/browser/quickInput.ts index ee3543a2203..9eb6fa7e574 100644 --- a/src/vs/base/parts/quickinput/browser/quickInput.ts +++ b/src/vs/base/parts/quickinput/browser/quickInput.ts @@ -1249,7 +1249,6 @@ export class QuickInputController extends Disposable { const inputBox = this._register(new QuickInputBox(filterContainer)); inputBox.setAttribute('aria-describedby', `${this.idPrefix}message`); - inputBox.setAttribute('aria-live', 'polite'); const visibleCountContainer = dom.append(filterContainer, $('.quick-input-visible-count')); visibleCountContainer.setAttribute('aria-live', 'polite'); diff --git a/src/vs/base/test/common/glob.test.ts b/src/vs/base/test/common/glob.test.ts index ba31ed0099c..bc70f0fa531 100644 --- a/src/vs/base/test/common/glob.test.ts +++ b/src/vs/base/test/common/glob.test.ts @@ -1099,4 +1099,19 @@ suite('Glob', () => { let p = 'scheme:/**/*.md'; assertGlobMatch(p, URI.file('super/duper/long/some/file.md').with({ scheme: 'scheme' }).toString()); }); + + test('expression fails when siblings use promises (https://github.com/microsoft/vscode/issues/146294)', async function () { + let siblings = ['test.html', 'test.txt', 'test.ts']; + let hasSibling = (name: string) => Promise.resolve(siblings.indexOf(name) !== -1); + + // { "**/*.js": { "when": "$(basename).ts" } } + let expression: glob.IExpression = { + '**/test.js': { when: '$(basename).js' }, + '**/*.js': { when: '$(basename).ts' } + }; + + const parsedExpression = glob.parse(expression); + + assert.strictEqual('**/*.js', await parsedExpression('test.js', undefined, hasSibling)); + }); }); diff --git a/src/vs/code/electron-main/app.ts b/src/vs/code/electron-main/app.ts index d7459f90ea6..82260d4a427 100644 --- a/src/vs/code/electron-main/app.ts +++ b/src/vs/code/electron-main/app.ts @@ -524,7 +524,7 @@ export class CodeApplication extends Disposable { // Create driver if (this.environmentMainService.driverHandle) { - const server = await serveDriver(mainProcessElectronServer, this.environmentMainService.driverHandle, this.environmentMainService, appInstantiationService); + const server = await serveDriver(mainProcessElectronServer, this.environmentMainService.driverHandle, appInstantiationService); this.logService.info('Driver started at:', this.environmentMainService.driverHandle); this._register(server); @@ -876,9 +876,21 @@ export class CodeApplication extends Disposable { return true; } - // If we have not yet handled the URI and we have no window opened (macOS only) - // we first open a window and then try to open that URI within that window - if (isMacintosh && windowsMainService.getWindowCount() === 0) { + // We should handle the URI in a new window if no window is open (macOS only) + let shouldOpenInNewWindow = isMacintosh && windowsMainService.getWindowCount() === 0; + + // or if the URL contains `windowId=_blank` + if (!shouldOpenInNewWindow) { + const params = new URLSearchParams(uri.query); + + if (params.get('windowId') === '_blank') { + params.delete('windowId'); + uri = uri.with({ query: params.toString() }); + shouldOpenInNewWindow = true; + } + } + + if (shouldOpenInNewWindow) { const [window] = windowsMainService.open({ context: OpenContext.API, cli: { ...environmentService.args }, diff --git a/src/vs/editor/common/config/editorOptions.ts b/src/vs/editor/common/config/editorOptions.ts index 2d38b52d373..febb7dac4d6 100644 --- a/src/vs/editor/common/config/editorOptions.ts +++ b/src/vs/editor/common/config/editorOptions.ts @@ -2503,6 +2503,11 @@ export interface IEditorInlayHintsOptions { */ enabled?: boolean; + /** + * + */ + toggle?: 'show' | 'hide' | null; + /** * Font size of inline hints. * Default to 90% of the editor font size. @@ -2531,7 +2536,7 @@ export type EditorInlayHintsOptions = Readonly { constructor() { - const defaults: EditorInlayHintsOptions = { enabled: true, fontSize: 0, fontFamily: '', displayStyle: 'compact' }; + const defaults: EditorInlayHintsOptions = { enabled: true, toggle: null, fontSize: 0, fontFamily: '', displayStyle: 'compact' }; super( EditorOption.inlayHints, 'inlayHints', defaults, { @@ -2540,6 +2545,16 @@ class EditorInlayHints extends BaseEditorOption