Compare commits

..

1 commit

Author SHA1 Message Date
Megan Rogge c2e79623a3
start working 2021-11-21 15:10:07 -08:00
327 changed files with 3565 additions and 8831 deletions

View file

@ -72,9 +72,6 @@ jobs:
- name: Run Unit Tests (Electron)
run: .\scripts\test.bat
- name: Run Unit Tests (node.js)
run: yarn test-node
- name: Run Unit Tests (Browser)
run: yarn test-browser --browser chromium
@ -88,7 +85,7 @@ jobs:
timeout-minutes: 10
run: .\resources\server\test\test-web-integration.bat --browser firefox
- name: Run Integration Tests (Remote)
- name: Run Remote Integration Tests (Electron)
timeout-minutes: 10
run: .\resources\server\test\test-remote-integration.bat
@ -150,10 +147,6 @@ jobs:
id: electron-unit-tests
run: DISPLAY=:10 ./scripts/test.sh
- name: Run Unit Tests (node.js)
id: nodejs-unit-tests
run: yarn test-node
- name: Run Unit Tests (Browser)
id: browser-unit-tests
run: DISPLAY=:10 yarn test-browser --browser chromium
@ -169,7 +162,7 @@ jobs:
id: browser-integration-tests
run: DISPLAY=:10 ./resources/server/test/test-web-integration.sh --browser chromium
- name: Run Integration Tests (Remote)
- name: Run Remote Integration Tests (Electron)
id: electron-remote-integration-tests
timeout-minutes: 7
run: DISPLAY=:10 ./resources/server/test/test-remote-integration.sh
@ -229,9 +222,6 @@ jobs:
- name: Run Unit Tests (Electron)
run: DISPLAY=:10 ./scripts/test.sh
- name: Run Unit Tests (node.js)
run: yarn test-node
- name: Run Unit Tests (Browser)
run: DISPLAY=:10 yarn test-browser --browser chromium
@ -244,7 +234,7 @@ jobs:
- name: Run Integration Tests (Browser)
run: DISPLAY=:10 ./resources/server/test/test-web-integration.sh --browser webkit
- name: Run Integration Tests (Remote)
- name: Run Remote Integration Tests (Electron)
timeout-minutes: 7
run: DISPLAY=:10 ./resources/server/test/test-remote-integration.sh

View file

@ -1 +1 @@
2021-11-24T12:04:58.681Z
2021-11-19T08:23:35Z

View file

@ -39,26 +39,32 @@ steps:
- script: |
set -e
git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro")
displayName: Merge distro
- script: |
mkdir -p .build
node build/azure-pipelines/common/computeNodeModulesCacheKey.js $VSCODE_ARCH $ENABLE_TERRAPIN > .build/yarnlockhash
displayName: Prepare yarn cache flags
- task: Cache@2
inputs:
key: "nodeModules | $(Agent.OS) | .build/yarnlockhash"
path: .build/node_modules_cache
cacheHitVar: NODE_MODULES_RESTORED
displayName: Restore node_modules cache
sudo xcode-select -s /Applications/Xcode_12.2.app
displayName: Switch to Xcode 12
condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'arm64'))
- script: |
set -e
tar -xzf .build/node_modules_cache/cache.tgz
condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true'))
displayName: Extract node_modules cache
git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro")
displayName: Merge distro
# - script: |
# mkdir -p .build
# node build/azure-pipelines/common/computeNodeModulesCacheKey.js $VSCODE_ARCH $ENABLE_TERRAPIN > .build/yarnlockhash
# displayName: Prepare yarn cache flags
# - task: Cache@2
# inputs:
# key: "nodeModules | $(Agent.OS) | .build/yarnlockhash"
# path: .build/node_modules_cache
# cacheHitVar: NODE_MODULES_RESTORED
# displayName: Restore node_modules cache
# - script: |
# set -e
# tar -xzf .build/node_modules_cache/cache.tgz
# condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true'))
# displayName: Extract node_modules cache
- script: |
set -e
@ -78,6 +84,7 @@ steps:
set -e
export npm_config_arch=$(VSCODE_ARCH)
export npm_config_node_gyp=$(which node-gyp)
export SDKROOT=/Applications/Xcode_12.2.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX11.0.sdk
for i in {1..3}; do # try 3 times, for Terrapin
yarn --frozen-lockfile && break
@ -94,13 +101,13 @@ steps:
displayName: Install dependencies
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
- script: |
set -e
node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt
mkdir -p .build/node_modules_cache
tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
displayName: Create node_modules archive
# - script: |
# set -e
# node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt
# mkdir -p .build/node_modules_cache
# tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt
# condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
# displayName: Create node_modules archive
# This script brings in the right resources (images, icons, etc) based on the quality (insiders, stable, exploration)
- script: |
@ -172,13 +179,6 @@ steps:
timeoutInMinutes: 7
condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
- script: |
set -e
yarn test-node --build
displayName: Run unit tests (node.js)
timeoutInMinutes: 7
condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
- script: |
set -e
yarn test-browser --build --browser chromium --browser webkit --browser firefox --tfs "Browser Unit Tests"
@ -215,7 +215,7 @@ steps:
INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME/Contents/MacOS/Electron" \
VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-darwin" \
./resources/server/test/test-remote-integration.sh
displayName: Run integration tests (Remote)
displayName: Run remote integration tests (Electron)
timeoutInMinutes: 7
condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false'))

View file

@ -47,23 +47,23 @@ steps:
git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro")
displayName: Merge distro
- script: |
mkdir -p .build
node build/azure-pipelines/common/computeNodeModulesCacheKey.js "alpine" $ENABLE_TERRAPIN > .build/yarnlockhash
displayName: Prepare yarn cache flags
# - script: |
# mkdir -p .build
# node build/azure-pipelines/common/computeNodeModulesCacheKey.js "alpine" $ENABLE_TERRAPIN > .build/yarnlockhash
# displayName: Prepare yarn cache flags
- task: Cache@2
inputs:
key: "nodeModules | $(Agent.OS) | .build/yarnlockhash"
path: .build/node_modules_cache
cacheHitVar: NODE_MODULES_RESTORED
displayName: Restore node_modules cache
# - task: Cache@2
# inputs:
# key: "nodeModules | $(Agent.OS) | .build/yarnlockhash"
# path: .build/node_modules_cache
# cacheHitVar: NODE_MODULES_RESTORED
# displayName: Restore node_modules cache
- script: |
set -e
tar -xzf .build/node_modules_cache/cache.tgz
condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true'))
displayName: Extract node_modules cache
# - script: |
# set -e
# tar -xzf .build/node_modules_cache/cache.tgz
# condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true'))
# displayName: Extract node_modules cache
- script: |
set -e
@ -89,13 +89,13 @@ steps:
displayName: Install dependencies
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
- script: |
set -e
node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt
mkdir -p .build/node_modules_cache
tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
displayName: Create node_modules archive
# - script: |
# set -e
# node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt
# mkdir -p .build/node_modules_cache
# tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt
# condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
# displayName: Create node_modules archive
- script: |
set -e

View file

@ -38,23 +38,23 @@ steps:
git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro")
displayName: Merge distro
- script: |
mkdir -p .build
node build/azure-pipelines/common/computeNodeModulesCacheKey.js $VSCODE_ARCH $ENABLE_TERRAPIN > .build/yarnlockhash
displayName: Prepare yarn cache flags
# - script: |
# mkdir -p .build
# node build/azure-pipelines/common/computeNodeModulesCacheKey.js $VSCODE_ARCH $ENABLE_TERRAPIN > .build/yarnlockhash
# displayName: Prepare yarn cache flags
- task: Cache@2
inputs:
key: "nodeModules | $(Agent.OS) | .build/yarnlockhash"
path: .build/node_modules_cache
cacheHitVar: NODE_MODULES_RESTORED
displayName: Restore node_modules cache
# - task: Cache@2
# inputs:
# key: "nodeModules | $(Agent.OS) | .build/yarnlockhash"
# path: .build/node_modules_cache
# cacheHitVar: NODE_MODULES_RESTORED
# displayName: Restore node_modules cache
- script: |
set -e
tar -xzf .build/node_modules_cache/cache.tgz
condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true'))
displayName: Extract node_modules cache
# - script: |
# set -e
# tar -xzf .build/node_modules_cache/cache.tgz
# condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true'))
# displayName: Extract node_modules cache
- script: |
set -e
@ -110,13 +110,13 @@ steps:
displayName: Install dependencies
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
- script: |
set -e
node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt
mkdir -p .build/node_modules_cache
tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
displayName: Create node_modules archive
# - script: |
# set -e
# node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt
# mkdir -p .build/node_modules_cache
# tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt
# condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
# displayName: Create node_modules archive
- script: |
set -e
@ -162,13 +162,6 @@ steps:
timeoutInMinutes: 7
condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
- script: |
set -e
yarn test-node --build
displayName: Run unit tests (node.js)
timeoutInMinutes: 7
condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false'))
- script: |
set -e
yarn test-browser --build --browser chromium --tfs "Browser Unit Tests"
@ -207,7 +200,7 @@ steps:
INTEGRATION_TEST_ELECTRON_PATH="$APP_ROOT/$APP_NAME" \
VSCODE_REMOTE_SERVER_PATH="$(agent.builddirectory)/vscode-reh-linux-$(VSCODE_ARCH)" \
./resources/server/test/test-remote-integration.sh
displayName: Run integration tests (Remote)
displayName: Run remote integration tests (Electron)
timeoutInMinutes: 7
condition: and(succeeded(), eq(variables['VSCODE_ARCH'], 'x64'), eq(variables['VSCODE_STEP_ON_IT'], 'false'))

View file

@ -27,24 +27,24 @@ steps:
git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro")
displayName: Merge distro
- script: |
mkdir -p .build
node build/azure-pipelines/common/computeNodeModulesCacheKey.js $VSCODE_ARCH $ENABLE_TERRAPIN > .build/yarnlockhash
displayName: Prepare yarn cache flags
# - script: |
# mkdir -p .build
# node build/azure-pipelines/common/computeNodeModulesCacheKey.js $VSCODE_ARCH $ENABLE_TERRAPIN > .build/yarnlockhash
# displayName: Prepare yarn cache flags
# using `genericNodeModules` instead of `nodeModules` here to avoid sharing the cache with builds running inside containers
- task: Cache@2
inputs:
key: "genericNodeModules | $(Agent.OS) | .build/yarnlockhash"
path: .build/node_modules_cache
cacheHitVar: NODE_MODULES_RESTORED
displayName: Restore node_modules cache
# # using `genericNodeModules` instead of `nodeModules` here to avoid sharing the cache with builds running inside containers
# - task: Cache@2
# inputs:
# key: "genericNodeModules | $(Agent.OS) | .build/yarnlockhash"
# path: .build/node_modules_cache
# cacheHitVar: NODE_MODULES_RESTORED
# displayName: Restore node_modules cache
- script: |
set -e
tar -xzf .build/node_modules_cache/cache.tgz
condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true'))
displayName: Extract node_modules cache
# - script: |
# set -e
# tar -xzf .build/node_modules_cache/cache.tgz
# condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true'))
# displayName: Extract node_modules cache
- script: |
set -e
@ -77,13 +77,13 @@ steps:
displayName: Install dependencies
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
- script: |
set -e
node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt
mkdir -p .build/node_modules_cache
tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
displayName: Create node_modules archive
# - script: |
# set -e
# node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt
# mkdir -p .build/node_modules_cache
# tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt
# condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
# displayName: Create node_modules archive
# Mixin must run before optimize, because the CSS loader will inline small SVGs
- script: |

View file

@ -38,23 +38,23 @@ steps:
git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro")
displayName: Merge distro
- script: |
mkdir -p .build
node build/azure-pipelines/common/computeNodeModulesCacheKey.js "web" $ENABLE_TERRAPIN > .build/yarnlockhash
displayName: Prepare yarn cache flags
# - script: |
# mkdir -p .build
# node build/azure-pipelines/common/computeNodeModulesCacheKey.js "web" $ENABLE_TERRAPIN > .build/yarnlockhash
# displayName: Prepare yarn cache flags
- task: Cache@2
inputs:
key: "nodeModules | $(Agent.OS) | .build/yarnlockhash"
path: .build/node_modules_cache
cacheHitVar: NODE_MODULES_RESTORED
displayName: Restore node_modules cache
# - task: Cache@2
# inputs:
# key: "nodeModules | $(Agent.OS) | .build/yarnlockhash"
# path: .build/node_modules_cache
# cacheHitVar: NODE_MODULES_RESTORED
# displayName: Restore node_modules cache
- script: |
set -e
tar -xzf .build/node_modules_cache/cache.tgz
condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true'))
displayName: Extract node_modules cache
# - script: |
# set -e
# tar -xzf .build/node_modules_cache/cache.tgz
# condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true'))
# displayName: Extract node_modules cache
- script: |
set -e
@ -80,13 +80,13 @@ steps:
displayName: Install dependencies
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
- script: |
set -e
node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt
mkdir -p .build/node_modules_cache
tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
displayName: Create node_modules archive
# - script: |
# set -e
# node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt
# mkdir -p .build/node_modules_cache
# tar -czf .build/node_modules_cache/cache.tgz --files-from .build/node_modules_list.txt
# condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
# displayName: Create node_modules archive
- script: |
set -e

View file

@ -42,25 +42,25 @@ steps:
exec { git pull --no-rebase https://github.com/$(VSCODE_MIXIN_REPO).git $(node -p "require('./package.json').distro") }
displayName: Merge distro
- powershell: |
"$(VSCODE_ARCH)" | Out-File -Encoding ascii -NoNewLine .build\arch
"$env:ENABLE_TERRAPIN" | Out-File -Encoding ascii -NoNewLine .build\terrapin
node build/azure-pipelines/common/computeNodeModulesCacheKey.js > .build/yarnlockhash
displayName: Prepare yarn cache flags
# - powershell: |
# "$(VSCODE_ARCH)" | Out-File -Encoding ascii -NoNewLine .build\arch
# "$env:ENABLE_TERRAPIN" | Out-File -Encoding ascii -NoNewLine .build\terrapin
# node build/azure-pipelines/common/computeNodeModulesCacheKey.js > .build/yarnlockhash
# displayName: Prepare yarn cache flags
- task: Cache@2
inputs:
key: "nodeModules | $(Agent.OS) | .build/arch, .build/terrapin, .build/yarnlockhash"
path: .build/node_modules_cache
cacheHitVar: NODE_MODULES_RESTORED
displayName: Restore node_modules cache
# - task: Cache@2
# inputs:
# key: "nodeModules | $(Agent.OS) | .build/arch, .build/terrapin, .build/yarnlockhash"
# path: .build/node_modules_cache
# cacheHitVar: NODE_MODULES_RESTORED
# displayName: Restore node_modules cache
- powershell: |
. build/azure-pipelines/win32/exec.ps1
$ErrorActionPreference = "Stop"
exec { 7z.exe x .build/node_modules_cache/cache.7z -aos }
condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true'))
displayName: Extract node_modules cache
# - powershell: |
# . build/azure-pipelines/win32/exec.ps1
# $ErrorActionPreference = "Stop"
# exec { 7z.exe x .build/node_modules_cache/cache.7z -aos }
# condition: and(succeeded(), eq(variables.NODE_MODULES_RESTORED, 'true'))
# displayName: Extract node_modules cache
- powershell: |
. build/azure-pipelines/win32/exec.ps1
@ -84,14 +84,14 @@ steps:
displayName: Install dependencies
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
- powershell: |
. build/azure-pipelines/win32/exec.ps1
$ErrorActionPreference = "Stop"
exec { node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt }
exec { mkdir -Force .build/node_modules_cache }
exec { 7z.exe a .build/node_modules_cache/cache.7z -mx3 `@.build/node_modules_list.txt }
condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
displayName: Create node_modules archive
# - powershell: |
# . build/azure-pipelines/win32/exec.ps1
# $ErrorActionPreference = "Stop"
# exec { node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt }
# exec { mkdir -Force .build/node_modules_cache }
# exec { 7z.exe a .build/node_modules_cache/cache.7z -mx3 `@.build/node_modules_list.txt }
# condition: and(succeeded(), ne(variables.NODE_MODULES_RESTORED, 'true'))
# displayName: Create node_modules archive
- powershell: |
. build/azure-pipelines/win32/exec.ps1
@ -150,14 +150,6 @@ steps:
timeoutInMinutes: 7
condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64'))
- powershell: |
. build/azure-pipelines/win32/exec.ps1
$ErrorActionPreference = "Stop"
exec { yarn test-node --build }
displayName: Run unit tests (node.js)
timeoutInMinutes: 7
condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64'))
- powershell: |
. build/azure-pipelines/win32/exec.ps1
$ErrorActionPreference = "Stop"
@ -195,7 +187,7 @@ steps:
$AppProductJson = Get-Content -Raw -Path "$AppRoot\resources\app\product.json" | ConvertFrom-Json
$AppNameShort = $AppProductJson.nameShort
exec { $env:INTEGRATION_TEST_ELECTRON_PATH = "$AppRoot\$AppNameShort.exe"; $env:VSCODE_REMOTE_SERVER_PATH = "$(agent.builddirectory)\vscode-reh-win32-$(VSCODE_ARCH)"; .\resources\server\test\test-remote-integration.bat }
displayName: Run integration tests (Remote)
displayName: Run remote integration tests (Electron)
timeoutInMinutes: 7
condition: and(succeeded(), eq(variables['VSCODE_STEP_ON_IT'], 'false'), ne(variables['VSCODE_ARCH'], 'arm64'))

View file

@ -38,7 +38,6 @@ const compilations = [
'emmet/tsconfig.json',
'extension-editing/tsconfig.json',
'git/tsconfig.json',
'git-base/tsconfig.json',
'github-authentication/tsconfig.json',
'github/tsconfig.json',
'grunt/tsconfig.json',
@ -210,7 +209,6 @@ exports.watchExtensionMedia = watchExtensionMedia;
const compileExtensionMediaBuildTask = task.define('compile-extension-media-build', () => ext.buildExtensionMedia(false, '.build/extensions'));
gulp.task(compileExtensionMediaBuildTask);
exports.compileExtensionMediaBuildTask = compileExtensionMediaBuildTask;
//#endregion

View file

@ -25,7 +25,7 @@ const File = require('vinyl');
const fs = require('fs');
const glob = require('glob');
const { compileBuildTask } = require('./gulpfile.compile');
const { compileExtensionsBuildTask, compileExtensionMediaBuildTask } = require('./gulpfile.extensions');
const { compileExtensionsBuildTask } = require('./gulpfile.extensions');
const { vscodeWebEntryPoints, vscodeWebResourceIncludes, createVSCodeWebFileContentMapper } = require('./gulpfile.vscode.web');
const cp = require('child_process');
@ -381,7 +381,6 @@ function packageTask(type, platform, arch, sourceFolderName, destinationFolderNa
const serverTask = task.define(`vscode-${type}${dashed(platform)}${dashed(arch)}${dashed(minified)}`, task.series(
compileBuildTask,
compileExtensionsBuildTask,
compileExtensionMediaBuildTask,
minified ? minifyTask : optimizeTask,
serverTaskCI
));

View file

@ -31,7 +31,7 @@ const { config } = require('./lib/electron');
const createAsar = require('./lib/asar').createAsar;
const minimist = require('minimist');
const { compileBuildTask } = require('./gulpfile.compile');
const { compileExtensionsBuildTask, compileExtensionMediaBuildTask } = require('./gulpfile.extensions');
const { compileExtensionsBuildTask } = require('./gulpfile.extensions');
const { getSettingsSearchBuildId, shouldSetupSettingsSearch } = require('./azure-pipelines/upload-configuration');
// Build
@ -379,7 +379,6 @@ BUILD_TARGETS.forEach(buildTarget => {
const vscodeTask = task.define(`vscode${dashed(platform)}${dashed(arch)}${dashed(minified)}`, task.series(
compileBuildTask,
compileExtensionsBuildTask,
compileExtensionMediaBuildTask,
minified ? minifyVSCodeTask : optimizeVSCodeTask,
vscodeTaskCI
));

View file

@ -1,7 +1,7 @@
{
"name": "monaco-editor-core",
"private": true,
"version": "0.31.0",
"version": "0.30.0",
"description": "A browser based code editor",
"author": "Microsoft Corporation",
"license": "MIT",

View file

@ -17,7 +17,6 @@ exports.dirs = [
'extensions/emmet',
'extensions/extension-editing',
'extensions/git',
'extensions/git-base',
'extensions/github',
'extensions/github-authentication',
'extensions/grunt',

View file

@ -54,7 +54,7 @@
"fs-extra": "^9.1.0",
"got": "11.8.1",
"gulp-merge-json": "^2.1.1",
"iconv-lite-umd": "0.6.10",
"iconv-lite-umd": "0.6.8",
"jsonc-parser": "^2.3.0",
"mime": "^1.4.1",
"mkdirp": "^1.0.4",

View file

@ -1,37 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
const path = require('path');
const fs = require('fs');
const cp = require('child_process');
function installHeaders(rcFile) {
const lines = fs.readFileSync(rcFile, 'utf8').split(/\r\n?/g);
let disturl, target;
for (const line of lines) {
let match = line.match(/\s*disturl\s*(.*)$/);
if (match !== null && match.length >= 1) {
disturl = match[1];
}
match = line.match(/\s*target\s*(.*)$/);
if (match !== null && match.length >= 1) {
target = match[1];
}
}
if (disturl !== undefined && target !== undefined) {
console.log(`Pre-fetch headers for ${target} from ${disturl}`);
cp.execSync(`node-gyp install --dist-url ${disturl} ${target}`);
}
}
function main() {
installHeaders(path.join(__dirname, '..', '.yarnrc'));
installHeaders(path.join(__dirname, '..', 'remote', '.yarnrc'));
}
if (require.main === module) {
main();
}

View file

@ -1627,10 +1627,10 @@ https-proxy-agent@^5.0.0:
agent-base "6"
debug "4"
iconv-lite-umd@0.6.10:
version "0.6.10"
resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.10.tgz#faec47521e095b8e3a7175ae08e1b4ae0359a735"
integrity sha512-8NtgTa/m1jVq7vdywmD5+SqIlZsB59wtsjaylQuExyCojMq1tHVQxmHjeqVSYwKwnmQbH4mZ1Dxx1eqDkPgaqA==
iconv-lite-umd@0.6.8:
version "0.6.8"
resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.8.tgz#5ad310ec126b260621471a2d586f7f37b9958ec0"
integrity sha512-zvXJ5gSwMC9JD3wDzH8CoZGc1pbiJn12Tqjk8BXYCnYz3hYL5GRjHW8LEykjXhV9WgNGI4rgpgHcbIiBfrRq6A==
inflight@^1.0.4:
version "1.0.6"

View file

@ -1,17 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
//@ts-check
'use strict';
const withDefaults = require('../shared.webpack.config');
module.exports = withDefaults({
context: __dirname,
entry: {
extension: './src/extension.ts'
}
});

View file

@ -8,41 +8,10 @@
"engines": {
"vscode": "0.10.x"
},
"categories": [
"Other"
],
"activationEvents": [
"*"
],
"main": "./out/extension.js",
"icon": "resources/icons/git.png",
"scripts": {
"compile": "gulp compile-extension:git-base",
"watch": "gulp watch-extension:git-base",
"update-grammar": "node ./build/update-grammars.js"
},
"capabilities": {
"virtualWorkspaces": true,
"untrustedWorkspaces": {
"supported": true
}
},
"contributes": {
"commands": [
{
"command": "git-base.api.getRemoteSources",
"title": "%command.api.getRemoteSources%",
"category": "Git Base API"
}
],
"menus": {
"commandPalette": [
{
"command": "git-base.api.getRemoteSources",
"when": "false"
}
]
},
"languages": [
{
"id": "git-commit",
@ -97,15 +66,5 @@
"path": "./syntaxes/ignore.tmLanguage.json"
}
]
},
"dependencies": {
"vscode-nls": "^4.0.0"
},
"devDependencies": {
"@types/node": "14.x"
},
"repository": {
"type": "git",
"url": "https://github.com/microsoft/vscode.git"
}
}

View file

@ -1,5 +1,4 @@
{
"displayName": "Git Base",
"description": "Git static contributions and pickers.",
"command.api.getRemoteSources": "Get Remote Sources"
"description": "Git static contributions and pickers."
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.3 KiB

View file

@ -1,37 +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 { Disposable, commands } from 'vscode';
import { Model } from '../model';
import { pickRemoteSource } from '../remoteSource';
import { GitBaseExtensionImpl } from './extension';
import { API, PickRemoteSourceOptions, PickRemoteSourceResult, RemoteSourceProvider } from './git-base';
export class ApiImpl implements API {
constructor(private _model: Model) { }
pickRemoteSource(options: PickRemoteSourceOptions): Promise<PickRemoteSourceResult | string | undefined> {
return pickRemoteSource(this._model, options as any);
}
registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable {
return this._model.registerRemoteSourceProvider(provider);
}
}
export function registerAPICommands(extension: GitBaseExtensionImpl): Disposable {
const disposables: Disposable[] = [];
disposables.push(commands.registerCommand('git-base.api.getRemoteSources', (opts?: PickRemoteSourceOptions) => {
if (!extension.model) {
return;
}
return pickRemoteSource(extension.model, opts as any);
}));
return Disposable.from(...disposables);
}

View file

@ -1,55 +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 { Model } from '../model';
import { GitBaseExtension, API } from './git-base';
import { Event, EventEmitter } from 'vscode';
import { ApiImpl } from './api1';
export class GitBaseExtensionImpl implements GitBaseExtension {
enabled: boolean = false;
private _onDidChangeEnablement = new EventEmitter<boolean>();
readonly onDidChangeEnablement: Event<boolean> = this._onDidChangeEnablement.event;
private _model: Model | undefined = undefined;
set model(model: Model | undefined) {
this._model = model;
const enabled = !!model;
if (this.enabled === enabled) {
return;
}
this.enabled = enabled;
this._onDidChangeEnablement.fire(this.enabled);
}
get model(): Model | undefined {
return this._model;
}
constructor(model?: Model) {
if (model) {
this.enabled = true;
this._model = model;
}
}
getAPI(version: number): API {
if (!this._model) {
throw new Error('Git model not found');
}
if (version !== 1) {
throw new Error(`No API version ${version} found.`);
}
return new ApiImpl(this._model);
}
}

View file

@ -1,60 +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 { Disposable, Event, ProviderResult, Uri } from 'vscode';
export { ProviderResult } from 'vscode';
export interface API {
registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable;
pickRemoteSource(options: PickRemoteSourceOptions): Promise<string | PickRemoteSourceResult | undefined>;
}
export interface GitBaseExtension {
readonly enabled: boolean;
readonly onDidChangeEnablement: Event<boolean>;
/**
* Returns a specific API version.
*
* Throws error if git-base extension is disabled. You can listed to the
* [GitBaseExtension.onDidChangeEnablement](#GitBaseExtension.onDidChangeEnablement)
* event to know when the extension becomes enabled/disabled.
*
* @param version Version number.
* @returns API instance
*/
getAPI(version: 1): API;
}
export interface PickRemoteSourceOptions {
readonly providerLabel?: (provider: RemoteSourceProvider) => string;
readonly urlLabel?: string;
readonly providerName?: string;
readonly branch?: boolean; // then result is PickRemoteSourceResult
}
export interface PickRemoteSourceResult {
readonly url: string;
readonly branch?: string;
}
export interface RemoteSource {
readonly name: string;
readonly description?: string;
readonly url: string | string[];
}
export interface RemoteSourceProvider {
readonly name: string;
/**
* Codicon name
*/
readonly icon?: string;
readonly supportsQuery?: boolean;
getBranches?(url: string): ProviderResult<string[]>;
getRemoteSources(query?: string): ProviderResult<RemoteSource[]>;
}

View file

@ -1,69 +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 { done } from './util';
export function debounce(delay: number): Function {
return decorate((fn, key) => {
const timerKey = `$debounce$${key}`;
return function (this: any, ...args: any[]) {
clearTimeout(this[timerKey]);
this[timerKey] = setTimeout(() => fn.apply(this, args), delay);
};
});
}
export const throttle = decorate(_throttle);
function _throttle<T>(fn: Function, key: string): Function {
const currentKey = `$throttle$current$${key}`;
const nextKey = `$throttle$next$${key}`;
const trigger = function (this: any, ...args: any[]) {
if (this[nextKey]) {
return this[nextKey];
}
if (this[currentKey]) {
this[nextKey] = done(this[currentKey]).then(() => {
this[nextKey] = undefined;
return trigger.apply(this, args);
});
return this[nextKey];
}
this[currentKey] = fn.apply(this, args) as Promise<T>;
const clear = () => this[currentKey] = undefined;
done(this[currentKey]).then(clear, clear);
return this[currentKey];
};
return trigger;
}
function decorate(decorator: (fn: Function, key: string) => Function): Function {
return (_target: any, key: string, descriptor: any) => {
let fnKey: string | null = null;
let fn: Function | null = null;
if (typeof descriptor.value === 'function') {
fnKey = 'value';
fn = descriptor.value;
} else if (typeof descriptor.get === 'function') {
fnKey = 'get';
fn = descriptor.get;
}
if (!fn || !fnKey) {
throw new Error('not supported');
}
descriptor[fnKey] = decorator(fn, key);
};
}

View file

@ -1,16 +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 { ExtensionContext } from 'vscode';
import { registerAPICommands } from './api/api1';
import { GitBaseExtensionImpl } from './api/extension';
import { Model } from './model';
export function activate(context: ExtensionContext): GitBaseExtensionImpl {
const apiImpl = new GitBaseExtensionImpl(new Model());
context.subscriptions.push(registerAPICommands(apiImpl));
return apiImpl;
}

View file

@ -1,34 +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 { EventEmitter, Disposable } from 'vscode';
import { toDisposable } from './util';
import { RemoteSourceProvider } from './api/git-base';
import { IRemoteSourceProviderRegistry } from './remoteProvider';
export class Model implements IRemoteSourceProviderRegistry {
private remoteSourceProviders = new Set<RemoteSourceProvider>();
private _onDidAddRemoteSourceProvider = new EventEmitter<RemoteSourceProvider>();
readonly onDidAddRemoteSourceProvider = this._onDidAddRemoteSourceProvider.event;
private _onDidRemoveRemoteSourceProvider = new EventEmitter<RemoteSourceProvider>();
readonly onDidRemoveRemoteSourceProvider = this._onDidRemoveRemoteSourceProvider.event;
registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable {
this.remoteSourceProviders.add(provider);
this._onDidAddRemoteSourceProvider.fire(provider);
return toDisposable(() => {
this.remoteSourceProviders.delete(provider);
this._onDidRemoveRemoteSourceProvider.fire(provider);
});
}
getRemoteProviders(): RemoteSourceProvider[] {
return [...this.remoteSourceProviders.values()];
}
}

View file

@ -1,170 +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 { QuickPickItem, window, QuickPick } from 'vscode';
import * as nls from 'vscode-nls';
import { RemoteSourceProvider, RemoteSource, PickRemoteSourceOptions, PickRemoteSourceResult } from './api/git-base';
import { Model } from './model';
import { throttle, debounce } from './decorators';
const localize = nls.loadMessageBundle();
async function getQuickPickResult<T extends QuickPickItem>(quickpick: QuickPick<T>): Promise<T | undefined> {
const result = await new Promise<T | undefined>(c => {
quickpick.onDidAccept(() => c(quickpick.selectedItems[0]));
quickpick.onDidHide(() => c(undefined));
quickpick.show();
});
quickpick.hide();
return result;
}
class RemoteSourceProviderQuickPick {
private quickpick: QuickPick<QuickPickItem & { remoteSource?: RemoteSource }>;
constructor(private provider: RemoteSourceProvider) {
this.quickpick = window.createQuickPick();
this.quickpick.ignoreFocusOut = true;
if (provider.supportsQuery) {
this.quickpick.placeholder = localize('type to search', "Repository name (type to search)");
this.quickpick.onDidChangeValue(this.onDidChangeValue, this);
} else {
this.quickpick.placeholder = localize('type to filter', "Repository name");
}
}
@debounce(300)
private onDidChangeValue(): void {
this.query();
}
@throttle
private async query(): Promise<void> {
this.quickpick.busy = true;
try {
const remoteSources = await this.provider.getRemoteSources(this.quickpick.value) || [];
if (remoteSources.length === 0) {
this.quickpick.items = [{
label: localize('none found', "No remote repositories found."),
alwaysShow: true
}];
} else {
this.quickpick.items = remoteSources.map(remoteSource => ({
label: remoteSource.name,
description: remoteSource.description || (typeof remoteSource.url === 'string' ? remoteSource.url : remoteSource.url[0]),
remoteSource,
alwaysShow: true
}));
}
} catch (err) {
this.quickpick.items = [{ label: localize('error', "$(error) Error: {0}", err.message), alwaysShow: true }];
console.error(err);
} finally {
this.quickpick.busy = false;
}
}
async pick(): Promise<RemoteSource | undefined> {
this.query();
const result = await getQuickPickResult(this.quickpick);
return result?.remoteSource;
}
}
export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions & { branch?: false | undefined }): Promise<string | undefined>;
export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions & { branch: true }): Promise<PickRemoteSourceResult | undefined>;
export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions = {}): Promise<string | PickRemoteSourceResult | undefined> {
const quickpick = window.createQuickPick<(QuickPickItem & { provider?: RemoteSourceProvider, url?: string })>();
quickpick.ignoreFocusOut = true;
if (options.providerName) {
const provider = model.getRemoteProviders()
.filter(provider => provider.name === options.providerName)[0];
if (provider) {
return await pickProviderSource(provider, options);
}
}
const providers = model.getRemoteProviders()
.map(provider => ({ label: (provider.icon ? `$(${provider.icon}) ` : '') + (options.providerLabel ? options.providerLabel(provider) : provider.name), alwaysShow: true, provider }));
quickpick.placeholder = providers.length === 0
? localize('provide url', "Provide repository URL")
: localize('provide url or pick', "Provide repository URL or pick a repository source.");
const updatePicks = (value?: string) => {
if (value) {
quickpick.items = [{
label: options.urlLabel ?? localize('url', "URL"),
description: value,
alwaysShow: true,
url: value
},
...providers];
} else {
quickpick.items = providers;
}
};
quickpick.onDidChangeValue(updatePicks);
updatePicks();
const result = await getQuickPickResult(quickpick);
if (result) {
if (result.url) {
return result.url;
} else if (result.provider) {
return await pickProviderSource(result.provider, options);
}
}
return undefined;
}
async function pickProviderSource(provider: RemoteSourceProvider, options: PickRemoteSourceOptions = {}): Promise<string | PickRemoteSourceResult | undefined> {
const quickpick = new RemoteSourceProviderQuickPick(provider);
const remote = await quickpick.pick();
let url: string | undefined;
if (remote) {
if (typeof remote.url === 'string') {
url = remote.url;
} else if (remote.url.length > 0) {
url = await window.showQuickPick(remote.url, { ignoreFocusOut: true, placeHolder: localize('pick url', "Choose a URL to clone from.") });
}
}
if (!url || !options.branch) {
return url;
}
if (!provider.getBranches) {
return { url };
}
const branches = await provider.getBranches(url);
if (!branches) {
return { url };
}
const branch = await window.showQuickPick(branches, {
placeHolder: localize('branch name', "Branch name")
});
if (!branch) {
return { url };
}
return { url, branch };
}

View file

@ -1,69 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export interface IDisposable {
dispose(): void;
}
export function toDisposable(dispose: () => void): IDisposable {
return { dispose };
}
export function done<T>(promise: Promise<T>): Promise<void> {
return promise.then<void>(() => undefined);
}
export namespace Versions {
declare type VersionComparisonResult = -1 | 0 | 1;
export interface Version {
major: number;
minor: number;
patch: number;
pre?: string;
}
export function compare(v1: string | Version, v2: string | Version): VersionComparisonResult {
if (typeof v1 === 'string') {
v1 = fromString(v1);
}
if (typeof v2 === 'string') {
v2 = fromString(v2);
}
if (v1.major > v2.major) { return 1; }
if (v1.major < v2.major) { return -1; }
if (v1.minor > v2.minor) { return 1; }
if (v1.minor < v2.minor) { return -1; }
if (v1.patch > v2.patch) { return 1; }
if (v1.patch < v2.patch) { return -1; }
if (v1.pre === undefined && v2.pre !== undefined) { return 1; }
if (v1.pre !== undefined && v2.pre === undefined) { return -1; }
if (v1.pre !== undefined && v2.pre !== undefined) {
return v1.pre.localeCompare(v2.pre) as VersionComparisonResult;
}
return 0;
}
export function from(major: string | number, minor: string | number, patch?: string | number, pre?: string): Version {
return {
major: typeof major === 'string' ? parseInt(major, 10) : major,
minor: typeof minor === 'string' ? parseInt(minor, 10) : minor,
patch: patch === undefined || patch === null ? 0 : typeof patch === 'string' ? parseInt(patch, 10) : patch,
pre: pre,
};
}
export function fromString(version: string): Version {
const [ver, pre] = version.split('-');
const [major, minor, patch] = ver.split('.');
return from(major, minor, patch, pre);
}
}

View file

@ -1,14 +0,0 @@
{
"extends": "../tsconfig.base.json",
"compilerOptions": {
"outDir": "./out",
"experimentalDecorators": true,
"typeRoots": [
"./node_modules/@types"
]
},
"include": [
"src/**/*",
"../../src/vscode-dts/vscode.d.ts"
]
}

View file

@ -1,13 +0,0 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@types/node@14.x":
version "14.17.33"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.33.tgz#011ee28e38dc7aee1be032ceadf6332a0ab15b12"
integrity sha512-noEeJ06zbn3lOh4gqe2v7NMGS33jrulfNqYFDjjEbhpDEHR5VTxgYNQSBqBlJIsBJW3uEYDgD6kvMnrrhGzq8g==
vscode-nls@^4.0.0:
version "4.1.2"
resolved "https://registry.yarnpkg.com/vscode-nls/-/vscode-nls-4.1.2.tgz#ca8bf8bb82a0987b32801f9fddfdd2fb9fd3c167"
integrity sha512-7bOHxPsfyuCqmP+hZXscLhiHwe7CSuFE4hyhbs22xPIhQ4jv99FcR4eBzfYYVLP356HNFpdvz63FFb/xw6T4Iw==

View file

@ -24,9 +24,6 @@
"*",
"onFileSystem:git"
],
"extensionDependencies": [
"vscode.git-base"
],
"main": "./out/main",
"icon": "resources/icons/git.png",
"scripts": {
@ -84,8 +81,7 @@
"command": "git.openChange",
"title": "%command.openChange%",
"category": "Git",
"icon": "$(compare-changes)",
"enablement": "scmActiveResourceHasChanges"
"icon": "$(compare-changes)"
},
{
"command": "git.openAllChanges",
@ -2369,7 +2365,7 @@
"dependencies": {
"byline": "^5.0.0",
"file-type": "^7.2.0",
"iconv-lite-umd": "0.6.10",
"iconv-lite-umd": "0.6.8",
"jschardet": "3.0.0",
"vscode-extension-telemetry": "0.4.3",
"vscode-nls": "^4.0.0",

View file

@ -5,13 +5,12 @@
import { Model } from '../model';
import { Repository as BaseRepository, Resource } from '../repository';
import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions, RemoteSourceProvider, RemoteSourcePublisher } from './git';
import { InputBox, Git, API, Repository, Remote, RepositoryState, Branch, ForcePushMode, Ref, Submodule, Commit, Change, RepositoryUIState, Status, LogOptions, APIState, CommitOptions, RefType, RemoteSourceProvider, CredentialsProvider, BranchQuery, PushErrorHandler, PublishEvent, FetchOptions } from './git';
import { Event, SourceControlInputBox, Uri, SourceControl, Disposable, commands } from 'vscode';
import { combinedDisposable, mapEvent } from '../util';
import { mapEvent } from '../util';
import { toGitUri } from '../uri';
import { pickRemoteSource, PickRemoteSourceOptions } from '../remoteSource';
import { GitExtensionImpl } from './extension';
import { GitBaseApi } from '../git-base';
import { PickRemoteSourceOptions } from './git-base';
class ApiInputBox implements InputBox {
set value(value: string) { this._inputBox.value = value; }
@ -284,18 +283,7 @@ export class ApiImpl implements API {
}
registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable {
const disposables: Disposable[] = [];
if (provider.publishRepository) {
disposables.push(this._model.registerRemoteSourcePublisher(provider as RemoteSourcePublisher));
}
disposables.push(GitBaseApi.getAPI().registerRemoteSourceProvider(provider));
return combinedDisposable(disposables);
}
registerRemoteSourcePublisher(publisher: RemoteSourcePublisher): Disposable {
return this._model.registerRemoteSourcePublisher(publisher);
return this._model.registerRemoteSourceProvider(provider);
}
registerCredentialsProvider(provider: CredentialsProvider): Disposable {
@ -382,7 +370,11 @@ export function registerAPICommands(extension: GitExtensionImpl): Disposable {
}));
disposables.push(commands.registerCommand('git.api.getRemoteSources', (opts?: PickRemoteSourceOptions) => {
return commands.executeCommand('git-base.api.getRemoteSources', opts);
if (!extension.model) {
return;
}
return pickRemoteSource(extension.model, opts as any);
}));
return Disposable.from(...disposables);

View file

@ -1,60 +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 { Disposable, Event, ProviderResult, Uri } from 'vscode';
export { ProviderResult } from 'vscode';
export interface API {
pickRemoteSource(options: PickRemoteSourceOptions): Promise<string | PickRemoteSourceResult | undefined>;
registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable;
}
export interface GitBaseExtension {
readonly enabled: boolean;
readonly onDidChangeEnablement: Event<boolean>;
/**
* Returns a specific API version.
*
* Throws error if git-base extension is disabled. You can listed to the
* [GitBaseExtension.onDidChangeEnablement](#GitBaseExtension.onDidChangeEnablement)
* event to know when the extension becomes enabled/disabled.
*
* @param version Version number.
* @returns API instance
*/
getAPI(version: 1): API;
}
export interface PickRemoteSourceOptions {
readonly providerLabel?: (provider: RemoteSourceProvider) => string;
readonly urlLabel?: string;
readonly providerName?: string;
readonly branch?: boolean; // then result is PickRemoteSourceResult
}
export interface PickRemoteSourceResult {
readonly url: string;
readonly branch?: string;
}
export interface RemoteSource {
readonly name: string;
readonly description?: string;
readonly url: string | string[];
}
export interface RemoteSourceProvider {
readonly name: string;
/**
* Codicon name
*/
readonly icon?: string;
readonly supportsQuery?: boolean;
getBranches?(url: string): ProviderResult<string[]>;
getRemoteSources(query?: string): ProviderResult<RemoteSource[]>;
}

View file

@ -231,12 +231,6 @@ export interface RemoteSourceProvider {
publishRepository?(repository: Repository): Promise<void>;
}
export interface RemoteSourcePublisher {
readonly name: string;
readonly icon?: string; // codicon name
publishRepository(repository: Repository): Promise<void>;
}
export interface Credentials {
readonly username: string;
readonly password: string;
@ -271,7 +265,6 @@ export interface API {
init(root: Uri): Promise<Repository | null>;
openRepository(root: Uri): Promise<Repository | null>
registerRemoteSourcePublisher(publisher: RemoteSourcePublisher): Disposable;
registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable;
registerCredentialsProvider(provider: CredentialsProvider): Disposable;
registerPushErrorHandler(handler: PushErrorHandler): Disposable;

View file

@ -8,7 +8,7 @@ import * as path from 'path';
import { Command, commands, Disposable, LineChange, MessageOptions, OutputChannel, Position, ProgressLocation, QuickPickItem, Range, SourceControlResourceState, TextDocumentShowOptions, TextEditor, Uri, ViewColumn, window, workspace, WorkspaceEdit, WorkspaceFolder, TimelineItem, env, Selection, TextDocumentContentProvider } from 'vscode';
import TelemetryReporter from 'vscode-extension-telemetry';
import * as nls from 'vscode-nls';
import { Branch, ForcePushMode, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourcePublisher } from './api/git';
import { Branch, ForcePushMode, GitErrorCodes, Ref, RefType, Status, CommitOptions, RemoteSourceProvider } from './api/git';
import { Git, Stash } from './git';
import { Model } from './model';
import { Repository, Resource, ResourceGroupType } from './repository';
@ -392,7 +392,7 @@ export class CommandCenter {
async cloneRepository(url?: string, parentPath?: string, options: { recursive?: boolean } = {}): Promise<void> {
if (!url || typeof url !== 'string') {
url = await pickRemoteSource({
url = await pickRemoteSource(this.model, {
providerLabel: provider => localize('clonefrom', "Clone from {0}", provider.name),
urlLabel: localize('repourl', "Clone from URL")
});
@ -2215,7 +2215,7 @@ export class CommandCenter {
@command('git.addRemote', { repository: true })
async addRemote(repository: Repository): Promise<string | undefined> {
const url = await pickRemoteSource({
const url = await pickRemoteSource(this.model, {
providerLabel: provider => localize('addfrom', "Add remote from {0}", provider.name),
urlLabel: localize('addFrom', "Add remote from URL")
});
@ -2360,19 +2360,19 @@ export class CommandCenter {
const remotes = repository.remotes;
if (remotes.length === 0) {
const publishers = this.model.getRemoteSourcePublishers();
const providers = this.model.getRemoteProviders().filter(p => !!p.publishRepository);
if (publishers.length === 0) {
if (providers.length === 0) {
window.showWarningMessage(localize('no remotes to publish', "Your repository has no remotes configured to publish to."));
return;
}
let publisher: RemoteSourcePublisher;
let provider: RemoteSourceProvider;
if (publishers.length === 1) {
publisher = publishers[0];
if (providers.length === 1) {
provider = providers[0];
} else {
const picks = publishers
const picks = providers
.map(provider => ({ label: (provider.icon ? `$(${provider.icon}) ` : '') + localize('publish to', "Publish to {0}", provider.name), alwaysShow: true, provider }));
const placeHolder = localize('pick provider', "Pick a provider to publish the branch '{0}' to:", branchName);
const choice = await window.showQuickPick(picks, { placeHolder });
@ -2381,10 +2381,10 @@ export class CommandCenter {
return;
}
publisher = choice.provider;
provider = choice.provider;
}
await publisher.publishRepository(new ApiRepository(repository));
await provider.publishRepository!(new ApiRepository(repository));
this.model.firePublishEvent(repository, branchName);
return;

View file

@ -1,30 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { extensions } from 'vscode';
import { API as GitBaseAPI, GitBaseExtension } from './api/git-base';
export class GitBaseApi {
private static _gitBaseApi: GitBaseAPI | undefined;
static getAPI(): GitBaseAPI {
if (!this._gitBaseApi) {
const gitBaseExtension = extensions.getExtension<GitBaseExtension>('vscode.git-base')!.exports;
const onDidChangeGitBaseExtensionEnablement = (enabled: boolean) => {
this._gitBaseApi = enabled ? gitBaseExtension.getAPI(1) : undefined;
};
gitBaseExtension.onDidChangeEnablement(onDidChangeGitBaseExtensionEnablement);
onDidChangeGitBaseExtensionEnablement(gitBaseExtension.enabled);
if (!this._gitBaseApi) {
throw new Error('vscode.git-base extension is not enabled.');
}
}
return this._gitBaseApi;
}
}

View file

@ -12,11 +12,11 @@ import * as path from 'path';
import * as fs from 'fs';
import * as nls from 'vscode-nls';
import { fromGitUri } from './uri';
import { APIState as State, CredentialsProvider, PushErrorHandler, PublishEvent, RemoteSourcePublisher } from './api/git';
import { APIState as State, RemoteSourceProvider, CredentialsProvider, PushErrorHandler, PublishEvent } from './api/git';
import { Askpass } from './askpass';
import { IRemoteSourceProviderRegistry } from './remoteProvider';
import { IPushErrorHandlerRegistry } from './pushError';
import { ApiRepository } from './api/api1';
import { IRemoteSourcePublisherRegistry } from './remotePublisher';
const localize = nls.loadMessageBundle();
@ -48,7 +48,7 @@ interface OpenRepository extends Disposable {
repository: Repository;
}
export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerRegistry {
export class Model implements IRemoteSourceProviderRegistry, IPushErrorHandlerRegistry {
private _onDidOpenRepository = new EventEmitter<Repository>();
readonly onDidOpenRepository: Event<Repository> = this._onDidOpenRepository.event;
@ -95,13 +95,13 @@ export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerR
return eventToPromise(filterEvent(this.onDidChangeState, s => s === 'initialized')) as Promise<any>;
}
private remoteSourcePublishers = new Set<RemoteSourcePublisher>();
private remoteSourceProviders = new Set<RemoteSourceProvider>();
private _onDidAddRemoteSourcePublisher = new EventEmitter<RemoteSourcePublisher>();
readonly onDidAddRemoteSourcePublisher = this._onDidAddRemoteSourcePublisher.event;
private _onDidAddRemoteSourceProvider = new EventEmitter<RemoteSourceProvider>();
readonly onDidAddRemoteSourceProvider = this._onDidAddRemoteSourceProvider.event;
private _onDidRemoveRemoteSourcePublisher = new EventEmitter<RemoteSourcePublisher>();
readonly onDidRemoveRemoteSourcePublisher = this._onDidRemoveRemoteSourcePublisher.event;
private _onDidRemoveRemoteSourceProvider = new EventEmitter<RemoteSourceProvider>();
readonly onDidRemoveRemoteSourceProvider = this._onDidRemoveRemoteSourceProvider.event;
private pushErrorHandlers = new Set<PushErrorHandler>();
@ -496,24 +496,24 @@ export class Model implements IRemoteSourcePublisherRegistry, IPushErrorHandlerR
return undefined;
}
registerRemoteSourcePublisher(publisher: RemoteSourcePublisher): Disposable {
this.remoteSourcePublishers.add(publisher);
this._onDidAddRemoteSourcePublisher.fire(publisher);
registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable {
this.remoteSourceProviders.add(provider);
this._onDidAddRemoteSourceProvider.fire(provider);
return toDisposable(() => {
this.remoteSourcePublishers.delete(publisher);
this._onDidRemoveRemoteSourcePublisher.fire(publisher);
this.remoteSourceProviders.delete(provider);
this._onDidRemoveRemoteSourceProvider.fire(provider);
});
}
getRemoteSourcePublishers(): RemoteSourcePublisher[] {
return [...this.remoteSourcePublishers.values()];
}
registerCredentialsProvider(provider: CredentialsProvider): Disposable {
return this.askpass.registerCredentialsProvider(provider);
}
getRemoteProviders(): RemoteSourceProvider[] {
return [...this.remoteSourceProviders.values()];
}
registerPushErrorHandler(handler: PushErrorHandler): Disposable {
this.pushErrorHandlers.add(handler);
return toDisposable(() => this.pushErrorHandlers.delete(handler));

View file

@ -4,12 +4,11 @@
*--------------------------------------------------------------------------------------------*/
import { Disposable, Event } from 'vscode';
import { RemoteSourceProvider } from './api/git-base';
import { RemoteSourceProvider } from './api/git';
export interface IRemoteSourceProviderRegistry {
readonly onDidAddRemoteSourceProvider: Event<RemoteSourceProvider>;
readonly onDidRemoveRemoteSourceProvider: Event<RemoteSourceProvider>;
getRemoteProviders(): RemoteSourceProvider[];
registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable;
getRemoteProviders(): RemoteSourceProvider[];
}

View file

@ -1,15 +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 { Disposable, Event } from 'vscode';
import { RemoteSourcePublisher } from './api/git';
export interface IRemoteSourcePublisherRegistry {
readonly onDidAddRemoteSourcePublisher: Event<RemoteSourcePublisher>;
readonly onDidRemoveRemoteSourcePublisher: Event<RemoteSourcePublisher>;
getRemoteSourcePublishers(): RemoteSourcePublisher[];
registerRemoteSourcePublisher(publisher: RemoteSourcePublisher): Disposable;
}

View file

@ -3,11 +3,180 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { PickRemoteSourceOptions, PickRemoteSourceResult } from './api/git-base';
import { GitBaseApi } from './git-base';
import { QuickPickItem, window, QuickPick } from 'vscode';
import * as nls from 'vscode-nls';
import { RemoteSourceProvider, RemoteSource } from './api/git';
import { Model } from './model';
import { throttle, debounce } from './decorators';
export async function pickRemoteSource(options: PickRemoteSourceOptions & { branch?: false | undefined }): Promise<string | undefined>;
export async function pickRemoteSource(options: PickRemoteSourceOptions & { branch: true }): Promise<PickRemoteSourceResult | undefined>;
export async function pickRemoteSource(options: PickRemoteSourceOptions = {}): Promise<string | PickRemoteSourceResult | undefined> {
return GitBaseApi.getAPI().pickRemoteSource(options);
const localize = nls.loadMessageBundle();
async function getQuickPickResult<T extends QuickPickItem>(quickpick: QuickPick<T>): Promise<T | undefined> {
const result = await new Promise<T | undefined>(c => {
quickpick.onDidAccept(() => c(quickpick.selectedItems[0]));
quickpick.onDidHide(() => c(undefined));
quickpick.show();
});
quickpick.hide();
return result;
}
class RemoteSourceProviderQuickPick {
private quickpick: QuickPick<QuickPickItem & { remoteSource?: RemoteSource }>;
constructor(private provider: RemoteSourceProvider) {
this.quickpick = window.createQuickPick();
this.quickpick.ignoreFocusOut = true;
if (provider.supportsQuery) {
this.quickpick.placeholder = localize('type to search', "Repository name (type to search)");
this.quickpick.onDidChangeValue(this.onDidChangeValue, this);
} else {
this.quickpick.placeholder = localize('type to filter', "Repository name");
}
}
@debounce(300)
private onDidChangeValue(): void {
this.query();
}
@throttle
private async query(): Promise<void> {
this.quickpick.busy = true;
try {
const remoteSources = await this.provider.getRemoteSources(this.quickpick.value) || [];
if (remoteSources.length === 0) {
this.quickpick.items = [{
label: localize('none found', "No remote repositories found."),
alwaysShow: true
}];
} else {
this.quickpick.items = remoteSources.map(remoteSource => ({
label: remoteSource.name,
description: remoteSource.description || (typeof remoteSource.url === 'string' ? remoteSource.url : remoteSource.url[0]),
remoteSource,
alwaysShow: true
}));
}
} catch (err) {
this.quickpick.items = [{ label: localize('error', "$(error) Error: {0}", err.message), alwaysShow: true }];
console.error(err);
} finally {
this.quickpick.busy = false;
}
}
async pick(): Promise<RemoteSource | undefined> {
this.query();
const result = await getQuickPickResult(this.quickpick);
return result?.remoteSource;
}
}
export interface PickRemoteSourceOptions {
readonly providerLabel?: (provider: RemoteSourceProvider) => string;
readonly urlLabel?: string;
readonly providerName?: string;
readonly branch?: boolean; // then result is PickRemoteSourceResult
}
export interface PickRemoteSourceResult {
readonly url: string;
readonly branch?: string;
}
export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions & { branch?: false | undefined }): Promise<string | undefined>;
export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions & { branch: true }): Promise<PickRemoteSourceResult | undefined>;
export async function pickRemoteSource(model: Model, options: PickRemoteSourceOptions = {}): Promise<string | PickRemoteSourceResult | undefined> {
const quickpick = window.createQuickPick<(QuickPickItem & { provider?: RemoteSourceProvider, url?: string })>();
quickpick.ignoreFocusOut = true;
if (options.providerName) {
const provider = model.getRemoteProviders()
.filter(provider => provider.name === options.providerName)[0];
if (provider) {
return await pickProviderSource(provider, options);
}
}
const providers = model.getRemoteProviders()
.map(provider => ({ label: (provider.icon ? `$(${provider.icon}) ` : '') + (options.providerLabel ? options.providerLabel(provider) : provider.name), alwaysShow: true, provider }));
quickpick.placeholder = providers.length === 0
? localize('provide url', "Provide repository URL")
: localize('provide url or pick', "Provide repository URL or pick a repository source.");
const updatePicks = (value?: string) => {
if (value) {
quickpick.items = [{
label: options.urlLabel ?? localize('url', "URL"),
description: value,
alwaysShow: true,
url: value
},
...providers];
} else {
quickpick.items = providers;
}
};
quickpick.onDidChangeValue(updatePicks);
updatePicks();
const result = await getQuickPickResult(quickpick);
if (result) {
if (result.url) {
return result.url;
} else if (result.provider) {
return await pickProviderSource(result.provider, options);
}
}
return undefined;
}
async function pickProviderSource(provider: RemoteSourceProvider, options: PickRemoteSourceOptions = {}): Promise<string | PickRemoteSourceResult | undefined> {
const quickpick = new RemoteSourceProviderQuickPick(provider);
const remote = await quickpick.pick();
let url: string | undefined;
if (remote) {
if (typeof remote.url === 'string') {
url = remote.url;
} else if (remote.url.length > 0) {
url = await window.showQuickPick(remote.url, { ignoreFocusOut: true, placeHolder: localize('pick url', "Choose a URL to clone from.") });
}
}
if (!url || !options.branch) {
return url;
}
if (!provider.getBranches) {
return { url };
}
const branches = await provider.getBranches(url);
if (!branches) {
return { url };
}
const branch = await window.showQuickPick(branches, {
placeHolder: localize('branch name', "Branch name")
});
if (!branch) {
return { url };
}
return { url, branch };
}

View file

@ -16,9 +16,9 @@ import { toGitUri } from './uri';
import { anyEvent, combinedDisposable, debounceEvent, dispose, EmptyDisposable, eventToPromise, filterEvent, find, IDisposable, isDescendant, onceEvent } from './util';
import { IFileWatcher, watch } from './watch';
import { Log, LogLevel } from './log';
import { IRemoteSourceProviderRegistry } from './remoteProvider';
import { IPushErrorHandlerRegistry } from './pushError';
import { ApiRepository } from './api/api1';
import { IRemoteSourcePublisherRegistry } from './remotePublisher';
const timeout = (millis: number) => new Promise(c => setTimeout(c, millis));
@ -850,8 +850,8 @@ export class Repository implements Disposable {
constructor(
private readonly repository: BaseRepository,
remoteSourceProviderRegistry: IRemoteSourceProviderRegistry,
private pushErrorHandlerRegistry: IPushErrorHandlerRegistry,
remoteSourcePublisherRegistry: IRemoteSourcePublisherRegistry,
globalState: Memento,
outputChannel: OutputChannel
) {
@ -959,7 +959,7 @@ export class Repository implements Disposable {
}
}, null, this.disposables);
const statusBar = new StatusBarCommands(this, remoteSourcePublisherRegistry);
const statusBar = new StatusBarCommands(this, remoteSourceProviderRegistry);
this.disposables.push(statusBar);
statusBar.onDidChange(() => this._sourceControl.statusBarCommands = statusBar.commands, null, this.disposables);
this._sourceControl.statusBarCommands = statusBar.commands;

View file

@ -7,8 +7,8 @@ import { Disposable, Command, EventEmitter, Event, workspace, Uri } from 'vscode
import { Repository, Operation } from './repository';
import { anyEvent, dispose, filterEvent } from './util';
import * as nls from 'vscode-nls';
import { Branch, RemoteSourcePublisher } from './api/git';
import { IRemoteSourcePublisherRegistry } from './remotePublisher';
import { Branch, RemoteSourceProvider } from './api/git';
import { IRemoteSourceProviderRegistry } from './remoteProvider';
const localize = nls.loadMessageBundle();
@ -44,7 +44,7 @@ interface SyncStatusBarState {
readonly isSyncRunning: boolean;
readonly hasRemotes: boolean;
readonly HEAD: Branch | undefined;
readonly remoteSourcePublishers: RemoteSourcePublisher[];
readonly remoteSourceProviders: RemoteSourceProvider[];
}
class SyncStatusBar {
@ -60,20 +60,21 @@ class SyncStatusBar {
this._onDidChange.fire();
}
constructor(private repository: Repository, private remoteSourcePublisherRegistry: IRemoteSourcePublisherRegistry) {
constructor(private repository: Repository, private remoteSourceProviderRegistry: IRemoteSourceProviderRegistry) {
this._state = {
enabled: true,
isSyncRunning: false,
hasRemotes: false,
HEAD: undefined,
remoteSourcePublishers: remoteSourcePublisherRegistry.getRemoteSourcePublishers()
remoteSourceProviders: this.remoteSourceProviderRegistry.getRemoteProviders()
.filter(p => !!p.publishRepository)
};
repository.onDidRunGitStatus(this.onDidRunGitStatus, this, this.disposables);
repository.onDidChangeOperations(this.onDidChangeOperations, this, this.disposables);
anyEvent(remoteSourcePublisherRegistry.onDidAddRemoteSourcePublisher, remoteSourcePublisherRegistry.onDidRemoveRemoteSourcePublisher)
(this.onDidChangeRemoteSourcePublishers, this, this.disposables);
anyEvent(remoteSourceProviderRegistry.onDidAddRemoteSourceProvider, remoteSourceProviderRegistry.onDidRemoveRemoteSourceProvider)
(this.onDidChangeRemoteSourceProviders, this, this.disposables);
const onEnablementChange = filterEvent(workspace.onDidChangeConfiguration, e => e.affectsConfiguration('git.enableStatusBarSync'));
onEnablementChange(this.updateEnablement, this, this.disposables);
@ -103,10 +104,11 @@ class SyncStatusBar {
};
}
private onDidChangeRemoteSourcePublishers(): void {
private onDidChangeRemoteSourceProviders(): void {
this.state = {
...this.state,
remoteSourcePublishers: this.remoteSourcePublisherRegistry.getRemoteSourcePublishers()
remoteSourceProviders: this.remoteSourceProviderRegistry.getRemoteProviders()
.filter(p => !!p.publishRepository)
};
}
@ -116,12 +118,12 @@ class SyncStatusBar {
}
if (!this.state.hasRemotes) {
if (this.state.remoteSourcePublishers.length === 0) {
if (this.state.remoteSourceProviders.length === 0) {
return;
}
const tooltip = this.state.remoteSourcePublishers.length === 1
? localize('publish to', "Publish to {0}", this.state.remoteSourcePublishers[0].name)
const tooltip = this.state.remoteSourceProviders.length === 1
? localize('publish to', "Publish to {0}", this.state.remoteSourceProviders[0].name)
: localize('publish to...', "Publish to...");
return {
@ -186,8 +188,8 @@ export class StatusBarCommands {
private checkoutStatusBar: CheckoutStatusBar;
private disposables: Disposable[] = [];
constructor(repository: Repository, remoteSourcePublisherRegistry: IRemoteSourcePublisherRegistry) {
this.syncStatusBar = new SyncStatusBar(repository, remoteSourcePublisherRegistry);
constructor(repository: Repository, remoteSourceProviderRegistry: IRemoteSourceProviderRegistry) {
this.syncStatusBar = new SyncStatusBar(repository, remoteSourceProviderRegistry);
this.checkoutStatusBar = new CheckoutStatusBar(repository);
this.onDidChange = anyEvent(this.syncStatusBar.onDidChange, this.checkoutStatusBar.onDidChange);
}

View file

@ -46,10 +46,10 @@ file-type@^7.2.0:
resolved "https://registry.yarnpkg.com/file-type/-/file-type-7.2.0.tgz#113cfed52e1d6959ab80248906e2f25a8cdccb74"
integrity sha1-ETz+1S4daVmrgCSJBuLyWozcy3Q=
iconv-lite-umd@0.6.10:
version "0.6.10"
resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.10.tgz#faec47521e095b8e3a7175ae08e1b4ae0359a735"
integrity sha512-8NtgTa/m1jVq7vdywmD5+SqIlZsB59wtsjaylQuExyCojMq1tHVQxmHjeqVSYwKwnmQbH4mZ1Dxx1eqDkPgaqA==
iconv-lite-umd@0.6.8:
version "0.6.8"
resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.8.tgz#5ad310ec126b260621471a2d586f7f37b9958ec0"
integrity sha512-zvXJ5gSwMC9JD3wDzH8CoZGc1pbiJn12Tqjk8BXYCnYz3hYL5GRjHW8LEykjXhV9WgNGI4rgpgHcbIiBfrRq6A==
isexe@^2.0.0:
version "2.0.0"

View file

@ -16,7 +16,7 @@
"*"
],
"extensionDependencies": [
"vscode.git-base"
"vscode.git"
],
"main": "./out/extension.js",
"capabilities": {
@ -32,14 +32,6 @@
"title": "Publish to GitHub"
}
],
"menus": {
"commandPalette": [
{
"command": "github.publish",
"when": "git-base.gitEnabled"
}
]
},
"configuration": [
{
"title": "GitHub",

View file

@ -3,94 +3,43 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { commands, Disposable, ExtensionContext, extensions } from 'vscode';
import { Disposable, ExtensionContext, extensions } from 'vscode';
import { GithubRemoteSourceProvider } from './remoteSourceProvider';
import { GitExtension } from './typings/git';
import { registerCommands } from './commands';
import { GithubCredentialProviderManager } from './credentialProvider';
import { dispose, combinedDisposable } from './util';
import { GithubPushErrorHandler } from './pushErrorHandler';
import { GitBaseExtension } from './typings/git-base';
import { GithubRemoteSourcePublisher } from './remoteSourcePublisher';
export function activate(context: ExtensionContext): void {
context.subscriptions.push(initializeGitBaseExtension());
context.subscriptions.push(initializeGitExtension());
}
function initializeGitBaseExtension(): Disposable {
const disposables = new Set<Disposable>();
context.subscriptions.push(combinedDisposable(disposables));
const initialize = () => {
const init = () => {
try {
const gitBaseAPI = gitBaseExtension.getAPI(1);
const gitAPI = gitExtension.getAPI(1);
disposables.add(gitBaseAPI.registerRemoteSourceProvider(new GithubRemoteSourceProvider()));
}
catch (err) {
disposables.add(registerCommands(gitAPI));
disposables.add(gitAPI.registerRemoteSourceProvider(new GithubRemoteSourceProvider(gitAPI)));
disposables.add(new GithubCredentialProviderManager(gitAPI));
disposables.add(gitAPI.registerPushErrorHandler(new GithubPushErrorHandler()));
} catch (err) {
console.error('Could not initialize GitHub extension');
console.warn(err);
}
};
const onDidChangeGitBaseExtensionEnablement = (enabled: boolean) => {
const onDidChangeGitExtensionEnablement = (enabled: boolean) => {
if (!enabled) {
dispose(disposables);
disposables.clear();
} else {
initialize();
init();
}
};
const gitBaseExtension = extensions.getExtension<GitBaseExtension>('vscode.git-base')!.exports;
disposables.add(gitBaseExtension.onDidChangeEnablement(onDidChangeGitBaseExtensionEnablement));
onDidChangeGitBaseExtensionEnablement(gitBaseExtension.enabled);
return combinedDisposable(disposables);
}
function initializeGitExtension(): Disposable {
const disposables = new Set<Disposable>();
let gitExtension = extensions.getExtension<GitExtension>('vscode.git');
const initialize = () => {
gitExtension!.activate()
.then(extension => {
const onDidChangeGitExtensionEnablement = (enabled: boolean) => {
if (enabled) {
const gitAPI = extension.getAPI(1);
disposables.add(registerCommands(gitAPI));
disposables.add(new GithubCredentialProviderManager(gitAPI));
disposables.add(gitAPI.registerPushErrorHandler(new GithubPushErrorHandler()));
disposables.add(gitAPI.registerRemoteSourcePublisher(new GithubRemoteSourcePublisher(gitAPI)));
commands.executeCommand('setContext', 'git-base.gitEnabled', true);
} else {
dispose(disposables);
disposables.clear();
}
};
disposables.add(extension.onDidChangeEnablement(onDidChangeGitExtensionEnablement));
onDidChangeGitExtensionEnablement(extension.enabled);
});
};
if (gitExtension) {
initialize();
} else {
const disposable = extensions.onDidChange(() => {
if (!gitExtension && extensions.getExtension<GitExtension>('vscode.git')) {
gitExtension = extensions.getExtension<GitExtension>('vscode.git');
initialize();
dispose(disposable);
}
});
disposables.add(disposable);
}
return combinedDisposable(disposables);
const gitExtension = extensions.getExtension<GitExtension>('vscode.git')!.exports;
context.subscriptions.push(gitExtension.onDidChangeEnablement(onDidChangeGitExtensionEnablement));
onDidChangeGitExtensionEnablement(gitExtension.enabled);
}

View file

@ -3,9 +3,10 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { RemoteSourceProvider, RemoteSource } from './typings/git-base';
import { API as GitAPI, RemoteSourceProvider, RemoteSource, Repository } from './typings/git';
import { getOctokit } from './auth';
import { Octokit } from '@octokit/rest';
import { publishRepository } from './publish';
function parse(url: string): { owner: string, repo: string } | undefined {
const match = /^https:\/\/github\.com\/([^/]+)\/([^/]+)\.git/i.exec(url)
@ -29,6 +30,8 @@ export class GithubRemoteSourceProvider implements RemoteSourceProvider {
private userReposCache: RemoteSource[] = [];
constructor(private gitAPI: GitAPI) { }
async getRemoteSources(query?: string): Promise<RemoteSource[]> {
const octokit = await getOctokit();
@ -105,4 +108,8 @@ export class GithubRemoteSourceProvider implements RemoteSourceProvider {
return branches.sort((a, b) => a === defaultBranch ? -1 : b === defaultBranch ? 1 : 0);
}
publishRepository(repository: Repository): Promise<void> {
return publishRepository(this.gitAPI, repository);
}
}

View file

@ -1,18 +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 { publishRepository } from './publish';
import { API as GitAPI, RemoteSourcePublisher, Repository } from './typings/git';
export class GithubRemoteSourcePublisher implements RemoteSourcePublisher {
readonly name = 'GitHub';
readonly icon = 'github';
constructor(private gitAPI: GitAPI) { }
publishRepository(repository: Repository): Promise<void> {
return publishRepository(this.gitAPI, repository);
}
}

View file

@ -1,60 +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 { Disposable, Event, ProviderResult, Uri } from 'vscode';
export { ProviderResult } from 'vscode';
export interface API {
registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable;
pickRemoteSource(options: PickRemoteSourceOptions): Promise<string | PickRemoteSourceResult | undefined>;
}
export interface GitBaseExtension {
readonly enabled: boolean;
readonly onDidChangeEnablement: Event<boolean>;
/**
* Returns a specific API version.
*
* Throws error if git-base extension is disabled. You can listed to the
* [GitBaseExtension.onDidChangeEnablement](#GitBaseExtension.onDidChangeEnablement)
* event to know when the extension becomes enabled/disabled.
*
* @param version Version number.
* @returns API instance
*/
getAPI(version: 1): API;
}
export interface PickRemoteSourceOptions {
readonly providerLabel?: (provider: RemoteSourceProvider) => string;
readonly urlLabel?: string;
readonly providerName?: string;
readonly branch?: boolean; // then result is PickRemoteSourceResult
}
export interface PickRemoteSourceResult {
readonly url: string;
readonly branch?: string;
}
export interface RemoteSource {
readonly name: string;
readonly description?: string;
readonly url: string | string[];
}
export interface RemoteSourceProvider {
readonly name: string;
/**
* Codicon name
*/
readonly icon?: string;
readonly supportsQuery?: boolean;
getBranches?(url: string): ProviderResult<string[]>;
getRemoteSources(query?: string): ProviderResult<RemoteSource[]>;
}

View file

@ -216,12 +216,6 @@ export interface RemoteSourceProvider {
publishRepository?(repository: Repository): Promise<void>;
}
export interface RemoteSourcePublisher {
readonly name: string;
readonly icon?: string; // codicon name
publishRepository(repository: Repository): Promise<void>;
}
export interface Credentials {
readonly username: string;
readonly password: string;
@ -249,7 +243,6 @@ export interface API {
getRepository(uri: Uri): Repository | null;
init(root: Uri): Promise<Repository | null>;
registerRemoteSourcePublisher(publisher: RemoteSourcePublisher): Disposable;
registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable;
registerCredentialsProvider(provider: CredentialsProvider): Disposable;
registerPushErrorHandler(handler: PushErrorHandler): Disposable;

View file

@ -6,7 +6,6 @@
import { workspace, extensions, Uri, EventEmitter, Disposable } from 'vscode';
import { resolvePath, joinPath } from './requests';
export function getCustomDataSource(toDispose: Disposable[]) {
let pathsInWorkspace = getCustomDataPathsInAllWorkspaces();
let pathsInExtensions = getCustomDataPathsFromAllExtensions();
@ -15,7 +14,7 @@ export function getCustomDataSource(toDispose: Disposable[]) {
toDispose.push(extensions.onDidChange(_ => {
const newPathsInExtensions = getCustomDataPathsFromAllExtensions();
if (pathsInExtensions.size !== newPathsInExtensions.size || ![...pathsInExtensions].every(path => newPathsInExtensions.has(path))) {
if (newPathsInExtensions.length !== pathsInExtensions.length || !newPathsInExtensions.every((val, idx) => val === pathsInExtensions[idx])) {
pathsInExtensions = newPathsInExtensions;
onChange.fire();
}
@ -27,16 +26,9 @@ export function getCustomDataSource(toDispose: Disposable[]) {
}
}));
toDispose.push(workspace.onDidChangeTextDocument(e => {
const path = e.document.uri.toString();
if (pathsInExtensions.has(path) || pathsInWorkspace.has(path)) {
onChange.fire();
}
}));
return {
get uris() {
return [...pathsInWorkspace].concat([...pathsInExtensions]);
return pathsInWorkspace.concat(pathsInExtensions);
},
get onDidChange() {
return onChange.event;
@ -44,31 +36,21 @@ export function getCustomDataSource(toDispose: Disposable[]) {
};
}
function isURI(uriOrPath: string) {
return /^(?<scheme>\w[\w\d+.-]*):/.test(uriOrPath);
}
function getCustomDataPathsInAllWorkspaces(): Set<string> {
function getCustomDataPathsInAllWorkspaces(): string[] {
const workspaceFolders = workspace.workspaceFolders;
const dataPaths = new Set<string>();
const dataPaths: string[] = [];
if (!workspaceFolders) {
return dataPaths;
}
const collect = (uriOrPaths: string[] | undefined, rootFolder: Uri) => {
if (Array.isArray(uriOrPaths)) {
for (const uriOrPath of uriOrPaths) {
if (typeof uriOrPath === 'string') {
if (!isURI(uriOrPath)) {
// path in the workspace
dataPaths.add(resolvePath(rootFolder, uriOrPath).toString());
} else {
// external uri
dataPaths.add(uriOrPath);
}
const collect = (paths: string[] | undefined, rootFolder: Uri) => {
if (Array.isArray(paths)) {
for (const path of paths) {
if (typeof path === 'string') {
dataPaths.push(resolvePath(rootFolder, path).toString());
}
}
}
@ -92,20 +74,13 @@ function getCustomDataPathsInAllWorkspaces(): Set<string> {
return dataPaths;
}
function getCustomDataPathsFromAllExtensions(): Set<string> {
const dataPaths = new Set<string>();
function getCustomDataPathsFromAllExtensions(): string[] {
const dataPaths: string[] = [];
for (const extension of extensions.all) {
const customData = extension.packageJSON?.contributes?.html?.customData;
if (Array.isArray(customData)) {
for (const uriOrPath of customData) {
if (!isURI(uriOrPath)) {
// relative path in an extension
dataPaths.add(joinPath(extension.extensionUri, uriOrPath).toString());
} else {
// external uri
dataPaths.add(uriOrPath);
}
for (const rp of customData) {
dataPaths.push(joinPath(extension.extensionUri, rp).toString());
}
}
}

View file

@ -16,7 +16,7 @@ import {
DocumentRangeFormattingRequest, ProvideCompletionItemsSignature, TextDocumentIdentifier, RequestType0, Range as LspRange, NotificationType, CommonLanguageClient
} from 'vscode-languageclient';
import { activateTagClosing } from './tagClosing';
import { RequestService, serveFileSystemRequests } from './requests';
import { RequestService } from './requests';
import { getCustomDataSource } from './customData';
namespace CustomDataChangedNotification {
@ -120,8 +120,6 @@ export function startClient(context: ExtensionContext, newLanguageClient: Langua
toDispose.push(disposable);
client.onReady().then(() => {
toDispose.push(serveFileSystemRequests(client, runtime));
client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris);
customDataSource.onDidChange(() => {
client.sendNotification(CustomDataChangedNotification.type, customDataSource.uris);

View file

@ -3,7 +3,7 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Uri, workspace, Disposable } from 'vscode';
import { Uri, workspace } from 'vscode';
import { RequestType, CommonLanguageClient } from 'vscode-languageclient';
import { Runtime } from './htmlClient';
@ -18,9 +18,8 @@ export namespace FsReadDirRequest {
export const type: RequestType<string, [string, FileType][], any> = new RequestType('fs/readDir');
}
export function serveFileSystemRequests(client: CommonLanguageClient, runtime: Runtime): Disposable {
const disposables = [];
disposables.push(client.onRequest(FsContentRequest.type, (param: { uri: string; encoding?: string; }) => {
export function serveFileSystemRequests(client: CommonLanguageClient, runtime: Runtime) {
client.onRequest(FsContentRequest.type, (param: { uri: string; encoding?: string; }) => {
const uri = Uri.parse(param.uri);
if (uri.scheme === 'file' && runtime.fs) {
return runtime.fs.getContent(param.uri);
@ -28,22 +27,21 @@ export function serveFileSystemRequests(client: CommonLanguageClient, runtime: R
return workspace.fs.readFile(uri).then(buffer => {
return new runtime.TextDecoder(param.encoding).decode(buffer);
});
}));
disposables.push(client.onRequest(FsReadDirRequest.type, (uriString: string) => {
});
client.onRequest(FsReadDirRequest.type, (uriString: string) => {
const uri = Uri.parse(uriString);
if (uri.scheme === 'file' && runtime.fs) {
return runtime.fs.readDirectory(uriString);
}
return workspace.fs.readDirectory(uri);
}));
disposables.push(client.onRequest(FsStatRequest.type, (uriString: string) => {
});
client.onRequest(FsStatRequest.type, (uriString: string) => {
const uri = Uri.parse(uriString);
if (uri.scheme === 'file' && runtime.fs) {
return runtime.fs.stat(uriString);
}
return workspace.fs.stat(uri);
}));
return Disposable.from(...disposables);
});
}
export enum FileType {

View file

@ -3,85 +3,28 @@
* 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 { window, languages, Uri, LanguageStatusSeverity, Disposable, commands, QuickPickItem } from 'vscode';
import { JSONLanguageStatus } from './jsonClient';
import * as nls from 'vscode-nls';
const localize = nls.loadMessageBundle();
type ShowSchemasInput = {
schemas: string[];
uri: string;
};
interface ShowSchemasItem extends QuickPickItem {
uri: Uri;
}
function equalsIgnoreCase(a: string, b: string): boolean {
return a.length === b.length && a.toLowerCase().localeCompare(b.toLowerCase()) === 0;
}
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;
}
}
}
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 };
}
const wf = findWorkspaceFolder(uri);
if (wf) {
return { label: uri.path.substring(wf.uri.path.length + 1), description: 'Workspace', 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 };
}
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());
const showSchemasCommand = commands.registerCommand('_json.showAssociatedSchemaList', arg => {
const items = arg.schemas.sort().map((a: string) => ({ label: a }));
const quickPick = window.createQuickPick<QuickPickItem>();
quickPick.title = localize('schemaPicker.title', 'Associated JSON Schemas');
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);
const selectedSchema = quickPick.selectedItems[0].label;
commands.executeCommand('vscode.open', Uri.parse(selectedSchema));
quickPick.dispose();
});
});
@ -103,20 +46,19 @@ export function createLanguageStatusItem(documentSelector: string[], statusReque
if (schemas.length === 0) {
statusItem.text = localize('status.noSchema', 'Validated without JSON schema');
} 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]
tooltip: schemas[0],
arguments: [Uri.parse(schemas[0])]
};
} 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]
arguments: [{ schemas }]
};
}
} catch (e) {
@ -137,3 +79,5 @@ export function createLanguageStatusItem(documentSelector: string[], statusReque
return Disposable.from(statusItem, activeEditorListener, showSchemasCommand);
}

View file

@ -87,12 +87,7 @@
"language": "markdown",
"path": "./snippets/markdown.code-snippets"
}
],
"configurationDefaults": {
"[markdown]": {
"editor.unicodeHighlight.ambiguousCharacters": false
}
}
]
},
"scripts": {
"update-grammar": "node ../node_modules/vscode-grammar-updater/bin microsoft/vscode-markdown-tm-grammar syntaxes/markdown.tmLanguage ./syntaxes/markdown.tmLanguage.json"

View file

@ -142,16 +142,20 @@ export class AzureActiveDirectoryService {
} catch (e) {
// If we aren't connected to the internet, then wait and try to refresh again later.
if (e.message === REFRESH_NETWORK_FAILURE) {
this._tokens.push({
accessToken: undefined,
refreshToken: session.refreshToken,
account: {
label: session.account.label ?? session.account.displayName!,
id: session.account.id
},
scope: session.scope,
sessionId: session.id
});
const didSucceedOnRetry = await this.handleRefreshNetworkError(session.id, session.refreshToken, session.scope);
if (!didSucceedOnRetry) {
this._tokens.push({
accessToken: undefined,
refreshToken: session.refreshToken,
account: {
label: session.account.label ?? session.account.displayName!,
id: session.account.id
},
scope: session.scope,
sessionId: session.id
});
this.pollForReconnect(session.id, session.refreshToken, session.scope);
}
} else {
await this.removeSession(session.id);
}
@ -197,8 +201,9 @@ export class AzureActiveDirectoryService {
const token = await this.refreshToken(session.refreshToken, session.scope, session.id);
added.push(this.convertToSessionSync(token));
} catch (e) {
// Network failures will automatically retry on next poll.
if (e.message !== REFRESH_NETWORK_FAILURE) {
if (e.message === REFRESH_NETWORK_FAILURE) {
// Ignore, will automatically retry on next poll.
} else {
await this.removeSession(session.id);
}
}
@ -318,7 +323,7 @@ export class AzureActiveDirectoryService {
}
}
if (!scopes) {
const sessions = this._tokens.map(token => this.convertToSessionSync(token));
const sessions = await this.sessions;
Logger.info(`Got ${sessions.length} sessions for all scopes...`);
return sessions;
}
@ -524,7 +529,12 @@ export class AzureActiveDirectoryService {
Logger.info('Triggering change session event...');
onDidChangeSessions.fire({ added: [], removed: [], changed: [this.convertToSessionSync(refreshedToken)] });
} catch (e) {
if (e.message !== REFRESH_NETWORK_FAILURE) {
if (e.message === REFRESH_NETWORK_FAILURE) {
const didSucceedOnRetry = await this.handleRefreshNetworkError(token.sessionId, token.refreshToken, scope);
if (!didSucceedOnRetry) {
this.pollForReconnect(token.sessionId, token.refreshToken, token.scope);
}
} else {
await this.removeSession(token.sessionId);
onDidChangeSessions.fire({ added: [], removed: [this.convertToSessionSync(token)], changed: [] });
}
@ -581,9 +591,23 @@ export class AzureActiveDirectoryService {
const endpointUrl = proxyEndpoints?.microsoft || loginEndpointUrl;
const endpoint = `${endpointUrl}${tenant}/oauth2/v2.0/token`;
const json = await this.fetchTokenResponse(endpoint, postData, scope);
Logger.info(`Exchanging login code for token (for scopes: ${scope}) succeeded!`);
return this.getTokenFromResponse(json, scope);
const result = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': postData.length.toString()
},
body: postData
});
if (result.ok) {
const json = await result.json();
Logger.info(`Exchanging login code for token (for scopes: ${scope}) succeeded!`);
return this.getTokenFromResponse(json, scope);
} else {
Logger.error(`Exchanging login code for token (for scopes: ${scope}) failed: ${await result.text()}`);
throw new Error('Unable to login.');
}
} catch (e) {
Logger.error(`Error exchanging code for token (for scopes ${scope}): ${e}`);
throw e;
@ -600,46 +624,6 @@ export class AzureActiveDirectoryService {
}
}
private async fetchTokenResponse(endpoint: string, postData: string, scopes: string): Promise<ITokenResponse> {
let attempts = 0;
while (attempts <= 3) {
attempts++;
let result: Response | undefined;
let errorMessage: string | undefined;
try {
result = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': postData.length.toString()
},
body: postData
});
} catch (e) {
errorMessage = e.message ?? e;
}
if (!result || result.status > 499) {
if (attempts > 3) {
Logger.error(`Fetching token failed for scopes (${scopes}): ${result ? await result.text() : errorMessage}`);
break;
}
// Exponential backoff
await new Promise(resolve => setTimeout(resolve, 5 * attempts * attempts * 1000));
continue;
} else if (!result.ok) {
// For 4XX errors, the user may actually have an expired token or have changed
// their password recently which is throwing a 4XX. For this, we throw an error
// so that the user can be prompted to sign in again.
throw new Error(await result.text());
}
return await result.json() as ITokenResponse;
}
throw new Error(REFRESH_NETWORK_FAILURE);
}
private async doRefreshToken(refreshToken: string, scope: string, sessionId: string): Promise<IToken> {
Logger.info(`Refreshing token for scopes: ${scope}`);
const postData = querystring.stringify({
@ -649,25 +633,37 @@ export class AzureActiveDirectoryService {
scope: scope
});
const proxyEndpoints: { [providerId: string]: string } | undefined = await vscode.commands.executeCommand('workbench.getCodeExchangeProxyEndpoints');
const endpointUrl = proxyEndpoints?.microsoft || loginEndpointUrl;
const endpoint = `${endpointUrl}${tenant}/oauth2/v2.0/token`;
let result: Response;
try {
const proxyEndpoints: { [providerId: string]: string } | undefined = await vscode.commands.executeCommand('workbench.getCodeExchangeProxyEndpoints');
const endpointUrl = proxyEndpoints?.microsoft || loginEndpointUrl;
const endpoint = `${endpointUrl}${tenant}/oauth2/v2.0/token`;
result = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'Content-Length': postData.length.toString()
},
body: postData
});
} catch (e) {
Logger.error(`Refreshing token failed (for scopes: ${scope}) Error: ${e}`);
throw new Error(REFRESH_NETWORK_FAILURE);
}
try {
const json = await this.fetchTokenResponse(endpoint, postData, scope);
const token = this.getTokenFromResponse(json, scope, sessionId);
await this.setToken(token, scope);
Logger.info(`Token refresh success for scopes: ${token.scope}`);
return token;
} catch (e) {
if (e.message === REFRESH_NETWORK_FAILURE) {
// We were unable to refresh because of a network failure (i.e. the user lost internet access).
// so set up a timeout to try again later.
this.pollForReconnect(sessionId, refreshToken, scope);
throw e;
if (result.ok) {
const json = await result.json();
const token = this.getTokenFromResponse(json, scope, sessionId);
await this.setToken(token, scope);
Logger.info(`Token refresh success for scopes: ${token.scope}`);
return token;
} else {
throw new Error('Bad request.');
}
} catch (e) {
vscode.window.showErrorMessage(localize('signOut', "You have been signed out because reading stored authentication information failed."));
Logger.error(`Refreshing token failed (for scopes: ${scope}): ${e.message}`);
Logger.error(`Refreshing token failed (for scopes: ${scope}): ${result.statusText}`);
throw new Error('Refreshing token failed');
}
}
@ -694,7 +690,7 @@ export class AzureActiveDirectoryService {
private pollForReconnect(sessionId: string, refreshToken: string, scope: string): void {
this.clearSessionTimeout(sessionId);
Logger.trace(`Setting up reconnection timeout for scopes: ${scope}...`);
this._refreshTimeouts.set(sessionId, setTimeout(async () => {
try {
const refreshedToken = await this.refreshToken(refreshToken, scope, sessionId);
@ -705,6 +701,29 @@ export class AzureActiveDirectoryService {
}, 1000 * 60 * 30));
}
private handleRefreshNetworkError(sessionId: string, refreshToken: string, scope: string, attempts: number = 1): Promise<boolean> {
return new Promise((resolve, _) => {
if (attempts === 3) {
Logger.error(`Token refresh (for scopes: ${scope}) failed after 3 attempts`);
return resolve(false);
}
const delayBeforeRetry = 5 * attempts * attempts;
this.clearSessionTimeout(sessionId);
this._refreshTimeouts.set(sessionId, setTimeout(async () => {
try {
const refreshedToken = await this.refreshToken(refreshToken, scope, sessionId);
onDidChangeSessions.fire({ added: [], removed: [], changed: [this.convertToSessionSync(refreshedToken)] });
return resolve(true);
} catch (e) {
return resolve(await this.handleRefreshNetworkError(sessionId, refreshToken, scope, attempts + 1));
}
}, 1000 * delayBeforeRetry));
});
}
public async removeSession(sessionId: string): Promise<vscode.AuthenticationSession | undefined> {
Logger.info(`Logging out of session '${sessionId}'`);
const token = this.removeInMemorySessionData(sessionId);

View file

@ -168,7 +168,7 @@ export const keywords: IEntries = {
description: 'This language construct is equivalent to exit().',
},
echo: {
description: 'Outputs all parameters. \r\n\r\necho is not actually a function (it is a language construct), so you are not required to use parentheses with it. echo (unlike some other language constructs) does not behave like a function, so it cannot always be used in the context of a function. Additionally, if you want to pass more than one parameter to echo, the parameters must not be enclosed within parentheses.\r\n\r\necho also has a shortcut syntax, where you can immediately follow the opening tag with an equals sign. This short syntax only works with the short_open_tag configuration setting enabled.',
description: 'Outputs all parameters. \r\n\r\necho() is not actually a function (it is a language construct), so you are not required to use parentheses with it. echo() (unlike some other language constructs) does not behave like a function, so it cannot always be used in the context of a function. Additionally, if you want to pass more than one parameter to echo(), the parameters must not be enclosed within parentheses.\r\n\r\necho() also has a shortcut syntax, where you can immediately follow the opening tag with an equals sign. This short syntax only works with the short_open_tag configuration setting enabled.',
signature: '( string $arg1 [, string $... ] ): void'
},
empty: {
@ -184,10 +184,10 @@ export const keywords: IEntries = {
signature: '( string $code_str ): mixed'
},
include: {
description: 'The include statement includes and evaluates the specified file.',
description: 'The include() statement includes and evaluates the specified file.',
},
include_once: {
description: 'The include_once statement includes and evaluates the specified file during the execution of the script. This is a behavior similar to the include statement, with the only difference being that if the code from a file has already been included, it will not be included again. As the name suggests, it will be included just once. \r\n\r\ninclude_once may be used in cases where the same file might be included and evaluated more than once during a particular execution of a script, so in this case it may help avoid problems such as function redefinitions, variable value reassignments, etc.',
description: 'The include_once() statement includes and evaluates the specified file during the execution of the script. This is a behavior similar to the include() statement, with the only difference being that if the code from a file has already been included, it will not be included again. As the name suggests, it will be included just once. \r\n\r\ninclude_once() may be used in cases where the same file might be included and evaluated more than once during a particular execution of a script, so in this case it may help avoid problems such as function redefinitions, variable value reassignments, etc.',
},
isset: {
description: 'Determine if a variable is set and is not NULL. \r\n\r\nIf a variable has been unset with unset(), it will no longer be set. isset() will return FALSE if testing a variable that has been set to NULL. Also note that a NULL byte is not equivalent to the PHP NULL constant. \r\n\r\nIf multiple parameters are supplied then isset() will return TRUE only if all of the parameters are set. Evaluation goes from left to right and stops as soon as an unset variable is encountered.',
@ -198,13 +198,13 @@ export const keywords: IEntries = {
signature: '( mixed $varname [, mixed $... ] ): array'
},
require: {
description: 'require is identical to include except upon failure it will also produce a fatal E_COMPILE_ERROR level error. In other words, it will halt the script whereas include only emits a warning (E_WARNING) which allows the script to continue.',
description: 'require() is identical to include() except upon failure it will also produce a fatal E_COMPILE_ERROR level error. In other words, it will halt the script whereas include() only emits a warning (E_WARNING) which allows the script to continue.',
},
require_once: {
description: 'The require_once statement is identical to require except PHP will check if the file has already been included, and if so, not include (require) it again.',
description: 'The require_once() statement is identical to require() except PHP will check if the file has already been included, and if so, not include (require) it again.',
},
return: {
description: 'If called from within a function, the return statement immediately ends execution of the current function, and returns its argument as the value of the function call. return will also end the execution of an eval() statement or script file. \r\n\r\nIf called from the global scope, then execution of the current script file is ended. If the current script file was included or required, then control is passed back to the calling file. Furthermore, if the current script file was included, then the value given to return will be returned as the value of the include call. If return is called from within the main script file, then script execution ends. If the current script file was named by the auto_prepend_file or auto_append_file configuration options in php.ini, then that script file\'s execution is ended.',
description: 'If called from within a function, the return() statement immediately ends execution of the current function, and returns its argument as the value of the function call. return() will also end the execution of an eval() statement or script file. \r\n\r\nIf called from the global scope, then execution of the current script file is ended. If the current script file was include()ed or require()ed, then control is passed back to the calling file. Furthermore, if the current script file was include()ed, then the value given to return() will be returned as the value of the include() call. If return() is called from within the main script file, then script execution ends. If the current script file was named by the auto_prepend_file or auto_append_file configuration options in php.ini, then that script file\'s execution is ended.',
},
print: {
description: 'Outputs arg. \r\n\r\nprint() is not actually a real function (it is a language construct) so you are not required to use parentheses with its argument list.',
@ -321,4 +321,4 @@ export const keywords: IEntries = {
},
xor: {
},
};
};

View file

@ -6,7 +6,6 @@
"license": "MIT",
"enabledApiProposals": [
"authSession",
"contribViewsRemote",
"customEditorMove",
"diffCommand",
"documentFiltersExclusive",

View file

@ -11,6 +11,7 @@ interface QuickPickExpected {
events: string[];
activeItems: string[][];
selectionItems: string[][];
values: string[];
acceptedItems: {
active: string[][];
selection: string[][];
@ -18,6 +19,15 @@ interface QuickPickExpected {
};
}
interface InputBoxExpected {
events: string[];
values: string[];
accepted: {
values: string[];
dispose: boolean[];
};
}
suite('vscode API - quick input', function () {
teardown(async function () {
@ -35,6 +45,7 @@ suite('vscode API - quick input', function () {
events: ['active', 'active', 'selection', 'accept', 'hide'],
activeItems: [['eins'], ['zwei']],
selectionItems: [['zwei']],
values: [],
acceptedItems: {
active: [['zwei']],
selection: [['zwei']],
@ -61,6 +72,7 @@ suite('vscode API - quick input', function () {
events: ['active', 'selection', 'accept', 'hide'],
activeItems: [['zwei']],
selectionItems: [['zwei']],
values: [],
acceptedItems: {
active: [['zwei']],
selection: [['zwei']],
@ -87,6 +99,7 @@ suite('vscode API - quick input', function () {
events: ['active', 'selection', 'active', 'selection', 'accept', 'hide'],
activeItems: [['eins'], ['zwei']],
selectionItems: [['eins'], ['eins', 'zwei']],
values: [],
acceptedItems: {
active: [['zwei']],
selection: [['eins', 'zwei']],
@ -117,6 +130,7 @@ suite('vscode API - quick input', function () {
events: ['active', 'selection', 'accept', 'selection', 'accept', 'hide'],
activeItems: [['eins']],
selectionItems: [['zwei'], ['drei']],
values: [],
acceptedItems: {
active: [['eins'], ['eins']],
selection: [['zwei'], ['drei']],
@ -142,6 +156,7 @@ suite('vscode API - quick input', function () {
events: ['active', 'selection', 'accept', 'active', 'selection', 'active', 'selection', 'accept', 'hide'],
activeItems: [['eins'], [], ['drei']],
selectionItems: [['eins'], [], ['drei']],
values: [],
acceptedItems: {
active: [['eins'], ['drei']],
selection: [['eins'], ['drei']],
@ -163,6 +178,40 @@ suite('vscode API - quick input', function () {
.catch(err => done(err));
});
// NOTE: This test is currently accepting the wrong behavior of #135971
// so that we can test the fix for #137279.
test('createQuickPick, onDidChangeValue gets triggered', function (_done) {
let done = (err?: any) => {
done = () => { };
_done(err);
};
const quickPick = createQuickPick({
events: ['active', 'active', 'active', 'active', 'value', 'active', 'active', 'value', 'hide'],
activeItems: [['eins'], ['zwei'], [], ['zwei'], [], ['eins']],
selectionItems: [],
values: ['zwei', ''],
acceptedItems: {
active: [],
selection: [],
dispose: []
},
}, (err?: any) => done(err));
quickPick.items = ['eins', 'zwei'].map(label => ({ label }));
quickPick.show();
(async () => {
quickPick.value = 'zwei';
await timeout(async () => {
quickPick.value = '';
await timeout(async () => {
quickPick.hide();
}, 0);
}, 0);
})()
.catch(err => done(err));
});
test('createQuickPick, dispose in onDidHide', function (_done) {
let done = (err?: any) => {
done = () => { };
@ -248,6 +297,34 @@ suite('vscode API - quick input', function () {
quickPick.hide();
await waitForHide(quickPick);
});
test('createInputBox, onDidChangeValue gets triggered', function (_done) {
let done = (err?: any) => {
done = () => { };
_done(err);
};
const quickPick = createInputBox({
events: ['value', 'accept', 'hide'],
values: ['zwei'],
accepted: {
values: ['zwei'],
dispose: [true]
},
}, (err?: any) => done(err));
quickPick.show();
(async () => {
quickPick.value = 'zwei';
await timeout(async () => {
await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem');
await timeout(async () => {
quickPick.hide();
}, 0);
}, 0);
})()
.catch(err => done(err));
});
});
function createQuickPick(expected: QuickPickExpected, done: (err?: any) => void, record = false) {
@ -316,9 +393,78 @@ function createQuickPick(expected: QuickPickExpected, done: (err?: any) => void,
}
});
quickPick.onDidChangeValue(value => {
if (record) {
console.log('value');
return;
}
try {
eventIndex++;
assert.strictEqual('value', expected.events.shift(), `onDidChangeValue (event ${eventIndex})`);
const expectedValue = expected.values.shift();
assert.deepStrictEqual(value, expectedValue, `onDidChangeValue event value (event ${eventIndex})`);
} catch (err) {
done(err);
}
});
return quickPick;
}
function createInputBox(expected: InputBoxExpected, done: (err?: any) => void, record = false) {
const inputBox = window.createInputBox();
let eventIndex = -1;
inputBox.onDidAccept(() => {
if (record) {
console.log('accept');
return;
}
try {
eventIndex++;
assert.strictEqual('accept', expected.events.shift(), `onDidAccept (event ${eventIndex})`);
const expectedValue = expected.accepted.values.shift();
assert.deepStrictEqual(inputBox.value, expectedValue, `onDidAccept event value (event ${eventIndex})`);
if (expected.accepted.dispose.shift()) {
inputBox.dispose();
}
} catch (err) {
done(err);
}
});
inputBox.onDidHide(() => {
if (record) {
console.log('hide');
done();
return;
}
try {
assert.strictEqual('hide', expected.events.shift());
done();
} catch (err) {
done(err);
}
});
inputBox.onDidChangeValue(value => {
if (record) {
console.log('value');
return;
}
try {
eventIndex++;
assert.strictEqual('value', expected.events.shift(), `onDidChangeValue (event ${eventIndex})`);
const expectedValue = expected.values.shift();
assert.deepStrictEqual(value, expectedValue, `onDidChangeValue event value (event ${eventIndex})`);
} catch (err) {
done(err);
}
});
return inputBox;
}
async function timeout<T>(run: () => Promise<T> | T, ms: number): Promise<T> {
return new Promise<T>(resolve => setTimeout(() => resolve(run()), ms));
}

View file

@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.63.0",
"distro": "1aa3ab55b3cceca22ca6d647dc0095d562d23c8d",
"distro": "f5a2c7f01629f6d3c0dc65e3fbb3818033206bf2",
"author": {
"name": "Microsoft Corporation"
},
@ -11,7 +11,6 @@
"scripts": {
"test": "mocha",
"test-browser": "node test/unit/browser/index.js",
"test-node": "mocha test/unit/node/index.js --delay --ui=tdd --exit",
"preinstall": "node build/npm/preinstall.js",
"postinstall": "node build/npm/postinstall.js",
"compile": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js compile",
@ -28,6 +27,7 @@
"watch-extensions": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js watch-extensions watch-extension-media",
"watch-extensionsd": "deemon yarn watch-extensions",
"kill-watch-extensionsd": "deemon --kill yarn watch-extensions",
"mocha": "mocha test/unit/node/all.js --delay",
"precommit": "node build/hygiene.js",
"gulp": "node --max_old_space_size=8192 ./node_modules/gulp/bin/gulp.js",
"electron": "node build/lib/electron",
@ -55,12 +55,11 @@
"minify-vscode-reh-web": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js minify-vscode-reh-web",
"hygiene": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js hygiene",
"core-ci": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js core-ci",
"extensions-ci": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js extensions-ci",
"prefetch-headers": "node build/prefetchHeaders.js"
"extensions-ci": "node --max_old_space_size=4095 ./node_modules/gulp/bin/gulp.js extensions-ci"
},
"dependencies": {
"@microsoft/applicationinsights-web": "^2.6.4",
"@parcel/watcher": "2.0.3",
"@parcel/watcher": "2.0.2",
"@vscode/sqlite3": "4.0.12",
"@vscode/sudo-prompt": "9.3.1",
"@vscode/vscode-languagedetection": "1.0.21",
@ -68,12 +67,12 @@
"graceful-fs": "4.2.8",
"http-proxy-agent": "^2.1.0",
"https-proxy-agent": "^2.2.3",
"iconv-lite-umd": "0.6.10",
"iconv-lite-umd": "0.6.8",
"jschardet": "3.0.0",
"keytar": "7.2.0",
"minimist": "^1.2.5",
"native-is-elevated": "0.4.3",
"native-keymap": "3.0.2",
"native-keymap": "3.0.1",
"native-watchdog": "1.3.0",
"node-pty": "0.11.0-beta11",
"spdlog": "^0.13.0",
@ -141,7 +140,7 @@
"file-loader": "^4.2.0",
"glob": "^5.0.13",
"gulp": "^4.0.0",
"gulp-atom-electron": "^1.32.1",
"gulp-atom-electron": "1.32.0",
"gulp-azure-storage": "^0.12.1",
"gulp-bom": "^3.0.0",
"gulp-buffer": "0.0.2",
@ -170,6 +169,7 @@
"istanbul-lib-report": "^3.0.0",
"istanbul-lib-source-maps": "^4.0.0",
"istanbul-reports": "^3.0.0",
"jsdom-no-contextify": "^3.1.0",
"lazy.js": "^0.4.2",
"merge-options": "^1.0.1",
"mime": "^1.4.1",

View file

@ -7,8 +7,6 @@
"licenseName": "MIT",
"licenseUrl": "https://github.com/microsoft/vscode/blob/main/LICENSE.txt",
"serverGreeting": [],
"serverLicense": [],
"serverLicensePrompt": "",
"win32DirName": "Microsoft Code OSS",
"win32NameVersion": "Microsoft Code OSS",
"win32RegValueName": "CodeOSS",

View file

@ -4,14 +4,14 @@
"private": true,
"dependencies": {
"@microsoft/applicationinsights-web": "^2.6.4",
"@parcel/watcher": "2.0.3",
"@parcel/watcher": "2.0.2",
"@vscode/vscode-languagedetection": "1.0.21",
"applicationinsights": "1.0.8",
"cookie": "^0.4.0",
"graceful-fs": "4.2.8",
"http-proxy-agent": "^2.1.0",
"https-proxy-agent": "^2.2.3",
"iconv-lite-umd": "0.6.10",
"iconv-lite-umd": "0.6.8",
"jschardet": "3.0.0",
"minimist": "^1.2.5",
"native-watchdog": "1.3.0",

View file

@ -5,7 +5,7 @@
"dependencies": {
"@microsoft/applicationinsights-web": "^2.6.4",
"@vscode/vscode-languagedetection": "1.0.21",
"iconv-lite-umd": "0.6.10",
"iconv-lite-umd": "0.6.8",
"jschardet": "3.0.0",
"tas-client-umd": "0.1.4",
"vscode-oniguruma": "1.6.1",

View file

@ -88,10 +88,10 @@
resolved "https://registry.yarnpkg.com/@vscode/vscode-languagedetection/-/vscode-languagedetection-1.0.21.tgz#89b48f293f6aa3341bb888c1118d16ff13b032d3"
integrity sha512-zSUH9HYCw5qsCtd7b31yqkpaCU6jhtkKLkvOOA8yTrIRfBSOFb8PPhgmMicD7B/m+t4PwOJXzU1XDtrM9Fd3/g==
iconv-lite-umd@0.6.10:
version "0.6.10"
resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.10.tgz#faec47521e095b8e3a7175ae08e1b4ae0359a735"
integrity sha512-8NtgTa/m1jVq7vdywmD5+SqIlZsB59wtsjaylQuExyCojMq1tHVQxmHjeqVSYwKwnmQbH4mZ1Dxx1eqDkPgaqA==
iconv-lite-umd@0.6.8:
version "0.6.8"
resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.8.tgz#5ad310ec126b260621471a2d586f7f37b9958ec0"
integrity sha512-zvXJ5gSwMC9JD3wDzH8CoZGc1pbiJn12Tqjk8BXYCnYz3hYL5GRjHW8LEykjXhV9WgNGI4rgpgHcbIiBfrRq6A==
jschardet@3.0.0:
version "3.0.0"

View file

@ -83,10 +83,10 @@
resolved "https://registry.yarnpkg.com/@microsoft/dynamicproto-js/-/dynamicproto-js-1.1.4.tgz#40e1c0ad20743fcee1604a7df2c57faf0aa1af87"
integrity sha512-Ot53G927ykMF8cQ3/zq4foZtdk+Tt1YpX7aUTHxBU7UHNdkEiBvBfZSq+rnlUmKCJ19VatwPG4mNzvcGpBj4og==
"@parcel/watcher@2.0.3":
version "2.0.3"
resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.3.tgz#2bae7720f2b9c21ea0b89bab55479c7e8937231e"
integrity sha512-PHh5PArr3nYGYVj9z/NSfDmmKEBNrg2bzoFgxzjTRBBxPUKx039x3HF6VGLFIfrghjJxcYn/IeSpdVwfob7KFA==
"@parcel/watcher@2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@parcel/watcher/-/watcher-2.0.2.tgz#46bef14584497147bad5247cfb41f80b24d24dfb"
integrity sha512-WGJY55/mTAGse2C9VVi2oo+p05oJ0kiSHmOjV33+ywgKgUkUh6B/qFQ5kBO/9mH686qqtV3k2zH1QNm+XX4+lw==
dependencies:
node-addon-api "^3.2.1"
node-gyp-build "^4.3.0"
@ -305,10 +305,10 @@ https-proxy-agent@^5.0.0:
agent-base "6"
debug "4"
iconv-lite-umd@0.6.10:
version "0.6.10"
resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.10.tgz#faec47521e095b8e3a7175ae08e1b4ae0359a735"
integrity sha512-8NtgTa/m1jVq7vdywmD5+SqIlZsB59wtsjaylQuExyCojMq1tHVQxmHjeqVSYwKwnmQbH4mZ1Dxx1eqDkPgaqA==
iconv-lite-umd@0.6.8:
version "0.6.8"
resolved "https://registry.yarnpkg.com/iconv-lite-umd/-/iconv-lite-umd-0.6.8.tgz#5ad310ec126b260621471a2d586f7f37b9958ec0"
integrity sha512-zvXJ5gSwMC9JD3wDzH8CoZGc1pbiJn12Tqjk8BXYCnYz3hYL5GRjHW8LEykjXhV9WgNGI4rgpgHcbIiBfrRq6A==
inherits@~2.0.1:
version "2.0.4"

View file

@ -37,7 +37,7 @@ const ALLOWED_CORS_ORIGINS = [
'http://127.0.0.1:8080',
];
const WEB_PLAYGROUND_VERSION = '0.0.13';
const WEB_PLAYGROUND_VERSION = '0.0.12';
const args = minimist(process.argv, {
boolean: [

View file

@ -939,15 +939,9 @@ class FocusTracker extends Disposable implements IFocusTracker {
private _refreshStateHandler: () => void;
private static hasFocusWithin(element: HTMLElement): boolean {
const shadowRoot = getShadowRoot(element);
const activeElement = (shadowRoot ? shadowRoot.activeElement : document.activeElement);
return isAncestor(activeElement, element);
}
constructor(element: HTMLElement | Window) {
super();
let hasFocus = FocusTracker.hasFocusWithin(<HTMLElement>element);
let hasFocus = isAncestor(document.activeElement, <HTMLElement>element);
let loosingFocus = false;
const onFocus = () => {
@ -972,7 +966,7 @@ class FocusTracker extends Disposable implements IFocusTracker {
};
this._refreshStateHandler = () => {
let currentNodeHasFocus = FocusTracker.hasFocusWithin(<HTMLElement>element);
let currentNodeHasFocus = isAncestor(document.activeElement, <HTMLElement>element);
if (currentNodeHasFocus !== hasFocus) {
if (hasFocus) {
onBlur();
@ -984,8 +978,6 @@ class FocusTracker extends Disposable implements IFocusTracker {
this._register(addDisposableListener(element, EventType.FOCUS, onFocus, true));
this._register(addDisposableListener(element, EventType.BLUR, onBlur, true));
this._register(addDisposableListener(element, EventType.FOCUS_IN, () => this._refreshStateHandler()));
this._register(addDisposableListener(element, EventType.FOCUS_OUT, () => this._refreshStateHandler()));
}
refreshState() {

View file

@ -178,7 +178,10 @@ export class ActionWithDropdownActionViewItem extends ActionViewItem {
const menuActionsProvider = {
getActions: () => {
const actionsProvider = (<IActionWithDropdownActionViewItemOptions>this.options).menuActionsOrProvider;
return Array.isArray(actionsProvider) ? actionsProvider : (actionsProvider as IActionProvider).getActions(); // TODO: microsoft/TypeScript#42768
return [this._action, ...(Array.isArray(actionsProvider)
? actionsProvider
: (actionsProvider as IActionProvider).getActions()) // TODO: microsoft/TypeScript#42768
];
}
};
this.dropdownMenuActionViewItem = new DropdownMenuActionViewItem(this._register(new Action('dropdownAction', undefined)), menuActionsProvider, this.contextMenuProvider, { classNames: ['dropdown', ...Codicon.dropDownButton.classNamesArray, ...(<IActionWithDropdownActionViewItemOptions>this.options).menuActionClassNames || []] });

View file

@ -254,7 +254,7 @@ export function topAsync<T>(array: T[], compare: (a: T, b: T) => number, n: numb
const result = array.slice(0, n).sort(compare);
for (let i = n, m = Math.min(n + batch, o); i < o; i = m, m = Math.min(m + batch, o)) {
if (i > n) {
await new Promise(resolve => setTimeout(resolve)); // any other delay function would starve I/O
await new Promise(resolve => setTimeout(resolve)); // nextTick() would starve I/O.
}
if (token && token.isCancellationRequested) {
throw canceled();

View file

@ -9,7 +9,6 @@ import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IDisposable, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { extUri as defaultExtUri, IExtUri } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { setTimeout0 } from 'vs/base/common/platform';
export function isThenable<T>(obj: unknown): obj is Promise<T> {
return !!obj && typeof (obj as unknown as Promise<T>).then === 'function';
@ -969,7 +968,6 @@ export interface IdleDeadline {
readonly didTimeout: boolean;
timeRemaining(): number;
}
/**
* Execute the callback the next time the browser is idle
*/
@ -981,11 +979,8 @@ declare function cancelIdleCallback(handle: number): void;
(function () {
if (typeof requestIdleCallback !== 'function' || typeof cancelIdleCallback !== 'function') {
runWhenIdle = (runner) => {
setTimeout0(() => {
if (disposed) {
return;
}
const end = Date.now() + 3; // yield often
const handle = setTimeout(() => {
const end = Date.now() + 15; // one frame at 64fps
runner(Object.freeze({
didTimeout: true,
timeRemaining() {
@ -1000,6 +995,7 @@ declare function cancelIdleCallback(handle: number): void;
return;
}
disposed = true;
clearTimeout(handle);
}
};
};
@ -1203,10 +1199,10 @@ export class IntervalCounter {
private value = 0;
constructor(private readonly interval: number, private readonly nowFn = () => Date.now()) { }
constructor(private readonly interval: number) { }
increment(): number {
const now = this.nowFn();
const now = Date.now();
// We are outside of the range of `interval` and as such
// start counting from 0 and remember the time
@ -1337,281 +1333,3 @@ export namespace Promises {
}
//#endregion
//#region
const enum AsyncIterableSourceState {
Initial,
DoneOK,
DoneError,
}
/**
* An object that allows to emit async values asynchronously or bring the iterable to an error state using `reject()`.
* This emitter is valid only for the duration of the executor (until the promise returned by the executor settles).
*/
export interface AsyncIterableEmitter<T> {
/**
* The value will be appended at the end.
*
* **NOTE** If `reject()` has already been called, this method has no effect.
*/
emitOne(value: T): void;
/**
* The values will be appended at the end.
*
* **NOTE** If `reject()` has already been called, this method has no effect.
*/
emitMany(values: T[]): void;
/**
* Writing an error will permanently invalidate this iterable.
* The current users will receive an error thrown, as will all future users.
*
* **NOTE** If `reject()` have already been called, this method has no effect.
*/
reject(error: Error): void;
}
/**
* An executor for the `AsyncIterableObject` that has access to an emitter.
*/
export interface AyncIterableExecutor<T> {
/**
* @param emitter An object that allows to emit async values valid only for the duration of the executor.
*/
(emitter: AsyncIterableEmitter<T>): void | Promise<void>
}
/**
* A rich implementation for an `AsyncIterable<T>`.
*/
export class AsyncIterableObject<T> implements AsyncIterable<T> {
public static fromArray<T>(items: T[]): AsyncIterableObject<T> {
return new AsyncIterableObject<T>((writer) => {
writer.emitMany(items);
});
}
public static fromPromise<T>(promise: Promise<T[]>): AsyncIterableObject<T> {
return new AsyncIterableObject<T>(async (emitter) => {
emitter.emitMany(await promise);
});
}
public static fromPromises<T>(promises: Promise<T>[]): AsyncIterableObject<T> {
return new AsyncIterableObject<T>(async (emitter) => {
await Promise.all(promises.map(async (p) => emitter.emitOne(await p)));
});
}
public static merge<T>(iterables: AsyncIterable<T>[]): AsyncIterableObject<T> {
return new AsyncIterableObject(async (emitter) => {
await Promise.all(iterables.map(async (iterable) => {
for await(const item of iterable) {
emitter.emitOne(item);
}
}));
});
}
public static EMPTY = AsyncIterableObject.fromArray<any>([]);
private _state: AsyncIterableSourceState;
private _results: T[];
private _error: Error | null;
private readonly _onStateChanged: Emitter<void>;
constructor(executor: AyncIterableExecutor<T>) {
this._state = AsyncIterableSourceState.Initial;
this._results = [];
this._error = null;
this._onStateChanged = new Emitter<void>();
queueMicrotask(async () => {
const writer: AsyncIterableEmitter<T> = {
emitOne: (item) => this.emitOne(item),
emitMany: (items) => this.emitMany(items),
reject: (error) => this.reject(error)
};
try {
await Promise.resolve(executor(writer));
this.resolve();
} catch (err) {
this.reject(err);
} finally {
writer.emitOne = undefined!;
writer.emitMany = undefined!;
writer.reject = undefined!;
}
});
}
[Symbol.asyncIterator](): AsyncIterator<T, undefined, undefined> {
let i = 0;
return {
next: async () => {
do {
if (this._state === AsyncIterableSourceState.DoneError) {
throw this._error;
}
if (i < this._results.length) {
return { done: false, value: this._results[i++] };
}
if (this._state === AsyncIterableSourceState.DoneOK) {
return { done: true, value: undefined };
}
await Event.toPromise(this._onStateChanged.event);
} while (true);
}
};
}
public static map<T, R>(iterable: AsyncIterable<T>, mapFn: (item: T) => R): AsyncIterableObject<R> {
return new AsyncIterableObject<R>(async (emitter) => {
for await(const item of iterable) {
emitter.emitOne(mapFn(item));
}
});
}
public map<R>(mapFn: (item: T) => R): AsyncIterableObject<R> {
return AsyncIterableObject.map(this, mapFn);
}
public static filter<T>(iterable: AsyncIterable<T>, filterFn: (item: T) => boolean): AsyncIterableObject<T> {
return new AsyncIterableObject<T>(async (emitter) => {
for await(const item of iterable) {
if (filterFn(item)) {
emitter.emitOne(item);
}
}
});
}
public filter(filterFn: (item: T) => boolean): AsyncIterableObject<T> {
return AsyncIterableObject.filter(this, filterFn);
}
public static coalesce<T>(iterable: AsyncIterable<T | undefined | null>): AsyncIterableObject<T> {
return <AsyncIterableObject<T>>AsyncIterableObject.filter(iterable, item => !!item);
}
public coalesce(): AsyncIterableObject<NonNullable<T>> {
return AsyncIterableObject.coalesce(this) as AsyncIterableObject<NonNullable<T>>;
}
public static async toPromise<T>(iterable: AsyncIterable<T>): Promise<T[]> {
const result: T[] = [];
for await (const item of iterable) {
result.push(item);
}
return result;
}
public toPromise(): Promise<T[]> {
return AsyncIterableObject.toPromise(this);
}
/**
* The value will be appended at the end.
*
* **NOTE** If `resolve()` or `reject()` have already been called, this method has no effect.
*/
private emitOne(value: T): void {
if (this._state !== AsyncIterableSourceState.Initial) {
return;
}
// it is important to add new values at the end,
// as we may have iterators already running on the array
this._results.push(value);
this._onStateChanged.fire();
}
/**
* The values will be appended at the end.
*
* **NOTE** If `resolve()` or `reject()` have already been called, this method has no effect.
*/
private emitMany(values: T[]): void {
if (this._state !== AsyncIterableSourceState.Initial) {
return;
}
// it is important to add new values at the end,
// as we may have iterators already running on the array
this._results = this._results.concat(values);
this._onStateChanged.fire();
}
/**
* Calling `resolve()` will mark the result array as complete.
*
* **NOTE** `resolve()` must be called, otherwise all consumers of this iterable will hang indefinitely, similar to a non-resolved promise.
* **NOTE** If `resolve()` or `reject()` have already been called, this method has no effect.
*/
private resolve(): void {
if (this._state !== AsyncIterableSourceState.Initial) {
return;
}
this._state = AsyncIterableSourceState.DoneOK;
this._onStateChanged.fire();
}
/**
* Writing an error will permanently invalidate this iterable.
* The current users will receive an error thrown, as will all future users.
*
* **NOTE** If `resolve()` or `reject()` have already been called, this method has no effect.
*/
private reject(error: Error) {
if (this._state !== AsyncIterableSourceState.Initial) {
return;
}
this._state = AsyncIterableSourceState.DoneError;
this._error = error;
this._onStateChanged.fire();
}
}
export class CancelableAsyncIterableObject<T> extends AsyncIterableObject<T> {
constructor(
private readonly _source: CancellationTokenSource,
executor: AyncIterableExecutor<T>
) {
super(executor);
}
cancel(): void {
this._source.cancel();
}
}
export function createCancelableAsyncIterable<T>(callback: (token: CancellationToken) => AsyncIterable<T>): CancelableAsyncIterableObject<T> {
const source = new CancellationTokenSource();
const innerIterable = callback(source.token);
return new CancelableAsyncIterableObject<T>(source, async (emitter) => {
const subscription = source.token.onCancellationRequested(() => {
subscription.dispose();
source.dispose();
emitter.reject(canceled());
});
try {
for await (const item of innerIterable) {
if (source.token.isCancellationRequested) {
// canceled in the meantime
return;
}
emitter.emitOne(item);
}
subscription.dispose();
source.dispose();
} catch (err) {
subscription.dispose();
source.dispose();
emitter.reject(err);
}
});
}
//#endregion

View file

@ -43,14 +43,6 @@ export class VSBuffer {
}
}
static fromByteArray(source: number[]): VSBuffer {
const result = VSBuffer.alloc(source.length);
for (let i = 0, len = source.length; i < len; i++) {
result.buffer[i] = source[i];
}
return result;
}
static concat(buffers: VSBuffer[], totalLength?: number): VSBuffer {
if (typeof totalLength === 'undefined') {
totalLength = 0;
@ -78,12 +70,6 @@ export class VSBuffer {
this.byteLength = this.buffer.byteLength;
}
clone(): VSBuffer {
const result = VSBuffer.alloc(this.byteLength);
result.set(this);
return result;
}
toString(): string {
if (hasBuffer) {
return this.buffer.toString();
@ -104,20 +90,11 @@ export class VSBuffer {
set(array: VSBuffer, offset?: number): void;
set(array: Uint8Array, offset?: number): void;
set(array: ArrayBuffer, offset?: number): void;
set(array: ArrayBufferView, offset?: number): void;
set(array: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView, offset?: number): void;
set(array: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView, offset?: number): void {
set(array: VSBuffer | Uint8Array, offset?: number): void {
if (array instanceof VSBuffer) {
this.buffer.set(array.buffer, offset);
} else if (array instanceof Uint8Array) {
this.buffer.set(array, offset);
} else if (array instanceof ArrayBuffer) {
this.buffer.set(new Uint8Array(array), offset);
} else if (ArrayBuffer.isView(array)) {
this.buffer.set(new Uint8Array(array.buffer, array.byteOffset, array.byteLength), offset);
} else {
throw new Error(`Unkown argument 'array'`);
this.buffer.set(array, offset);
}
}

View file

@ -198,7 +198,7 @@ export namespace Event {
/**
* @deprecated DO NOT use, this leaks memory
*/
export function buffer<T>(event: Event<T>, flushAfterTimeout = false, _buffer: T[] = []): Event<T> {
export function buffer<T>(event: Event<T>, nextTick = false, _buffer: T[] = []): Event<T> {
let buffer: T[] | null = _buffer.slice();
let listener: IDisposable | null = event(e => {
@ -225,7 +225,7 @@ export namespace Event {
onFirstListenerDidAdd() {
if (buffer) {
if (flushAfterTimeout) {
if (nextTick) {
setTimeout(flush);
} else {
flush();

View file

@ -40,9 +40,7 @@
*/
function _define() {
// Identify browser environment when following property is not present
// https://nodejs.org/dist/latest-v16.x/docs/api/perf_hooks.html#performancenodetiming
if (typeof performance === 'object' && typeof performance.mark === 'function' && !performance.nodeTiming) {
if (typeof performance === 'object' && typeof performance.mark === 'function') {
// in a browser context, reuse performance-util
if (typeof performance.timeOrigin !== 'number' && !performance.timing) {

View file

@ -11,7 +11,6 @@ let _isLinux = false;
let _isLinuxSnap = false;
let _isNative = false;
let _isWeb = false;
let _isElectron = false;
let _isIOS = false;
let _locale: string | undefined = undefined;
let _language: string = LANGUAGE_DEFAULT;
@ -39,6 +38,7 @@ export interface INodeProcess {
platform: string;
arch: string;
env: IProcessEnvironment;
nextTick?: (callback: (...args: any[]) => void) => void;
versions?: {
electron?: string;
};
@ -62,8 +62,7 @@ if (typeof globals.vscode !== 'undefined' && typeof globals.vscode.process !== '
nodeProcess = process;
}
const isElectronProcess = typeof nodeProcess?.versions?.electron === 'string';
const isElectronRenderer = isElectronProcess && nodeProcess?.type === 'renderer';
const isElectronRenderer = typeof nodeProcess?.versions?.electron === 'string' && nodeProcess.type === 'renderer';
export const isElectronSandboxed = isElectronRenderer && nodeProcess?.sandboxed;
interface INavigator {
@ -91,7 +90,6 @@ else if (typeof nodeProcess === 'object') {
_isMacintosh = (nodeProcess.platform === 'darwin');
_isLinux = (nodeProcess.platform === 'linux');
_isLinuxSnap = _isLinux && !!nodeProcess.env['SNAP'] && !!nodeProcess.env['SNAP_REVISION'];
_isElectron = isElectronProcess;
_locale = LANGUAGE_DEFAULT;
_language = LANGUAGE_DEFAULT;
const rawNlsConfig = nodeProcess.env['VSCODE_NLS_CONFIG'];
@ -143,7 +141,6 @@ export const isMacintosh = _isMacintosh;
export const isLinux = _isLinux;
export const isLinuxSnap = _isLinuxSnap;
export const isNative = _isNative;
export const isElectron = _isElectron;
export const isWeb = _isWeb;
export const isIOS = _isIOS;
export const platform = _platform;
@ -189,13 +186,14 @@ export const locale = _locale;
*/
export const translationsConfigFile = _translationsConfigFile;
/**
* See https://html.spec.whatwg.org/multipage/timers-and-user-prompts.html#:~:text=than%204%2C%20then-,set%20timeout%20to%204,-.
*
* Works similarly to `setTimeout(0)` but doesn't suffer from the 4ms artificial delay
* that browsers set when the nesting level is > 5.
*/
export const setTimeout0 = (() => {
interface ISetImmediate {
(callback: (...args: unknown[]) => void): void;
}
export const setImmediate: ISetImmediate = (function defineSetImmediate() {
if (globals.setImmediate) {
return globals.setImmediate.bind(globals);
}
if (typeof globals.postMessage === 'function' && !globals.importScripts) {
interface IQueueElement {
id: number;
@ -203,10 +201,10 @@ export const setTimeout0 = (() => {
}
let pending: IQueueElement[] = [];
globals.addEventListener('message', (e: MessageEvent) => {
if (e.data && e.data.vscodeScheduleAsyncWork) {
if (e.data && e.data.vscodeSetImmediateId) {
for (let i = 0, len = pending.length; i < len; i++) {
const candidate = pending[i];
if (candidate.id === e.data.vscodeScheduleAsyncWork) {
if (candidate.id === e.data.vscodeSetImmediateId) {
pending.splice(i, 1);
candidate.callback();
return;
@ -221,10 +219,14 @@ export const setTimeout0 = (() => {
id: myId,
callback: callback
});
globals.postMessage({ vscodeScheduleAsyncWork: myId }, '*');
globals.postMessage({ vscodeSetImmediateId: myId }, '*');
};
}
return (callback: () => void) => setTimeout(callback);
if (typeof nodeProcess?.nextTick === 'function') {
return nodeProcess.nextTick.bind(nodeProcess);
}
const _promise = Promise.resolve();
return (callback: (...args: unknown[]) => void) => _promise.then(callback);
})();
export const enum OperatingSystem {

View file

@ -3,9 +3,9 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { globals, INodeProcess, isMacintosh, isWindows } from 'vs/base/common/platform';
import { globals, INodeProcess, isMacintosh, isWindows, setImmediate } from 'vs/base/common/platform';
let safeProcess: Omit<INodeProcess, 'arch'> & { arch: string | undefined; };
let safeProcess: Omit<INodeProcess, 'arch'> & { nextTick: (callback: (...args: any[]) => void) => void; arch: string | undefined; };
declare const process: INodeProcess;
// Native sandbox environment
@ -15,7 +15,8 @@ if (typeof globals.vscode !== 'undefined' && typeof globals.vscode.process !== '
get platform() { return sandboxProcess.platform; },
get arch() { return sandboxProcess.arch; },
get env() { return sandboxProcess.env; },
cwd() { return sandboxProcess.cwd(); }
cwd() { return sandboxProcess.cwd(); },
nextTick(callback: (...args: any[]) => void): void { return setImmediate(callback); }
};
}
@ -25,7 +26,8 @@ else if (typeof process !== 'undefined') {
get platform() { return process.platform; },
get arch() { return process.arch; },
get env() { return process.env; },
cwd() { return process.env['VSCODE_CWD'] || process.cwd(); }
cwd() { return process.env['VSCODE_CWD'] || process.cwd(); },
nextTick(callback: (...args: any[]) => void): void { return process.nextTick!(callback); }
};
}
@ -36,6 +38,7 @@ else {
// Supported
get platform() { return isWindows ? 'win32' : isMacintosh ? 'darwin' : 'linux'; },
get arch() { return undefined; /* arch is undefined in web */ },
nextTick(callback: (...args: any[]) => void): void { return setImmediate(callback); },
// Unsupported
get env() { return {}; },
@ -65,6 +68,12 @@ export const env = safeProcess.env;
*/
export const platform = safeProcess.platform;
/**
* Provides safe access to the `nextTick` method in node.js, sandboxed or web
* environments.
*/
export const nextTick = safeProcess.nextTick;
/**
* Provides safe access to the `arch` method in node.js, sandboxed or web
* environments.

View file

@ -120,8 +120,6 @@ export interface IProductConfiguration {
readonly showTelemetryOptOut?: boolean;
readonly serverGreeting: string[];
readonly serverLicense?: string[];
readonly serverLicensePrompt?: string;
readonly npsSurveyUrl?: string;
readonly cesSurveyUrl?: string;

File diff suppressed because one or more lines are too long

View file

@ -287,7 +287,7 @@ export function NotImplementedProxy<T>(name: string): { new(): T } {
};
}
export function assertNever(value: never, message = 'Unreachable'): never {
export function assertNever(value: never, message = 'Unreachable') {
throw new Error(message);
}

View file

@ -6,91 +6,10 @@
import { VSBuffer } from 'vs/base/common/buffer';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, dispose, IDisposable } from 'vs/base/common/lifecycle';
import * as platform from 'vs/base/common/platform';
import * as process from 'vs/base/common/process';
import { IIPCLogger, IMessagePassingProtocol, IPCClient } from 'vs/base/parts/ipc/common/ipc';
export const enum SocketDiagnosticsEventType {
Created = 'created',
Read = 'read',
Write = 'write',
Open = 'open',
Error = 'error',
Close = 'close',
BrowserWebSocketBlobReceived = 'browserWebSocketBlobReceived',
NodeEndReceived = 'nodeEndReceived',
NodeEndSent = 'nodeEndSent',
NodeDrainBegin = 'nodeDrainBegin',
NodeDrainEnd = 'nodeDrainEnd',
zlibInflateError = 'zlibInflateError',
zlibInflateData = 'zlibInflateData',
zlibInflateInitialWrite = 'zlibInflateInitialWrite',
zlibInflateInitialFlushFired = 'zlibInflateInitialFlushFired',
zlibInflateWrite = 'zlibInflateWrite',
zlibInflateFlushFired = 'zlibInflateFlushFired',
zlibDeflateError = 'zlibDeflateError',
zlibDeflateData = 'zlibDeflateData',
zlibDeflateWrite = 'zlibDeflateWrite',
zlibDeflateFlushFired = 'zlibDeflateFlushFired',
WebSocketNodeSocketWrite = 'webSocketNodeSocketWrite',
WebSocketNodeSocketPeekedHeader = 'webSocketNodeSocketPeekedHeader',
WebSocketNodeSocketReadHeader = 'webSocketNodeSocketReadHeader',
WebSocketNodeSocketReadData = 'webSocketNodeSocketReadData',
WebSocketNodeSocketUnmaskedData = 'webSocketNodeSocketUnmaskedData',
WebSocketNodeSocketDrainBegin = 'webSocketNodeSocketDrainBegin',
WebSocketNodeSocketDrainEnd = 'webSocketNodeSocketDrainEnd',
ProtocolHeaderRead = 'protocolHeaderRead',
ProtocolMessageRead = 'protocolMessageRead',
ProtocolHeaderWrite = 'protocolHeaderWrite',
ProtocolMessageWrite = 'protocolMessageWrite',
ProtocolWrite = 'protocolWrite',
}
export namespace SocketDiagnostics {
export const enableDiagnostics = false;
export interface IRecord {
timestamp: number;
id: string;
label: string;
type: SocketDiagnosticsEventType;
buff?: VSBuffer;
data?: any;
}
export const records: IRecord[] = [];
const socketIds = new WeakMap<any, string>();
let lastUsedSocketId = 0;
function getSocketId(nativeObject: any, label: string): string {
if (!socketIds.has(nativeObject)) {
const id = String(++lastUsedSocketId);
socketIds.set(nativeObject, id);
}
return socketIds.get(nativeObject)!;
}
export function traceSocketEvent(nativeObject: any, socketDebugLabel: string, type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void {
if (!enableDiagnostics) {
return;
}
const id = getSocketId(nativeObject, socketDebugLabel);
if (data instanceof VSBuffer || data instanceof Uint8Array || data instanceof ArrayBuffer || ArrayBuffer.isView(data)) {
const copiedData = VSBuffer.alloc(data.byteLength);
copiedData.set(data);
records.push({ timestamp: Date.now(), id, label: socketDebugLabel, type, buff: copiedData });
} else {
// data is a custom object
records.push({ timestamp: Date.now(), id, label: socketDebugLabel, type, data: data });
}
}
}
export const enum SocketCloseEventType {
NodeSocketCloseEvent = 0,
WebSocketCloseEvent = 1
@ -143,8 +62,6 @@ export interface ISocket extends IDisposable {
write(buffer: VSBuffer): void;
end(): void;
drain(): Promise<void>;
traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void;
}
let emptyBuffer: VSBuffer | null = null;
@ -253,23 +170,9 @@ const enum ProtocolMessageType {
Regular = 1,
Control = 2,
Ack = 3,
KeepAlive = 4,
Disconnect = 5,
ReplayRequest = 6,
Pause = 7,
Resume = 8
}
function protocolMessageTypeToString(messageType: ProtocolMessageType) {
switch (messageType) {
case ProtocolMessageType.None: return 'None';
case ProtocolMessageType.Regular: return 'Regular';
case ProtocolMessageType.Control: return 'Control';
case ProtocolMessageType.Ack: return 'Ack';
case ProtocolMessageType.Disconnect: return 'Disconnect';
case ProtocolMessageType.ReplayRequest: return 'ReplayRequest';
case ProtocolMessageType.Pause: return 'PauseWriting';
case ProtocolMessageType.Resume: return 'ResumeWriting';
}
ReplayRequest = 6
}
export const enum ProtocolConstants {
@ -279,11 +182,17 @@ export const enum ProtocolConstants {
*/
AcknowledgeTime = 2000, // 2 seconds
/**
* If there is a sent message that has been unacknowledged for 20 seconds,
* and we didn't see any incoming server data in the past 20 seconds,
* then consider the connection has timed out.
* If there is a message that has been unacknowledged for 10 seconds, consider the connection closed...
*/
TimeoutTime = 20000, // 20 seconds
AcknowledgeTimeoutTime = 20000, // 20 seconds
/**
* Send at least a message every 5s for keep alive reasons.
*/
KeepAliveTime = 5000, // 5 seconds
/**
* If there is no message received for 10 seconds, consider the connection closed...
*/
KeepAliveTimeoutTime = 20000, // 20 seconds
/**
* If there is no reconnection within this time-frame, consider the connection permanently closed...
*/
@ -361,9 +270,6 @@ class ProtocolReader extends Disposable {
this._state.messageType = buff.readUInt8(0);
this._state.id = buff.readUInt32BE(1);
this._state.ack = buff.readUInt32BE(5);
this._socket.traceSocketEvent(SocketDiagnosticsEventType.ProtocolHeaderRead, { messageType: protocolMessageTypeToString(this._state.messageType), id: this._state.id, ack: this._state.ack, messageSize: this._state.readLen });
} else {
// buff is the body
const messageType = this._state.messageType;
@ -377,8 +283,6 @@ class ProtocolReader extends Disposable {
this._state.id = 0;
this._state.ack = 0;
this._socket.traceSocketEvent(SocketDiagnosticsEventType.ProtocolMessageRead, buff);
this._onMessage.fire(new ProtocolMessage(messageType, id, ack, buff));
if (this._isDisposed) {
@ -402,7 +306,6 @@ class ProtocolReader extends Disposable {
class ProtocolWriter {
private _isDisposed: boolean;
private _isPaused: boolean;
private readonly _socket: ISocket;
private _data: VSBuffer[];
private _totalLength: number;
@ -410,7 +313,6 @@ class ProtocolWriter {
constructor(socket: ISocket) {
this._isDisposed = false;
this._isPaused = false;
this._socket = socket;
this._data = [];
this._totalLength = 0;
@ -436,15 +338,6 @@ class ProtocolWriter {
this._writeNow();
}
public pause(): void {
this._isPaused = true;
}
public resume(): void {
this._isPaused = false;
this._scheduleWriting();
}
public write(msg: ProtocolMessage) {
if (this._isDisposed) {
// ignore: there could be left-over promises which complete and then
@ -458,10 +351,6 @@ class ProtocolWriter {
header.writeUInt32BE(msg.id, 1);
header.writeUInt32BE(msg.ack, 5);
header.writeUInt32BE(msg.data.byteLength, 9);
this._socket.traceSocketEvent(SocketDiagnosticsEventType.ProtocolHeaderWrite, { messageType: protocolMessageTypeToString(msg.type), id: msg.id, ack: msg.ack, messageSize: msg.data.byteLength });
this._socket.traceSocketEvent(SocketDiagnosticsEventType.ProtocolMessageWrite, msg.data);
this._writeSoon(header, msg.data);
}
@ -481,31 +370,17 @@ class ProtocolWriter {
private _writeSoon(header: VSBuffer, data: VSBuffer): void {
if (this._bufferAdd(header, data)) {
this._scheduleWriting();
platform.setImmediate(() => {
this._writeNow();
});
}
}
private _writeNowTimeout: any = null;
private _scheduleWriting(): void {
if (this._writeNowTimeout) {
return;
}
this._writeNowTimeout = setTimeout(() => {
this._writeNowTimeout = null;
this._writeNow();
});
}
private _writeNow(): void {
if (this._totalLength === 0) {
return;
}
if (this._isPaused) {
return;
}
const data = this._bufferTake();
this._socket.traceSocketEvent(SocketDiagnosticsEventType.ProtocolWrite, { byteLength: data.byteLength });
this._socket.write(data);
this._socket.write(this._bufferTake());
}
}
@ -608,8 +483,8 @@ export class BufferedEmitter<T> {
this._hasListeners = true;
// it is important to deliver these messages after this call, but before
// other messages have a chance to be received (to guarantee in order delivery)
// that's why we're using here queueMicrotask and not other types of timeouts
queueMicrotask(() => this._deliverMessages());
// that's why we're using here nextTick and not other types of timeouts
process.nextTick(() => this._deliverMessages());
},
onLastListenerRemove: () => {
this._hasListeners = false;
@ -777,6 +652,9 @@ export class PersistentProtocol implements IMessagePassingProtocol {
private _incomingMsgLastTime: number;
private _incomingAckTimeout: any | null;
private _outgoingKeepAliveTimeout: any | null;
private _incomingKeepAliveTimeout: any | null;
private _lastReplayRequestTime: number;
private _socket: ISocket;
@ -818,6 +696,9 @@ export class PersistentProtocol implements IMessagePassingProtocol {
this._incomingMsgLastTime = 0;
this._incomingAckTimeout = null;
this._outgoingKeepAliveTimeout = null;
this._incomingKeepAliveTimeout = null;
this._lastReplayRequestTime = 0;
this._socketDisposables = [];
@ -831,6 +712,9 @@ export class PersistentProtocol implements IMessagePassingProtocol {
if (initialChunk) {
this._socketReader.acceptChunk(initialChunk);
}
this._sendKeepAliveCheck();
this._recvKeepAliveCheck();
}
dispose(): void {
@ -842,6 +726,14 @@ export class PersistentProtocol implements IMessagePassingProtocol {
clearTimeout(this._incomingAckTimeout);
this._incomingAckTimeout = null;
}
if (this._outgoingKeepAliveTimeout) {
clearTimeout(this._outgoingKeepAliveTimeout);
this._outgoingKeepAliveTimeout = null;
}
if (this._incomingKeepAliveTimeout) {
clearTimeout(this._incomingKeepAliveTimeout);
this._incomingKeepAliveTimeout = null;
}
this._socketDisposables = dispose(this._socketDisposables);
}
@ -855,18 +747,50 @@ export class PersistentProtocol implements IMessagePassingProtocol {
this._socketWriter.flush();
}
sendPause(): void {
const msg = new ProtocolMessage(ProtocolMessageType.Pause, 0, 0, getEmptyBuffer());
this._socketWriter.write(msg);
private _sendKeepAliveCheck(): void {
if (this._outgoingKeepAliveTimeout) {
// there will be a check in the near future
return;
}
const timeSinceLastOutgoingMsg = Date.now() - this._socketWriter.lastWriteTime;
if (timeSinceLastOutgoingMsg >= ProtocolConstants.KeepAliveTime) {
// sufficient time has passed since last message was written,
// and no message from our side needed to be sent in the meantime,
// so we will send a message containing only a keep alive.
const msg = new ProtocolMessage(ProtocolMessageType.KeepAlive, 0, 0, getEmptyBuffer());
this._socketWriter.write(msg);
this._sendKeepAliveCheck();
return;
}
this._outgoingKeepAliveTimeout = setTimeout(() => {
this._outgoingKeepAliveTimeout = null;
this._sendKeepAliveCheck();
}, ProtocolConstants.KeepAliveTime - timeSinceLastOutgoingMsg + 5);
}
sendResume(): void {
const msg = new ProtocolMessage(ProtocolMessageType.Resume, 0, 0, getEmptyBuffer());
this._socketWriter.write(msg);
}
private _recvKeepAliveCheck(): void {
if (this._incomingKeepAliveTimeout) {
// there will be a check in the near future
return;
}
pauseSocketWriting() {
this._socketWriter.pause();
const timeSinceLastIncomingMsg = Date.now() - this._socketReader.lastReadTime;
if (timeSinceLastIncomingMsg >= ProtocolConstants.KeepAliveTimeoutTime) {
// It's been a long time since we received a server message
// But this might be caused by the event loop being busy and failing to read messages
if (!this._loadEstimator.hasHighLoad()) {
// Trash the socket
this._onSocketTimeout.fire(undefined);
return;
}
}
this._incomingKeepAliveTimeout = setTimeout(() => {
this._incomingKeepAliveTimeout = null;
this._recvKeepAliveCheck();
}, Math.max(ProtocolConstants.KeepAliveTimeoutTime - timeSinceLastIncomingMsg, 0) + 5);
}
public getSocket(): ISocket {
@ -907,6 +831,9 @@ export class PersistentProtocol implements IMessagePassingProtocol {
this._socketWriter.write(toSend[i]);
}
this._recvAckCheck();
this._sendKeepAliveCheck();
this._recvKeepAliveCheck();
}
public acceptDisconnect(): void {
@ -927,59 +854,34 @@ export class PersistentProtocol implements IMessagePassingProtocol {
} while (true);
}
switch (msg.type) {
case ProtocolMessageType.None: {
// N/A
break;
}
case ProtocolMessageType.Regular: {
if (msg.id > this._incomingMsgId) {
if (msg.id !== this._incomingMsgId + 1) {
// in case we missed some messages we ask the other party to resend them
const now = Date.now();
if (now - this._lastReplayRequestTime > 10000) {
// send a replay request at most once every 10s
this._lastReplayRequestTime = now;
this._socketWriter.write(new ProtocolMessage(ProtocolMessageType.ReplayRequest, 0, 0, getEmptyBuffer()));
}
} else {
this._incomingMsgId = msg.id;
this._incomingMsgLastTime = Date.now();
this._sendAckCheck();
this._onMessage.fire(msg.data);
if (msg.type === ProtocolMessageType.Regular) {
if (msg.id > this._incomingMsgId) {
if (msg.id !== this._incomingMsgId + 1) {
// in case we missed some messages we ask the other party to resend them
const now = Date.now();
if (now - this._lastReplayRequestTime > 10000) {
// send a replay request at most once every 10s
this._lastReplayRequestTime = now;
this._socketWriter.write(new ProtocolMessage(ProtocolMessageType.ReplayRequest, 0, 0, getEmptyBuffer()));
}
} else {
this._incomingMsgId = msg.id;
this._incomingMsgLastTime = Date.now();
this._sendAckCheck();
this._onMessage.fire(msg.data);
}
break;
}
case ProtocolMessageType.Control: {
this._onControlMessage.fire(msg.data);
break;
}
case ProtocolMessageType.Ack: {
// nothing to do
break;
}
case ProtocolMessageType.Disconnect: {
this._onDidDispose.fire();
break;
}
case ProtocolMessageType.ReplayRequest: {
// Send again all unacknowledged messages
const toSend = this._outgoingUnackMsg.toArray();
for (let i = 0, len = toSend.length; i < len; i++) {
this._socketWriter.write(toSend[i]);
}
this._recvAckCheck();
break;
}
case ProtocolMessageType.Pause: {
this._socketWriter.pause();
break;
}
case ProtocolMessageType.Resume: {
this._socketWriter.resume();
break;
} else if (msg.type === ProtocolMessageType.Control) {
this._onControlMessage.fire(msg.data);
} else if (msg.type === ProtocolMessageType.Disconnect) {
this._onDidDispose.fire();
} else if (msg.type === ProtocolMessageType.ReplayRequest) {
// Send again all unacknowledged messages
const toSend = this._outgoingUnackMsg.toArray();
for (let i = 0, len = toSend.length; i < len; i++) {
this._socketWriter.write(toSend[i]);
}
this._recvAckCheck();
}
}
@ -1056,15 +958,8 @@ export class PersistentProtocol implements IMessagePassingProtocol {
const oldestUnacknowledgedMsg = this._outgoingUnackMsg.peek()!;
const timeSinceOldestUnacknowledgedMsg = Date.now() - oldestUnacknowledgedMsg.writtenTime;
const timeSinceLastReceivedSomeData = Date.now() - this._socketReader.lastReadTime;
if (
timeSinceOldestUnacknowledgedMsg >= ProtocolConstants.TimeoutTime
&& timeSinceLastReceivedSomeData >= ProtocolConstants.TimeoutTime
) {
if (timeSinceOldestUnacknowledgedMsg >= ProtocolConstants.AcknowledgeTimeoutTime) {
// It's been a long time since our sent message was acknowledged
// and a long time since we received some data
// But this might be caused by the event loop being busy and failing to read messages
if (!this._loadEstimator.hasHighLoad()) {
// Trash the socket
@ -1076,7 +971,7 @@ export class PersistentProtocol implements IMessagePassingProtocol {
this._outgoingAckTimeout = setTimeout(() => {
this._outgoingAckTimeout = null;
this._recvAckCheck();
}, Math.max(ProtocolConstants.TimeoutTime - timeSinceOldestUnacknowledgedMsg, 500));
}, Math.max(ProtocolConstants.AcknowledgeTimeoutTime - timeSinceOldestUnacknowledgedMsg, 0) + 5);
}
private _sendAck(): void {
@ -1090,39 +985,3 @@ export class PersistentProtocol implements IMessagePassingProtocol {
this._socketWriter.write(msg);
}
}
// (() => {
// if (!SocketDiagnostics.enableDiagnostics) {
// return;
// }
// if (typeof require.__$__nodeRequire !== 'function') {
// console.log(`Can only log socket diagnostics on native platforms.`);
// return;
// }
// const type = (
// process.argv.includes('--type=renderer')
// ? 'renderer'
// : (process.argv.includes('--type=extensionHost')
// ? 'extensionHost'
// : (process.argv.some(item => item.includes('server/main'))
// ? 'server'
// : 'unknown'
// )
// )
// );
// setTimeout(() => {
// SocketDiagnostics.records.forEach(r => {
// if (r.buff) {
// r.data = Buffer.from(r.buff.buffer).toString('base64');
// r.buff = undefined;
// }
// });
// const fs = <typeof import('fs')>require.__$__nodeRequire('fs');
// const path = <typeof import('path')>require.__$__nodeRequire('path');
// const logPath = path.join(process.cwd(),`${type}-${process.pid}`);
// console.log(`dumping socket diagnostics at ${logPath}`);
// fs.writeFileSync(logPath, JSON.stringify(SocketDiagnostics.records));
// }, 20000);
// })();

View file

@ -14,25 +14,17 @@ import { join } from 'vs/base/common/path';
import { Platform, platform } from 'vs/base/common/platform';
import { generateUuid } from 'vs/base/common/uuid';
import { ClientConnectionEvent, IPCServer } from 'vs/base/parts/ipc/common/ipc';
import { ChunkStream, Client, ISocket, Protocol, SocketCloseEvent, SocketCloseEventType, SocketDiagnostics, SocketDiagnosticsEventType } from 'vs/base/parts/ipc/common/ipc.net';
import { ChunkStream, Client, ISocket, Protocol, SocketCloseEvent, SocketCloseEventType } from 'vs/base/parts/ipc/common/ipc.net';
import * as zlib from 'zlib';
export class NodeSocket implements ISocket {
public readonly debugLabel: string;
public readonly socket: Socket;
private readonly _errorListener: (err: any) => void;
public traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void {
SocketDiagnostics.traceSocketEvent(this.socket, this.debugLabel, type, data);
}
constructor(socket: Socket, debugLabel: string = '') {
this.debugLabel = debugLabel;
constructor(socket: Socket) {
this.socket = socket;
this.traceSocketEvent(SocketDiagnosticsEventType.Created, { type: 'NodeSocket' });
this._errorListener = (err: any) => {
this.traceSocketEvent(SocketDiagnosticsEventType.Error, { code: err?.code, message: err?.message });
if (err) {
if (err.code === 'EPIPE') {
// An EPIPE exception at the wrong time can lead to a renderer process crash
@ -55,10 +47,7 @@ export class NodeSocket implements ISocket {
}
public onData(_listener: (e: VSBuffer) => void): IDisposable {
const listener = (buff: Buffer) => {
this.traceSocketEvent(SocketDiagnosticsEventType.Read, buff);
_listener(VSBuffer.wrap(buff));
};
const listener = (buff: Buffer) => _listener(VSBuffer.wrap(buff));
this.socket.on('data', listener);
return {
dispose: () => this.socket.off('data', listener)
@ -67,7 +56,6 @@ export class NodeSocket implements ISocket {
public onClose(listener: (e: SocketCloseEvent) => void): IDisposable {
const adapter = (hadError: boolean) => {
this.traceSocketEvent(SocketDiagnosticsEventType.Close, { hadError });
listener({
type: SocketCloseEventType.NodeSocketCloseEvent,
hadError: hadError,
@ -81,13 +69,9 @@ export class NodeSocket implements ISocket {
}
public onEnd(listener: () => void): IDisposable {
const adapter = () => {
this.traceSocketEvent(SocketDiagnosticsEventType.NodeEndReceived);
listener();
};
this.socket.on('end', adapter);
this.socket.on('end', listener);
return {
dispose: () => this.socket.off('end', adapter)
dispose: () => this.socket.off('end', listener)
};
}
@ -103,8 +87,7 @@ export class NodeSocket implements ISocket {
// > However, the false return value is only advisory and the writable stream will unconditionally
// > accept and buffer chunk even if it has not been allowed to drain.
try {
this.traceSocketEvent(SocketDiagnosticsEventType.Write, buffer);
this.socket.write(buffer.buffer, (err: any) => {
this.socket.write(<Buffer>buffer.buffer, (err: any) => {
if (err) {
if (err.code === 'EPIPE') {
// An EPIPE exception at the wrong time can lead to a renderer process crash
@ -133,15 +116,12 @@ export class NodeSocket implements ISocket {
}
public end(): void {
this.traceSocketEvent(SocketDiagnosticsEventType.NodeEndSent);
this.socket.end();
}
public drain(): Promise<void> {
this.traceSocketEvent(SocketDiagnosticsEventType.NodeDrainBegin);
return new Promise<void>((resolve, reject) => {
if (this.socket.bufferSize === 0) {
this.traceSocketEvent(SocketDiagnosticsEventType.NodeDrainEnd);
resolve();
return;
}
@ -151,7 +131,6 @@ export class NodeSocket implements ISocket {
this.socket.off('error', finished);
this.socket.off('timeout', finished);
this.socket.off('drain', finished);
this.traceSocketEvent(SocketDiagnosticsEventType.NodeDrainEnd);
resolve();
};
this.socket.on('close', finished);
@ -174,17 +153,25 @@ const enum ReadState {
Fin = 4
}
interface ISocketTracer {
traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void
}
/**
* See https://tools.ietf.org/html/rfc6455#section-5.2
*/
export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketTracer {
export class WebSocketNodeSocket extends Disposable implements ISocket {
public readonly socket: NodeSocket;
private readonly _flowManager: WebSocketFlowManager;
public readonly permessageDeflate: boolean;
private _totalIncomingWireBytes: number;
private _totalIncomingDataBytes: number;
private _totalOutgoingWireBytes: number;
private _totalOutgoingDataBytes: number;
private readonly _zlibInflate: zlib.InflateRaw | null;
private readonly _zlibDeflate: zlib.DeflateRaw | null;
private _zlibDeflateFlushWaitingCount: number;
private readonly _onDidZlibFlush = this._register(new Emitter<void>());
private readonly _recordInflateBytes: boolean;
private readonly _recordedInflateBytes: Buffer[] = [];
private readonly _pendingInflateData: Buffer[] = [];
private readonly _pendingDeflateData: Buffer[] = [];
private readonly _incomingData: ChunkStream;
private readonly _onData = this._register(new Emitter<VSBuffer>());
private readonly _onClose = this._register(new Emitter<SocketCloseEvent>());
@ -199,16 +186,27 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT
mask: 0
};
public get permessageDeflate(): boolean {
return this._flowManager.permessageDeflate;
public get totalIncomingWireBytes(): number {
return this._totalIncomingWireBytes;
}
public get totalIncomingDataBytes(): number {
return this._totalIncomingDataBytes;
}
public get totalOutgoingWireBytes(): number {
return this._totalOutgoingWireBytes;
}
public get totalOutgoingDataBytes(): number {
return this._totalOutgoingDataBytes;
}
public get recordedInflateBytes(): VSBuffer {
return this._flowManager.recordedInflateBytes;
}
public traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void {
this.socket.traceSocketEvent(type, data);
if (this._recordInflateBytes) {
return VSBuffer.wrap(Buffer.concat(this._recordedInflateBytes));
}
return VSBuffer.alloc(0);
}
/**
@ -226,34 +224,69 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT
constructor(socket: NodeSocket, permessageDeflate: boolean, inflateBytes: VSBuffer | null, recordInflateBytes: boolean) {
super();
this.socket = socket;
this.traceSocketEvent(SocketDiagnosticsEventType.Created, { type: 'WebSocketNodeSocket', permessageDeflate, inflateBytesLength: inflateBytes?.byteLength || 0, recordInflateBytes });
this._flowManager = this._register(new WebSocketFlowManager(
this,
permessageDeflate,
inflateBytes,
recordInflateBytes,
this._onData,
(data, compressed) => this._write(data, compressed)
));
this._register(this._flowManager.onError((err) => {
// zlib errors are fatal, since we have no idea how to recover
console.error(err);
onUnexpectedError(err);
this._onClose.fire({
type: SocketCloseEventType.NodeSocketCloseEvent,
hadError: true,
error: err
this._totalIncomingWireBytes = 0;
this._totalIncomingDataBytes = 0;
this._totalOutgoingWireBytes = 0;
this._totalOutgoingDataBytes = 0;
this.permessageDeflate = permessageDeflate;
this._recordInflateBytes = recordInflateBytes;
if (permessageDeflate) {
// See https://tools.ietf.org/html/rfc7692#page-16
// To simplify our logic, we don't negotiate the window size
// and simply dedicate (2^15) / 32kb per web socket
this._zlibInflate = zlib.createInflateRaw({
windowBits: 15
});
}));
this._zlibInflate.on('error', (err) => {
// zlib errors are fatal, since we have no idea how to recover
console.error(err);
onUnexpectedError(err);
this._onClose.fire({
type: SocketCloseEventType.NodeSocketCloseEvent,
hadError: true,
error: err
});
});
this._zlibInflate.on('data', (data: Buffer) => {
this._pendingInflateData.push(data);
});
if (inflateBytes) {
this._zlibInflate.write(inflateBytes.buffer);
this._zlibInflate.flush(() => {
this._pendingInflateData.length = 0;
});
}
this._zlibDeflate = zlib.createDeflateRaw({
windowBits: 15
});
this._zlibDeflate.on('error', (err) => {
// zlib errors are fatal, since we have no idea how to recover
console.error(err);
onUnexpectedError(err);
this._onClose.fire({
type: SocketCloseEventType.NodeSocketCloseEvent,
hadError: true,
error: err
});
});
this._zlibDeflate.on('data', (data: Buffer) => {
this._pendingDeflateData.push(data);
});
} else {
this._zlibInflate = null;
this._zlibDeflate = null;
}
this._zlibDeflateFlushWaitingCount = 0;
this._incomingData = new ChunkStream();
this._register(this.socket.onData(data => this._acceptChunk(data)));
this._register(this.socket.onClose((e) => this._onClose.fire(e)));
}
public override dispose(): void {
if (this._flowManager.isProcessingWriteQueue()) {
if (this._zlibDeflateFlushWaitingCount > 0) {
// Wait for any outstanding writes to finish before disposing
this._register(this._flowManager.onDidFinishProcessingWriteQueue(() => {
this._register(this._onDidZlibFlush.event(() => {
this.dispose();
}));
} else {
@ -275,16 +308,36 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT
}
public write(buffer: VSBuffer): void {
this._flowManager.writeMessage(buffer);
this._totalOutgoingDataBytes += buffer.byteLength;
if (this._zlibDeflate) {
this._zlibDeflate.write(<Buffer>buffer.buffer);
this._zlibDeflateFlushWaitingCount++;
// See https://zlib.net/manual.html#Constants
this._zlibDeflate.flush(/*Z_SYNC_FLUSH*/2, () => {
this._zlibDeflateFlushWaitingCount--;
let data = Buffer.concat(this._pendingDeflateData);
this._pendingDeflateData.length = 0;
// See https://tools.ietf.org/html/rfc7692#section-7.2.1
data = data.slice(0, data.length - 4);
if (!this._isEnded) {
// Avoid ERR_STREAM_WRITE_AFTER_END
this._write(VSBuffer.wrap(data), true);
}
if (this._zlibDeflateFlushWaitingCount === 0) {
this._onDidZlibFlush.fire();
}
});
} else {
this._write(buffer, false);
}
}
private _write(buffer: VSBuffer, compressed: boolean): void {
if (this._isEnded) {
// Avoid ERR_STREAM_WRITE_AFTER_END
return;
}
this.traceSocketEvent(SocketDiagnosticsEventType.WebSocketNodeSocketWrite, buffer);
let headerLen = Constants.MinHeaderByteSize;
if (buffer.byteLength < 126) {
headerLen += 0;
@ -321,6 +374,7 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT
header.writeUInt8((buffer.byteLength >>> 0) & 0b11111111, ++offset);
}
this._totalOutgoingWireBytes += header.byteLength + buffer.byteLength;
this.socket.write(VSBuffer.concat([header, buffer]));
}
@ -333,6 +387,7 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT
if (data.byteLength === 0) {
return;
}
this._totalIncomingWireBytes += data.byteLength;
this._incomingData.acceptChunk(data);
@ -358,8 +413,6 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT
this._state.firstFrameOfMessage = Boolean(finBit);
this._state.mask = 0;
this.traceSocketEvent(SocketDiagnosticsEventType.WebSocketNodeSocketPeekedHeader, { headerSize: this._state.readLen, compressed: this._state.compressed, fin: this._state.fin });
} else if (this._state.state === ReadState.ReadHeader) {
// read entire header
const header = this._incomingData.read(this._state.readLen);
@ -400,268 +453,52 @@ export class WebSocketNodeSocket extends Disposable implements ISocket, ISocketT
this._state.readLen = len;
this._state.mask = mask;
this.traceSocketEvent(SocketDiagnosticsEventType.WebSocketNodeSocketPeekedHeader, { bodySize: this._state.readLen, compressed: this._state.compressed, fin: this._state.fin, mask: this._state.mask });
} else if (this._state.state === ReadState.ReadBody) {
// read body
const body = this._incomingData.read(this._state.readLen);
this.traceSocketEvent(SocketDiagnosticsEventType.WebSocketNodeSocketReadData, body);
unmask(body, this._state.mask);
this.traceSocketEvent(SocketDiagnosticsEventType.WebSocketNodeSocketUnmaskedData, body);
this._state.state = ReadState.PeekHeader;
this._state.readLen = Constants.MinHeaderByteSize;
this._state.mask = 0;
this._flowManager.acceptFrame(body, this._state.compressed, !!this._state.fin);
if (this._zlibInflate && this._state.compressed) {
// See https://datatracker.ietf.org/doc/html/rfc7692#section-9.2
// Even if permessageDeflate is negotiated, it is possible
// that the other side might decide to send uncompressed messages
// So only decompress messages that have the RSV 1 bit set
//
// See https://tools.ietf.org/html/rfc7692#section-7.2.2
if (this._recordInflateBytes) {
this._recordedInflateBytes.push(Buffer.from(<Buffer>body.buffer));
}
this._zlibInflate.write(<Buffer>body.buffer);
if (this._state.fin) {
if (this._recordInflateBytes) {
this._recordedInflateBytes.push(Buffer.from([0x00, 0x00, 0xff, 0xff]));
}
this._zlibInflate.write(Buffer.from([0x00, 0x00, 0xff, 0xff]));
}
this._zlibInflate.flush(() => {
const data = Buffer.concat(this._pendingInflateData);
this._pendingInflateData.length = 0;
this._totalIncomingDataBytes += data.length;
this._onData.fire(VSBuffer.wrap(data));
});
} else {
this._totalIncomingDataBytes += body.byteLength;
this._onData.fire(body);
}
}
}
}
public async drain(): Promise<void> {
this.traceSocketEvent(SocketDiagnosticsEventType.WebSocketNodeSocketDrainBegin);
if (this._flowManager.isProcessingWriteQueue()) {
await Event.toPromise(this._flowManager.onDidFinishProcessingWriteQueue);
if (this._zlibDeflateFlushWaitingCount > 0) {
await Event.toPromise(this._onDidZlibFlush.event);
}
await this.socket.drain();
this.traceSocketEvent(SocketDiagnosticsEventType.WebSocketNodeSocketDrainEnd);
}
}
class WebSocketFlowManager extends Disposable {
private readonly _onError = this._register(new Emitter<Error>());
public readonly onError = this._onError.event;
private readonly _zlibInflateStream: ZlibInflateStream | null;
private readonly _zlibDeflateStream: ZlibDeflateStream | null;
private readonly _writeQueue: VSBuffer[] = [];
private readonly _readQueue: { data: VSBuffer, isCompressed: boolean, isLastFrameOfMessage: boolean }[] = [];
private readonly _onDidFinishProcessingWriteQueue = this._register(new Emitter<void>());
public readonly onDidFinishProcessingWriteQueue = this._onDidFinishProcessingWriteQueue.event;
public get permessageDeflate(): boolean {
return Boolean(this._zlibInflateStream && this._zlibDeflateStream);
}
public get recordedInflateBytes(): VSBuffer {
if (this._zlibInflateStream) {
return this._zlibInflateStream.recordedInflateBytes;
}
return VSBuffer.alloc(0);
}
constructor(
private readonly _tracer: ISocketTracer,
permessageDeflate: boolean,
inflateBytes: VSBuffer | null,
recordInflateBytes: boolean,
private readonly _onData: Emitter<VSBuffer>,
private readonly _writeFn: (data: VSBuffer, compressed: boolean) => void
) {
super();
if (permessageDeflate) {
// See https://tools.ietf.org/html/rfc7692#page-16
// To simplify our logic, we don't negotiate the window size
// and simply dedicate (2^15) / 32kb per web socket
this._zlibInflateStream = this._register(new ZlibInflateStream(this._tracer, recordInflateBytes, inflateBytes, { windowBits: 15 }));
this._zlibDeflateStream = this._register(new ZlibDeflateStream(this._tracer, { windowBits: 15 }));
this._register(this._zlibInflateStream.onError((err) => this._onError.fire(err)));
this._register(this._zlibDeflateStream.onError((err) => this._onError.fire(err)));
} else {
this._zlibInflateStream = null;
this._zlibDeflateStream = null;
}
}
public writeMessage(message: VSBuffer): void {
this._writeQueue.push(message);
this._processWriteQueue();
}
private _isProcessingWriteQueue = false;
private async _processWriteQueue(): Promise<void> {
if (this._isProcessingWriteQueue) {
return;
}
this._isProcessingWriteQueue = true;
while (this._writeQueue.length > 0) {
const message = this._writeQueue.shift()!;
if (this._zlibDeflateStream) {
const data = await this._deflateMessage(this._zlibDeflateStream, message);
this._writeFn(data, true);
} else {
this._writeFn(message, false);
}
}
this._isProcessingWriteQueue = false;
this._onDidFinishProcessingWriteQueue.fire();
}
public isProcessingWriteQueue(): boolean {
return (this._isProcessingWriteQueue);
}
/**
* Subsequent calls should wait for the previous `_deflateBuffer` call to complete.
*/
private _deflateMessage(zlibDeflateStream: ZlibDeflateStream, buffer: VSBuffer): Promise<VSBuffer> {
return new Promise<VSBuffer>((resolve, reject) => {
zlibDeflateStream.write(buffer);
zlibDeflateStream.flush(data => resolve(data));
});
}
public acceptFrame(data: VSBuffer, isCompressed: boolean, isLastFrameOfMessage: boolean): void {
this._readQueue.push({ data, isCompressed, isLastFrameOfMessage });
this._processReadQueue();
}
private _isProcessingReadQueue = false;
private async _processReadQueue(): Promise<void> {
if (this._isProcessingReadQueue) {
return;
}
this._isProcessingReadQueue = true;
while (this._readQueue.length > 0) {
const frameInfo = this._readQueue.shift()!;
if (this._zlibInflateStream && frameInfo.isCompressed) {
// See https://datatracker.ietf.org/doc/html/rfc7692#section-9.2
// Even if permessageDeflate is negotiated, it is possible
// that the other side might decide to send uncompressed messages
// So only decompress messages that have the RSV 1 bit set
const data = await this._inflateFrame(this._zlibInflateStream, frameInfo.data, frameInfo.isLastFrameOfMessage);
this._onData.fire(data);
} else {
this._onData.fire(frameInfo.data);
}
}
this._isProcessingReadQueue = false;
}
/**
* Subsequent calls should wait for the previous `transformRead` call to complete.
*/
private _inflateFrame(zlibInflateStream: ZlibInflateStream, buffer: VSBuffer, isLastFrameOfMessage: boolean): Promise<VSBuffer> {
return new Promise<VSBuffer>((resolve, reject) => {
// See https://tools.ietf.org/html/rfc7692#section-7.2.2
zlibInflateStream.write(buffer);
if (isLastFrameOfMessage) {
zlibInflateStream.write(VSBuffer.fromByteArray([0x00, 0x00, 0xff, 0xff]));
}
zlibInflateStream.flush(data => resolve(data));
});
}
}
class ZlibInflateStream extends Disposable {
private readonly _onError = this._register(new Emitter<Error>());
public readonly onError = this._onError.event;
private readonly _zlibInflate: zlib.InflateRaw;
private readonly _recordedInflateBytes: VSBuffer[] = [];
private readonly _pendingInflateData: VSBuffer[] = [];
public get recordedInflateBytes(): VSBuffer {
if (this._recordInflateBytes) {
return VSBuffer.concat(this._recordedInflateBytes);
}
return VSBuffer.alloc(0);
}
constructor(
private readonly _tracer: ISocketTracer,
private readonly _recordInflateBytes: boolean,
inflateBytes: VSBuffer | null,
options: zlib.ZlibOptions
) {
super();
this._zlibInflate = zlib.createInflateRaw(options);
this._zlibInflate.on('error', (err) => {
this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateError, { message: err?.message, code: (<any>err)?.code });
this._onError.fire(err);
});
this._zlibInflate.on('data', (data: Buffer) => {
this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateData, data);
this._pendingInflateData.push(VSBuffer.wrap(data));
});
if (inflateBytes) {
this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateInitialWrite, inflateBytes.buffer);
this._zlibInflate.write(inflateBytes.buffer);
this._zlibInflate.flush(() => {
this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateInitialFlushFired);
this._pendingInflateData.length = 0;
});
}
}
public write(buffer: VSBuffer): void {
if (this._recordInflateBytes) {
this._recordedInflateBytes.push(buffer.clone());
}
this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateWrite, buffer);
this._zlibInflate.write(buffer.buffer);
}
public flush(callback: (data: VSBuffer) => void): void {
this._zlibInflate.flush(() => {
this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibInflateFlushFired);
const data = VSBuffer.concat(this._pendingInflateData);
this._pendingInflateData.length = 0;
callback(data);
});
}
}
class ZlibDeflateStream extends Disposable {
private readonly _onError = this._register(new Emitter<Error>());
public readonly onError = this._onError.event;
private readonly _zlibDeflate: zlib.DeflateRaw;
private readonly _pendingDeflateData: VSBuffer[] = [];
constructor(
private readonly _tracer: ISocketTracer,
options: zlib.ZlibOptions
) {
super();
this._zlibDeflate = zlib.createDeflateRaw({
windowBits: 15
});
this._zlibDeflate.on('error', (err) => {
this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibDeflateError, { message: err?.message, code: (<any>err)?.code });
this._onError.fire(err);
});
this._zlibDeflate.on('data', (data: Buffer) => {
this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibDeflateData, data);
this._pendingDeflateData.push(VSBuffer.wrap(data));
});
}
public write(buffer: VSBuffer): void {
this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibDeflateWrite, buffer.buffer);
this._zlibDeflate.write(<Buffer>buffer.buffer);
}
public flush(callback: (data: VSBuffer) => void): void {
// See https://zlib.net/manual.html#Constants
this._zlibDeflate.flush(/*Z_SYNC_FLUSH*/2, () => {
this._tracer.traceSocketEvent(SocketDiagnosticsEventType.zlibDeflateFlushFired);
let data = VSBuffer.concat(this._pendingDeflateData);
this._pendingDeflateData.length = 0;
// See https://tools.ietf.org/html/rfc7692#section-7.2.1
data = data.slice(0, data.byteLength - 4);
callback(data);
});
}
}
@ -760,7 +597,7 @@ export class Server extends IPCServer {
const onConnection = Event.fromNodeEventEmitter<Socket>(server, 'connection');
return Event.map(onConnection, socket => ({
protocol: new Protocol(new NodeSocket(socket, 'ipc-server-connection')),
protocol: new Protocol(new NodeSocket(socket)),
onDidClientDisconnect: Event.once(Event.fromNodeEventEmitter<void>(socket, 'close'))
}));
}
@ -802,7 +639,7 @@ export function connect(hook: any, clientId: string): Promise<Client> {
return new Promise<Client>((c, e) => {
const socket = createConnection(hook, () => {
socket.removeListener('error', e);
c(Client.fromSocket(new NodeSocket(socket, `ipc-client${clientId}`), clientId));
c(Client.fromSocket(new NodeSocket(socket), clientId));
});
socket.once('error', e);

View file

@ -11,7 +11,7 @@ import { Barrier, timeout } from 'vs/base/common/async';
import { VSBuffer } from 'vs/base/common/buffer';
import { Emitter } from 'vs/base/common/event';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { ILoadEstimator, PersistentProtocol, Protocol, ProtocolConstants, SocketCloseEvent, SocketDiagnosticsEventType } from 'vs/base/parts/ipc/common/ipc.net';
import { ILoadEstimator, PersistentProtocol, Protocol, ProtocolConstants, SocketCloseEvent } from 'vs/base/parts/ipc/common/ipc.net';
import { createRandomIPCHandle, createStaticIPCHandle, NodeSocket, WebSocketNodeSocket } from 'vs/base/parts/ipc/node/ipc.net';
import { runWithFakedTimers } from 'vs/base/test/common/timeTravelScheduler';
import { ensureNoDisposablesAreLeakedInTestSuite } from 'vs/base/test/common/utils';
@ -342,7 +342,7 @@ suite('PersistentProtocol reconnection', () => {
assert.strictEqual(b.unacknowledgedCount, 1);
// wait for scheduled _recvAckCheck() to execute
await timeout(2 * ProtocolConstants.TimeoutTime);
await timeout(2 * ProtocolConstants.AcknowledgeTimeoutTime);
assert.strictEqual(a.unacknowledgedCount, 1);
assert.strictEqual(b.unacknowledgedCount, 1);
@ -351,7 +351,7 @@ suite('PersistentProtocol reconnection', () => {
a.endAcceptReconnection();
assert.strictEqual(timeoutListenerCalled, false);
await timeout(2 * ProtocolConstants.TimeoutTime);
await timeout(2 * ProtocolConstants.AcknowledgeTimeoutTime);
assert.strictEqual(a.unacknowledgedCount, 0);
assert.strictEqual(b.unacknowledgedCount, 0);
assert.strictEqual(timeoutListenerCalled, false);
@ -364,59 +364,6 @@ suite('PersistentProtocol reconnection', () => {
}
);
});
test('writing can be paused', async () => {
await runWithFakedTimers({ useFakeTimers: true, maxTaskCount: 100 }, async () => {
const loadEstimator: ILoadEstimator = {
hasHighLoad: () => false
};
const ether = new Ether();
const aSocket = new NodeSocket(ether.a);
const a = new PersistentProtocol(aSocket, null, loadEstimator);
const aMessages = new MessageStream(a);
const bSocket = new NodeSocket(ether.b);
const b = new PersistentProtocol(bSocket, null, loadEstimator);
const bMessages = new MessageStream(b);
// send one message A -> B
a.send(VSBuffer.fromString('a1'));
const a1 = await bMessages.waitForOne();
assert.strictEqual(a1.toString(), 'a1');
// ask A to pause writing
b.sendPause();
// send a message B -> A
b.send(VSBuffer.fromString('b1'));
const b1 = await aMessages.waitForOne();
assert.strictEqual(b1.toString(), 'b1');
// send a message A -> B (this should be blocked at A)
a.send(VSBuffer.fromString('a2'));
// wait a long time and check that not even acks are written
await timeout(2 * ProtocolConstants.AcknowledgeTime);
assert.strictEqual(a.unacknowledgedCount, 1);
assert.strictEqual(b.unacknowledgedCount, 1);
// ask A to resume writing
b.sendResume();
// check that B receives message
const a2 = await bMessages.waitForOne();
assert.strictEqual(a2.toString(), 'a2');
// wait a long time and check that acks are written
await timeout(2 * ProtocolConstants.AcknowledgeTime);
assert.strictEqual(a.unacknowledgedCount, 0);
assert.strictEqual(b.unacknowledgedCount, 0);
aMessages.dispose();
bMessages.dispose();
a.dispose();
b.dispose();
});
});
});
suite('IPC, create handle', () => {
@ -485,9 +432,6 @@ suite('WebSocketNodeSocket', () => {
private readonly _onClose = new Emitter<SocketCloseEvent>();
public readonly onClose = this._onClose.event;
public traceSocketEvent(type: SocketDiagnosticsEventType, data?: VSBuffer | Uint8Array | ArrayBuffer | ArrayBufferView | any): void {
}
constructor() {
super();
}
@ -578,14 +522,5 @@ suite('WebSocketNodeSocket', () => {
const actual = await testReading(frames, true);
assert.deepStrictEqual(actual, 'Hello');
});
test('A single-frame compressed text message followed by a single-frame non-compressed text message', async () => {
const frames = [
[0xc1, 0x07, 0xf2, 0x48, 0xcd, 0xc9, 0xc9, 0x07, 0x00], // contains "Hello"
[0x81, 0x05, 0x77, 0x6f, 0x72, 0x6c, 0x64] // contains "world"
];
const actual = await testReading(frames, true);
assert.deepStrictEqual(actual, 'Helloworld');
});
});
});

View file

@ -490,6 +490,11 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
if (this._value !== value) {
this._value = value || '';
this.update();
// TODO: Remove this duplicate code and have the updating of the input box handle this.
const didFilter = this.ui.list.filter(this.filterValue(this.ui.inputBox.value));
if (didFilter) {
this.trySelectFirst();
}
this.onDidChangeValueEmitter.fire(this._value);
}
}
@ -1056,7 +1061,6 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
}
class InputBox extends QuickInput implements IInputBox {
private _value = '';
private _valueSelection: Readonly<[number, number]> | undefined;
private valueSelectionUpdated = true;
private _placeholder: string | undefined;
@ -1066,12 +1070,11 @@ class InputBox extends QuickInput implements IInputBox {
private readonly onDidAcceptEmitter = this._register(new Emitter<void>());
get value() {
return this._value;
return this.ui.inputBox.value;
}
set value(value: string) {
this._value = value || '';
this.update();
this.ui.inputBox.value = value ?? '';
}
set valueSelection(valueSelection: Readonly<[number, number]>) {
@ -1118,10 +1121,6 @@ class InputBox extends QuickInput implements IInputBox {
if (!this.visible) {
this.visibleDisposables.add(
this.ui.inputBox.onDidChange(value => {
if (value === this.value) {
return;
}
this._value = value;
this.onDidValueChangeEmitter.fire(value);
}));
this.visibleDisposables.add(this.ui.onDidAccept(() => this.onDidAcceptEmitter.fire()));
@ -1141,9 +1140,6 @@ class InputBox extends QuickInput implements IInputBox {
};
this.ui.setVisibilities(visibilities);
super.update();
if (this.ui.inputBox.value !== this.value) {
this.ui.inputBox.value = this.value;
}
if (this.valueSelectionUpdated) {
this.valueSelectionUpdated = false;
this.ui.inputBox.select(this._valueSelection && { start: this._valueSelection[0], end: this._valueSelection[1] });

View file

@ -740,14 +740,25 @@ suite('Async', () => {
});
test('IntervalCounter', async () => {
let now = 0;
const counter = new async.IntervalCounter(5, () => now);
let now = Date.now();
const counter = new async.IntervalCounter(5);
let ellapsed = Date.now() - now;
if (ellapsed > 4) {
return; // flaky (https://github.com/microsoft/vscode/issues/114028)
}
assert.strictEqual(counter.increment(), 1);
assert.strictEqual(counter.increment(), 2);
assert.strictEqual(counter.increment(), 3);
now = 10;
now = Date.now();
await async.timeout(10);
ellapsed = Date.now() - now;
if (ellapsed < 5) {
return; // flaky (https://github.com/microsoft/vscode/issues/114028)
}
assert.strictEqual(counter.increment(), 1);
assert.strictEqual(counter.increment(), 2);

View file

@ -48,6 +48,7 @@ suite('Event', function () {
let doc = new Samples.Document3();
document.createElement('div').onclick = function () { };
let subscription = doc.onDidChange(counter.onEvent, counter);
doc.setText('far');

View file

@ -263,7 +263,7 @@ export const originalGlobalValues = {
Date: globalThis.Date,
};
function setTimeout(scheduler: Scheduler, handler: TimerHandler, timeout: number = 0): IDisposable {
function setTimeout(scheduler: Scheduler, handler: TimerHandler, timeout: number): IDisposable {
if (typeof handler === 'string') {
throw new Error('String handler args should not be used and are not supported');
}
@ -324,7 +324,7 @@ function setInterval(scheduler: Scheduler, handler: TimerHandler, interval: numb
}
function overwriteGlobals(scheduler: Scheduler): IDisposable {
globalThis.setTimeout = ((handler: TimerHandler, timeout?: number) => setTimeout(scheduler, handler, timeout)) as any;
globalThis.setTimeout = ((handler: TimerHandler, timeout: number) => setTimeout(scheduler, handler, timeout)) as any;
globalThis.clearTimeout = (timeoutId: any) => {
if (typeof timeoutId === 'object' && timeoutId && 'dispose' in timeoutId) {
timeoutId.dispose();

View file

@ -41,7 +41,7 @@ import { EncryptionMainService, IEncryptionMainService } from 'vs/platform/encry
import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { IEnvironmentMainService } from 'vs/platform/environment/electron-main/environmentMainService';
import { isLaunchedFromCli } from 'vs/platform/environment/node/argvHelper';
import { getResolvedShellEnv } from 'vs/platform/environment/node/shellEnv';
import { resolveShellEnv } from 'vs/platform/environment/node/shellEnv';
import { IExtensionUrlTrustService } from 'vs/platform/extensionManagement/common/extensionUrlTrust';
import { ExtensionUrlTrustService } from 'vs/platform/extensionManagement/node/extensionUrlTrustService';
import { IExtensionHostStarter, ipcExtensionHostStarterChannelName } from 'vs/platform/extensions/common/extensionHostStarter';
@ -1033,7 +1033,7 @@ export class CodeApplication extends Disposable {
private async resolveShellEnvironment(args: NativeParsedArgs, env: IProcessEnvironment, notifyOnError: boolean): Promise<typeof process.env> {
try {
return await getResolvedShellEnv(this.logService, args, env);
return await resolveShellEnv(this.logService, args, env);
} catch (error) {
const errorMessage = toErrorMessage(error);
if (notifyOnError) {

View file

@ -181,14 +181,12 @@ class ProcessRenderer implements ITreeRenderer<ProcessItem, void, IProcessItemTe
const windowTitle = this.mapPidToWindowTitle.get(element.pid);
name = windowTitle !== undefined ? `${name} (${this.mapPidToWindowTitle.get(element.pid)})` : name;
}
const pid = element.pid.toFixed(0);
templateData.name.textContent = name;
templateData.name.title = element.cmd;
templateData.CPU.textContent = element.load.toFixed(0);
templateData.PID.textContent = pid;
templateData.PID.parentElement!.id = `pid-${pid}`;
templateData.PID.textContent = element.pid.toFixed(0);
const memory = this.platform === 'win32' ? element.mem : (this.totalMem * (element.mem / 100));
templateData.memory.textContent = (memory / ByteSize.MB).toFixed(0);
@ -452,7 +450,7 @@ class ProcessExplorer {
items.push({
label: localize('copy', "Copy"),
click: () => {
const row = document.getElementById(`pid-${pid}`);
const row = document.getElementById(pid.toString());
if (row) {
this.nativeHostService.writeClipboardText(row.innerText);
}

View file

@ -22,7 +22,7 @@ import { NativeParsedArgs } from 'vs/platform/environment/common/argv';
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
import { NativeEnvironmentService } from 'vs/platform/environment/node/environmentService';
import { ExtensionGalleryServiceWithNoStorageService } from 'vs/platform/extensionManagement/common/extensionGalleryService';
import { IExtensionGalleryService, IExtensionManagementCLIService, IExtensionManagementService, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionGalleryService, IExtensionManagementCLIService, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionManagementCLIService } from 'vs/platform/extensionManagement/common/extensionManagementCLIService';
import { ExtensionManagementService } from 'vs/platform/extensionManagement/node/extensionManagementService';
import { IFileService } from 'vs/platform/files/common/files';
@ -217,8 +217,7 @@ class CliMain extends Disposable {
// Install Extension
else if (this.argv['install-extension'] || this.argv['install-builtin-extension']) {
const installOptions: InstallOptions = { isMachineScoped: !!this.argv['do-not-sync'], installPreReleaseVersion: !!this.argv['pre-release'] };
return extensionManagementCLIService.installExtensions(this.asExtensionIdOrVSIX(this.argv['install-extension'] || []), this.argv['install-builtin-extension'] || [], installOptions, !!this.argv['force']);
return extensionManagementCLIService.installExtensions(this.asExtensionIdOrVSIX(this.argv['install-extension'] || []), this.argv['install-builtin-extension'] || [], !!this.argv['do-not-sync'], !!this.argv['force']);
}
// Uninstall Extension

View file

@ -1518,6 +1518,31 @@ export namespace CoreNavigationCommands {
precondition: undefined
}));
export const ExpandLineSelection: CoreEditorCommand = registerEditorCommand(new class extends CoreEditorCommand {
constructor() {
super({
id: 'expandLineSelection',
precondition: undefined,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyCode.KeyL
}
});
}
public runCoreEditorCommand(viewModel: IViewModel, args: any): void {
viewModel.model.pushStackElement();
viewModel.setCursorStates(
args.source,
CursorChangeReason.Explicit,
CursorMoveCommands.expandLineSelection(viewModel, viewModel.getCursorStates())
);
viewModel.revealPrimaryCursor(args.source, true);
}
});
export const CancelSelection: CoreEditorCommand = registerEditorCommand(new class extends CoreEditorCommand {
constructor() {
super({

View file

@ -899,8 +899,6 @@ export interface ICodeEditor extends editorCommon.IEditor {
* @internal
*/
hasModel(): this is IActiveCodeEditor;
setBanner(bannerDomNode: HTMLElement | null, height: number): void;
}
/**

View file

@ -244,8 +244,6 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
private _decorationTypeKeysToIds: { [decorationTypeKey: string]: string[] };
private _decorationTypeSubtypes: { [decorationTypeKey: string]: { [subtype: string]: boolean } };
private _bannerDomNode: HTMLElement | null = null;
constructor(
domElement: HTMLElement,
_options: Readonly<editorBrowser.IEditorConstructionOptions>,
@ -1492,19 +1490,6 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
Configuration.applyFontInfoSlow(target, this._configuration.options.get(EditorOption.fontInfo));
}
public setBanner(domNode: HTMLElement | null, domNodeHeight: number): void {
if (this._bannerDomNode && this._domElement.contains(this._bannerDomNode)) {
this._domElement.removeChild(this._bannerDomNode);
}
this._bannerDomNode = domNode;
this._configuration.reserveHeight(domNode ? domNodeHeight : 0);
if (this._bannerDomNode) {
this._domElement.prepend(this._bannerDomNode);
}
}
protected _attachModel(model: ITextModel | null): void {
if (!model) {
this._modelData = null;
@ -1718,9 +1703,6 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
if (removeDomNode && this._domElement.contains(removeDomNode)) {
this._domElement.removeChild(removeDomNode);
}
if (this._bannerDomNode && this._domElement.contains(this._bannerDomNode)) {
this._domElement.removeChild(this._bannerDomNode);
}
return model;
}
@ -2025,6 +2007,12 @@ class CodeEditorWidgetFocusTracker extends Disposable {
this._hasFocus = false;
this._onChange.fire(undefined);
}));
this._register(dom.addDisposableListener(domElement, 'focusin', () => {
this._domFocusTracker.refreshState();
}));
this._register(dom.addDisposableListener(domElement, 'focusout', () => {
this._domFocusTracker.refreshState();
}));
}
public hasFocus(): boolean {

View file

@ -313,7 +313,6 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC
private _rawOptions: IEditorOptions;
private _readOptions: RawEditorOptions;
protected _validatedOptions: ValidatedEditorOptions;
private _reservedHeight: number = 0;
constructor(isSimpleWidget: boolean, _options: Readonly<IEditorOptions>) {
super();
@ -368,7 +367,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC
const env: IEnvironmentalOptions = {
memory: this._computeOptionsMemory,
outerWidth: partialEnv.outerWidth,
outerHeight: partialEnv.outerHeight - this._reservedHeight,
outerHeight: partialEnv.outerHeight,
fontInfo: this.readConfiguration(bareFontInfo),
extraEditorClassName: partialEnv.extraEditorClassName,
isDominatedByLongLines: this._isDominatedByLongLines,
@ -459,10 +458,6 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC
protected abstract readConfiguration(styling: BareFontInfo): FontInfo;
public reserveHeight(height: number) {
this._reservedHeight = height;
this._recomputeOptions();
}
}
export const editorConfigurationBaseNode = Object.freeze<IConfigurationNode>({

View file

@ -640,8 +640,6 @@ export interface IEditorOptions {
* Controls the behavior of editor guides.
*/
guides?: IGuidesOptions;
unicodeHighlight?: IUnicodeHighlightOptions;
}
/**
@ -3247,120 +3245,6 @@ class EditorScrollbar extends BaseEditorOption<EditorOption.scrollbar, InternalE
//#endregion
//#region UnicodeHighlight
export type DeriveFromWorkspaceTrust = 'deriveFromWorkspaceTrust';
/**
* @internal
*/
export const deriveFromWorkspaceTrust: DeriveFromWorkspaceTrust = 'deriveFromWorkspaceTrust';
/**
* Configuration options for unicode highlighting.
*/
export interface IUnicodeHighlightOptions {
nonBasicASCII?: boolean | DeriveFromWorkspaceTrust;
invisibleCharacters?: boolean | DeriveFromWorkspaceTrust;
ambiguousCharacters?: boolean | DeriveFromWorkspaceTrust;
includeComments?: boolean | DeriveFromWorkspaceTrust;
/**
* A list of allowed code points in a single string.
*/
allowedCharacters?: string;
}
/**
* @internal
*/
export type InternalUnicodeHighlightOptions = Required<Readonly<IUnicodeHighlightOptions>>;
/**
* @internal
*/
export const unicodeHighlightConfigKeys = {
allowedCharacters: 'editor.unicodeHighlight.allowedCharacters',
invisibleCharacters: 'editor.unicodeHighlight.invisibleCharacters',
nonBasicASCII: 'editor.unicodeHighlight.nonBasicASCII',
ambiguousCharacters: 'editor.unicodeHighlight.ambiguousCharacters',
includeComments: 'editor.unicodeHighlight.includeComments',
};
class UnicodeHighlight extends BaseEditorOption<EditorOption.unicodeHighlighting, InternalUnicodeHighlightOptions> {
constructor() {
const defaults: InternalUnicodeHighlightOptions = {
nonBasicASCII: deriveFromWorkspaceTrust,
invisibleCharacters: deriveFromWorkspaceTrust,
ambiguousCharacters: deriveFromWorkspaceTrust,
includeComments: deriveFromWorkspaceTrust,
allowedCharacters: '',
};
super(
EditorOption.unicodeHighlighting, 'unicodeHighlight', defaults,
{
[unicodeHighlightConfigKeys.nonBasicASCII]: {
restricted: true,
type: ['boolean', 'string'],
enum: [true, false, deriveFromWorkspaceTrust],
default: defaults.nonBasicASCII,
description: nls.localize('unicodeHighlight.nonBasicASCII', "Controls whether all non-basic ASCII characters are highlighted. Only characters between U+0020 and U+007E, tab, line-feed and carriage-return are considered basic ASCII.")
},
[unicodeHighlightConfigKeys.invisibleCharacters]: {
restricted: true,
type: ['boolean', 'string'],
enum: [true, false, deriveFromWorkspaceTrust],
default: defaults.invisibleCharacters,
description: nls.localize('unicodeHighlight.invisibleCharacters', "Controls whether characters that just reserve space or have no width at all are highlighted.")
},
[unicodeHighlightConfigKeys.ambiguousCharacters]: {
restricted: true,
type: ['boolean', 'string'],
enum: [true, false, deriveFromWorkspaceTrust],
default: defaults.ambiguousCharacters,
description: nls.localize('unicodeHighlight.ambiguousCharacters', "Controls whether characters are highlighted that can be confused with basic ASCII characters, except those that are common in the current user locale.")
},
[unicodeHighlightConfigKeys.includeComments]: {
restricted: true,
type: ['boolean', 'string'],
enum: [true, false, deriveFromWorkspaceTrust],
default: defaults.includeComments,
description: nls.localize('unicodeHighlight.includeComments', "Controls whether characters in comments should also be subject to unicode highlighting.")
},
[unicodeHighlightConfigKeys.allowedCharacters]: {
restricted: true,
type: 'string',
default: defaults.allowedCharacters,
description: nls.localize('unicodeHighlight.allowedCharacters', "Defines allowed characters that are not being highlighted.")
},
}
);
}
public validate(_input: any): InternalUnicodeHighlightOptions {
if (!_input || typeof _input !== 'object') {
return this.defaultValue;
}
const input = _input as IUnicodeHighlightOptions;
return {
nonBasicASCII: primitiveSet<boolean | DeriveFromWorkspaceTrust>(input.nonBasicASCII, deriveFromWorkspaceTrust, [true, false, deriveFromWorkspaceTrust]),
invisibleCharacters: primitiveSet<boolean | DeriveFromWorkspaceTrust>(input.invisibleCharacters, deriveFromWorkspaceTrust, [true, false, deriveFromWorkspaceTrust]),
ambiguousCharacters: primitiveSet<boolean | DeriveFromWorkspaceTrust>(input.ambiguousCharacters, deriveFromWorkspaceTrust, [true, false, deriveFromWorkspaceTrust]),
includeComments: primitiveSet<boolean | DeriveFromWorkspaceTrust>(input.includeComments, deriveFromWorkspaceTrust, [true, false, deriveFromWorkspaceTrust]),
allowedCharacters: string(input.allowedCharacters, ''),
};
}
}
function string(value: unknown, defaultValue: string): string {
if (typeof value !== 'string') {
return defaultValue;
}
return value;
}
//#endregion
//#region inlineSuggest
export interface IInlineSuggestOptions {
@ -4335,7 +4219,6 @@ export const enum EditorOption {
suggestSelection,
tabCompletion,
tabIndex,
unicodeHighlighting,
unusualLineTerminators,
useShadowDOM,
useTabStops,
@ -4924,7 +4807,6 @@ export const EditorOptions = {
EditorOption.tabIndex, 'tabIndex',
0, -1, Constants.MAX_SAFE_SMALL_INTEGER
)),
unicodeHighlight: register(new UnicodeHighlight()),
unusualLineTerminators: register(new EditorStringEnumOption(
EditorOption.unusualLineTerminators, 'unusualLineTerminators',
'prompt' as 'auto' | 'off' | 'prompt',

View file

@ -100,23 +100,6 @@ export class Range {
return true;
}
/**
* Test if `position` is in `range`. If the position is at the edges, will return false.
* @internal
*/
public static strictContainsPosition(range: IRange, position: IPosition): boolean {
if (position.lineNumber < range.startLineNumber || position.lineNumber > range.endLineNumber) {
return false;
}
if (position.lineNumber === range.startLineNumber && position.column <= range.startColumn) {
return false;
}
if (position.lineNumber === range.endLineNumber && position.column >= range.endColumn) {
return false;
}
return true;
}
/**
* Test if range is in this range. If the range is equal to this range, will return true.
*/

View file

@ -162,7 +162,6 @@ export interface IConfiguration extends IDisposable {
observeReferenceElement(dimension?: IDimension): void;
updatePixelRatio(): void;
setIsDominatedByLongLines(isDominatedByLongLines: boolean): void;
reserveHeight(height: number): void;
}
// --- view

View file

@ -168,12 +168,6 @@ export interface IModelDecorationOptions {
* If set, text will be injected in the view before the range.
*/
before?: InjectedTextOptions | null;
/**
* If set, this decoration will not be rendered for comment tokens.
* @internal
*/
hideInCommentTokens?: boolean | null;
}
/**
@ -760,7 +754,7 @@ export interface ITextModel {
getLineLastNonWhitespaceColumn(lineNumber: number): number;
/**
* Create a valid position.
* Create a valid position,
*/
validatePosition(position: IPosition): Position;
@ -800,7 +794,7 @@ export interface ITextModel {
getPositionAt(offset: number): Position;
/**
* Get a range covering the entire model.
* Get a range covering the entire model
*/
getFullModelRange(): Range;

View file

@ -2457,7 +2457,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
const bracketsContainingActivePosition =
(startLineNumber <= activePosition.lineNumber && activePosition.lineNumber <= endLineNumber)
// Does active position intersect with the view port? -> Intersect bracket pairs with activePosition
? bracketPairs.filter(bp => Range.strictContainsPosition(bp.range, activePosition))
? bracketPairs.filter(bp => bp.range.containsPosition(activePosition))
: this._bracketPairColorizer.getBracketPairsInRange(
Range.fromPositions(activePosition)
);
@ -2516,7 +2516,7 @@ export class TextModel extends Disposable implements model.ITextModel, IDecorati
renderHorizontalEndLineAtTheBottom = true;
}
}
// TODO: Consider indentation when computing guideVisibleColumn
const start = pair.openingBracketRange.getStartPosition();
const end = (pair.closingBracketRange?.getStartPosition() ?? pair.range.getEndPosition());
@ -3044,8 +3044,6 @@ export class ModelDecorationOptions implements model.IModelDecorationOptions {
readonly afterContentClassName: string | null;
readonly after: ModelDecorationInjectedTextOptions | null;
readonly before: ModelDecorationInjectedTextOptions | null;
readonly hideInCommentTokens: boolean | null;
private constructor(options: model.IModelDecorationOptions) {
this.description = options.description;
@ -3069,7 +3067,6 @@ export class ModelDecorationOptions implements model.IModelDecorationOptions {
this.afterContentClassName = options.afterContentClassName ? cleanClassName(options.afterContentClassName) : null;
this.after = options.after ? ModelDecorationInjectedTextOptions.from(options.after) : null;
this.before = options.before ? ModelDecorationInjectedTextOptions.from(options.before) : null;
this.hideInCommentTokens = options.hideInCommentTokens ?? false;
}
}
ModelDecorationOptions.EMPTY = ModelDecorationOptions.register({ description: 'empty' });

View file

@ -15,7 +15,7 @@ import { TextModel } from 'vs/editor/common/model/textModel';
import { Disposable } from 'vs/base/common/lifecycle';
import { StopWatch } from 'vs/base/common/stopwatch';
import { MultilineTokensBuilder, countEOL } from 'vs/editor/common/model/tokensStore';
import { runWhenIdle, IdleDeadline } from 'vs/base/common/async';
import * as platform from 'vs/base/common/platform';
const enum Constants {
CHEAP_TOKENIZATION_LENGTH_LIMIT = 2048
@ -255,26 +255,19 @@ export class TextModelTokenization extends Disposable {
this._beginBackgroundTokenization();
}
private _isScheduled = false;
private _beginBackgroundTokenization(): void {
if (this._isScheduled || !this._textModel.isAttachedToEditor() || !this._hasLinesToTokenize()) {
return;
if (this._textModel.isAttachedToEditor() && this._hasLinesToTokenize()) {
platform.setImmediate(() => {
if (this._isDisposed) {
// disposed in the meantime
return;
}
this._revalidateTokensNow();
});
}
this._isScheduled = true;
runWhenIdle((deadline) => {
this._isScheduled = false;
if (this._isDisposed) {
// disposed in the meantime
return;
}
this._revalidateTokensNow(deadline);
});
}
private _revalidateTokensNow(deadline: IdleDeadline): void {
private _revalidateTokensNow(): void {
const textModelLastLineNumber = this._textModel.getLineCount();
const MAX_ALLOWED_TIME = 1;
@ -282,7 +275,7 @@ export class TextModelTokenization extends Disposable {
const sw = StopWatch.create(false);
let tokenizedLineNumber = -1;
do {
while (this._hasLinesToTokenize()) {
if (sw.elapsed() > MAX_ALLOWED_TIME) {
// Stop if MAX_ALLOWED_TIME is reached
break;
@ -293,7 +286,7 @@ export class TextModelTokenization extends Disposable {
if (tokenizedLineNumber >= textModelLastLineNumber) {
break;
}
} while (this._hasLinesToTokenize() && deadline.timeRemaining() > 0);
}
this._beginBackgroundTokenization();
this._textModel.setTokens(builder.tokens, !this._hasLinesToTokenize());

View file

@ -539,10 +539,11 @@ export interface CompletionItem {
/**
* A string or snippet that should be inserted in a document when selecting
* this completion.
* is used.
*/
insertText: string;
/**
* Additional rules (as bitmask) that should be applied when inserting
* Addition rules (as bitmask) that should be applied when inserting
* this completion.
*/
insertTextRules?: CompletionItemInsertTextRule;
@ -676,8 +677,6 @@ export interface InlineCompletionContext {
export interface SelectedSuggestionInfo {
range: IRange;
text: string;
isSnippetText: boolean;
completionKind: CompletionItemKind;
}
export interface InlineCompletion {

View file

@ -10,7 +10,6 @@ import { ILanguageExtensionPoint } from 'vs/editor/common/services/modeService';
import { Registry } from 'vs/platform/registry/common/platform';
import { IDisposable } from 'vs/base/common/lifecycle';
import { Mimes } from 'vs/base/common/mime';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
// Define extension point ids
export const Extensions = {
@ -87,12 +86,3 @@ LanguageConfigurationRegistry.register(PLAINTEXT_MODE_ID, {
offSide: true
}
}, 0);
Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration)
.registerDefaultConfigurations([{
overrides: {
'[plaintext]': {
'editor.unicodeHighlight.ambiguousCharacters': false
}
}
}]);

View file

@ -1,215 +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 { IRange, Range } from 'vs/editor/common/core/range';
import { Searcher } from 'vs/editor/common/model/textModelSearch';
import * as strings from 'vs/base/common/strings';
import { IUnicodeHighlightsResult } from 'vs/editor/common/services/editorWorkerService';
import { assertNever } from 'vs/base/common/types';
export class UnicodeTextModelHighlighter {
public static computeUnicodeHighlights(model: IUnicodeCharacterSearcherTarget, options: UnicodeHighlighterOptions, range?: IRange): IUnicodeHighlightsResult {
const startLine = range ? range.startLineNumber : 1;
const endLine = range ? range.endLineNumber : model.getLineCount();
const codePointHighlighter = new CodePointHighlighter(options);
const candidates = codePointHighlighter.getCandidateCodePoints();
let regex: RegExp;
if (candidates === 'allNonBasicAscii') {
regex = new RegExp('[^\\t\\n\\r\\x20-\\x7E]', 'g');
} else {
regex = new RegExp(`${buildRegExpCharClassExpr(Array.from(candidates))}`, 'g');
}
const searcher = new Searcher(null, regex);
const ranges: Range[] = [];
let hasMore = false;
let m: RegExpExecArray | null;
let ambiguousCharacterCount = 0;
let invisibleCharacterCount = 0;
let nonBasicAsciiCharacterCount = 0;
forLoop:
for (let lineNumber = startLine, lineCount = endLine; lineNumber <= lineCount; lineNumber++) {
const lineContent = model.getLineContent(lineNumber);
const lineLength = lineContent.length;
// Reset regex to search from the beginning
searcher.reset(0);
do {
m = searcher.next(lineContent);
if (m) {
let startIndex = m.index;
let endIndex = m.index + m[0].length;
// Extend range to entire code point
if (startIndex > 0) {
const charCodeBefore = lineContent.charCodeAt(startIndex - 1);
if (strings.isHighSurrogate(charCodeBefore)) {
startIndex--;
}
}
if (endIndex + 1 < lineLength) {
const charCodeBefore = lineContent.charCodeAt(endIndex - 1);
if (strings.isHighSurrogate(charCodeBefore)) {
endIndex++;
}
}
const str = lineContent.substring(startIndex, endIndex);
const highlightReason = codePointHighlighter.shouldHighlightNonBasicASCII(str);
if (highlightReason !== SimpleHighlightReason.None) {
if (highlightReason === SimpleHighlightReason.Ambiguous) {
ambiguousCharacterCount++;
} else if (highlightReason === SimpleHighlightReason.Invisible) {
invisibleCharacterCount++;
} else if (highlightReason === SimpleHighlightReason.NonBasicASCII) {
nonBasicAsciiCharacterCount++;
} else {
assertNever(highlightReason);
}
const MAX_RESULT_LENGTH = 1000;
if (ranges.length >= MAX_RESULT_LENGTH) {
hasMore = true;
break forLoop;
}
ranges.push(new Range(lineNumber, startIndex + 1, lineNumber, endIndex + 1));
}
}
} while (m);
}
return {
ranges,
hasMore,
ambiguousCharacterCount,
invisibleCharacterCount,
nonBasicAsciiCharacterCount
};
}
public static computeUnicodeHighlightReason(char: string, options: UnicodeHighlighterOptions): UnicodeHighlighterReason | null {
const codePointHighlighter = new CodePointHighlighter(options);
const reason = codePointHighlighter.shouldHighlightNonBasicASCII(char);
switch (reason) {
case SimpleHighlightReason.None:
return null;
case SimpleHighlightReason.Invisible:
return { kind: UnicodeHighlighterReasonKind.Invisible };
case SimpleHighlightReason.Ambiguous:
const primaryConfusable = strings.AmbiguousCharacters.getPrimaryConfusable(char.codePointAt(0)!)!;
return { kind: UnicodeHighlighterReasonKind.Ambiguous, confusableWith: String.fromCodePoint(primaryConfusable) };
case SimpleHighlightReason.NonBasicASCII:
return { kind: UnicodeHighlighterReasonKind.NonBasicAscii };
}
}
}
function buildRegExpCharClassExpr(codePoints: number[], flags?: string): string {
const src = `[${strings.escapeRegExpCharacters(
codePoints.map((i) => String.fromCodePoint(i)).join('')
)}]`;
return src;
}
export const enum UnicodeHighlighterReasonKind {
Ambiguous, Invisible, NonBasicAscii
}
export type UnicodeHighlighterReason = {
kind: UnicodeHighlighterReasonKind.Ambiguous;
confusableWith: string;
} | {
kind: UnicodeHighlighterReasonKind.Invisible;
} | {
kind: UnicodeHighlighterReasonKind.NonBasicAscii
};
class CodePointHighlighter {
private readonly allowedCodePoints: Set<number>;
constructor(private readonly options: UnicodeHighlighterOptions) {
this.allowedCodePoints = new Set(options.allowedCodePoints);
}
public getCandidateCodePoints(): Set<number> | 'allNonBasicAscii' {
if (this.options.nonBasicASCII) {
return 'allNonBasicAscii';
}
const set = new Set<number>();
if (this.options.invisibleCharacters) {
for (const cp of strings.InvisibleCharacters.codePoints) {
set.add(cp);
}
}
if (this.options.ambiguousCharacters) {
for (const cp of strings.AmbiguousCharacters.getPrimaryConfusableCodePoints()) {
set.add(cp);
}
}
for (const cp of this.allowedCodePoints) {
set.delete(cp);
}
return set;
}
public shouldHighlightNonBasicASCII(character: string): SimpleHighlightReason {
const codePoint = character.codePointAt(0)!;
if (this.allowedCodePoints.has(codePoint)) {
return SimpleHighlightReason.None;
}
if (this.options.nonBasicASCII) {
return SimpleHighlightReason.NonBasicASCII;
}
if (this.options.invisibleCharacters) {
const isAllowedInvisibleCharacter = character === ' ' || character === '\n' || character === '\t';
// TODO check for emojis
if (!isAllowedInvisibleCharacter && strings.InvisibleCharacters.isInvisibleCharacter(codePoint)) {
return SimpleHighlightReason.Invisible;
}
}
if (this.options.ambiguousCharacters) {
if (strings.AmbiguousCharacters.isAmbiguous(codePoint)) {
return SimpleHighlightReason.Ambiguous;
}
}
return SimpleHighlightReason.None;
}
}
const enum SimpleHighlightReason {
None,
NonBasicASCII,
Invisible,
Ambiguous
}
export interface IUnicodeCharacterSearcherTarget {
getLineCount(): number;
getLineContent(lineNumber: number): string;
}
export interface UnicodeHighlighterOptions {
nonBasicASCII: boolean;
ambiguousCharacters: boolean;
invisibleCharacters: boolean;
includeComments: boolean;
allowedCodePoints: number[];
}

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