Merge remote-tracking branch 'origin/main' into hediet/tokenization

This commit is contained in:
Alex Dima 2022-04-05 21:35:06 +02:00
commit 786f16633a
No known key found for this signature in database
GPG key ID: 39563C1504FDD0C9
198 changed files with 4537 additions and 3176 deletions

View file

@ -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"
},
}

View file

@ -1,4 +1,4 @@
disturl "https://electronjs.org/headers"
target "17.3.0"
target "17.3.1"
runtime "electron"
build_from_source "true"

View file

@ -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)

View file

@ -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)

View file

@ -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'))

View file

@ -33,6 +33,7 @@ async function main() {
'CodeResources',
'fsevents.node',
'Info.plist',
'MainMenu.nib',
'.npmrc'
],
outAppPath,

View file

@ -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,

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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": {

View file

@ -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;
}
}

View file

@ -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%"
}
}
}

View file

@ -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`."

View file

@ -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"
},

View file

@ -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"

View file

@ -161,25 +161,27 @@ export class DefaultCompletionItemProvider implements vscode.CompletionItemProvi
return;
}
let noiseCheckPromise: Thenable<any> = Promise.resolve();
let isNoisePromise: Thenable<boolean> = 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.SymbolInformation[]>('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.SymbolInformation[] | undefined>('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!);

View file

@ -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('<div style="|"', [{ label: 'padding: ;' }]);
await testHtmlCompletionProvider(`<div style='|'`, [{ label: 'padding: ;' }]);
await testHtmlCompletionProvider(`<div style='p|'`, [{ label: 'padding: ;' }]);
await testHtmlCompletionProvider(`<div style='color: #0|'`, [{ label: '#000000' }]);
await testCompletionProvider('html', '<div style="|"', [{ label: 'padding: ;' }]);
await testCompletionProvider('html', `<div style='|'`, [{ label: 'padding: ;' }]);
await testCompletionProvider('html', `<div style='p|'`, [{ label: 'padding: ;' }]);
await testCompletionProvider('html', `<div style='color: #0|'`, [{ label: '#000000' }]);
});
// https://github.com/microsoft/vscode/issues/79766
test('#79766, correct region determination', async () => {
await testHtmlCompletionProvider(`<div style="color: #000">di|</div>`, [
await testCompletionProvider('html', `<div style="color: #000">di|</div>`, [
{ label: 'div', documentation: `<div>|</div>` }
]);
});
// 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: '<div a="b"></div>', documentation: '<div a="b">|</div>' }
]);
});
});
interface TestCompletionItem {
@ -49,11 +57,11 @@ interface TestCompletionItem {
documentation?: string;
}
function testHtmlCompletionProvider(contents: string, expectedItems: TestCompletionItem[]): Thenable<any> {
function testCompletionProvider(fileExtension: string, contents: string, expectedItems: TestCompletionItem[] | undefined): Thenable<boolean> {
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<any> {
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}`);

View file

@ -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 {

View file

@ -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",

View file

@ -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))) {

View file

@ -49,6 +49,15 @@
"scope": "resource",
"default": true,
"description": "%config.gitAuthentication%"
},
"github.gitProtocol": {
"type": "string",
"enum": [
"https",
"ssh"
],
"default": "https",
"description": "%config.gitProtocol%"
}
}
}

View file

@ -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)"
}

View file

@ -53,4 +53,3 @@ export function getOctokit(): Promise<Octokit> {
return _octokit;
}

View file

@ -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;

View file

@ -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);

View file

@ -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
};
}

View file

@ -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 '';

View file

@ -87,7 +87,7 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
const languageService = await host.getLanguageService(jsDocument);
const syntaxDiagnostics: ts.Diagnostic[] = languageService.getSyntacticDiagnostics(jsDocument.uri);
const semanticDiagnostics = languageService.getSemanticDiagnostics(jsDocument.uri);
return syntaxDiagnostics.concat(semanticDiagnostics).map((diag: ts.Diagnostic): Diagnostic => {
return syntaxDiagnostics.concat(semanticDiagnostics).filter(d => d.code !== 1108).map((diag: ts.Diagnostic): Diagnostic => {
return {
range: convertRange(jsDocument, diag),
severity: DiagnosticSeverity.Error,

View file

@ -120,7 +120,9 @@ suite('HTML Embedded Support', () => {
assertEmbeddedLanguageContent('<html><script>var i = 0;</script></html>', 'javascript', ' var i = 0; ');
assertEmbeddedLanguageContent('<script type="text/javascript">var i = 0;</script>', 'javascript', ' var i = 0; ');
assertEmbeddedLanguageContent('<div onKeyUp="foo()" onkeydown="bar()"/>', 'javascript', ' ()=>{foo()}; ()=>{bar()}; ');
assertEmbeddedLanguageContent('<div onKeyUp="foo()" onkeydown="bar()"/>', 'javascript', ' foo(); bar(); ');
assertEmbeddedLanguageContent('<div onKeyUp="return"/>', 'javascript', ' return; ');
});
});

View file

@ -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",

View file

@ -68,7 +68,7 @@ interface Settings {
};
}
interface JSONSchemaSettings {
export interface JSONSchemaSettings {
fileMatch?: string[];
url?: string;
schema?: any;

View file

@ -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<any>; 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<JSONSchemaSettings[]>('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<ShowSchemasItem>();
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<JSONLanguageStatus>): 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<ShowSchemasItem>();
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');

View file

@ -204,7 +204,8 @@ export const activate: ActivationFunction<void> = (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

View file

@ -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"
}
]
},

View file

@ -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.",

View file

@ -21,9 +21,11 @@ export class CommandManager {
this.commands.clear();
}
public register<T extends Command>(command: T): T {
public register<T extends Command>(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));
}
}
}

View file

@ -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));

View file

@ -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<MdLink>) {
for (const link of links) {
if (link.kind === 'definition') {
this._map.set(link.ref, link);
this._map.set(link.ref.text, link);
}
}
}

View file

@ -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<vscode.SnippetTextEdit | undefined> {
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);
}
});
}

View file

@ -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<string>('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));
}

View file

@ -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,

View file

@ -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<vscode.Location[] | undefined> {
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<MdReference[]> {
public async getAllReferencesAtPosition(document: SkinnyTextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<MdReference[]> {
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<MdReference[]> {
private async getReferencesToLinkAtPosition(document: SkinnyTextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<MdReference[]> {
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<MdReference[]> {
private async getReferencesToLink(sourceLink: MdLink, token: vscode.CancellationToken): Promise<MdReference[]> {
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<MdReference[]> {
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<MdReference> {
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<MdLink>, refToFind: string, from: { resource: vscode.Uri; range: vscode.Range }): Iterable<MdReference> {
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),
};
}

View file

@ -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<undefined | vscode.Range> {
const references = await this.referencesProvider.getAllReferences(document, position, token);
public async prepareRename(document: SkinnyTextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<undefined | { readonly range: vscode.Range; readonly placeholder: string }> {
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<vscode.WorkspaceEdit | undefined> {
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;
}
}

View file

@ -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 },
);
});
});

View file

@ -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');

View file

@ -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<undefined | { readonly range: vscode.Range; readonly placeholder: string }> {
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));
});
});

View file

@ -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;
}

View file

@ -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",

View file

@ -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 {

View file

@ -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);

View file

@ -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;
};

View file

@ -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",

View file

@ -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",

View file

@ -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"
},

View file

@ -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"
}
}

View file

@ -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==

View file

@ -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"

View file

@ -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<string | null>[] | 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<boolean>) {
let name: string | undefined = undefined;
let resultPromises: Promise<string | null>[] | 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;

View file

@ -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');

View file

@ -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));
});
});

View file

@ -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 },

View file

@ -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<Required<IEditorInlayHintsOptions
class EditorInlayHints extends BaseEditorOption<EditorOption.inlayHints, IEditorInlayHintsOptions, EditorInlayHintsOptions> {
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<EditorOption.inlayHints, IEditor
default: defaults.enabled,
description: nls.localize('inlayHints.enable', "Enables the inlay hints in the editor.")
},
'editor.inlayHints.toggle': {
type: 'string',
enum: ['show', 'hide'],
markdownEnumDescriptions: [
nls.localize('toogle.show', "Inlay hints are hidden by default and only show when holding `Ctrl+Alt`"),
nls.localize('toogle.hide', "Inlay hints are showing by default and hide when holding `Ctrl+Alt`"),
],
default: defaults.toggle,
markdownDescription: nls.localize('inlayHints.toggle', "Control if inlay hints temporarily show or hide when `Ctrl+Alt` is pressed and held.")
},
'editor.inlayHints.fontSize': {
type: 'number',
default: defaults.fontSize,
@ -2571,6 +2586,7 @@ class EditorInlayHints extends BaseEditorOption<EditorOption.inlayHints, IEditor
const input = _input as IEditorInlayHintsOptions;
return {
enabled: boolean(input.enabled, this.defaultValue.enabled),
toggle: stringSet<'show' | 'hide' | null>(input.toggle, this.defaultValue.toggle, ['show', 'hide']),
fontSize: EditorIntOption.clampedInt(input.fontSize, this.defaultValue.fontSize, 0, 100),
fontFamily: EditorStringOption.string(input.fontFamily, this.defaultValue.fontFamily),
displayStyle: stringSet<'standard' | 'compact'>(input.displayStyle, this.defaultValue.displayStyle, ['standard', 'compact'])

View file

@ -1968,3 +1968,23 @@ export enum ExternalUriOpenerPriority {
Default = 2,
Preferred = 3,
}
/**
* @internal
*/
export interface IDataTransferItem {
asString(): Thenable<string>;
value: any;
}
/**
* @internal
*/
export type IDataTransfer = Map<string, IDataTransferItem>;
/**
* @internal
*/
export interface DocumentOnDropEditProvider {
provideDocumentOnDropEdits(model: model.ITextModel, position: IPosition, dataTransfer: IDataTransfer, token: CancellationToken): ProviderResult<SnippetTextEdit>;
}

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { LanguageFeatureRegistry, NotebookInfoResolver } from 'vs/editor/common/languageFeatureRegistry';
import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider } from 'vs/editor/common/languages';
import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider } from 'vs/editor/common/languages';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
export const ILanguageFeaturesService = createDecorator<ILanguageFeaturesService>('ILanguageFeaturesService');
@ -67,6 +67,8 @@ export interface ILanguageFeaturesService {
readonly evaluatableExpressionProvider: LanguageFeatureRegistry<EvaluatableExpressionProvider>;
readonly documentOnDropEditProvider: LanguageFeatureRegistry<DocumentOnDropEditProvider>;
// --
setNotebookTypeResolver(resolver: NotebookInfoResolver | undefined): void;

View file

@ -5,7 +5,7 @@
import { URI } from 'vs/base/common/uri';
import { LanguageFeatureRegistry, NotebookInfo, NotebookInfoResolver } from 'vs/editor/common/languageFeatureRegistry';
import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider } from 'vs/editor/common/languages';
import { CodeActionProvider, CodeLensProvider, CompletionItemProvider, DeclarationProvider, DefinitionProvider, DocumentColorProvider, DocumentFormattingEditProvider, DocumentHighlightProvider, DocumentOnDropEditProvider, DocumentRangeFormattingEditProvider, DocumentRangeSemanticTokensProvider, DocumentSemanticTokensProvider, DocumentSymbolProvider, EvaluatableExpressionProvider, FoldingRangeProvider, HoverProvider, ImplementationProvider, InlayHintsProvider, InlineCompletionsProvider, InlineValuesProvider, LinkedEditingRangeProvider, LinkProvider, OnTypeFormattingEditProvider, ReferenceProvider, RenameProvider, SelectionRangeProvider, SignatureHelpProvider, TypeDefinitionProvider } from 'vs/editor/common/languages';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
@ -40,7 +40,7 @@ export class LanguageFeaturesService implements ILanguageFeaturesService {
readonly evaluatableExpressionProvider = new LanguageFeatureRegistry<EvaluatableExpressionProvider>(this._score.bind(this));
readonly documentRangeSemanticTokensProvider = new LanguageFeatureRegistry<DocumentRangeSemanticTokensProvider>(this._score.bind(this));
readonly documentSemanticTokensProvider = new LanguageFeatureRegistry<DocumentSemanticTokensProvider>(this._score.bind(this));
readonly documentOnDropEditProvider = new LanguageFeatureRegistry<DocumentOnDropEditProvider>(this._score.bind(this));
private _notebookTypeResolver?: NotebookInfoResolver;

View file

@ -773,6 +773,18 @@ function _applyRenderWhitespace(input: RenderLineInput, lineContent: string, len
isInWhitespace = lineIsEmptyOrWhitespace || charIndex > lastNonWhitespaceIndex;
}
if (isInWhitespace && tokenContainsRTL) {
// If the token contains RTL text, breaking it up into multiple line parts
// to render whitespace might affect the browser's bidi layout.
//
// We render whitespace in such tokens only if the whitespace
// is the leading or the trailing whitespace of the line,
// which doesn't affect the browser's bidi layout.
if (charIndex >= firstNonWhitespaceIndex && charIndex <= lastNonWhitespaceIndex) {
isInWhitespace = false;
}
}
if (wasInWhitespace) {
// was in whitespace token
if (!isInWhitespace || (!useMonospaceOptimizations && tmpIndent >= tabSize)) {
@ -959,7 +971,7 @@ function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): Render
sb.appendASCIIString('<span ');
if (partContainsRTL) {
sb.appendASCIIString('dir="auto" ');
sb.appendASCIIString('style="unicode-bidi:isolate" ');
}
sb.appendASCIIString('class="');
sb.appendASCIIString(partRendersWhitespaceWithWidth ? 'mtkz' : partType);

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { Selection } from 'vs/editor/common/core/selection';
import { ICommand } from 'vs/editor/common/editorCommon';
import { EncodedTokenizationResult, ColorId, IState, MetadataConsts, TokenizationRegistry } from 'vs/editor/common/languages';
@ -15,7 +15,6 @@ import { ILanguageService } from 'vs/editor/common/languages/language';
import { ILinePreflightData, IPreflightData, ISimpleModel, LineCommentCommand, Type } from 'vs/editor/contrib/comment/browser/lineCommentCommand';
import { testCommand } from 'vs/editor/test/browser/testCommand';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { MockMode } from 'vs/editor/test/common/mocks/mockMode';
import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService';
function createTestCommandHelper(commentsConfig: CommentRule, commandFactory: (accessor: ServicesAccessor, selection: Selection) => ICommand): (lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection) => void {
@ -1083,13 +1082,15 @@ suite('Editor Contrib - Line Comment in mixed modes', () => {
const OUTER_LANGUAGE_ID = 'outerMode';
const INNER_LANGUAGE_ID = 'innerMode';
class OuterMode extends MockMode {
class OuterMode extends Disposable {
private readonly languageId = OUTER_LANGUAGE_ID;
constructor(
commentsConfig: CommentRule,
@ILanguageService languageService: ILanguageService,
@ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService
) {
super(OUTER_LANGUAGE_ID);
super();
this._register(languageService.registerLanguage({ id: this.languageId }));
this._register(languageConfigurationService.register(this.languageId, {
comments: commentsConfig
}));
@ -1115,12 +1116,15 @@ suite('Editor Contrib - Line Comment in mixed modes', () => {
}
}
class InnerMode extends MockMode {
class InnerMode extends Disposable {
private readonly languageId = INNER_LANGUAGE_ID;
constructor(
commentsConfig: CommentRule,
@ILanguageService languageService: ILanguageService,
@ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService
) {
super(INNER_LANGUAGE_ID);
super();
this._register(languageService.registerLanguage({ id: this.languageId }));
this._register(languageConfigurationService.register(this.languageId, {
comments: commentsConfig
}));

View file

@ -472,7 +472,7 @@ export class FoldingController extends Disposable implements IEditorContribution
}
private onEditorMouseUp(e: IEditorMouseEvent): void {
const foldingModel = this.getFoldingModel();
const foldingModel = this.foldingModel;
if (!foldingModel || !this.mouseDownInfo || !e.target) {
return;
}
@ -495,47 +495,43 @@ export class FoldingController extends Disposable implements IEditorContribution
}
}
foldingModel.then(foldingModel => {
if (foldingModel) {
let region = foldingModel.getRegionAtLine(lineNumber);
if (region && region.startLineNumber === lineNumber) {
let isCollapsed = region.isCollapsed;
if (iconClicked || isCollapsed) {
let surrounding = e.event.altKey;
let toToggle = [];
if (surrounding) {
let filter = (otherRegion: FoldingRegion) => !otherRegion.containedBy(region!) && !region!.containedBy(otherRegion);
let toMaybeToggle = foldingModel.getRegionsInside(null, filter);
for (const r of toMaybeToggle) {
if (r.isCollapsed) {
toToggle.push(r);
}
}
// if any surrounding regions are folded, unfold those. Otherwise, fold all surrounding
if (toToggle.length === 0) {
toToggle = toMaybeToggle;
}
let region = foldingModel.getRegionAtLine(lineNumber);
if (region && region.startLineNumber === lineNumber) {
let isCollapsed = region.isCollapsed;
if (iconClicked || isCollapsed) {
let surrounding = e.event.altKey;
let toToggle = [];
if (surrounding) {
let filter = (otherRegion: FoldingRegion) => !otherRegion.containedBy(region!) && !region!.containedBy(otherRegion);
let toMaybeToggle = foldingModel.getRegionsInside(null, filter);
for (const r of toMaybeToggle) {
if (r.isCollapsed) {
toToggle.push(r);
}
else {
let recursive = e.event.middleButton || e.event.shiftKey;
if (recursive) {
for (const r of foldingModel.getRegionsInside(region)) {
if (r.isCollapsed === isCollapsed) {
toToggle.push(r);
}
}
}
// when recursive, first only collapse all children. If all are already folded or there are no children, also fold parent.
if (isCollapsed || !recursive || toToggle.length === 0) {
toToggle.push(region);
}
}
foldingModel.toggleCollapseState(toToggle);
this.reveal({ lineNumber, column: 1 });
}
// if any surrounding regions are folded, unfold those. Otherwise, fold all surrounding
if (toToggle.length === 0) {
toToggle = toMaybeToggle;
}
}
else {
let recursive = e.event.middleButton || e.event.shiftKey;
if (recursive) {
for (const r of foldingModel.getRegionsInside(region)) {
if (r.isCollapsed === isCollapsed) {
toToggle.push(r);
}
}
}
// when recursive, first only collapse all children. If all are already folded or there are no children, also fold parent.
if (isCollapsed || !recursive || toToggle.length === 0) {
toToggle.push(region);
}
}
foldingModel.toggleCollapseState(toToggle);
this.reveal({ lineNumber, column: 1 });
}
}).then(undefined, onUnexpectedError);
}
}
public reveal(position: IPosition): void {

View file

@ -3,6 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ModifierKeyEmitter } from 'vs/base/browser/dom';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { RunOnceScheduler } from 'vs/base/common/async';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
@ -19,7 +20,7 @@ import { EditOperation } from 'vs/editor/common/core/editOperation';
import { Range } from 'vs/editor/common/core/range';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import * as languages from 'vs/editor/common/languages';
import { IModelDeltaDecoration, InjectedTextCursorStops, ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model';
import { IModelDeltaDecoration, InjectedTextCursorStops, InjectedTextOptions, ITextModel, TrackedRangeStickiness } from 'vs/editor/common/model';
import { ModelDecorationInjectedTextOptions } from 'vs/editor/common/model/textModel';
import { IFeatureDebounceInformation, ILanguageFeatureDebounceService } from 'vs/editor/common/services/languageFeatureDebounce';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
@ -80,6 +81,11 @@ class ActiveInlayHintInfo {
constructor(readonly part: RenderedInlayHintLabelPart, readonly hasTriggerModifier: boolean) { }
}
const enum InlayHintRenderMode {
InjectedText,
TrackingOnly
}
// --- controller
export class InlayHintsController implements IEditorContribution {
@ -98,6 +104,7 @@ export class InlayHintsController implements IEditorContribution {
private readonly _decorationsMetadata = new Map<string, { item: InlayHintItem; classNameRef: IDisposable }>();
private readonly _ruleFactory = new DynamicCssRules(this._editor);
private _renderMode = InlayHintRenderMode.InjectedText;
private _activeInlayHintPart?: ActiveInlayHintInfo;
constructor(
@ -131,7 +138,8 @@ export class InlayHintsController implements IEditorContribution {
this._sessionDisposables.clear();
this._removeAllDecorations();
if (!this._editor.getOption(EditorOption.inlayHints).enabled) {
const options = this._editor.getOption(EditorOption.inlayHints);
if (!options.enabled) {
return;
}
@ -215,6 +223,26 @@ export class InlayHintsController implements IEditorContribution {
scheduler.schedule(delay);
}));
if (!options.toggle) {
this._renderMode = InlayHintRenderMode.InjectedText;
} else {
let defaultMode: InlayHintRenderMode;
let altMode: InlayHintRenderMode;
if (options.toggle === 'show') {
defaultMode = InlayHintRenderMode.TrackingOnly;
altMode = InlayHintRenderMode.InjectedText;
} else {
defaultMode = InlayHintRenderMode.InjectedText;
altMode = InlayHintRenderMode.TrackingOnly;
}
this._renderMode = defaultMode;
this._sessionDisposables.add(ModifierKeyEmitter.getInstance().event(e => {
this._renderMode = e.altKey && e.ctrlKey ? altMode : defaultMode;
const ranges = this._getHintsRanges();
this._updateHintsDecorators(ranges, ranges.map(this._getInlineHintsForRange, this).flat());
}));
}
// mouse gestures
this._sessionDisposables.add(this._installLinkGesture());
this._sessionDisposables.add(this._installDblClickGesture());
@ -253,16 +281,11 @@ export class InlayHintsController implements IEditorContribution {
const lineNumber = labelPart.item.hint.position.lineNumber;
const range = new Range(lineNumber, 1, lineNumber, model.getLineMaxColumn(lineNumber));
const lineHints = new Set<InlayHintItem>();
for (const data of this._decorationsMetadata.values()) {
if (range.containsRange(data.item.anchor.range)) {
lineHints.add(data.item);
}
}
this._updateHintsDecorators([range], Array.from(lineHints));
const lineHints = this._getInlineHintsForRange(range);
this._updateHintsDecorators([range], lineHints);
sessionStore.add(toDisposable(() => {
this._activeInlayHintPart = undefined;
this._updateHintsDecorators([range], Array.from(lineHints));
this._updateHintsDecorators([range], lineHints);
}));
}));
store.add(gesture.onCancel(() => sessionStore.clear()));
@ -282,6 +305,16 @@ export class InlayHintsController implements IEditorContribution {
return store;
}
private _getInlineHintsForRange(range: Range) {
const lineHints = new Set<InlayHintItem>();
for (const data of this._decorationsMetadata.values()) {
if (range.containsRange(data.item.anchor.range)) {
lineHints.add(data.item);
}
}
return Array.from(lineHints);
}
private _installDblClickGesture(): IDisposable {
return this._editor.onMouseUp(async e => {
if (e.event.detail !== 2) {
@ -376,6 +409,13 @@ export class InlayHintsController implements IEditorContribution {
// utils to collect/create injected text decorations
const newDecorationsData: { item: InlayHintItem; decoration: IModelDeltaDecoration; classNameRef: IDisposable }[] = [];
const addInjectedText = (item: InlayHintItem, ref: ClassNameReference, content: string, cursorStops: InjectedTextCursorStops, attachedData?: RenderedInlayHintLabelPart): void => {
const opts: InjectedTextOptions = {
content,
inlineClassNameAffectsLetterSpacing: true,
inlineClassName: ref.className,
cursorStops,
attachedData
};
newDecorationsData.push({
item,
classNameRef: ref,
@ -387,13 +427,7 @@ export class InlayHintsController implements IEditorContribution {
showIfCollapsed: item.anchor.range.isEmpty(), // "original" range is empty
collapseOnReplaceEdit: !item.anchor.range.isEmpty(),
stickiness: TrackedRangeStickiness.AlwaysGrowsWhenTypingAtEdges,
[item.anchor.direction]: {
content,
inlineClassNameAffectsLetterSpacing: true,
inlineClassName: ref.className,
cursorStops,
attachedData
}
[item.anchor.direction]: this._renderMode === InlayHintRenderMode.InjectedText ? opts : undefined
}
}
});

View file

@ -2,13 +2,15 @@
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable } from 'vs/base/common/lifecycle';
import { EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions';
import { Selection } from 'vs/editor/common/core/selection';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { IndentationRule } from 'vs/editor/common/languages/languageConfiguration';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { LanguageService } from 'vs/editor/common/services/languageService';
import { MoveLinesCommand } from 'vs/editor/contrib/linesOperations/browser/moveLinesCommand';
import { testCommand } from 'vs/editor/test/browser/testCommand';
import { MockMode } from 'vs/editor/test/common/mocks/mockMode';
import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService';
function testMoveLinesDownCommand(lines: string[], selection: Selection, expectedLines: string[], expectedSelection: Selection, languageConfigurationService = new TestLanguageConfigurationService()): void {
@ -259,13 +261,15 @@ suite('Editor Contrib - Move Lines Command', () => {
});
});
class IndentRulesMode extends MockMode {
private static readonly _id = 'moveLinesIndentMode';
class IndentRulesMode extends Disposable {
public readonly languageId = 'moveLinesIndentMode';
constructor(
indentationRules: IndentationRule,
@ILanguageService languageService: ILanguageService,
@ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService
) {
super(IndentRulesMode._id);
super();
this._register(languageService.registerLanguage({ id: this.languageId }));
this._register(languageConfigurationService.register(this.languageId, {
indentationRules: indentationRules
}));
@ -282,8 +286,9 @@ suite('Editor contrib - Move Lines Command honors Indentation Rules', () => {
// https://github.com/microsoft/vscode/issues/28552#issuecomment-307862797
test('first line indentation adjust to 0', () => {
const languageService = new LanguageService();
const languageConfigurationService = new TestLanguageConfigurationService();
const mode = new IndentRulesMode(indentRules, languageConfigurationService);
const mode = new IndentRulesMode(indentRules, languageService, languageConfigurationService);
testMoveLinesUpWithIndentCommand(
mode.languageId,
@ -303,12 +308,14 @@ suite('Editor contrib - Move Lines Command honors Indentation Rules', () => {
);
mode.dispose();
languageService.dispose();
});
// https://github.com/microsoft/vscode/issues/28552#issuecomment-307867717
test('move lines across block', () => {
const languageService = new LanguageService();
const languageConfigurationService = new TestLanguageConfigurationService();
const mode = new IndentRulesMode(indentRules, languageConfigurationService);
const mode = new IndentRulesMode(indentRules, languageService, languageConfigurationService);
testMoveLinesDownWithIndentCommand(
mode.languageId,
@ -334,6 +341,7 @@ suite('Editor contrib - Move Lines Command honors Indentation Rules', () => {
);
mode.dispose();
languageService.dispose();
});
@ -360,12 +368,14 @@ suite('Editor contrib - Move Lines Command honors Indentation Rules', () => {
});
});
class EnterRulesMode extends MockMode {
private static readonly _id = 'moveLinesEnterMode';
class EnterRulesMode extends Disposable {
public readonly languageId = 'moveLinesEnterMode';
constructor(
@ILanguageService languageService: ILanguageService,
@ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService
) {
super(EnterRulesMode._id);
super();
this._register(languageService.registerLanguage({ id: this.languageId }));
this._register(languageConfigurationService.register(this.languageId, {
indentationRules: {
decreaseIndentPattern: /^\s*\[$/,
@ -381,8 +391,9 @@ class EnterRulesMode extends MockMode {
suite('Editor - contrib - Move Lines Command honors onEnter Rules', () => {
test('issue #54829. move block across block', () => {
const languageService = new LanguageService();
const languageConfigurationService = new TestLanguageConfigurationService();
const mode = new EnterRulesMode(languageConfigurationService);
const mode = new EnterRulesMode(languageService, languageConfigurationService);
testMoveLinesDownWithIndentCommand(
mode.languageId,
@ -413,5 +424,6 @@ suite('Editor - contrib - Move Lines Command honors onEnter Rules', () => {
);
mode.dispose();
languageService.dispose();
});
});

View file

@ -1095,7 +1095,7 @@ export class FocusNextCursor extends EditorAction {
constructor() {
super({
id: 'editor.action.focusNextCursor',
label: nls.localize('mutlicursor.focusNextCursor', "Focus next cursor"),
label: nls.localize('mutlicursor.focusNextCursor', "Focus Next Cursor"),
description: {
description: nls.localize('mutlicursor.focusNextCursor.description', "Focuses the next cursor"),
args: [],
@ -1134,7 +1134,7 @@ export class FocusPreviousCursor extends EditorAction {
constructor() {
super({
id: 'editor.action.focusPreviousCursor',
label: nls.localize('mutlicursor.focusPreviousCursor', "Focus previous cursor"),
label: nls.localize('mutlicursor.focusPreviousCursor', "Focus Previous Cursor"),
description: {
description: nls.localize('mutlicursor.focusPreviousCursor.description', "Focuses the previous cursor"),
args: [],

View file

@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Event } from 'vs/base/common/event';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { Position } from 'vs/editor/common/core/position';
@ -15,10 +16,14 @@ import { BracketSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/bro
import { provideSelectionRanges } from 'vs/editor/contrib/smartSelect/browser/smartSelect';
import { WordSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/browser/wordSelections';
import { createModelServices } from 'vs/editor/test/common/testTextModel';
import { StaticLanguageSelector } from 'vs/editor/test/common/mocks/mockMode';
import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/javascriptOnEnterRules';
import { LanguageFeatureRegistry } from 'vs/editor/common/languageFeatureRegistry';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { ILanguageSelection, ILanguageService } from 'vs/editor/common/languages/language';
class StaticLanguageSelector implements ILanguageSelection {
readonly onDidChange: Event<string> = Event.None;
constructor(public readonly languageId: string) { }
}
suite('SmartSelect', () => {

View file

@ -5,26 +5,41 @@
import { CancellationToken } from 'vs/base/common/cancellation';
import { FuzzyScore } from 'vs/base/common/filters';
import { Iterable } from 'vs/base/common/iterator';
import { IDisposable, RefCountedDisposable } from 'vs/base/common/lifecycle';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { EditorOption, FindComputedEditorOptionValueById } from 'vs/editor/common/config/editorOptions';
import { Position } from 'vs/editor/common/core/position';
import { Range } from 'vs/editor/common/core/range';
import { ISingleEditOperation } from 'vs/editor/common/core/editOperation';
import { IPosition, Position } from 'vs/editor/common/core/position';
import { IRange, Range } from 'vs/editor/common/core/range';
import { IWordAtPosition } from 'vs/editor/common/core/wordHelper';
import { IEditorContribution } from 'vs/editor/common/editorCommon';
import { InlineCompletion, InlineCompletionContext, InlineCompletions, InlineCompletionsProvider } from 'vs/editor/common/languages';
import { Command, CompletionItemProvider, CompletionTriggerKind, InlineCompletion, InlineCompletionContext, InlineCompletions, InlineCompletionsProvider } from 'vs/editor/common/languages';
import { ITextModel } from 'vs/editor/common/model';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
import { CompletionItemInsertTextRule } from 'vs/editor/common/standalone/standaloneEnums';
import { CompletionModel, LineContext } from 'vs/editor/contrib/suggest/browser/completionModel';
import { CompletionItemModel, provideSuggestionItems, QuickSuggestionsOptions } from 'vs/editor/contrib/suggest/browser/suggest';
import { CompletionItem, CompletionItemModel, CompletionOptions, provideSuggestionItems, QuickSuggestionsOptions } from 'vs/editor/contrib/suggest/browser/suggest';
import { ISuggestMemoryService } from 'vs/editor/contrib/suggest/browser/suggestMemory';
import { WordDistance } from 'vs/editor/contrib/suggest/browser/wordDistance';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
class InlineCompletionResults extends RefCountedDisposable implements InlineCompletions {
class SuggestInlineCompletion implements InlineCompletion {
constructor(
readonly range: IRange,
readonly insertText: string | { snippet: string },
readonly filterText: string,
readonly additionalTextEdits: ISingleEditOperation[] | undefined,
readonly command: Command | undefined,
readonly completion: CompletionItem,
) { }
}
class InlineCompletionResults extends RefCountedDisposable implements InlineCompletions<SuggestInlineCompletion> {
constructor(
readonly model: ITextModel,
@ -32,6 +47,7 @@ class InlineCompletionResults extends RefCountedDisposable implements InlineComp
readonly word: IWordAtPosition,
readonly completionModel: CompletionModel,
completions: CompletionItemModel,
@ISuggestMemoryService private readonly _suggestMemoryService: ISuggestMemoryService,
) {
super(completions.disposable);
}
@ -42,9 +58,17 @@ class InlineCompletionResults extends RefCountedDisposable implements InlineComp
&& this.completionModel.incomplete.size === 0; // no incomplete results
}
get items(): InlineCompletion[] {
const result: InlineCompletion[] = [];
for (const item of this.completionModel.items) {
get items(): SuggestInlineCompletion[] {
const result: SuggestInlineCompletion[] = [];
// Split items by preselected index. This ensures the memory-selected item shows first and that better/worst
// ranked items are before/after
const { items } = this.completionModel;
const selectedIndex = this._suggestMemoryService.select(this.model, { lineNumber: this.line, column: this.word.endColumn + this.completionModel.lineContext.characterCountDelta }, items);
const first = Iterable.slice(items, selectedIndex);
const second = Iterable.slice(items, 0, selectedIndex);
for (const item of Iterable.concat(first, second)) {
if (item.score === FuzzyScore.Default) {
// skip items that have no overlap
@ -59,13 +83,14 @@ class InlineCompletionResults extends RefCountedDisposable implements InlineComp
? { snippet: item.completion.insertText }
: item.completion.insertText;
result.push({
result.push(new SuggestInlineCompletion(
range,
filterText: item.filterTextLow ?? item.labelLow,
insertText,
command: item.completion.command,
additionalTextEdits: item.completion.additionalTextEdits
});
item.filterTextLow ?? item.labelLow,
item.completion.additionalTextEdits,
item.completion.command,
item
));
}
return result;
}
@ -80,6 +105,7 @@ class SuggestInlineCompletions implements InlineCompletionsProvider<InlineComple
private readonly _getEditorOption: <T extends EditorOption>(id: T, model: ITextModel) => FindComputedEditorOptionValueById<T>,
@ILanguageFeaturesService private readonly _languageFeatureService: ILanguageFeaturesService,
@IClipboardService private readonly _clipboardService: IClipboardService,
@ISuggestMemoryService private readonly _suggestMemoryService: ISuggestMemoryService,
) { }
async provideInlineCompletions(model: ITextModel, position: Position, context: InlineCompletionContext, token: CancellationToken): Promise<InlineCompletionResults | undefined> {
@ -102,15 +128,24 @@ class SuggestInlineCompletions implements InlineCompletionsProvider<InlineComple
return undefined;
}
const wordInfo = model.getWordAtPosition(position);
if (!wordInfo || wordInfo.word.length === 0 || wordInfo.endColumn !== position.column) {
// not without true prefix, not inside word
// We consider non-empty leading words and trigger characters. The latter only
// when no word is being typed (word characters superseed trigger characters)
let wordInfo = model.getWordUntilPosition(position);
let triggerCharacterInfo: { ch: string; providers: Set<CompletionItemProvider> } | undefined;
if (!wordInfo.word) {
triggerCharacterInfo = this._getTriggerCharacterInfo(model, position);
}
if (!wordInfo.word && !triggerCharacterInfo) {
// not at word, not a trigger character
return;
}
let result: InlineCompletionResults;
const leadingLineContents = model.getValueInRange(new Range(position.lineNumber, 1, position.lineNumber, position.column));
if (this._lastResult?.canBeReused(model, position.lineNumber, wordInfo)) {
if (!triggerCharacterInfo && this._lastResult?.canBeReused(model, position.lineNumber, wordInfo)) {
// reuse a previous result iff possible, only a refilter is needed
// TODO@jrieken this can be improved further and only incomplete results can be updated
// console.log(`REUSE with ${wordInfo.word}`);
@ -124,8 +159,8 @@ class SuggestInlineCompletions implements InlineCompletionsProvider<InlineComple
const completions = await provideSuggestionItems(
this._languageFeatureService.completionProvider,
model, position,
undefined,
undefined,
new CompletionOptions(undefined, undefined, triggerCharacterInfo?.providers),
triggerCharacterInfo && { triggerKind: CompletionTriggerKind.TriggerCharacter, triggerCharacter: triggerCharacterInfo.ch },
token
);
@ -143,16 +178,34 @@ class SuggestInlineCompletions implements InlineCompletionsProvider<InlineComple
this._getEditorOption(EditorOption.snippetSuggestions, model),
clipboardText
);
result = new InlineCompletionResults(model, position.lineNumber, wordInfo, completionModel, completions);
result = new InlineCompletionResults(model, position.lineNumber, wordInfo, completionModel, completions, this._suggestMemoryService);
}
this._lastResult = result;
return result;
}
handleItemDidShow(_completions: InlineCompletionResults, item: SuggestInlineCompletion): void {
item.completion.resolve(CancellationToken.None);
}
freeInlineCompletions(result: InlineCompletionResults): void {
result.release();
}
private _getTriggerCharacterInfo(model: ITextModel, position: IPosition) {
const ch = model.getValueInRange(Range.fromPositions({ lineNumber: position.lineNumber, column: position.column - 1 }, position));
const providers = new Set<CompletionItemProvider>();
for (const provider of this._languageFeatureService.completionProvider.all(model)) {
if (provider.triggerCharacters?.includes(ch)) {
providers.add(provider);
}
}
if (providers.size === 0) {
return undefined;
}
return { providers, ch };
}
}
class EditorContribution implements IEditorContribution {

View file

@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { Event } from 'vs/base/common/event';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { mock } from 'vs/base/test/common/mock';
import { CoreEditingCommands } from 'vs/editor/browser/coreCommands';
@ -26,7 +26,6 @@ import { LineContext, SuggestModel } from 'vs/editor/contrib/suggest/browser/sug
import { ISelectedSuggestion } from 'vs/editor/contrib/suggest/browser/suggestWidget';
import { createTestCodeEditor, ITestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
import { createModelServices, createTextModel, instantiateTextModel } from 'vs/editor/test/common/testTextModel';
import { MockMode } from 'vs/editor/test/common/mocks/mockMode';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { MockKeybindingService } from 'vs/platform/keybinding/test/common/mockKeybindingService';
@ -68,12 +67,14 @@ suite('SuggestModel - Context', function () {
const OUTER_LANGUAGE_ID = 'outerMode';
const INNER_LANGUAGE_ID = 'innerMode';
class OuterMode extends MockMode {
class OuterMode extends Disposable {
public readonly languageId = OUTER_LANGUAGE_ID;
constructor(
@ILanguageService languageService: ILanguageService,
@ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService,
) {
super(OUTER_LANGUAGE_ID);
super();
this._register(languageService.registerLanguage({ id: this.languageId }));
this._register(languageConfigurationService.register(this.languageId, {}));
this._register(TokenizationRegistry.register(this.languageId, {
@ -102,11 +103,14 @@ suite('SuggestModel - Context', function () {
}
}
class InnerMode extends MockMode {
class InnerMode extends Disposable {
public readonly languageId = INNER_LANGUAGE_ID;
constructor(
@ILanguageService languageService: ILanguageService,
@ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService
) {
super(INNER_LANGUAGE_ID);
super();
this._register(languageService.registerLanguage({ id: this.languageId }));
this._register(languageConfigurationService.register(this.languageId, {}));
}
}

View file

@ -28,6 +28,8 @@ import { LanguageSelector } from 'vs/editor/common/languageSelector';
* Register information about a new language.
*/
export function register(language: ILanguageExtensionPoint): void {
// Intentionally using the `ModesRegistry` here to avoid
// instantiating services too quickly in the standalone editor.
ModesRegistry.registerLanguage(language);
}

View file

@ -10,11 +10,13 @@ import { Selection } from 'vs/editor/common/core/selection';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { getEditOperation, testCommand } from 'vs/editor/test/browser/testCommand';
import { withEditorModel } from 'vs/editor/test/common/testTextModel';
import { MockMode } from 'vs/editor/test/common/mocks/mockMode';
import { javascriptOnEnterRules } from 'vs/editor/test/common/modes/supports/javascriptOnEnterRules';
import { EditorAutoIndentStrategy } from 'vs/editor/common/config/editorOptions';
import { ISingleEditOperation } from 'vs/editor/common/core/editOperation';
import { TestLanguageConfigurationService } from 'vs/editor/test/common/modes/testLanguageConfigurationService';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
/**
* Create single edit operation
@ -27,14 +29,17 @@ export function createSingleEditOp(text: string, positionLineNumber: number, pos
};
}
class DocBlockCommentMode extends MockMode {
class DocBlockCommentMode extends Disposable {
private static readonly _id = 'commentMode';
public static languageId = 'commentMode';
public readonly languageId = DocBlockCommentMode.languageId;
constructor(
@ILanguageService languageService: ILanguageService,
@ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService
) {
super(DocBlockCommentMode._id);
super();
this._register(languageService.registerLanguage({ id: this.languageId }));
this._register(languageConfigurationService.register(this.languageId, {
brackets: [
['(', ')'],
@ -47,7 +52,7 @@ class DocBlockCommentMode extends MockMode {
}
}
function testShiftCommand(lines: string[], languageId: string | null, useTabStops: boolean, selection: Selection, expectedLines: string[], expectedSelection: Selection, languageConfigurationService = new TestLanguageConfigurationService()): void {
function testShiftCommand(lines: string[], languageId: string | null, useTabStops: boolean, selection: Selection, expectedLines: string[], expectedSelection: Selection, prepare?: (accessor: ServicesAccessor, disposables: DisposableStore) => void): void {
testCommand(lines, languageId, selection, (accessor, sel) => new ShiftCommand(sel, {
isUnshift: false,
tabSize: 4,
@ -55,10 +60,10 @@ function testShiftCommand(lines: string[], languageId: string | null, useTabStop
insertSpaces: false,
useTabStops: useTabStops,
autoIndent: EditorAutoIndentStrategy.Full,
}, languageConfigurationService), expectedLines, expectedSelection);
}, accessor.get(ILanguageConfigurationService)), expectedLines, expectedSelection, undefined, prepare);
}
function testUnshiftCommand(lines: string[], languageId: string | null, useTabStops: boolean, selection: Selection, expectedLines: string[], expectedSelection: Selection, languageConfigurationService = new TestLanguageConfigurationService()): void {
function testUnshiftCommand(lines: string[], languageId: string | null, useTabStops: boolean, selection: Selection, expectedLines: string[], expectedSelection: Selection, prepare?: (accessor: ServicesAccessor, disposables: DisposableStore) => void): void {
testCommand(lines, languageId, selection, (accessor, sel) => new ShiftCommand(sel, {
isUnshift: true,
tabSize: 4,
@ -66,14 +71,13 @@ function testUnshiftCommand(lines: string[], languageId: string | null, useTabSt
insertSpaces: false,
useTabStops: useTabStops,
autoIndent: EditorAutoIndentStrategy.Full,
}, languageConfigurationService), expectedLines, expectedSelection);
}, accessor.get(ILanguageConfigurationService)), expectedLines, expectedSelection, undefined, prepare);
}
function withDockBlockCommentMode(callback: (mode: DocBlockCommentMode, languageConfigurationService: TestLanguageConfigurationService) => void): void {
const languageConfigurationService = new TestLanguageConfigurationService();
let mode = new DocBlockCommentMode(languageConfigurationService);
callback(mode, languageConfigurationService);
mode.dispose();
function prepareDocBlockCommentLanguage(accessor: ServicesAccessor, disposables: DisposableStore) {
const languageConfigurationService = accessor.get(ILanguageConfigurationService);
const languageService = accessor.get(ILanguageService);
disposables.add(new DocBlockCommentMode(languageService, languageConfigurationService));
}
suite('Editor Commands - ShiftCommand', () => {
@ -558,105 +562,99 @@ suite('Editor Commands - ShiftCommand', () => {
});
test('issue #348: indenting around doc block comments', () => {
withDockBlockCommentMode((mode, languageConfigurationService) => {
testShiftCommand(
[
'',
'/**',
' * a doc comment',
' */',
'function hello() {}'
],
DocBlockCommentMode.languageId,
true,
new Selection(1, 1, 5, 20),
[
'',
'\t/**',
'\t * a doc comment',
'\t */',
'\tfunction hello() {}'
],
new Selection(1, 1, 5, 21),
prepareDocBlockCommentLanguage
);
testShiftCommand(
[
'',
'/**',
' * a doc comment',
' */',
'function hello() {}'
],
mode.languageId,
true,
new Selection(1, 1, 5, 20),
[
'',
'\t/**',
'\t * a doc comment',
'\t */',
'\tfunction hello() {}'
],
new Selection(1, 1, 5, 21),
languageConfigurationService
);
testUnshiftCommand(
[
'',
'/**',
' * a doc comment',
' */',
'function hello() {}'
],
DocBlockCommentMode.languageId,
true,
new Selection(1, 1, 5, 20),
[
'',
'/**',
' * a doc comment',
' */',
'function hello() {}'
],
new Selection(1, 1, 5, 20),
prepareDocBlockCommentLanguage
);
testUnshiftCommand(
[
'',
'/**',
' * a doc comment',
' */',
'function hello() {}'
],
mode.languageId,
true,
new Selection(1, 1, 5, 20),
[
'',
'/**',
' * a doc comment',
' */',
'function hello() {}'
],
new Selection(1, 1, 5, 20),
languageConfigurationService
);
testUnshiftCommand(
[
'\t',
'\t/**',
'\t * a doc comment',
'\t */',
'\tfunction hello() {}'
],
mode.languageId,
true,
new Selection(1, 1, 5, 21),
[
'',
'/**',
' * a doc comment',
' */',
'function hello() {}'
],
new Selection(1, 1, 5, 20),
languageConfigurationService
);
});
testUnshiftCommand(
[
'\t',
'\t/**',
'\t * a doc comment',
'\t */',
'\tfunction hello() {}'
],
DocBlockCommentMode.languageId,
true,
new Selection(1, 1, 5, 21),
[
'',
'/**',
' * a doc comment',
' */',
'function hello() {}'
],
new Selection(1, 1, 5, 20),
prepareDocBlockCommentLanguage
);
});
test('issue #1609: Wrong indentation of block comments', () => {
withDockBlockCommentMode((mode, languageConfigurationService) => {
testShiftCommand(
[
'',
'/**',
' * test',
' *',
' * @type {number}',
' */',
'var foo = 0;'
],
mode.languageId,
true,
new Selection(1, 1, 7, 13),
[
'',
'\t/**',
'\t * test',
'\t *',
'\t * @type {number}',
'\t */',
'\tvar foo = 0;'
],
new Selection(1, 1, 7, 14),
languageConfigurationService
);
});
testShiftCommand(
[
'',
'/**',
' * test',
' *',
' * @type {number}',
' */',
'var foo = 0;'
],
DocBlockCommentMode.languageId,
true,
new Selection(1, 1, 7, 13),
[
'',
'\t/**',
'\t * test',
'\t *',
'\t * @type {number}',
'\t */',
'\tvar foo = 0;'
],
new Selection(1, 1, 7, 14),
prepareDocBlockCommentLanguage
);
});
test('issue #1620: a) Line indent doesn\'t handle leading whitespace properly', () => {

View file

@ -1,23 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { ModesRegistry } from 'vs/editor/common/languages/modesRegistry';
import { ILanguageSelection } from 'vs/editor/common/languages/language';
export class MockMode extends Disposable {
constructor(
public readonly languageId: string
) {
super();
this._register(ModesRegistry.registerLanguage({ id: languageId }));
}
}
export class StaticLanguageSelector implements ILanguageSelection {
readonly onDidChange: Event<string> = Event.None;
constructor(public readonly languageId: string) { }
}

View file

@ -13,7 +13,6 @@ import { InternalModelContentChangeEvent, ModelRawContentChangedEvent, ModelRawF
import { EncodedTokenizationResult, IState, MetadataConsts, TokenizationRegistry } from 'vs/editor/common/languages';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { NullState } from 'vs/editor/common/languages/nullTokenize';
import { MockMode } from 'vs/editor/test/common/mocks/mockMode';
import { createModelServices, createTextModel, instantiateTextModel } from 'vs/editor/test/common/testTextModel';
import { ILanguageService } from 'vs/editor/common/languages/language';
@ -381,16 +380,19 @@ suite('Editor Model - Words', () => {
const OUTER_LANGUAGE_ID = 'outerMode';
const INNER_LANGUAGE_ID = 'innerMode';
class OuterMode extends MockMode {
class OuterMode extends Disposable {
public readonly languageId = OUTER_LANGUAGE_ID;
constructor(
@ILanguageService languageService: ILanguageService,
@ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService
) {
super(OUTER_LANGUAGE_ID);
const languageIdCodec = languageService.languageIdCodec;
super();
this._register(languageService.registerLanguage({ id: this.languageId }));
this._register(languageConfigurationService.register(this.languageId, {}));
const languageIdCodec = languageService.languageIdCodec;
this._register(TokenizationRegistry.register(this.languageId, {
getInitialState: (): IState => NullState,
tokenize: undefined!,
@ -417,11 +419,16 @@ suite('Editor Model - Words', () => {
}
}
class InnerMode extends MockMode {
class InnerMode extends Disposable {
public readonly languageId = INNER_LANGUAGE_ID;
constructor(
@ILanguageService languageService: ILanguageService,
@ILanguageConfigurationService languageConfigurationService: ILanguageConfigurationService
) {
super(INNER_LANGUAGE_ID);
super();
this._register(languageService.registerLanguage({ id: this.languageId }));
this._register(languageConfigurationService.register(this.languageId, {}));
}
}

View file

@ -4,20 +4,36 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { EncodedTokenizationResult, ColorId, FontStyle, IState, MetadataConsts, TokenizationRegistry } from 'vs/editor/common/languages';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { tokenizeLineToHTML, _tokenizeToString } from 'vs/editor/common/languages/textToHtmlTokenizer';
import { LanguageIdCodec } from 'vs/editor/common/services/languagesRegistry';
import { TestLineToken, TestLineTokens } from 'vs/editor/test/common/core/testLineToken';
import { MockMode } from 'vs/editor/test/common/mocks/mockMode';
import { createModelServices } from 'vs/editor/test/common/testTextModel';
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock';
suite('Editor Modes - textToHtmlTokenizer', () => {
let disposables: DisposableStore;
let instantiationService: TestInstantiationService;
setup(() => {
disposables = new DisposableStore();
instantiationService = createModelServices(disposables);
});
teardown(() => {
disposables.dispose();
});
function toStr(pieces: { className: string; text: string }[]): string {
let resultArr = pieces.map((t) => `<span class="${t.className}">${t.text}</span>`);
return resultArr.join('');
}
test('TextToHtmlTokenizer 1', () => {
let mode = new Mode();
const mode = disposables.add(instantiationService.createInstance(Mode));
let support = TokenizationRegistry.get(mode.languageId)!;
let actual = _tokenizeToString('.abc..def...gh', new LanguageIdCodec(), support);
@ -32,12 +48,10 @@ suite('Editor Modes - textToHtmlTokenizer', () => {
let expectedStr = `<div class="monaco-tokenized-source">${toStr(expected)}</div>`;
assert.strictEqual(actual, expectedStr);
mode.dispose();
});
test('TextToHtmlTokenizer 2', () => {
let mode = new Mode();
const mode = disposables.add(instantiationService.createInstance(Mode));
let support = TokenizationRegistry.get(mode.languageId)!;
let actual = _tokenizeToString('.abc..def...gh\n.abc..def...gh', new LanguageIdCodec(), support);
@ -62,8 +76,6 @@ suite('Editor Modes - textToHtmlTokenizer', () => {
let expectedStr = `<div class="monaco-tokenized-source">${expectedStr1}<br/>${expectedStr2}</div>`;
assert.strictEqual(actual, expectedStr);
mode.dispose();
});
test('tokenizeLineToHTML', () => {
@ -278,12 +290,15 @@ suite('Editor Modes - textToHtmlTokenizer', () => {
});
class Mode extends MockMode {
class Mode extends Disposable {
private static readonly _id = 'textToHtmlTokenizerMode';
private readonly languageId = 'textToHtmlTokenizerMode';
constructor() {
super(Mode._id);
constructor(
@ILanguageService languageService: ILanguageService
) {
super();
this._register(languageService.registerLanguage({ id: this.languageId }));
this._register(TokenizationRegistry.register(this.languageId, {
getInitialState: (): IState => null!,
tokenize: undefined!,

View file

@ -465,8 +465,8 @@ suite('viewLineRenderer.renderLine', () => {
const expectedOutput = [
'<span class="mtk6">var</span>',
'<span dir="auto" class="mtk1">\u00a0קודמות\u00a0=\u00a0</span>',
'<span dir="auto" class="mtk20">"מיותר\u00a0קודמות\u00a0צ\'ט\u00a0של,\u00a0אם\u00a0לשון\u00a0העברית\u00a0שינויים\u00a0ויש,\u00a0אם"</span>',
'<span style="unicode-bidi:isolate" class="mtk1">\u00a0קודמות\u00a0=\u00a0</span>',
'<span style="unicode-bidi:isolate" class="mtk20">"מיותר\u00a0קודמות\u00a0צ\'ט\u00a0של,\u00a0אם\u00a0לשון\u00a0העברית\u00a0שינויים\u00a0ויש,\u00a0אם"</span>',
'<span class="mtk1">;</span>'
].join('');
@ -519,9 +519,9 @@ suite('viewLineRenderer.renderLine', () => {
'<span class="mtk4">\u00a0</span>',
'<span class="mtk5">value</span>',
'<span class="mtk4">=</span>',
'<span dir="auto" class="mtk6">"العربية"</span>',
'<span style="unicode-bidi:isolate" class="mtk6">"العربية"</span>',
'<span class="mtk2">&gt;</span>',
'<span dir="auto" class="mtk4">العربية</span>',
'<span style="unicode-bidi:isolate" class="mtk4">العربية</span>',
'<span class="mtk2">&lt;/</span>',
'<span class="mtk3">option</span>',
'<span class="mtk2">&gt;</span>',
@ -553,6 +553,52 @@ suite('viewLineRenderer.renderLine', () => {
assert.strictEqual(_actual.containsRTL, true);
});
test('issue #99589: Rendering whitespace influences bidi layout', () => {
const lineText = ' [\"🖨️ چاپ فاکتور\",\"🎨 تنظیمات\"]';
const lineParts = createViewLineTokens([
createPart(5, 2),
createPart(21, 3),
createPart(22, 2),
createPart(34, 3),
createPart(35, 2),
]);
const expectedOutput = [
'<span class="mtkw">\u00b7\u00b7\u00b7\u00b7</span>',
'<span class="mtk2">[</span>',
'<span style="unicode-bidi:isolate" class="mtk3">"🖨️\u00a0چاپ\u00a0فاکتور"</span>',
'<span class="mtk2">,</span>',
'<span style="unicode-bidi:isolate" class="mtk3">"🎨\u00a0تنظیمات"</span>',
'<span class="mtk2">]</span>'
].join('');
const _actual = renderViewLine(new RenderLineInput(
true,
true,
lineText,
false,
false,
true,
0,
lineParts,
[],
4,
0,
10,
10,
10,
-1,
'all',
false,
false,
null
));
assert.strictEqual(_actual.html, '<span dir="ltr">' + expectedOutput + '</span>');
assert.strictEqual(_actual.containsRTL, true);
});
test('issue #6885: Splits large tokens', () => {
// 1 1 1
// 1 2 3 4 5 6 7 8 9 0 1 2
@ -738,7 +784,7 @@ suite('viewLineRenderer.renderLine', () => {
const lineText = 'את גרמנית בהתייחסות שמו, שנתי המשפט אל חפש, אם כתב אחרים ולחבר. של התוכן אודות בויקיפדיה כלל, של עזרה כימיה היא. על עמוד יוצרים מיתולוגיה סדר, אם שכל שתפו לעברית שינויים, אם שאלות אנגלית עזה. שמות בקלות מה סדר.';
const lineParts = createViewLineTokens([createPart(lineText.length, 1)]);
const expectedOutput = [
'<span dir="auto" class="mtk1">את\u00a0גרמנית\u00a0בהתייחסות\u00a0שמו,\u00a0שנתי\u00a0המשפט\u00a0אל\u00a0חפש,\u00a0אם\u00a0כתב\u00a0אחרים\u00a0ולחבר.\u00a0של\u00a0התוכן\u00a0אודות\u00a0בויקיפדיה\u00a0כלל,\u00a0של\u00a0עזרה\u00a0כימיה\u00a0היא.\u00a0על\u00a0עמוד\u00a0יוצרים\u00a0מיתולוגיה\u00a0סדר,\u00a0אם\u00a0שכל\u00a0שתפו\u00a0לעברית\u00a0שינויים,\u00a0אם\u00a0שאלות\u00a0אנגלית\u00a0עזה.\u00a0שמות\u00a0בקלות\u00a0מה\u00a0סדר.</span>'
'<span style="unicode-bidi:isolate" class="mtk1">את\u00a0גרמנית\u00a0בהתייחסות\u00a0שמו,\u00a0שנתי\u00a0המשפט\u00a0אל\u00a0חפש,\u00a0אם\u00a0כתב\u00a0אחרים\u00a0ולחבר.\u00a0של\u00a0התוכן\u00a0אודות\u00a0בויקיפדיה\u00a0כלל,\u00a0של\u00a0עזרה\u00a0כימיה\u00a0היא.\u00a0על\u00a0עמוד\u00a0יוצרים\u00a0מיתולוגיה\u00a0סדר,\u00a0אם\u00a0שכל\u00a0שתפו\u00a0לעברית\u00a0שינויים,\u00a0אם\u00a0שאלות\u00a0אנגלית\u00a0עזה.\u00a0שמות\u00a0בקלות\u00a0מה\u00a0סדר.</span>'
];
const actual = renderViewLine(new RenderLineInput(
false,

4
src/vs/monaco.d.ts vendored
View file

@ -3722,6 +3722,10 @@ declare namespace monaco.editor {
* Defaults to true.
*/
enabled?: boolean;
/**
*
*/
toggle?: 'show' | 'hide' | null;
/**
* Font size of inline hints.
* Default to 90% of the editor font size.

View file

@ -1,205 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { getClientArea, getTopLeftOffset } from 'vs/base/browser/dom';
import { coalesce } from 'vs/base/common/arrays';
import { language, locale } from 'vs/base/common/platform';
import { IElement, ILocaleInfo, ILocalizedStrings, IWindowDriver } from 'vs/platform/driver/common/driver';
import localizedStrings from 'vs/platform/localizations/common/localizedStrings';
export abstract class BaseWindowDriver implements IWindowDriver {
abstract click(selector: string, xoffset?: number, yoffset?: number): Promise<void>;
abstract doubleClick(selector: string): Promise<void>;
async setValue(selector: string, text: string): Promise<void> {
const element = document.querySelector(selector);
if (!element) {
return Promise.reject(new Error(`Element not found: ${selector}`));
}
const inputElement = element as HTMLInputElement;
inputElement.value = text;
const event = new Event('input', { bubbles: true, cancelable: true });
inputElement.dispatchEvent(event);
}
async getTitle(): Promise<string> {
return document.title;
}
async isActiveElement(selector: string): Promise<boolean> {
const element = document.querySelector(selector);
if (element !== document.activeElement) {
const chain: string[] = [];
let el = document.activeElement;
while (el) {
const tagName = el.tagName;
const id = el.id ? `#${el.id}` : '';
const classes = coalesce(el.className.split(/\s+/g).map(c => c.trim())).map(c => `.${c}`).join('');
chain.unshift(`${tagName}${id}${classes}`);
el = el.parentElement;
}
throw new Error(`Active element not found. Current active element is '${chain.join(' > ')}'. Looking for ${selector}`);
}
return true;
}
async getElements(selector: string, recursive: boolean): Promise<IElement[]> {
const query = document.querySelectorAll(selector);
const result: IElement[] = [];
for (let i = 0; i < query.length; i++) {
const element = query.item(i);
result.push(this.serializeElement(element, recursive));
}
return result;
}
private serializeElement(element: Element, recursive: boolean): IElement {
const attributes = Object.create(null);
for (let j = 0; j < element.attributes.length; j++) {
const attr = element.attributes.item(j);
if (attr) {
attributes[attr.name] = attr.value;
}
}
const children: IElement[] = [];
if (recursive) {
for (let i = 0; i < element.children.length; i++) {
const child = element.children.item(i);
if (child) {
children.push(this.serializeElement(child, true));
}
}
}
const { left, top } = getTopLeftOffset(element as HTMLElement);
return {
tagName: element.tagName,
className: element.className,
textContent: element.textContent || '',
attributes,
children,
left,
top
};
}
async getElementXY(selector: string, xoffset?: number, yoffset?: number): Promise<{ x: number; y: number }> {
const offset = typeof xoffset === 'number' && typeof yoffset === 'number' ? { x: xoffset, y: yoffset } : undefined;
return this._getElementXY(selector, offset);
}
async typeInEditor(selector: string, text: string): Promise<void> {
const element = document.querySelector(selector);
if (!element) {
throw new Error(`Editor not found: ${selector}`);
}
const textarea = element as HTMLTextAreaElement;
const start = textarea.selectionStart;
const newStart = start + text.length;
const value = textarea.value;
const newValue = value.substr(0, start) + text + value.substr(start);
textarea.value = newValue;
textarea.setSelectionRange(newStart, newStart);
const event = new Event('input', { 'bubbles': true, 'cancelable': true });
textarea.dispatchEvent(event);
}
async getTerminalBuffer(selector: string): Promise<string[]> {
const element = document.querySelector(selector);
if (!element) {
throw new Error(`Terminal not found: ${selector}`);
}
const xterm = (element as any).xterm;
if (!xterm) {
throw new Error(`Xterm not found: ${selector}`);
}
const lines: string[] = [];
for (let i = 0; i < xterm.buffer.active.length; i++) {
lines.push(xterm.buffer.active.getLine(i)!.translateToString(true));
}
return lines;
}
async writeInTerminal(selector: string, text: string): Promise<void> {
const element = document.querySelector(selector);
if (!element) {
throw new Error(`Element not found: ${selector}`);
}
const xterm = (element as any).xterm;
if (!xterm) {
throw new Error(`Xterm not found: ${selector}`);
}
xterm._core.coreService.triggerDataEvent(text);
}
getLocaleInfo(): Promise<ILocaleInfo> {
return Promise.resolve({
language: language,
locale: locale
});
}
getLocalizedStrings(): Promise<ILocalizedStrings> {
return Promise.resolve({
open: localizedStrings.open,
close: localizedStrings.close,
find: localizedStrings.find
});
}
protected async _getElementXY(selector: string, offset?: { x: number; y: number }): Promise<{ x: number; y: number }> {
const element = document.querySelector(selector);
if (!element) {
return Promise.reject(new Error(`Element not found: ${selector}`));
}
const { left, top } = getTopLeftOffset(element as HTMLElement);
const { width, height } = getClientArea(element as HTMLElement);
let x: number, y: number;
if (offset) {
x = left + offset.x;
y = top + offset.y;
} else {
x = left + (width / 2);
y = top + (height / 2);
}
x = Math.round(x);
y = Math.round(y);
return { x, y };
}
abstract openDevTools(): Promise<void>;
}

View file

@ -3,23 +3,210 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { BaseWindowDriver } from 'vs/platform/driver/browser/baseDriver';
import { getClientArea, getTopLeftOffset } from 'vs/base/browser/dom';
import { coalesce } from 'vs/base/common/arrays';
import { language, locale } from 'vs/base/common/platform';
import { IElement, ILocaleInfo, ILocalizedStrings, IWindowDriver } from 'vs/platform/driver/common/driver';
import localizedStrings from 'vs/platform/localizations/common/localizedStrings';
class BrowserWindowDriver extends BaseWindowDriver {
click(selector: string, xoffset?: number | undefined, yoffset?: number | undefined): Promise<void> {
throw new Error('Method not implemented.');
export class BrowserWindowDriver implements IWindowDriver {
async setValue(selector: string, text: string): Promise<void> {
const element = document.querySelector(selector);
if (!element) {
return Promise.reject(new Error(`Element not found: ${selector}`));
}
const inputElement = element as HTMLInputElement;
inputElement.value = text;
const event = new Event('input', { bubbles: true, cancelable: true });
inputElement.dispatchEvent(event);
}
doubleClick(selector: string): Promise<void> {
throw new Error('Method not implemented.');
async getTitle(): Promise<string> {
return document.title;
}
openDevTools(): Promise<void> {
async isActiveElement(selector: string): Promise<boolean> {
const element = document.querySelector(selector);
if (element !== document.activeElement) {
const chain: string[] = [];
let el = document.activeElement;
while (el) {
const tagName = el.tagName;
const id = el.id ? `#${el.id}` : '';
const classes = coalesce(el.className.split(/\s+/g).map(c => c.trim())).map(c => `.${c}`).join('');
chain.unshift(`${tagName}${id}${classes}`);
el = el.parentElement;
}
throw new Error(`Active element not found. Current active element is '${chain.join(' > ')}'. Looking for ${selector}`);
}
return true;
}
async getElements(selector: string, recursive: boolean): Promise<IElement[]> {
const query = document.querySelectorAll(selector);
const result: IElement[] = [];
for (let i = 0; i < query.length; i++) {
const element = query.item(i);
result.push(this.serializeElement(element, recursive));
}
return result;
}
private serializeElement(element: Element, recursive: boolean): IElement {
const attributes = Object.create(null);
for (let j = 0; j < element.attributes.length; j++) {
const attr = element.attributes.item(j);
if (attr) {
attributes[attr.name] = attr.value;
}
}
const children: IElement[] = [];
if (recursive) {
for (let i = 0; i < element.children.length; i++) {
const child = element.children.item(i);
if (child) {
children.push(this.serializeElement(child, true));
}
}
}
const { left, top } = getTopLeftOffset(element as HTMLElement);
return {
tagName: element.tagName,
className: element.className,
textContent: element.textContent || '',
attributes,
children,
left,
top
};
}
async getElementXY(selector: string, xoffset?: number, yoffset?: number): Promise<{ x: number; y: number }> {
const offset = typeof xoffset === 'number' && typeof yoffset === 'number' ? { x: xoffset, y: yoffset } : undefined;
return this._getElementXY(selector, offset);
}
async typeInEditor(selector: string, text: string): Promise<void> {
const element = document.querySelector(selector);
if (!element) {
throw new Error(`Editor not found: ${selector}`);
}
const textarea = element as HTMLTextAreaElement;
const start = textarea.selectionStart;
const newStart = start + text.length;
const value = textarea.value;
const newValue = value.substr(0, start) + text + value.substr(start);
textarea.value = newValue;
textarea.setSelectionRange(newStart, newStart);
const event = new Event('input', { 'bubbles': true, 'cancelable': true });
textarea.dispatchEvent(event);
}
async getTerminalBuffer(selector: string): Promise<string[]> {
const element = document.querySelector(selector);
if (!element) {
throw new Error(`Terminal not found: ${selector}`);
}
const xterm = (element as any).xterm;
if (!xterm) {
throw new Error(`Xterm not found: ${selector}`);
}
const lines: string[] = [];
for (let i = 0; i < xterm.buffer.active.length; i++) {
lines.push(xterm.buffer.active.getLine(i)!.translateToString(true));
}
return lines;
}
async writeInTerminal(selector: string, text: string): Promise<void> {
const element = document.querySelector(selector);
if (!element) {
throw new Error(`Element not found: ${selector}`);
}
const xterm = (element as any).xterm;
if (!xterm) {
throw new Error(`Xterm not found: ${selector}`);
}
xterm._core.coreService.triggerDataEvent(text);
}
getLocaleInfo(): Promise<ILocaleInfo> {
return Promise.resolve({
language: language,
locale: locale
});
}
getLocalizedStrings(): Promise<ILocalizedStrings> {
return Promise.resolve({
open: localizedStrings.open,
close: localizedStrings.close,
find: localizedStrings.find
});
}
protected async _getElementXY(selector: string, offset?: { x: number; y: number }): Promise<{ x: number; y: number }> {
const element = document.querySelector(selector);
if (!element) {
return Promise.reject(new Error(`Element not found: ${selector}`));
}
const { left, top } = getTopLeftOffset(element as HTMLElement);
const { width, height } = getClientArea(element as HTMLElement);
let x: number, y: number;
if (offset) {
x = left + offset.x;
y = top + offset.y;
} else {
x = left + (width / 2);
y = top + (height / 2);
}
x = Math.round(x);
y = Math.round(y);
return { x, y };
}
click(selector: string, xoffset?: number, yoffset?: number): Promise<void> {
// This is actually not used in the playwright drivers
// that can implement `click` natively via the driver
throw new Error('Method not implemented.');
}
}
export async function registerWindowDriver(): Promise<IDisposable> {
(<any>window).driver = new BrowserWindowDriver();
return Disposable.None;
export function registerWindowDriver(): void {
Object.assign(window, { driver: new BrowserWindowDriver() });
}

View file

@ -40,14 +40,11 @@ export interface IDriver {
readonly _serviceBrand: undefined;
getWindowIds(): Promise<number[]>;
capturePage(windowId: number): Promise<string>;
startTracing(windowId: number, name: string): Promise<void>;
stopTracing(windowId: number, name: string, persist: boolean): Promise<void>;
reloadWindow(windowId: number): Promise<void>;
exitApplication(): Promise<boolean>;
exitApplication(): Promise<number /* main PID */>;
dispatchKeybinding(windowId: number, keybinding: string): Promise<void>;
click(windowId: number, selector: string, xoffset?: number | undefined, yoffset?: number | undefined): Promise<void>;
doubleClick(windowId: number, selector: string): Promise<void>;
setValue(windowId: number, selector: string, text: string): Promise<void>;
getTitle(windowId: number): Promise<string>;
isActiveElement(windowId: number, selector: string): Promise<boolean>;
@ -62,7 +59,6 @@ export interface IDriver {
export interface IWindowDriver {
click(selector: string, xoffset?: number | undefined, yoffset?: number | undefined): Promise<void>;
doubleClick(selector: string): Promise<void>;
setValue(selector: string, text: string): Promise<void>;
getTitle(): Promise<string>;
isActiveElement(selector: string): Promise<boolean>;
@ -79,11 +75,7 @@ export interface IWindowDriver {
export const ID = 'driverService';
export const IDriver = createDecorator<IDriver>(ID);
export interface IDriverOptions {
verbose: boolean;
}
export interface IWindowDriverRegistry {
registerWindowDriver(windowId: number): Promise<IDriverOptions>;
registerWindowDriver(windowId: number): Promise<void>;
reloadWindowDriver(windowId: number): Promise<void>;
}

View file

@ -5,7 +5,7 @@
import { Event } from 'vs/base/common/event';
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { IDriverOptions, IElement, ILocaleInfo, ILocalizedStrings as ILocalizedStrings, IWindowDriver, IWindowDriverRegistry } from 'vs/platform/driver/common/driver';
import { IElement, ILocaleInfo, ILocalizedStrings as ILocalizedStrings, IWindowDriver, IWindowDriverRegistry } from 'vs/platform/driver/common/driver';
export class WindowDriverChannel implements IServerChannel {
@ -18,7 +18,6 @@ export class WindowDriverChannel implements IServerChannel {
call(_: unknown, command: string, arg?: any): Promise<any> {
switch (command) {
case 'click': return this.driver.click(arg[0], arg[1], arg[2]);
case 'doubleClick': return this.driver.doubleClick(arg);
case 'setValue': return this.driver.setValue(arg[0], arg[1]);
case 'getTitle': return this.driver.getTitle();
case 'isActiveElement': return this.driver.isActiveElement(arg);
@ -45,10 +44,6 @@ export class WindowDriverChannelClient implements IWindowDriver {
return this.channel.call('click', [selector, xoffset, yoffset]);
}
doubleClick(selector: string): Promise<void> {
return this.channel.call('doubleClick', selector);
}
setValue(selector: string, text: string): Promise<void> {
return this.channel.call('setValue', [selector, text]);
}
@ -96,7 +91,7 @@ export class WindowDriverRegistryChannelClient implements IWindowDriverRegistry
constructor(private channel: IChannel) { }
registerWindowDriver(windowId: number): Promise<IDriverOptions> {
registerWindowDriver(windowId: number): Promise<void> {
return this.channel.call('registerWindowDriver', windowId);
}

View file

@ -12,7 +12,7 @@ import { combinedDisposable, IDisposable } from 'vs/base/common/lifecycle';
import { OS } from 'vs/base/common/platform';
import { IPCServer, StaticRouter } from 'vs/base/parts/ipc/common/ipc';
import { serve as serveNet } from 'vs/base/parts/ipc/node/ipc.net';
import { IDriver, IDriverOptions, IElement, ILocaleInfo, ILocalizedStrings, IWindowDriver, IWindowDriverRegistry } from 'vs/platform/driver/common/driver';
import { IDriver, IElement, ILocaleInfo, ILocalizedStrings, IWindowDriver, IWindowDriverRegistry } from 'vs/platform/driver/common/driver';
import { WindowDriverChannelClient } from 'vs/platform/driver/common/driverIpc';
import { DriverChannel, WindowDriverRegistryChannel } from 'vs/platform/driver/node/driver';
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
@ -40,7 +40,6 @@ export class Driver implements IDriver, IWindowDriverRegistry {
constructor(
private windowServer: IPCServer,
private options: IDriverOptions,
@IWindowsMainService private readonly windowsMainService: IWindowsMainService,
@ILifecycleMainService private readonly lifecycleMainService: ILifecycleMainService,
@IFileService private readonly fileService: IFileService,
@ -48,13 +47,12 @@ export class Driver implements IDriver, IWindowDriverRegistry {
@ILogService private readonly logService: ILogService
) { }
async registerWindowDriver(windowId: number): Promise<IDriverOptions> {
async registerWindowDriver(windowId: number): Promise<void> {
this.logService.info(`[driver] registerWindowDriver(${windowId})`);
this.registeredWindowIds.add(windowId);
this.reloadingWindowIds.delete(windowId);
this.onDidReloadingChange.fire();
return this.options;
}
async reloadWindowDriver(windowId: number): Promise<void> {
@ -71,16 +69,6 @@ export class Driver implements IDriver, IWindowDriverRegistry {
return windowIds;
}
async capturePage(windowId: number): Promise<string> {
const window = this.windowsMainService.getWindowById(windowId) ?? this.windowsMainService.getLastActiveWindow(); // fallback to active window to ensure we capture window
if (!window?.win) {
throw new Error('Invalid window');
}
const webContents = window.win.webContents;
const image = await webContents.capturePage();
return image.toPNG().toString('base64');
}
async startTracing(windowId: number, name: string): Promise<void> {
// ignore - tracing is not implemented yet
@ -97,24 +85,23 @@ export class Driver implements IDriver, IWindowDriverRegistry {
await this.fileService.writeFile(URI.file(join(this.environmentMainService.logsPath, `${name}.png`)), VSBuffer.wrap(buffer));
}
async reloadWindow(windowId: number): Promise<void> {
this.logService.info(`[driver] reloadWindow(${windowId})`);
await this.whenUnfrozen(windowId);
const window = this.windowsMainService.getWindowById(windowId);
if (!window) {
private async capturePage(windowId: number): Promise<string> {
const window = this.windowsMainService.getWindowById(windowId) ?? this.windowsMainService.getLastActiveWindow(); // fallback to active window to ensure we capture window
if (!window?.win) {
throw new Error('Invalid window');
}
this.reloadingWindowIds.add(windowId);
this.lifecycleMainService.reload(window);
const webContents = window.win.webContents;
const image = await webContents.capturePage();
return image.toPNG().toString('base64');
}
exitApplication(): Promise<boolean> {
async exitApplication(): Promise<number> {
this.logService.info(`[driver] exitApplication()`);
return this.lifecycleMainService.quit();
this.lifecycleMainService.quit();
return process.pid;
}
async dispatchKeybinding(windowId: number, keybinding: string): Promise<void> {
@ -175,11 +162,6 @@ export class Driver implements IDriver, IWindowDriverRegistry {
await windowDriver.click(selector, xoffset, yoffset);
}
async doubleClick(windowId: number, selector: string): Promise<void> {
const windowDriver = await this.getWindowDriver(windowId);
await windowDriver.doubleClick(selector);
}
async setValue(windowId: number, selector: string, text: string): Promise<void> {
const windowDriver = await this.getWindowDriver(windowId);
await windowDriver.setValue(selector, text);
@ -249,11 +231,9 @@ export class Driver implements IDriver, IWindowDriverRegistry {
export async function serve(
windowServer: IPCServer,
handle: string,
environmentMainService: IEnvironmentMainService,
instantiationService: IInstantiationService
): Promise<IDisposable> {
const verbose = environmentMainService.driverVerbose;
const driver = instantiationService.createInstance(Driver, windowServer, { verbose });
const driver = instantiationService.createInstance(Driver, windowServer);
const windowDriverRegistryChannel = new WindowDriverRegistryChannel(driver);
windowServer.registerChannel('windowDriverRegistry', windowDriverRegistryChannel);

View file

@ -5,13 +5,32 @@
import { timeout } from 'vs/base/common/async';
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { BaseWindowDriver } from 'vs/platform/driver/browser/baseDriver';
import { BrowserWindowDriver } from 'vs/platform/driver/browser/driver';
import { WindowDriverChannel, WindowDriverRegistryChannelClient } from 'vs/platform/driver/common/driverIpc';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IMainProcessService } from 'vs/platform/ipc/electron-sandbox/services';
import { INativeHostService } from 'vs/platform/native/electron-sandbox/native';
class WindowDriver extends BaseWindowDriver {
interface INativeWindowDriverHelper {
exitApplication(): Promise<number /* main process PID */>;
}
class NativeWindowDriver extends BrowserWindowDriver {
constructor(private readonly helper: INativeWindowDriverHelper) {
super();
}
exitApplication(): Promise<number> {
return this.helper.exitApplication();
}
}
export function registerWindowDriver(helper: INativeWindowDriverHelper): void {
Object.assign(window, { driver: new NativeWindowDriver(helper) });
}
class LegacyNativeWindowDriver extends BrowserWindowDriver {
constructor(
@INativeHostService private readonly nativeHostService: INativeHostService
@ -19,16 +38,13 @@ class WindowDriver extends BaseWindowDriver {
super();
}
click(selector: string, xoffset?: number, yoffset?: number): Promise<void> {
override click(selector: string, xoffset?: number, yoffset?: number): Promise<void> {
const offset = typeof xoffset === 'number' && typeof yoffset === 'number' ? { x: xoffset, y: yoffset } : undefined;
return this._click(selector, 1, offset);
return this.doClick(selector, 1, offset);
}
doubleClick(selector: string): Promise<void> {
return this._click(selector, 2);
}
private async _click(selector: string, clickCount: number, offset?: { x: number; y: number }): Promise<void> {
private async doClick(selector: string, clickCount: number, offset?: { x: number; y: number }): Promise<void> {
const { x, y } = await this._getElementXY(selector, offset);
await this.nativeHostService.sendInputEvent({ type: 'mouseDown', x, y, button: 'left', clickCount } as any);
@ -37,17 +53,19 @@ class WindowDriver extends BaseWindowDriver {
await this.nativeHostService.sendInputEvent({ type: 'mouseUp', x, y, button: 'left', clickCount } as any);
await timeout(100);
}
async openDevTools(): Promise<void> {
await this.nativeHostService.openDevTools({ mode: 'detach' });
}
}
export async function registerWindowDriver(accessor: ServicesAccessor, windowId: number): Promise<IDisposable> {
/**
* Old school window driver that is implemented by us
* from the main process.
*
* @deprecated
*/
export async function registerLegacyWindowDriver(accessor: ServicesAccessor, windowId: number): Promise<IDisposable> {
const instantiationService = accessor.get(IInstantiationService);
const mainProcessService = accessor.get(IMainProcessService);
const windowDriver = instantiationService.createInstance(WindowDriver);
const windowDriver = instantiationService.createInstance(LegacyNativeWindowDriver);
const windowDriverChannel = new WindowDriverChannel(windowDriver);
mainProcessService.registerChannel('windowDriver', windowDriverChannel);

View file

@ -20,14 +20,11 @@ export class DriverChannel implements IServerChannel {
call(_: unknown, command: string, arg?: any): Promise<any> {
switch (command) {
case 'getWindowIds': return this.driver.getWindowIds();
case 'capturePage': return this.driver.capturePage(arg);
case 'startTracing': return this.driver.startTracing(arg[0], arg[1]);
case 'stopTracing': return this.driver.stopTracing(arg[0], arg[1], arg[2]);
case 'reloadWindow': return this.driver.reloadWindow(arg);
case 'exitApplication': return this.driver.exitApplication();
case 'dispatchKeybinding': return this.driver.dispatchKeybinding(arg[0], arg[1]);
case 'click': return this.driver.click(arg[0], arg[1], arg[2], arg[3]);
case 'doubleClick': return this.driver.doubleClick(arg[0], arg[1]);
case 'setValue': return this.driver.setValue(arg[0], arg[1], arg[2]);
case 'getTitle': return this.driver.getTitle(arg[0]);
case 'isActiveElement': return this.driver.isActiveElement(arg[0], arg[1]);
@ -54,10 +51,6 @@ export class DriverChannelClient implements IDriver {
return this.channel.call('getWindowIds');
}
capturePage(windowId: number): Promise<string> {
return this.channel.call('capturePage', windowId);
}
startTracing(windowId: number, name: string): Promise<void> {
return this.channel.call('startTracing', [windowId, name]);
}
@ -66,11 +59,7 @@ export class DriverChannelClient implements IDriver {
return this.channel.call('stopTracing', [windowId, name, persist]);
}
reloadWindow(windowId: number): Promise<void> {
return this.channel.call('reloadWindow', windowId);
}
exitApplication(): Promise<boolean> {
exitApplication(): Promise<number> {
return this.channel.call('exitApplication');
}
@ -82,10 +71,6 @@ export class DriverChannelClient implements IDriver {
return this.channel.call('click', [windowId, selector, xoffset, yoffset]);
}
doubleClick(windowId: number, selector: string): Promise<void> {
return this.channel.call('doubleClick', [windowId, selector]);
}
setValue(windowId: number, selector: string, text: string): Promise<void> {
return this.channel.call('setValue', [windowId, selector, text]);
}

View file

@ -79,8 +79,11 @@ export interface NativeParsedArgs {
'max-memory'?: string;
'file-write'?: boolean;
'file-chmod'?: boolean;
/**
* @deprecated use `enable-smoke-test-driver`
*/
'driver'?: string;
'driver-verbose'?: boolean;
'enable-smoke-test-driver'?: boolean;
'remote'?: string;
'force'?: boolean;
'do-not-sync'?: boolean;

View file

@ -35,7 +35,6 @@ export interface IEnvironmentMainService extends INativeEnvironmentService {
// --- config
sandbox: boolean;
driverVerbose: boolean;
disableUpdates: boolean;
}
@ -59,9 +58,6 @@ export class EnvironmentMainService extends NativeEnvironmentService implements
@memoize
get sandbox(): boolean { return !!this.args['__sandbox']; }
@memoize
get driverVerbose(): boolean { return !!this.args['driver-verbose']; }
@memoize
get disableUpdates(): boolean { return !!this.args['disable-updates']; }

View file

@ -99,6 +99,7 @@ export const OPTIONS: OptionDescriptions<Required<NativeParsedArgs>> = {
'export-default-configuration': { type: 'string' },
'install-source': { type: 'string' },
'driver': { type: 'string' },
'enable-smoke-test-driver': { type: 'boolean' },
'logExtensionHostCommunication': { type: 'boolean' },
'skip-release-notes': { type: 'boolean' },
'skip-welcome': { type: 'boolean' },
@ -114,7 +115,6 @@ export const OPTIONS: OptionDescriptions<Required<NativeParsedArgs>> = {
'open-url': { type: 'boolean' },
'file-write': { type: 'boolean' },
'file-chmod': { type: 'boolean' },
'driver-verbose': { type: 'boolean' },
'install-builtin-extension': { type: 'string[]' },
'force': { type: 'boolean' },
'do-not-sync': { type: 'boolean' },

View file

@ -238,7 +238,7 @@ export interface IFileService {
* Note: recursive file watching is not supported from this method. Only events from files
* that are direct children of the provided resource will be reported.
*/
watch(resource: URI): IDisposable;
watch(resource: URI, options?: IWatchOptions): IDisposable;
/**
* Frees up any resources occupied by this service.

View file

@ -5,19 +5,24 @@
import { Codicon } from 'vs/base/common/codicons';
import { URI, UriComponents } from 'vs/base/common/uri';
import { localize } from 'vs/nls';
import { IExtensionTerminalProfile, ITerminalProfile, TerminalIcon } from 'vs/platform/terminal/common/terminal';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
export function createProfileSchemaEnums(detectedProfiles: ITerminalProfile[], extensionProfiles?: readonly IExtensionTerminalProfile[]): {
values: string[] | undefined;
values: (string | null)[] | undefined;
markdownDescriptions: string[] | undefined;
} {
const result = detectedProfiles.map(e => {
const result: { name: string | null; description: string }[] = [{
name: null,
description: localize('terminalAutomaticProfile', 'Automatically detect the default')
}];
result.push(...detectedProfiles.map(e => {
return {
name: e.profileName,
description: createProfileDescription(e)
};
});
}));
if (extensionProfiles) {
result.push(...extensionProfiles.map(extensionProfile => {
return {

View file

@ -12,8 +12,12 @@ suite('terminalProfiles', () => {
suite('createProfileSchemaEnums', () => {
test('should return an empty array when there are no profiles', () => {
deepStrictEqual(createProfileSchemaEnums([]), {
values: [],
markdownDescriptions: []
values: [
null
],
markdownDescriptions: [
'Automatically detect the default'
]
});
});
test('should return a single entry when there is one profile', () => {
@ -23,8 +27,14 @@ suite('terminalProfiles', () => {
isDefault: true
};
deepStrictEqual(createProfileSchemaEnums([profile]), {
values: ['name'],
markdownDescriptions: ['$(terminal) name\n- path: path']
values: [
null,
'name'
],
markdownDescriptions: [
'Automatically detect the default',
'$(terminal) name\n- path: path'
]
});
});
test('should show all profile information', () => {
@ -42,8 +52,14 @@ suite('terminalProfiles', () => {
overrideName: true
};
deepStrictEqual(createProfileSchemaEnums([profile]), {
values: ['name'],
markdownDescriptions: [`$(zap) name\n- path: path\n- args: ['a','b']\n- overrideName: true\n- color: terminal.ansiRed\n- env: {\"c\":\"d\",\"e\":\"f\"}`]
values: [
null,
'name'
],
markdownDescriptions: [
'Automatically detect the default',
`$(zap) name\n- path: path\n- args: ['a','b']\n- overrideName: true\n- color: terminal.ansiRed\n- env: {\"c\":\"d\",\"e\":\"f\"}`
]
});
});
test('should return a multiple entries when there are multiple profiles', () => {
@ -58,8 +74,13 @@ suite('terminalProfiles', () => {
isDefault: false
};
deepStrictEqual(createProfileSchemaEnums([profile1, profile2]), {
values: ['name', 'foo'],
values: [
null,
'name',
'foo'
],
markdownDescriptions: [
'Automatically detect the default',
'$(terminal) name\n- path: path',
'$(terminal) foo\n- path: bar'
]

View file

@ -289,7 +289,7 @@ export const editorErrorForeground = registerColor('editorError.foreground', { d
export const editorErrorBorder = registerColor('editorError.border', { dark: null, light: null, hcDark: Color.fromHex('#E47777').transparent(0.8), hcLight: '#B5200D' }, nls.localize('errorBorder', 'Border color of error boxes in the editor.'));
export const editorWarningBackground = registerColor('editorWarning.background', { dark: null, light: null, hcDark: null, hcLight: null }, nls.localize('editorWarning.background', 'Background color of warning text in the editor. The color must not be opaque so as not to hide underlying decorations.'), true);
export const editorWarningForeground = registerColor('editorWarning.foreground', { dark: '#CCA700', light: '#BF8803', hcDark: '#FFD3700', hcLight: '#895503' }, nls.localize('editorWarning.foreground', 'Foreground color of warning squigglies in the editor.'));
export const editorWarningForeground = registerColor('editorWarning.foreground', { dark: '#CCA700', light: '#BF8803', hcDark: '#FFD37', hcLight: '#895503' }, nls.localize('editorWarning.foreground', 'Foreground color of warning squigglies in the editor.'));
export const editorWarningBorder = registerColor('editorWarning.border', { dark: null, light: null, hcDark: Color.fromHex('#FFCC00').transparent(0.8), hcLight: '#' }, nls.localize('warningBorder', 'Border color of warning boxes in the editor.'));
export const editorInfoBackground = registerColor('editorInfo.background', { dark: null, light: null, hcDark: null, hcLight: null }, nls.localize('editorInfo.background', 'Background color of info text in the editor. The color must not be opaque so as not to hide underlying decorations.'), true);

View file

@ -304,7 +304,7 @@ export class MainThreadDocumentsAndEditors {
this._mainThreadDocuments = this._toDispose.add(new MainThreadDocuments(extHostContext, this._modelService, this._textFileService, fileService, textModelResolverService, environmentService, uriIdentityService, workingCopyFileService, pathService));
extHostContext.set(MainContext.MainThreadDocuments, this._mainThreadDocuments);
this._mainThreadEditors = this._toDispose.add(new MainThreadTextEditors(this, extHostContext, codeEditorService, this._editorService, this._editorGroupService, instantiationService));
this._mainThreadEditors = this._toDispose.add(new MainThreadTextEditors(this, extHostContext, codeEditorService, this._editorService, this._editorGroupService));
extHostContext.set(MainContext.MainThreadTextEditors, this._mainThreadEditors);
// It is expected that the ctor of the state computer calls our `_onDelta`.

View file

@ -561,5 +561,22 @@ export class MainThreadEditorTabs implements MainThreadEditorTabsShape {
// TODO @jrieken This isn't quite right how can we say true for some but not others?
return results.every(result => result);
}
async $closeGroup(groupIds: number[], preserveFocus?: boolean): Promise<boolean> {
const groupCloseResults: boolean[] = [];
for (const groupId of groupIds) {
const group = this._editorGroupsService.getGroup(groupId);
if (group) {
// TODO @lramos15 change this to use group.closeAllEditors once it
// is enriched to return a boolean
groupCloseResults.push(await group.closeEditors([...group.editors], { preserveFocus }));
// Make sure group is empty but still there before removing it
if (group.count === 0 && this._editorGroupsService.getGroup(group.id)) {
this._editorGroupsService.removeGroup(group);
}
}
}
return groupCloseResults.every(result => result);
}
//#endregion
}

View file

@ -14,7 +14,7 @@ import { IDecorationOptions, IDecorationRenderOptions } from 'vs/editor/common/e
import { ISingleEditOperation } from 'vs/editor/common/core/editOperation';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { ITextEditorOptions, IResourceEditorInput, EditorActivation, EditorResolution } from 'vs/platform/editor/common/editor';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { MainThreadTextEditor } from 'vs/workbench/api/browser/mainThreadEditor';
import { ExtHostContext, ExtHostEditorsShape, IApplyEditsOptions, ITextDocumentShowOptions, ITextEditorConfigurationUpdate, ITextEditorPositionData, IUndoStopOptions, MainThreadTextEditorsShape, TextEditorRevealType } from 'vs/workbench/api/common/extHost.protocol';
import { editorGroupToColumn, columnToEditorGroup, EditorGroupColumn } from 'vs/workbench/services/editor/common/editorGroupColumn';
@ -27,13 +27,6 @@ import { ILineChange } from 'vs/editor/common/diff/diffComputer';
import { IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
import { IEditorControl } from 'vs/workbench/common/editor';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { DataTransferConverter } from 'vs/workbench/api/common/shared/dataTransfer';
import { IPosition } from 'vs/editor/common/core/position';
import { IDataTransfer, IDataTransferItem } from 'vs/workbench/common/dnd';
import { extractEditorsDropData } from 'vs/workbench/browser/dnd';
import { Mimes } from 'vs/base/common/mime';
import { distinct } from 'vs/base/common/arrays';
import { performSnippetEdit } from 'vs/editor/contrib/snippet/browser/snippetController2';
export interface IMainThreadEditorLocator {
getEditor(id: string): MainThreadTextEditor | undefined;
@ -51,7 +44,6 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
private _textEditorsListenersMap: { [editorId: string]: IDisposable[] };
private _editorPositionData: ITextEditorPositionData | null;
private _registeredDecorationTypes: { [decorationType: string]: boolean };
private readonly _dropIntoEditorListeners = new Map<ICodeEditor, IDisposable>();
constructor(
private readonly _editorLocator: IMainThreadEditorLocator,
@ -59,7 +51,6 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
@ICodeEditorService private readonly _codeEditorService: ICodeEditorService,
@IEditorService private readonly _editorService: IEditorService,
@IEditorGroupsService private readonly _editorGroupService: IEditorGroupsService,
@IInstantiationService private readonly _instantiationService: IInstantiationService,
) {
this._instanceId = String(++MainThreadTextEditors.INSTANCE_COUNT);
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostEditors);
@ -71,22 +62,6 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
this._toDispose.add(this._editorGroupService.onDidRemoveGroup(() => this._updateActiveAndVisibleTextEditors()));
this._toDispose.add(this._editorGroupService.onDidMoveGroup(() => this._updateActiveAndVisibleTextEditors()));
const registerDropListenerOnEditor = (editor: ICodeEditor) => {
this._dropIntoEditorListeners.get(editor)?.dispose();
this._dropIntoEditorListeners.set(editor, editor.onDropIntoEditor(e => this.onDropIntoEditor(editor, e.position, e.event)));
};
this._toDispose.add(_codeEditorService.onCodeEditorAdd(registerDropListenerOnEditor));
this._toDispose.add(_codeEditorService.onCodeEditorRemove(editor => {
this._dropIntoEditorListeners.get(editor)?.dispose();
this._dropIntoEditorListeners.delete(editor);
}));
for (const editor of this._codeEditorService.listCodeEditors()) {
registerDropListenerOnEditor(editor);
}
this._registeredDecorationTypes = Object.create(null);
}
@ -99,8 +74,6 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
for (let decorationType in this._registeredDecorationTypes) {
this._codeEditorService.removeDecorationType(decorationType);
}
dispose(this._dropIntoEditorListeners.values());
this._dropIntoEditorListeners.clear();
this._registeredDecorationTypes = Object.create(null);
}
@ -140,59 +113,6 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
return result;
}
private async onDropIntoEditor(editor: ICodeEditor, position: IPosition, dragEvent: DragEvent) {
if (!dragEvent.dataTransfer || !editor.hasModel()) {
return;
}
const id = this._editorLocator.getIdOfCodeEditor(editor);
if (typeof id !== 'string') {
return;
}
const modelVersionNow = editor.getModel().getVersionId();
const textEditorDataTransfer: IDataTransfer = new Map<string, IDataTransferItem>();
for (const item of dragEvent.dataTransfer.items) {
if (item.kind === 'string') {
const type = item.type;
const asStringValue = new Promise<string>(resolve => item.getAsString(resolve));
textEditorDataTransfer.set(type, {
asString: () => asStringValue,
value: undefined
});
}
}
if (!textEditorDataTransfer.has(Mimes.uriList.toLowerCase())) {
const editorData = (await this._instantiationService.invokeFunction(extractEditorsDropData, dragEvent))
.filter(input => input.resource)
.map(input => input.resource!.toString());
if (editorData.length) {
const str = distinct(editorData).join('\n');
textEditorDataTransfer.set(Mimes.uriList.toLowerCase(), {
asString: () => Promise.resolve(str),
value: undefined
});
}
}
if (textEditorDataTransfer.size === 0) {
return;
}
const dataTransferDto = await DataTransferConverter.toDataTransferDTO(textEditorDataTransfer);
const edits = await this._proxy.$textEditorHandleDrop(id, position, dataTransferDto);
if (edits.length === 0) {
return;
}
if (editor.getModel().getVersionId() === modelVersionNow) {
const [first] = edits; // TODO@jrieken define how to pick the "one snippet edit";
performSnippetEdit(editor, first);
}
}
// --- from extension host process
async $tryShowTextDocument(resource: UriComponents, options: ITextDocumentShowOptions): Promise<string | undefined> {

View file

@ -3,43 +3,49 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDisposable } from 'vs/base/common/lifecycle';
import { Emitter, Event } from 'vs/base/common/event';
import { ITextModel } from 'vs/editor/common/model';
import { ISingleEditOperation } from 'vs/editor/common/core/editOperation';
import * as languages from 'vs/editor/common/languages';
import * as search from 'vs/workbench/contrib/search/common/search';
import { CancellationToken } from 'vs/base/common/cancellation';
import { Position as EditorPosition } from 'vs/editor/common/core/position';
import { Range as EditorRange, IRange } from 'vs/editor/common/core/range';
import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, ILanguageConfigurationDto, IRegExpDto, IIndentationRuleDto, IOnEnterRuleDto, ILocationDto, IWorkspaceSymbolDto, reviveWorkspaceEditDto, IDocumentFilterDto, ILocationLinkDto, ISignatureHelpProviderMetadataDto, ILinkDto, ICallHierarchyItemDto, ISuggestDataDto, ICodeActionDto, ISuggestDataDtoField, ISuggestResultDtoField, ICodeActionProviderMetadataDto, ILanguageWordDefinitionDto, IdentifiableInlineCompletions, IdentifiableInlineCompletion, ITypeHierarchyItemDto, IInlayHintDto } from '../common/extHost.protocol';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { LanguageConfiguration, IndentationRule, OnEnterRule } from 'vs/editor/common/languages/languageConfiguration';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
import { URI } from 'vs/base/common/uri';
import { Selection } from 'vs/editor/common/core/selection';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import * as typeh from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy';
import { mixin } from 'vs/base/common/objects';
import { decodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto';
import { revive } from 'vs/base/common/marshalling';
import { CancellationError } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { revive } from 'vs/base/common/marshalling';
import { mixin } from 'vs/base/common/objects';
import { URI } from 'vs/base/common/uri';
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
import { ISingleEditOperation } from 'vs/editor/common/core/editOperation';
import { Position as EditorPosition } from 'vs/editor/common/core/position';
import { IRange, Range as EditorRange } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
import * as languages from 'vs/editor/common/languages';
import { ILanguageService } from 'vs/editor/common/languages/language';
import { IndentationRule, LanguageConfiguration, OnEnterRule } from 'vs/editor/common/languages/languageConfiguration';
import { ILanguageConfigurationService } from 'vs/editor/common/languages/languageConfigurationRegistry';
import { ITextModel } from 'vs/editor/common/model';
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
import { decodeSemanticTokensDto } from 'vs/editor/common/services/semanticTokensDto';
import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { DataTransferConverter } from 'vs/workbench/api/common/shared/dataTransfer';
import * as callh from 'vs/workbench/contrib/callHierarchy/common/callHierarchy';
import * as search from 'vs/workbench/contrib/search/common/search';
import * as typeh from 'vs/workbench/contrib/typeHierarchy/common/typeHierarchy';
import { extHostNamedCustomer, IExtHostContext } from 'vs/workbench/services/extensions/common/extHostCustomers';
import { ExtHostContext, ExtHostLanguageFeaturesShape, ICallHierarchyItemDto, ICodeActionDto, ICodeActionProviderMetadataDto, IdentifiableInlineCompletion, IdentifiableInlineCompletions, IDocumentFilterDto, IIndentationRuleDto, IInlayHintDto, ILanguageConfigurationDto, ILanguageWordDefinitionDto, ILinkDto, ILocationDto, ILocationLinkDto, IOnEnterRuleDto, IRegExpDto, ISignatureHelpProviderMetadataDto, ISuggestDataDto, ISuggestDataDtoField, ISuggestResultDtoField, ITypeHierarchyItemDto, IWorkspaceSymbolDto, MainContext, MainThreadLanguageFeaturesShape, reviveWorkspaceEditDto } from '../common/extHost.protocol';
@extHostNamedCustomer(MainContext.MainThreadLanguageFeatures)
export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesShape {
export class MainThreadLanguageFeatures extends Disposable implements MainThreadLanguageFeaturesShape {
private readonly _proxy: ExtHostLanguageFeaturesShape;
private readonly _registrations = new Map<number, IDisposable>();
private readonly _dropIntoEditorListeners = new Map<ICodeEditor, IDisposable>();
constructor(
extHostContext: IExtHostContext,
@ILanguageService private readonly _languageService: ILanguageService,
@ILanguageConfigurationService private readonly _languageConfigurationService: ILanguageConfigurationService,
@ILanguageFeaturesService private readonly _languageFeaturesService: ILanguageFeaturesService,
) {
super();
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostLanguageFeatures);
if (this._languageService) {
@ -71,11 +77,16 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
}
}
dispose(): void {
override dispose(): void {
for (const registration of this._registrations.values()) {
registration.dispose();
}
this._registrations.clear();
dispose(this._dropIntoEditorListeners.values());
this._dropIntoEditorListeners.clear();
super.dispose();
}
$unregister(handle: number): void {
@ -850,6 +861,17 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
}));
}
// --- document drop Edits
$registerDocumentOnDropProvider(handle: number, selector: IDocumentFilterDto[]): void {
this._registrations.set(handle, this._languageFeaturesService.documentOnDropEditProvider.register(selector, {
provideDocumentOnDropEdits: async (model, position, dataTransfer, token) => {
const dataTransferDto = await DataTransferConverter.toDataTransferDTO(dataTransfer);
return this._proxy.$provideDocumentOnDropEdits(handle, model.uri, position, dataTransferDto, token);
}
}));
}
}
export class MainThreadDocumentSemanticTokensProvider implements languages.DocumentSemanticTokensProvider {

View file

@ -510,7 +510,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
containerTitle: item.contextualTitle || viewContainer?.title,
canToggleVisibility: true,
canMoveView: viewContainer?.id !== REMOTE,
treeView: type === ViewType.Tree ? this.instantiationService.createInstance(CustomTreeView, item.id, item.name) : undefined,
treeView: type === ViewType.Tree ? this.instantiationService.createInstance(CustomTreeView, item.id, item.name, extension.description.identifier.value) : undefined,
collapsed: this.showCollapsed(container) || initialVisibility === InitialVisibility.Collapsed,
order: order,
extensionId: extension.description.identifier,

Some files were not shown because too many files have changed in this diff Show more