Merge branch 'master' into alex/python-language-configuration

This commit is contained in:
Alexandru Dima 2021-01-21 12:13:43 +01:00 committed by GitHub
commit c735c8b291
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
132 changed files with 3530 additions and 1955 deletions

View file

@ -5,7 +5,10 @@
"ecmaVersion": 6,
"sourceType": "module"
},
"plugins": ["@typescript-eslint", "jsdoc"],
"plugins": [
"@typescript-eslint",
"jsdoc"
],
"rules": {
"constructor-super": "warn",
"curly": "warn",
@ -43,7 +46,9 @@
"warn",
{
"selector": "class",
"format": ["PascalCase"]
"format": [
"PascalCase"
]
}
],
"code-no-unused-expressions": [
@ -60,11 +65,26 @@
"warn",
{
"common": [],
"node": ["common"],
"browser": ["common"],
"electron-sandbox": ["common", "browser"],
"electron-browser": ["common", "browser", "node", "electron-sandbox"],
"electron-main": ["common", "node"]
"node": [
"common"
],
"browser": [
"common"
],
"electron-sandbox": [
"common",
"browser"
],
"electron-browser": [
"common",
"browser",
"node",
"electron-sandbox"
],
"electron-main": [
"common",
"node"
]
}
],
"code-import-patterns": [
@ -74,7 +94,10 @@
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
{
"target": "**/vs/base/common/**",
"restrictions": ["vs/nls", "**/vs/base/common/**"]
"restrictions": [
"vs/nls",
"**/vs/base/common/**"
]
},
{
"target": "**/vs/base/test/common/**",
@ -432,7 +455,11 @@
},
{
"target": "**/vs/workbench/api/worker/**",
"restrictions": ["vscode", "vs/nls", "**/vs/**/{common,worker}/**"]
"restrictions": [
"vscode",
"vs/nls",
"**/vs/**/{common,worker}/**"
]
},
{
"target": "**/vs/workbench/electron-sandbox/**",
@ -878,7 +905,13 @@
},
{
"target": "**/api/**.test.ts",
"restrictions": ["**/vs/**", "assert", "sinon", "crypto", "vscode"]
"restrictions": [
"**/vs/**",
"assert",
"sinon",
"crypto",
"vscode"
]
},
{
"target": "**/{node,electron-browser,electron-main}/**/*.test.ts",
@ -903,28 +936,46 @@
},
{
"target": "**/**.test.ts",
"restrictions": ["**/vs/**", "assert", "sinon", "crypto", "xterm*"]
"restrictions": [
"**/vs/**",
"assert",
"sinon",
"crypto",
"xterm*"
]
},
{
"target": "**/test/**",
"restrictions": ["**/vs/**", "assert", "sinon", "crypto", "xterm*"]
"restrictions": [
"**/vs/**",
"assert",
"sinon",
"crypto",
"xterm*"
]
}
]
},
"overrides": [
{
"files": ["*.js"],
"files": [
"*.js"
],
"rules": {
"jsdoc/no-types": "off"
}
},
{
"files": ["**/vscode.d.ts", "**/vscode.proposed.d.ts"],
"files": [
"**/vscode.d.ts",
"**/vscode.proposed.d.ts"
],
"rules": {
"vscode-dts-create-func": "warn",
"vscode-dts-literal-or-types": "warn",
"vscode-dts-interface-naming": "warn",
"vscode-dts-cancellation": "warn",
"vscode-dts-use-thenable": "warn",
"vscode-dts-provider-naming": [
"warn",
{
@ -940,7 +991,10 @@
"vscode-dts-event-naming": [
"warn",
{
"allowed": ["onCancellationRequested", "event"],
"allowed": [
"onCancellationRequested",
"event"
],
"verbs": [
"accept",
"change",

2
.github/actions/build-chat/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
dist
node_modules

10
.github/actions/build-chat/action.yml vendored Normal file
View file

@ -0,0 +1,10 @@
name: 'Build Chat'
description: 'Notify in chat about build results.'
author: 'Christof Marti'
inputs:
workflow_run_url:
description: 'Workflow run URL of the completed build.'
required: true
runs:
using: 'node12'
main: 'dist/main.js'

20
.github/actions/build-chat/package.json vendored Normal file
View file

@ -0,0 +1,20 @@
{
"name": "build-chat",
"version": "0.0.0",
"author": "Microsoft Corporation",
"license": "MIT",
"description": "A GitHub action to create a Windows Package Manager manifest file.",
"main": "dist/main.js",
"scripts": {
"build": "tsc"
},
"dependencies": {
"@actions/core": "^1.2.6",
"@octokit/rest": "^18.0.12",
"@slack/web-api": "^6.0.0"
},
"devDependencies": {
"@types/node": "^14.14.22",
"typescript": "^4.1.3"
}
}

183
.github/actions/build-chat/src/main.ts vendored Normal file
View file

@ -0,0 +1,183 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as core from '@actions/core';
import { Octokit, RestEndpointMethodTypes } from '@octokit/rest';
import { WebClient } from '@slack/web-api';
(async () => {
const actionUrl = core.getInput('workflow_run_url');
const url = actionUrl || 'https://api.github.com/repos/microsoft/vscode/actions/runs/500731641';
console.log(url);
const parts = url.split('/');
const owner = parts[parts.length - 5];
const repo = parts[parts.length - 4];
const runId = parseInt(parts[parts.length - 1], 10);
if (actionUrl) {
await handleNotification(owner, repo, runId);
} else {
const results = await buildComplete(owner, repo, runId);
for (const message of [...results.logMessages, ...results.messages]) {
console.log(message);
}
}
})()
.then(null, console.error);
const testChannels = ['bot-log', 'bot-test-log'];
async function handleNotification(owner: string, repo: string, runId: number) {
const results = await buildComplete(owner, repo, runId);
if (results.logMessages.length || results.messages.length) {
const web = new WebClient(process.env.SLACK_TOKEN);
const memberships = await listAllMemberships(web);
const memberTestChannels = memberships.filter(m => testChannels.indexOf(m.name) !== -1);
for (const message of results.logMessages) {
for (const testChannel of memberTestChannels) {
await web.chat.postMessage({
text: message,
channel: testChannel.id,
});
}
}
for (const message of results.messages) {
for (const channel of memberships) {
await web.chat.postMessage({
text: message,
channel: channel.id,
});
}
}
}
}
async function buildComplete(owner: string, repo: string, runId: number) {
console.log(`buildComplete: https://github.com/${owner}/${repo}/actions/runs/${runId}`);
const auth = `token ${process.env.GITHUB_TOKEN}`;
const octokit = new Octokit({ auth });
const buildResult = (await octokit.actions.getWorkflowRun({
owner,
repo,
run_id: runId,
})).data;
if (buildResult.head_branch !== 'master' && !buildResult.head_branch?.startsWith('release/')) {
console.error('Private branch. Terminating.')
return { logMessages: [], messages: [] };
}
// const buildQuery = `${buildsApiUrl}?$top=10&maxTime=${buildResult.finishTime}&definitions=${buildResult.definition.id}&branchName=${buildResult.sourceBranch}&resultFilter=${results.join(',')}&api-version=5.0-preview.4`;
const buildResults = (await octokit.actions.listWorkflowRuns({
owner,
repo,
workflow_id: buildResult.workflow_id,
branch: buildResult.head_branch || undefined,
per_page: 5, // More returns 502s.
})).data.workflow_runs
.filter(run => run.status === 'completed');
const currentBuildIndex = buildResults.findIndex(build => build.id === buildResult.id);
if (currentBuildIndex === -1) {
console.error('Build not on first page. Terminating.')
console.error(buildResults.map(({ id, status, conclusion }) => ({ id, status, conclusion })));
return { logMessages: [], messages: [] };
}
const slicedResults = buildResults.slice(currentBuildIndex, currentBuildIndex + 2);
const builds = slicedResults
.map<Build>((build, i, array) => ({
data: build,
previousSourceVersion: i < array.length - 1 ? array[i + 1].head_sha : undefined,
authors: [],
buildHtmlUrl: build.html_url,
changesHtmlUrl: '',
}));
const logMessages = builds.slice(0, 1)
.map(build => `Id: ${build.data.id} | Branch: ${build.data.head_branch} | Conclusion: ${build.data.conclusion} | Created: ${build.data.created_at} | Updated: ${build.data.updated_at}`);
const transitionedBuilds = builds.filter((build, i, array) => i < array.length - 1 && transitioned(build, array[i + 1]));
await Promise.all(transitionedBuilds
.map(async build => {
if (build.previousSourceVersion) {
const cmp = await compareCommits(octokit, owner, repo, build.previousSourceVersion, build.data.head_sha);
const commits = cmp.data.commits;
const authors = new Set<string>([
...commits.map((c: any) => c.author.login),
...commits.map((c: any) => c.committer.login),
]);
authors.delete('web-flow'); // GitHub Web UI committer
build.authors = [...authors];
build.changesHtmlUrl = `https://github.com/${owner}/${repo}/compare/${build.previousSourceVersion.substr(0, 7)}...${build.data.head_sha.substr(0, 7)}`; // Shorter than: cmp.data.html_url
}
}));
const vscode = repo === 'vscode';
const name = vscode ? `VS Code ${buildResult.name} Build` : buildResult.name;
// TBD: `Requester: ${vstsToSlackUser(build.requester, build.degraded)}${pingBenForSmokeTests && releaseBuild && build.result === 'partiallySucceeded' ? ' | Ping: @bpasero' : ''}`
const messages = transitionedBuilds.map(build => `${name}
Result: ${build.data.conclusion} | Branch: ${build.data.head_branch} | Authors: ${githubToSlackUsers(build.authors, build.degraded).sort().join(', ') || `None (rebuild)`}
Build: ${build.buildHtmlUrl}
Changes: ${build.changesHtmlUrl}`);
return { logMessages, messages };
}
const conclusions = ['success', 'failure']
function transitioned(newer: Build, older: Build) {
const newerResult = newer.data.conclusion || 'success';
const olderResult = older.data.conclusion || 'success';
if (newerResult === olderResult) {
return false;
}
if (conclusions.indexOf(newerResult) > conclusions.indexOf(olderResult)) {
newer.degraded = true;
}
return true;
}
async function compareCommits(octokit: Octokit, owner: string, repo: string, base: string, head: string) {
return octokit.repos.compareCommits({ owner, repo, base, head });
}
function githubToSlackUsers(githubUsers: string[], at?: boolean) {
return githubUsers.map(g => githubToAccounts[g] ? `${at ? '@' : ''}${githubToAccounts[g].slack}` : g);
}
const accounts = [
{
github: 'tbd',
slack: 'tbd'
}
];
type Accounts = (typeof accounts)[0];
const githubToAccounts = accounts.reduce((m, e) => {
m[e.github] = e;
return m;
}, <Record<string, Accounts>>{});
interface AllChannels {
channels: {
id: string;
name: string;
is_member: boolean;
}[];
}
async function listAllMemberships(web: WebClient) {
const groups = await web.conversations.list({ types: 'public_channel,private_channel' }) as unknown as AllChannels;
return groups.channels
.filter(c => c.is_member);
}
interface Build {
data: RestEndpointMethodTypes['actions']['getWorkflowRun']['response']['data'];
previousSourceVersion: string | undefined;
authors: string[];
buildHtmlUrl: string;
changesHtmlUrl: string;
degraded?: boolean;
}

View file

@ -0,0 +1,18 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2017",
"strict": true,
"noUnusedLocals": true,
"resolveJsonModule": true,
"lib": [
"es2017"
],
"sourceMap": true,
"outDir": "./dist",
"rootDir": "./src",
},
"exclude": [
"node_modules"
]
}

296
.github/actions/build-chat/yarn.lock vendored Normal file
View file

@ -0,0 +1,296 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1
"@actions/core@^1.2.6":
version "1.2.6"
resolved "https://registry.yarnpkg.com/@actions/core/-/core-1.2.6.tgz#a78d49f41a4def18e88ce47c2cac615d5694bf09"
integrity sha512-ZQYitnqiyBc3D+k7LsgSBmMDVkOVidaagDG7j3fOym77jNunWRuYx7VSHa9GNfFZh+zh61xsCjRj4JxMZlDqTA==
"@octokit/auth-token@^2.4.4":
version "2.4.4"
resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.4.4.tgz#ee31c69b01d0378c12fd3ffe406030f3d94d3b56"
integrity sha512-LNfGu3Ro9uFAYh10MUZVaT7X2CnNm2C8IDQmabx+3DygYIQjs9FwzFAHN/0t6mu5HEPhxcb1XOuxdpY82vCg2Q==
dependencies:
"@octokit/types" "^6.0.0"
"@octokit/core@^3.2.3":
version "3.2.4"
resolved "https://registry.yarnpkg.com/@octokit/core/-/core-3.2.4.tgz#5791256057a962eca972e31818f02454897fd106"
integrity sha512-d9dTsqdePBqOn7aGkyRFe7pQpCXdibSJ5SFnrTr0axevObZrpz3qkWm7t/NjYv5a66z6vhfteriaq4FRz3e0Qg==
dependencies:
"@octokit/auth-token" "^2.4.4"
"@octokit/graphql" "^4.5.8"
"@octokit/request" "^5.4.12"
"@octokit/types" "^6.0.3"
before-after-hook "^2.1.0"
universal-user-agent "^6.0.0"
"@octokit/endpoint@^6.0.1":
version "6.0.10"
resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.10.tgz#741ce1fa2f4fb77ce8ebe0c6eaf5ce63f565f8e8"
integrity sha512-9+Xef8nT7OKZglfkOMm7IL6VwxXUQyR7DUSU0LH/F7VNqs8vyd7es5pTfz9E7DwUIx7R3pGscxu1EBhYljyu7Q==
dependencies:
"@octokit/types" "^6.0.0"
is-plain-object "^5.0.0"
universal-user-agent "^6.0.0"
"@octokit/graphql@^4.5.8":
version "4.5.8"
resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.5.8.tgz#d42373633c3015d0eafce64a8ce196be167fdd9b"
integrity sha512-WnCtNXWOrupfPJgXe+vSmprZJUr0VIu14G58PMlkWGj3cH+KLZEfKMmbUQ6C3Wwx6fdhzVW1CD5RTnBdUHxhhA==
dependencies:
"@octokit/request" "^5.3.0"
"@octokit/types" "^6.0.0"
universal-user-agent "^6.0.0"
"@octokit/openapi-types@^3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-3.0.0.tgz#f73d48af2d21bf4f97fbf38fae43b54699e0dbba"
integrity sha512-jOp1CVRw+OBJaZtG9QzZggvJXvyzgDXuW948SWsDiwmyDuCjeYCiF3TDD/qvhpF580RfP7iBIos4AVU6yhgMlA==
"@octokit/plugin-paginate-rest@^2.6.2":
version "2.8.0"
resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.8.0.tgz#2b41e12b494e895bf5fb5b12565d2c80a0ecc6ae"
integrity sha512-HtuEQ2AYE4YFEBQN0iHmMsIvVucd5RsnwJmRKIsfAg1/ZeoMaU+jXMnTAZqIUEmcVJA27LjHUm3f1hxf8Fpdxw==
dependencies:
"@octokit/types" "^6.4.0"
"@octokit/plugin-request-log@^1.0.2":
version "1.0.2"
resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.2.tgz#394d59ec734cd2f122431fbaf05099861ece3c44"
integrity sha512-oTJSNAmBqyDR41uSMunLQKMX0jmEXbwD1fpz8FG27lScV3RhtGfBa1/BBLym+PxcC16IBlF7KH9vP1BUYxA+Eg==
"@octokit/plugin-rest-endpoint-methods@4.4.1":
version "4.4.1"
resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.4.1.tgz#105cf93255432155de078c9efc33bd4e14d1cd63"
integrity sha512-+v5PcvrUcDeFXf8hv1gnNvNLdm4C0+2EiuWt9EatjjUmfriM1pTMM+r4j1lLHxeBQ9bVDmbywb11e3KjuavieA==
dependencies:
"@octokit/types" "^6.1.0"
deprecation "^2.3.1"
"@octokit/request-error@^2.0.0":
version "2.0.4"
resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.0.4.tgz#07dd5c0521d2ee975201274c472a127917741262"
integrity sha512-LjkSiTbsxIErBiRh5wSZvpZqT4t0/c9+4dOe0PII+6jXR+oj/h66s7E4a/MghV7iT8W9ffoQ5Skoxzs96+gBPA==
dependencies:
"@octokit/types" "^6.0.0"
deprecation "^2.0.0"
once "^1.4.0"
"@octokit/request@^5.3.0", "@octokit/request@^5.4.12":
version "5.4.12"
resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.4.12.tgz#b04826fa934670c56b135a81447be2c1723a2ffc"
integrity sha512-MvWYdxengUWTGFpfpefBBpVmmEYfkwMoxonIB3sUGp5rhdgwjXL1ejo6JbgzG/QD9B/NYt/9cJX1pxXeSIUCkg==
dependencies:
"@octokit/endpoint" "^6.0.1"
"@octokit/request-error" "^2.0.0"
"@octokit/types" "^6.0.3"
deprecation "^2.0.0"
is-plain-object "^5.0.0"
node-fetch "^2.6.1"
once "^1.4.0"
universal-user-agent "^6.0.0"
"@octokit/rest@^18.0.12":
version "18.0.12"
resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.0.12.tgz#278bd41358c56d87c201e787e8adc0cac132503a"
integrity sha512-hNRCZfKPpeaIjOVuNJzkEL6zacfZlBPV8vw8ReNeyUkVvbuCvvrrx8K8Gw2eyHHsmd4dPlAxIXIZ9oHhJfkJpw==
dependencies:
"@octokit/core" "^3.2.3"
"@octokit/plugin-paginate-rest" "^2.6.2"
"@octokit/plugin-request-log" "^1.0.2"
"@octokit/plugin-rest-endpoint-methods" "4.4.1"
"@octokit/types@^6.0.0", "@octokit/types@^6.0.3", "@octokit/types@^6.1.0", "@octokit/types@^6.4.0":
version "6.4.0"
resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.4.0.tgz#f3f47be70bcdb3c26f2c2619f3dd0ced466a265c"
integrity sha512-1FEmuVppZE2zG0rBdQlviRz5cp0udyI63zyhBVPrm0FRNAsQkAXU7IYWQg1XvhChFut8YbFYN1usQpk54D6/4w==
dependencies:
"@octokit/openapi-types" "^3.0.0"
"@types/node" ">= 8"
"@slack/logger@>=1.0.0 <3.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@slack/logger/-/logger-2.0.0.tgz#6a4e1c755849bc0f66dac08a8be54ce790ec0e6b"
integrity sha512-OkIJpiU2fz6HOJujhlhfIGrc8hB4ibqtf7nnbJQDerG0BqwZCfmgtK5sWzZ0TkXVRBKD5MpLrTmCYyMxoMCgPw==
dependencies:
"@types/node" ">=8.9.0"
"@slack/types@^1.7.0":
version "1.10.0"
resolved "https://registry.yarnpkg.com/@slack/types/-/types-1.10.0.tgz#cbf7d83e1027f4cbfd13d6b429f120c7fb09127a"
integrity sha512-tA7GG7Tj479vojfV3AoxbckalA48aK6giGjNtgH6ihpLwTyHE3fIgRrvt8TWfLwW8X8dyu7vgmAsGLRG7hWWOg==
"@slack/web-api@^6.0.0":
version "6.0.0"
resolved "https://registry.yarnpkg.com/@slack/web-api/-/web-api-6.0.0.tgz#14c65ed73c66a187e5f20e12c3898dfd8d5cbf7c"
integrity sha512-YD1wqWuzrYPf4RQyD7OnYS5lImUmNWn+G5V6Qt0N97fPYxqhT72YJtRdSnsTc3VkH5R5imKOhYxb+wqI9hiHnA==
dependencies:
"@slack/logger" ">=1.0.0 <3.0.0"
"@slack/types" "^1.7.0"
"@types/is-stream" "^1.1.0"
"@types/node" ">=12.0.0"
axios "^0.21.1"
eventemitter3 "^3.1.0"
form-data "^2.5.0"
is-stream "^1.1.0"
p-queue "^6.6.1"
p-retry "^4.0.0"
"@types/is-stream@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@types/is-stream/-/is-stream-1.1.0.tgz#b84d7bb207a210f2af9bed431dc0fbe9c4143be1"
integrity sha512-jkZatu4QVbR60mpIzjINmtS1ZF4a/FqdTUTBeQDVOQ2PYyidtwFKr0B5G6ERukKwliq+7mIXvxyppwzG5EgRYg==
dependencies:
"@types/node" "*"
"@types/node@*", "@types/node@>= 8", "@types/node@>=12.0.0", "@types/node@>=8.9.0", "@types/node@^14.14.22":
version "14.14.22"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.22.tgz#0d29f382472c4ccf3bd96ff0ce47daf5b7b84b18"
integrity sha512-g+f/qj/cNcqKkc3tFqlXOYjrmZA+jNBiDzbP3kH+B+otKFqAdPgVTGP1IeKRdMml/aE69as5S4FqtxAbl+LaMw==
"@types/retry@^0.12.0":
version "0.12.0"
resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d"
integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
axios@^0.21.1:
version "0.21.1"
resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8"
integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==
dependencies:
follow-redirects "^1.10.0"
before-after-hook@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.1.0.tgz#b6c03487f44e24200dd30ca5e6a1979c5d2fb635"
integrity sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A==
combined-stream@^1.0.6:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies:
delayed-stream "~1.0.0"
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
deprecation@^2.0.0, deprecation@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919"
integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==
eventemitter3@^3.1.0:
version "3.1.2"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7"
integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q==
eventemitter3@^4.0.4:
version "4.0.7"
resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f"
integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==
follow-redirects@^1.10.0:
version "1.13.1"
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.1.tgz#5f69b813376cee4fd0474a3aba835df04ab763b7"
integrity sha512-SSG5xmZh1mkPGyKzjZP8zLjltIfpW32Y5QpdNJyjcfGxK3qo3NDDkZOZSFiGn1A6SclQxY9GzEwAHQ3dmYRWpg==
form-data@^2.5.0:
version "2.5.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4"
integrity sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.6"
mime-types "^2.1.12"
is-plain-object@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344"
integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==
is-stream@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ=
mime-db@1.45.0:
version "1.45.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.45.0.tgz#cceeda21ccd7c3a745eba2decd55d4b73e7879ea"
integrity sha512-CkqLUxUk15hofLoLyljJSrukZi8mAtgd+yE5uO4tqRZsdsAJKv0O+rFMhVDRJgozy+yG6md5KwuXhD4ocIoP+w==
mime-types@^2.1.12:
version "2.1.28"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.28.tgz#1160c4757eab2c5363888e005273ecf79d2a0ecd"
integrity sha512-0TO2yJ5YHYr7M2zzT7gDU1tbwHxEUWBCLt0lscSNpcdAfFyJOVEpRYNS7EXVcTLNj/25QO8gulHC5JtTzSE2UQ==
dependencies:
mime-db "1.45.0"
node-fetch@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
once@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
dependencies:
wrappy "1"
p-finally@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
p-queue@^6.6.1:
version "6.6.2"
resolved "https://registry.yarnpkg.com/p-queue/-/p-queue-6.6.2.tgz#2068a9dcf8e67dd0ec3e7a2bcb76810faa85e426"
integrity sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==
dependencies:
eventemitter3 "^4.0.4"
p-timeout "^3.2.0"
p-retry@^4.0.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.2.0.tgz#ea9066c6b44f23cab4cd42f6147cdbbc6604da5d"
integrity sha512-jPH38/MRh263KKcq0wBNOGFJbm+U6784RilTmHjB/HM9kH9V8WlCpVUcdOmip9cjXOh6MxZ5yk1z2SjDUJfWmA==
dependencies:
"@types/retry" "^0.12.0"
retry "^0.12.0"
p-timeout@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-3.2.0.tgz#c7e17abc971d2a7962ef83626b35d635acf23dfe"
integrity sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==
dependencies:
p-finally "^1.0.0"
retry@^0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b"
integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=
typescript@^4.1.3:
version "4.1.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.3.tgz#519d582bd94cba0cf8934c7d8e8467e473f53bb7"
integrity sha512-B3ZIOf1IKeH2ixgHhj6la6xdwR9QrLC5d1VKeCSY4tvkqhF2eqd9O7txNlS0PO3GrBAFIdr3L1ndNwteUbZLYg==
universal-user-agent@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee"
integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=

35
.github/workflows/build-chat.yml vendored Normal file
View file

@ -0,0 +1,35 @@
name: "Build Chat"
on:
workflow_run:
workflows:
- CI
types:
- completed
branches:
- master
- release/*
jobs:
main:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v2
- name: Setup Node.js 12.x
uses: actions/setup-node@v1.4.4
with:
node-version: "12.x"
- name: Build
run: yarn install && yarn run build
working-directory: .github/actions/build-chat
- name: Build Chat
uses: ./.github/actions/build-chat
with:
workflow_run_url: ${{ github.event.workflow_run.url }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }}

View file

@ -32,13 +32,15 @@ jobs:
- name: Compute node modules cache key
id: nodeModulesCacheKey
run: echo "::set-output name=value::$(node build/azure-pipelines/common/computeNodeModulesCacheKey.js)"
- name: Cache node modules
- name: Cache node_modules archive
id: cacheNodeModules
uses: actions/cache@v2
with:
path: "**/node_modules"
key: ${{ runner.os }}-cacheNodeModules10-${{ steps.nodeModulesCacheKey.outputs.value }}
restore-keys: ${{ runner.os }}-cacheNodeModules10-
path: ".build/node_modules_cache"
key: "${{ runner.os }}-cacheNodeModulesArchive-${{ steps.nodeModulesCacheKey.outputs.value }}"
- name: Extract node_modules archive
if: ${{ steps.cacheNodeModules.outputs.cache-hit == 'true' }}
run: 7z.exe x .build/node_modules_cache/cache.7z -aos
- name: Get yarn cache directory path
id: yarnCacheDirPath
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
@ -56,6 +58,13 @@ jobs:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
ELECTRON_SKIP_BINARY_DOWNLOAD: 1
run: yarn --frozen-lockfile --network-timeout 180000
- name: Create node_modules archive
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
run: |
mkdir -Force .build
node build/azure-pipelines/common/listNodeModules.js .build/node_modules_list.txt
mkdir -Force .build/node_modules_cache
7z.exe a .build/node_modules_cache/cache.7z -mx3 `@.build/node_modules_list.txt
- name: Compile and Download
run: yarn npm-run-all --max_old_space_size=4095 -lp compile "electron x64" playwright-install download-builtin-extensions
@ -100,8 +109,8 @@ jobs:
uses: actions/cache@v2
with:
path: "**/node_modules"
key: ${{ runner.os }}-cacheNodeModules10-${{ steps.nodeModulesCacheKey.outputs.value }}
restore-keys: ${{ runner.os }}-cacheNodeModules10-
key: ${{ runner.os }}-cacheNodeModules11-${{ steps.nodeModulesCacheKey.outputs.value }}
restore-keys: ${{ runner.os }}-cacheNodeModules11-
- name: Get yarn cache directory path
id: yarnCacheDirPath
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
@ -156,8 +165,8 @@ jobs:
uses: actions/cache@v2
with:
path: "**/node_modules"
key: ${{ runner.os }}-cacheNodeModules10-${{ steps.nodeModulesCacheKey.outputs.value }}
restore-keys: ${{ runner.os }}-cacheNodeModules10-
key: ${{ runner.os }}-cacheNodeModules11-${{ steps.nodeModulesCacheKey.outputs.value }}
restore-keys: ${{ runner.os }}-cacheNodeModules11-
- name: Get yarn cache directory path
id: yarnCacheDirPath
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}
@ -209,8 +218,8 @@ jobs:
uses: actions/cache@v2
with:
path: "**/node_modules"
key: ${{ runner.os }}-cacheNodeModules10-${{ steps.nodeModulesCacheKey.outputs.value }}
restore-keys: ${{ runner.os }}-cacheNodeModules10-
key: ${{ runner.os }}-cacheNodeModules11-${{ steps.nodeModulesCacheKey.outputs.value }}
restore-keys: ${{ runner.os }}-cacheNodeModules11-
- name: Get yarn cache directory path
id: yarnCacheDirPath
if: ${{ steps.cacheNodeModules.outputs.cache-hit != 'true' }}

View file

@ -0,0 +1,14 @@
[
{
"kind": 1,
"language": "markdown",
"value": "## Native Notebook Paper Cuts\n\nThis notebook serves as an ongoing collection of paper cut issues that we encounter while dogfooding native notebooks. With that in mind only promote issues that really turn you off when using notebooks, e.g issues that make you wanna stop using native notebooks. To mark an issue (bug, feature-request, etc) as paper cut add the labels: `notebook` _and_ `papercut :drop_of_blood:`",
"editable": true
},
{
"kind": 2,
"language": "github-issues",
"value": "repo:microsoft/vscode is:open label:notebook label:\"papercut :drop_of_blood:\"",
"editable": true
}
]

View file

@ -9,7 +9,7 @@ const es = require("event-stream");
const vfs = require("vinyl-fs");
const util = require("../lib/util");
// @ts-ignore
const deps = require("../dependencies");
const deps = require("../lib/dependencies");
const azure = require('gulp-azure-storage');
const root = path.dirname(path.dirname(__dirname));
const commit = util.getVersion(root);

View file

@ -11,7 +11,7 @@ import * as Vinyl from 'vinyl';
import * as vfs from 'vinyl-fs';
import * as util from '../lib/util';
// @ts-ignore
import * as deps from '../dependencies';
import * as deps from '../lib/dependencies';
const azure = require('gulp-azure-storage');
const root = path.dirname(path.dirname(__dirname));

View file

@ -26,7 +26,7 @@ const packageJson = require('../package.json');
const product = require('../product.json');
const crypto = require('crypto');
const i18n = require('./lib/i18n');
const { getProductionDependencies } = require('./dependencies');
const { getProductionDependencies } = require('./lib/dependencies');
const { config } = require('./lib/electron');
const createAsar = require('./lib/asar').createAsar;
const minimist = require('minimist');

60
build/lib/dependencies.js Normal file
View file

@ -0,0 +1,60 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
Object.defineProperty(exports, "__esModule", { value: true });
exports.getProductionDependencies = void 0;
const path = require("path");
const cp = require("child_process");
const _ = require("underscore");
const parseSemver = require('parse-semver');
function asYarnDependency(prefix, tree) {
let parseResult;
try {
parseResult = parseSemver(tree.name);
}
catch (err) {
err.message += `: ${tree.name}`;
console.warn(`Could not parse semver: ${tree.name}`);
return null;
}
// not an actual dependency in disk
if (parseResult.version !== parseResult.range) {
return null;
}
const name = parseResult.name;
const version = parseResult.version;
const dependencyPath = path.join(prefix, name);
const children = [];
for (const child of (tree.children || [])) {
const dep = asYarnDependency(path.join(prefix, name, 'node_modules'), child);
if (dep) {
children.push(dep);
}
}
return { name, version, path: dependencyPath, children };
}
function getYarnProductionDependencies(cwd) {
const raw = cp.execSync('yarn list --json', { cwd, encoding: 'utf8', env: Object.assign(Object.assign({}, process.env), { NODE_ENV: 'production' }), stdio: [null, null, 'inherit'] });
const match = /^{"type":"tree".*$/m.exec(raw);
if (!match || match.length !== 1) {
throw new Error('Could not parse result of `yarn list --json`');
}
const trees = JSON.parse(match[0]).data.trees;
return trees
.map(tree => asYarnDependency(path.join(cwd, 'node_modules'), tree))
.filter((dep) => !!dep);
}
function getProductionDependencies(cwd) {
const result = [];
const deps = getYarnProductionDependencies(cwd);
const flatten = (dep) => { result.push({ name: dep.name, version: dep.version, path: dep.path }); dep.children.forEach(flatten); };
deps.forEach(flatten);
return _.uniq(result);
}
exports.getProductionDependencies = getProductionDependencies;
if (require.main === module) {
const root = path.dirname(path.dirname(__dirname));
console.log(JSON.stringify(getProductionDependencies(root), null, ' '));
}

View file

@ -5,12 +5,27 @@
'use strict';
const path = require('path');
import * as path from 'path';
import * as cp from 'child_process';
import * as _ from 'underscore';
const parseSemver = require('parse-semver');
const cp = require('child_process');
const _ = require('underscore');
function asYarnDependency(prefix, tree) {
interface Tree {
readonly name: string;
readonly children?: Tree[];
}
interface FlatDependency {
readonly name: string;
readonly version: string;
readonly path: string;
}
interface Dependency extends FlatDependency {
readonly children: Dependency[];
}
function asYarnDependency(prefix: string, tree: Tree): Dependency | null {
let parseResult;
try {
@ -42,7 +57,7 @@ function asYarnDependency(prefix, tree) {
return { name, version, path: dependencyPath, children };
}
function getYarnProductionDependencies(cwd) {
function getYarnProductionDependencies(cwd: string): Dependency[] {
const raw = cp.execSync('yarn list --json', { cwd, encoding: 'utf8', env: { ...process.env, NODE_ENV: 'production' }, stdio: [null, null, 'inherit'] });
const match = /^{"type":"tree".*$/m.exec(raw);
@ -50,25 +65,22 @@ function getYarnProductionDependencies(cwd) {
throw new Error('Could not parse result of `yarn list --json`');
}
const trees = JSON.parse(match[0]).data.trees;
const trees = JSON.parse(match[0]).data.trees as Tree[];
return trees
.map(tree => asYarnDependency(path.join(cwd, 'node_modules'), tree))
.filter(dep => !!dep);
.filter<Dependency>((dep): dep is Dependency => !!dep);
}
function getProductionDependencies(cwd) {
const result = [];
export function getProductionDependencies(cwd: string): FlatDependency[] {
const result: FlatDependency[] = [];
const deps = getYarnProductionDependencies(cwd);
const flatten = dep => { result.push({ name: dep.name, version: dep.version, path: dep.path }); dep.children.forEach(flatten); };
const flatten = (dep: Dependency) => { result.push({ name: dep.name, version: dep.version, path: dep.path }); dep.children.forEach(flatten); };
deps.forEach(flatten);
return _.uniq(result);
}
module.exports.getProductionDependencies = getProductionDependencies;
if (require.main === module) {
const root = path.dirname(__dirname);
const root = path.dirname(path.dirname(__dirname));
console.log(JSON.stringify(getProductionDependencies(root), null, ' '));
}

View file

@ -0,0 +1,24 @@
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
module.exports = new class ApiEventNaming {
constructor() {
this.meta = {
messages: {
usage: 'Use the Thenable-type instead of the Promise type',
}
};
}
create(context) {
return {
['TSTypeAnnotation TSTypeReference Identifier[name="Promise"]']: (node) => {
context.report({
node,
messageId: 'usage',
});
}
};
}
};

View file

@ -0,0 +1,30 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as eslint from 'eslint';
export = new class ApiEventNaming implements eslint.Rule.RuleModule {
readonly meta: eslint.Rule.RuleMetaData = {
messages: {
usage: 'Use the Thenable-type instead of the Promise type',
}
};
create(context: eslint.Rule.RuleContext): eslint.Rule.RuleListener {
return {
['TSTypeAnnotation TSTypeReference Identifier[name="Promise"]']: (node: any) => {
context.report({
node,
messageId: 'usage',
});
}
};
}
};

View file

@ -269,6 +269,11 @@ export class ApiImpl implements API {
return this.getRepository(root) || null;
}
async openRepository(root: Uri): Promise<Repository | null> {
await this._model.openRepository(root.fsPath);
return this.getRepository(root) || null;
}
registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable {
return this._model.registerRemoteSourceProvider(provider);
}

View file

@ -254,6 +254,7 @@ export interface API {
toGitUri(uri: Uri, ref: string): Uri;
getRepository(uri: Uri): Repository | null;
init(root: Uri): Promise<Repository | null>;
openRepository(root: Uri): Promise<Repository | null>
registerRemoteSourceProvider(provider: RemoteSourceProvider): Disposable;
registerCredentialsProvider(provider: CredentialsProvider): Disposable;

View file

@ -467,7 +467,7 @@ export class Git {
try {
const networkPath = await new Promise<string | undefined>(resolve =>
realpath.native(`${letter}:`, { encoding: 'utf8' }, (err, resolvedPath) =>
realpath.native(`${letter}:\\`, { encoding: 'utf8' }, (err, resolvedPath) =>
resolve(err !== null ? undefined : resolvedPath),
),
);

View file

@ -1369,7 +1369,7 @@ export class Repository implements Disposable {
await this.repository.fetch({ all: true });
}
if (await this.checkIfMaybeRebased(branch)) {
if (await this.checkIfMaybeRebased(this.HEAD?.name)) {
await this.repository.pull(rebase, remote, branch, { unshallow, tags });
}
});
@ -1440,7 +1440,7 @@ export class Repository implements Disposable {
await this.repository.fetch({ all: true, cancellationToken });
}
if (await this.checkIfMaybeRebased(pullBranch)) {
if (await this.checkIfMaybeRebased(this.HEAD?.name)) {
await this.repository.pull(rebase, remoteName, pullBranch, { tags, cancellationToken });
}
};
@ -1472,7 +1472,7 @@ export class Repository implements Disposable {
});
}
private async checkIfMaybeRebased(branch?: string) {
private async checkIfMaybeRebased(currentBranch?: string) {
const config = workspace.getConfiguration('git');
const shouldIgnore = config.get<boolean>('ignoreRebaseWarning') === true;
@ -1481,12 +1481,16 @@ export class Repository implements Disposable {
}
const maybeRebased = await this.run(Operation.Log, async () => {
const result = await this.repository.run(['log', '--oneline', '--cherry', `${branch ?? ''}...${branch ?? ''}@{upstream}`, '--']);
if (result.exitCode) {
try {
const result = await this.repository.run(['log', '--oneline', '--cherry', `${currentBranch ?? ''}...${currentBranch ?? ''}@{upstream}`, '--']);
if (result.exitCode) {
return false;
}
return /^=/.test(result.stdout);
} catch {
return false;
}
return /^=/.test(result.stdout);
});
if (!maybeRebased) {
@ -1497,9 +1501,9 @@ export class Repository implements Disposable {
const pull = { title: localize('pull', "Pull") };
const cancel = { title: localize('dont pull', "Don't Pull") };
const result = await window.showWarningMessage(
branch
? localize('pull branch maybe rebased', "It looks like branch \'{0}\' might have been rebased. Are you sure you still want to pull?", branch)
: localize('pull maybe rebased', "It looks like the current branch might have been rebased. Are you sure you still want to pull?"),
currentBranch
? localize('pull branch maybe rebased', "It looks like the current branch \'{0}\' might have been rebased. Are you sure you still want to pull into it?", currentBranch)
: localize('pull maybe rebased', "It looks like the current branch might have been rebased. Are you sure you still want to pull into it?"),
always, pull, cancel
);

View file

@ -34,7 +34,7 @@ export class Keychain {
constructor(private context: vscode.ExtensionContext) { }
async setToken(token: string): Promise<void> {
try {
return await this.context.secrets.set(SERVICE_ID, token);
return await this.context.secrets.store(SERVICE_ID, token);
} catch (e) {
// Ignore
Logger.error(`Setting token failed: ${e}`);

View file

@ -48,7 +48,7 @@ export class Keychain {
async setToken(token: string): Promise<void> {
try {
return await this.context.secrets.set(SERVICE_ID, token);
return await this.context.secrets.store(SERVICE_ID, token);
} catch (e) {
Logger.error(`Setting token failed: ${e}`);

View file

@ -31,7 +31,7 @@ const jsTsLanguageConfiguration: vscode.LanguageConfiguration = {
}, {
// e.g. * ...|
beforeText: /^(\t|[ ])*[ ]\*([ ]([^\*]|\*(?!\/))*)?$/,
oneLineAboveText: /(?=^(\s*(\/\*\*|\*)).*)(?=(?!(\s*\*\/)))/,
previousLineText: /(?=^(\s*(\/\*\*|\*)).*)(?=(?!(\s*\*\/)))/,
action: { indentAction: vscode.IndentAction.None, appendText: '* ' },
}, {
// e.g. */|

View file

@ -215,7 +215,8 @@ export function activate(context: vscode.ExtensionContext) {
}, (progress) => doResolve(_authority, progress));
},
tunnelFactory,
tunnelFeatures: { elevation: true, public: false }
tunnelFeatures: { elevation: true, public: false },
showCandidatePort: async (_host, port) => port === 100
});
context.subscriptions.push(authorityResolverDisposable);
@ -316,18 +317,47 @@ function getConfiguration<T>(id: string): T | undefined {
}
function tunnelFactory(tunnelOptions: vscode.TunnelOptions): Promise<vscode.Tunnel> | undefined {
const onDidDispose: vscode.EventEmitter<void> = new vscode.EventEmitter();
let isDisposed = false;
return Promise.resolve({
localAddress: { host: 'localhost', port: (tunnelOptions.localAddressPort === undefined ? tunnelOptions.remoteAddress.port : tunnelOptions.localAddressPort) + 1 },
remoteAddress: tunnelOptions.remoteAddress,
public: tunnelOptions.public,
onDidDispose: onDidDispose.event,
dispose: () => {
if (!isDisposed) {
isDisposed = true;
onDidDispose.fire();
let remotePort = tunnelOptions.remoteAddress.port;
if (remotePort === 100) {
return createTunnelServer();
} else {
const port: number = (tunnelOptions.localAddressPort || remotePort) + 1;
const dummyTunnel = newTunnel({ host: 'localhost', port });
return Promise.resolve(dummyTunnel);
}
function newTunnel(localAddress: { host: string, port: number }) {
const onDidDispose: vscode.EventEmitter<void> = new vscode.EventEmitter();
let isDisposed = false;
return {
localAddress,
remoteAddress: tunnelOptions.remoteAddress,
public: tunnelOptions.public,
onDidDispose: onDidDispose.event,
dispose: () => {
if (!isDisposed) {
isDisposed = true;
onDidDispose.fire();
}
}
}
});
};
}
function createTunnelServer(): Promise<vscode.Tunnel> {
return new Promise<vscode.Tunnel>((res, _rej) => {
const proxyServer = net.createServer(proxySocket => {
outputChannel.appendLine(`Connection accepted`);
const remoteSocket = net.createConnection({ host: tunnelOptions.remoteAddress.host, port: tunnelOptions.remoteAddress.port });
remoteSocket.pipe(proxySocket);
proxySocket.pipe(remoteSocket);
});
proxyServer.listen(tunnelOptions.localAddressPort === undefined ? 0 : tunnelOptions.localAddressPort, () => {
const localPort = (<net.AddressInfo>proxyServer.address()).port;
console.log(`New tunnel server: Remote ${tunnelOptions.remoteAddress.port} -> local ${localPort}`);
const tunnel = newTunnel({ host: 'localhost', port: localPort });
res(tunnel);
});
});
}
}

View file

@ -1,7 +1,7 @@
{
"name": "code-oss-dev",
"version": "1.53.0",
"distro": "d714854350ee61de7f105af6bb54b64404be26e2",
"distro": "9743d4c538f650da5636ce1b2994c719dfa8e953",
"author": {
"name": "Microsoft Corporation"
},

View file

@ -21,6 +21,7 @@ const vfs = require('vinyl-fs');
const uuid = require('uuid');
const extensions = require('../../build/lib/extensions');
const { getBuiltInExtensions } = require('../../build/lib/builtInExtensions');
const APP_ROOT = path.join(__dirname, '..', '..');
const BUILTIN_EXTENSIONS_ROOT = path.join(APP_ROOT, 'extensions');
@ -90,6 +91,8 @@ const exists = (path) => util.promisify(fs.exists)(path);
const readFile = (path) => util.promisify(fs.readFile)(path);
async function getBuiltInExtensionInfos() {
await getBuiltInExtensions();
const allExtensions = [];
/** @type {Object.<string, string>} */
const locations = {};
@ -400,8 +403,8 @@ async function handleRoot(req, res) {
const secondaryHost = (
req.headers['host']
? req.headers['host'].replace(':' + PORT, ':' + SECONDARY_PORT)
: `${HOST}:${SECONDARY_PORT}`
? req.headers['host'].replace(':' + PORT, ':' + SECONDARY_PORT)
: `${HOST}:${SECONDARY_PORT}`
);
const webConfigJSON = {
folderUri: folderUri,

View file

@ -1013,3 +1013,61 @@ export class IntervalCounter {
}
//#endregion
export type ValueCallback<T = any> = (value: T | Promise<T>) => void;
/**
* Creates a promise whose resolution or rejection can be controlled imperatively.
*/
export class DeferredPromise<T> {
private completeCallback!: ValueCallback<T>;
private errorCallback!: (err: any) => void;
private rejected = false;
private resolved = false;
public get isRejected() {
return this.rejected;
}
public get isResolved() {
return this.resolved;
}
public get isSettled() {
return this.rejected || this.resolved;
}
public p: Promise<T>;
constructor() {
this.p = new Promise<T>((c, e) => {
this.completeCallback = c;
this.errorCallback = e;
});
}
public complete(value: T) {
return new Promise<void>(resolve => {
this.completeCallback(value);
this.resolved = true;
resolve();
});
}
public error(err: any) {
return new Promise<void>(resolve => {
this.errorCallback(err);
this.rejected = true;
resolve();
});
}
public cancel() {
new Promise<void>(resolve => {
this.errorCallback(errors.canceled());
this.rejected = true;
resolve();
});
}
}

View file

@ -37,6 +37,11 @@ export class Client extends MessagePortClient implements IDisposable {
*/
export async function connect(window: BrowserWindow): Promise<MessagePortMain> {
// Assert healthy window to talk to
if (window.webContents.isDestroyed()) {
throw new Error('ipc.mp#connect: Cannot talk to window because it is closed or destroyed');
}
// Ask to create message channel inside the window
// and send over a UUID to correlate the response
const nonce = generateUuid();

View file

@ -780,4 +780,31 @@ suite('Async', () => {
assert.equal(ct1!.isCancellationRequested, true, 'should cancel a');
assert.equal(ct2!.isCancellationRequested, true, 'should cancel b');
});
suite('DeferredPromise', () => {
test('resolves', async () => {
const deferred = new async.DeferredPromise<number>();
assert.strictEqual(deferred.isResolved, false);
deferred.complete(42);
assert.strictEqual(await deferred.p, 42);
assert.strictEqual(deferred.isResolved, true);
});
test('rejects', async () => {
const deferred = new async.DeferredPromise<number>();
assert.strictEqual(deferred.isRejected, false);
const err = new Error('oh no!');
deferred.error(err);
assert.strictEqual(await deferred.p.catch(e => e), err);
assert.strictEqual(deferred.isRejected, true);
});
test('cancels', async () => {
const deferred = new async.DeferredPromise<number>();
assert.strictEqual(deferred.isRejected, false);
deferred.cancel();
assert.strictEqual((await deferred.p.catch(e => e)).name, 'Canceled');
assert.strictEqual(deferred.isRejected, true);
});
});
});

View file

@ -5,47 +5,10 @@
import { join } from 'vs/base/common/path';
import { URI } from 'vs/base/common/uri';
import { canceled } from 'vs/base/common/errors';
import { isWindows } from 'vs/base/common/platform';
export type ValueCallback<T = any> = (value: T | Promise<T>) => void;
export class DeferredPromise<T> {
private completeCallback!: ValueCallback<T>;
private errorCallback!: (err: any) => void;
public p: Promise<any>;
constructor() {
this.p = new Promise<any>((c, e) => {
this.completeCallback = c;
this.errorCallback = e;
});
}
public complete(value: T) {
return new Promise<void>(resolve => {
this.completeCallback(value);
resolve();
});
}
public error(err: any) {
return new Promise<void>(resolve => {
this.errorCallback(err);
resolve();
});
}
public cancel() {
new Promise<void>(resolve => {
this.errorCallback(canceled());
resolve();
});
}
}
export function toResource(this: any, path: string) {
if (isWindows) {
return URI.file(join('C:\\', btoa(this.test.fullTitle()), path));

View file

@ -14,6 +14,7 @@ import { browserCodeLoadingCacheStrategy } from 'vs/base/common/platform';
import { ISharedProcess, ISharedProcessConfiguration } from 'vs/platform/sharedProcess/node/sharedProcess';
import { Disposable } from 'vs/base/common/lifecycle';
import { connect as connectMessagePort } from 'vs/base/parts/ipc/electron-main/ipc.mp';
import { assertIsDefined } from 'vs/base/common/types';
export class SharedProcess extends Disposable implements ISharedProcess {
@ -49,8 +50,18 @@ export class SharedProcess extends Disposable implements ISharedProcess {
// workbench window will communicate directly
await this.whenReady();
// connect to the shared process window
const port = await this.connect();
// Check back if the requesting window meanwhile closed
// Since shared process is delayed on startup there is
// a chance that the window close before the shared process
// was ready for a connection.
if (e.sender.isDestroyed()) {
return port.close();
}
// send the port back to the requesting window
e.sender.postMessage('vscode:createSharedProcessMessageChannelResult', nonce, [port]);
});
}
@ -208,13 +219,9 @@ export class SharedProcess extends Disposable implements ISharedProcess {
// Wait for shared process being ready to accept connection
await this.whenIpcReady;
// Assert healthy shared process window
if (!this.window || this.window.webContents.isDestroyed()) {
throw new Error('Cannot connect to shared process window because the window is closed or destroyed');
}
// Connect and return message port
return connectMessagePort(this.window);
const window = assertIsDefined(this.window);
return connectMessagePort(window);
}
async toggle(): Promise<void> {

View file

@ -424,11 +424,12 @@ export class CodeWindow extends Disposable implements ICodeWindow {
this.dispose();
});
// Prevent loading of svgs
this._win.webContents.session.webRequest.onBeforeRequest(null!, (details, callback) => {
if (details.url.indexOf('.svg') > 0) {
const svgFileSchemes = new Set([Schemas.file, Schemas.vscodeFileResource, 'devtools']);
this._win.webContents.session.webRequest.onBeforeRequest((details, callback) => {
// Prevent loading of remote svgs
if (details.url.endsWith('.svg')) {
const uri = URI.parse(details.url);
if (uri && !uri.scheme.match(/file/i) && uri.path.endsWith('.svg')) {
if (uri && !svgFileSchemes.has(uri.scheme)) {
return callback({ cancel: true });
}
}
@ -436,12 +437,24 @@ export class CodeWindow extends Disposable implements ICodeWindow {
return callback({});
});
this._win.webContents.session.webRequest.onHeadersReceived(null!, (details, callback) => {
this._win.webContents.session.webRequest.onHeadersReceived((details, callback) => {
const responseHeaders = details.responseHeaders as Record<string, (string) | (string[])>;
const contentType = (responseHeaders['content-type'] || responseHeaders['Content-Type']);
if (contentType && Array.isArray(contentType) && contentType.some(x => x.toLowerCase().indexOf('image/svg') >= 0)) {
return callback({ cancel: true });
if (contentType && Array.isArray(contentType)) {
// https://github.com/microsoft/vscode/issues/97564
// ensure local svg files have Content-Type image/svg+xml
if (details.url.endsWith('.svg')) {
const uri = URI.parse(details.url);
if (uri && svgFileSchemes.has(uri.scheme)) {
responseHeaders['Content-Type'] = ['image/svg+xml'];
return callback({ cancel: false, responseHeaders });
}
}
if (contentType.some(x => x.toLowerCase().includes('image/svg'))) {
return callback({ cancel: true });
}
}
return callback({ cancel: false });

View file

@ -4,6 +4,8 @@
*--------------------------------------------------------------------------------------------*/
import { release } from 'os';
import * as fs from 'fs';
import { gracefulify } from 'graceful-fs';
import { isAbsolute, join } from 'vs/base/common/path';
import { raceTimeout } from 'vs/base/common/async';
import product from 'vs/platform/product/common/product';
@ -36,108 +38,119 @@ import { buildTelemetryMessage } from 'vs/platform/telemetry/node/telemetry';
import { FileService } from 'vs/platform/files/common/fileService';
import { IFileService } from 'vs/platform/files/common/files';
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { Disposable } from 'vs/base/common/lifecycle';
import { IProductService } from 'vs/platform/product/common/productService';
import { ExtensionManagementCLIService } from 'vs/platform/extensionManagement/common/extensionManagementCLIService';
import { URI } from 'vs/base/common/uri';
import { LocalizationsService } from 'vs/platform/localizations/node/localizations';
import { ILocalizationsService } from 'vs/platform/localizations/common/localizations';
import { setUnexpectedErrorHandler } from 'vs/base/common/errors';
import { toErrorMessage } from 'vs/base/common/errorMessage';
export class Main {
export class CliMain extends Disposable {
constructor(
@INativeEnvironmentService private readonly environmentService: INativeEnvironmentService,
@IExtensionManagementCLIService private readonly extensionManagementCLIService: IExtensionManagementCLIService
) { }
private argv: NativeParsedArgs
) {
super();
async run(argv: NativeParsedArgs): Promise<void> {
if (argv['install-source']) {
await this.setInstallSource(argv['install-source']);
return;
// Enable gracefulFs
gracefulify(fs);
this.registerListeners();
}
private registerListeners(): void {
// Dispose on exit
process.once('exit', () => this.dispose());
}
async run(): Promise<void> {
// Services
const [instantiationService, appenders] = await this.initServices();
return instantiationService.invokeFunction(async accessor => {
const logService = accessor.get(ILogService);
const environmentService = accessor.get(INativeEnvironmentService);
const extensionManagementCLIService = accessor.get(IExtensionManagementCLIService);
// Log info
logService.info('CLI main', this.argv);
// Error handler
this.registerErrorHandler(logService);
// Run based on argv
await this.doRun(environmentService, extensionManagementCLIService);
// Flush the remaining data in AI adapter (with 1s timeout)
return raceTimeout(combinedAppender(...appenders).flush(), 1000);
});
}
private async initServices(): Promise<[IInstantiationService, AppInsightsAppender[]]> {
const services = new ServiceCollection();
// Environment
const environmentService = new NativeEnvironmentService(this.argv);
services.set(IEnvironmentService, environmentService);
services.set(INativeEnvironmentService, environmentService);
// Init folders
await Promise.all([environmentService.appSettingsHome.fsPath, environmentService.extensionsPath].map(path => path ? mkdirp(path) : undefined));
// Log
const logLevel = getLogLevel(environmentService);
const loggers: ILogService[] = [];
loggers.push(new SpdLogService('cli', environmentService.logsPath, logLevel));
if (logLevel === LogLevel.Trace) {
loggers.push(new ConsoleLogService(logLevel));
}
if (argv['list-extensions']) {
await this.extensionManagementCLIService.listExtensions(!!argv['show-versions'], argv['category']);
} else if (argv['install-extension'] || argv['install-builtin-extension']) {
await this.extensionManagementCLIService.installExtensions(this.asExtensionIdOrVSIX(argv['install-extension'] || []), argv['install-builtin-extension'] || [], !!argv['do-not-sync'], !!argv['force']);
} else if (argv['uninstall-extension']) {
await this.extensionManagementCLIService.uninstallExtensions(this.asExtensionIdOrVSIX(argv['uninstall-extension']), !!argv['force']);
} else if (argv['locate-extension']) {
await this.extensionManagementCLIService.locateExtension(argv['locate-extension']);
} else if (argv['telemetry']) {
console.log(buildTelemetryMessage(this.environmentService.appRoot, this.environmentService.extensionsPath));
}
}
const logService = this._register(new MultiplexLogService(loggers));
services.set(ILogService, logService);
private asExtensionIdOrVSIX(inputs: string[]): (string | URI)[] {
return inputs.map(input => /\.vsix$/i.test(input) ? URI.file(isAbsolute(input) ? input : join(process.cwd(), input)) : input);
}
// Files
const fileService = this._register(new FileService(logService));
services.set(IFileService, fileService);
private setInstallSource(installSource: string): Promise<void> {
return writeFile(this.environmentService.installSourcePath, installSource.slice(0, 30));
}
const diskFileSystemProvider = this._register(new DiskFileSystemProvider(logService));
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
}
// Configuration
const configurationService = this._register(new ConfigurationService(environmentService.settingsResource, fileService));
services.set(IConfigurationService, configurationService);
const eventPrefix = 'monacoworkbench';
// Init config
await configurationService.initialize();
export async function main(argv: NativeParsedArgs): Promise<void> {
const services = new ServiceCollection();
const disposables = new DisposableStore();
// State
const stateService = new StateService(environmentService, logService);
services.set(IStateService, stateService);
const environmentService = new NativeEnvironmentService(argv);
const logLevel = getLogLevel(environmentService);
const loggers: ILogService[] = [];
loggers.push(new SpdLogService('cli', environmentService.logsPath, logLevel));
if (logLevel === LogLevel.Trace) {
loggers.push(new ConsoleLogService(logLevel));
}
const logService = new MultiplexLogService(loggers);
process.once('exit', () => logService.dispose());
logService.info('main', argv);
await Promise.all<void | undefined>([environmentService.appSettingsHome.fsPath, environmentService.extensionsPath]
.map((path): undefined | Promise<void> => path ? mkdirp(path) : undefined));
// Files
const fileService = new FileService(logService);
disposables.add(fileService);
services.set(IFileService, fileService);
const diskFileSystemProvider = new DiskFileSystemProvider(logService);
disposables.add(diskFileSystemProvider);
fileService.registerProvider(Schemas.file, diskFileSystemProvider);
const configurationService = new ConfigurationService(environmentService.settingsResource, fileService);
disposables.add(configurationService);
await configurationService.initialize();
services.set(IEnvironmentService, environmentService);
services.set(INativeEnvironmentService, environmentService);
services.set(ILogService, logService);
services.set(IConfigurationService, configurationService);
services.set(IStateService, new SyncDescriptor(StateService));
services.set(IProductService, { _serviceBrand: undefined, ...product });
const instantiationService: IInstantiationService = new InstantiationService(services);
return instantiationService.invokeFunction(async accessor => {
const stateService = accessor.get(IStateService);
// Product
services.set(IProductService, { _serviceBrand: undefined, ...product });
const { appRoot, extensionsPath, extensionDevelopmentLocationURI, isBuilt, installSourcePath } = environmentService;
const services = new ServiceCollection();
// Request
services.set(IRequestService, new SyncDescriptor(RequestService));
// Extensions
services.set(IExtensionManagementService, new SyncDescriptor(ExtensionManagementService));
services.set(IExtensionGalleryService, new SyncDescriptor(ExtensionGalleryService));
services.set(IExtensionManagementCLIService, new SyncDescriptor(ExtensionManagementCLIService));
// Localizations
services.set(ILocalizationsService, new SyncDescriptor(LocalizationsService));
// Telemetry
const appenders: AppInsightsAppender[] = [];
if (isBuilt && !extensionDevelopmentLocationURI && !environmentService.disableTelemetry && product.enableTelemetry) {
if (product.aiConfig && product.aiConfig.asimovKey) {
appenders.push(new AppInsightsAppender(eventPrefix, null, product.aiConfig.asimovKey));
appenders.push(new AppInsightsAppender('monacoworkbench', null, product.aiConfig.asimovKey));
}
const config: ITelemetryServiceConfig = {
@ -153,17 +166,70 @@ export async function main(argv: NativeParsedArgs): Promise<void> {
services.set(ITelemetryService, NullTelemetryService);
}
const instantiationService2 = instantiationService.createChild(services);
const main = instantiationService2.createInstance(Main);
return [new InstantiationService(services), appenders];
}
try {
await main.run(argv);
private registerErrorHandler(logService: ILogService): void {
// Flush the remaining data in AI adapter.
// If it does not complete in 1 second, exit the process.
await raceTimeout(combinedAppender(...appenders).flush(), 1000);
} finally {
disposables.dispose();
// Install handler for unexpected errors
setUnexpectedErrorHandler(error => {
const message = toErrorMessage(error, true);
if (!message) {
return;
}
logService.error(message);
});
}
private async doRun(environmentService: INativeEnvironmentService, extensionManagementCLIService: IExtensionManagementCLIService): Promise<void> {
// Install Source
if (this.argv['install-source']) {
return this.setInstallSource(environmentService, this.argv['install-source']);
}
});
// List Extensions
if (this.argv['list-extensions']) {
return extensionManagementCLIService.listExtensions(!!this.argv['show-versions'], this.argv['category']);
}
// Install Extension
else if (this.argv['install-extension'] || this.argv['install-builtin-extension']) {
return extensionManagementCLIService.installExtensions(this.asExtensionIdOrVSIX(this.argv['install-extension'] || []), this.argv['install-builtin-extension'] || [], !!this.argv['do-not-sync'], !!this.argv['force']);
}
// Uninstall Extension
else if (this.argv['uninstall-extension']) {
return extensionManagementCLIService.uninstallExtensions(this.asExtensionIdOrVSIX(this.argv['uninstall-extension']), !!this.argv['force']);
}
// Locate Extension
else if (this.argv['locate-extension']) {
return extensionManagementCLIService.locateExtension(this.argv['locate-extension']);
}
// Telemetry
else if (this.argv['telemetry']) {
console.log(buildTelemetryMessage(environmentService.appRoot, environmentService.extensionsPath));
}
}
private asExtensionIdOrVSIX(inputs: string[]): (string | URI)[] {
return inputs.map(input => /\.vsix$/i.test(input) ? URI.file(isAbsolute(input) ? input : join(process.cwd(), input)) : input);
}
private setInstallSource(environmentService: INativeEnvironmentService, installSource: string): Promise<void> {
return writeFile(environmentService.installSourcePath, installSource.slice(0, 30));
}
}
export async function main(argv: NativeParsedArgs): Promise<void> {
const cliMain = new CliMain(argv);
try {
await cliMain.run();
} finally {
cliMain.dispose();
}
}

View file

@ -150,7 +150,7 @@ export interface OnEnterRule {
/**
* This rule will only execute if the text above the this line matches this regular expression.
*/
oneLineAboveText?: RegExp;
previousLineText?: RegExp;
/**
* The action to execute.
*/

View file

@ -101,11 +101,11 @@ export class RichEditSupport {
return this._electricCharacter;
}
public onEnter(autoIndent: EditorAutoIndentStrategy, oneLineAboveText: string, beforeEnterText: string, afterEnterText: string): EnterAction | null {
public onEnter(autoIndent: EditorAutoIndentStrategy, previousLineText: string, beforeEnterText: string, afterEnterText: string): EnterAction | null {
if (!this._onEnterSupport) {
return null;
}
return this._onEnterSupport.onEnter(autoIndent, oneLineAboveText, beforeEnterText, afterEnterText);
return this._onEnterSupport.onEnter(autoIndent, previousLineText, beforeEnterText, afterEnterText);
}
private static _mergeConf(prev: LanguageConfiguration | null, current: LanguageConfiguration): LanguageConfiguration {
@ -700,17 +700,17 @@ export class LanguageConfigurationRegistryImpl {
afterEnterText = endScopedLineTokens.getLineContent().substr(range.endColumn - 1 - scopedLineTokens.firstCharOffset);
}
let oneLineAboveText = '';
let previousLineText = '';
if (range.startLineNumber > 1 && scopedLineTokens.firstCharOffset === 0) {
// This is not the first line and the entire line belongs to this mode
const oneLineAboveScopedLineTokens = this.getScopedLineTokens(model, range.startLineNumber - 1);
if (oneLineAboveScopedLineTokens.languageId === scopedLineTokens.languageId) {
// The line above ends with text belonging to the same mode
oneLineAboveText = oneLineAboveScopedLineTokens.getLineContent();
previousLineText = oneLineAboveScopedLineTokens.getLineContent();
}
}
const enterResult = richEditSupport.onEnter(autoIndent, oneLineAboveText, beforeEnterText, afterEnterText);
const enterResult = richEditSupport.onEnter(autoIndent, previousLineText, beforeEnterText, afterEnterText);
if (!enterResult) {
return null;
}

View file

@ -49,7 +49,7 @@ export class OnEnterSupport {
this._regExpRules = opts.onEnterRules || [];
}
public onEnter(autoIndent: EditorAutoIndentStrategy, oneLineAboveText: string, beforeEnterText: string, afterEnterText: string): EnterAction | null {
public onEnter(autoIndent: EditorAutoIndentStrategy, previousLineText: string, beforeEnterText: string, afterEnterText: string): EnterAction | null {
// (1): `regExpRules`
if (autoIndent >= EditorAutoIndentStrategy.Advanced) {
for (let i = 0, len = this._regExpRules.length; i < len; i++) {
@ -61,8 +61,8 @@ export class OnEnterSupport {
reg: rule.afterText,
text: afterEnterText
}, {
reg: rule.oneLineAboveText,
text: oneLineAboveText
reg: rule.previousLineText,
text: previousLineText
}].every((obj): boolean => {
return obj.reg ? obj.reg.test(obj.text) : true;
});

View file

@ -299,13 +299,13 @@ export class MoveLinesCommand implements ICommand {
}
}
private matchEnterRule(model: ITextModel, indentConverter: IIndentConverter, tabSize: number, line: number, oneLineAbove: number, oneLineAboveText?: string) {
private matchEnterRule(model: ITextModel, indentConverter: IIndentConverter, tabSize: number, line: number, oneLineAbove: number, previousLineText?: string) {
let validPrecedingLine = oneLineAbove;
while (validPrecedingLine >= 1) {
// ship empty lines as empty lines just inherit indentation
let lineContent;
if (validPrecedingLine === oneLineAbove && oneLineAboveText !== undefined) {
lineContent = oneLineAboveText;
if (validPrecedingLine === oneLineAbove && previousLineText !== undefined) {
lineContent = previousLineText;
} else {
lineContent = model.getLineContent(validPrecedingLine);
}

View file

@ -3,7 +3,6 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { first } from 'vs/base/common/async';
import { onUnexpectedExternalError } from 'vs/base/common/errors';
import { IPosition, Position } from 'vs/editor/common/core/position';
import { ITextModel } from 'vs/editor/common/model';
@ -20,19 +19,26 @@ export const Context = {
MultipleSignatures: new RawContextKey<boolean>('parameterHintsMultipleSignatures', false),
};
export function provideSignatureHelp(
export async function provideSignatureHelp(
model: ITextModel,
position: Position,
context: modes.SignatureHelpContext,
token: CancellationToken
): Promise<modes.SignatureHelpResult | null | undefined> {
): Promise<modes.SignatureHelpResult | undefined> {
const supports = modes.SignatureHelpProviderRegistry.ordered(model);
return first(supports.map(support => () => {
return Promise.resolve(support.provideSignatureHelp(model, position, token, context))
.catch<modes.SignatureHelpResult | undefined>(e => onUnexpectedExternalError(e));
}));
for (const support of supports) {
try {
const result = await support.provideSignatureHelp(model, position, token, context);
if (result) {
return result;
}
} catch (err) {
onUnexpectedExternalError(err);
}
}
return undefined;
}
CommandsRegistry.registerCommand('_executeSignatureHelpProvider', async (accessor, ...args: [URI, IPosition, string?]) => {

View file

@ -62,34 +62,34 @@ suite('SnippetController', () => {
editor.setPosition({ lineNumber: 4, column: 2 });
snippetController.insert(template);
assert.equal(editor.getModel()!.getLineContent(4), '\tfor (var index; index < array.length; index++) {');
assert.equal(editor.getModel()!.getLineContent(5), '\t\tvar element = array[index];');
assert.equal(editor.getModel()!.getLineContent(6), '\t\t');
assert.equal(editor.getModel()!.getLineContent(7), '\t}');
assert.strictEqual(editor.getModel()!.getLineContent(4), '\tfor (var index; index < array.length; index++) {');
assert.strictEqual(editor.getModel()!.getLineContent(5), '\t\tvar element = array[index];');
assert.strictEqual(editor.getModel()!.getLineContent(6), '\t\t');
assert.strictEqual(editor.getModel()!.getLineContent(7), '\t}');
editor.trigger('test', 'type', { text: 'i' });
assert.equal(editor.getModel()!.getLineContent(4), '\tfor (var i; i < array.length; i++) {');
assert.equal(editor.getModel()!.getLineContent(5), '\t\tvar element = array[i];');
assert.equal(editor.getModel()!.getLineContent(6), '\t\t');
assert.equal(editor.getModel()!.getLineContent(7), '\t}');
assert.strictEqual(editor.getModel()!.getLineContent(4), '\tfor (var i; i < array.length; i++) {');
assert.strictEqual(editor.getModel()!.getLineContent(5), '\t\tvar element = array[i];');
assert.strictEqual(editor.getModel()!.getLineContent(6), '\t\t');
assert.strictEqual(editor.getModel()!.getLineContent(7), '\t}');
snippetController.next();
editor.trigger('test', 'type', { text: 'arr' });
assert.equal(editor.getModel()!.getLineContent(4), '\tfor (var i; i < arr.length; i++) {');
assert.equal(editor.getModel()!.getLineContent(5), '\t\tvar element = arr[i];');
assert.equal(editor.getModel()!.getLineContent(6), '\t\t');
assert.equal(editor.getModel()!.getLineContent(7), '\t}');
assert.strictEqual(editor.getModel()!.getLineContent(4), '\tfor (var i; i < arr.length; i++) {');
assert.strictEqual(editor.getModel()!.getLineContent(5), '\t\tvar element = arr[i];');
assert.strictEqual(editor.getModel()!.getLineContent(6), '\t\t');
assert.strictEqual(editor.getModel()!.getLineContent(7), '\t}');
snippetController.prev();
editor.trigger('test', 'type', { text: 'j' });
assert.equal(editor.getModel()!.getLineContent(4), '\tfor (var j; j < arr.length; j++) {');
assert.equal(editor.getModel()!.getLineContent(5), '\t\tvar element = arr[j];');
assert.equal(editor.getModel()!.getLineContent(6), '\t\t');
assert.equal(editor.getModel()!.getLineContent(7), '\t}');
assert.strictEqual(editor.getModel()!.getLineContent(4), '\tfor (var j; j < arr.length; j++) {');
assert.strictEqual(editor.getModel()!.getLineContent(5), '\t\tvar element = arr[j];');
assert.strictEqual(editor.getModel()!.getLineContent(6), '\t\t');
assert.strictEqual(editor.getModel()!.getLineContent(7), '\t}');
snippetController.next();
snippetController.next();
assert.deepEqual(editor.getPosition(), new Position(6, 3));
assert.deepStrictEqual(editor.getPosition(), new Position(6, 3));
});
});
@ -98,13 +98,13 @@ suite('SnippetController', () => {
editor.setPosition({ lineNumber: 4, column: 2 });
snippetController.insert(template);
assert.equal(editor.getModel()!.getLineContent(4), '\tfor (var index; index < array.length; index++) {');
assert.equal(editor.getModel()!.getLineContent(5), '\t\tvar element = array[index];');
assert.equal(editor.getModel()!.getLineContent(6), '\t\t');
assert.equal(editor.getModel()!.getLineContent(7), '\t}');
assert.strictEqual(editor.getModel()!.getLineContent(4), '\tfor (var index; index < array.length; index++) {');
assert.strictEqual(editor.getModel()!.getLineContent(5), '\t\tvar element = array[index];');
assert.strictEqual(editor.getModel()!.getLineContent(6), '\t\t');
assert.strictEqual(editor.getModel()!.getLineContent(7), '\t}');
snippetController.cancel();
assert.deepEqual(editor.getPosition(), new Position(4, 16));
assert.deepStrictEqual(editor.getPosition(), new Position(4, 16));
});
});
@ -121,7 +121,7 @@ suite('SnippetController', () => {
// text: null
// }]);
// assert.equal(snippetController.isInSnippetMode(), false);
// assert.strictEqual(snippetController.isInSnippetMode(), false);
// });
// });
@ -138,7 +138,7 @@ suite('SnippetController', () => {
// text: null
// }]);
// assert.equal(snippetController.isInSnippetMode(), false);
// assert.strictEqual(snippetController.isInSnippetMode(), false);
// });
// });
@ -155,7 +155,7 @@ suite('SnippetController', () => {
// text: '\nHello'
// }]);
// assert.equal(snippetController.isInSnippetMode(), false);
// assert.strictEqual(snippetController.isInSnippetMode(), false);
// });
// });
@ -172,7 +172,7 @@ suite('SnippetController', () => {
// text: '\nHello'
// }]);
// assert.equal(snippetController.isInSnippetMode(), false);
// assert.strictEqual(snippetController.isInSnippetMode(), false);
// });
// });
@ -183,7 +183,7 @@ suite('SnippetController', () => {
editor.getModel()!.setValue('goodbye');
assert.equal(snippetController.isInSnippetMode(), false);
assert.strictEqual(snippetController.isInSnippetMode(), false);
});
});
@ -194,7 +194,7 @@ suite('SnippetController', () => {
editor.getModel()!.undo();
assert.equal(snippetController.isInSnippetMode(), false);
assert.strictEqual(snippetController.isInSnippetMode(), false);
});
});
@ -205,7 +205,7 @@ suite('SnippetController', () => {
editor.setPosition({ lineNumber: 1, column: 1 });
assert.equal(snippetController.isInSnippetMode(), false);
assert.strictEqual(snippetController.isInSnippetMode(), false);
});
});
@ -216,7 +216,7 @@ suite('SnippetController', () => {
editor.setModel(null);
assert.equal(snippetController.isInSnippetMode(), false);
assert.strictEqual(snippetController.isInSnippetMode(), false);
});
});
@ -227,7 +227,7 @@ suite('SnippetController', () => {
snippetController.dispose();
assert.equal(snippetController.isInSnippetMode(), false);
assert.strictEqual(snippetController.isInSnippetMode(), false);
});
});
@ -241,7 +241,7 @@ suite('SnippetController', () => {
codeSnippet = 'foo$0';
snippetController.insert(codeSnippet);
assert.equal(editor.getSelections()!.length, 2);
assert.strictEqual(editor.getSelections()!.length, 2);
const [first, second] = editor.getSelections()!;
assert.ok(first.equalsRange({ startLineNumber: 1, startColumn: 4, endLineNumber: 1, endColumn: 4 }), first.toString());
assert.ok(second.equalsRange({ startLineNumber: 2, startColumn: 4, endLineNumber: 2, endColumn: 4 }), second.toString());
@ -256,7 +256,7 @@ suite('SnippetController', () => {
codeSnippet = 'foo$0bar';
snippetController.insert(codeSnippet);
assert.equal(editor.getSelections()!.length, 2);
assert.strictEqual(editor.getSelections()!.length, 2);
const [first, second] = editor.getSelections()!;
assert.ok(first.equalsRange({ startLineNumber: 1, startColumn: 4, endLineNumber: 1, endColumn: 4 }), first.toString());
assert.ok(second.equalsRange({ startLineNumber: 2, startColumn: 4, endLineNumber: 2, endColumn: 4 }), second.toString());
@ -271,7 +271,7 @@ suite('SnippetController', () => {
codeSnippet = 'foo$0bar';
snippetController.insert(codeSnippet);
assert.equal(editor.getSelections()!.length, 2);
assert.strictEqual(editor.getSelections()!.length, 2);
const [first, second] = editor.getSelections()!;
assert.ok(first.equalsRange({ startLineNumber: 1, startColumn: 4, endLineNumber: 1, endColumn: 4 }), first.toString());
assert.ok(second.equalsRange({ startLineNumber: 1, startColumn: 14, endLineNumber: 1, endColumn: 14 }), second.toString());
@ -286,7 +286,7 @@ suite('SnippetController', () => {
codeSnippet = 'foo\n$0\nbar';
snippetController.insert(codeSnippet);
assert.equal(editor.getSelections()!.length, 2);
assert.strictEqual(editor.getSelections()!.length, 2);
const [first, second] = editor.getSelections()!;
assert.ok(first.equalsRange({ startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 1 }), first.toString());
assert.ok(second.equalsRange({ startLineNumber: 4, startColumn: 1, endLineNumber: 4, endColumn: 1 }), second.toString());
@ -301,7 +301,7 @@ suite('SnippetController', () => {
codeSnippet = 'foo\n$0\nbar';
snippetController.insert(codeSnippet);
assert.equal(editor.getSelections()!.length, 2);
assert.strictEqual(editor.getSelections()!.length, 2);
const [first, second] = editor.getSelections()!;
assert.ok(first.equalsRange({ startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 1 }), first.toString());
assert.ok(second.equalsRange({ startLineNumber: 4, startColumn: 1, endLineNumber: 4, endColumn: 1 }), second.toString());
@ -315,7 +315,7 @@ suite('SnippetController', () => {
codeSnippet = 'xo$0r';
snippetController.insert(codeSnippet, { overwriteBefore: 1 });
assert.equal(editor.getSelections()!.length, 1);
assert.strictEqual(editor.getSelections()!.length, 1);
assert.ok(editor.getSelection()!.equalsRange({ startLineNumber: 2, startColumn: 8, endColumn: 8, endLineNumber: 2 }));
});
});
@ -328,9 +328,9 @@ suite('SnippetController', () => {
codeSnippet = '{{% url_**$1** %}}';
controller.insert(codeSnippet, { overwriteBefore: 2 });
assert.equal(editor.getSelections()!.length, 1);
assert.strictEqual(editor.getSelections()!.length, 1);
assert.ok(editor.getSelection()!.equalsRange({ startLineNumber: 1, startColumn: 27, endLineNumber: 1, endColumn: 27 }));
assert.equal(editor.getModel()!.getValue(), 'example example {{% url_**** %}}');
assert.strictEqual(editor.getModel()!.getValue(), 'example example {{% url_**** %}}');
}, ['example example sc']);
@ -346,9 +346,9 @@ suite('SnippetController', () => {
controller.insert(codeSnippet, { overwriteBefore: 2 });
assert.equal(editor.getSelections()!.length, 1);
assert.strictEqual(editor.getSelections()!.length, 1);
assert.ok(editor.getSelection()!.equalsRange({ startLineNumber: 2, startColumn: 2, endLineNumber: 2, endColumn: 2 }), editor.getSelection()!.toString());
assert.equal(editor.getModel()!.getValue(), 'afterEach((done) => {\n\ttest\n});');
assert.strictEqual(editor.getModel()!.getValue(), 'afterEach((done) => {\n\ttest\n});');
}, ['af']);
@ -364,9 +364,9 @@ suite('SnippetController', () => {
controller.insert(codeSnippet, { overwriteBefore: 2 });
assert.equal(editor.getSelections()!.length, 1);
assert.strictEqual(editor.getSelections()!.length, 1);
assert.ok(editor.getSelection()!.equalsRange({ startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 1 }), editor.getSelection()!.toString());
assert.equal(editor.getModel()!.getValue(), 'afterEach((done) => {\n\ttest\n});');
assert.strictEqual(editor.getModel()!.getValue(), 'afterEach((done) => {\n\ttest\n});');
}, ['af']);
@ -380,8 +380,8 @@ suite('SnippetController', () => {
controller.insert(codeSnippet, { overwriteBefore: 8 });
assert.equal(editor.getModel()!.getValue(), 'after');
assert.equal(editor.getSelections()!.length, 1);
assert.strictEqual(editor.getModel()!.getValue(), 'after');
assert.strictEqual(editor.getSelections()!.length, 1);
assert.ok(editor.getSelection()!.equalsRange({ startLineNumber: 1, startColumn: 4, endLineNumber: 1, endColumn: 4 }), editor.getSelection()!.toString());
}, ['afterone']);
@ -404,7 +404,7 @@ suite('SnippetController', () => {
controller.insert(codeSnippet, { overwriteBefore: 2 });
assert.equal(editor.getSelections()!.length, 2);
assert.strictEqual(editor.getSelections()!.length, 2);
const [first, second] = editor.getSelections()!;
assert.ok(first.equalsRange({ startLineNumber: 5, startColumn: 3, endLineNumber: 5, endColumn: 3 }), first.toString());
@ -429,7 +429,7 @@ suite('SnippetController', () => {
controller.insert(codeSnippet, { overwriteBefore: 2 });
assert.equal(editor.getSelections()!.length, 1);
assert.strictEqual(editor.getSelections()!.length, 1);
const [first] = editor.getSelections()!;
assert.ok(first.equalsRange({ startLineNumber: 2, startColumn: 3, endLineNumber: 2, endColumn: 3 }), first.toString());
@ -465,7 +465,7 @@ suite('SnippetController', () => {
codeSnippet = '_foo';
controller.insert(codeSnippet, { overwriteBefore: 1 });
assert.equal(editor.getModel()!.getValue(), 'this._foo\nabc_foo');
assert.strictEqual(editor.getModel()!.getValue(), 'this._foo\nabc_foo');
}, ['this._', 'abc']);
@ -478,7 +478,7 @@ suite('SnippetController', () => {
codeSnippet = 'XX';
controller.insert(codeSnippet, { overwriteBefore: 1 });
assert.equal(editor.getModel()!.getValue(), 'this.XX\nabcXX');
assert.strictEqual(editor.getModel()!.getValue(), 'this.XX\nabcXX');
}, ['this._', 'abc']);
@ -492,7 +492,7 @@ suite('SnippetController', () => {
codeSnippet = '_foo';
controller.insert(codeSnippet, { overwriteBefore: 1 });
assert.equal(editor.getModel()!.getValue(), 'this._foo\nabc_foo\ndef_foo');
assert.strictEqual(editor.getModel()!.getValue(), 'this._foo\nabc_foo\ndef_foo');
}, ['this._', 'abc', 'def_']);
@ -506,7 +506,7 @@ suite('SnippetController', () => {
codeSnippet = '._foo';
controller.insert(codeSnippet, { overwriteBefore: 2 });
assert.equal(editor.getModel()!.getValue(), 'this._foo\nabc._foo\ndef._foo');
assert.strictEqual(editor.getModel()!.getValue(), 'this._foo\nabc._foo\ndef._foo');
}, ['this._', 'abc', 'def._']);
@ -520,7 +520,7 @@ suite('SnippetController', () => {
codeSnippet = '._foo';
controller.insert(codeSnippet, { overwriteBefore: 2 });
assert.equal(editor.getModel()!.getValue(), 'this._foo\nabc._foo\ndef._foo');
assert.strictEqual(editor.getModel()!.getValue(), 'this._foo\nabc._foo\ndef._foo');
}, ['this._', 'abc', 'def._']);
@ -534,7 +534,7 @@ suite('SnippetController', () => {
codeSnippet = '._foo';
controller.insert(codeSnippet, { overwriteBefore: 2 });
assert.equal(editor.getModel()!.getValue(), 'this._._foo\na._foo\ndef._._foo');
assert.strictEqual(editor.getModel()!.getValue(), 'this._._foo\na._foo\ndef._._foo');
}, ['this._', 'abc', 'def._']);
@ -550,7 +550,7 @@ suite('SnippetController', () => {
codeSnippet = 'document';
controller.insert(codeSnippet, { overwriteBefore: 3 });
assert.equal(editor.getModel()!.getValue(), '{document}\n{document && true}');
assert.strictEqual(editor.getModel()!.getValue(), '{document}\n{document && true}');
}, ['{foo}', '{foo && true}']);
});
@ -565,7 +565,7 @@ suite('SnippetController', () => {
codeSnippet = 'for (var ${1:i}=0; ${1:i}<len; ${1:i}++) { $0 }';
controller.insert(codeSnippet);
assert.equal(editor.getModel()!.getValue(), 'for (var i=0; i<len; i++) { }for (var i=0; i<len; i++) { }');
assert.strictEqual(editor.getModel()!.getValue(), 'for (var i=0; i<len; i++) { }for (var i=0; i<len; i++) { }');
}, ['for (var i=0; i<len; i++) { }']);
@ -578,7 +578,7 @@ suite('SnippetController', () => {
codeSnippet = 'for (let ${1:i}=0; ${1:i}<len; ${1:i}++) { $0 }';
controller.insert(codeSnippet);
assert.equal(editor.getModel()!.getValue(), 'for (let i=0; i<len; i++) { }for (var i=0; i<len; i++) { }');
assert.strictEqual(editor.getModel()!.getValue(), 'for (let i=0; i<len; i++) { }for (var i=0; i<len; i++) { }');
}, ['for (var i=0; i<len; i++) { }']);

View file

@ -21,13 +21,13 @@ suite('SnippetController2', function () {
const actual = s.shift()!;
assert.ok(selection.equalsSelection(actual), `actual=${selection.toString()} <> expected=${actual.toString()}`);
}
assert.equal(s.length, 0);
assert.strictEqual(s.length, 0);
}
function assertContextKeys(service: MockContextKeyService, inSnippet: boolean, hasPrev: boolean, hasNext: boolean): void {
assert.equal(SnippetController2.InSnippetMode.getValue(service), inSnippet, `inSnippetMode`);
assert.equal(SnippetController2.HasPrevTabstop.getValue(service), hasPrev, `HasPrevTabstop`);
assert.equal(SnippetController2.HasNextTabstop.getValue(service), hasNext, `HasNextTabstop`);
assert.strictEqual(SnippetController2.InSnippetMode.getValue(service), inSnippet, `inSnippetMode`);
assert.strictEqual(SnippetController2.HasPrevTabstop.getValue(service), hasPrev, `HasPrevTabstop`);
assert.strictEqual(SnippetController2.HasNextTabstop.getValue(service), hasNext, `HasNextTabstop`);
}
let editor: ICodeEditor;
@ -40,7 +40,7 @@ suite('SnippetController2', function () {
model = createTextModel('if\n $state\nfi');
editor = createTestCodeEditor({ model: model });
editor.setSelections([new Selection(1, 1, 1, 1), new Selection(2, 5, 2, 5)]);
assert.equal(model.getEOL(), '\n');
assert.strictEqual(model.getEOL(), '\n');
});
teardown(function () {
@ -78,9 +78,9 @@ suite('SnippetController2', function () {
assertContextKeys(contextKeys, false, false, false);
editor.trigger('test', 'type', { text: '\t' });
assert.equal(SnippetController2.InSnippetMode.getValue(contextKeys), false);
assert.equal(SnippetController2.HasNextTabstop.getValue(contextKeys), false);
assert.equal(SnippetController2.HasPrevTabstop.getValue(contextKeys), false);
assert.strictEqual(SnippetController2.InSnippetMode.getValue(contextKeys), false);
assert.strictEqual(SnippetController2.HasNextTabstop.getValue(contextKeys), false);
assert.strictEqual(SnippetController2.HasPrevTabstop.getValue(contextKeys), false);
});
test('insert, insert -> cursor moves out (left/right)', function () {
@ -111,7 +111,7 @@ suite('SnippetController2', function () {
const ctrl = new SnippetController2(editor, logService, contextKeys);
ctrl.insert('foo${1:bar}foo$0');
assert.equal(SnippetController2.InSnippetMode.getValue(contextKeys), true);
assert.strictEqual(SnippetController2.InSnippetMode.getValue(contextKeys), true);
assertSelections(editor, new Selection(1, 4, 1, 7), new Selection(2, 8, 2, 11));
// bad selection change

View file

@ -531,8 +531,8 @@ suite('SnippetParser', () => {
let snippet = new SnippetParser().parse('This ${1:is ${2:nested}}$0', true);
let [first, second] = snippet.placeholders;
assert.deepEqual(snippet.enclosingPlaceholders(first), []);
assert.deepEqual(snippet.enclosingPlaceholders(second), [first]);
assert.deepStrictEqual(snippet.enclosingPlaceholders(first), []);
assert.deepStrictEqual(snippet.enclosingPlaceholders(second), [first]);
});
test('TextmateSnippet#offset', () => {

View file

@ -23,14 +23,14 @@ suite('SnippetSession', function () {
const actual = s.shift()!;
assert.ok(selection.equalsSelection(actual), `actual=${selection.toString()} <> expected=${actual.toString()}`);
}
assert.equal(s.length, 0);
assert.strictEqual(s.length, 0);
}
setup(function () {
model = createTextModel('function foo() {\n console.log(a);\n}');
editor = createTestCodeEditor({ model: model }) as IActiveCodeEditor;
editor.setSelections([new Selection(1, 1, 1, 1), new Selection(2, 5, 2, 5)]);
assert.equal(model.getEOL(), '\n');
assert.strictEqual(model.getEOL(), '\n');
});
teardown(function () {
@ -43,7 +43,7 @@ suite('SnippetSession', function () {
function assertNormalized(position: IPosition, input: string, expected: string): void {
const snippet = new SnippetParser().parse(input);
SnippetSession.adjustWhitespace(model, position, snippet, true, true);
assert.equal(snippet.toTextmateString(), expected);
assert.strictEqual(snippet.toTextmateString(), expected);
}
assertNormalized(new Position(1, 1), 'foo', 'foo');
@ -73,7 +73,7 @@ suite('SnippetSession', function () {
test('text edits & selection', function () {
const session = new SnippetSession(editor, 'foo${1:bar}foo$0');
session.insert();
assert.equal(editor.getModel()!.getValue(), 'foobarfoofunction foo() {\n foobarfooconsole.log(a);\n}');
assert.strictEqual(editor.getModel()!.getValue(), 'foobarfoofunction foo() {\n foobarfooconsole.log(a);\n}');
assertSelections(editor, new Selection(1, 4, 1, 7), new Selection(2, 8, 2, 11));
session.next();
@ -86,7 +86,7 @@ suite('SnippetSession', function () {
editor.setSelections([new Selection(2, 5, 2, 5), new Selection(1, 1, 1, 1)]);
session.insert();
assert.equal(model.getValue(), 'barfunction foo() {\n barconsole.log(a);\n}');
assert.strictEqual(model.getValue(), 'barfunction foo() {\n barconsole.log(a);\n}');
assertSelections(editor, new Selection(2, 5, 2, 8), new Selection(1, 1, 1, 4));
});
@ -107,7 +107,7 @@ suite('SnippetSession', function () {
test('snippets, just text', function () {
const session = new SnippetSession(editor, 'foobar');
session.insert();
assert.equal(model.getValue(), 'foobarfunction foo() {\n foobarconsole.log(a);\n}');
assert.strictEqual(model.getValue(), 'foobarfunction foo() {\n foobarconsole.log(a);\n}');
assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(2, 11, 2, 11));
});
@ -116,7 +116,7 @@ suite('SnippetSession', function () {
const session = new SnippetSession(editor, 'foo\n\t${1:bar}\n$0');
session.insert();
assert.equal(editor.getModel()!.getValue(), 'foo\n bar\nfunction foo() {\n foo\n bar\n console.log(a);\n}');
assert.strictEqual(editor.getModel()!.getValue(), 'foo\n bar\nfunction foo() {\n foo\n bar\n console.log(a);\n}');
assertSelections(editor, new Selection(2, 5, 2, 8), new Selection(5, 9, 5, 12));
@ -129,7 +129,7 @@ suite('SnippetSession', function () {
editor.setSelection(new Selection(2, 5, 2, 5));
const session = new SnippetSession(editor, 'abc\n foo\n bar\n$0', { overwriteBefore: 0, overwriteAfter: 0, adjustWhitespace: false, clipboardText: undefined, overtypingCapturer: undefined });
session.insert();
assert.equal(editor.getModel()!.getValue(), 'function foo() {\n abc\n foo\n bar\nconsole.log(a);\n}');
assert.strictEqual(editor.getModel()!.getValue(), 'function foo() {\n abc\n foo\n bar\nconsole.log(a);\n}');
});
test('snippets, selections -> next/prev', () => {
@ -171,7 +171,7 @@ suite('SnippetSession', function () {
// go to final tabstop
session.next();
assert.equal(model.getValue(), 'fX_bar_function foo() {\n fX_bar_console.log(a);\n}');
assert.strictEqual(model.getValue(), 'fX_bar_function foo() {\n fX_bar_console.log(a);\n}');
assertSelections(editor, new Selection(1, 8, 1, 8), new Selection(2, 12, 2, 12));
});
@ -180,7 +180,7 @@ suite('SnippetSession', function () {
editor.setSelections([new Selection(1, 1, 1, 4), new Selection(1, 9, 1, 12)]);
new SnippetSession(editor, 'x$0').insert();
assert.equal(model.getValue(), 'x_bar_x');
assert.strictEqual(model.getValue(), 'x_bar_x');
assertSelections(editor, new Selection(1, 2, 1, 2), new Selection(1, 8, 1, 8));
});
@ -189,7 +189,7 @@ suite('SnippetSession', function () {
editor.setSelections([new Selection(1, 1, 1, 4), new Selection(1, 9, 1, 12)]);
new SnippetSession(editor, 'LONGER$0').insert();
assert.equal(model.getValue(), 'LONGER_bar_LONGER');
assert.strictEqual(model.getValue(), 'LONGER_bar_LONGER');
assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(1, 18, 1, 18));
});
@ -203,11 +203,11 @@ suite('SnippetSession', function () {
editor.trigger('test', 'type', { text: 'foo-' });
session.next();
assert.equal(model.getValue(), 'foo_foo-bar_foo');
assert.strictEqual(model.getValue(), 'foo_foo-bar_foo');
assertSelections(editor, new Selection(1, 12, 1, 12));
editor.trigger('test', 'type', { text: 'XXX' });
assert.equal(model.getValue(), 'foo_foo-barXXX_foo');
assert.strictEqual(model.getValue(), 'foo_foo-barXXX_foo');
session.prev();
assertSelections(editor, new Selection(1, 5, 1, 9));
session.next();
@ -242,7 +242,7 @@ suite('SnippetSession', function () {
editor.trigger('test', 'type', { text: '333' });
session.next();
assert.equal(model.getValue(), '111222333function foo() {\n 111222333console.log(a);\n}');
assert.strictEqual(model.getValue(), '111222333function foo() {\n 111222333console.log(a);\n}');
assertSelections(editor, new Selection(1, 10, 1, 10), new Selection(2, 14, 2, 14));
session.prev();
@ -269,22 +269,22 @@ suite('SnippetSession', function () {
editor.trigger('test', 'type', { text: '333' });
session.next();
assert.equal(session.isAtLastPlaceholder, true);
assert.strictEqual(session.isAtLastPlaceholder, true);
});
test('snippets, gracefully move over final tabstop', function () {
const session = new SnippetSession(editor, '${1}bar$0');
session.insert();
assert.equal(session.isAtLastPlaceholder, false);
assert.strictEqual(session.isAtLastPlaceholder, false);
assertSelections(editor, new Selection(1, 1, 1, 1), new Selection(2, 5, 2, 5));
session.next();
assert.equal(session.isAtLastPlaceholder, true);
assert.strictEqual(session.isAtLastPlaceholder, true);
assertSelections(editor, new Selection(1, 4, 1, 4), new Selection(2, 8, 2, 8));
session.next();
assert.equal(session.isAtLastPlaceholder, true);
assert.strictEqual(session.isAtLastPlaceholder, true);
assertSelections(editor, new Selection(1, 4, 1, 4), new Selection(2, 8, 2, 8));
});
@ -294,46 +294,46 @@ suite('SnippetSession', function () {
assertSelections(editor, new Selection(1, 5, 1, 7), new Selection(2, 9, 2, 11));
editor.trigger('test', 'type', { text: 'XXX' });
assert.equal(model.getValue(), 'log(XXX);function foo() {\n log(XXX);console.log(a);\n}');
assert.strictEqual(model.getValue(), 'log(XXX);function foo() {\n log(XXX);console.log(a);\n}');
session.next();
assert.equal(session.isAtLastPlaceholder, false);
assert.strictEqual(session.isAtLastPlaceholder, false);
// assertSelections(editor, new Selection(1, 7, 1, 7), new Selection(2, 11, 2, 11));
session.next();
assert.equal(session.isAtLastPlaceholder, true);
assert.strictEqual(session.isAtLastPlaceholder, true);
assertSelections(editor, new Selection(1, 10, 1, 10), new Selection(2, 14, 2, 14));
});
test('snippets, selections and snippet ranges', function () {
const session = new SnippetSession(editor, '${1:foo}farboo${2:bar}$0');
session.insert();
assert.equal(model.getValue(), 'foofarboobarfunction foo() {\n foofarboobarconsole.log(a);\n}');
assert.strictEqual(model.getValue(), 'foofarboobarfunction foo() {\n foofarboobarconsole.log(a);\n}');
assertSelections(editor, new Selection(1, 1, 1, 4), new Selection(2, 5, 2, 8));
assert.equal(session.isSelectionWithinPlaceholders(), true);
assert.strictEqual(session.isSelectionWithinPlaceholders(), true);
editor.setSelections([new Selection(1, 1, 1, 1)]);
assert.equal(session.isSelectionWithinPlaceholders(), false);
assert.strictEqual(session.isSelectionWithinPlaceholders(), false);
editor.setSelections([new Selection(1, 6, 1, 6), new Selection(2, 10, 2, 10)]);
assert.equal(session.isSelectionWithinPlaceholders(), false); // in snippet, outside placeholder
assert.strictEqual(session.isSelectionWithinPlaceholders(), false); // in snippet, outside placeholder
editor.setSelections([new Selection(1, 6, 1, 6), new Selection(2, 10, 2, 10), new Selection(1, 1, 1, 1)]);
assert.equal(session.isSelectionWithinPlaceholders(), false); // in snippet, outside placeholder
assert.strictEqual(session.isSelectionWithinPlaceholders(), false); // in snippet, outside placeholder
editor.setSelections([new Selection(1, 6, 1, 6), new Selection(2, 10, 2, 10), new Selection(2, 20, 2, 21)]);
assert.equal(session.isSelectionWithinPlaceholders(), false);
assert.strictEqual(session.isSelectionWithinPlaceholders(), false);
// reset selection to placeholder
session.next();
assert.equal(session.isSelectionWithinPlaceholders(), true);
assert.strictEqual(session.isSelectionWithinPlaceholders(), true);
assertSelections(editor, new Selection(1, 10, 1, 13), new Selection(2, 14, 2, 17));
// reset selection to placeholder
session.next();
assert.equal(session.isSelectionWithinPlaceholders(), true);
assert.equal(session.isAtLastPlaceholder, true);
assert.strictEqual(session.isSelectionWithinPlaceholders(), true);
assert.strictEqual(session.isAtLastPlaceholder, true);
assertSelections(editor, new Selection(1, 13, 1, 13), new Selection(2, 17, 2, 17));
});
@ -344,20 +344,20 @@ suite('SnippetSession', function () {
const first = new SnippetSession(editor, 'foo${2:bar}foo$0');
first.insert();
assert.equal(model.getValue(), 'foobarfoo');
assert.strictEqual(model.getValue(), 'foobarfoo');
assertSelections(editor, new Selection(1, 4, 1, 7));
const second = new SnippetSession(editor, 'ba${1:zzzz}$0');
second.insert();
assert.equal(model.getValue(), 'foobazzzzfoo');
assert.strictEqual(model.getValue(), 'foobazzzzfoo');
assertSelections(editor, new Selection(1, 6, 1, 10));
second.next();
assert.equal(second.isAtLastPlaceholder, true);
assert.strictEqual(second.isAtLastPlaceholder, true);
assertSelections(editor, new Selection(1, 10, 1, 10));
first.next();
assert.equal(first.isAtLastPlaceholder, true);
assert.strictEqual(first.isAtLastPlaceholder, true);
assertSelections(editor, new Selection(1, 13, 1, 13));
});
@ -365,11 +365,11 @@ suite('SnippetSession', function () {
const session = new SnippetSession(editor, 'farboo$0');
session.insert();
assert.equal(session.isAtLastPlaceholder, true);
assert.equal(session.isSelectionWithinPlaceholders(), false);
assert.strictEqual(session.isAtLastPlaceholder, true);
assert.strictEqual(session.isSelectionWithinPlaceholders(), false);
editor.trigger('test', 'type', { text: 'XXX' });
assert.equal(session.isSelectionWithinPlaceholders(), false);
assert.strictEqual(session.isSelectionWithinPlaceholders(), false);
});
test('snippets, typing at beginning', function () {
@ -379,12 +379,12 @@ suite('SnippetSession', function () {
session.insert();
editor.setSelection(new Selection(1, 2, 1, 2));
assert.equal(session.isSelectionWithinPlaceholders(), false);
assert.equal(session.isAtLastPlaceholder, true);
assert.strictEqual(session.isSelectionWithinPlaceholders(), false);
assert.strictEqual(session.isAtLastPlaceholder, true);
editor.trigger('test', 'type', { text: 'XXX' });
assert.equal(model.getLineContent(1), 'fXXXfarboounction foo() {');
assert.equal(session.isSelectionWithinPlaceholders(), false);
assert.strictEqual(model.getLineContent(1), 'fXXXfarboounction foo() {');
assert.strictEqual(session.isSelectionWithinPlaceholders(), false);
session.next();
assertSelections(editor, new Selection(1, 11, 1, 11));
@ -412,7 +412,7 @@ suite('SnippetSession', function () {
const session = new SnippetSession(editor, '@line=$TM_LINE_NUMBER$0');
session.insert();
assert.equal(model.getValue(), '@line=1function foo() {\n @line=2console.log(a);\n}');
assert.strictEqual(model.getValue(), '@line=1function foo() {\n @line=2console.log(a);\n}');
assertSelections(editor, new Selection(1, 8, 1, 8), new Selection(2, 12, 2, 12));
});
@ -428,10 +428,10 @@ suite('SnippetSession', function () {
session.next();
assertSelections(editor, new Selection(1, 22, 1, 22));
assert.equal(session.isAtLastPlaceholder, false);
assert.strictEqual(session.isAtLastPlaceholder, false);
session.next();
assert.equal(session.isAtLastPlaceholder, true);
assert.strictEqual(session.isAtLastPlaceholder, true);
assertSelections(editor, new Selection(1, 23, 1, 23));
session.prev();
@ -456,8 +456,8 @@ suite('SnippetSession', function () {
editor.trigger('test', 'type', { text: 'foo' });
session.next();
assert.equal(model.getValue(), 'bar');
assert.equal(session.isAtLastPlaceholder, true);
assert.strictEqual(model.getValue(), 'bar');
assert.strictEqual(session.isAtLastPlaceholder, true);
assertSelections(editor, new Selection(1, 4, 1, 4));
});
@ -471,8 +471,8 @@ suite('SnippetSession', function () {
editor.trigger('test', 'type', { text: 'foo' });
session.next();
assert.equal(model.getValue(), 'foo baz bar');
assert.equal(session.isAtLastPlaceholder, true);
assert.strictEqual(model.getValue(), 'foo baz bar');
assert.strictEqual(session.isAtLastPlaceholder, true);
assertSelections(editor, new Selection(1, 12, 1, 12));
});
@ -493,8 +493,8 @@ suite('SnippetSession', function () {
assertSelections(editor, new Selection(1, 16, 1, 16));
session.next();
assert.equal(model.getValue(), 'clk : std_logic;\n');
assert.equal(session.isAtLastPlaceholder, true);
assert.strictEqual(model.getValue(), 'clk : std_logic;\n');
assert.strictEqual(session.isAtLastPlaceholder, true);
assertSelections(editor, new Selection(2, 1, 2, 1));
});
@ -532,8 +532,8 @@ suite('SnippetSession', function () {
editor.trigger('test', 'type', { text: 'string' });
session.next();
assert.equal(model.getValue(), expected);
assert.equal(session.isAtLastPlaceholder, true);
assert.strictEqual(model.getValue(), expected);
assert.strictEqual(session.isAtLastPlaceholder, true);
assertSelections(editor, new Selection(4, 2, 4, 2));
});
@ -556,8 +556,8 @@ suite('SnippetSession', function () {
editor.trigger('test', 'type', { text: ' := \'1\'' });
session.next();
assert.equal(model.getValue(), 'clk : std_logic := \'1\';\n');
assert.equal(session.isAtLastPlaceholder, true);
assert.strictEqual(model.getValue(), 'clk : std_logic := \'1\';\n');
assert.strictEqual(session.isAtLastPlaceholder, true);
assertSelections(editor, new Selection(2, 1, 2, 1));
});
@ -570,13 +570,13 @@ suite('SnippetSession', function () {
assertSelections(editor, new Selection(1, 1, 1, 2), new Selection(1, 5, 1, 6));
session.next();
assert.equal(model.getValue(), '{fff}');
assert.strictEqual(model.getValue(), '{fff}');
assertSelections(editor, new Selection(1, 2, 1, 5));
editor.trigger('test', 'type', { text: 'ggg' });
session.next();
assert.equal(model.getValue(), '{ggg}');
assert.equal(session.isAtLastPlaceholder, true);
assert.strictEqual(model.getValue(), '{ggg}');
assert.strictEqual(session.isAtLastPlaceholder, true);
assertSelections(editor, new Selection(1, 6, 1, 6));
});
@ -584,7 +584,7 @@ suite('SnippetSession', function () {
editor.getModel().setValue('');
const session = new SnippetSession(editor, '${1:{}${2:fff}${1/[\\{]/}/}$0');
session.insert();
assert.equal(editor.getModel().getValue(), '{fff{');
assert.strictEqual(editor.getModel().getValue(), '{fff{');
assertSelections(editor, new Selection(1, 1, 1, 2), new Selection(1, 5, 1, 6));
session.next();
@ -599,25 +599,25 @@ suite('SnippetSession', function () {
editor.trigger('test', 'type', { text: '1' });
editor.trigger('test', 'type', { text: '\n' });
assert.equal(editor.getModel()!.getValue(), 'test 1\n');
assert.strictEqual(editor.getModel()!.getValue(), 'test 1\n');
session.merge('test ${1:replaceme}');
editor.trigger('test', 'type', { text: '2' });
editor.trigger('test', 'type', { text: '\n' });
assert.equal(editor.getModel()!.getValue(), 'test 1\ntest 2\n');
assert.strictEqual(editor.getModel()!.getValue(), 'test 1\ntest 2\n');
session.merge('test ${1:replaceme}');
editor.trigger('test', 'type', { text: '3' });
editor.trigger('test', 'type', { text: '\n' });
assert.equal(editor.getModel()!.getValue(), 'test 1\ntest 2\ntest 3\n');
assert.strictEqual(editor.getModel()!.getValue(), 'test 1\ntest 2\ntest 3\n');
session.merge('test ${1:replaceme}');
editor.trigger('test', 'type', { text: '4' });
editor.trigger('test', 'type', { text: '\n' });
assert.equal(editor.getModel()!.getValue(), 'test 1\ntest 2\ntest 3\ntest 4\n');
assert.strictEqual(editor.getModel()!.getValue(), 'test 1\ntest 2\ntest 3\ntest 4\n');
});
test('Snippet variable text isn\'t whitespace normalised, #31124', function () {
@ -642,7 +642,7 @@ suite('SnippetSession', function () {
'end'
].join('\n');
assert.equal(editor.getModel()!.getValue(), expected);
assert.strictEqual(editor.getModel()!.getValue(), expected);
editor.getModel()!.setValue([
'start',
@ -665,7 +665,7 @@ suite('SnippetSession', function () {
'end'
].join('\n');
assert.equal(editor.getModel()!.getValue(), expected);
assert.strictEqual(editor.getModel()!.getValue(), expected);
});
test('Selecting text from left to right, and choosing item messes up code, #31199', function () {
@ -680,7 +680,7 @@ suite('SnippetSession', function () {
editor.setSelections([new Selection(1, 9, 1, 12)]);
new SnippetSession(editor, 'far', { overwriteBefore: 3, overwriteAfter: 0, adjustWhitespace: true, clipboardText: undefined, overtypingCapturer: undefined }).insert();
assert.equal(model.getValue(), 'console.far');
assert.strictEqual(model.getValue(), 'console.far');
});
test('Tabs don\'t get replaced with spaces in snippet transformations #103818', function () {

View file

@ -50,9 +50,9 @@ suite('Snippet Variables Resolver', function () {
const variable = <Variable>snippet.children[0];
variable.resolve(resolver);
if (variable.children.length === 0) {
assert.equal(undefined, expected);
assert.strictEqual(undefined, expected);
} else {
assert.equal(variable.toString(), expected);
assert.strictEqual(variable.toString(), expected);
}
}
@ -129,17 +129,17 @@ suite('Snippet Variables Resolver', function () {
test('TextmateSnippet, resolve variable', function () {
const snippet = new SnippetParser().parse('"$TM_CURRENT_WORD"', true);
assert.equal(snippet.toString(), '""');
assert.strictEqual(snippet.toString(), '""');
snippet.resolveVariables(resolver);
assert.equal(snippet.toString(), '"this"');
assert.strictEqual(snippet.toString(), '"this"');
});
test('TextmateSnippet, resolve variable with default', function () {
const snippet = new SnippetParser().parse('"${TM_CURRENT_WORD:foo}"', true);
assert.equal(snippet.toString(), '"foo"');
assert.strictEqual(snippet.toString(), '"foo"');
snippet.resolveVariables(resolver);
assert.equal(snippet.toString(), '"this"');
assert.strictEqual(snippet.toString(), '"this"');
});
test('More useful environment variables for snippets, #32737', function () {
@ -171,14 +171,14 @@ suite('Snippet Variables Resolver', function () {
.resolveVariables({ resolve(variable) { return varValue || variable.name; } });
const actual = snippet.toString();
assert.equal(actual, expected);
assert.strictEqual(actual, expected);
}
test('Variable Snippet Transform', function () {
const snippet = new SnippetParser().parse('name=${TM_FILENAME/(.*)\\..+$/$1/}', true);
snippet.resolveVariables(resolver);
assert.equal(snippet.toString(), 'name=text');
assert.strictEqual(snippet.toString(), 'name=text');
assertVariableResolve2('${ThisIsAVar/([A-Z]).*(Var)/$2/}', 'Var');
assertVariableResolve2('${ThisIsAVar/([A-Z]).*(Var)/$2-${1:/downcase}/}', 'Var-t');
@ -269,7 +269,7 @@ suite('Snippet Variables Resolver', function () {
const snippet = new SnippetParser().parse(`$${varName}`);
const variable = <Variable>snippet.children[0];
assert.equal(variable.resolve(resolver), true, `${varName} failed to resolve`);
assert.strictEqual(variable.resolve(resolver), true, `${varName} failed to resolve`);
}
test('Add time variables for snippets #41631, #43140', function () {
@ -294,10 +294,10 @@ suite('Snippet Variables Resolver', function () {
const snippet = new SnippetParser().parse('${TM_LINE_NUMBER/(10)/${1:?It is:It is not}/} line 10', true);
snippet.resolveVariables({ resolve() { return '10'; } });
assert.equal(snippet.toString(), 'It is line 10');
assert.strictEqual(snippet.toString(), 'It is line 10');
snippet.resolveVariables({ resolve() { return '11'; } });
assert.equal(snippet.toString(), 'It is not line 10');
assert.strictEqual(snippet.toString(), 'It is not line 10');
});
test('Add workspace name and folder variables for snippets #68261', function () {

View file

@ -18,7 +18,7 @@ export const javascriptOnEnterRules = [
}, {
// e.g. * ...|
beforeText: /^(\t|[ ])*[ ]\*([ ]([^\*]|\*(?!\/))*)?$/,
oneLineAboveText: /(?=^(\s*(\/\*\*|\*)).*)(?=(?!(\s*\*\/)))/,
previousLineText: /(?=^(\s*(\/\*\*|\*)).*)(?=(?!(\s*\*\/)))/,
action: { indentAction: IndentAction.None, appendText: '* ' }
}, {
// e.g. */|

View file

@ -51,8 +51,8 @@ suite('OnEnter', () => {
let support = new OnEnterSupport({
onEnterRules: javascriptOnEnterRules
});
let testIndentAction = (oneLineAboveText: string, beforeText: string, afterText: string, expectedIndentAction: IndentAction | null, expectedAppendText: string | null, removeText: number = 0) => {
let actual = support.onEnter(EditorAutoIndentStrategy.Advanced, oneLineAboveText, beforeText, afterText);
let testIndentAction = (previousLineText: string, beforeText: string, afterText: string, expectedIndentAction: IndentAction | null, expectedAppendText: string | null, removeText: number = 0) => {
let actual = support.onEnter(EditorAutoIndentStrategy.Advanced, previousLineText, beforeText, afterText);
if (expectedIndentAction === null) {
assert.strictEqual(actual, null, 'isNull:' + beforeText);
} else {

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

@ -5417,7 +5417,7 @@ declare namespace monaco.languages {
/**
* This rule will only execute if the text above the this line matches this regular expression.
*/
oneLineAboveText?: RegExp;
previousLineText?: RegExp;
/**
* The action to execute.
*/

View file

@ -42,12 +42,16 @@ export class ExtensionIdentifierWithVersion implements IExtensionIdentifierWithV
}
}
export function getExtensionId(publisher: string, name: string): string {
return `${publisher}.${name}`;
}
export function adoptToGalleryExtensionId(id: string): string {
return id.toLocaleLowerCase();
}
export function getGalleryExtensionId(publisher: string, name: string): string {
return `${publisher.toLocaleLowerCase()}.${name.toLocaleLowerCase()}`;
return adoptToGalleryExtensionId(getExtensionId(publisher, name));
}
export function groupByExtension<T>(extensions: T[], getExtensionIdentifier: (t: T) => IExtensionIdentifier): T[][] {

View file

@ -826,7 +826,7 @@ function workbenchTreeDataPreamble<T, TFilterData, TOptions extends IAbstractTre
};
const accessibilityOn = accessibilityService.isScreenReaderOptimized();
const keyboardNavigation = accessibilityOn ? 'simple' : configurationService.getValue<string>(keyboardNavigationSettingKey);
const keyboardNavigation = options.simpleKeyboardNavigation || accessibilityOn ? 'simple' : configurationService.getValue<string>(keyboardNavigationSettingKey);
const horizontalScrolling = options.horizontalScrolling !== undefined ? options.horizontalScrolling : configurationService.getValue<boolean>(horizontalScrollingKey);
const [workbenchListOptions, disposable] = toWorkbenchListOptions(options, configurationService, keybindingService);
const additionalScrollHeight = options.additionalScrollHeight;

View file

@ -42,6 +42,24 @@ export interface ITunnelProvider {
forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<RemoteTunnel | undefined> | undefined;
}
export interface ITunnel {
remoteAddress: { port: number, host: string };
/**
* The complete local address(ex. localhost:1234)
*/
localAddress: string;
public?: boolean;
/**
* Implementers of Tunnel should fire onDidDispose when dispose is called.
*/
onDidDispose: Event<void>;
dispose(): Promise<void> | void;
}
export interface ITunnelService {
readonly _serviceBrand: undefined;
@ -177,6 +195,7 @@ export abstract class AbstractTunnelService implements ITunnelService {
const resolvedTunnel = this.retainOrCreateTunnel(addressProvider, remoteHost, remotePort, localPort, elevateIfNeeded, isPublic);
if (!resolvedTunnel) {
this.logService.trace(`Tunnel was not created.`);
return resolvedTunnel;
}

View file

@ -5,6 +5,7 @@
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { adoptToGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IStorageService, IStorageValueChangeEvent, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
@ -32,7 +33,7 @@ export class ExtensionsStorageSyncService extends Disposable implements IExtensi
declare readonly _serviceBrand: undefined;
private static toKey(extension: IExtensionIdWithVersion): string {
return `extensionKeys/${extension.id}@${extension.version}`;
return `extensionKeys/${adoptToGalleryExtensionId(extension.id)}@${extension.version}`;
}
private static fromKey(key: string): IExtensionIdWithVersion | undefined {

View file

@ -11,7 +11,7 @@ import { Event } from 'vs/base/common/event';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IExtensionManagementService, IExtensionGalleryService, IGlobalExtensionEnablementService, ILocalExtension } from 'vs/platform/extensionManagement/common/extensionManagement';
import { ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { areSameExtensions, getExtensionId, getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { IFileService } from 'vs/platform/files/common/files';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { merge } from 'vs/platform/userDataSync/common/extensionsMerge';
@ -73,6 +73,15 @@ async function parseAndMigrateExtensions(syncData: ISyncData, extensionManagemen
return extensions;
}
function getExtensionStorageState(publisher: string, name: string, storageService: IStorageService): IStringDictionary<any> {
const extensionStorageValue = storageService.get(getExtensionId(publisher, name) /* use the same id used in extension host */, StorageScope.GLOBAL) || '{}';
return JSON.parse(extensionStorageValue);
}
function storeExtensionStorageState(publisher: string, name: string, extensionState: IStringDictionary<any>, storageService: IStorageService): void {
storageService.store(getExtensionId(publisher, name) /* use the same id used in extension host */, JSON.stringify(extensionState), StorageScope.GLOBAL, StorageTarget.MACHINE);
}
export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
private static readonly EXTENSIONS_DATA_URI = URI.from({ scheme: USER_DATA_SYNC_SCHEME, authority: 'extensions', path: `/extensions.json` });
@ -99,7 +108,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
@IUserDataSyncBackupStoreService userDataSyncBackupStoreService: IUserDataSyncBackupStoreService,
@IExtensionManagementService private readonly extensionManagementService: IExtensionManagementService,
@IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService,
@IIgnoredExtensionsManagementService private readonly extensionSyncManagementService: IIgnoredExtensionsManagementService,
@IIgnoredExtensionsManagementService private readonly ignoredExtensionsManagementService: IIgnoredExtensionsManagementService,
@IUserDataSyncLogService logService: IUserDataSyncLogService,
@IExtensionGalleryService private readonly extensionGalleryService: IExtensionGalleryService,
@IConfigurationService configurationService: IConfigurationService,
@ -125,7 +134,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
const installedExtensions = await this.extensionManagementService.getInstalled();
const localExtensions = this.getLocalExtensions(installedExtensions);
const ignoredExtensions = this.extensionSyncManagementService.getIgnoredExtensions(installedExtensions);
const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions);
if (remoteExtensions) {
this.logService.trace(`${this.syncResourceLogLabel}: Merging remote extensions with local extensions...`);
@ -209,7 +218,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
private async acceptLocal(resourcePreview: IExtensionResourcePreview): Promise<IExtensionResourceMergeResult> {
const installedExtensions = await this.extensionManagementService.getInstalled();
const ignoredExtensions = this.extensionSyncManagementService.getIgnoredExtensions(installedExtensions);
const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions);
const mergeResult = merge(resourcePreview.localExtensions, null, null, resourcePreview.skippedExtensions, ignoredExtensions);
const { added, removed, updated, remote } = mergeResult;
return {
@ -225,7 +234,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
private async acceptRemote(resourcePreview: IExtensionResourcePreview): Promise<IExtensionResourceMergeResult> {
const installedExtensions = await this.extensionManagementService.getInstalled();
const ignoredExtensions = this.extensionSyncManagementService.getIgnoredExtensions(installedExtensions);
const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions);
const remoteExtensions = resourcePreview.remoteContent ? JSON.parse(resourcePreview.remoteContent) : null;
if (remoteExtensions !== null) {
const mergeResult = merge(resourcePreview.localExtensions, remoteExtensions, resourcePreview.localExtensions, [], ignoredExtensions);
@ -285,7 +294,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
async resolveContent(uri: URI): Promise<string | null> {
if (this.extUri.isEqual(uri, ExtensionsSynchroniser.EXTENSIONS_DATA_URI)) {
const installedExtensions = await this.extensionManagementService.getInstalled();
const ignoredExtensions = this.extensionSyncManagementService.getIgnoredExtensions(installedExtensions);
const ignoredExtensions = this.ignoredExtensionsManagementService.getIgnoredExtensions(installedExtensions);
const localExtensions = this.getLocalExtensions(installedExtensions).filter(e => !ignoredExtensions.some(id => areSameExtensions({ id }, e.identifier)));
return this.format(localExtensions);
}
@ -363,7 +372,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
// Builtin Extension Sync: Enablement & State
if (installedExtension && installedExtension.isBuiltin) {
if (e.state && installedExtension.manifest.version === e.version) {
this.updateExtensionState(e.state, e.identifier.id, installedExtension.manifest.version);
this.updateExtensionState(e.state, installedExtension.manifest.publisher, installedExtension.manifest.name, installedExtension.manifest.version);
}
if (e.disabled) {
this.logService.trace(`${this.syncResourceLogLabel}: Disabling extension...`, e.identifier.id);
@ -382,14 +391,16 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
const extension = await this.extensionGalleryService.getCompatibleExtension(e.identifier);
/* Update extension state only if
* extension is installed and version is same as synced version or
* extension is not installed and installable
* extension is installed and version is same as synced version or
* extension is not installed and installable
*/
if (e.state &&
(installedExtension ? installedExtension.manifest.version === e.version /* Installed and has same version */
: !!extension /* Installable */)
) {
this.updateExtensionState(e.state, e.identifier.id, installedExtension?.manifest.version);
const publisher = installedExtension ? installedExtension.manifest.publisher : extension!.publisher;
const name = installedExtension ? installedExtension.manifest.name : extension!.name;
this.updateExtensionState(e.state, publisher, name, installedExtension?.manifest.version);
}
if (extension) {
@ -436,15 +447,15 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
return newSkippedExtensions;
}
private updateExtensionState(state: IStringDictionary<any>, id: string, version?: string): void {
const extensionState = JSON.parse(this.storageService.get(id, StorageScope.GLOBAL) || '{}');
const keys = version ? this.extensionsStorageSyncService.getKeysForSync({ id, version }) : undefined;
private updateExtensionState(state: IStringDictionary<any>, publisher: string, name: string, version: string | undefined): void {
const extensionState = getExtensionStorageState(publisher, name, this.storageService);
const keys = version ? this.extensionsStorageSyncService.getKeysForSync({ id: getGalleryExtensionId(publisher, name), version }) : undefined;
if (keys) {
keys.forEach(key => extensionState[key] = state[key]);
} else {
forEach(state, ({ key, value }) => extensionState[key] = value);
}
this.storageService.store(id, JSON.stringify(extensionState), StorageScope.GLOBAL, StorageTarget.MACHINE);
storeExtensionStorageState(publisher, name, extensionState, this.storageService);
}
private parseExtensions(syncData: ISyncData): ISyncExtension[] {
@ -465,8 +476,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
try {
const keys = this.extensionsStorageSyncService.getKeysForSync({ id: identifier.id, version: manifest.version });
if (keys) {
const extensionStorageValue = this.storageService.get(identifier.id, StorageScope.GLOBAL) || '{}';
const extensionStorageState = JSON.parse(extensionStorageValue);
const extensionStorageState = getExtensionStorageState(manifest.publisher, manifest.name, this.storageService);
syncExntesion.state = Object.keys(extensionStorageState).reduce((state: IStringDictionary<any>, key) => {
if (keys.includes(key)) {
state[key] = extensionStorageState[key];
@ -490,6 +500,7 @@ export class ExtensionsInitializer extends AbstractInitializer {
@IExtensionGalleryService private readonly galleryService: IExtensionGalleryService,
@IGlobalExtensionEnablementService private readonly extensionEnablementService: IGlobalExtensionEnablementService,
@IStorageService private readonly storageService: IStorageService,
@IIgnoredExtensionsManagementService private readonly ignoredExtensionsManagementService: IIgnoredExtensionsManagementService,
@IFileService fileService: IFileService,
@IEnvironmentService environmentService: IEnvironmentService,
@IUserDataSyncLogService logService: IUserDataSyncLogService,
@ -511,12 +522,18 @@ export class ExtensionsInitializer extends AbstractInitializer {
const newlyEnabledExtensions: ILocalExtension[] = [];
const installedExtensions = await this.extensionManagementService.getInstalled();
const newExtensionsToSync = new Map<string, ISyncExtension>();
const installedExtensionsToSync: ISyncExtension[] = [];
const installedExtensionsToSync: { syncExtension: ISyncExtension, installedExtension: ILocalExtension }[] = [];
const toInstall: { names: string[], uuids: string[] } = { names: [], uuids: [] };
const toDisable: IExtensionIdentifier[] = [];
for (const extension of remoteExtensions) {
if (installedExtensions.some(i => areSameExtensions(i.identifier, extension.identifier))) {
installedExtensionsToSync.push(extension);
if (this.ignoredExtensionsManagementService.hasToNeverSyncExtension(extension.identifier.id)) {
// Skip extension ignored to sync
continue;
}
const installedExtension = installedExtensions.find(i => areSameExtensions(i.identifier, extension.identifier));
if (installedExtension) {
installedExtensionsToSync.push({ syncExtension: extension, installedExtension });
if (extension.disabled) {
toDisable.push(extension.identifier);
}
@ -536,11 +553,11 @@ export class ExtensionsInitializer extends AbstractInitializer {
}
// 1. Initialise already installed extensions state
for (const extensionToSync of installedExtensionsToSync) {
if (extensionToSync.state) {
const extensionState = JSON.parse(this.storageService.get(extensionToSync.identifier.id, StorageScope.GLOBAL) || '{}');
forEach(extensionToSync.state, ({ key, value }) => extensionState[key] = value);
this.storageService.store(extensionToSync.identifier.id, JSON.stringify(extensionState), StorageScope.GLOBAL, StorageTarget.MACHINE);
for (const { syncExtension, installedExtension } of installedExtensionsToSync) {
if (syncExtension.state) {
const extensionState = getExtensionStorageState(installedExtension.manifest.publisher, installedExtension.manifest.name, this.storageService);
forEach(syncExtension.state, ({ key, value }) => extensionState[key] = value);
storeExtensionStorageState(installedExtension.manifest.publisher, installedExtension.manifest.name, extensionState, this.storageService);
}
}
@ -560,7 +577,7 @@ export class ExtensionsInitializer extends AbstractInitializer {
try {
const extensionToSync = newExtensionsToSync.get(galleryExtension.identifier.id.toLowerCase())!;
if (extensionToSync.state) {
this.storageService.store(extensionToSync.identifier.id, JSON.stringify(extensionToSync.state), StorageScope.GLOBAL, StorageTarget.MACHINE);
storeExtensionStorageState(galleryExtension.publisher, galleryExtension.name, extensionToSync.state, this.storageService);
}
this.logService.trace(`Installing extension...`, galleryExtension.identifier.id);
const local = await this.extensionManagementService.installFromGallery(galleryExtension, { isMachineScoped: false } /* pass options to prevent install and sync dialog in web */);

53
src/vs/vscode.d.ts vendored
View file

@ -4697,6 +4697,10 @@ declare module 'vscode' {
* This rule will only execute if the text after the cursor matches this regular expression.
*/
afterText?: RegExp;
/**
* This rule will only execute if the text above the current line matches this regular expression.
*/
previousLineText?: RegExp;
/**
* The action to execute.
*/
@ -5821,6 +5825,11 @@ declare module 'vscode' {
setKeysForSync(keys: string[]): void;
};
/**
* A storage utility for secrets.
*/
readonly secrets: SecretStorage;
/**
* The uri of the directory containing the extension.
*/
@ -5958,6 +5967,48 @@ declare module 'vscode' {
update(key: string, value: any): Thenable<void>;
}
/**
* The event data that is fired when a secret is added or removed.
*/
export interface SecretStorageChangeEvent {
/**
* The key of the secret that has changed.
*/
readonly key: string;
}
/**
* Represents a storage utility for secrets, information that is
* sensitive.
*/
export interface SecretStorage {
/**
* Retrieve a secret that was stored with key. Returns undefined if there
* is no password matching that key.
* @param key The key the secret was stored under.
* @returns The stored value or `undefined`.
*/
get(key: string): Thenable<string | undefined>;
/**
* Store a secret under a given key.
* @param key The key to store the secret under.
* @param value The secret.
*/
store(key: string, value: string): Thenable<void>;
/**
* Remove a secret from storage.
* @param key The key the secret was stored under.
*/
delete(key: string): Thenable<void>;
/**
* Fires when a secret is stored or deleted.
*/
onDidChange: Event<SecretStorageChangeEvent>;
}
/**
* Represents a color theme kind.
*/
@ -12161,7 +12212,7 @@ declare module 'vscode' {
/**
* Optional reaction handler for creating and deleting reactions on a [comment](#Comment).
*/
reactionHandler?: (comment: Comment, reaction: CommentReaction) => Promise<void>;
reactionHandler?: (comment: Comment, reaction: CommentReaction) => Thenable<void>;
/**
* Dispose this comment controller.

View file

@ -881,15 +881,6 @@ declare module 'vscode' {
//#endregion
//#region https://github.com/microsoft/vscode/issues/58440
export interface OnEnterRule {
/**
* This rule will only execute if the text above the line matches this regular expression.
*/
oneLineAboveText?: RegExp;
}
//#endregion
//#region Tree View: https://github.com/microsoft/vscode/issues/61313 @alexr00
export interface TreeView<T> extends Disposable {
reveal(element: T | undefined, options?: { select?: boolean, focus?: boolean, expand?: boolean | number; }): Thenable<void>;
@ -1577,16 +1568,16 @@ declare module 'vscode' {
* resolve the raw content for `uri` as the resouce is not necessarily a file on disk.
*/
// eslint-disable-next-line vscode-dts-provider-naming
openNotebook(uri: Uri, openContext: NotebookDocumentOpenContext): NotebookData | Promise<NotebookData>;
openNotebook(uri: Uri, openContext: NotebookDocumentOpenContext): NotebookData | Thenable<NotebookData>;
// eslint-disable-next-line vscode-dts-provider-naming
// eslint-disable-next-line vscode-dts-cancellation
resolveNotebook(document: NotebookDocument, webview: NotebookCommunication): Promise<void>;
resolveNotebook(document: NotebookDocument, webview: NotebookCommunication): Thenable<void>;
// eslint-disable-next-line vscode-dts-provider-naming
saveNotebook(document: NotebookDocument, cancellation: CancellationToken): Promise<void>;
saveNotebook(document: NotebookDocument, cancellation: CancellationToken): Thenable<void>;
// eslint-disable-next-line vscode-dts-provider-naming
saveNotebookAs(targetResource: Uri, document: NotebookDocument, cancellation: CancellationToken): Promise<void>;
saveNotebookAs(targetResource: Uri, document: NotebookDocument, cancellation: CancellationToken): Thenable<void>;
// eslint-disable-next-line vscode-dts-provider-naming
backupNotebook(document: NotebookDocument, context: NotebookDocumentBackupContext, cancellation: CancellationToken): Promise<NotebookDocumentBackup>;
backupNotebook(document: NotebookDocument, context: NotebookDocumentBackupContext, cancellation: CancellationToken): Thenable<NotebookDocumentBackup>;
}
export interface NotebookKernel {
@ -1685,7 +1676,7 @@ declare module 'vscode' {
): Disposable;
export function createNotebookEditorDecorationType(options: NotebookDecorationRenderOptions): NotebookEditorDecorationType;
export function openNotebookDocument(uri: Uri, viewType?: string): Promise<NotebookDocument>;
export function openNotebookDocument(uri: Uri, viewType?: string): Thenable<NotebookDocument>;
export const onDidOpenNotebookDocument: Event<NotebookDocument>;
export const onDidCloseNotebookDocument: Event<NotebookDocument>;
export const onDidSaveNotebookDocument: Event<NotebookDocument>;
@ -1729,7 +1720,7 @@ declare module 'vscode' {
export const onDidChangeActiveNotebookEditor: Event<NotebookEditor | undefined>;
export const onDidChangeNotebookEditorSelection: Event<NotebookEditorSelectionChangeEvent>;
export const onDidChangeNotebookEditorVisibleRanges: Event<NotebookEditorVisibleRangesChangeEvent>;
export function showNotebookDocument(document: NotebookDocument, options?: NotebookDocumentShowOptions): Promise<NotebookEditor>;
export function showNotebookDocument(document: NotebookDocument, options?: NotebookDocumentShowOptions): Thenable<NotebookEditor>;
}
//#endregion
@ -1940,7 +1931,7 @@ declare module 'vscode' {
}
export namespace languages {
export function getTokenInformationAtPosition(document: TextDocument, position: Position): Promise<TokenInformation>;
export function getTokenInformationAtPosition(document: TextDocument, position: Position): Thenable<TokenInformation>;
}
//#endregion
@ -2507,54 +2498,21 @@ declare module 'vscode' {
export function openExternal(target: Uri, options?: OpenExternalOptions): Thenable<boolean>;
}
//#endregion
//#endregionn
//#region https://github.com/microsoft/vscode/issues/112249
//#region https://github.com/Microsoft/vscode/issues/15178
/**
* The event data that is fired when a secret is added or removed.
*/
export interface SecretStorageChangeEvent {
/**
* The key of the secret that has changed.
*/
key: string;
// TODO@API must be a class
export interface OpenEditorInfo {
name: string;
resource: Uri;
}
/**
* Represents a storage utility for secrets, information that is
* sensitive.
*/
export interface SecretStorage {
/**
* Retrieve a secret that was stored with key. Returns undefined if there
* is no password matching that key.
* @param key The key the password was stored under.
* @returns The stored value or `undefined`.
*/
get(key: string): Thenable<string | undefined>;
export namespace window {
export const openEditors: ReadonlyArray<OpenEditorInfo>;
/**
* Store a secret under a given key.
* @param key The key to store the password under.
* @param value The password.
*/
set(key: string, value: string): Thenable<void>;
/**
* Remove a secret from storage.
* @param key The key the password was stored under.
*/
delete(key: string): Thenable<void>;
/**
* Fires when a secret is set or deleted.
*/
onDidChange: Event<SecretStorageChangeEvent>;
}
export interface ExtensionContext {
secrets: SecretStorage;
// todo@API proper event type
export const onDidChangeOpenEditors: Event<void>;
}
//#endregion

View file

@ -3,13 +3,12 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDisposable } from 'vs/base/common/lifecycle';
import { DisposableStore, dispose, IDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
import { IExtHostContext, MainContext } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostContext, IExtHostEditorTabsShape, IExtHostContext, MainContext, IEditorTabDto } from 'vs/workbench/api/common/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { Verbosity } from 'vs/workbench/common/editor';
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
import { GroupChangeKind, IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
export interface ITabInfo {
name: string;
@ -19,34 +18,62 @@ export interface ITabInfo {
@extHostNamedCustomer(MainContext.MainThreadEditorTabs)
export class MainThreadEditorTabs {
private readonly _registration: IDisposable;
private static _GroupEventFilter = new Set([GroupChangeKind.EDITOR_CLOSE, GroupChangeKind.EDITOR_OPEN]);
private readonly _dispoables = new DisposableStore();
private readonly _groups = new Map<IEditorGroup, IDisposable>();
private readonly _proxy: IExtHostEditorTabsShape;
constructor(
_extHostContext: IExtHostContext,
extHostContext: IExtHostContext,
@IEditorGroupsService private readonly _editorGroupsService: IEditorGroupsService,
) {
this._registration = CommandsRegistry.registerCommand('_textEditorTabs', () => {
return this._fetchTextEditors();
});
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostEditorTabs);
this._editorGroupsService.groups.forEach(this._subscribeToGroup, this);
this._dispoables.add(_editorGroupsService.onDidAddGroup(this._subscribeToGroup, this));
this._dispoables.add(_editorGroupsService.onDidRemoveGroup(e => {
const subscription = this._groups.get(e);
if (subscription) {
subscription.dispose();
this._groups.delete(e);
this._pushEditorTabs();
}
}));
this._pushEditorTabs();
}
dispose(): void {
this._registration.dispose();
dispose(this._groups.values());
this._dispoables.dispose();
}
private _fetchTextEditors(): ITabInfo[] {
const result: ITabInfo[] = [];
private _subscribeToGroup(group: IEditorGroup) {
this._groups.get(group)?.dispose();
const listener = group.onDidGroupChange(e => {
if (MainThreadEditorTabs._GroupEventFilter.has(e.kind)) {
this._pushEditorTabs();
}
});
this._groups.set(group, listener);
}
private _pushEditorTabs(): void {
const tabs: IEditorTabDto[] = [];
for (const group of this._editorGroupsService.groups) {
for (const editor of group.editors) {
if (editor.isDisposed() || !editor.resource) {
continue;
}
result.push({
tabs.push({
group: group.id,
name: editor.getTitle(Verbosity.SHORT) ?? '',
resource: editor.resource
});
}
}
return result;
this._proxy.$acceptEditorTabs(tabs);
}
}

View file

@ -688,7 +688,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
return {
beforeText: MainThreadLanguageFeatures._reviveRegExp(onEnterRule.beforeText),
afterText: onEnterRule.afterText ? MainThreadLanguageFeatures._reviveRegExp(onEnterRule.afterText) : undefined,
oneLineAboveText: onEnterRule.oneLineAboveText ? MainThreadLanguageFeatures._reviveRegExp(onEnterRule.oneLineAboveText) : undefined,
previousLineText: onEnterRule.previousLineText ? MainThreadLanguageFeatures._reviveRegExp(onEnterRule.previousLineText) : undefined,
action: onEnterRule.action
};
}

View file

@ -25,7 +25,7 @@ export class ExtHostSecretState implements ExtHostSecretStateShape {
return this._proxy.$getPassword(extensionId, key);
}
set(extensionId: string, key: string, value: string): Promise<void> {
store(extensionId: string, key: string, value: string): Promise<void> {
return this._proxy.$setPassword(extensionId, key, value);
}

View file

@ -84,6 +84,7 @@ import { IExtHostFileSystemInfo } from 'vs/workbench/api/common/extHostFileSyste
import { ExtHostTesting } from 'vs/workbench/api/common/extHostTesting';
import { ExtHostUriOpeners } from 'vs/workbench/api/common/extHostUriOpener';
import { IExtHostSecretState } from 'vs/workbench/api/common/exHostSecretState';
import { ExtHostEditorTabs } from 'vs/workbench/api/common/extHostEditorTabs';
export interface IExtensionApiFactory {
(extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode;
@ -133,6 +134,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
const extHostOutputService = rpcProtocol.set(ExtHostContext.ExtHostOutputService, accessor.get(IExtHostOutputService));
// manually create and register addressable instances
const extHostEditorTabs = rpcProtocol.set(ExtHostContext.ExtHostEditorTabs, new ExtHostEditorTabs());
const extHostUrls = rpcProtocol.set(ExtHostContext.ExtHostUrls, new ExtHostUrls(rpcProtocol));
const extHostDocuments = rpcProtocol.set(ExtHostContext.ExtHostDocuments, new ExtHostDocuments(rpcProtocol, extHostDocumentsAndEditors));
const extHostDocumentContentProviders = rpcProtocol.set(ExtHostContext.ExtHostDocumentContentProviders, new ExtHostDocumentContentProvider(rpcProtocol, extHostDocumentsAndEditors, extHostLogService));
@ -683,6 +685,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
checkProposedApiEnabled(extension);
return extHostUriOpeners.registerUriOpener(extension.identifier, id, schemes, opener, metadata);
},
get openEditors() {
checkProposedApiEnabled(extension);
return extHostEditorTabs.tabs;
},
get onDidChangeOpenEditors() {
checkProposedApiEnabled(extension);
return extHostEditorTabs.onDidChangeTabs;
}
};
// namespace: workspace

View file

@ -335,7 +335,7 @@ export interface IIndentationRuleDto {
export interface IOnEnterRuleDto {
beforeText: IRegExpDto;
afterText?: IRegExpDto;
oneLineAboveText?: IRegExpDto;
previousLineText?: IRegExpDto;
action: EnterAction;
}
export interface ILanguageConfigurationDto {
@ -607,15 +607,29 @@ export interface MainThreadEditorInsetsShape extends IDisposable {
$postMessage(handle: number, value: any): Promise<boolean>;
}
export interface MainThreadEditorTabsShape extends IDisposable {
}
export interface ExtHostEditorInsetsShape {
$onDidDispose(handle: number): void;
$onDidReceiveMessage(handle: number, message: any): void;
}
//#region --- open editors model
export interface MainThreadEditorTabsShape extends IDisposable {
// manage tabs: move, close, rearrange etc
}
export interface IEditorTabDto {
group: number;
name: string;
resource: UriComponents
}
export interface IExtHostEditorTabsShape {
$acceptEditorTabs(tabs: IEditorTabDto[]): void;
}
//#endregion
export type WebviewHandle = string;
export interface WebviewPanelShowOptions {
@ -1927,6 +1941,7 @@ export const ExtHostContext = {
ExtHostCustomEditors: createExtId<ExtHostCustomEditorsShape>('ExtHostCustomEditors'),
ExtHostWebviewViews: createExtId<ExtHostWebviewViewsShape>('ExtHostWebviewViews'),
ExtHostEditorInsets: createExtId<ExtHostEditorInsetsShape>('ExtHostEditorInsets'),
ExtHostEditorTabs: createExtId<IExtHostEditorTabsShape>('ExtHostEditorTabs'),
ExtHostProgress: createMainId<ExtHostProgressShape>('ExtHostProgress'),
ExtHostComments: createMainId<ExtHostCommentsShape>('ExtHostComments'),
ExtHostSecretState: createMainId<ExtHostSecretStateShape>('ExtHostSecretState'),

View file

@ -0,0 +1,39 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import type * as vscode from 'vscode';
import { IEditorTabDto, IExtHostEditorTabsShape } from 'vs/workbench/api/common/extHost.protocol';
import { URI } from 'vs/base/common/uri';
import { Emitter, Event } from 'vs/base/common/event';
export interface IEditorTab {
name: string;
group: number;
resource: vscode.Uri
}
export class ExtHostEditorTabs implements IExtHostEditorTabsShape {
private readonly _onDidChangeTabs = new Emitter<void>();
readonly onDidChangeTabs: Event<void> = this._onDidChangeTabs.event;
private _tabs: IEditorTab[] = [];
get tabs(): readonly IEditorTab[] {
return this._tabs;
}
$acceptEditorTabs(tabs: IEditorTabDto[]): void {
this._tabs = tabs.map(dto => {
return {
name: dto.name,
group: dto.group,
resource: URI.revive(dto.resource)
};
});
this._onDidChangeTabs.fire();
}
}

View file

@ -1921,7 +1921,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
return {
beforeText: ExtHostLanguageFeatures._serializeRegExp(onEnterRule.beforeText),
afterText: onEnterRule.afterText ? ExtHostLanguageFeatures._serializeRegExp(onEnterRule.afterText) : undefined,
oneLineAboveText: onEnterRule.oneLineAboveText ? ExtHostLanguageFeatures._serializeRegExp(onEnterRule.oneLineAboveText) : undefined,
previousLineText: onEnterRule.previousLineText ? ExtHostLanguageFeatures._serializeRegExp(onEnterRule.previousLineText) : undefined,
action: onEnterRule.action
};
}

View file

@ -33,8 +33,8 @@ export class ExtensionSecrets implements vscode.SecretStorage {
return this._secretState.get(this._id, key);
}
set(key: string, value: string): Promise<void> {
return this._secretState.set(this._id, key, value);
store(key: string, value: string): Promise<void> {
return this._secretState.store(this._id, key, value);
}
delete(key: string): Promise<void> {

View file

@ -197,6 +197,10 @@ export class ExtHostTesting implements ExtHostTestingShape {
try {
await provider.runTests({ tests, debug: req.debug }, cancellation);
for (const { collection } of this.testSubscriptions.values()) {
collection.flushDiff(); // ensure all states are updated
}
return EMPTY_TEST_RESULT;
} catch (e) {
console.error(e); // so it appears to attached debuggers
@ -621,16 +625,9 @@ class TextDocumentTestObserverFactory extends AbstractTestObserverFactory {
const uriString = resourceUri.toString();
this.diffListeners.set(uriString, onDiff);
const disposeListener = this.documents.onDidRemoveDocuments(evt => {
if (evt.some(delta => delta.document.uri.toString() === uriString)) {
this.unlisten(resourceUri);
}
});
this.proxy.$subscribeToDiffs(ExtHostTestingResource.TextDocument, resourceUri);
return new Disposable(() => {
this.proxy.$unsubscribeFromDiffs(ExtHostTestingResource.TextDocument, resourceUri);
disposeListener.dispose();
this.diffListeners.delete(uriString);
});
}

View file

@ -2315,9 +2315,6 @@ export class FunctionBreakpoint extends Breakpoint {
constructor(functionName: string, enabled?: boolean, condition?: string, hitCondition?: string, logMessage?: string) {
super(enabled, condition, hitCondition, logMessage);
if (!functionName) {
throw illegalArgument('functionName');
}
this.functionName = functionName;
}
}

View file

@ -15,13 +15,13 @@ import * as fs from 'fs';
import * as pfs from 'vs/base/node/pfs';
import { isLinux } from 'vs/base/common/platform';
import { IExtHostTunnelService, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { asPromise } from 'vs/base/common/async';
import { Event, Emitter } from 'vs/base/common/event';
import { TunnelOptions, TunnelCreationOptions } from 'vs/platform/remote/common/tunnel';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { promisify } from 'util';
import { MovingAverage } from 'vs/base/common/numbers';
import { CandidatePort } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { ILogService } from 'vs/platform/log/common/log';
class ExtensionTunnel implements vscode.Tunnel {
private _onDispose: Emitter<void> = new Emitter();
@ -142,7 +142,8 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
constructor(
@IExtHostRpcService extHostRpc: IExtHostRpcService,
@IExtHostInitDataService initData: IExtHostInitDataService
@IExtHostInitDataService initData: IExtHostInitDataService,
@ILogService private readonly logService: ILogService
) {
super();
this._proxy = extHostRpc.getProxy(MainContext.MainThreadTunnelService);
@ -234,16 +235,24 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
async $forwardPort(tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<TunnelDto | undefined> {
if (this._forwardPortProvider) {
const providedPort = this._forwardPortProvider(tunnelOptions, tunnelCreationOptions);
if (providedPort !== undefined) {
return asPromise(() => providedPort).then(tunnel => {
try {
this.logService.trace('$forwardPort: Getting tunnel from provider.');
const providedPort = this._forwardPortProvider(tunnelOptions, tunnelCreationOptions);
this.logService.trace('$forwardPort: Got tunnel promise from provider.');
if (providedPort !== undefined) {
const tunnel = await providedPort;
this.logService.trace('$forwardPort: Successfully awaited tunnel from provider.');
if (!this._extensionTunnels.has(tunnelOptions.remoteAddress.host)) {
this._extensionTunnels.set(tunnelOptions.remoteAddress.host, new Map());
}
const disposeListener = this._register(tunnel.onDidDispose(() => this._proxy.$closeTunnel(tunnel.remoteAddress)));
this._extensionTunnels.get(tunnelOptions.remoteAddress.host)!.set(tunnelOptions.remoteAddress.port, { tunnel, disposeListener });
return Promise.resolve(TunnelDto.fromApiTunnel(tunnel));
});
return TunnelDto.fromApiTunnel(tunnel);
} else {
this.logService.trace('$forwardPort: Tunnel is undefined');
}
} catch (e) {
this.logService.trace('$forwardPort: tunnel provider error');
}
}
return undefined;

View file

@ -785,8 +785,8 @@ export class ActivitybarPart extends Part implements IActivityBarService {
}
}
// Check cache only in desktop local window and if extensions are not yet registered
if (!this.environmentService.remoteAuthority && !this.hasExtensionsRegistered) {
// Check cache only if extensions are not yet registered and current window is not native (desktop) remote connection window
if (!this.hasExtensionsRegistered && !(this.environmentService.remoteAuthority && isNative)) {
cachedViewContainer = cachedViewContainer || this.cachedViewContainers.find(({ id }) => id === viewContainerId);
// Show builtin ViewContainer if not registered yet

View file

@ -711,7 +711,7 @@ export class CustomMenubarControl extends MenubarControl {
if (href) {
webNavigationActions.push(new Action('goHome', nls.localize('goHome', "Go Home"), undefined, true,
async (event?: MouseEvent) => {
if (event?.ctrlKey) {
if ((!isMacintosh && event?.ctrlKey) || (isMacintosh && event?.metaKey)) {
window.open(href, '_blank');
} else {
window.location.href = href;

View file

@ -40,7 +40,7 @@ interface IEnterAction {
interface IOnEnterRule {
beforeText: string | IRegExp;
afterText?: string | IRegExp;
oneLineAboveText?: string | IRegExp;
previousLineText?: string | IRegExp;
action: IEnterAction;
}
@ -328,10 +328,10 @@ export class LanguageConfigurationFileHandler {
resultingOnEnterRule.afterText = afterText;
}
}
if (onEnterRule.oneLineAboveText) {
const oneLineAboveText = this._parseRegex(languageIdentifier, `onEnterRules[${i}].oneLineAboveText`, onEnterRule.oneLineAboveText);
if (oneLineAboveText) {
resultingOnEnterRule.oneLineAboveText = oneLineAboveText;
if (onEnterRule.previousLineText) {
const previousLineText = this._parseRegex(languageIdentifier, `onEnterRules[${i}].previousLineText`, onEnterRule.previousLineText);
if (previousLineText) {
resultingOnEnterRule.previousLineText = previousLineText;
}
}
result = result || [];
@ -741,21 +741,21 @@ const schema: IJSONSchema = {
}
}
},
oneLineAboveText: {
previousLineText: {
type: ['string', 'object'],
description: nls.localize('schema.onEnterRules.oneLineAboveText', 'This rule will only execute if the text above the line matches this regular expression.'),
description: nls.localize('schema.onEnterRules.previousLineText', 'This rule will only execute if the text above the line matches this regular expression.'),
properties: {
pattern: {
type: 'string',
description: nls.localize('schema.onEnterRules.oneLineAboveText.pattern', 'The RegExp pattern for oneLineAboveText.'),
description: nls.localize('schema.onEnterRules.previousLineText.pattern', 'The RegExp pattern for previousLineText.'),
default: '',
},
flags: {
type: 'string',
description: nls.localize('schema.onEnterRules.oneLineAboveText.flags', 'The RegExp flags for oneLineAboveText.'),
description: nls.localize('schema.onEnterRules.previousLineText.flags', 'The RegExp flags for previousLineText.'),
default: '',
pattern: '^([gimuy]+)$',
patternErrorMessage: nls.localize('schema.onEnterRules.oneLineAboveText.errorMessage', 'Must match the pattern `/^([gimuy]+)$/`.')
patternErrorMessage: nls.localize('schema.onEnterRules.previousLineText.errorMessage', 'Must match the pattern `/^([gimuy]+)$/`.')
}
}
},

View file

@ -209,11 +209,6 @@ class DocumentSymbolsOutline implements IOutline<DocumentSymbolItem> {
}
async reveal(entry: DocumentSymbolItem, options: IEditorOptions, sideBySide: boolean): Promise<void> {
if (entry instanceof OutlineElement) {
const position = Range.getStartPosition(entry.symbol.selectionRange);
this._editor.revealPositionInCenterIfOutsideViewport(position, ScrollType.Immediate);
this._editor.setPosition(position);
}
const model = OutlineModel.get(entry);
if (!model || !(entry instanceof OutlineElement)) {
return;

View file

@ -478,7 +478,7 @@ export abstract class InstallInOtherServerAction extends ExtensionAction {
|| !this.extension.local
|| this.extension.state !== ExtensionState.Installed
|| this.extension.type !== ExtensionType.User
|| this.extension.enablementState === EnablementState.DisabledByEnvironemt
|| this.extension.enablementState === EnablementState.DisabledByEnvironment
) {
return false;
}

View file

@ -19,8 +19,8 @@ export default class Messages {
public static MARKERS_PANEL_TITLE_PROBLEMS: string = nls.localize('markers.panel.title.problems', "Problems");
public static MARKERS_PANEL_NO_PROBLEMS_BUILT: string = nls.localize('markers.panel.no.problems.build', "No problems have been detected in the workspace so far.");
public static MARKERS_PANEL_NO_PROBLEMS_ACTIVE_FILE_BUILT: string = nls.localize('markers.panel.no.problems.activeFile.build', "No problems have been detected in the current file so far.");
public static MARKERS_PANEL_NO_PROBLEMS_BUILT: string = nls.localize('markers.panel.no.problems.build', "No problems have been detected in the workspace.");
public static MARKERS_PANEL_NO_PROBLEMS_ACTIVE_FILE_BUILT: string = nls.localize('markers.panel.no.problems.activeFile.build', "No problems have been detected in the current file.");
public static MARKERS_PANEL_NO_PROBLEMS_FILTERS: string = nls.localize('markers.panel.no.problems.filters', "No results found with provided filter criteria.");
public static MARKERS_PANEL_ACTION_TOOLTIP_MORE_FILTERS: string = nls.localize('markers.panel.action.moreFilters', "More Filters...");

View file

@ -17,7 +17,7 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService
import { CommandsRegistry, ICommandService } from 'vs/platform/commands/common/commands';
import { ContextKeyExpr, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { InputFocusedContext, InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { IQuickInputService, IQuickPickItem, QuickPickInput } from 'vs/platform/quickinput/common/quickInput';
import { CATEGORIES } from 'vs/workbench/common/actions';
@ -141,9 +141,9 @@ abstract class NotebookAction extends Action2 {
super(desc);
}
async run(accessor: ServicesAccessor, context?: INotebookActionContext): Promise<void> {
async run(accessor: ServicesAccessor, context?: any): Promise<void> {
if (!this.isNotebookActionContext(context)) {
context = this.getActiveEditorContext(accessor);
context = this.getEditorContextFromArgsOrActive(accessor, context);
if (!context) {
return;
}
@ -158,7 +158,7 @@ abstract class NotebookAction extends Action2 {
return !!context && !!(context as INotebookActionContext).notebookEditor;
}
protected getActiveEditorContext(accessor: ServicesAccessor): INotebookActionContext | undefined {
protected getEditorContextFromArgsOrActive(accessor: ServicesAccessor, context?: any): INotebookActionContext | undefined {
const editorService = accessor.get(IEditorService);
const editor = getActiveNotebookEditor(editorService);
@ -198,7 +198,7 @@ abstract class NotebookCellAction<T = INotebookCellActionContext> extends Notebo
return this.runWithContext(accessor, contextFromArgs);
}
const activeEditorContext = this.getActiveEditorContext(accessor);
const activeEditorContext = this.getEditorContextFromArgsOrActive(accessor);
if (this.isCellActionContext(activeEditorContext)) {
return this.runWithContext(accessor, activeEditorContext);
}
@ -207,6 +207,22 @@ abstract class NotebookCellAction<T = INotebookCellActionContext> extends Notebo
abstract runWithContext(accessor: ServicesAccessor, context: INotebookCellActionContext): Promise<void>;
}
export function getWidgetFromUri(accessor: ServicesAccessor, uri: URI) {
const editorService = accessor.get(IEditorService);
const notebookWidgetService = accessor.get(INotebookEditorWidgetService);
const editorId = editorService.getEditors(EditorsOrder.SEQUENTIAL).find(editorId => editorId.editor instanceof NotebookEditorInput && editorId.editor.resource?.toString() === uri.toString());
if (editorId) {
const widget = notebookWidgetService.widgets.find(widget => widget.textModel?.viewType === (editorId.editor as NotebookEditorInput).viewType && widget.uri?.toString() === editorId.editor.resource!.toString());
if (widget && widget.hasModel()) {
return widget;
}
}
return undefined;
}
registerAction2(class extends NotebookCellAction<ICellRange> {
constructor() {
super({
@ -259,30 +275,19 @@ registerAction2(class extends NotebookCellAction<ICellRange> {
const uri = URI.revive(additionalArgs[0]);
if (uri) {
// we will run cell against this document
const editorService = accessor.get(IEditorService);
const editorGroupService = accessor.get(IEditorGroupsService);
const instantiationService = accessor.get(IInstantiationService);
const notebookWidgetService = accessor.get(INotebookEditorWidgetService);
const editorId = editorService.getEditors(EditorsOrder.SEQUENTIAL).find(editorId => editorId.editor instanceof NotebookEditorInput && editorId.editor.resource?.toString() === uri.toString());
const widget = getWidgetFromUri(accessor, uri);
if (widget) {
const cells = widget.viewModel.viewCells;
if (editorId) {
const group = editorGroupService.getGroup(editorId.groupId);
const widget = instantiationService.invokeFunction(notebookWidgetService.retrieveWidget, group!, editorId.editor as NotebookEditorInput);
if (widget.value?.hasModel()) {
const cells = widget.value.viewModel.viewCells;
return {
notebookEditor: widget.value!,
cell: cells[context.start]
};
}
return {
notebookEditor: widget,
cell: cells[context.start]
};
}
}
}
const activeEditorContext = this.getActiveEditorContext(accessor);
const activeEditorContext = this.getEditorContextFromArgsOrActive(accessor);
if (!activeEditorContext || !activeEditorContext.notebookEditor.viewModel || context.start >= activeEditorContext.notebookEditor.viewModel.viewCells.length) {
return;
@ -326,18 +331,39 @@ registerAction2(class extends NotebookCellAction<ICellRange> {
}
}
}
},
{
name: 'uri',
description: 'The document uri',
constraint: URI
}
]
},
});
}
getCellContextFromArgs(accessor: ServicesAccessor, context?: ICellRange): INotebookCellActionContext | undefined {
getCellContextFromArgs(accessor: ServicesAccessor, context?: ICellRange, ...additionalArgs: any[]): INotebookCellActionContext | undefined {
if (!context || typeof context.start !== 'number' || typeof context.end !== 'number' || context.start >= context.end) {
return;
}
const activeEditorContext = this.getActiveEditorContext(accessor);
if (additionalArgs.length && additionalArgs[0]) {
const uri = URI.revive(additionalArgs[0]);
if (uri) {
const widget = getWidgetFromUri(accessor, uri);
if (widget) {
const cells = widget.viewModel.viewCells;
return {
notebookEditor: widget,
cell: cells[context.start]
};
}
}
}
const activeEditorContext = this.getEditorContextFromArgsOrActive(accessor);
if (!activeEditorContext || !activeEditorContext.notebookEditor.viewModel || context.start >= activeEditorContext.notebookEditor.viewModel.viewCells.length) {
return;
@ -491,19 +517,62 @@ registerAction2(class extends NotebookAction {
super({
id: EXECUTE_NOTEBOOK_COMMAND_ID,
title: localize('notebookActions.executeNotebook', "Execute Notebook"),
description: {
description: localize('notebookActions.executeNotebook', "Execute Notebook"),
args: [
{
name: 'uri',
description: 'The document uri',
constraint: URI
}
]
},
});
}
getEditorContextFromArgsOrActive(accessor: ServicesAccessor, context?: UriComponents): INotebookActionContext | undefined {
if (context) {
const uri = URI.revive(context);
if (uri) {
const widget = getWidgetFromUri(accessor, uri);
if (widget) {
return {
notebookEditor: widget,
};
}
}
}
const editorService = accessor.get(IEditorService);
const editor = getActiveNotebookEditor(editorService);
if (!editor) {
return;
}
if (!editor.hasModel()) {
return;
}
const activeCell = editor.getActiveCell();
return {
cell: activeCell,
notebookEditor: editor
};
}
async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise<void> {
renderAllMarkdownCells(context);
const editorService = accessor.get(IEditorService);
const editor = editorService.getEditors(EditorsOrder.MOST_RECENTLY_ACTIVE).find(
editor => editor.editor instanceof NotebookEditorInput && editor.editor.viewType === context.notebookEditor.viewModel.viewType && editor.editor.resource.toString() === context.notebookEditor.viewModel.uri.toString());
const editorGroupService = accessor.get(IEditorGroupsService);
const group = editorGroupService.activeGroup;
if (group) {
if (group.activeEditor) {
group.pinEditor(group.activeEditor);
}
if (editor) {
const group = editorGroupService.getGroup(editor.groupId);
group?.pinEditor(editor.editor);
}
return context.notebookEditor.executeNotebook();
@ -523,9 +592,51 @@ registerAction2(class extends NotebookAction {
super({
id: CANCEL_NOTEBOOK_COMMAND_ID,
title: localize('notebookActions.cancelNotebook', "Cancel Notebook Execution"),
description: {
description: localize('notebookActions.executeNotebook', "Execute Notebook"),
args: [
{
name: 'uri',
description: 'The document uri',
constraint: URI
}
]
},
});
}
getEditorContextFromArgsOrActive(accessor: ServicesAccessor, context?: UriComponents): INotebookActionContext | undefined {
if (context) {
const uri = URI.revive(context);
if (uri) {
const widget = getWidgetFromUri(accessor, uri);
if (widget) {
return {
notebookEditor: widget,
};
}
}
}
const editorService = accessor.get(IEditorService);
const editor = getActiveNotebookEditor(editorService);
if (!editor) {
return;
}
if (!editor.hasModel()) {
return;
}
const activeCell = editor.getActiveCell();
return {
cell: activeCell,
notebookEditor: editor
};
}
async runWithContext(accessor: ServicesAccessor, context: INotebookActionContext): Promise<void> {
return context.notebookEditor.cancelNotebookExecution();
}
@ -745,7 +856,7 @@ registerAction2(class extends NotebookAction {
}
async run(accessor: ServicesAccessor): Promise<void> {
const context = this.getActiveEditorContext(accessor);
const context = this.getEditorContextFromArgsOrActive(accessor);
if (context) {
this.runWithContext(accessor, context);
}
@ -770,7 +881,7 @@ registerAction2(class extends NotebookAction {
}
async run(accessor: ServicesAccessor): Promise<void> {
const context = this.getActiveEditorContext(accessor);
const context = this.getEditorContextFromArgsOrActive(accessor);
if (context) {
this.runWithContext(accessor, context);
}

View file

@ -232,7 +232,8 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri
this._register(this.editorService.overrideOpenEditor({
getEditorOverrides: (resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined) => {
const currentEditorForResource = group && this.editorService.findEditors(resource, group);
const currentEditorsForResource = group && this.editorService.findEditors(resource, group);
const currentEditorForResource = currentEditorsForResource && currentEditorsForResource.length ? currentEditorsForResource[0] : undefined;
const associatedEditors = distinct([
...this.getUserAssociatedNotebookEditors(resource),
@ -335,8 +336,25 @@ export class NotebookContribution extends Disposable implements IWorkbenchContri
return undefined;
}
if (originalInput instanceof NotebookEditorInput) {
return undefined;
if (id && originalInput instanceof NotebookEditorInput) {
if (originalInput.viewType === id) {
return undefined;
} else {
return {
override: (async () => {
const notebookInput = NotebookEditorInput.create(this.instantiationService, originalInput.resource, originalInput.getName(), id);
await group.replaceEditors([{
editor: originalInput,
replacement: notebookInput
}]);
if (group.activeEditorPane?.input === notebookInput) {
return group.activeEditorPane;
} else {
return undefined;
}
})()
};
}
}
if (originalInput instanceof NotebookDiffEditorInput) {

View file

@ -16,5 +16,6 @@ export interface IBorrowValue<T> {
export interface INotebookEditorWidgetService {
_serviceBrand: undefined;
widgets: NotebookEditorWidget[];
retrieveWidget(accessor: ServicesAccessor, group: IEditorGroup, input: NotebookEditorInput): IBorrowValue<NotebookEditorWidget>;
}

View file

@ -21,6 +21,10 @@ export class NotebookEditorWidgetService implements INotebookEditorWidgetService
private readonly _notebookWidgets = new Map<number, ResourceMap<{ widget: NotebookEditorWidget, token: number | undefined }>>();
private readonly _disposables = new DisposableStore();
get widgets() {
return [...this._notebookWidgets.values()].map(val => [...val.values()].map(widget => widget.widget)).reduce((prev, curr) => { return [...prev, ...curr]; }, []);
}
constructor(
@IEditorGroupsService editorGroupService: IEditorGroupsService,
@IEditorService editorService: IEditorService,

View file

@ -253,7 +253,6 @@ export class OutlinePane extends ViewPane {
// update: refresh tree
this._domNode.classList.remove('message');
tree.updateChildren();
tree.expandAll();
}
};
updateTree();

View file

@ -3,44 +3,59 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ITunnelService, TunnelOptions, RemoteTunnel, TunnelCreationOptions } from 'vs/platform/remote/common/tunnel';
import { ITunnelService, TunnelOptions, RemoteTunnel, TunnelCreationOptions, ITunnel } from 'vs/platform/remote/common/tunnel';
import { Disposable } from 'vs/base/common/lifecycle';
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { URI } from 'vs/base/common/uri';
import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { ILogService } from 'vs/platform/log/common/log';
export class TunnelFactoryContribution extends Disposable implements IWorkbenchContribution {
constructor(
@ITunnelService tunnelService: ITunnelService,
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
@IOpenerService openerService: IOpenerService,
@IRemoteExplorerService remoteExplorerService: IRemoteExplorerService
@IRemoteExplorerService remoteExplorerService: IRemoteExplorerService,
@ILogService logService: ILogService
) {
super();
const tunnelFactory = environmentService.options?.tunnelProvider?.tunnelFactory;
if (tunnelFactory) {
this._register(tunnelService.setTunnelProvider({
forwardPort: (tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<RemoteTunnel> | undefined => {
const tunnelPromise = tunnelFactory(tunnelOptions, tunnelCreationOptions);
if (!tunnelPromise) {
return undefined;
forwardPort: (tunnelOptions: TunnelOptions, tunnelCreationOptions: TunnelCreationOptions): Promise<RemoteTunnel | undefined> | undefined => {
let tunnelPromise: Promise<ITunnel> | undefined;
try {
tunnelPromise = tunnelFactory(tunnelOptions, tunnelCreationOptions);
} catch (e) {
logService.trace('tunnelFactory: tunnel provider error');
}
return new Promise(resolve => {
tunnelPromise.then(async (tunnel) => {
const localAddress = tunnel.localAddress.startsWith('http') ? tunnel.localAddress : `http://${tunnel.localAddress}`;
const remoteTunnel: RemoteTunnel = {
tunnelRemotePort: tunnel.remoteAddress.port,
tunnelRemoteHost: tunnel.remoteAddress.host,
// The tunnel factory may give us an inaccessible local address.
// To make sure this doesn't happen, resolve the uri immediately.
localAddress: (await openerService.resolveExternalUri(URI.parse(localAddress))).resolved.toString(),
public: !!tunnel.public,
dispose: async () => { await tunnel.dispose; }
};
resolve(remoteTunnel);
});
return new Promise(async (resolve) => {
if (!tunnelPromise) {
resolve(undefined);
return;
}
let tunnel: ITunnel;
try {
tunnel = await tunnelPromise;
} catch (e) {
logService.trace('tunnelFactory: tunnel provider promise error');
resolve(undefined);
return;
}
const localAddress = tunnel.localAddress.startsWith('http') ? tunnel.localAddress : `http://${tunnel.localAddress}`;
const remoteTunnel: RemoteTunnel = {
tunnelRemotePort: tunnel.remoteAddress.port,
tunnelRemoteHost: tunnel.remoteAddress.host,
// The tunnel factory may give us an inaccessible local address.
// To make sure this doesn't happen, resolve the uri immediately.
localAddress: (await openerService.resolveExternalUri(URI.parse(localAddress))).resolved.toString(),
public: !!tunnel.public,
dispose: async () => { await tunnel.dispose(); }
};
resolve(remoteTunnel);
});
}
}, environmentService.options?.tunnelProvider?.features ?? { elevation: false, public: false }));

View file

@ -5,9 +5,9 @@
import * as assert from 'assert';
import * as errors from 'vs/base/common/errors';
import { DeferredPromise } from 'vs/base/test/common/utils';
import { QueryType, IFileQuery } from 'vs/workbench/services/search/common/search';
import { FileQueryCacheState } from 'vs/workbench/contrib/search/common/cacheState';
import { DeferredPromise } from 'vs/base/common/async';
suite('FileQueryCacheState', () => {

View file

@ -4,10 +4,9 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import * as sinon from 'sinon';
import { timeout } from 'vs/base/common/async';
import { DeferredPromise, timeout } from 'vs/base/common/async';
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
import { URI } from 'vs/base/common/uri';
import { DeferredPromise } from 'vs/base/test/common/utils';
import { Range } from 'vs/editor/common/core/range';
import { IModelService } from 'vs/editor/common/services/modelService';
import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl';

View file

@ -21,7 +21,7 @@ suite('Snippets', function () {
let file = new TestSnippetFile(URI.file('somepath/foo.code-snippets'), []);
let bucket: Snippet[] = [];
file.select('', bucket);
assert.equal(bucket.length, 0);
assert.strictEqual(bucket.length, 0);
file = new TestSnippetFile(URI.file('somepath/foo.code-snippets'), [
new Snippet(['foo'], 'FooSnippet1', 'foo', '', 'snippet', 'test', SnippetSource.User),
@ -34,23 +34,23 @@ suite('Snippets', function () {
bucket = [];
file.select('foo', bucket);
assert.equal(bucket.length, 2);
assert.strictEqual(bucket.length, 2);
bucket = [];
file.select('fo', bucket);
assert.equal(bucket.length, 0);
assert.strictEqual(bucket.length, 0);
bucket = [];
file.select('bar', bucket);
assert.equal(bucket.length, 1);
assert.strictEqual(bucket.length, 1);
bucket = [];
file.select('bar.comment', bucket);
assert.equal(bucket.length, 2);
assert.strictEqual(bucket.length, 2);
bucket = [];
file.select('bazz', bucket);
assert.equal(bucket.length, 1);
assert.strictEqual(bucket.length, 1);
});
test('SnippetFile#select - any scope', function () {
@ -62,7 +62,7 @@ suite('Snippets', function () {
let bucket: Snippet[] = [];
file.select('foo', bucket);
assert.equal(bucket.length, 2);
assert.strictEqual(bucket.length, 2);
});
@ -70,9 +70,9 @@ suite('Snippets', function () {
function assertNeedsClipboard(body: string, expected: boolean): void {
let snippet = new Snippet(['foo'], 'FooSnippet1', 'foo', '', body, 'test', SnippetSource.User);
assert.equal(snippet.needsClipboard, expected);
assert.strictEqual(snippet.needsClipboard, expected);
assert.equal(SnippetParser.guessNeedsClipboard(body), expected);
assert.strictEqual(SnippetParser.guessNeedsClipboard(body), expected);
}
assertNeedsClipboard('foo$CLIPBOARD', true);

View file

@ -14,7 +14,7 @@ suite('getNonWhitespacePrefix', () => {
getLineContent: (lineNumber: number) => line
};
let actual = getNonWhitespacePrefix(model, new Position(1, column));
assert.equal(actual, expected);
assert.strictEqual(actual, expected);
}
test('empty line', () => {

View file

@ -11,9 +11,9 @@ suite('SnippetRewrite', function () {
function assertRewrite(input: string, expected: string | boolean): void {
const actual = new Snippet(['foo'], 'foo', 'foo', 'foo', input, 'foo', SnippetSource.User);
if (typeof expected === 'boolean') {
assert.equal(actual.codeSnippet, input);
assert.strictEqual(actual.codeSnippet, input);
} else {
assert.equal(actual.codeSnippet, expected);
assert.strictEqual(actual.codeSnippet, expected);
}
}
@ -48,8 +48,8 @@ suite('SnippetRewrite', function () {
test('lazy bogous variable rewrite', function () {
const snippet = new Snippet(['fooLang'], 'foo', 'prefix', 'desc', 'This is ${bogous} because it is a ${var}', 'source', SnippetSource.Extension);
assert.equal(snippet.body, 'This is ${bogous} because it is a ${var}');
assert.equal(snippet.codeSnippet, 'This is ${1:bogous} because it is a ${2:var}');
assert.equal(snippet.isBogous, true);
assert.strictEqual(snippet.body, 'This is ${bogous} because it is a ${var}');
assert.strictEqual(snippet.codeSnippet, 'This is ${1:bogous} because it is a ${2:var}');
assert.strictEqual(snippet.isBogous, true);
});
});

View file

@ -80,8 +80,8 @@ suite('SnippetsService', function () {
const model = createTextModel('', undefined, modeService.getLanguageIdentifier('fooLang'));
return provider.provideCompletionItems(model, new Position(1, 1), context)!.then(result => {
assert.equal(result.incomplete, undefined);
assert.equal(result.suggestions.length, 2);
assert.strictEqual(result.incomplete, undefined);
assert.strictEqual(result.suggestions.length, 2);
});
});
@ -91,14 +91,14 @@ suite('SnippetsService', function () {
const model = createTextModel('bar', undefined, modeService.getLanguageIdentifier('fooLang'));
return provider.provideCompletionItems(model, new Position(1, 4), context)!.then(result => {
assert.equal(result.incomplete, undefined);
assert.equal(result.suggestions.length, 1);
assert.deepEqual(result.suggestions[0].label, {
assert.strictEqual(result.incomplete, undefined);
assert.strictEqual(result.suggestions.length, 1);
assert.deepStrictEqual(result.suggestions[0].label, {
name: 'bar',
type: 'barTest'
});
assert.equal((result.suggestions[0].range as any).insert.startColumn, 1);
assert.equal(result.suggestions[0].insertText, 'barCodeSnippet');
assert.strictEqual((result.suggestions[0].range as any).insert.startColumn, 1);
assert.strictEqual(result.suggestions[0].insertText, 'barCodeSnippet');
});
});
@ -126,48 +126,48 @@ suite('SnippetsService', function () {
const model = createTextModel('bar-bar', undefined, modeService.getLanguageIdentifier('fooLang'));
await provider.provideCompletionItems(model, new Position(1, 3), context)!.then(result => {
assert.equal(result.incomplete, undefined);
assert.equal(result.suggestions.length, 2);
assert.deepEqual(result.suggestions[0].label, {
assert.strictEqual(result.incomplete, undefined);
assert.strictEqual(result.suggestions.length, 2);
assert.deepStrictEqual(result.suggestions[0].label, {
name: 'bar',
type: 'barTest'
});
assert.equal(result.suggestions[0].insertText, 's1');
assert.equal((result.suggestions[0].range as any).insert.startColumn, 1);
assert.deepEqual(result.suggestions[1].label, {
assert.strictEqual(result.suggestions[0].insertText, 's1');
assert.strictEqual((result.suggestions[0].range as any).insert.startColumn, 1);
assert.deepStrictEqual(result.suggestions[1].label, {
name: 'bar-bar',
type: 'name'
});
assert.equal(result.suggestions[1].insertText, 's2');
assert.equal((result.suggestions[1].range as any).insert.startColumn, 1);
assert.strictEqual(result.suggestions[1].insertText, 's2');
assert.strictEqual((result.suggestions[1].range as any).insert.startColumn, 1);
});
await provider.provideCompletionItems(model, new Position(1, 5), context)!.then(result => {
assert.equal(result.incomplete, undefined);
assert.equal(result.suggestions.length, 1);
assert.deepEqual(result.suggestions[0].label, {
assert.strictEqual(result.incomplete, undefined);
assert.strictEqual(result.suggestions.length, 1);
assert.deepStrictEqual(result.suggestions[0].label, {
name: 'bar-bar',
type: 'name'
});
assert.equal(result.suggestions[0].insertText, 's2');
assert.equal((result.suggestions[0].range as any).insert.startColumn, 1);
assert.strictEqual(result.suggestions[0].insertText, 's2');
assert.strictEqual((result.suggestions[0].range as any).insert.startColumn, 1);
});
await provider.provideCompletionItems(model, new Position(1, 6), context)!.then(result => {
assert.equal(result.incomplete, undefined);
assert.equal(result.suggestions.length, 2);
assert.deepEqual(result.suggestions[0].label, {
assert.strictEqual(result.incomplete, undefined);
assert.strictEqual(result.suggestions.length, 2);
assert.deepStrictEqual(result.suggestions[0].label, {
name: 'bar',
type: 'barTest'
});
assert.equal(result.suggestions[0].insertText, 's1');
assert.equal((result.suggestions[0].range as any).insert.startColumn, 5);
assert.deepEqual(result.suggestions[1].label, {
assert.strictEqual(result.suggestions[0].insertText, 's1');
assert.strictEqual((result.suggestions[0].range as any).insert.startColumn, 5);
assert.deepStrictEqual(result.suggestions[1].label, {
name: 'bar-bar',
type: 'name'
});
assert.equal(result.suggestions[1].insertText, 's2');
assert.equal((result.suggestions[1].range as any).insert.startColumn, 1);
assert.strictEqual(result.suggestions[1].insertText, 's2');
assert.strictEqual((result.suggestions[1].range as any).insert.startColumn, 1);
});
});
@ -186,21 +186,21 @@ suite('SnippetsService', function () {
let model = createTextModel('\t<?php', undefined, modeService.getLanguageIdentifier('fooLang'));
return provider.provideCompletionItems(model, new Position(1, 7), context)!.then(result => {
assert.equal(result.suggestions.length, 1);
assert.strictEqual(result.suggestions.length, 1);
model.dispose();
model = createTextModel('\t<?', undefined, modeService.getLanguageIdentifier('fooLang'));
return provider.provideCompletionItems(model, new Position(1, 4), context)!;
}).then(result => {
assert.equal(result.suggestions.length, 1);
assert.equal((result.suggestions[0].range as any).insert.startColumn, 2);
assert.strictEqual(result.suggestions.length, 1);
assert.strictEqual((result.suggestions[0].range as any).insert.startColumn, 2);
model.dispose();
model = createTextModel('a<?', undefined, modeService.getLanguageIdentifier('fooLang'));
return provider.provideCompletionItems(model, new Position(1, 4), context)!;
}).then(result => {
assert.equal(result.suggestions.length, 1);
assert.equal((result.suggestions[0].range as any).insert.startColumn, 2);
assert.strictEqual(result.suggestions.length, 1);
assert.strictEqual((result.suggestions[0].range as any).insert.startColumn, 2);
model.dispose();
});
});
@ -221,10 +221,10 @@ suite('SnippetsService', function () {
let model = createTextModel('<head>\n\t\n>/head>', undefined, modeService.getLanguageIdentifier('fooLang'));
return provider.provideCompletionItems(model, new Position(1, 1), context)!.then(result => {
assert.equal(result.suggestions.length, 1);
assert.strictEqual(result.suggestions.length, 1);
return provider.provideCompletionItems(model, new Position(2, 2), context)!;
}).then(result => {
assert.equal(result.suggestions.length, 1);
assert.strictEqual(result.suggestions.length, 1);
});
});
@ -251,13 +251,13 @@ suite('SnippetsService', function () {
let model = createTextModel('', undefined, modeService.getLanguageIdentifier('fooLang'));
return provider.provideCompletionItems(model, new Position(1, 1), context)!.then(result => {
assert.equal(result.suggestions.length, 2);
assert.strictEqual(result.suggestions.length, 2);
let [first, second] = result.suggestions;
assert.deepEqual(first.label, {
assert.deepStrictEqual(first.label, {
name: 'first',
type: 'first'
});
assert.deepEqual(second.label, {
assert.deepStrictEqual(second.label, {
name: 'second',
type: 'second'
});
@ -279,13 +279,13 @@ suite('SnippetsService', function () {
let model = createTextModel('p-', undefined, modeService.getLanguageIdentifier('fooLang'));
let result = await provider.provideCompletionItems(model, new Position(1, 2), context)!;
assert.equal(result.suggestions.length, 1);
assert.strictEqual(result.suggestions.length, 1);
result = await provider.provideCompletionItems(model, new Position(1, 3), context)!;
assert.equal(result.suggestions.length, 1);
assert.strictEqual(result.suggestions.length, 1);
result = await provider.provideCompletionItems(model, new Position(1, 3), context)!;
assert.equal(result.suggestions.length, 1);
assert.strictEqual(result.suggestions.length, 1);
});
test('No snippets suggestion on long lines beyond character 100 #58807', async function () {
@ -304,7 +304,7 @@ suite('SnippetsService', function () {
let model = createTextModel('Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea b', undefined, modeService.getLanguageIdentifier('fooLang'));
let result = await provider.provideCompletionItems(model, new Position(1, 158), context)!;
assert.equal(result.suggestions.length, 1);
assert.strictEqual(result.suggestions.length, 1);
});
test('Type colon will trigger snippet #60746', async function () {
@ -323,7 +323,7 @@ suite('SnippetsService', function () {
let model = createTextModel(':', undefined, modeService.getLanguageIdentifier('fooLang'));
let result = await provider.provideCompletionItems(model, new Position(1, 2), context)!;
assert.equal(result.suggestions.length, 0);
assert.strictEqual(result.suggestions.length, 0);
});
test('substring of prefix can\'t trigger snippet #60737', async function () {
@ -342,8 +342,8 @@ suite('SnippetsService', function () {
let model = createTextModel('template', undefined, modeService.getLanguageIdentifier('fooLang'));
let result = await provider.provideCompletionItems(model, new Position(1, 9), context)!;
assert.equal(result.suggestions.length, 1);
assert.deepEqual(result.suggestions[0].label, {
assert.strictEqual(result.suggestions.length, 1);
assert.deepStrictEqual(result.suggestions[0].label, {
name: 'mytemplate',
type: 'mytemplate'
});
@ -365,7 +365,7 @@ suite('SnippetsService', function () {
let model = createTextModel('Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea Thisisaverylonglinegoingwithmore100bcharactersandthismakesintellisensebecomea b text_after_b', undefined, modeService.getLanguageIdentifier('fooLang'));
let result = await provider.provideCompletionItems(model, new Position(1, 158), context)!;
assert.equal(result.suggestions.length, 1);
assert.strictEqual(result.suggestions.length, 1);
});
test('issue #61296: VS code freezes when editing CSS file with emoji', async function () {
@ -388,7 +388,7 @@ suite('SnippetsService', function () {
let model = createTextModel('.🐷-a-b', undefined, modeService.getLanguageIdentifier('fooLang'));
let result = await provider.provideCompletionItems(model, new Position(1, 8), context)!;
assert.equal(result.suggestions.length, 1);
assert.strictEqual(result.suggestions.length, 1);
});
test('No snippets shown when triggering completions at whitespace on line that already has text #62335', async function () {
@ -407,7 +407,7 @@ suite('SnippetsService', function () {
let model = createTextModel('a ', undefined, modeService.getLanguageIdentifier('fooLang'));
let result = await provider.provideCompletionItems(model, new Position(1, 3), context)!;
assert.equal(result.suggestions.length, 1);
assert.strictEqual(result.suggestions.length, 1);
});
test('Snippet prefix with special chars and numbers does not work #62906', async function () {
@ -434,16 +434,16 @@ suite('SnippetsService', function () {
let model = createTextModel(' <', undefined, modeService.getLanguageIdentifier('fooLang'));
let result = await provider.provideCompletionItems(model, new Position(1, 3), context)!;
assert.equal(result.suggestions.length, 1);
assert.strictEqual(result.suggestions.length, 1);
let [first] = result.suggestions;
assert.equal((first.range as any).insert.startColumn, 2);
assert.strictEqual((first.range as any).insert.startColumn, 2);
model = createTextModel('1', undefined, modeService.getLanguageIdentifier('fooLang'));
result = await provider.provideCompletionItems(model, new Position(1, 2), context)!;
assert.equal(result.suggestions.length, 1);
assert.strictEqual(result.suggestions.length, 1);
[first] = result.suggestions;
assert.equal((first.range as any).insert.startColumn, 1);
assert.strictEqual((first.range as any).insert.startColumn, 1);
});
test('Snippet replace range', async function () {
@ -462,26 +462,26 @@ suite('SnippetsService', function () {
let model = createTextModel('not wordFoo bar', undefined, modeService.getLanguageIdentifier('fooLang'));
let result = await provider.provideCompletionItems(model, new Position(1, 3), context)!;
assert.equal(result.suggestions.length, 1);
assert.strictEqual(result.suggestions.length, 1);
let [first] = result.suggestions;
assert.equal((first.range as any).insert.endColumn, 3);
assert.equal((first.range as any).replace.endColumn, 9);
assert.strictEqual((first.range as any).insert.endColumn, 3);
assert.strictEqual((first.range as any).replace.endColumn, 9);
model = createTextModel('not woFoo bar', undefined, modeService.getLanguageIdentifier('fooLang'));
result = await provider.provideCompletionItems(model, new Position(1, 3), context)!;
assert.equal(result.suggestions.length, 1);
assert.strictEqual(result.suggestions.length, 1);
[first] = result.suggestions;
assert.equal((first.range as any).insert.endColumn, 3);
assert.equal((first.range as any).replace.endColumn, 3);
assert.strictEqual((first.range as any).insert.endColumn, 3);
assert.strictEqual((first.range as any).replace.endColumn, 3);
model = createTextModel('not word', undefined, modeService.getLanguageIdentifier('fooLang'));
result = await provider.provideCompletionItems(model, new Position(1, 1), context)!;
assert.equal(result.suggestions.length, 1);
assert.strictEqual(result.suggestions.length, 1);
[first] = result.suggestions;
assert.equal((first.range as any).insert.endColumn, 1);
assert.equal((first.range as any).replace.endColumn, 9);
assert.strictEqual((first.range as any).insert.endColumn, 1);
assert.strictEqual((first.range as any).replace.endColumn, 9);
});
test('Snippet replace-range incorrect #108894', async function () {
@ -501,10 +501,10 @@ suite('SnippetsService', function () {
let model = createTextModel('filler e KEEP ng filler', undefined, modeService.getLanguageIdentifier('fooLang'));
let result = await provider.provideCompletionItems(model, new Position(1, 9), context)!;
assert.equal(result.suggestions.length, 1);
assert.strictEqual(result.suggestions.length, 1);
let [first] = result.suggestions;
assert.equal((first.range as any).insert.endColumn, 9);
assert.equal((first.range as any).replace.endColumn, 9);
assert.strictEqual((first.range as any).insert.endColumn, 9);
assert.strictEqual((first.range as any).replace.endColumn, 9);
});
test('Snippet will replace auto-closing pair if specified in prefix', async function () {

View file

@ -997,6 +997,7 @@ export class TerminalInstance extends Disposable implements ITerminalInstance {
case 'pwsh.exe':
return WindowsShellType.PowerShell;
case 'bash.exe':
case 'git-cmd.exe':
return WindowsShellType.GitBash;
case 'wsl.exe':
case 'ubuntu.exe':

View file

@ -7,7 +7,7 @@ import { Iterable } from 'vs/base/common/iterator';
import { IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
import { TestRunState } from 'vs/workbench/api/common/extHostTypes';
import { ITestTreeElement } from 'vs/workbench/contrib/testing/browser/explorerProjections';
import { maxPriority, statePriority } from 'vs/workbench/contrib/testing/browser/testExplorerTree';
import { maxPriority, statePriority } from 'vs/workbench/contrib/testing/common/testingStates';
import { InternalTestItem, TestIdWithProvider } from 'vs/workbench/contrib/testing/common/testCollection';
/**

View file

@ -7,11 +7,8 @@ import { IIdentityProvider } from 'vs/base/browser/ui/list/list';
import { ICompressedTreeElement } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree';
import { ITreeElement } from 'vs/base/browser/ui/tree/tree';
import { TestRunState } from 'vs/workbench/api/common/extHostTypes';
import { ITestTreeElement } from 'vs/workbench/contrib/testing/browser/explorerProjections';
export const isRunningState = (s: TestRunState) => s === TestRunState.Queued || s === TestRunState.Running;
export const testIdentityProvider: IIdentityProvider<ITestTreeElement> = {
getId(element) {
return element.treeId;

View file

@ -14,10 +14,10 @@ import { Location as ModeLocation } from 'vs/editor/common/modes';
import { TestRunState } from 'vs/workbench/api/common/extHostTypes';
import { ITestTreeElement, ITestTreeProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections';
import { locationsEqual, TestLocationStore } from 'vs/workbench/contrib/testing/browser/explorerProjections/locationStore';
import { isRunningState, NodeChangeList, NodeRenderDirective, NodeRenderFn, peersHaveChildren } from 'vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper';
import { NodeChangeList, NodeRenderDirective, NodeRenderFn, peersHaveChildren } from 'vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper';
import { StateElement } from 'vs/workbench/contrib/testing/browser/explorerProjections/stateNodes';
import { statesInOrder } from 'vs/workbench/contrib/testing/browser/testExplorerTree';
import { AbstractIncrementalTestCollection, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, TestIdWithProvider, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
import { isRunningState, statesInOrder } from 'vs/workbench/contrib/testing/common/testingStates';
import { TestSubscriptionListener } from 'vs/workbench/contrib/testing/common/workspaceTestCollectionService';
interface IStatusTestItem extends IncrementalTestCollectionItem {

View file

@ -15,9 +15,10 @@ import { TestRunState } from 'vs/workbench/api/common/extHostTypes';
import { ITestTreeElement, ITestTreeProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections';
import { ListElementType } from 'vs/workbench/contrib/testing/browser/explorerProjections/hierarchalByName';
import { locationsEqual, TestLocationStore } from 'vs/workbench/contrib/testing/browser/explorerProjections/locationStore';
import { isRunningState, NodeChangeList, NodeRenderFn } from 'vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper';
import { NodeChangeList, NodeRenderFn } from 'vs/workbench/contrib/testing/browser/explorerProjections/nodeHelper';
import { StateElement } from 'vs/workbench/contrib/testing/browser/explorerProjections/stateNodes';
import { AbstractIncrementalTestCollection, IncrementalChangeCollector, IncrementalTestCollectionItem, InternalTestItem, TestIdWithProvider, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
import { isRunningState } from 'vs/workbench/contrib/testing/common/testingStates';
import { TestSubscriptionListener } from 'vs/workbench/contrib/testing/common/workspaceTestCollectionService';
class ListTestStateElement implements ITestTreeElement {

View file

@ -11,7 +11,8 @@ import { TestRunState } from 'vs/workbench/api/common/extHostTypes';
import { testStatesToIconColors } from 'vs/workbench/contrib/testing/browser/theme';
export const testingViewIcon = registerIcon('test-view-icon', Codicon.beaker, localize('testViewIcon', 'View icon of the test view.'));
export const testingRunIcon = registerIcon('testing-run-icon', Codicon.debugStart, localize('testingRunIcon', 'Icon of the "run test" action.'));
export const testingRunIcon = registerIcon('testing-run-icon', Codicon.run, localize('testingRunIcon', 'Icon of the "run test" action.'));
export const testingRunAllIcon = registerIcon('testing-run-icon', Codicon.runAll, localize('testingRunAllIcon', 'Icon of the "run all tests" action.'));
export const testingDebugIcon = registerIcon('testing-debug-icon', Codicon.debugAlt, localize('testingDebugIcon', 'Icon of the "debug test" action.'));
export const testingCancelIcon = registerIcon('testing-cancel-icon', Codicon.close, localize('testingCancelIcon', 'Icon to cancel ongoing test runs.'));

View file

@ -46,6 +46,10 @@
margin-right: 0.25em;
}
.test-explorer .test-explorer-messages {
padding: 0 12px 8px;
}
.monaco-workbench
.test-explorer
.monaco-action-bar
@ -63,11 +67,11 @@
}
/** -- peek */
.monaco-editor .zone-widget .zone-widget-container.peekview-widget {
.monaco-editor .zone-widget.test-output-peek .zone-widget-container.peekview-widget {
border-top-width: 2px;
border-bottom-width: 2px;
}
/* .monaco-editor .zone-widget .zone-widget-container.peekview-widget .peekview-title .filename {
height: 22px;
} */
.monaco-editor .zone-widget.test-output-peek .zone-widget-container.peekview-widget .peekview-title .filename {
max-height: 29px;
}

View file

@ -226,7 +226,7 @@ export class RunAllAction extends RunOrDebugAllAllAction {
super(
'testing.runAll',
localize('runAllTests', 'Run All Tests'),
icons.testingDebugIcon,
icons.testingRunAllIcon,
false,
localize('noTestProvider', 'No tests found in this workspace. You may need to install a test provider extension'),
);

View file

@ -33,8 +33,10 @@ import { TestService } from 'vs/workbench/contrib/testing/common/testServiceImpl
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle';
import * as Action from './testExplorerActions';
import { ITestResultService, TestResultService } from 'vs/workbench/contrib/testing/common/testResultService';
registerSingleton(ITestService, TestService);
registerSingleton(ITestResultService, TestResultService);
registerSingleton(IWorkspaceTestCollectionService, WorkspaceTestCollectionService);
const viewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({

View file

@ -10,12 +10,14 @@ import { IListAccessibilityProvider } from 'vs/base/browser/ui/list/listWidget';
import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
import { ObjectTree } from 'vs/base/browser/ui/tree/objectTree';
import { ITreeEvent, ITreeFilter, ITreeNode, ITreeRenderer, ITreeSorter, TreeFilterResult, TreeVisibility } from 'vs/base/browser/ui/tree/tree';
import { DeferredPromise } from 'vs/base/common/async';
import { Color, RGBA } from 'vs/base/common/color';
import { throttle } from 'vs/base/common/decorators';
import { Event } from 'vs/base/common/event';
import { FuzzyScore } from 'vs/base/common/filters';
import { splitGlobAware } from 'vs/base/common/glob';
import { Iterable } from 'vs/base/common/iterator';
import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
import { Disposable, DisposableStore, MutableDisposable, toDisposable } from 'vs/base/common/lifecycle';
import 'vs/css!./media/testing';
import { ICodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
@ -30,10 +32,11 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { WorkbenchObjectTree } from 'vs/platform/list/browser/listService';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { IProgressService } from 'vs/platform/progress/common/progress';
import { IProgress, IProgressService, IProgressStep } from 'vs/platform/progress/common/progress';
import { IStorageService, StorageScope, StorageTarget } from 'vs/platform/storage/common/storage';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { foreground } from 'vs/platform/theme/common/colorRegistry';
import { IThemeService, registerThemingParticipant, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { TestRunState } from 'vs/workbench/api/common/extHostTypes';
import { IResourceLabel, IResourceLabelOptions, IResourceLabelProps, ResourceLabels } from 'vs/workbench/browser/labels';
import { ViewPane } from 'vs/workbench/browser/parts/views/viewPane';
@ -47,13 +50,15 @@ import { StateByLocationProjection } from 'vs/workbench/contrib/testing/browser/
import { StateByNameProjection } from 'vs/workbench/contrib/testing/browser/explorerProjections/stateByName';
import { StateElement } from 'vs/workbench/contrib/testing/browser/explorerProjections/stateNodes';
import { testingStatesToIcons } from 'vs/workbench/contrib/testing/browser/icons';
import { cmpPriority, isFailedState } from 'vs/workbench/contrib/testing/browser/testExplorerTree';
import { TestingExplorerFilter, TestingFilterState } from 'vs/workbench/contrib/testing/browser/testingExplorerFilter';
import { TestingOutputPeekController } from 'vs/workbench/contrib/testing/browser/testingOutputPeek';
import { TestExplorerViewGrouping, TestExplorerViewMode } from 'vs/workbench/contrib/testing/common/constants';
import { IWorkspaceTestCollectionService, TestSubscriptionListener } from 'vs/workbench/contrib/testing/common/workspaceTestCollectionService';
import { TestExplorerViewGrouping, TestExplorerViewMode, Testing } from 'vs/workbench/contrib/testing/common/constants';
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
import { cmpPriority, isFailedState } from 'vs/workbench/contrib/testing/common/testingStates';
import { ITestResultService, sumCounts, TestStateCount } from 'vs/workbench/contrib/testing/common/testResultService';
import { ITestService } from 'vs/workbench/contrib/testing/common/testService';
import { IWorkspaceTestCollectionService, TestSubscriptionListener } from 'vs/workbench/contrib/testing/common/workspaceTestCollectionService';
import { IActivityService, NumberBadge, ProgressBadge } from 'vs/workbench/services/activity/common/activity';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { DebugAction, RunAction } from './testExplorerActions';
@ -101,12 +106,13 @@ export class TestingExplorerView extends ViewPane {
this.filter = this.instantiationService.createInstance(TestingExplorerFilter, this.container, this.filterState);
this._register(this.filter);
const messagesContainer = dom.append(this.container, dom.$('.test-explorer-messages'));
this._register(this.instantiationService.createInstance(TestRunProgress, messagesContainer, this.getProgressLocation()));
const listContainer = dom.append(this.container, dom.$('.test-explorer-tree'));
this.viewModel = this.instantiationService.createInstance(TestingExplorerViewModel, listContainer, this.onDidChangeBodyVisibility, this.currentSubscription, this.filterState);
this._register(this.viewModel);
this.getProgressIndicator().show(true);
this._register(this.onDidChangeBodyVisibility(visible => {
if (!visible && this.currentSubscription) {
this.currentSubscription.dispose();
@ -127,7 +133,7 @@ export class TestingExplorerView extends ViewPane {
this.filter.saveState();
}
private updateProgressIndicator(busy: number) {
private updateDiscoveryProgress(busy: number) {
if (!busy && this.finishDiscovery) {
this.finishDiscovery();
this.finishDiscovery = undefined;
@ -148,7 +154,7 @@ export class TestingExplorerView extends ViewPane {
private createSubscription() {
const handle = this.testCollection.subscribeToWorkspaceTests();
handle.subscription.onBusyProvidersChange(() => this.updateProgressIndicator(handle.subscription.busyProviders));
handle.subscription.onBusyProvidersChange(() => this.updateDiscoveryProgress(handle.subscription.busyProviders));
return handle;
}
}
@ -228,6 +234,7 @@ export class TestingExplorerViewModel extends Disposable {
instantiationService.createInstance(TestsRenderer, labels)
],
{
simpleKeyboardNavigation: true,
identityProvider: instantiationService.createInstance(IdentityProvider),
hideTwistiesOfChildlessElements: true,
sorter: instantiationService.createInstance(TreeSorter),
@ -453,6 +460,12 @@ class CodeEditorTracker {
}
}
const enum FilterResult {
Include,
Exclude,
Inherit,
}
class TestsFilter implements ITreeFilter<ITestTreeElement> {
private filters: [include: boolean, value: string][] | undefined;
@ -482,25 +495,32 @@ class TestsFilter implements ITreeFilter<ITestTreeElement> {
}
public filter(element: ITestTreeElement): TreeFilterResult<void> {
if (this.testFilterText(element.label)) {
return TreeVisibility.Visible;
for (let e: ITestTreeElement | null = element; e; e = e.parentItem) {
switch (this.testFilterText(e.label)) {
case FilterResult.Exclude:
return TreeVisibility.Hidden;
case FilterResult.Include:
return TreeVisibility.Visible;
case FilterResult.Inherit:
// continue to parent
}
}
return Iterable.isEmpty(element.children) ? TreeVisibility.Hidden : TreeVisibility.Recurse;
return TreeVisibility.Recurse;
}
private testFilterText(data: string) {
if (!this.filters) {
return true;
return FilterResult.Include;
}
// start as included if the first glob is a negation
let included = this.filters[0][0] === false;
let included = this.filters[0][0] === false ? FilterResult.Exclude : FilterResult.Inherit;
data = data.toLowerCase();
for (const [include, filter] of this.filters) {
if (data.includes(filter)) {
included = include;
included = include ? FilterResult.Include : FilterResult.Exclude;
}
}
@ -616,9 +636,13 @@ class TestsRenderer implements ITreeRenderer<ITestTreeElement, FuzzyScore, TestT
label.resource = test.item.location.uri;
}
options.title = 'hover title';
options.fileKind = FileKind.FILE;
let title = element.label;
for (let p = element.parentItem; p; p = p.parentItem) {
title = `${p.label}, ${title}`;
}
options.title = title;
options.fileKind = FileKind.FILE;
label.description = element.description;
} else {
options.fileKind = FileKind.ROOT_FOLDER;
@ -647,3 +671,134 @@ class TestsRenderer implements ITreeRenderer<ITestTreeElement, FuzzyScore, TestT
templateData.actionBar.dispose();
}
}
const collectCounts = (count: TestStateCount) => {
const failed = count[TestRunState.Errored] + count[TestRunState.Failed];
const passed = count[TestRunState.Passed];
const skipped = count[TestRunState.Skipped];
return {
passed,
failed,
runSoFar: passed + failed,
totalWillBeRun: passed + failed + count[TestRunState.Queued] + count[TestRunState.Running],
skipped,
};
};
const getProgressText = ({ passed, runSoFar, skipped }: { passed: number, runSoFar: number, skipped: number }) => {
const percent = (passed / runSoFar * 100).toFixed(0);
if (skipped === 0) {
return localize('testProgress', '{0}/{1} tests passed ({2}%)', passed, runSoFar, percent);
} else {
return localize('testProgressWithSkip', '{0}/{1} tests passed ({2}%, {3} skipped)', passed, runSoFar, percent, skipped);
}
};
class TestRunProgress {
private current?: { update: IProgress<IProgressStep>; deferred: DeferredPromise<void> };
private badge = new MutableDisposable();
private readonly resultLister = this.resultService.onNewTestResult(result => {
this.updateProgress();
this.updateBadge();
result.onChange(this.throttledProgressUpdate, this);
result.onComplete(() => {
this.throttledProgressUpdate();
this.updateBadge();
});
});
constructor(
private readonly messagesContainer: HTMLElement,
private readonly location: string,
@IProgressService private readonly progress: IProgressService,
@ITestResultService private readonly resultService: ITestResultService,
@IActivityService private readonly activityService: IActivityService,
) {
}
public dispose() {
this.resultLister.dispose();
this.current?.deferred.complete();
this.badge.dispose();
}
@throttle(200)
private throttledProgressUpdate() {
this.updateProgress();
}
private updateProgress() {
const running = this.resultService.results.filter(r => !r.isComplete);
if (!running.length) {
this.setIdleText(this.resultService.results[0]?.counts);
this.current?.deferred.complete();
this.current = undefined;
} else if (!this.current) {
this.progress.withProgress({ location: this.location, total: 100 }, update => {
this.current = { update, deferred: new DeferredPromise() };
this.updateProgress();
return this.current.deferred.p;
});
} else {
const counts = sumCounts(running.map(r => r.counts));
this.setRunningText(counts);
const { runSoFar, totalWillBeRun } = collectCounts(counts);
this.current.update.report({ increment: runSoFar, total: totalWillBeRun });
}
}
private setRunningText(counts: TestStateCount) {
this.messagesContainer.dataset.state = 'running';
const collected = collectCounts(counts);
if (collected.runSoFar === 0) {
this.messagesContainer.innerText = localize('testResultStarting', 'Test run is starting...');
} else {
this.messagesContainer.innerText = getProgressText(collected);
}
}
private setIdleText(lastCount?: TestStateCount) {
if (!lastCount) {
this.messagesContainer.innerText = '';
} else {
const collected = collectCounts(lastCount);
this.messagesContainer.dataset.state = collected.failed ? 'failed' : 'running';
this.messagesContainer.innerText = getProgressText(collected);
}
}
private updateBadge() {
this.badge.value = undefined;
const result = this.resultService.results[0]; // currently running, or last run
if (!result) {
return;
}
if (!result.isComplete) {
const badge = new ProgressBadge(() => localize('testBadgeRunning', 'Test run in progress'));
this.badge.value = this.activityService.showViewActivity(Testing.ExplorerViewId, { badge, clazz: 'progress-badge' });
return;
}
const failures = result.counts[TestRunState.Failed] + result.counts[TestRunState.Errored];
if (failures === 0) {
return;
}
const badge = new NumberBadge(failures, () => localize('testBadgeFailures', '{0} tests failed', failures));
this.badge.value = this.activityService.showViewActivity(Testing.ExplorerViewId, { badge });
}
}
registerThemingParticipant((theme, collector) => {
if (theme.type === 'dark') {
const foregroundColor = theme.getColor(foreground);
if (foregroundColor) {
const fgWithOpacity = new Color(new RGBA(foregroundColor.rgba.r, foregroundColor.rgba.g, foregroundColor.rgba.b, 0.65));
collector.addRule(`.test-explorer .test-explorer-messages { color: ${fgWithOpacity}; }`);
}
}
});

View file

@ -102,7 +102,7 @@ export class TestingOutputPeek extends PeekViewWidget {
@IInstantiationService protected readonly instantiationService: IInstantiationService,
@ITextModelService private readonly modelService: ITextModelService,
) {
super(editor, { showFrame: false, showArrow: true, isResizeable: true, isAccessible: true }, instantiationService);
super(editor, { showFrame: false, showArrow: true, isResizeable: true, isAccessible: true, className: 'test-output-peek' }, instantiationService);
this._disposables.add(themeService.onDidColorThemeChange(this.applyTheme, this));
this.applyTheme(themeService.getColorTheme());

View file

@ -188,6 +188,10 @@ export class SingleUseTestCollection implements IDisposable {
@throttle(200)
protected throttleSendDiff() {
this.flushDiff();
}
public flushDiff() {
const diff = this.collectDiff();
if (diff.length) {
this.publishDiff(diff);

View file

@ -0,0 +1,229 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Emitter, Event } from 'vs/base/common/event';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { TestRunState } from 'vs/workbench/api/common/extHostTypes';
import { IncrementalTestCollectionItem, InternalTestItem, TestIdWithProvider } from 'vs/workbench/contrib/testing/common/testCollection';
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
import { isRunningState, statesInOrder } from 'vs/workbench/contrib/testing/common/testingStates';
import { IMainThreadTestCollection } from 'vs/workbench/contrib/testing/common/testService';
export type TestStateCount = { [K in TestRunState]: number };
const makeEmptyCounts = () => {
const o: Partial<TestStateCount> = {};
for (const state of statesInOrder) {
o[state] = 0;
}
return o as TestStateCount;
};
export const sumCounts = (counts: TestStateCount[]) => {
const total = makeEmptyCounts();
for (const count of counts) {
for (const state of statesInOrder) {
total[state] += count[state];
}
}
return total;
};
const makeNode = (
collection: IMainThreadTestCollection,
test: IncrementalTestCollectionItem,
): TestResultItem => {
const mapped: TestResultItem = { ...test, children: [] };
for (const childId of test.children) {
const child = collection.getNodeById(childId);
if (child) {
mapped.children.push(makeNode(collection, child));
}
}
return mapped;
};
export interface TestResultItem extends InternalTestItem {
children: TestResultItem[]
}
/**
* Results of a test. These are created when the test initially started running
* and marked as "complete" when the run finishes.
*/
export class TestResult {
/**
* Creates a new TestResult, pulling tests from the associated list
* of collections.
*/
public static from(
collections: ReadonlyArray<IMainThreadTestCollection>,
tests: ReadonlyArray<TestIdWithProvider>,
) {
const mapped: TestResultItem[] = [];
for (const test of tests) {
for (const collection of collections) {
const node = collection.getNodeById(test.testId);
if (node) {
mapped.push(makeNode(collection, node));
break;
}
}
}
return new TestResult(mapped);
}
private completeEmitter = new Emitter<void>();
private changeEmitter = new Emitter<void>();
private _complete = false;
private _cachedCounts?: { [K in TestRunState]: number };
public onChange = this.changeEmitter.event;
public onComplete = this.completeEmitter.event;
/**
* Gets whether the test run has finished.
*/
public get isComplete() {
return this._complete;
}
/**
* Gets a count of tests in each state.
*/
public get counts() {
if (this._cachedCounts) {
return this._cachedCounts;
}
const counts = makeEmptyCounts();
this.forEachTest(({ item }) => {
counts[item.state.runState]++;
});
if (this._complete) {
this._cachedCounts = counts;
}
return counts;
}
constructor(public readonly tests: TestResultItem[]) { }
/**
* Notifies the service that all tests are complete.
*/
public markComplete() {
if (this._complete) {
throw new Error('cannot complete a test result multiple times');
}
// shallow clone test items to 'disconnect' them from the underlying
// connection and stop state changes. Also, marked any still-running
// tests as skipped.
this.forEachTest(test => {
test.item = { ...test.item };
if (isRunningState(test.item.state.runState)) {
test.item.state = { ...test.item.state, runState: TestRunState.Skipped };
}
});
this._complete = true;
this.completeEmitter.fire();
}
/**
* Fires the 'change' event, should be called by the runner.
*/
public notifyChanged() {
this.changeEmitter.fire();
}
private forEachTest(fn: (test: TestResultItem) => void) {
const queue = [this.tests];
while (queue.length) {
for (const test of queue.pop()!) {
fn(test);
queue.push(test.children);
}
}
}
}
export interface ITestResultService {
readonly _serviceBrand: undefined;
/**
* List of test results. Currently running tests are always at the top.
*/
readonly results: TestResult[];
/**
* Fired after a new event is added to the 'active' array.
*/
readonly onNewTestResult: Event<TestResult>;
/**
* Adds a new test result to the collection.
*/
push(result: TestResult): TestResult;
}
export const ITestResultService = createDecorator<ITestResultService>('testResultService');
const RETAIN_LAST_RESULTS = 10;
export class TestResultService implements ITestResultService {
declare _serviceBrand: undefined;
private newResultEmitter = new Emitter<TestResult>();
/**
* @inheritdoc
*/
public results: TestResult[] = [];
/**
* @inheritdoc
*/
public readonly onNewTestResult = this.newResultEmitter.event;
private readonly isRunning: IContextKey<boolean>;
constructor(@IContextKeyService contextKeyService: IContextKeyService) {
this.isRunning = TestingContextKeys.isRunning.bindTo(contextKeyService);
}
/**
* @inheritdoc
*/
public push(result: TestResult): TestResult {
this.results.unshift(result);
if (this.results.length > RETAIN_LAST_RESULTS) {
this.results.pop();
}
result.onComplete(this.onComplete, this);
this.isRunning.set(true);
this.newResultEmitter.fire(result);
return result;
}
private onComplete() {
// move the complete test run down behind any still-running ones
for (let i = 0; i < this.results.length - 2; i++) {
if (this.results[i].isComplete && !this.results[i + 1].isComplete) {
[this.results[i], this.results[i + 1]] = [this.results[i + 1], this.results[i]];
}
}
this.isRunning.set(!this.results[0]?.isComplete);
}
}

View file

@ -75,10 +75,7 @@ export interface ITestService {
readonly onDidChangeProviders: Event<{ delta: number; }>;
readonly providers: number;
readonly subscriptions: ReadonlyArray<{ resource: ExtHostTestingResource, uri: URI; }>;
readonly testRuns: Iterable<RunTestsRequest>;
readonly onTestRunStarted: Event<RunTestsRequest>;
readonly onTestRunCompleted: Event<{ req: RunTestsRequest, result: RunTestsResult; }>;
registerTestController(id: string, controller: MainTestController): void;
unregisterTestController(id: string): void;

View file

@ -16,6 +16,7 @@ import { INotificationService } from 'vs/platform/notification/common/notificati
import { ExtHostTestingResource } from 'vs/workbench/api/common/extHost.protocol';
import { AbstractIncrementalTestCollection, collectTestResults, EMPTY_TEST_RESULT, getTestSubscriptionKey, IncrementalTestCollectionItem, InternalTestItem, RunTestsRequest, RunTestsResult, TestDiffOpType, TestIdWithProvider, TestsDiff } from 'vs/workbench/contrib/testing/common/testCollection';
import { TestingContextKeys } from 'vs/workbench/contrib/testing/common/testingContextKeys';
import { ITestResultService, TestResult } from 'vs/workbench/contrib/testing/common/testResultService';
import { IMainThreadTestCollection, ITestService, MainTestController, TestDiffListener } from 'vs/workbench/contrib/testing/common/testService';
type TestLocationIdent = { resource: ExtHostTestingResource, uri: URI };
@ -39,7 +40,6 @@ export class TestService extends Disposable implements ITestService {
private readonly busyStateChangeEmitter = new Emitter<TestLocationIdent & { busy: boolean }>();
private readonly changeProvidersEmitter = new Emitter<{ delta: number }>();
private readonly providerCount: IContextKey<number>;
private readonly isRunning: IContextKey<boolean>;
private readonly runStartedEmitter = new Emitter<RunTestsRequest>();
private readonly runCompletedEmitter = new Emitter<{ req: RunTestsRequest, result: RunTestsResult }>();
private readonly runningTests = new Map<RunTestsRequest, CancellationTokenSource>();
@ -48,10 +48,9 @@ export class TestService extends Disposable implements ITestService {
public readonly onTestRunStarted = this.runStartedEmitter.event;
public readonly onTestRunCompleted = this.runCompletedEmitter.event;
constructor(@IContextKeyService contextKeyService: IContextKeyService, @INotificationService private readonly notificationService: INotificationService) {
constructor(@IContextKeyService contextKeyService: IContextKeyService, @INotificationService private readonly notificationService: INotificationService, @ITestResultService private readonly testResults: ITestResultService) {
super();
this.providerCount = TestingContextKeys.providerCount.bindTo(contextKeyService);
this.isRunning = TestingContextKeys.isRunning.bindTo(contextKeyService);
}
/**
@ -128,32 +127,37 @@ export class TestService extends Disposable implements ITestService {
* @inheritdoc
*/
public async runTests(req: RunTestsRequest, token = CancellationToken.None): Promise<RunTestsResult> {
const tests = groupBy(req.tests, (a, b) => a.providerId === b.providerId ? 0 : 1);
const cancelSource = new CancellationTokenSource(token);
const requests = tests.map(group => {
const providerId = group[0].providerId;
const controller = this.testControllers.get(providerId);
return controller?.runTests({ providerId, debug: req.debug, ids: group.map(t => t.testId) }, cancelSource.token).catch(err => {
this.notificationService.error(localize('testError', 'An error occurred attempting to run tests: {0}', err.message));
let result: TestResult | undefined;
const subscriptions = [...this.testSubscriptions.values()]
.filter(v => req.tests.some(t => v.collection.getNodeById(t.testId)))
.map(s => this.subscribeToDiffs(s.ident.resource, s.ident.uri, () => result?.notifyChanged()));
result = this.testResults.push(TestResult.from(subscriptions.map(s => s.collection), req.tests));
try {
const tests = groupBy(req.tests, (a, b) => a.providerId === b.providerId ? 0 : 1);
const cancelSource = new CancellationTokenSource(token);
const requests = tests.map(group => {
const providerId = group[0].providerId;
const controller = this.testControllers.get(providerId);
return controller?.runTests({ providerId, debug: req.debug, ids: group.map(t => t.testId) }, cancelSource.token).catch(err => {
this.notificationService.error(localize('testError', 'An error occurred attempting to run tests: {0}', err.message));
return EMPTY_TEST_RESULT;
});
}).filter(isDefined);
if (requests.length === 0) {
return EMPTY_TEST_RESULT;
});
}).filter(isDefined);
}
if (requests.length === 0) {
return EMPTY_TEST_RESULT;
this.runningTests.set(req, cancelSource);
const result = collectTestResults(await Promise.all(requests));
this.runningTests.delete(req);
return result;
} finally {
subscriptions.forEach(s => s.dispose());
result.markComplete();
}
this.runningTests.set(req, cancelSource);
this.runStartedEmitter.fire(req);
this.isRunning.set(true);
const result = collectTestResults(await Promise.all(requests));
this.runningTests.delete(req);
this.runCompletedEmitter.fire({ req, result });
this.isRunning.set(this.runningTests.size > 0);
return result;
}
/**
@ -223,7 +227,7 @@ export class TestService extends Disposable implements ITestService {
const sub = this.testSubscriptions.get(getTestSubscriptionKey(resource, URI.revive(uri)));
if (sub) {
sub.collection.apply(diff);
console.log('accept', sub.collection, diff);
// console.log('accept', sub.collection, diff);
sub.onDiff.fire(diff);
}
}

View file

@ -38,3 +38,5 @@ export const cmpPriority = (a: TestRunState, b: TestRunState) => statePriority[b
export const maxPriority = (a: TestRunState, b: TestRunState) => statePriority[a] > statePriority[b] ? a : b;
export const statesInOrder = Object.keys(statePriority).map(s => Number(s) as TestRunState).sort(cmpPriority);
export const isRunningState = (s: TestRunState) => s === TestRunState.Queued || s === TestRunState.Running;

View file

@ -79,10 +79,10 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
getEnablementState(extension: IExtension): EnablementState {
if (this.extensionBisectService.isDisabledByBisect(extension)) {
return EnablementState.DisabledByEnvironemt;
return EnablementState.DisabledByEnvironment;
}
if (this._isDisabledInEnv(extension)) {
return EnablementState.DisabledByEnvironemt;
return EnablementState.DisabledByEnvironment;
}
if (this._isDisabledByExtensionKind(extension)) {
return EnablementState.DisabledByExtensionKind;
@ -97,7 +97,7 @@ export class ExtensionEnablementService extends Disposable implements IWorkbench
return false;
}
const enablementState = this.getEnablementState(extension);
if (enablementState === EnablementState.DisabledByEnvironemt || enablementState === EnablementState.DisabledByExtensionKind) {
if (enablementState === EnablementState.DisabledByEnvironment || enablementState === EnablementState.DisabledByExtensionKind) {
return false;
}
return true;

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